/*
 * Stilltype.
 * Literata on near-black (or warm paper), one green, and a speed graph on the
 * card of every finished run. Tokens first, then shell, then screens. See DESIGN.md.
 * Motion rule: fluid. Motion may live on the typing surface mid-run when it helps
 * someone follow the run; it scales to zero under reduced motion.
 */

@font-face { font-family: "Geist Mono"; src: url("/src/fonts/geist-mono-400.ttf") format("truetype"); font-weight: 400; font-display: swap; }
@font-face { font-family: "Geist Mono"; src: url("/src/fonts/geist-mono-500.ttf") format("truetype"); font-weight: 500; font-display: swap; }
@font-face { font-family: "Literata"; src: url("/src/fonts/literata-roman.woff2") format("woff2"); font-weight: 300 700; font-style: normal; font-display: swap; }
@font-face { font-family: "Literata"; src: url("/src/fonts/literata-italic.woff2") format("woff2"); font-weight: 300 700; font-style: italic; font-display: swap; }
/* Typing and app font choices (src/fonts.js). A face loads only once text uses it. */
@font-face { font-family: "Source Serif 4"; src: url("/src/fonts/source-serif-roman.woff2") format("woff2"); font-weight: 200 900; font-style: normal; font-display: swap; }
@font-face { font-family: "Source Serif 4"; src: url("/src/fonts/source-serif-italic.woff2") format("woff2"); font-weight: 200 900; font-style: italic; font-display: swap; }
@font-face { font-family: "Inter"; src: url("/src/fonts/inter-roman.woff2") format("woff2"); font-weight: 100 900; font-style: normal; font-display: swap; }
@font-face { font-family: "Inter"; src: url("/src/fonts/inter-italic.woff2") format("woff2"); font-weight: 100 900; font-style: italic; font-display: swap; }
@font-face { font-family: "JetBrains Mono"; src: url("/src/fonts/jetbrains-mono-400.woff2") format("woff2"); font-weight: 400; font-display: swap; }
@font-face { font-family: "IBM Plex Mono"; src: url("/src/fonts/ibm-plex-mono-400.woff2") format("woff2"); font-weight: 400; font-display: swap; }
@font-face { font-family: "Roboto Mono"; src: url("/src/fonts/roboto-mono-400.woff2") format("woff2"); font-weight: 400; font-display: swap; }
@font-face { font-family: "Fira Code"; src: url("/src/fonts/fira-code-400.woff2") format("woff2"); font-weight: 400; font-display: swap; }

/* ── Tokens ─────────────────────────────────────────────────────────────── */

:root {
  --font-literata: "Literata", "Iowan Old Style", "Palatino Linotype", Palatino, "Book Antiqua", Georgia, serif;
  --font-source-serif: "Source Serif 4", Georgia, serif;
  --font-inter: "Inter", system-ui, sans-serif;
  /* The passage, compose field and replay; --font-app is everything else readable. */
  --font-typing: var(--font-literata);
  --font-app: var(--font-literata);
  /* Literata's baseline below the middle of its line; src/app.js measures the chosen typing font's.
     Every caret's top stands 0.985em above it, so carets keep their place against the letters in any font. */
  --typing-baseline: 0.435em;
  --font-mono: "Geist Mono", ui-monospace, "SFMono-Regular", Menlo, Consolas, monospace;

  --text-fine: 12px;
  --text-mono: 12px;
  --text-label: 14px;
  --text-small: 15px;
  --text-field: 16px;
  --text-body: 17px;
  --text-lead: 20px;
  --text-title: 30px;
  --text-display: 48px;
  --text-numeral: 132px;
  --text-numeral-mobile: 112px;
  --text-numeral-narrow: 80px;

  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-6: 24px;
  --space-8: 32px;
  --space-10: 40px;
  --space-14: 56px;
  --space-16: 64px;

  --radius-cell: 2px;
  --radius-sm: 6px;
  --radius: 10px;
  --radius-control: 12px;
  --radius-lg: 16px;
  --radius-xl: 22px;
  --radius-full: 999px;

  --control: 44px;
  --disabled-opacity: 0.38;
  --settings-height: 704px;
  --settings-body-height: 520px;
  --settings-sidebar: 216px;
  --settings-label: 208px;
  --settings-row: 72px;
  --settings-selection-line: 2px;
  --settings-title-line: 40px;
  --settings-option-line: 18px;
  --settings-preview-line: 56px;
  --settings-preview-small: 22px;
  --settings-preview-medium: var(--text-title);
  --settings-preview-large: 36px;
  --sound-range-width: 104px;
  --sound-range-height: 36px;
  --heat-key: 52px;
  --heat-gap: 6px;
  --content-max: 1080px;
  /* The run card: its width, the speed trace's height (time labels included), and the narrowest the
     conditions line gets beside the actions before it drops under them. */
  --result-card: 900px;
  --result-trace: 144px;
  --result-conditions: 240px;
  /* Open practice from the rail to the shortcut row at the default text size: the practice screen's anchor. */
  --practice-block: 510px;
  /* Progress charts: about 2.5 wide to 1 tall, between these heights. */
  --chart-height: 400px;
  --chart-height-min: 240px;
  /* Background image (src/wallpaper.js) over the top of the page, faded out at the countdown, dimmed while a
     run is in progress: dither puts one dot every pitch in a tint of the accent; gradient averages the photo
     over cells this size; text the picture would take below the contrast floor gets an outline this wide in the
     ground colour (half of the stroke shows, under the letter). Blur, opacity and reach are the person's
     (settings → appearance), set on the layer. */
  --wallpaper-pitch: 3px;
  --wallpaper-dot: 2px;
  --wallpaper-cell: 160px;
  --wallpaper-reach: 0;
  --wallpaper-tint: 35%;
  --wallpaper-halo: 2px;
  --wallpaper-dim: 0.25;
  --wallpaper-blur: 0px;
  --wallpaper-opacity: 1;

  /* Motion: every duration scales from one place and drops to zero for reduced motion. */
  --motion-scale: 1;
  --dur-micro: calc(80ms * var(--motion-scale));
  --dur-quick: calc(150ms * var(--motion-scale));
  --dur-base: calc(200ms * var(--motion-scale));
  --dur-fast: calc(250ms * var(--motion-scale));
  --dur-medium: calc(350ms * var(--motion-scale));
  --dur-develop: calc(450ms * var(--motion-scale));
  --ease-out: cubic-bezier(0.22, 1, 0.36, 1);
}

:root[data-typing-font="source-serif"] { --font-typing: var(--font-source-serif); }
:root[data-typing-font="inter"] { --font-typing: var(--font-inter); }
:root[data-typing-font="jetbrains-mono"] { --font-typing: "JetBrains Mono", ui-monospace, monospace; }
:root[data-typing-font="ibm-plex-mono"] { --font-typing: "IBM Plex Mono", ui-monospace, monospace; }
:root[data-typing-font="roboto-mono"] { --font-typing: "Roboto Mono", ui-monospace, monospace; }
:root[data-typing-font="fira-code"] { --font-typing: "Fira Code", ui-monospace, monospace; }
:root[data-app-font="source-serif"] { --font-app: var(--font-source-serif); }
:root[data-app-font="inter"] { --font-app: var(--font-inter); }

:root,
:root[data-theme="dark"] {
  color-scheme: dark;
  --backdrop: rgba(0, 0, 0, 0.5);
  --ground: #0f0f0e;
  --ground-2: #151514;
  --raised: #1c1b1a;
  --raised-2: rgba(255, 255, 255, 0.085);
  --text: #ece8e1;
  --muted: #a39e96;
  --placeholder: rgba(163, 158, 150, 0.8);
  --hairline: rgba(255, 255, 255, 0.075);
  --hairline-strong: rgba(255, 255, 255, 0.15);
  --outline: #7d7972;
  --accent: #8fd18a;
  --accent-ink: #0f1a0e;
  --accent-soft: rgba(143, 209, 138, 0.13);
  --practice-level-1: rgba(143, 209, 138, 0.3);
  --practice-level-2: rgba(143, 209, 138, 0.55);
  --practice-level-3: rgba(143, 209, 138, 0.8);
  --danger: #ff8a6b;
  --danger-soft: rgba(255, 138, 107, 0.16);
  --heat-1: rgba(255, 138, 107, 0.18);
  --heat-2: rgba(255, 138, 107, 0.36);
  --heat-3: rgba(255, 138, 107, 0.56);
  --warning: #e0b565;
  --selection: #34322e;
  /* The word highlight: a faint wash of the text colour, light enough that untyped (muted) letters keep 4.5:1. */
  --word-highlight: color-mix(in oklab, var(--text) 5%, transparent);
  --ghost: #b9c3ff;
  --shadow-float: 0 8px 24px rgba(0, 0, 0, 0.35);
  --card-top: #1f1e1c;
  --card-bottom: #121211;
  /* Finger map: one neutral band behind alternate fingers (index, ring). Not a colour. */
  --finger-band: rgba(255, 255, 255, 0.1);
}

:root[data-theme="light"] {
  color-scheme: light;
  --backdrop: rgba(40, 32, 20, 0.22);
  --ground: #f4f1eb;
  --ground-2: #ece8e0;
  --raised: #fbf9f5;
  --raised-2: rgba(27, 26, 25, 0.07);
  --text: #1b1a19;
  --muted: #6b665e;
  --placeholder: rgba(107, 102, 94, 0.92);
  --hairline: rgba(27, 26, 25, 0.1);
  --hairline-strong: rgba(27, 26, 25, 0.2);
  --outline: #857f75;
  --accent: #217245;
  --accent-ink: #fbf9f5;
  --accent-soft: rgba(46, 125, 79, 0.11);
  --practice-level-1: rgba(46, 125, 79, 0.3);
  --practice-level-2: rgba(46, 125, 79, 0.55);
  --practice-level-3: rgba(46, 125, 79, 0.8);
  --danger: #ad3d26;
  --danger-soft: rgba(180, 67, 44, 0.12);
  --heat-1: rgba(180, 67, 44, 0.08);
  --heat-2: rgba(180, 67, 44, 0.24);
  --heat-3: rgba(180, 67, 44, 0.44);
  --warning: #8a5a00;
  --selection: #e3dccf;
  --word-highlight: color-mix(in oklab, var(--text) 5%, transparent);
  --ghost: #434db5;
  --shadow-float: 0 8px 24px rgba(70, 56, 30, 0.12);
  --card-top: #fffdf9;
  --card-bottom: #efeae1;
  --finger-band: rgba(27, 26, 25, 0.05);
}

/* The other eight themes: the same tokens as dark and light, nothing else. Type, radii and motion are
   shared; each keeps the roles (accent for action, current state and the caret, danger for mistakes). */
:root[data-theme="paper"] {
  color-scheme: light;
  --backdrop: rgba(40, 32, 20, 0.22);
  --ground: #fbf8f1;
  --ground-2: #f3eee3;
  --raised: #fffefa;
  --raised-2: rgba(20, 18, 16, 0.06);
  --text: #141210;
  --muted: #5e5850;
  --placeholder: #5e5850;
  --hairline: rgba(20, 18, 16, 0.12);
  --hairline-strong: rgba(20, 18, 16, 0.24);
  --outline: #7a7368;
  --accent: #1f6b45;
  --accent-ink: #fffefa;
  --accent-soft: rgba(31, 107, 69, 0.1);
  --practice-level-1: rgba(31, 107, 69, 0.3);
  --practice-level-2: rgba(31, 107, 69, 0.55);
  --practice-level-3: rgba(31, 107, 69, 0.8);
  --danger: #a8321c;
  --danger-soft: rgba(168, 50, 28, 0.12);
  --heat-1: rgba(168, 50, 28, 0.08);
  --heat-2: rgba(168, 50, 28, 0.24);
  --heat-3: rgba(168, 50, 28, 0.44);
  --warning: #7a4f00;
  --selection: #e8dfcc;
  --word-highlight: color-mix(in oklab, var(--text) 5%, transparent);
  --ghost: #3f4bb0;
  --shadow-float: none;
  --card-top: #fffefa;
  --card-bottom: #fffefa;
  --finger-band: rgba(20, 18, 16, 0.05);
}

:root[data-theme="shadcn"] {
  color-scheme: light;
  --backdrop: rgba(0, 0, 0, 0.5);
  --ground: #ffffff;
  --ground-2: #fafafa;
  --raised: #ffffff;
  --raised-2: #f4f4f5;
  --text: #09090b;
  --muted: #6b6b74;
  --placeholder: #6b6b74;
  --hairline: #e4e4e7;
  --hairline-strong: #d4d4d8;
  --outline: #71717a;
  --accent: #166534;
  --accent-ink: #ffffff;
  --accent-soft: rgba(22, 101, 52, 0.06);
  --practice-level-1: rgba(22, 101, 52, 0.3);
  --practice-level-2: rgba(22, 101, 52, 0.55);
  --practice-level-3: rgba(22, 101, 52, 0.8);
  --danger: #b91c1c;
  --danger-soft: rgba(220, 38, 38, 0.1);
  --heat-1: rgba(220, 38, 38, 0.08);
  --heat-2: rgba(220, 38, 38, 0.22);
  --heat-3: rgba(220, 38, 38, 0.4);
  --warning: #a16207;
  --selection: #e4e4e7;
  --word-highlight: color-mix(in oklab, var(--text) 5%, transparent);
  --ghost: #4f46e5;
  --shadow-float: 0 1px 3px rgba(0, 0, 0, 0.1), 0 1px 2px rgba(0, 0, 0, 0.06);
  --card-top: #ffffff;
  --card-bottom: #fafafa;
  --finger-band: rgba(9, 9, 11, 0.05);
}

:root[data-theme="terminal"] {
  color-scheme: dark;
  --backdrop: rgba(0, 0, 0, 0.6);
  --ground: #0b0f0b;
  --ground-2: #0f150f;
  --raised: #151c15;
  --raised-2: rgba(120, 255, 140, 0.07);
  --text: #c4ecbf;
  --muted: #80a67c;
  --placeholder: #80a67c;
  --hairline: rgba(120, 255, 140, 0.09);
  --hairline-strong: rgba(120, 255, 140, 0.18);
  --outline: #5f805c;
  --accent: #4fe86b;
  --accent-ink: #051a09;
  --accent-soft: rgba(79, 232, 107, 0.13);
  --practice-level-1: rgba(79, 232, 107, 0.3);
  --practice-level-2: rgba(79, 232, 107, 0.55);
  --practice-level-3: rgba(79, 232, 107, 0.8);
  --danger: #ff8a6b;
  --danger-soft: rgba(255, 138, 107, 0.16);
  --heat-1: rgba(255, 138, 107, 0.18);
  --heat-2: rgba(255, 138, 107, 0.36);
  --heat-3: rgba(255, 138, 107, 0.5);
  --warning: #e0c065;
  --selection: #233522;
  --word-highlight: color-mix(in oklab, var(--text) 5%, transparent);
  --ghost: #b9c3ff;
  --shadow-float: 0 8px 24px rgba(0, 0, 0, 0.45);
  --card-top: #141b14;
  --card-bottom: #0a0e0a;
  --finger-band: rgba(120, 255, 140, 0.09);
}

:root[data-theme="soft"] {
  color-scheme: light;
  --backdrop: rgba(40, 30, 80, 0.22);
  --ground: #f5f3fb;
  --ground-2: #eeeaf8;
  --raised: #ffffff;
  --raised-2: rgba(91, 75, 214, 0.08);
  --text: #1e1b2e;
  --muted: #5b5670;
  --placeholder: #5b5670;
  --hairline: rgba(30, 27, 46, 0.08);
  --hairline-strong: rgba(30, 27, 46, 0.16);
  --outline: #8a85a0;
  --accent: #5b4bd6;
  --accent-ink: #ffffff;
  --accent-soft: rgba(91, 75, 214, 0.12);
  --practice-level-1: rgba(91, 75, 214, 0.3);
  --practice-level-2: rgba(91, 75, 214, 0.55);
  --practice-level-3: rgba(91, 75, 214, 0.8);
  --danger: #b43628;
  --danger-soft: rgba(180, 54, 40, 0.12);
  --heat-1: rgba(180, 54, 40, 0.08);
  --heat-2: rgba(180, 54, 40, 0.24);
  --heat-3: rgba(180, 54, 40, 0.44);
  --warning: #8a5a00;
  --selection: #e2ddf7;
  --word-highlight: color-mix(in oklab, var(--text) 5%, transparent);
  --ghost: #963875;
  --shadow-float: 0 6px 20px rgba(60, 45, 120, 0.1);
  --card-top: #ffffff;
  --card-bottom: #f8f6fd;
  --finger-band: rgba(30, 27, 46, 0.05);
}

:root[data-theme="midnight"] {
  color-scheme: dark;
  --backdrop: rgba(0, 0, 0, 0.6);
  --ground: #08080a;
  --ground-2: #0c0c0f;
  --raised: #131317;
  --raised-2: rgba(255, 240, 220, 0.06);
  --text: #cfc8bc;
  --muted: #918b80;
  --placeholder: #918b80;
  --hairline: rgba(255, 240, 220, 0.06);
  --hairline-strong: rgba(255, 240, 220, 0.12);
  --outline: #6a655d;
  --accent: #e0a54a;
  --accent-ink: #1a1205;
  --accent-soft: rgba(224, 165, 74, 0.13);
  --practice-level-1: rgba(224, 165, 74, 0.3);
  --practice-level-2: rgba(224, 165, 74, 0.55);
  --practice-level-3: rgba(224, 165, 74, 0.8);
  --danger: #f07d63;
  --danger-soft: rgba(240, 125, 99, 0.15);
  --heat-1: rgba(240, 125, 99, 0.18);
  --heat-2: rgba(240, 125, 99, 0.36);
  --heat-3: rgba(240, 125, 99, 0.48);
  --warning: #cdbf7a;
  --selection: #24221e;
  --word-highlight: color-mix(in oklab, var(--text) 5%, transparent);
  --ghost: #9aa6e8;
  --shadow-float: 0 8px 24px rgba(0, 0, 0, 0.5);
  --card-top: #141417;
  --card-bottom: #0a0a0c;
  --finger-band: rgba(255, 240, 220, 0.08);
}

:root[data-theme="swiss"] {
  color-scheme: light;
  --backdrop: rgba(0, 0, 0, 0.4);
  --ground: #ffffff;
  --ground-2: #f5f5f5;
  --raised: #ffffff;
  --raised-2: #f0f0f0;
  --text: #000000;
  --muted: #555555;
  --placeholder: #555555;
  --hairline: rgba(0, 0, 0, 0.12);
  --hairline-strong: rgba(0, 0, 0, 0.3);
  --outline: #767676;
  --accent: #000000;
  --accent-ink: #ffffff;
  --accent-soft: rgba(0, 0, 0, 0.06);
  --practice-level-1: rgba(0, 0, 0, 0.3);
  --practice-level-2: rgba(0, 0, 0, 0.55);
  --practice-level-3: rgba(0, 0, 0, 0.8);
  --danger: #d30612;
  --danger-soft: rgba(211, 6, 18, 0.1);
  --heat-1: rgba(211, 6, 18, 0.08);
  --heat-2: rgba(211, 6, 18, 0.22);
  --heat-3: rgba(211, 6, 18, 0.4);
  --warning: #8a5a00;
  --selection: #e6e6e6;
  --word-highlight: color-mix(in oklab, var(--text) 5%, transparent);
  --ghost: #1f3fbf;
  --shadow-float: none;
  --card-top: #ffffff;
  --card-bottom: #ffffff;
  --finger-band: rgba(0, 0, 0, 0.05);
}

:root[data-theme="e-ink"] {
  color-scheme: light;
  --backdrop: rgba(0, 0, 0, 0.25);
  --ground: #eeeeea;
  --ground-2: #e7e7e3;
  --raised: #f6f6f2;
  --raised-2: rgba(0, 0, 0, 0.05);
  --text: #111111;
  --muted: #555552;
  --placeholder: #555552;
  --hairline: rgba(0, 0, 0, 0.1);
  --hairline-strong: rgba(0, 0, 0, 0.2);
  --outline: #80807c;
  --accent: #333333;
  --accent-ink: #f6f6f2;
  --accent-soft: rgba(0, 0, 0, 0.07);
  --practice-level-1: rgba(0, 0, 0, 0.2);
  --practice-level-2: rgba(0, 0, 0, 0.4);
  --practice-level-3: rgba(0, 0, 0, 0.6);
  --danger: #333333;
  --danger-soft: rgba(0, 0, 0, 0.14);
  --heat-1: rgba(0, 0, 0, 0.06);
  --heat-2: rgba(0, 0, 0, 0.16);
  --heat-3: rgba(0, 0, 0, 0.3);
  --warning: #4a4a4a;
  --selection: #d6d6d2;
  --word-highlight: color-mix(in oklab, var(--text) 5%, transparent);
  --ghost: #4a4a4a;
  --shadow-float: none;
  --card-top: #f6f6f2;
  --card-bottom: #f6f6f2;
  --finger-band: rgba(0, 0, 0, 0.06);
}

:root[data-theme="nord"] {
  color-scheme: dark;
  --backdrop: rgba(15, 18, 24, 0.6);
  --ground: #2e3440;
  --ground-2: #333a47;
  --raised: #3b4252;
  --raised-2: rgba(236, 239, 244, 0.045);
  --text: #eceff4;
  --muted: #b8c0ce;
  --placeholder: #b8c0ce;
  --hairline: rgba(236, 239, 244, 0.08);
  --hairline-strong: rgba(236, 239, 244, 0.16);
  --outline: #8f99ab;
  --accent: #adc697;
  --accent-ink: #2e3440;
  --accent-soft: rgba(173, 198, 151, 0.08);
  --practice-level-1: rgba(163, 190, 140, 0.3);
  --practice-level-2: rgba(163, 190, 140, 0.55);
  --practice-level-3: rgba(163, 190, 140, 0.8);
  --danger: #f5b4a1;
  --danger-soft: rgba(208, 135, 112, 0.16);
  --heat-1: rgba(208, 135, 112, 0.18);
  --heat-2: rgba(208, 135, 112, 0.36);
  --heat-3: rgba(208, 135, 112, 0.56);
  --warning: #ebcb8b;
  --selection: #434c5e;
  --word-highlight: color-mix(in oklab, var(--text) 5%, transparent);
  --ghost: #93c8d6;
  --shadow-float: 0 8px 24px rgba(15, 18, 24, 0.35);
  --card-top: #3b4252;
  --card-bottom: #343b49;
  --finger-band: rgba(236, 239, 244, 0.08);
}

/* A custom theme (src/custom-themes.js) sets its colours inline on :root, over dark or light. The surfaces
   and tints follow the colours they come from, at dark's or light's shares, unless the theme sets them too. */
:root[data-theme][data-custom] {
  --outline: color-mix(in srgb, var(--muted) 77%, var(--ground));
  --practice-level-1: color-mix(in srgb, var(--accent) 30%, transparent);
  --practice-level-2: color-mix(in srgb, var(--accent) 55%, transparent);
  --practice-level-3: color-mix(in srgb, var(--accent) 80%, transparent);
}
:root[data-theme="dark"][data-custom] {
  --ground-2: color-mix(in srgb, var(--text) 3%, var(--ground));
  --raised: color-mix(in srgb, var(--text) 6%, var(--ground));
  --card-top: color-mix(in srgb, var(--text) 7%, var(--ground));
  --card-bottom: color-mix(in srgb, var(--text) 1%, var(--ground));
  --selection: color-mix(in srgb, var(--text) 17%, var(--ground));
  --raised-2: color-mix(in srgb, var(--text) 8.5%, transparent);
  --placeholder: color-mix(in srgb, var(--muted) 80%, transparent);
  --hairline: color-mix(in srgb, var(--text) 7.5%, transparent);
  --hairline-strong: color-mix(in srgb, var(--text) 15%, transparent);
  --accent-soft: color-mix(in srgb, var(--accent) 13%, transparent);
  --danger-soft: color-mix(in srgb, var(--danger) 16%, transparent);
  --heat-1: color-mix(in srgb, var(--danger) 18%, transparent);
  --heat-2: color-mix(in srgb, var(--danger) 36%, transparent);
  --heat-3: color-mix(in srgb, var(--danger) 56%, transparent);
  --finger-band: color-mix(in srgb, var(--text) 10%, transparent);
}
:root[data-theme="light"][data-custom] {
  --ground-2: color-mix(in srgb, var(--text) 3%, var(--ground));
  --raised: color-mix(in srgb, #ffffff 60%, var(--ground));
  --card-top: color-mix(in srgb, #ffffff 80%, var(--ground));
  --card-bottom: color-mix(in srgb, var(--text) 3%, var(--ground));
  --selection: color-mix(in srgb, var(--text) 8%, var(--ground));
  --raised-2: color-mix(in srgb, var(--text) 7%, transparent);
  --placeholder: color-mix(in srgb, var(--muted) 92%, transparent);
  --hairline: color-mix(in srgb, var(--text) 10%, transparent);
  --hairline-strong: color-mix(in srgb, var(--text) 20%, transparent);
  --accent-soft: color-mix(in srgb, var(--accent) 11%, transparent);
  --danger-soft: color-mix(in srgb, var(--danger) 12%, transparent);
  --heat-1: color-mix(in srgb, var(--danger) 8%, transparent);
  --heat-2: color-mix(in srgb, var(--danger) 24%, transparent);
  --heat-3: color-mix(in srgb, var(--danger) 44%, transparent);
  --finger-band: color-mix(in srgb, var(--text) 5%, transparent);
}

@media (prefers-reduced-motion: reduce) {
  :root { --motion-scale: 0; }
  *, *::before, *::after {
    animation-duration: 1ms !important;
    animation-delay: 0ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 1ms !important;
    scroll-behavior: auto !important;
  }
}

/* ── Base ───────────────────────────────────────────────────────────────── */

* { box-sizing: border-box; }

html {
  min-width: 320px;
  background: var(--ground);
  scrollbar-color: var(--hairline-strong) var(--ground);
  /* Focus and scrollIntoView stop clear of the opaque dock: sticky at the top here, fixed to the bottom under 761px. */
  scroll-padding-top: calc(var(--space-4) + 58px + var(--space-6));
}

body {
  margin: 0;
  background: var(--ground);
  color: var(--text);
  font-family: var(--font-app);
  font-size: var(--text-body);
  line-height: 1.6;
  font-optical-sizing: auto;
  font-kerning: normal;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  transition: background-color var(--dur-medium) var(--ease-out), color var(--dur-medium) var(--ease-out);
  /* The background image sits between the body's colour and the page. */
  isolation: isolate;
}

h1, h2, h3 { text-wrap: balance; font-weight: 400; }
p { text-wrap: pretty; }

button, input, select, textarea { font: inherit; color: inherit; }
/* WebKit counts a select's whole longest option as page overflow, however narrow the select. */
select { contain: paint; }
button, summary, a { -webkit-tap-highlight-color: transparent; }
button { cursor: pointer; }

/* Neutral ring, so a focused secondary control never reads as a second primary. */
:focus-visible {
  outline: 2px solid var(--text);
  outline-offset: 3px;
}

::selection { background: var(--selection); color: var(--text); }

[hidden] { display: none !important; }

.num { font-variant-numeric: tabular-nums lining-nums; }

.sr-only,
#book-options .quote-filter-trigger > :first-child,
#typing-status {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

kbd {
  min-width: 22px;
  height: 22px;
  padding: 0 6px;
  border: 1px solid var(--hairline-strong);
  border-bottom-width: 2px;
  border-radius: var(--radius-sm);
  display: inline-grid;
  place-items: center;
  color: var(--muted);
  font: 500 var(--text-fine)/1 var(--font-mono);
}

.skip-link {
  position: fixed;
  z-index: 60;
  top: var(--space-3);
  left: var(--space-3);
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius);
  background: var(--raised);
  color: var(--accent);
  text-decoration: none;
  transform: translateY(calc(-100% - 40px));
  transition: transform var(--dur-fast) var(--ease-out);
}

.skip-link:focus { transform: translateY(0); }


/* ── Shell ──────────────────────────────────────────────────────────────── */

.app-shell {
  width: min(var(--content-max), calc(100% - 48px));
  min-height: 100vh;
  min-height: 100svh;
  margin-inline: auto;
  display: grid;
  grid-template-rows: auto 1fr auto;
}

.dock-zone {
  position: sticky;
  top: 0;
  z-index: 40;
  display: flex;
  justify-content: center;
  padding-top: var(--space-4);
  pointer-events: none;
}

.dock {
  position: relative;
  pointer-events: auto;
  height: 58px;
  padding: 6px 6px 6px 20px;
  border: 1px solid var(--hairline-strong);
  border-radius: var(--radius-lg);
  background: var(--raised);
  box-shadow: var(--shadow-float);
  display: flex;
  align-items: center;
  gap: var(--space-1);
}

.brand {
  margin-right: var(--space-4);
  padding: 4px 2px;
  color: var(--text);
  text-decoration: none;
  font-size: 21px;
  line-height: 1;
  white-space: nowrap;
}

.brand-word { font-style: italic; font-weight: 500; letter-spacing: -0.01em; }
.brand-dot { color: var(--accent); font-weight: 700; }

/* Gliding indicator shared by the dock, the mode segments and the length segments. */
.glide { position: relative; isolation: isolate; }

.glide-pill {
  position: absolute;
  z-index: -1;
  top: var(--pill-y, 3px);
  left: 0;
  width: var(--pill-w, 0);
  height: var(--pill-h, 0);
  border-radius: var(--radius-control);
  background: var(--raised-2);
  transform: translateX(var(--pill-x, 0));
  opacity: var(--pill-o, 0);
  transition: transform var(--dur-base) var(--ease-out);
}

.glide.no-anim .glide-pill { transition: none; }

.dock-nav { display: flex; align-items: center; gap: 2px; }

.nav-item {
  height: var(--control);
  padding: 0 18px;
  border: 0;
  border-radius: var(--radius-control);
  background: transparent;
  color: var(--muted);
  font-size: 16px;
  white-space: nowrap;
  transition: color var(--dur-quick) var(--ease-out);
}

.nav-item:hover,
.nav-item.is-active { color: var(--text); }

.nav-label-short { display: none; }

.dock-sep {
  width: 1px;
  height: 24px;
  margin: 0 var(--space-1);
  background: var(--hairline-strong);
  flex: none;
}

.dock-command {
  width: var(--control);
  height: var(--control);
  padding: 0;
  border: 0;
  border-radius: var(--radius-control);
  background: transparent;
  color: var(--muted);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: color var(--dur-quick) var(--ease-out), background-color var(--dur-quick) var(--ease-out);
}

.dock-command:hover { color: var(--text); background: var(--raised-2); }
.dock-command .search-glyph { width: 19px; height: 19px; fill: none; stroke: currentColor; stroke-width: 1.6; stroke-linecap: round; }

/* Settings is a labelled dock control so it is findable from every screen. */
.dock-settings { width: auto; padding: 0 14px 0 12px; gap: var(--space-2); cursor: pointer; }
.dock-settings .settings-glyph { width: 19px; height: 19px; flex: none; fill: none; stroke: currentColor; stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round; }

.icon-control {
  width: var(--control);
  height: var(--control);
  padding: 0;
  border: 0;
  border-radius: var(--radius-control);
  background: transparent;
  color: var(--muted);
  display: inline-grid;
  place-items: center;
  transition: color var(--dur-quick) var(--ease-out), background-color var(--dur-quick) var(--ease-out);
}

.icon-control:hover { color: var(--text); background: var(--raised-2); }

.icon-control svg,
.palette-search svg {
  width: 19px;
  height: 19px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
}

#settings-dialog {
  width: min(var(--content-max), calc(100% - var(--space-8)));
  height: min(var(--settings-height), calc(100dvh - var(--space-8)));
  max-width: none;
  max-height: none;
  padding: 0;
  color: var(--text);
  background: var(--raised);
  border: 1px solid var(--hairline-strong);
  border-radius: var(--radius-xl);
  box-shadow: var(--shadow-float);
  overflow: clip;
}
#settings-dialog[open] { display: flex; flex-direction: column; animation: palette-in var(--dur-quick) var(--ease-out); }
#settings-dialog::backdrop { background: var(--backdrop); }
.settings-header { display: flex; flex: none; align-items: center; justify-content: space-between; padding: var(--space-6) var(--space-8) var(--space-4); }
.settings-header h2 { margin: 0; font-size: var(--text-title); font-weight: 400; line-height: var(--settings-title-line); }
:is(#settings-close, #release-close) { display: flex; align-items: center; gap: var(--space-3); min-height: var(--control); padding-inline: var(--space-3); border: 1px solid var(--hairline-strong); border-radius: var(--radius-sm); background: transparent; color: var(--muted); cursor: pointer; font: var(--text-mono)/var(--space-4) var(--font-mono); }
:is(#settings-close, #release-close):hover { color: var(--text); background: var(--raised-2); }
.release-notes { display: grid; gap: var(--space-3); margin: 0; padding: 0 var(--space-8) var(--space-8) calc(var(--space-8) + var(--space-4)); font-size: var(--text-small); }
.settings-body { display: flex; flex: 1; min-height: 0; max-height: var(--settings-body-height); }
.settings-nav { width: var(--settings-sidebar); flex: none; display: flex; flex-direction: column; gap: var(--space-2); padding: var(--space-4); border-right: 1px solid var(--hairline-strong); }
.settings-nav button { flex: none; text-align: left; padding: var(--space-2) var(--space-4); min-height: var(--control); border: 0; border-left: var(--settings-selection-line) solid transparent; border-radius: var(--radius-sm); color: var(--muted); background: transparent; font: var(--text-small)/var(--settings-option-line) var(--font-app); cursor: pointer; }
.settings-nav button:hover { background: var(--raised-2); color: var(--text); }
.settings-nav button[aria-current] { color: var(--accent); border-left-color: var(--accent); background: var(--accent-soft); }
.settings-nav .settings-import { margin-top: var(--space-6); font-size: var(--text-label); line-height: 1.7; }
.settings-content { flex: 1; min-width: 0; overflow-y: auto; overscroll-behavior: contain; padding: var(--space-4) var(--space-8); }
.settings-intro { display: grid; gap: var(--space-1); padding-block: var(--space-2); }
.settings-intro h3 { font-size: var(--text-lead); font-weight: 400; line-height: 1.6; margin: 0; }
.preference-row { display: flex; align-items: center; gap: var(--space-6); min-height: var(--settings-row); padding-block: var(--space-3); border-bottom: 1px solid var(--hairline); }
.preference-copy { display: flex; flex-direction: column; flex: none; width: var(--settings-label); gap: var(--space-1); }
.preference-copy strong { font-size: var(--text-small); font-weight: 600; line-height: 1.6; }
.preference-note, .preference-lock { color: var(--muted); font: var(--text-label)/1.4 var(--font-app); }
.preference-lock { font-style: italic; }
.setting-options { display: flex; flex: 1; flex-wrap: wrap; min-width: 0; gap: var(--space-1); margin: 0; border: 0; padding: 0; }
.preference-row-wide { flex-direction: column; align-items: flex-start; gap: var(--space-2); }
.preference-row-wide .setting-options { flex: none; width: 100%; }
.setting-option { position: relative; display: flex; cursor: pointer; }
.setting-option input { position: absolute; inset: 0; opacity: 0; width: 100%; height: 100%; margin: 0; cursor: inherit; }
.setting-option span { display: flex; align-items: center; justify-content: center; min-width: var(--control); min-height: var(--control); padding-inline: var(--space-4); border-radius: var(--radius-sm); color: var(--muted); font: var(--text-small)/var(--settings-option-line) var(--font-app); }
.setting-option:hover span { color: var(--text); background: var(--raised-2); }
.setting-option input:checked + span { color: var(--accent); background: var(--accent-soft); box-shadow: inset 0 calc(-1 * var(--settings-selection-line)) 0 var(--accent); }
.setting-option input:focus-visible + span { outline: var(--settings-selection-line) solid var(--text); outline-offset: var(--space-1); }
button.setting-option { border: 0; padding: 0; background: none; outline: none; }
button.setting-option:focus-visible span { outline: var(--settings-selection-line) solid var(--text); outline-offset: var(--space-1); }
.setting-option:has(:disabled) { cursor: default; opacity: var(--disabled-opacity); }
.preference-range { display: flex; align-items: center; gap: var(--space-3); }
.preference-value { min-width: 4ch; color: var(--muted); font-size: var(--text-small); text-align: right; }
.pace-note { color: var(--muted); font: var(--text-fine)/1.4 var(--font-mono); }
.pace-note:empty { display: none; }
.preference-row .pace-wpm { width: var(--settings-label); }
.settings-preview { padding-top: var(--space-4); display: grid; gap: var(--space-2); }
#settings-preview-passage { margin: 0; border-left: var(--settings-selection-line) solid var(--accent); padding-left: var(--space-3); font-family: var(--font-typing); font-size: var(--settings-preview-medium); line-height: var(--settings-preview-line); }
body[data-font-size="22"] #settings-preview-passage { font-size: var(--settings-preview-small); }
body[data-font-size="36"] #settings-preview-passage { font-size: var(--settings-preview-large); }
body[data-word-highlight="current"] #settings-preview-passage span:nth-child(3),
body[data-word-highlight="next"] #settings-preview-passage span:nth-child(4) { background: var(--word-highlight); border-radius: var(--radius-sm); }
#settings-preview-passage { position: relative; isolation: isolate; }
body[data-word-highlight-slide="true"] #settings-preview-passage > span:is(:nth-child(3), :nth-child(4)) { background: none; }
body[data-tape-preference="true"] #settings-preview-passage { white-space: nowrap; overflow: hidden; }
/* Custom themes: new and edit sit after the theme options and read like them. */
.setting-action { display: flex; align-items: center; justify-content: center; min-width: var(--control); min-height: var(--control); padding-inline: var(--space-4); border: 0; border-radius: var(--radius-sm); background: transparent; color: var(--muted); font: var(--text-small)/var(--settings-option-line) var(--font-app); cursor: pointer; }
.setting-action:hover:not(:disabled) { color: var(--text); background: var(--raised-2); }
.setting-action:focus-visible { outline: var(--settings-selection-line) solid var(--text); outline-offset: var(--space-1); }
.setting-action[aria-expanded="true"] { color: var(--accent); }
.setting-action:disabled { cursor: default; opacity: var(--disabled-opacity); }
.theme-editor { display: grid; gap: var(--space-3); padding-block: var(--space-4); border-bottom: 1px solid var(--hairline); }
.theme-vibe { display: flex; gap: var(--space-2); }
.theme-vibe .input { flex: 1; min-width: 0; }
.theme-status { margin: 0; color: var(--danger); font-size: var(--text-label); }
.theme-status:empty { display: none; }
.theme-colors { display: grid; grid-template-columns: repeat(auto-fill, minmax(var(--settings-label), 1fr)); gap: var(--space-1) var(--space-6); }
.theme-color { display: grid; grid-template-columns: minmax(0, 1fr) var(--control) 10ch; align-items: center; gap: var(--space-2); }
.theme-color label { color: var(--muted); font-size: var(--text-small); }
.theme-color input[type="color"] { width: var(--control); height: var(--control); padding: var(--space-1); border: 1px solid var(--hairline-strong); border-radius: var(--radius-sm); background: transparent; cursor: pointer; }
.theme-hex { padding-inline: var(--space-2); font: var(--text-mono)/1 var(--font-mono); }
.theme-issue { grid-column: 1 / -1; color: var(--danger); font: var(--text-fine)/1.4 var(--font-mono); }
.theme-issue:empty { display: none; }
.theme-more summary { line-height: var(--control); color: var(--muted); font-size: var(--text-small); cursor: pointer; }
.theme-more summary:hover { color: var(--text); }
.theme-more[open] summary { margin-bottom: var(--space-2); }
.theme-editor-actions { display: flex; justify-content: flex-end; gap: var(--space-2); }
#theme-delete { margin-right: auto; }
.settings-footer { display: flex; flex: none; align-items: center; justify-content: space-between; gap: var(--space-6); border-top: 1px solid var(--hairline-strong); padding: var(--space-4) var(--space-8); }
.settings-footer p { font-size: var(--text-label); line-height: 1.7; color: var(--muted); margin: 0; }
.settings-footer .btn { flex: none; padding-inline: var(--space-6); font-size: var(--text-small); }
body:has(#settings-dialog[open]) { overflow: hidden; }

@media (max-width: 760px) {
  #settings-dialog { width: calc(100% - var(--space-4)); height: min(var(--settings-height), calc(100dvh - var(--space-4))); border-radius: var(--radius-lg); }
  .settings-header { padding: var(--space-3) var(--space-4); }
  .settings-body { flex-direction: column; max-height: none; }
  .settings-nav { width: auto; flex-direction: row; flex-wrap: wrap; gap: var(--space-1); padding: var(--space-2) var(--space-3); border-right: 0; border-bottom: 1px solid var(--hairline-strong); }
  .settings-nav button { padding-inline: var(--space-2); border-left: 0; border-bottom: var(--settings-selection-line) solid transparent; }
  .settings-nav [aria-current] { border-bottom-color: var(--accent); }
  .settings-nav .settings-import { margin: 0; }
  .settings-content { padding: var(--space-3) var(--space-4); }
  .preference-row { flex-direction: column; align-items: flex-start; gap: var(--space-2); }
  .preference-copy { width: auto; }
  .setting-options { flex: none; width: 100%; }
  .setting-option span { padding-inline: var(--space-3); }
  .settings-footer { gap: var(--space-3); padding: var(--space-3) var(--space-4); }
  .settings-footer p { font-size: var(--text-fine); }
}

/* The track is an outline rule (3:1 on raised in both themes); the filled part and thumb are the
   accent. app.js keeps --fill at the value for WebKit, which has no progress part. */
.range {
  --fill: 50%;
  appearance: none;
  width: var(--sound-range-width);
  height: var(--sound-range-height);
  margin: 0;
  border-radius: var(--radius-sm);
  background: transparent;
  cursor: pointer;
}

.range::-webkit-slider-runnable-track {
  height: var(--space-1);
  border-radius: var(--radius-full);
  background: linear-gradient(to right, var(--accent) var(--fill), var(--outline) var(--fill));
}

.range::-webkit-slider-thumb {
  appearance: none;
  width: var(--space-4);
  height: var(--space-4);
  margin-top: calc((var(--space-1) - var(--space-4)) / 2);
  border: 0;
  border-radius: var(--radius-full);
  background: var(--accent);
}

.range::-moz-range-track { height: var(--space-1); border-radius: var(--radius-full); background: var(--outline); }
.range::-moz-range-progress { height: var(--space-1); border-radius: var(--radius-full); background: var(--accent); }
.range::-moz-range-thumb { width: var(--space-4); height: var(--space-4); border: 0; border-radius: var(--radius-full); background: var(--accent); }

.preference-range:has(> .range:disabled) { opacity: var(--disabled-opacity); }
.range:disabled { cursor: not-allowed; }
.keybindings-category > p { font-size: var(--text-small); color: var(--muted); }
.keybinding-row { margin-block: var(--space-4); }
.keybinding-row label { display: block; font-size: var(--text-small); margin-bottom: var(--space-2); }
.keybinding-controls, .keybinding-actions { display: flex; align-items: center; gap: var(--space-2); }
.keybinding-controls .input { min-width: 0; flex: 1; font: var(--text-fine)/1.5 var(--font-mono); }
.keybinding-controls .btn { flex: none; }
.keybindings-category .btn { height: var(--control); }
.keybindings-category .keybinding-error { color: var(--danger); margin-top: var(--space-2); }
.keybinding-error:empty { display: none; }
.keybinding-controls [aria-invalid="true"] { border-color: var(--danger); }
.keybinding-controls [aria-pressed="true"] { outline: 2px solid var(--accent); }


.site-footer {
  padding: var(--space-8) 0 var(--space-6);
  display: flex;
  justify-content: space-between;
  gap: var(--space-6);
  color: var(--muted);
  font: var(--text-fine)/1.5 var(--font-mono);
  letter-spacing: 0.02em;
}

.personal-best { color: var(--muted); }

/* Corner: the inbox and the person button, top right, level with the dock, on the same raised surface. */
.corner {
  position: fixed;
  z-index: 40;
  top: var(--space-4);
  right: var(--space-4);
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
}

.account,
.inbox { position: relative; }

.corner summary { list-style: none; }
.corner summary::-webkit-details-marker { display: none; }

.corner-trigger {
  width: 58px;
  height: 58px;
  border: 1px solid var(--hairline-strong);
  border-radius: var(--radius-lg);
  background: var(--raised);
  box-shadow: var(--shadow-float);
  color: var(--muted);
  display: grid;
  place-items: center;
  cursor: pointer;
  transition: color var(--dur-quick) var(--ease-out);
}

.corner-trigger:hover,
.corner details[open] > .corner-trigger,
.account[data-state="member"] .account-trigger { color: var(--text); }

.corner-trigger svg {
  width: 21px;
  height: 21px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* Signed in: a small green mark on the person, the current state. */
.account[data-state="member"] .account-trigger { position: relative; }
.account[data-state="member"] .account-trigger::after {
  content: "";
  position: absolute;
  top: 15px;
  right: 15px;
  width: 7px;
  height: 7px;
  border-radius: var(--radius-full);
  background: var(--accent);
}

.corner-popover {
  position: absolute;
  z-index: 50;
  top: calc(100% + var(--space-2));
  right: 0;
  width: 300px;
  padding: var(--space-4);
  border: 1px solid var(--hairline-strong);
  border-radius: var(--radius-lg);
  background: var(--raised);
  box-shadow: var(--shadow-float);
  animation: fade-in var(--dur-quick) var(--ease-out);
}

/* Inbox: unread entries are counted on the envelope. The list scrolls: it keeps every open ghost race and
   the week's ghost race results, then the newest thirty others. */
.corner-trigger { position: relative; }

.inbox-count {
  position: absolute;
  top: 9px;
  right: 7px;
  min-width: 18px;
  height: 18px;
  padding: 0 5px;
  border-radius: var(--radius-full);
  background: var(--accent);
  color: var(--accent-ink);
  font-family: var(--font-app);
  font-size: var(--text-fine);
  font-weight: 500;
  line-height: 18px;
  text-align: center;
}

.inbox-popover { width: 340px; }
.inbox-heading { display: flex; align-items: center; justify-content: space-between; gap: var(--space-3); min-height: 32px; }
.inbox-heading p { margin: 0; }
.inbox-popover > .account-lede { margin: var(--space-2) 0 0; }

.inbox-list {
  list-style: none;
  margin: var(--space-2) 0 0;
  padding: 0;
  max-height: min(420px, 60vh);
  overflow-y: auto;
}

.inbox-entry {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--space-1) var(--space-3);
  padding: var(--space-3) 0;
  border-top: 1px solid var(--hairline);
}

.inbox-entry p { margin: 0; color: var(--muted); font-size: var(--text-small); line-height: 1.45; overflow-wrap: anywhere; }
.inbox-entry[data-unread] p { color: var(--text); }
.inbox-meta { grid-column: 1; color: var(--muted); font: var(--text-fine)/1.4 var(--font-mono); }
.inbox-entry button { grid-column: 2; grid-row: 1 / span 2; }

.account-section { display: grid; gap: var(--space-2); }
.account-section p { margin: 0; }

.account-kicker {
  color: var(--muted);
  font: var(--text-fine)/1.4 var(--font-mono);
  letter-spacing: 0.04em;
}

.account-lede { color: var(--muted); font-size: var(--text-small); line-height: 1.5; }
.account-email { font-size: var(--text-body); overflow-wrap: anywhere; }
.account-sync.status { align-items: baseline; white-space: normal; }
.account[data-sync="synced"] .account-sync { color: var(--accent); }
.account:is([data-sync="offline"], [data-sync="expired"]) .account-sync { color: var(--warning); }

.account-actions { margin-top: var(--space-2); display: grid; gap: var(--space-2); }
.account-member-actions { justify-items: start; }

.guest-note { color: var(--muted); }

.footer-link {
  margin: calc(var(--space-2) * -1);
  padding: var(--space-2);
  border: 0;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--muted);
  font: inherit;
  letter-spacing: inherit;
  text-decoration: underline;
  text-decoration-color: var(--hairline-strong);
  text-underline-offset: 3px;
  cursor: pointer;
  transition: color var(--dur-quick) var(--ease-out), text-decoration-color var(--dur-quick) var(--ease-out);
}

.footer-link:hover { color: var(--text); text-decoration-color: currentColor; }
.footer-links { display: flex; gap: var(--space-6); }

/* ── Controls ───────────────────────────────────────────────────────────── */

.btn {
  height: var(--control);
  padding: 0 20px;
  border: 1px solid transparent;
  border-radius: var(--radius-control);
  background: transparent;
  color: var(--text);
  font-size: 16px;
  font-weight: 500;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  white-space: nowrap;
  transition:
    background-color var(--dur-quick) var(--ease-out),
    color var(--dur-quick) var(--ease-out),
    border-color var(--dur-quick) var(--ease-out),
    transform var(--dur-quick) var(--ease-out);
}

.btn:active:not(:disabled, [aria-disabled="true"]) { transform: scale(0.98); }
.btn:disabled, .btn[aria-disabled="true"] { opacity: var(--disabled-opacity); cursor: not-allowed; }
/* A waiting control (aria-disabled) can hold keyboard focus. Its focus ring stays at full strength,
   so its own colours dim to the same 38% instead of the whole box. */
.btn[aria-disabled="true"]:focus-visible { opacity: 1; color: color-mix(in srgb, var(--text) 38%, transparent); }
.btn-primary[aria-disabled="true"]:focus-visible { color: color-mix(in srgb, var(--accent-ink) 38%, transparent); background: color-mix(in srgb, var(--accent) 38%, transparent); }
.btn-secondary[aria-disabled="true"]:focus-visible { border-color: color-mix(in srgb, var(--hairline-strong) 38%, transparent); }
.btn-ghost[aria-disabled="true"]:focus-visible { color: color-mix(in srgb, var(--muted) 38%, transparent); }

.btn-primary {
  background: var(--accent);
  color: var(--accent-ink);
  font-weight: 600;
}

.btn-primary:hover:not(:disabled, [aria-disabled="true"]) { background: color-mix(in oklab, var(--accent) 88%, var(--text)); }

.btn-secondary { border-color: var(--hairline-strong); }
.btn-secondary:hover:not(:disabled, [aria-disabled="true"]) { background: var(--raised-2); }
.btn-ghost { color: var(--muted); }
.btn-ghost:hover:not(:disabled, [aria-disabled="true"]) { color: var(--text); background: var(--raised-2); }
.btn-danger { color: var(--danger); border-color: color-mix(in oklab, var(--danger) 40%, transparent); }
.btn-danger:hover:not(:disabled, [aria-disabled="true"]) { background: var(--danger-soft); }
.btn-sm { height: 34px; padding: 0 12px; font-size: 14px; }

/* A quiet destructive action (e.g. a ghost "Clear history" button). */
.danger-text { color: var(--danger); }
.danger-text:hover:not(:disabled, [aria-disabled="true"]) { color: var(--danger); background: var(--danger-soft); }

.input {
  height: var(--control);
  width: 100%;
  padding: 0 14px;
  border: 1px solid var(--outline);
  border-radius: var(--radius-control);
  background: var(--raised);
  color: var(--text);
  font-size: 16px;
  transition: border-color var(--dur-quick) var(--ease-out), box-shadow var(--dur-quick) var(--ease-out);
}

/* opacity: 1 overrides the browser's own placeholder fade, so the token alone sets the contrast. */
.input::placeholder { color: var(--placeholder); opacity: 1; }
.input:focus-visible { outline: 0; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
.input[aria-invalid="true"] { border-color: var(--danger); }
.select { padding-right: 8px; cursor: pointer; }

.switch {
  appearance: none;
  width: 38px;
  height: 22px;
  margin: 0;
  flex: none;
  position: relative;
  border-radius: var(--radius-full);
  background: var(--hairline-strong);
  cursor: pointer;
  transition: background-color var(--dur-base) var(--ease-out);
}

.switch::before {
  content: "";
  position: absolute;
  top: 3px;
  left: 3px;
  width: 16px;
  height: 16px;
  border-radius: var(--radius-full);
  background: var(--text);
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
  transition: transform var(--dur-base) var(--ease-out), background-color var(--dur-base) var(--ease-out);
}

.switch:checked { background: var(--accent); }
.switch:checked::before { transform: translateX(16px); background: var(--accent-ink); }
.switch:disabled { opacity: 0.4; cursor: not-allowed; }

.field-error { min-height: 1.4em; margin: var(--space-2) 0 0; color: var(--danger); font-size: 14px; }

/* Status words: a dot and a word, coloured by meaning. app.js sets badge-* for the meaning. */
.status,
.badge {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  color: var(--muted);
  font: 500 var(--text-mono)/1.3 var(--font-mono);
  letter-spacing: 0.02em;
  white-space: nowrap;
}

.status-dot {
  width: 7px;
  height: 7px;
  flex: none;
  border-radius: var(--radius-full);
  background: currentColor;
}

.badge-info, .badge-success { color: var(--accent); }
.badge-warning { color: var(--warning); }
.badge-danger { color: var(--danger); }
.badge-neutral { color: var(--muted); }

.panel-label {
  color: var(--muted);
  font: 500 var(--text-mono)/1.4 var(--font-mono);
  letter-spacing: 0.04em;
}

.condition-copy {
  margin: 0;
  color: var(--muted);
  font: var(--text-mono)/1.5 var(--font-mono);
  letter-spacing: 0.01em;
}

@keyframes fade-in { from { opacity: 0; } }

/* ── Screens ────────────────────────────────────────────────────────────── */

#main-content { min-width: 0; display: grid; }

.screen {
  min-width: 0;
  width: 100%;
  padding: var(--space-14) 0 var(--space-10);
}

/* Practice appears instantly so a restart is never taxed; other screens fade in. */
.screen:not([data-screen="practice"]) { animation: fade-in var(--dur-quick) var(--ease-out); }

/* ── Practice ───────────────────────────────────────────────────────────── */

/* The screen sits where open practice's block, centred under the dock, would start, and every mode
   and session keeps that top: what a mode puts under the rail never moves the rail or the passage. */
.typing-screen {
  display: grid;
  align-content: start;
  min-height: calc(100svh - 170px);
  padding-top: max(var(--space-10), calc((100svh - 170px - var(--practice-block)) / 2));
}

/* The row paints above what follows it: the "more" menu over the passage, and its controls over the
   custom text panel, which rises into the space under the row. */
.practice-controls {
  position: relative;
  z-index: 1;
  margin: 0 auto var(--space-8);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: var(--space-3);
}

.segmented {
  padding: 3px;
  border: 1px solid var(--hairline);
  border-radius: var(--radius-control);
  display: flex;
  align-items: center;
  gap: 2px;
}

.segmented .glide-pill { border-radius: var(--radius); }

.segmented button,
.segmented .custom-length-input {
  height: 36px;
  padding: 0 15px;
  border: 0;
  border-radius: var(--radius);
  background: transparent;
  color: var(--muted);
  font-size: 15px;
  transition: color var(--dur-quick) var(--ease-out);
}

.segmented button:hover,
.segmented button[aria-pressed="true"] { color: var(--text); }

/* The mode buttons sit a little closer than other segmented controls, so the row stays short. */
/* Raised over the groups after it, so the "more" menu opens above them when they wrap below. */
.mode-options { z-index: 2; gap: 0; }
.mode-options button { padding: 0 var(--space-3); }

.practice-rail {
  display: flex;
  align-items: center;
  gap: var(--space-4);
}

.practice-rail :is(.segmented, .compact-switch),
.online-options :is(.segmented, .compact-switch) { background: var(--raised); }

/* Lengths, quote filters, code languages and the standard recipe take turns in one place. */
.practice-options { display: contents; }

@media (min-width: 1000px) {
  .practice-rail { gap: var(--space-6); }
}

@media (max-width: 900px) {
  .practice-rail { display: contents; }
}

/* "more": the rarer modes and the way into lessons. It reads as one more mode; once one of its
   modes is chosen it takes that mode's name and the gliding pill rests under it. */
.mode-more { position: relative; }
.mode-more > summary {
  height: 36px;
  padding: 0 var(--space-3);
  border-radius: var(--radius);
  color: var(--muted);
  font-size: 15px;
  display: flex;
  align-items: center;
  gap: var(--space-2);
  cursor: pointer;
  list-style: none;
  transition: color var(--dur-quick) var(--ease-out);
}
.mode-more > summary::-webkit-details-marker { display: none; }
/* The disclosure chevron of "drill something else", at the same rotated 1.5px corner. */
.mode-more > summary::after,
.conditions-disclosure > summary::after {
  content: "";
  width: 7px;
  height: 7px;
  flex: none;
  border-right: 1.5px solid currentColor;
  border-bottom: 1.5px solid currentColor;
  transform: translateY(-2px) rotate(45deg);
  transition: transform var(--dur-quick) var(--ease-out);
}
:is(.mode-more, .conditions-disclosure)[open] > summary::after { transform: translateY(2px) rotate(-135deg); }
.mode-more > summary:hover,
.mode-more[open] > summary,
.mode-more[data-active] > summary { color: var(--text); }

.mode-more-menu {
  position: absolute;
  z-index: 2;
  top: calc(100% + var(--space-2));
  left: 50%;
  width: max-content;
  padding: var(--space-1);
  border: 1px solid var(--hairline-strong);
  border-radius: var(--radius-lg);
  background: var(--raised);
  box-shadow: var(--shadow-float);
  display: grid;
  gap: 2px;
  transform: translateX(-50%);
  animation: fade-in var(--dur-quick) var(--ease-out);
}
.segmented .mode-more-menu button { width: 100%; padding: 0 var(--space-6) 0 var(--space-3); text-align: left; white-space: nowrap; }
.segmented .mode-more-menu button:hover,
.segmented .mode-more-menu button[aria-pressed="true"] { background: var(--raised-2); }
/* "learn to type" is a way in, not a mode: it sits under a rule, after the modes. */
.segmented .mode-more-menu .mode-more-lessons { margin-top: 2px; border-top: 1px solid var(--hairline); border-radius: 0 0 var(--radius) var(--radius); }

.segmented.compact button,
.segmented.compact .custom-length-input {
  min-width: 46px;
  padding: 0 8px;
  font: 500 13px/1 var(--font-mono);
}

/* Code language names are readable words, so they set in Literata like the mode buttons and the
   word "custom". */
.segmented.compact button[data-language] { font: 400 var(--text-small)/1 var(--font-app); }

/* The custom length: the pill becomes a short field in place, with no box of its own beyond the
   focus ring. Pill and field share one width, so the row never jumps, and the message sits in the
   gap under the row, so the passage never moves. The word "custom" is Literata like the mode
   options; only a number in the pill is mono, like the presets beside it. */
.custom-length { display: flex; }
.segmented.compact #custom-length { min-width: var(--space-16); padding: 0 var(--space-1); }
.segmented.compact #custom-length:not(.is-active) { font: 400 var(--text-small)/1 var(--font-app); }
.segmented .custom-length-input { width: var(--space-16); color: var(--text); text-align: center; }
.custom-length-input::placeholder { color: var(--muted); opacity: 1; }
/* A coral hairline, as on other fields, paired with the message's words; no tint, so the
   placeholder keeps its contrast. */
.custom-length-input[aria-invalid="true"] { box-shadow: inset 0 0 0 1px var(--danger); }

.custom-length-error {
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  min-height: 0;
  margin: var(--space-1) 0 0;
  line-height: 1.4;
  text-align: center;
}

/* Focus mode hides the row; the palette's "custom time…" shows it while the field is open, and a
   message left by an invalid entry stays readable until the run starts. */
body[data-focus-mode="true"] .practice-controls[data-editing],
body[data-focus-mode="true"] .custom-length-error:not(:empty) { visibility: visible; }

.compact-switch {
  height: 44px;
  padding: 0 14px 0 12px;
  border: 1px solid var(--hairline);
  border-radius: var(--radius-control);
  color: var(--muted);
  font-size: 15px;
  display: inline-flex;
  align-items: center;
  gap: 10px;
  cursor: pointer;
  transition: color var(--dur-quick) var(--ease-out);
}

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

/* A quiet text action: the first-run "learn to type" links. */
.quiet-link {
  min-height: var(--control);
  padding: 0 var(--space-2);
  border: 0;
  border-radius: var(--radius-control);
  background: transparent;
  color: var(--muted);
  font-size: var(--text-small);
  text-decoration: underline;
  text-decoration-color: var(--hairline-strong);
  text-underline-offset: 3px;
  transition: color var(--dur-quick) var(--ease-out), text-decoration-color var(--dur-quick) var(--ease-out);
}

.quiet-link:hover { color: var(--text); text-decoration-color: currentColor; }

.first-run-learn { margin: 0; color: var(--muted); font-size: var(--text-small); }
.first-run-learn .quiet-link { min-height: 0; padding: var(--space-1) 0; font-size: inherit; color: var(--text); }
.section-heading .first-run-learn { margin-top: var(--space-3); }
.compact-switch .switch { width: 30px; height: 18px; }
.compact-switch .switch::before { width: 12px; height: 12px; }
.compact-switch .switch:checked::before { transform: translateX(12px); }

/* Practice minimums, when set: a quiet condition label on its own centred line under every
   control (lengths included on phones), so the controls stay centred. Never a control. */
.minimum-note {
  flex-basis: 100%;
  order: 2;
  color: var(--muted);
  font: var(--text-mono)/1.4 var(--font-mono);
  letter-spacing: 0.01em;
  text-align: center;
}

/* Raised over the switch after it, so a filter's menu opens above it when the row wraps below. */
.quote-options {
  z-index: 1;
  margin: 0;
  padding-inline: var(--space-3);
  max-width: 100%;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-2) var(--space-3);
}

.quote-filter { position: relative; }
.quote-filter-trigger {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  width: max-content;
  min-width: var(--control);
  min-height: calc(var(--control) - var(--space-2));
  padding-inline: var(--space-1);
  border-radius: var(--radius);
  color: var(--muted);
  font: var(--text-label)/1.4 var(--font-app);
  cursor: pointer;
  white-space: nowrap;
}
.quote-filter-value {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  color: var(--text);
}
.quote-filter-chevron {
  color: var(--muted);
}
.quote-filter-trigger:hover,
.quote-filter-trigger[aria-expanded="true"] { background: var(--raised-2); }
.quote-filter-menu {
  position: absolute;
  top: calc(100% + var(--space-2));
  left: 0;
  width: max-content;
  min-width: 100%;
  padding: var(--space-1);
  border: 1px solid var(--hairline-strong);
  border-radius: var(--radius-lg);
  background: var(--raised);
  box-shadow: var(--shadow-float);
  animation: fade-in var(--dur-quick) var(--ease-out);
}
.quote-filter:last-child .quote-filter-menu { left: auto; right: 0; }
.quote-filter-option {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  min-height: var(--control);
  min-width: var(--control);
  padding-inline: var(--space-3);
  border-radius: var(--radius);
  color: var(--text);
  font: var(--text-label)/1.4 var(--font-app);
  cursor: pointer;
}
.quote-filter-option[aria-selected="true"] {
  background: var(--raised-2);
  outline: 1px solid var(--outline);
  outline-offset: -1px;
}
.quote-filter-check { color: var(--accent); }
/* Book mode: the book's name needs no visible label, and the page number sits beside it. */
.book-page {
  color: var(--muted);
  font: var(--text-label)/1.4 var(--font-app);
  white-space: nowrap;
}
.quote-filter-option:not([data-current]) .quote-filter-check { visibility: hidden; }
@media (forced-colors: active) {
  .quote-filter-menu { border-color: CanvasText; }
  .quote-filter-option[aria-selected="true"] { outline-color: Highlight; }
  .quote-filter-check { color: CanvasText; }
}

/* The standard test's fixed recipe. The lock notes live in settings. It hangs in the space under the
   controls, so the numbers and the passage sit where every other mode puts them. */
.standard-options {
  position: absolute;
  top: 100%;
  left: 50%;
  width: max-content;
  height: var(--space-8);
  display: grid;
  align-content: center;
  color: var(--muted);
  font-size: var(--text-label);
  text-align: center;
  transform: translateX(-50%);
}

.standard-options p { margin: 0; }

/* Bring your own text: an open section on a hairline rule, not a box (only floating objects are raised).
   It sits beside the passage, so it appears instantly, without a fade. */
.custom-options {
  width: min(560px, 100%);
  margin: calc(var(--space-4) * -1) auto var(--space-10);
  padding-top: var(--space-3);
  display: grid;
  gap: var(--space-2);
  border-top: 1px solid var(--hairline);
}
.custom-options .field-error { margin: 0; }
.custom-label { display: flex; justify-content: space-between; align-items: baseline; color: var(--muted); font-size: var(--text-label); }
.custom-count { font: var(--text-fine)/1 var(--font-mono); font-variant-numeric: tabular-nums; }
.custom-textarea { width: 100%; min-height: 96px; height: auto; padding: var(--space-2); resize: vertical; font-size: var(--text-label); line-height: 1.5; }
.custom-actions { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--space-2); }
.custom-actions label { display: flex; align-items: center; gap: var(--space-2); color: var(--muted); font-size: var(--text-label); }
.custom-actions .input { width: 160px; height: 38px; font-size: var(--text-label); }

.typing-form,
.shortcut-row { width: 100%; }

.measure-row {
  position: relative;
  padding-bottom: var(--space-6);
  margin-bottom: var(--space-6);
  border-bottom: 1px solid var(--hairline);
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--space-8);
}

.session-intro { min-width: 0; display: grid; gap: var(--space-2); }
.session-line { display: flex; align-items: center; gap: var(--space-3); }

.session-kicker {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
}

#session-title {
  margin: var(--space-1) 0 0;
  font-size: var(--text-display);
  line-height: 1.05;
  letter-spacing: -0.025em;
  /* A ghost race names the sender, whose name can be one long unbroken word. */
  overflow-wrap: anywhere;
}

#session-description {
  max-width: 58ch;
  margin: 0;
  color: var(--muted);
}

#condition-line {
  margin: 0;
  color: var(--muted);
  font: var(--text-mono)/1.5 var(--font-mono);
}

/* Open practice shows only the numbers; drills and retests keep kicker, title and description. */
body[data-session="practice"] .session-intro { display: none; }
body[data-session="practice"] .measure-row,
body[data-session="practice"] .live-stats { justify-content: flex-start; }

body[data-session="drill"] .practice-controls,
body[data-session="drill"] .quote-options,
body[data-session="lesson"] .practice-controls,
body[data-session="lesson"] .quote-options,
body[data-session="drill"] .standard-options,
body[data-session="retest"] .practice-controls,
body[data-session="retest"] .quote-options,
body[data-session="retest"] .standard-options { display: none; }

.live-stats {
  position: relative;
  flex: none;
  display: flex;
  align-items: flex-end;
  gap: var(--space-10);
}

/* Number and unit share one baseline. */
.live-stat {
  display: flex;
  align-items: baseline;
  gap: 6px;
  color: var(--muted);
  font: var(--text-body)/1.2 var(--font-app);
}

.live-stat strong {
  color: var(--text);
  font-family: var(--font-app);
  font-size: 48px;
  font-weight: 300;
  line-height: 1;
  letter-spacing: -0.035em;
}

.timer-stat strong { color: var(--accent); }

/* Pace caret label: positioned out of flow so showing it never moves the passage. */
.pace-label {
  position: absolute;
  top: 100%;
  left: 0;
  margin-top: var(--space-1);
  color: var(--muted);
  font: var(--text-mono)/1 var(--font-mono);
  white-space: nowrap;
}
.pace-label[hidden] { display: none; }

body[data-live-metrics="false"] .live-stat:not(.timer-stat) { visibility: hidden; }

/* While a run is in progress everything but the passage and the numbers steps back.
   app.js sets data-running; keyboard focus brings a control back. */
.dock-zone,
.corner,
.practice-controls,
.quote-options,
.standard-options,
.custom-options,
.shortcut-row,
.site-footer,
.compose-another { transition: opacity var(--dur-base) var(--ease-out); }

body[data-running="true"] :is(.dock-zone, .corner, .practice-controls, .quote-options, .standard-options, .custom-options, .shortcut-row, .site-footer, .compose-another):not(:focus-within) { opacity: 0; }
body[data-running="true"] :is(.dock, .corner, .practice-controls, .quote-options, .standard-options, .custom-options, .shortcut-row, .site-footer, .compose-another):not(:focus-within) { pointer-events: none; }

/* The background image. The layer fades it out and dims it mid-run; only its opacity changes, so a run never
   relays out or repaints it. Its ::before holds the style: dither is dots × the dithered image (inverted on light
   themes, where the accent is the dark end) over the tint; regular and gradient are the picture itself, blurred
   once by the compositor and overscanned so the blur never pulls the ground in at the edges. The person's
   opacity sits there too, so a slider drag never waits on the dim transition. ::before always covers the screen
   and the layer clips it, so reach moves only where the fade ends: gone at 92% of the layer, at the top of the
   practice screen's reading band (src/wallpaper.js measures it) at reach 0 and at the bottom of the screen at 1,
   on the practice screen only. */
.wallpaper {
  position: fixed;
  inset: 0 0 auto;
  z-index: -1;
  --wallpaper-end: calc(var(--wallpaper-top, 40svh) + var(--wallpaper-reach) * (100svh - var(--wallpaper-top, 40svh)));
  height: calc(var(--wallpaper-end) / 0.92);
  overflow: hidden;
  pointer-events: none;
  opacity: 0;
  mask-image: linear-gradient(#000 20%, rgb(0 0 0 / 0.45) 55%, rgb(0 0 0 / 0.1) 80%, #0000 92%);
  transition: opacity var(--dur-medium) var(--ease-out);
}
body:not([data-view="practice"]) .wallpaper { --wallpaper-end: var(--wallpaper-top, 40svh); }
.wallpaper::before {
  content: "";
  position: absolute;
  inset: 0 0 auto;
  height: 100svh;
  opacity: var(--wallpaper-opacity);
}
.wallpaper[data-style="dither"]::before {
  background: color-mix(in oklab, var(--accent) var(--wallpaper-tint), var(--ground));
  mask-image: conic-gradient(at var(--wallpaper-dot) var(--wallpaper-dot), #0000 75%, #000 0), var(--wallpaper-image), linear-gradient(light-dark(#000, #0000) 0 0);
  mask-size: var(--wallpaper-pitch) var(--wallpaper-pitch), var(--wallpaper-size), 100% 100%;
  mask-repeat: repeat, no-repeat, no-repeat;
  mask-composite: intersect, exclude;
  image-rendering: pixelated;
}
.wallpaper:is([data-style="regular"], [data-style="gradient"])::before {
  inset: calc(-2 * var(--wallpaper-blur)) calc(-2 * var(--wallpaper-blur)) auto;
  height: calc(100svh + 4 * var(--wallpaper-blur));
  background: var(--wallpaper-image) center / cover no-repeat;
  filter: blur(var(--wallpaper-blur));
}
.wallpaper[data-shown] { opacity: 1; }
body[data-running="true"] .wallpaper[data-shown] { opacity: var(--wallpaper-dim); }
@supports not (color: light-dark(#000, #fff)) { .wallpaper { display: none; } }
/* src/wallpaper.js marks text the picture would make hard to read: a stroke in the ground colour drawn under each
   letter, so it never thins the letter itself. */
[data-halo] { -webkit-text-stroke: var(--wallpaper-halo) var(--ground); paint-order: stroke fill; }

/* Caps Lock warning: sits in the gap under the measure rule, so showing it moves nothing.
   It is a warning, not chrome, so the running fade leaves it alone; it is a readable
   sentence, so it is set in Literata. */
.caps-warning {
  position: absolute;
  top: calc(100% + var(--space-1));
  right: 0;
  margin: 0;
  color: var(--muted);
  font: var(--text-small)/1.5 var(--font-app);
  white-space: nowrap;
  pointer-events: none;
  opacity: 0;
  visibility: hidden;
  transition: opacity var(--dur-base) var(--ease-out), visibility 0s linear var(--dur-base);
}

.caps-warning[data-on="true"] {
  opacity: 1;
  visibility: visible;
  transition: opacity var(--dur-base) var(--ease-out), visibility 0s;
}

/* Next to the passage mid-run, the warning appears and leaves instantly. */
body[data-running="true"] .caps-warning { transition: none; }

/* ── Typing zone ────────────────────────────────────────────────────────── */

.typing-wrap,
.race-typing-wrap,
.online-typing-wrap,
.duel-replay-wrap {
  --passage-size: 36px;
  --typing-line: calc(var(--passage-size) * 1.75);
  position: relative;
  height: calc(var(--typing-line) * 3);
  margin: var(--space-2) 0 var(--space-10);
  overflow: hidden;
  cursor: text;
  -webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 var(--space-3), #000 calc(100% - var(--space-4)), transparent 100%);
  mask-image: linear-gradient(to bottom, transparent 0, #000 var(--space-3), #000 calc(100% - var(--space-4)), transparent 100%);
}

body[data-font-size="22"] :is(.typing-wrap, .compose-wrap) { --passage-size: 29px; }
body[data-font-size="28"] :is(.typing-wrap, .compose-wrap) { --passage-size: 36px; }
body[data-font-size="36"] :is(.typing-wrap, .compose-wrap) { --passage-size: 44px; }

/* A lesson passage is about 60 characters, at most two lines from 901px wide at every text size.
   It reserves two lines there and sits closer to the finger map, so the map's finger names and
   thumbs row stay above the fold on a laptop screen (721px tall and up). */
@media (min-width: 901px) {
  body[data-session="lesson"] .typing-wrap { height: calc(var(--typing-line) * 2); margin-bottom: var(--space-6); }
  body[data-session="lesson"] #keyboard { margin-top: var(--space-4); }
}

/* On the narrowest phones the lesson's name steps down to the learn screen's heading size, one line
   instead of two, so the passage's first line clears the bottom dock without scrolling. */
@media (max-width: 360px) {
  body[data-session="lesson"] #session-title { font-size: var(--text-title); }
}

#text-display,
#race-text-display,
#online-text-display,
#online-replay-display {
  position: relative;
  color: var(--muted);
  font-family: var(--font-typing);
  font-size: var(--passage-size);
  font-weight: 400;
  line-height: var(--typing-line);
  letter-spacing: -0.004em;
  font-variant-ligatures: none;
  white-space: pre-wrap;
  overflow-wrap: break-word;
  transition: transform var(--dur-quick) var(--ease-out);
}

.word { display: inline-block; }
.word > .char { display: inline-block; }
.passage-spacer { display: block; width: 100%; pointer-events: none; }

/* Tape mode: the passage on one line. It advances in word-aligned steps with the same
   line-scroll transition as above, never letter by letter (src/tape.js). Edges fade sideways;
   the left fade appears once typed text has moved past it. */
body[data-tape="true"] .typing-wrap {
  /* The left fade; src/motion.js reads it so letters being corrected stay clear of it. */
  --tape-fade: var(--space-3);
  /* The unwrapped line must not widen the column around it. */
  contain: inline-size;
  height: var(--typing-line);
  -webkit-mask-image: linear-gradient(to right, #000 calc(100% - var(--space-4)), transparent 100%);
  mask-image: linear-gradient(to right, #000 calc(100% - var(--space-4)), transparent 100%);
}

body[data-tape="true"] .typing-wrap[data-tape-scrolled="true"] {
  -webkit-mask-image: linear-gradient(to right, transparent 0, #000 var(--tape-fade), #000 calc(100% - var(--space-4)), transparent 100%);
  mask-image: linear-gradient(to right, transparent 0, #000 var(--tape-fade), #000 calc(100% - var(--space-4)), transparent 100%);
}

body[data-tape="true"] #text-display { white-space: pre; overflow-wrap: normal; }
body[data-tape="true"] #text-display > .passage-spacer { display: inline-block; height: 0; }

.char { position: relative; }
.char.correct { color: var(--text); }

/* Colour plus a tint, so a miss never depends on colour alone. */
.char.wrong {
  color: var(--danger);
  background: var(--danger-soft);
}

/* A space is a plain inline box, shorter than the letter boxes, so its own
   tint would notch the row. Paint it at full line height instead. */
:is(#text-display, #race-text-display, #online-text-display) > .char.wrong { background: none; }
:is(#text-display, #race-text-display, #online-text-display) > .char.wrong::before {
  content: "";
  position: absolute;
  inset-inline: 0;
  top: calc(50% - var(--typing-line) / 2);
  height: var(--typing-line);
  background: var(--danger-soft);
}

/* Word highlight (settings): a quiet tint on the current or next word. It is a letter state like
   the ones above: it moves with the caret from word to word at once, with no transition of its own.
   It is neutral, so it never reads as the green current-state accent or the coral miss. The tint
   sits on the word's letters, not the word, and skips a miss, so a miss keeps exactly its own
   coral tint over whatever the passage sits on. */
:is(body[data-word-highlight="current"] .word.word-current, body[data-word-highlight="next"] .word.word-next) > .char:not(.wrong) {
  background: var(--word-highlight);
  transition: none;
}
:is(body[data-word-highlight="current"] .word.word-current, body[data-word-highlight="next"] .word.word-next) > .char:not(.wrong):first-child {
  border-start-start-radius: var(--radius-sm);
  border-end-start-radius: var(--radius-sm);
}
:is(body[data-word-highlight="current"] .word.word-current, body[data-word-highlight="next"] .word.word-next) > .char:not(.wrong):last-child {
  border-start-end-radius: var(--radius-sm);
  border-end-end-radius: var(--radius-sm);
}

/* Sliding (settings → word highlight → slide): one tint glides from word to word instead (src/motion.js),
   so the letters paint none of their own. It sits behind the letters; a miss paints its coral over it. */
body[data-word-highlight-slide="true"] :is(.word.word-current, .word.word-next) > .char:not(.wrong) { background: none; }
body:not([data-word-highlight-slide="true"]) .word-glide,
body[data-word-highlight="off"] .word-glide { display: none; }

.word-glide {
  position: absolute;
  top: 0;
  left: 0;
  z-index: -1;
  pointer-events: none;
}
.word-glide.is-hidden { visibility: hidden; }
.word-glide::before,
.word-glide > span {
  position: absolute;
  top: 0;
  left: 0;
  width: var(--radius-sm);
  height: 100%;
  background: var(--word-highlight);
}
.word-glide::before { content: ""; border-radius: var(--radius-sm) 0 0 var(--radius-sm); }
.word-glide > :first-child { left: var(--radius-sm); width: 1px; transform-origin: left; }
.word-glide > :last-child { border-radius: 0 var(--radius-sm) var(--radius-sm) 0; }
.word-glide,
.word-glide > span {
  transition: transform var(--dur-quick) var(--ease-out);
  will-change: transform;
}
.word-glide.is-snapping,
.word-glide.is-snapping > span { transition: none; }

/* Forced colours strip the tint, so keep the mark as a system highlight. */
@media (forced-colors: active) {
  .char.wrong,
  :is(#text-display, #race-text-display, #online-text-display) > .char.wrong::before {
    forced-color-adjust: none;
    color: MarkText;
    background: Mark;
  }

  /* The word tint is stripped too; a system-colour outline marks the word instead. */
  body[data-word-highlight="current"] .word.word-current,
  body[data-word-highlight="next"] .word.word-next { outline: 1px solid CanvasText; }
  .word-glide { display: none; }
}

.caret {
  position: absolute;
  top: 0;
  left: 0;
  z-index: 3;
  width: 0;
  height: var(--typing-line);
  pointer-events: none;
  will-change: transform;
}

.caret-bar {
  position: absolute;
  top: 50%;
  left: -1.5px;
  width: 3px;
  height: 1.15em;
  margin-top: calc(var(--typing-baseline) - 0.985em);
  border-radius: var(--radius-full);
  background: var(--accent);
  transition: opacity var(--dur-quick) var(--ease-out);
}

/* The one infinite loop: a slow breathe while the caret waits. */
[data-caret="idle"] .caret-bar { animation: caret-breathe 1300ms ease-in-out infinite; }

@keyframes caret-breathe {
  50% { opacity: 0.25; }
}

[data-blocked="true"] .caret-bar { background: var(--danger); }
[data-caret="done"] .caret-bar { opacity: 0; }

.typing-wrap:not(:focus-within) .caret-bar,
.race-typing-wrap:not(:focus-within) .caret-bar,
.online-typing-wrap:not(:focus-within) .caret-bar { opacity: 0.35; animation: none; }

/* The ghost pacer: a plain thin caret in its own colour. */
.ghost-caret {
  position: absolute;
  top: 0;
  left: 0;
  z-index: 2;
  width: 0;
  height: var(--typing-line);
  pointer-events: none;
  will-change: transform;
  opacity: 0.7;
  transition: opacity var(--dur-quick) var(--ease-out);
}

.ghost-caret::after {
  content: "";
  position: absolute;
  top: 50%;
  left: -1px;
  width: 2px;
  height: 1.15em;
  margin-top: calc(var(--typing-baseline) - 0.985em);
  border-radius: var(--radius-full);
  background: var(--ghost);
}

/* In a ghost race the caret is the opponent, so it is drawn at full strength (3:1 against the page). */
.ghost-caret[data-replay] { opacity: 1; }

.ghost-caret.is-hidden { opacity: 0; }

#typing-input,
#race-input,
#online-input {
  position: absolute;
  inset: 0;
  z-index: 4;
  width: 100%;
  height: 100%;
  padding: 0;
  border: 0;
  outline: 0;
  resize: none;
  overflow: hidden;
  color: transparent;
  background: transparent;
  caret-color: transparent;
  font-size: 16px;
  opacity: 0;
  cursor: text;
}

#typing-input:focus-visible,
#race-input:focus-visible,
#online-input:focus-visible { outline: 0; }

/* ── Progress line ──────────────────────────────────────────────────────── */

.progress-line {
  height: 2px;
  border-radius: var(--radius-full);
  background: var(--hairline);
  overflow: hidden;
}

#progress-bar,
#compose-progress-bar {
  display: block;
  height: 100%;
  background: var(--accent);
  transform: scaleX(var(--progress, 0));
  transform-origin: left center;
  transition: transform calc(140ms * var(--motion-scale)) linear;
}

/* The composing clock ticks every 100 ms, so its line steps with the clock instead of gliding under
   the writing. Never transitioned: a pointer nudge clears data-running while the minute runs on. */
#compose-progress-bar { transition: none; }

/* ── Practice footer, keyboard, focus ───────────────────────────────────── */

.typing-footer {
  min-height: var(--control);
  margin-top: var(--space-4);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
}

.typing-actions {
  display: flex;
  align-items: center;
  flex: none;
  gap: var(--space-1);
}

#input-help,
#input-help-reserve,
#race-help {
  margin: 0;
  color: var(--muted);
  font-size: 15px;
  font-style: italic;
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

/* A mid-run hint shares the passage notice's cell; the hidden reserve keeps the notice's height. */
.input-help-slot { display: grid; }
.input-help-slot > * { grid-area: 1 / 1; }
#input-help-reserve { visibility: hidden; }

.shortcut-row {
  margin-top: var(--space-6);
  display: flex;
  justify-content: center;
  flex-wrap: wrap;
  gap: var(--space-6);
  color: var(--muted);
  font: var(--text-fine)/1 var(--font-mono);
  letter-spacing: 0.03em;
}

.shortcut-row span { display: inline-flex; align-items: center; gap: 6px; }
.shortcut-row kbd { margin-right: 2px; }
.shortcut-row .compose-shortcut,
body[data-compose="true"] .shortcut-row .practice-shortcut { display: none; }
body[data-compose="true"] .shortcut-row .compose-shortcut { display: inline-flex; }

/* ── Composing ──────────────────────────────────────────────────────────── */

/* A prompt and an open writing field in the passage's place: no target text, no box. */
.compose-form { width: 100%; }

/* The prompt sits left and the clock right, unlike open practice, which shows only numbers. */
body[data-session="practice"] .compose-measure { justify-content: space-between; }
.compose-intro { min-width: 0; display: grid; gap: var(--space-2); }

.compose-prompt {
  max-width: 34ch;
  margin: 0;
  font-size: var(--text-title);
  line-height: 1.2;
  letter-spacing: -0.015em;
}

.compose-side {
  flex: none;
  display: flex;
  align-items: flex-end;
  gap: var(--space-6);
}

.compose-wrap {
  --compose-line: calc(var(--passage-size) * 1.6);
  margin: var(--space-2) 0 var(--space-6);
}

/* Three lines like the passage, scrolling inside, so the page never moves while you write. No
   scrollbar appears beside the writing, and the caret holds still instead of blinking. */
#compose-input {
  display: block;
  width: 100%;
  height: calc(var(--compose-line) * 3);
  margin: 0;
  padding: 0;
  border: 0;
  border-radius: 0;
  outline: 0;
  resize: none;
  overflow-y: auto;
  scrollbar-width: none;
  background: transparent;
  color: var(--text);
  caret-color: var(--accent);
  caret-animation: manual;
  font-family: var(--font-typing);
  font-size: var(--passage-size);
  font-weight: 400;
  line-height: var(--compose-line);
  letter-spacing: -0.004em;
}

#compose-input::-webkit-scrollbar { display: none; }
#compose-input::placeholder { color: var(--muted); opacity: 1; font-style: italic; }

/* Where `caret-animation` isn't supported, src/compose-caret.js makes the native caret transparent
   (it still edits, selects and composes) and draws this one in its place: the accent, solid, moved
   instantly and never animated. Chromium keeps the native caret above and none of this applies. */
.compose-wrap[data-caret="drawn"] { position: relative; }
.compose-wrap[data-caret="drawn"] #compose-input { caret-color: transparent; }
.compose-caret-layer { position: absolute; overflow: hidden; pointer-events: none; }

.compose-caret-mirror {
  position: absolute;
  top: 0;
  left: 0;
  box-sizing: border-box;
  margin: 0;
  border-style: solid;
  border-color: transparent;
  visibility: hidden;
}

.compose-caret {
  position: absolute;
  top: 0;
  left: -1px;
  width: 2px;
  background: var(--accent);
  pointer-events: none;
  animation: none;
  transition: none;
}

@media (forced-colors: active) {
  .compose-caret { forced-color-adjust: none; background: CanvasText; }
}

/* Finished: the writing and the clock stop being "current". */
.compose-form[data-state="done"] #compose-input { color: var(--muted); }
.compose-form[data-state="done"] .timer-stat strong { color: var(--muted); }

.compose-notes { min-width: 0; display: grid; gap: var(--space-1); }
.compose-help,
.compose-privacy { margin: 0; color: var(--muted); font-size: var(--text-small); }
.compose-help { font-style: italic; }
.compose-help:empty { display: none; }

.compose-result { padding-top: var(--space-6); display: grid; gap: var(--space-4); }
.compose-result h2 { margin: 0; font-size: var(--text-lead); line-height: 1.3; }

.compose-facts {
  margin: 0;
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--space-4);
}

.compose-facts div { display: flex; flex-direction: column; justify-content: space-between; gap: var(--space-1); }
.compose-facts dt { color: var(--muted); font: var(--text-fine)/1.3 var(--font-mono); letter-spacing: 0.02em; }

.compose-facts dd {
  margin: 0;
  display: flex;
  align-items: baseline;
  gap: var(--space-1);
  color: var(--muted);
  font: var(--text-body)/1.2 var(--font-app);
}

.compose-facts strong {
  color: var(--text);
  font-family: var(--font-app);
  font-size: var(--text-display);
  font-weight: 300;
  line-height: 1;
  letter-spacing: -0.035em;
}

.compose-result .metric-note { max-width: 72ch; }

.compose-result-foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3) var(--space-6);
}

.compose-saved { margin: 0; color: var(--muted); font-size: var(--text-small); }
.compose-result-actions { display: flex; flex-wrap: wrap; gap: var(--space-2); }

/* Composing has no best, so the footer doesn't show the copy-test one beside it. */
body[data-compose="true"] .personal-best { visibility: hidden; }

.keyboard {
  --key: 40px;
  --key-gap: 5px;
  --key-pitch: calc(var(--key) + var(--key-gap));
  margin: var(--space-6) auto var(--space-4);
  display: grid;
  justify-content: center;
  gap: var(--key-gap);
}

/* The thirty main keys keep a real row stagger: home a quarter key in, bottom three quarters. */
.key-row { display: flex; gap: var(--key-gap); }
.key-row:nth-child(2) { padding-inline-start: calc(var(--key-pitch) / 4); }
.key-row:nth-child(3) { padding-inline-start: calc(var(--key-pitch) * 3 / 4); }
.key-row:nth-child(4) { justify-content: center; }

.key {
  width: var(--key);
  height: var(--key);
  border: 1px solid var(--hairline-strong);
  border-bottom-width: 3px;
  border-radius: var(--radius);
  display: grid;
  place-items: center;
  color: var(--muted);
  font: 500 13px/1 var(--font-mono);
  transition: color var(--dur-micro) var(--ease-out), background-color var(--dur-micro) var(--ease-out), transform var(--dur-micro) var(--ease-out), border-color var(--dur-micro) var(--ease-out);
}

.key.space { width: calc(var(--key) * 6); }
.key.target { color: var(--text); border-color: color-mix(in oklab, var(--accent) 55%, transparent); }
.key.active { color: var(--accent-ink); background: var(--accent); border-color: var(--accent); transform: translateY(2px); }

/* Finger map: each key carries a small mono initial for the finger that types it (i, m, r, p), so it
   reads without shading, in forced colours too. A faint band behind alternate fingers (index, ring)
   keeps neighbouring fingers apart, and the two hands sit apart. Letters keep their colours, so drill
   targets still stand out. :where() keeps these below .key.target and .key.active. */
:where(.keyboard[data-fingers="true"]) [data-finger="index"] { --finger-mark: "i"; --finger-shade: var(--finger-band); }
:where(.keyboard[data-fingers="true"]) [data-finger="middle"] { --finger-mark: "m"; }
:where(.keyboard[data-fingers="true"]) [data-finger="ring"] { --finger-mark: "r"; --finger-shade: var(--finger-band); }
:where(.keyboard[data-fingers="true"]) [data-finger="pinky"] { --finger-mark: "p"; }
:where(.keyboard[data-fingers="true"]) .key { position: relative; background-color: var(--finger-shade, transparent); }
:where(.keyboard[data-fingers="true"]) .key::after {
  content: var(--finger-mark) / "";
  position: absolute;
  right: var(--space-1);
  bottom: 1px;
  color: var(--muted);
  font: 400 var(--text-fine)/1 var(--font-mono);
}
.key.active::after { color: inherit; }
:where(.keyboard[data-fingers="true"]) .key[data-column="5"] { margin-inline-start: var(--space-3); }

.finger-legend {
  margin: var(--space-2) 0 0;
  display: flex;
  justify-content: center;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-4);
  color: var(--muted);
  font: var(--text-fine)/1 var(--font-mono);
  letter-spacing: 0.03em;
}

.finger-legend span { display: inline-flex; align-items: center; gap: var(--space-2); }
.finger-legend [data-finger]::before {
  content: var(--finger-mark) / "";
  width: var(--space-6);
  height: var(--space-6);
  border: 1px solid var(--hairline-strong);
  border-radius: var(--radius-sm);
  display: grid;
  place-items: center;
  background: var(--finger-shade, transparent);
}

/* The finger map: a split keyboard, one column per finger, labelled under each column with a rule.
   Learned keys read in the text colour; the lesson's new keys get the full green edge. */
.finger-map {
  --key-size: 40px;
  grid-template-columns: auto auto;
  column-gap: var(--space-6);
  row-gap: var(--space-2);
}

.finger-hand {
  display: grid;
  grid-template-columns: repeat(5, var(--key-size));
  grid-template-rows: repeat(3, var(--key-size)) auto;
  gap: 5px;
}

.finger-hand .finger-label { align-self: start; }
.finger-map .key { width: auto; height: auto; }
.finger-map .key.is-known { color: var(--text); }
/* The caption points at this edge, so it is the full accent (3:1 or more against the ground in both themes). */
.finger-map .key.target { border-color: var(--accent); }

/* A name wider than its column ("middle") overflows evenly on both sides, and without extra letter
   spacing it keeps more than a space's width from the next name. */
.finger-label {
  padding-top: var(--space-1);
  border-top: 1px solid var(--hairline-strong);
  display: flex;
  justify-content: center;
  color: var(--muted);
  font: var(--text-fine)/1.3 var(--font-mono);
  white-space: nowrap;
}

.finger-label-short,
.finger-map > .finger-legend { display: none; }

.finger-map > .finger-legend { margin-top: var(--space-1); color: var(--muted); font: var(--text-fine)/1.4 var(--font-mono); text-align: center; }

.finger-thumbs {
  grid-column: 1 / -1;
  justify-self: center;
  display: flex;
  align-items: center;
  gap: var(--space-3);
}

/* Space sits centred under the two hands, with its label beside it. */
.finger-thumbs .key.space { width: calc(var(--key-size) * 6); height: var(--key-size); margin-left: calc(var(--space-3) + 6ch); }
.finger-thumbs .finger-label { width: 6ch; padding: 0; border: 0; justify-content: flex-start; }
.finger-map > .finger-legend { grid-column: 1 / -1; }

body[data-focus-mode="true"] .dock-zone,
body[data-focus-mode="true"] .corner,
body[data-focus-mode="true"] .practice-controls,
body[data-focus-mode="true"] .quote-options,
body[data-focus-mode="true"] .standard-options,
body[data-focus-mode="true"] .shortcut-row,
body[data-focus-mode="true"] .site-footer,
body[data-focus-mode="true"] .session-intro,
body[data-focus-mode="true"] .compose-another { visibility: hidden; }

.exit-focus {
  position: fixed;
  z-index: 45;
  top: var(--space-4);
  right: var(--space-4);
  display: none;
}

body[data-focus-mode="true"][data-view="practice"] .exit-focus { display: inline-flex; }

/* ── Results: the run card, on Practice in place of the passage ──────────── */

/* body[data-view="results"] is the practice screen showing a finished run: the rail stays, the passage
   and its footer give way to the card. */
.practice-result { display: none; }
body[data-view="results"] .practice-result { display: grid; }
body[data-view="results"] :is(.typing-form, .shortcut-row) { display: none; }

/* One card, centred under the rail; what follows it (a ghost race, its offer) keeps the card's width. */
.results-layout {
  grid-template-columns: minmax(0, var(--result-card));
  justify-content: center;
  gap: var(--space-8);
  align-items: start;
}

/* A counted run's headline is for screen readers only; with no note either, the heading takes no row. */
.result-heading:has(> h2.sr-only):has(> .result-note:empty) { display: contents; }
.result-heading h2 { margin: 0 0 var(--space-2); font-size: var(--text-title); font-weight: 400; line-height: 1.2; letter-spacing: -0.015em; }

/* A static print on card stock: the score and its facts, the trace, the words, then the actions. */
.run-card {
  min-width: 0;
  padding: var(--space-8);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-lg);
  background: linear-gradient(180deg, var(--card-top), var(--card-bottom));
  display: grid;
  gap: var(--space-8);
}

/* The actions under a hairline, the conditions line set against them on the right. */
.result-actions {
  padding-top: var(--space-8);
  border-top: 1px solid var(--hairline);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4) var(--space-6);
}
.result-buttons { display: flex; flex-wrap: wrap; gap: var(--space-2); }
.result-buttons .btn { font-size: var(--text-body); font-weight: 400; }
.result-buttons .btn-primary { font-weight: 500; }
.result-buttons .btn:not(.btn-ghost) { padding-inline: var(--space-6); }
.result-actions .condition-copy { flex: 1 1 var(--result-conditions); text-align: end; letter-spacing: 0.02em; }

.result-more { min-width: 0; display: grid; gap: var(--space-8); }
.result-more:not(:has(> :not([hidden]))) { display: none; }

/* ── Train: one drill, then its comparison ──────────────────────────────── */

.train-block {
  padding-top: calc(var(--space-16) * 2);
  display: grid;
  justify-items: center;
  text-align: center;
}

.train-block h1 {
  margin: 0;
  font-size: var(--text-display);
  font-weight: 400;
  line-height: 1.08;
  letter-spacing: -0.025em;
  overflow-wrap: anywhere;
}

/* The words the pair was missed in, the pair in coral. */
.train-words {
  margin: var(--space-4) 0 0;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-1) var(--space-6);
  font-size: var(--text-lead);
}
.train-words b { color: var(--danger); font-weight: inherit; }
.train-evidence { margin: var(--space-1) 0 0; font-size: var(--text-small); font-variant-numeric: tabular-nums lining-nums; }
.train-actions { margin-top: var(--space-8); display: flex; flex-wrap: wrap; justify-content: center; gap: var(--space-2); }
.train-last { margin: var(--space-14) 0 0; color: var(--muted); font-size: var(--text-small); font-variant-numeric: tabular-nums lining-nums; }

/* The comparison: three figures over a hairline, deltas muted. */
.train-block .comparison-rows {
  width: min(720px, 100%);
  margin: var(--space-10) 0 0;
  padding-top: var(--space-6);
  border-top: 1px solid var(--hairline);
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(0, 1fr));
  gap: var(--space-6);
}
.train-block .comparison-rows > div { display: grid; gap: var(--space-2); }
.train-block .comparison-rows > div[hidden] { display: none; }
.train-block .comparison-rows dt { color: var(--muted); font: var(--text-fine)/1.3 var(--font-mono); letter-spacing: 0.02em; }
.train-block .comparison-rows dd { margin: 0; display: grid; gap: var(--space-1); }
.train-block .comparison-rows strong { font-size: var(--text-title); font-weight: 400; line-height: 1.1; letter-spacing: -0.015em; }
.train-block .comparison-rows strong b { color: var(--muted); font-size: var(--text-small); font-weight: 400; margin-left: 1px; }
.train-block .comparison-rows dd > span { color: var(--muted); font-size: var(--text-small); font-variant-numeric: tabular-nums lining-nums; }
.train-block .condition-copy { margin: var(--space-6) 0 0; }
.train-block .condition-copy:empty { display: none; }
/* Focus lands on the outcome so it is read out; it isn't a control, so it draws no ring. */
#comparison-status:focus-visible { outline: none; }
#comparison-caveat { max-width: 60ch; margin: var(--space-4) 0 0; color: var(--muted); font-size: var(--text-small); }
#comparison-caveat:empty { display: none; }
.comparison-panel.is-uncompared .comparison-rows { display: none; }
#comparison-panel > .ghost-offer { width: min(720px, 100%); margin-top: var(--space-10); text-align: start; }

.run-card-graph { margin: 0; }

.speed-graph { height: var(--result-trace); }
/* Drawn at the container's pixel size. Capping it at the container keeps the old drawing from
   holding the card at its old width when the window narrows, so the observer sees the new width
   and redraws; until then it scales evenly rather than stretching. */
.speed-graph svg { display: block; max-width: 100%; height: auto; overflow: visible; }

.graph-axis { stroke: var(--hairline-strong); stroke-width: 1; }
.graph-grid { stroke: var(--hairline); stroke-width: 1; }
.graph-average { stroke: var(--muted); stroke-width: 1; stroke-dasharray: 2 4; }
.graph-label { fill: var(--muted); font: var(--text-fine) var(--font-mono); font-variant-numeric: tabular-nums; }
.graph-line { fill: none; stroke: var(--accent); stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.graph-miss { fill: var(--danger); stroke: var(--card-top); stroke-width: 2; }

/* The line draws once, left to right, and the mistakes fade in to finish with it. */
.is-drawing .graph-line { stroke-dasharray: 1; animation: graph-draw var(--dur-develop) var(--ease-out) both; }
.is-drawing .graph-miss { animation: graph-fade var(--dur-base) var(--ease-out) calc(var(--dur-develop) - var(--dur-base)) both; }
@keyframes graph-draw { from { stroke-dashoffset: 1; } to { stroke-dashoffset: 0; } }
@keyframes graph-fade { from { opacity: 0; } }

/* The score on the left; on the right the facts in one row, and under them the average and best. */
.run-card-head {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  align-items: end;
  gap: var(--space-6) 0;
}

/* A speed too wide for the row puts its unit underneath instead of pushing past the card. */
.run-card-score {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-3);
}

#result-wpm {
  font-family: var(--font-app);
  font-size: var(--text-numeral);
  font-weight: 400;
  line-height: 0.76;
  letter-spacing: -0.035em;
}

.run-card-unit { color: var(--muted); font: italic var(--text-lead)/1 var(--font-app); }

/* On a wide card the figure's side bearing pulls it back onto the card's left edge; a phone keeps the
   whole box inside the padding. */
@media (min-width: 761px) {
  #result-wpm { margin-inline-start: -0.09em; }
}

.run-card-side { min-width: 0; padding-bottom: var(--space-2); display: grid; gap: var(--space-3); }

.run-card-facts {
  margin: 0;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--space-3) 0;
}

/* Values sit on one line even when a label wraps; a cell never widens its track. */
.run-card-facts div { display: flex; flex-direction: column; justify-content: space-between; gap: var(--space-1); min-width: 0; }
.run-card-facts dt { color: var(--muted); font: 500 var(--text-mono)/1.35 var(--font-mono); letter-spacing: 0.04em; }
.run-card-facts dd { margin: 0; font-size: 24px; font-weight: 400; line-height: 1.15; }
.run-card-facts strong { font-weight: inherit; }
.run-card-facts b { color: var(--muted); font-size: 16px; font-weight: 400; margin-left: 1px; }

.run-card-standing { margin: 0; color: var(--muted); font: var(--text-small)/1.35 var(--font-app); font-variant-numeric: tabular-nums lining-nums; }

/* Words reached, static: plain ink, mistyped words in coral on the error tint. Past four lines it
   scrolls. One word at a time is the tab stop; the chosen word is underlined and its pace shows in
   the line below. The padding (offset by the margin) keeps a focused word's ring inside the scroller. */
.run-card-wordlist { display: grid; justify-items: start; gap: var(--space-3); }
.run-card-words {
  justify-self: stretch;
  margin: calc(var(--space-1) * -1) calc(var(--space-2) * -1);
  padding: var(--space-1) var(--space-2);
  max-height: calc(var(--text-body) * 1.6 * 4 + var(--space-1) * 2);
  overflow-y: auto;
  color: var(--muted);
  font: var(--text-body)/1.6 var(--font-app);
}
.run-card-words span { color: var(--text); border-radius: var(--radius-sm); cursor: pointer; }
.run-card-words .word-miss { padding-inline: var(--space-1); color: var(--danger); background: var(--danger-soft); }
.run-card-words [aria-selected="true"] { text-decoration: underline; text-underline-offset: 3px; }
.word-detail { margin: 0; color: var(--muted); font: var(--text-small)/1.35 var(--font-app); font-variant-numeric: tabular-nums lining-nums; }
.word-detail:empty { display: none; }

/* The way into word practice reads as a green line of text, with a full-height target under touch. */
.run-card-practice {
  margin: 0;
  padding: 0;
  border: 0;
  border-radius: var(--radius-sm);
  background: none;
  color: var(--accent);
  font: var(--text-small)/1.35 var(--font-app);
  cursor: pointer;
}
.run-card-practice:hover { text-decoration: underline; text-underline-offset: 3px; }

.metric-note {
  margin: 0;
  color: var(--muted);
  font-size: 13px;
  font-style: italic;
  line-height: 1.45;
}
@media (pointer: coarse) { .run-card-practice { min-height: var(--control); } }

.section-heading h1 {
  margin: 0 0 var(--space-3);
  font-size: var(--text-display);
  line-height: 1.08;
  letter-spacing: -0.025em;
}

.section-heading p {
  max-width: 58ch;
  margin: 0;
  color: var(--muted);
}

.result-note { font-size: var(--text-small); }
.result-note:empty { display: none; }

/* The ghost race result reads like the race results, under a hairline. */
.ghost-result {
  padding-top: var(--space-8);
  border-top: 1px solid var(--hairline);
}

.ghost-result h2 {
  margin: 0;
  font-size: var(--text-title);
  line-height: 1.2;
  letter-spacing: -0.015em;
  overflow-wrap: anywhere;
}

/* A quiet disclosure: the run card's "how counted" and the ghost race offer. */
.manual-panel {
  padding-top: var(--space-4);
  border-top: 1px solid var(--hairline);
}

.manual-panel > summary {
  min-height: var(--control);
  width: fit-content;
  display: flex;
  align-items: center;
  gap: var(--space-3);
  color: var(--muted);
  cursor: pointer;
  list-style: none;
  transition: color var(--dur-quick) var(--ease-out);
}

.manual-panel > summary::-webkit-details-marker { display: none; }

.manual-panel > summary::before {
  content: "";
  width: 7px;
  height: 7px;
  flex: none;
  border-right: 1.5px solid currentColor;
  border-bottom: 1.5px solid currentColor;
  transform: translateY(-2px) rotate(45deg);
  transition: transform var(--dur-quick) var(--ease-out);
}

.manual-panel[open] > summary::before { transform: translateY(2px) rotate(-135deg); }

.manual-panel > summary:hover,
.manual-panel[open] > summary { color: var(--text); }

.manual-panel > form { margin-top: var(--space-3); }

.input-action { display: flex; gap: var(--space-2); max-width: 460px; }
.input-action .input { flex: 1; }

/* Ghost races: the offer is another quiet disclosure; the result reads like the race results. */
.ghost-offer-copy { max-width: 60ch; margin: var(--space-3) 0 0; color: var(--muted); font-size: var(--text-small); }
.ghost-offer-form { margin-top: var(--space-4); display: flex; flex-wrap: wrap; align-items: flex-end; gap: var(--space-3) var(--space-4); }
.ghost-offer-form .filter-field { flex: 1 1 180px; }
#ghost-offer-empty .btn { margin-top: var(--space-3); }
.ghost-offer > .result-note { margin: var(--space-3) 0 0; }
.ghost-offer > .field-error:empty { display: none; }

.ghost-result .race-results { margin-top: var(--space-6); }
.ghost-result .race-result-row { grid-template-columns: minmax(0, 1fr) repeat(3, auto); gap: var(--space-2) var(--space-6); padding: var(--space-4) 0; }
.ghost-result .race-result-row strong { overflow-wrap: anywhere; }
.ghost-result > p { margin: var(--space-4) 0 0; color: var(--muted); font-size: var(--text-small); }
.ghost-result > p:empty { display: none; }
.ghost-result > .btn { margin-top: var(--space-4); }

/* ── Progress ───────────────────────────────────────────────────────────── */

.page-heading {
  margin-bottom: var(--space-10);
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--space-8);
}

.page-heading h1 { font-size: 56px; }

.empty-state {
  padding: var(--space-10) 0;
  border-top: 1px solid var(--hairline);
  display: grid;
  justify-items: start;
  gap: var(--space-3);
}

.empty-state h2 { margin: 0; font-size: var(--text-title); letter-spacing: -0.015em; }
.empty-state p { max-width: 56ch; margin: 0 0 var(--space-4); color: var(--muted); }

.history-list { border-top: 1px solid var(--hairline); }

.history-row {
  padding: var(--space-6) 0;
  border-bottom: 1px solid var(--hairline);
  display: block;
}

.history-summary {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 150px 150px 110px;
  align-items: center;
  gap: var(--space-6);
}

.history-main { min-width: 0; display: grid; gap: 4px; }
.history-date { color: var(--muted); font: var(--text-mono)/1.4 var(--font-mono); }
.history-target { font-size: 22px; font-weight: 400; }
.history-conditions { color: var(--muted); font: var(--text-fine)/1.5 var(--font-mono); }
.history-measure { display: grid; gap: 4px; }
.history-measure span { color: var(--muted); font: var(--text-fine)/1.3 var(--font-mono); }
.history-measure strong { font-size: 24px; font-weight: 400; letter-spacing: -0.02em; }
.history-status { justify-self: end; }

.history-summary, .run-summary { cursor: pointer; list-style: none; }
.history-summary::-webkit-details-marker, .run-summary::-webkit-details-marker { display: none; }
.history-summary:hover .history-target, .run-summary:hover strong { color: var(--accent); }
.history-row[open] .history-target { color: var(--accent); }
.history-detail { padding-top: var(--space-6); }
.history-detail p { color: var(--muted); }
.history-detail dl, .run-detail dl { display: flex; flex-wrap: wrap; gap: var(--space-6); }
.history-detail dt, .run-detail dt { font: var(--text-mono)/1.5 var(--font-mono); color: var(--muted); }
.history-detail dd, .run-detail dd { margin: var(--space-2) 0 0; font-size: var(--text-lead); }
/* Standard score: the one fixed recipe's best and recent median. It follows the filtered figures and
   charts when there is history, and heads the page above the empty state when there is none. */
.standard-score {
  margin-bottom: var(--space-8);
  padding-bottom: var(--space-8);
  border-bottom: 1px solid var(--hairline);
  display: grid;
  gap: var(--space-3);
}

.standard-score-heading { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--space-3) var(--space-4); }
.standard-score h2 { margin: 0; font-size: var(--text-title); font-weight: 400; }
.standard-figures { margin: var(--space-2) 0 0; display: flex; flex-wrap: wrap; gap: var(--space-4) var(--space-14); }
.standard-figures dt { color: var(--muted); font: var(--text-mono)/1.4 var(--font-mono); }
.standard-figures dd { margin: var(--space-2) 0 0; display: flex; align-items: baseline; gap: 6px; color: var(--muted); font: var(--text-body)/1.2 var(--font-app); }
/* Longhands, so the shorthand can't reset .num's tabular figures. */
.standard-figures strong { color: var(--text); font-family: var(--font-app); font-size: var(--text-display); font-weight: 300; line-height: 1; letter-spacing: -0.035em; }
.standard-score p { max-width: 64ch; margin: 0; }
.standard-score .result-note { color: var(--muted); }
/* With nothing scored, the section is one line: its name, the note, the action. */
.standard-score.is-empty { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--space-2) var(--space-4); }
.standard-score.is-empty .standard-score-heading { display: contents; }
.standard-score.is-empty #take-standard { order: 2; margin-left: auto; align-self: center; }
.standard-score.is-empty #standard-score-note { order: 1; }
.standard-figures[hidden] { display: none; }
/* The standard score's rule already divides it from the empty state, so the empty state drops its own. */
.standard-score + .empty-state { padding-top: 0; border-top: 0; }

.pill-row { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); }
.pill {
  min-height: var(--control);
  padding: 0 var(--space-4);
  border: 1px solid var(--hairline-strong);
  border-radius: var(--radius-full);
  background: transparent;
  color: var(--text);
  font-family: var(--font-app);
  font-size: var(--text-small);
  line-height: 1;
  white-space: nowrap;
  font-variant-numeric: tabular-nums lining-nums;
  transition: background-color var(--dur-quick) var(--ease-out), border-color var(--dur-quick) var(--ease-out);
}
.pill:hover:not(:disabled) { border-color: var(--outline); }
.pill:is([aria-pressed="true"], [aria-selected="true"]) { background: var(--accent-soft); border-color: var(--accent); }
/* A disabled pill keeps its hairline; only the text dims, to --outline, which holds 3:1 on both grounds. */
.pill:disabled { color: var(--outline); cursor: default; }
.pill-rule { width: 1px; height: var(--space-6); margin: 0 var(--space-2); background: var(--hairline-strong); }
.view-pill { font-size: var(--text-body); }
.progress-heading { align-items: center; }
.progress-heading h1 { margin: 0; font-size: var(--text-display); }
.progress-filters { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--space-3) var(--space-6); margin-bottom: var(--space-3); }
.progress-filters > .pill-row { min-width: 0; max-width: 100%; }
#progress-tests { flex: 1; flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none; }
#progress-tests > * { flex: none; }
#progress-period { flex-wrap: nowrap; }
#progress-tests:empty { display: none; }
.variant-select { field-sizing: content; min-width: var(--control); max-width: 100%; height: var(--control); padding: 0 var(--space-2) 0 0; border: 0; border-radius: var(--radius-sm); background: transparent; color: var(--muted); font: var(--text-mono)/1.4 var(--font-mono); text-overflow: ellipsis; transition: color var(--dur-quick) var(--ease-out); }
.variant-select:hover, .variant-select:focus-visible { color: var(--text); }
.variant-select option { background: var(--raised); color: var(--text); }
.conditions-disclosure { min-width: 0; max-width: 100%; margin-left: auto; }
.conditions-disclosure > summary { display: inline-flex; align-items: center; gap: var(--space-2); min-height: var(--control); color: var(--muted); font: var(--text-mono)/1.4 var(--font-mono); cursor: pointer; list-style: none; transition: color var(--dur-quick) var(--ease-out); }
.conditions-disclosure > summary::-webkit-details-marker { display: none; }
.conditions-disclosure > summary:hover { color: var(--text); }
.conditions-disclosure[open] { flex-basis: 100%; }
.conditions-disclosure[open] > summary { display: flex; width: fit-content; margin-left: auto; }
.progress-conditions { margin: 0 0 var(--space-2); }
/* Overview and practice figures: no boxes. A label, the numeral in Literata with its unit, a line under it. */
.progress-stats { margin: var(--space-8) 0 var(--space-4); display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: var(--space-6) var(--space-6); }
.progress-figure { min-width: 0; display: grid; align-content: start; gap: var(--space-1); }
.progress-figure dt { color: var(--muted); font-size: var(--text-small); }
.progress-figure dd { margin: 0; }
.figure-value { display: flex; align-items: baseline; gap: var(--space-2); font-size: var(--text-display); font-weight: 300; line-height: 1.1; letter-spacing: -0.02em; white-space: nowrap; }
#progress-stats .figure-value { font-size: var(--text-title); }
.figure-value [data-speed-unit], .figure-unit { color: var(--muted); font-size: var(--text-body); letter-spacing: 0; }
.figure-note { color: var(--muted); font-size: var(--text-small); }
/* Progress opens on the filtered runs: the filters and the period's figures, then the views, ruled off
   from the standard score below. */
#progress-content { padding-bottom: var(--space-4); }
.progress-views { margin-bottom: var(--space-8); padding: var(--space-8) 0; border-top: 1px solid var(--hairline); border-bottom: 1px solid var(--hairline); }
#progress-content[hidden] + .progress-views { border-top: 0; padding-top: 0; }
.view-tabs { margin-bottom: var(--space-6); }
.view-heading { margin: 0 0 var(--space-3); font-size: var(--text-lead); font-weight: 400; }
.view-panel:focus-visible { outline-offset: var(--space-2); }
/* Daily goal: static calendar, accent at four steps (--practice-level-1…3, then --accent); empty days sit on the hairline. */
.practice-goal { display: grid; gap: var(--space-8); }
/* The calendar fills the column, the practice figures in one row under it. */
.practice-layout { display: grid; gap: var(--space-8); }
.practice-calendar { min-width: 0; display: grid; gap: var(--space-3); }
.practice-stats { margin: 0; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: var(--space-6); }
.goal-chart { display: grid; grid-template-columns: auto minmax(0, 1fr); grid-template-areas: ". months" "weekdays calendar"; gap: var(--space-1) var(--space-2); }
.goal-months { grid-area: months; display: grid; grid-template-columns: repeat(var(--weeks, 26), minmax(0, 1fr)); gap: var(--space-1); color: var(--muted); font: var(--text-fine)/1.4 var(--font-mono); white-space: nowrap; }
.goal-weekdays { grid-area: weekdays; display: grid; grid-template-rows: repeat(7, minmax(0, 1fr)); gap: var(--space-1); align-items: center; color: var(--muted); font: var(--text-fine)/1 var(--font-mono); }
.goal-calendar {
  grid-area: calendar;
  display: grid; grid-template-columns: repeat(var(--weeks, 26), minmax(0, 1fr)); grid-template-rows: repeat(7, auto); grid-auto-flow: column;
  gap: var(--space-1);
}
.goal-legend { margin: 0; justify-self: end; display: flex; align-items: center; gap: var(--space-1); color: var(--muted); font-size: var(--text-label); }
.goal-legend span { width: var(--space-3); height: var(--space-3); border-radius: var(--radius-cell); background: var(--hairline); }
.goal-legend span:first-child { margin-left: var(--space-1); }
.goal-legend span:last-child { margin-right: var(--space-1); }
.goal-day-picker { display: none; }
.goal-day-picker .input { width: 100%; min-width: 0; max-width: 100%; }
.goal-day { aspect-ratio: 1; display: grid; place-items: center; border-radius: var(--radius-cell); background: var(--hairline); }
:is(.goal-day, .goal-legend span)[data-level="1"] { background: var(--practice-level-1); }
:is(.goal-day, .goal-legend span)[data-level="2"] { background: var(--practice-level-2); }
:is(.goal-day, .goal-legend span)[data-level="3"] { background: var(--practice-level-3); }
:is(.goal-day, .goal-legend span)[data-level="4"] { background: var(--accent); }
/* Goal met: a centre dot in accent-ink (≥3:1 on both green steps in both themes), so it never rests on colour alone. */
.goal-day[data-goal="met"]::after { content: ""; width: var(--space-1); height: var(--space-1); border-radius: var(--radius-full); background: var(--accent-ink); }
/* The focused or hovered day, in words. One line is reserved so the page below never shifts. */
.goal-caption { min-height: 1lh; margin: 0; color: var(--muted); font-size: var(--text-small); font-variant-numeric: tabular-nums; }
.filter-field { min-width: 0; display: grid; gap: var(--space-2); font-size: var(--text-small); }
.filter-field select { width: 100%; max-width: 100%; text-overflow: ellipsis; }
.progress-trend { display: grid; gap: 0; }
.progress-spread { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--space-10); }
.trend-figure { margin: 0; min-width: 0; display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: baseline; column-gap: var(--space-4); }
.trend-figure > svg, .trend-subline { grid-column: 1 / -1; }
.trend-figure figcaption { margin-bottom: var(--space-3); display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--space-1) var(--space-3); font-size: var(--text-lead); }
.trend-subline { margin: calc(var(--space-3) * -1) 0 var(--space-3); color: var(--muted); font-size: var(--text-small); }
.trend-caption-note { color: var(--muted); font: var(--text-mono)/1.4 var(--font-mono); }
.trend-legend { margin: 0; display: flex; align-items: center; gap: var(--space-2); color: var(--muted); font-size: var(--text-label); white-space: nowrap; }
.legend-line { width: var(--space-4); height: 2px; background: var(--accent); }
.legend-dot { width: var(--space-1); height: var(--space-1); margin-left: var(--space-3); border-radius: var(--radius-full); background: var(--outline); }
.trend-figure svg { width: 100%; height: auto; overflow: visible; }
.trend-rule { stroke: var(--hairline-strong); }
.trend-label { fill: var(--muted); font: var(--text-mono) var(--font-mono); }
.trend-callout { fill: var(--text); font: var(--text-label) var(--font-app); pointer-events: none; }
.trend-runs circle, .scatter-runs circle { fill: var(--outline); }
.trend-runs circle { opacity: 0.6; }
.trend-line { fill: none; stroke-width: 2; stroke: var(--accent); stroke-linejoin: round; }
/* The days' points show where they are named (the latest and the best) and when hovered or focused. */
.trend-dot { fill: var(--ground); stroke: var(--accent); stroke-width: 2; cursor: pointer; }
.trend-dot circle { opacity: 0; transition: opacity var(--dur-quick) var(--ease-out); }
.trend-dot:is(.trend-last-dot, .trend-best-dot, :hover, :focus) circle { opacity: 1; }
.trend-hit { fill: transparent; stroke: none; }
.trend-dot:hover, .trend-dot:focus { fill: var(--accent); stroke: var(--text); }
.spread-band { fill: var(--accent-soft); }
.spread-band-edge { stroke: var(--accent); stroke-width: 1; }
.spread-count-rule { stroke: var(--hairline); }
.spread-bar { fill: var(--outline); opacity: 0.6; }
.spread-median { stroke: var(--accent); stroke-width: 2; }
.strip-bar { fill: var(--outline); opacity: 0.6; }
.progress-danger { padding-top: var(--space-8); border-top: 1px solid var(--hairline); }
.progress-danger:has(> [hidden]) { display: none; }
.progress-section, .training-memory, .daily-challenge { padding: var(--space-8) 0; border-top: 1px solid var(--hairline); }
/* The standard score's rule and space already divide it from the first history section. */
#progress-history > .progress-section:first-child { padding-top: 0; border-top: 0; }
.progress-section h2, .training-memory h2, .daily-challenge h2 { margin: 0 0 var(--space-4); font-size: var(--text-title); font-weight: 400; }

/* Keys: static heatmap, coral steps only, no motion. Rows stagger by half a key, then a whole key. */
.key-heatmap { justify-content: center; gap: var(--heat-gap); margin: var(--space-4) 0 var(--space-3); }
.key-heatmap .key-row { justify-content: start; gap: var(--heat-gap); }
.key-heatmap .key-row:nth-child(2) { padding-left: calc(var(--heat-key) / 2); }
.key-heatmap .key-row:nth-child(3) { padding-left: var(--heat-key); }
.key-heatmap .key { width: var(--heat-key); height: var(--heat-key); align-content: center; gap: var(--space-1); color: var(--text); background: var(--heat, transparent); border-color: var(--heat-edge, var(--hairline-strong)); transition: none; border-bottom-width: 1px; cursor: pointer; }
:is(.key-heatmap .key, .heat-legend li)[data-heat="1"] { --heat: var(--heat-1); }
:is(.key-heatmap .key, .heat-legend li)[data-heat="2"] { --heat: var(--heat-2); }
:is(.key-heatmap .key, .heat-legend li)[data-heat="3"] { --heat: var(--heat-3); --heat-edge: var(--danger); }
.key-heatmap .key[data-heat="none"] { border-style: dashed; color: var(--outline); }
.key-heatmap .key[data-selected="true"] { outline: 2px solid var(--text); outline-offset: 2px; }
.key-heatmap[aria-hidden="true"] { pointer-events: none; }
.key-rate { font: 400 var(--text-mono)/1 var(--font-mono); font-variant-numeric: tabular-nums; }
.key[data-heat="1"] .key-rate { color: var(--muted); }
.heat-legend { display: flex; flex-wrap: wrap; gap: var(--space-2) var(--space-4); margin: 0 0 var(--space-4); padding: 0; list-style: none; font: 400 var(--text-mono)/1.4 var(--font-mono); color: var(--muted); }
.heat-legend li { display: inline-flex; align-items: center; gap: var(--space-2); }
.heat-legend li::before { content: ""; width: var(--space-4); height: var(--space-4); border: 1px solid var(--heat-edge, var(--hairline-strong)); border-radius: var(--radius-sm); background: var(--heat, transparent); }
.heat-legend li[data-heat="none"]::before { border-style: dashed; border-color: var(--outline); }
.key-select-field { margin: 0 0 var(--space-3); }
.key-detail { margin: 0 0 var(--space-6); font-variant-numeric: tabular-nums lining-nums; }
.pairs-heading { margin: 0 0 var(--space-2); font-size: var(--text-lead); font-weight: 400; }
.pairs-table { border-collapse: collapse; font-size: var(--text-small); }
.pairs-table th { font: 500 var(--text-mono)/1.4 var(--font-mono); color: var(--muted); text-align: left; padding: var(--space-2) var(--space-6) var(--space-2) 0; border-bottom: 1px solid var(--hairline); }
.pairs-table td { padding: var(--space-2) var(--space-6) var(--space-2) 0; border-bottom: 1px solid var(--hairline); }
.pairs-table td.num { font-variant-numeric: tabular-nums; }
.pair-bar { display: block; height: 3px; margin-top: var(--space-1); border-radius: var(--radius-full); background: var(--danger); }
/* The keyboard, its legend and the pairs under it, centred as one group the keyboard's width. */
.keys-layout { display: grid; grid-template-columns: minmax(0, auto); justify-content: center; gap: var(--space-6); }
.keys-layout[hidden] { display: none; }
.keys-map { display: grid; justify-items: center; }
.heat-legend { justify-content: center; }
.key-detail { text-align: center; }
.keys-pairs .pairs-table { width: 100%; }
@media (max-width: 600px) {
  .key-heatmap { --heat-key: var(--space-8); display: grid; grid-template-columns: minmax(0, 1fr); }
  .key-heatmap .key { flex: 1 1 0; min-width: 0; max-width: var(--heat-key); }
  .key-rate { display: none; }
  .pairs-table { width: 100%; }
  .pairs-table th, .pairs-table td { padding-right: var(--space-2); }
}
.training-target, .run-row, .daily-result { padding: var(--space-4) 0; border-bottom: 1px solid var(--hairline); }
.training-target { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: var(--space-4); align-items: center; }
.training-target h3 { margin: 0; font-size: var(--text-lead); }
.training-target p { margin: var(--space-2) 0 0; font-size: var(--text-small); color: var(--muted); }
.run-row { padding: var(--space-1) 0; }
.run-summary { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--space-3); min-height: var(--control); font-variant-numeric: tabular-nums lining-nums; }
.show-all-runs { margin-top: var(--space-6); }
.run-summary strong { font-size: var(--text-lead); font-weight: 400; }
.run-summary span { color: var(--muted); }
/* The bottom space keeps the reach of a "View passage" touch target off the next run's row. */
.run-detail { padding: var(--space-4) 0 var(--space-3); }
.run-detail p { color: var(--muted); font-size: var(--text-small); }
.daily-heading { display: flex; flex-wrap: wrap; justify-content: space-between; gap: var(--space-4); align-items: start; }
.daily-challenge > p { color: var(--muted); max-width: 60ch; }
.daily-result { display: flex; flex-wrap: wrap; justify-content: space-between; gap: var(--space-4); }
.daily-result span { color: var(--muted); }
.compose-row { padding: var(--space-4) 0; border-bottom: 1px solid var(--hairline); display: grid; gap: var(--space-1); }
.compose-row-main { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--space-2) var(--space-4); }
.compose-row-main strong { font-size: var(--text-lead); font-weight: 400; }
.compose-row-main span { color: var(--muted); }
.compose-row p { margin: 0; color: var(--muted); }
.compose-history .result-note { margin: 0 0 var(--space-2); max-width: 72ch; color: var(--muted); }

/* ── Learn to type ──────────────────────────────────────────────────────── */


.learn-layout {
  padding-top: var(--space-8);
  border-top: 1px solid var(--hairline);
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: var(--space-16);
  align-items: start;
}

.lesson-current { min-width: 0; display: grid; justify-items: start; gap: var(--space-3); }
.lesson-current h2 { margin: 0; font-size: var(--text-title); line-height: 1.2; letter-spacing: -0.015em; }
.lesson-current > p { max-width: 56ch; margin: 0; }
#lesson-current-copy { color: var(--muted); }
#lesson-current-meta:empty { display: none; }

.lesson-facts {
  width: 100%;
  margin: var(--space-2) 0;
  padding: var(--space-4) 0;
  border-block: 1px solid var(--hairline);
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--space-4);
}

/* Labels, values, targets and met words line up across the four facts, even when a label wraps. */
.lesson-facts > div { grid-row: span 4; display: grid; grid-template-rows: subgrid; row-gap: var(--space-1); align-items: end; }
.lesson-facts .status { align-self: start; }
.lesson-facts dt { color: var(--muted); font: var(--text-fine)/1.3 var(--font-mono); letter-spacing: 0.02em; }
.lesson-facts dd { margin: 0; font-size: var(--text-title); font-weight: 300; line-height: 1.1; letter-spacing: -0.02em; }
.lesson-facts dd.lesson-need { color: var(--muted); font-size: var(--text-small); font-weight: 400; letter-spacing: 0; }
.lesson-facts dd.status { font: 500 var(--text-mono)/1.3 var(--font-mono); letter-spacing: 0.02em; }

.lesson-actions { margin-top: var(--space-3); display: flex; flex-wrap: wrap; gap: var(--space-2); }

.finger-figure { margin: 0; display: grid; gap: var(--space-3); justify-items: center; }
.finger-figure .keyboard { margin: 0; }

.finger-caption {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-1) var(--space-4);
  color: var(--muted);
  font: var(--text-fine)/1.4 var(--font-mono);
  letter-spacing: 0.02em;
  text-align: center;
}


.lesson-list-section { margin-top: var(--space-10); padding-top: var(--space-8); border-top: 1px solid var(--hairline); }
.lesson-list-section h2 { margin: 0 0 var(--space-2); font-size: var(--text-title); font-weight: 400; }
.lesson-list-section .result-note { max-width: 60ch; margin: 0 0 var(--space-4); color: var(--muted); }
.lesson-list { margin: 0; padding: 0; list-style: none; border-top: 1px solid var(--hairline); }

.lesson-row {
  min-height: calc(var(--control) + var(--space-4));
  padding: var(--space-2) 0;
  border-bottom: 1px solid var(--hairline);
  display: grid;
  grid-template-columns: var(--space-8) minmax(0, 1fr) minmax(0, auto) 88px;
  align-items: center;
  gap: var(--space-4);
}

.lesson-number { color: var(--muted); font-size: var(--text-lead); }
.lesson-row-main { min-width: 0; display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2) var(--space-4); }
.lesson-keys { display: inline-flex; gap: var(--space-1); }
.lesson-keys kbd { color: var(--text); }
.lesson-row[data-state="locked"] .lesson-keys kbd { color: var(--muted); }
.lesson-row-note { color: var(--muted); font-size: var(--text-small); }
.lesson-row-detail { display: flex; flex-wrap: wrap; align-items: baseline; justify-content: flex-end; gap: var(--space-1) var(--space-3); }
.lesson-row-numbers { color: var(--muted); font-size: var(--text-small); }
.lesson-row .btn { justify-self: end; grid-column: 4; }

/* ── Challenge ──────────────────────────────────────────────────────────── */

.challenge-modes { display: flex; flex-wrap: wrap; gap: var(--space-2); margin-bottom: var(--space-8); }
.challenge-modes [aria-pressed="true"] { color: var(--accent); border-color: var(--accent); }
.online-copy { color: var(--muted); font-size: var(--text-small); }
.online-optional { font-size: var(--text-fine); }
.online-setup { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-4) var(--space-6); }
.online-setup-actions { grid-column: 1 / -1; display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-3) var(--space-4); }
/* Customize opens the practice row's own controls under the buttons, for this race only. */
#online-customize[aria-expanded="true"] { color: var(--text); background: var(--raised-2); }
.online-customize-chevron { width: var(--space-4); height: var(--space-4); fill: none; stroke: currentColor; stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; transition: transform var(--dur-quick) var(--ease-out); }
#online-customize[aria-expanded="true"] .online-customize-chevron { transform: rotate(180deg); }
.online-options { grid-column: 1 / -1; display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-4); }
.online-options[hidden] { display: none; }
.online-section-heading { display: flex; align-items: center; justify-content: space-between; gap: var(--space-4); }
.online-section-heading h2, .online-join h2 { margin: 0; font-size: var(--text-title); }
.online-inbox, .online-friends, .online-room, .online-join { border-top: 1px solid var(--hairline); padding-top: var(--space-8); margin-top: var(--space-8); }
.online-join .player-field { max-width: calc(var(--content-max) / 2); }
.online-actions { display: flex; gap: var(--space-3); margin-top: var(--space-6); }
.online-invite-row { display: flex; gap: var(--space-4); align-items: center; justify-content: space-between; padding: var(--space-4) 0; border-bottom: 1px solid var(--hairline); }
.online-invite-row p { margin: 0; }
.online-friend-list { list-style: none; margin: var(--space-2) 0 0; padding: 0; }
.online-friend-row { display: flex; gap: var(--space-4); align-items: center; justify-content: space-between; padding: var(--space-4) 0; border-bottom: 1px solid var(--hairline); }
.online-friend-name { min-width: 0; display: grid; gap: 2px; }
.online-friend-name strong { font-weight: 400; overflow-wrap: anywhere; }
.online-friend-name span { color: var(--muted); font-size: var(--text-small); overflow-wrap: anywhere; }
.online-friend-row .online-actions { margin: 0; flex: none; gap: var(--space-2); }
.online-after { margin-top: var(--space-6); }
.online-after > p { margin: 0 0 var(--space-3); }
.online-after .online-actions { margin-top: 0; flex-wrap: wrap; }
.online-share { margin: var(--space-6) 0; }
.online-share > label { color: var(--muted); font-size: var(--text-small); }
.online-link-row { display: flex; gap: var(--space-3); margin-top: var(--space-2); }
.online-link-row input { min-width: 0; flex: 1; }
/* Online race results: you on the left, them on the right, the pair's tally between. Size and
   brightness say who won without colour; the accent marks only the tally figure this race raised. */
.race-duel { margin-top: var(--space-8); }
.duel-scores { display: grid; grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr); gap: var(--space-6); text-align: center; }
.duel-side { min-width: 0; display: grid; grid-template-rows: auto var(--text-numeral-mobile) auto auto; justify-items: center; align-items: end; }
.duel-name { margin: 0; font-size: var(--text-lead); font-weight: 400; overflow-wrap: anywhere; }
.duel-speed { margin: 0; display: flex; align-items: baseline; gap: var(--space-2); }
.duel-wpm { font: 300 var(--text-numeral-mobile)/0.9 var(--font-app); letter-spacing: -0.04em; }
.duel-unit { color: var(--muted); font: var(--text-body)/1.2 var(--font-app); }
.duel-accuracy { margin: var(--space-4) 0 0; font-size: var(--text-title); }
.duel-side[data-outcome="lost"] :is(.duel-name, .duel-wpm, .duel-accuracy) { color: var(--muted); }
.duel-side[data-outcome="lost"] .duel-wpm { font-size: var(--text-numeral-narrow); }
.duel-choice { width: 100%; margin-top: var(--space-6); padding-top: var(--space-6); border-top: 1px solid var(--hairline); display: flex; justify-content: center; }
.duel-middle { padding-top: var(--space-6); display: grid; justify-items: center; align-content: start; gap: var(--space-3); }
.duel-tally { margin: 0; display: flex; align-items: baseline; gap: var(--space-3); font: 300 var(--text-numeral-narrow)/1 var(--font-app); }
.duel-tally > span[aria-hidden] { color: var(--muted); }
.duel-tally .is-raised { color: var(--accent); }
.duel-starting { margin: 0; color: var(--accent); }
.duel-check { width: var(--space-4); height: var(--space-4); fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; display: none; }
/* Picked: an accent outline with a check, pressed again to take the pick back. */
.duel-rematch[aria-pressed="true"] { border-color: var(--accent); background: var(--accent-soft); color: var(--accent); }
.duel-rematch[aria-pressed="true"]:hover:not([aria-disabled="true"]) { background: var(--accent-soft); }
.duel-rematch[aria-pressed="true"] .duel-check,
.duel-their-rematch[data-state="chosen"] .duel-check { display: block; }
/* Against a racer who left the button leads nowhere: a hairline outline, no fill. */
.duel-rematch[data-state="inactive"] { border-color: var(--hairline-strong); background: transparent; color: var(--muted); opacity: 1; cursor: default; }
.duel-their-rematch { min-height: var(--control); margin: 0; display: inline-flex; align-items: center; gap: var(--space-2); color: var(--muted); }
.duel-their-rematch[data-state="open"]::before { content: ""; width: var(--space-3); height: var(--space-3); border: 1.5px solid var(--muted); border-radius: var(--radius-full); }
.duel-their-rematch[data-state="chosen"] { color: var(--accent); }
.duel-review { margin-top: var(--space-10); display: grid; justify-items: center; gap: var(--space-6); }
.duel-toggle { background: var(--raised); }
.duel-toggle button[aria-pressed="true"] { background: var(--raised-2); }
.duel-graph { width: 100%; height: calc(var(--space-16) * 4); aspect-ratio: auto; }
.graph-line-theirs { stroke: var(--text); }
.duel-replay { width: 100%; }
.duel-replay-wrap { margin: 0; cursor: default; }
/* One colour per racer: your caret is the accent, theirs the text colour, each named under it. */
.duel-replay-wrap .ghost-caret::after { background: var(--text); }
.caret-tag { position: absolute; top: calc(50% + var(--typing-baseline) + var(--space-1)); left: 0; transform: translateX(-50%); }
.caret-tag > span { display: block; color: var(--accent); font: var(--text-fine)/1 var(--font-mono); white-space: nowrap; }
.ghost-caret .caret-tag > span { color: var(--text); }
.duel-replay-controls { margin-top: var(--space-4); display: flex; justify-content: center; gap: var(--space-3); }

.online-players { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-6); margin: var(--space-6) 0; }
.online-player-row { min-width: 0; display: grid; grid-template-columns: 1fr; gap: var(--space-2); }
.online-player-row strong { font-weight: 400; overflow-wrap: anywhere; }
.online-player-row > span { color: var(--muted); font: var(--text-mono)/1.4 var(--font-mono); }
.online-player-row .progress-track > span { transition: none; }
.online-countdown { min-height: var(--space-10); margin: var(--space-6) 0 var(--space-2); font-size: var(--text-title); color: var(--accent); }
.online-typing-wrap { margin-top: var(--space-6); }
#online-connection:not(:empty) { color: var(--warning); }
#online-help { min-height: var(--space-6); }

/* Friends board: two ruled lists, today's daily and this week's practice. Nothing on the board is
   coloured: your row is marked by the word "You". */
.friends-board { padding-top: var(--space-8); border-top: 1px solid var(--hairline); }
.friends-board-guest { display: grid; justify-items: start; gap: var(--space-4); margin: var(--space-6) 0; }
.friends-board-lists { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--space-10); margin: var(--space-6) 0; }
.board-column h3 { margin: 0; font-size: var(--text-lead); font-weight: 400; }
.board-column .condition-copy { margin: var(--space-1) 0 var(--space-3); }
.board-list { list-style: none; margin: 0; padding: 0; border-top: 1px solid var(--hairline); }
.board-row { display: grid; grid-template-columns: minmax(0, 1fr) auto; column-gap: var(--space-4); padding: var(--space-3) 0; border-bottom: 1px solid var(--hairline); }
.board-name { overflow-wrap: anywhere; }
.board-value { grid-column: 2; grid-row: 1 / span 2; align-self: center; white-space: nowrap; }
.board-value strong { font-size: var(--text-title); font-weight: 300; line-height: 1; letter-spacing: -0.015em; }
.board-unit { color: var(--muted); font: var(--text-body)/1.5 var(--font-app); }
.board-detail { color: var(--muted); font-size: var(--text-small); }
.friends-board-share { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2) var(--space-4); margin: var(--space-4) 0; }
.friends-board-off { margin: 0; max-width: 60ch; color: var(--muted); font-size: var(--text-small); }
/* Bests: one test shape at a time, picked with the same labelled select as Progress, then ruled rows. */
.board-shape { margin-top: var(--space-3); }
.board-bests .condition-copy { margin-top: var(--space-2); }
.friends-board-note { margin: var(--space-3) 0 0; color: var(--muted); font-size: var(--text-small); }
.friends-board-empty { margin-bottom: var(--space-6); }
.friends-board-empty p { margin: 0 0 var(--space-3); max-width: 60ch; color: var(--muted); font-size: var(--text-small); }

@media (max-width: 760px) {
  .online-setup, .online-players { grid-template-columns: 1fr; }
  .online-link-row { flex-wrap: wrap; }
  .online-link-row input { flex-basis: 100%; }
  .online-section-heading { flex-wrap: wrap; }
  .online-friend-row { flex-wrap: wrap; }
  :is(.online-typing-wrap, .duel-replay-wrap) { --passage-size: var(--text-title); }
}

.challenge-setup {
  padding-top: var(--space-8);
  border-top: 1px solid var(--hairline);
  display: grid;
  grid-template-columns: 1fr 1fr auto;
  align-items: end;
  gap: var(--space-6);
}

.player-field { display: grid; gap: var(--space-2); }

.player-field label {
  color: var(--muted);
  font-size: 15px;
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

.race-stage,
.handoff-panel,
.race-leaderboard {
  padding-top: var(--space-8);
  border-top: 1px solid var(--hairline);
}

.race-stage-heading,
.race-result-heading {
  margin-bottom: var(--space-6);
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  gap: var(--space-4);
}

.race-stage h2,
.race-leaderboard h2,
.handoff-panel h2 {
  margin: var(--space-2) 0 0;
  font-size: var(--text-title);
  letter-spacing: -0.015em;
}

.progress-track {
  width: 100%;
  height: 2px;
  overflow: hidden;
  border-radius: var(--radius-full);
  background: var(--hairline-strong);
}

.progress-track > span {
  display: block;
  height: 100%;
  background: var(--accent);
  transform: scaleX(var(--progress, 0));
  transform-origin: left center;
  transition: transform calc(140ms * var(--motion-scale)) linear;
}

.race-typing-wrap { margin-top: var(--space-6); }

/* The handoff hides the passage behind a blur until the next player reveals it. */
.handoff-panel { position: relative; min-height: 300px; display: grid; }

.handoff-veil {
  position: absolute;
  inset: var(--space-8) 0 0;
  overflow: hidden;
  color: var(--muted);
  font-size: 36px;
  line-height: 1.75;
  filter: blur(14px);
  opacity: 0.5;
  user-select: none;
  transition: filter var(--dur-base) var(--ease-out), opacity var(--dur-base) var(--ease-out);
}

.handoff-panel.is-revealing .handoff-veil { filter: blur(0); opacity: 0.9; }

.handoff-copy {
  position: relative;
  align-self: center;
  justify-self: center;
  max-width: 460px;
  padding: var(--space-8);
  border: 1px solid var(--hairline-strong);
  border-radius: var(--radius-lg);
  background: var(--raised);
  box-shadow: var(--shadow-float);
  display: grid;
  justify-items: center;
  gap: var(--space-6);
  text-align: center;
  transition: opacity var(--dur-fast) var(--ease-out);
}

.handoff-copy h2 { margin: 0; }
.handoff-panel.is-revealing .handoff-copy { opacity: 0; }

.race-results { border-top: 1px solid var(--hairline); }

.race-result-row {
  padding: var(--space-6) 0;
  border-bottom: 1px solid var(--hairline);
  display: grid;
  grid-template-columns: minmax(0, 1fr) 140px 160px 150px;
  align-items: baseline;
  gap: var(--space-6);
}

.race-result-row strong { font-size: 24px; font-weight: 400; }
.race-result-row:first-child strong { color: var(--accent); }
.race-result-row span { color: var(--muted); font: var(--text-mono)/1.4 var(--font-mono); }
.race-result-row span.num:first-of-type { color: var(--text); font: 300 34px/1 var(--font-app); letter-spacing: -0.03em; }
.race-leaderboard > p { margin: var(--space-4) 0 0; color: var(--muted); font-size: var(--text-small); }

/* ── Overlays: palette, dialog, toasts ──────────────────────────────────── */

.palette {
  position: fixed;
  top: 14vh;
  width: min(600px, calc(100% - 32px));
  max-height: 72vh;
  margin: 0 auto;
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--text);
  overflow: visible;
}

.palette::backdrop {
  background: var(--backdrop);
  animation: fade-in var(--dur-quick) var(--ease-out);
}

.palette-box {
  border: 1px solid var(--hairline-strong);
  border-radius: var(--radius-xl);
  background: var(--raised);
  box-shadow: var(--shadow-float);
  overflow: hidden;
  animation: palette-in var(--dur-quick) var(--ease-out);
}

/* palette.js waits 150ms before closing; keep palette-out at --dur-quick. */
.palette.is-closing .palette-box { animation: palette-out var(--dur-quick) var(--ease-out) forwards; }
.palette.is-closing::backdrop { opacity: 0; transition: opacity var(--dur-quick) var(--ease-out); }

@keyframes palette-in { from { opacity: 0; transform: translateY(-4px); } }
@keyframes palette-out { to { opacity: 0; transform: translateY(-4px); } }

.palette-search {
  height: 62px;
  padding: 0 calc(var(--space-2) + var(--space-3)) 0 var(--space-4);
  border-bottom: 1px solid var(--hairline);
  display: flex;
  align-items: center;
  gap: var(--space-2);
  color: var(--muted);
}

#palette-input {
  flex: 1;
  min-width: 0;
  height: 100%;
  padding: 0;
  border: 0;
  outline: 0;
  background: transparent;
  color: var(--text);
  font-size: 19px;
}

#palette-input::placeholder { color: var(--muted); opacity: 1; }

.palette-scroll {
  position: relative;
  max-height: min(420px, 52vh);
  padding: var(--space-2);
  overflow-y: auto;
  overscroll-behavior: contain;
}

.palette-highlight {
  position: absolute;
  top: 0;
  left: var(--space-2);
  right: var(--space-2);
  height: var(--hl-h, 46px);
  border-radius: var(--radius-control);
  background: var(--raised-2);
  transform: translateY(var(--hl-y, 0));
  opacity: var(--hl-o, 0);
  transition: transform var(--dur-quick) var(--ease-out), opacity var(--dur-quick) var(--ease-out);
  pointer-events: none;
}

#palette-list { position: relative; margin: 0; padding: 0; list-style: none; }

.palette-group {
  padding: var(--space-3) var(--space-3) var(--space-1);
  color: var(--muted);
  font: var(--text-fine)/1.4 var(--font-mono);
  letter-spacing: 0.04em;
}

.palette-option {
  min-height: 46px;
  padding: 0 var(--space-3);
  border-radius: var(--radius-control);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  color: var(--muted);
  cursor: pointer;
  transition: color var(--dur-quick) var(--ease-out);
}

.palette-option[aria-selected="true"] { color: var(--text); }
.palette-option .opt-label { display: flex; align-items: center; gap: var(--space-4); }
.palette-option .opt-mark { width: 6px; height: 6px; border-radius: var(--radius-full); background: var(--hairline-strong); flex: none; transition: background-color var(--dur-quick) var(--ease-out); }
.palette-option[aria-selected="true"] .opt-mark { background: var(--accent); }
.palette-option .opt-hint { color: var(--muted); font: var(--text-fine)/1 var(--font-mono); letter-spacing: 0.03em; white-space: nowrap; }
.palette-option .opt-hint.is-on { color: var(--accent); }
/* Centre each row on its lowercase letters, not the font's line box: Literata's sits about 2px low.
   The trimmed text still hit-tests over its full line, into the next row, so only the row takes the pointer. */
.palette-option :is(.opt-text, .opt-hint) { text-box: trim-both ex alphabetic; pointer-events: none; }
.palette-option.is-secret { color: var(--ghost); }
.palette-option.is-secret .opt-mark { background: var(--ghost); }

.palette-empty { margin: 0; padding: var(--space-6) var(--space-4); color: var(--muted); font-style: italic; }

#clear-dialog,
#account-dialog,
#replay-dialog,
#delete-account-dialog,
#suggest-dialog,
#import-dialog,
#release-dialog {
  width: min(460px, calc(100% - 32px));
  padding: 0;
  border: 1px solid var(--hairline-strong);
  border-radius: var(--radius-xl);
  background: var(--raised);
  color: var(--text);
  box-shadow: var(--shadow-float);
}

#clear-dialog[open],
#account-dialog[open],
#delete-account-dialog[open],
#replay-dialog[open],
#import-dialog[open],
#suggest-dialog[open],
#release-dialog[open] { animation: palette-in var(--dur-quick) var(--ease-out); }
#clear-dialog::backdrop,
#account-dialog::backdrop,
#delete-account-dialog::backdrop,
#replay-dialog::backdrop,
#import-dialog::backdrop,
#suggest-dialog::backdrop,
#release-dialog::backdrop { background: var(--backdrop); }


.account-fields { display: grid; gap: var(--space-3); }
.account-fields label { display: grid; gap: var(--space-2); color: var(--muted); font-size: var(--text-label); }
.account-fields .field-error { margin: 0; color: var(--danger); }

.account-or {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  color: var(--muted);
  font: var(--text-fine)/1 var(--font-mono);
  letter-spacing: 0.04em;
}

.account-or::before,
.account-or::after { content: ""; flex: 1; height: 1px; background: var(--hairline); }

.account-dialog-actions { justify-content: space-between; align-items: center; flex-wrap: wrap; }
.account-dialog-end { display: flex; gap: var(--space-2); margin-left: auto; }
#account-switch { padding: 0 var(--space-2); }

.dialog-content { padding: var(--space-8); display: grid; gap: var(--space-6); }
.dialog-content h2 { margin: var(--space-2) 0; font-size: 26px; }
.dialog-content p { margin: 0; color: var(--muted); }
.dialog-content > label { display: grid; gap: var(--space-2); color: var(--muted); font-size: 14px; }
.dialog-actions { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: var(--space-2); }

#suggest-dialog { width: min(520px, calc(100% - 32px)); }

/* Watch replay: the passage at reading size, instant letter states, a caret that jumps. */
#replay-dialog { width: min(760px, calc(100% - 32px)); }
#replay-dialog .dialog-actions { flex-wrap: wrap; }
/* On a narrow dialog the readout wraps below the title. */
.replay-head { display: flex; flex-wrap: wrap; justify-content: space-between; align-items: baseline; gap: var(--space-2) var(--space-4); }
.replay-readout { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--space-2) var(--space-4); }
/* Number and unit share one baseline, both in the app font, the unit smaller and muted. */
.replay-stat { display: flex; align-items: baseline; gap: var(--space-1); color: var(--muted); font: var(--text-label)/1.2 var(--font-app); }
.replay-stat-value { color: var(--text); font: var(--text-lead)/1 var(--font-app); font-variant-numeric: tabular-nums; }
/* At most three lines, like the live passage; replay-view.js keeps the caret on the first two.
   The inline padding (offset by the margin) keeps a line-start caret clear of the clip. */
.replay-text {
  --replay-line: 1.75em;
  max-height: calc(var(--replay-line) * 3);
  margin-inline: calc(var(--space-1) * -1);
  padding-inline: var(--space-1);
  overflow: hidden;
  color: var(--muted);
  font-family: var(--font-typing);
  font-size: var(--text-lead);
  line-height: var(--replay-line);
  white-space: pre-wrap;
  overflow-wrap: break-word;
  font-variant-ligatures: none;
}
.replay-text .char.is-caret::after {
  content: "";
  position: absolute;
  top: 50%;
  left: -1.5px;
  width: 3px;
  height: 1.15em;
  margin-top: calc(var(--typing-baseline) - 0.985em);
  border-radius: var(--radius-full);
  background: var(--accent);
}
.textarea { height: auto; min-height: 132px; padding: var(--space-3) 14px; line-height: 1.5; resize: vertical; }
.suggest-meta { margin-top: calc(var(--space-4) * -1); display: flex; justify-content: space-between; align-items: baseline; gap: var(--space-4); }
.suggest-meta .field-error { margin: 0; }
/* Import from Monkeytype: a paste box, then a report of what was brought over and what was left. */
#import-dialog { width: min(520px, calc(100% - 32px)); max-height: calc(100dvh - 32px); overflow-y: auto; }
.import-text { font: var(--text-label)/1.5 var(--font-mono); }
.import-meta { margin-top: calc(var(--space-4) * -1); display: flex; justify-content: space-between; align-items: flex-start; gap: var(--space-4); }
.import-meta .field-error { margin: 0; }
.import-meta .btn { flex: none; }
.import-group { display: grid; gap: var(--space-2); }
.import-group h3 { margin: 0; font-weight: 400; }
.import-group[hidden] { display: none; }
.import-rows { margin: 0; padding: 0; list-style: none; border-top: 1px solid var(--hairline); }
.import-rows li { padding: var(--space-2) 0; border-bottom: 1px solid var(--hairline); display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 2px var(--space-4); align-items: baseline; }
.import-setting { font-weight: 600; }
.import-value { text-align: right; }
.import-from { grid-column: 1 / -1; color: var(--muted); font: var(--text-fine)/1.4 var(--font-mono); overflow-wrap: anywhere; }
.import-reason { grid-column: 1 / -1; color: var(--muted); font-size: var(--text-label); }

.suggest-count { flex: none; color: var(--muted); font: var(--text-fine)/1.4 var(--font-mono); letter-spacing: 0.02em; }
.suggest-trap { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }

.toast {
  position: fixed;
  z-index: 70;
  left: 50%;
  bottom: var(--space-6);
  min-height: 52px;
  max-width: calc(100% - 32px);
  padding: 6px 6px 6px 18px;
  border: 1px solid var(--hairline-strong);
  border-radius: var(--radius-lg);
  background: var(--raised);
  box-shadow: var(--shadow-float);
  display: flex;
  align-items: center;
  gap: var(--space-3);
  color: var(--text);
  font-size: 15px;
  transform: translateX(-50%);
  animation: toast-in var(--dur-base) var(--ease-out);
}

@keyframes toast-in {
  from { opacity: 0; transform: translate(-50%, 6px); }
}

.toast .status-dot { color: var(--accent); }
.account-toast { padding-right: 20px; width: max-content; }
.account-toast .status-dot { color: var(--warning); }
.account-toast[data-tone="done"] .status-dot { color: var(--accent); }
.egg-toast { padding-right: 20px; bottom: calc(var(--space-6) + 64px); }

/* A race challenge drops in under the corner buttons, then leaves after TOAST_MS in src/inbox.js. */
.challenge-toast {
  top: calc(var(--space-4) + 58px + var(--space-2));
  right: var(--space-4);
  bottom: auto;
  left: auto;
  max-width: min(440px, calc(100% - 32px));
  transform: none;
  animation: toast-drop var(--dur-base) var(--ease-out);
}

@keyframes toast-drop {
  from { opacity: 0; transform: translateY(-6px); }
}

.challenge-toast #challenge-toast-text { min-width: 0; overflow-wrap: anywhere; }
.toast-close { width: 36px; padding: 0; justify-content: center; }
.toast-close svg { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; }
.egg-toast.is-solo { bottom: var(--space-6); }

.ghost-mark {
  width: 2px;
  height: 20px;
  flex: none;
  border-radius: var(--radius-full);
  background: var(--ghost);
}

/* ── Responsive ─────────────────────────────────────────────────────────── */

/* Beside the finger map, a lesson result needs about 340px for its four facts. */
@media (max-width: 1000px) {
  .learn-layout { grid-template-columns: minmax(0, 1fr); gap: var(--space-10); }
}

@media (max-width: 900px) {
  .history-summary { grid-template-columns: minmax(0, 1fr) 120px 120px; }
  .history-status { grid-column: 1 / -1; justify-self: start; }
  .challenge-setup { grid-template-columns: 1fr; }
  .race-result-row { grid-template-columns: 1fr 1fr; }
}

/* The centred dock must clear the account button: below 900px settings drops to its icon. */
@media (max-width: 900px) and (min-width: 761px) {
  .nav-item { padding: 0 12px; }
  .dock-settings { width: var(--control); padding: 0; justify-content: center; }
  .dock-settings .dock-label { display: none; }
}

/* Side by side, the corner would meet the centred dock: the inbox moves under the person. */
@media (max-width: 1023px) and (min-width: 761px) {
  .corner { flex-direction: column-reverse; align-items: flex-end; }
  .challenge-toast { top: calc(var(--space-4) + 2 * (58px + var(--space-2))); }
}

@media (max-width: 760px) {
  html { scroll-padding-top: var(--space-4); scroll-padding-bottom: calc(62px + 12px + env(safe-area-inset-bottom) + var(--space-4)); }
  .app-shell { width: calc(100% - 32px); padding-bottom: 96px; }

  .dock-zone {
    position: fixed;
    top: auto;
    bottom: 0;
    left: 0;
    right: 0;
    padding: 0 12px calc(12px + env(safe-area-inset-bottom));
  }

  .dock { width: 100%; height: 62px; padding: 6px 4px; gap: 2px; justify-content: space-between; }
  .brand, .dock-sep { display: none; }
  .dock-nav { flex: 1; min-width: 0; justify-content: space-between; gap: 0; }
  .nav-item { flex: 1 1 auto; min-width: var(--control); padding: 0 4px; font-size: 14px; }
  .dock-command { flex: none; }
  .dock-settings { width: var(--control); padding: 0; justify-content: center; }
  .dock-settings .dock-label { display: none; }

  /* The dock moves to the bottom; the corner stays top right and scrolls with the page. */
  .app-shell { padding-top: var(--space-10); }
  .corner { position: absolute; top: var(--space-3); right: var(--space-4); }
  .corner-trigger { width: var(--control); height: var(--control); border-radius: var(--radius-control); }
  .account[data-state="member"] .account-trigger::after { top: 9px; right: 9px; }
  .corner-popover { width: min(300px, calc(100vw - var(--space-8))); }
  .inbox-popover { right: calc(-1 * (var(--control) + var(--space-2))); width: min(340px, calc(100vw - var(--space-8))); }
  .inbox-count { top: 2px; right: 1px; }


  .screen { padding-top: var(--space-8); }
  .typing-screen { min-height: auto; padding-top: var(--space-6); }

  /* Six modes (with standard and compose) are wider than a phone column: the mode group wraps onto a
     second line rather than scroll sideways; the selection pill follows its button to either line. */
  .mode-options { flex-wrap: wrap; justify-content: center; max-width: 100%; }
  /* Two rows at most: mode and punctuation, then lengths. */
  .practice-controls { margin-bottom: var(--space-6); gap: var(--space-2); }
  /* "more" ends the mode row near the right edge, so its menu hangs from the right. */
  .mode-more-menu { left: auto; right: 0; transform: none; }
  .segmented button, .segmented .custom-length-input { height: 40px; padding: 0 var(--space-2); }
  .segmented.compact button, .segmented.compact .custom-length-input { min-width: 44px; padding: 0 6px; }
  .compact-switch { padding: 0 10px; gap: var(--space-2); font-size: 14px; }
  .custom-options { margin: calc(var(--space-4) * -1) auto var(--space-6); }
  /* On a phone the recipe takes the lengths' row, which stays laid out (unseen) so the row keeps its
     height; it wraps within the lengths' width. */
  .practice-options { display: grid; }
  .practice-options > * { grid-area: 1 / 1; }
  .standard-options { position: static; width: auto; height: auto; transform: none; contain: inline-size; }
  .standard-options p { line-height: 1.4; }
  .standard-options:not([hidden]) ~ #duration-options[hidden] { display: flex !important; visibility: hidden; }
  .quote-filter-trigger { min-height: var(--space-10); }

  .measure-row { flex-direction: column; align-items: stretch; gap: var(--space-4); }
  .live-stats { justify-content: space-between; gap: var(--space-4); }
  body[data-session="practice"] .live-stats { gap: var(--space-8); }
  .live-stat strong { font-size: 40px; }
  body[data-font-size="22"] :is(.typing-wrap, .compose-wrap) { --passage-size: 22px; }
  body[data-font-size="28"] :is(.typing-wrap, .compose-wrap) { --passage-size: 26px; }
  body[data-font-size="36"] :is(.typing-wrap, .compose-wrap) { --passage-size: 32px; }
  .compose-side { justify-content: space-between; }
  .compose-facts { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .compose-result-foot { flex-direction: column; align-items: stretch; }
  .compose-result-actions { flex-direction: column-reverse; }
  .shortcut-row { display: none; }
  .keyboard:not(.key-heatmap) { display: none; }
  /* The finger map stays in lessons, sized to the column; its labels shorten to a letter. */
  .keyboard.finger-map { --key-size: calc((100vw - 32px - var(--space-4) - 40px) / 10); display: grid; column-gap: var(--space-4); }
  .finger-label-full { display: none; }
  .finger-label-short { display: inline; }
  .finger-map > .finger-legend { display: block; }
  .finger-map .key { font-size: var(--text-fine); }
  .lesson-facts { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .lesson-row { grid-template-columns: var(--space-6) minmax(0, 1fr) auto; gap: var(--space-2) var(--space-3); }
  .lesson-row-detail { grid-column: 2; grid-row: 2; justify-content: flex-start; }
  .lesson-row .btn { grid-column: 3; grid-row: 1 / span 2; }
  .site-footer { flex-direction: column; justify-content: flex-end; align-items: flex-start; gap: var(--space-2); }
  .release-notes { padding: var(--space-2) var(--space-4) var(--space-6) var(--space-8); }
  .page-heading { flex-direction: column; align-items: flex-start; }
  .page-heading h1, .section-heading h1 { font-size: 38px; }
  #result-wpm { font-size: var(--text-numeral-mobile); }
  /* The run card stacks: the score over its facts, the conditions line under the actions. */
  .run-card { padding: var(--space-6); gap: var(--space-6); }
  .run-card-head { grid-template-columns: minmax(0, 1fr); }
  .result-actions { padding-top: var(--space-6); }
  .result-actions .condition-copy { text-align: start; }
  .train-block { padding-top: var(--space-16); }
  .train-block h1 { font-size: 38px; }
  .train-block .comparison-rows { gap: var(--space-3); }
  .history-summary { grid-template-columns: 1fr 1fr; gap: var(--space-3) var(--space-4); }
  .history-main { grid-column: 1 / -1; }
  .input-action { flex-direction: column; }
  .toast { bottom: 90px; }
  .toast.challenge-toast { top: calc(var(--space-3) + var(--control) + var(--space-2)); right: var(--space-4); bottom: auto; left: var(--space-4); max-width: none; }
  .egg-toast, .egg-toast.is-solo { bottom: 150px; }
  .handoff-veil { font-size: 26px; }
}

@media (max-width: 360px) {
  /* Eighteen weeks fill the 288px column, so the weekday names give way. */
  .goal-weekdays { display: none; }
  .goal-chart { grid-template-columns: auto; grid-template-areas: "months" "calendar"; }
  /* The mode buttons tighten so each wrapped row fits the 288px column. */
  .segmented button { padding: 0 var(--space-1); font-size: var(--text-label); }
  /* The full words don't fit beside settings and commands, so the dock shows its short verbs.
     The full word stays in the accessible name after the visible one (WCAG 2.5.3, label in name):
     "type practice", "track progress", "race challenge". */
  .nav-item { padding: 0 2px; font-size: 13px; }
  .nav-label-full { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; }
  .nav-label-short { display: inline; }
  /* Three-digit speed (cpm), timer and accuracy side by side. */
  body[data-session="practice"] .live-stats { gap: var(--space-4); }
  /* At 40px a three-digit speed beside a word count ("25/25") is wider than the 288px column. */
  .live-stat strong { font-size: var(--text-title); }
}

/* Any coarse pointer keeps a 44px minimum target. any-pointer, not pointer: a touchscreen laptop or
   a tablet with a trackpad reports its fine pointer as primary, and a finger still reaches every
   control. e2e/touch-targets.spec.js measures every control; touch-targets-hybrid.spec.js checks
   the hybrid case. Text-sized controls reach into the space around them with a transparent ::after,
   so neither the layout nor the focus ring moves. Relative position keeps that reach above the
   content after it. */
@media (any-pointer: coarse) {
  .quote-filter-trigger { min-height: var(--control); }
  .goal-day-picker { display: block; }
  .btn, .input, .nav-item, .segmented button, .segmented .custom-length-input, .icon-control, .mode-more > summary, .range { min-height: var(--control); }
  /* A short mode ("time") among five on a 320px phone is narrower than 44px: the row still fits. */
  .segmented button { min-width: var(--control); }
  .footer-link, .run-detail summary, .daily-challenge summary { position: relative; }
  .footer-link::after, .run-detail summary::after, .daily-challenge summary::after { content: ""; position: absolute; }
  /* 12px mono on a 1.5 line is 18px, 34px with its own 8px padding: 16px more, down into the
     footer's own padding, makes 50 and stays off the line before it. */
  .footer-link::after { inset: 0 0 calc(var(--space-4) * -1); }
  /* "View passage" and "Your daily results": a 27px line, 51px with 12px each side. */
  .run-detail summary::after, .daily-challenge summary::after { inset: calc(var(--space-3) * -1) 0; }
  .toast-close { min-width: var(--control); }
  /* Chart points: 48px tall and up to 48px wide, so the reach through the centre clears 44 at any
     sub-pixel offset, but never wider than the gap to the next point (see progress-view.js).
     The date labels are drawn over the end points; taps pass through every axis label. */
  .trend-hit { rx: min(var(--space-6), var(--trend-step) / 2); ry: var(--space-6); }
  .trend-label { pointer-events: none; }
  /* A link inside a sentence still gets a full touch target; the negative margin keeps its line from growing. */
  .first-run-learn .quiet-link { min-height: var(--control); margin-block: calc((var(--control) - 1lh) / -2); }
  /* Under 16px, iOS zooms the page in to type, and leaves it zoomed. The width stays shared with the pill. */
  .segmented.compact .custom-length-input { padding: 0 var(--space-1); font-size: var(--text-field); }
  #settings-close { min-width: var(--control); justify-content: center; }
}

/* Key hints ("esc restarts.", "esc" on settings close, the shortcut row) name keys a touch-only
   device doesn't have. A touchscreen laptop or a tablet with a keyboard case reports a fine
   pointer too, and keeps them. */
@media (any-pointer: coarse) and (not (any-pointer: fine)) {
  .key-hint { display: none; }
}

@property --fade-start { syntax: '<length>'; inherits: false; initial-value: 0px; }
@property --fade-end { syntax: '<length>'; inherits: false; initial-value: 0px; }
@keyframes pill-row-fade {
  0% { --fade-start: 0px; --fade-end: var(--space-10); }
  15% { --fade-start: var(--space-10); }
  85% { --fade-end: var(--space-10); }
  100% { --fade-start: var(--space-10); --fade-end: 0px; }
}

@media (max-width: 760px) {
  /* Each pill group is one row that scrolls sideways, never a wrapped cloud. Where scroll timelines run,
     an edge fades only while more of the row lies past it, so a row that fits doesn't fade. */
  .progress-screen .pill-row { flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none; overscroll-behavior-x: contain;
    margin-inline: calc(var(--space-1) * -1); padding: var(--space-1);
    mask-image: linear-gradient(to right, var(--text) calc(100% - var(--space-6)), transparent); }
  @supports (animation-timeline: scroll()) {
    .progress-screen .pill-row { animation: pill-row-fade linear both; animation-timeline: scroll(self inline);
      mask-image: linear-gradient(to right, transparent, var(--text) var(--fade-start), var(--text) calc(100% - var(--fade-end)), transparent); }
  }
  .progress-screen .pill-row::-webkit-scrollbar { display: none; }
  .progress-screen .pill-row > * { flex: none; }
  .progress-filters > #progress-tests, .progress-filters > #progress-period { flex-basis: 100%; }
  .progress-stats { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  /* On a phone the chart comes straight after the filters, so it starts on the first screen; the
     period's figures follow it, before the standard score and the runs. */
  .progress-screen:not([hidden]) { display: flex; flex-direction: column; }
  #progress-content:not([hidden]) { display: contents; }
  #progress-stats, #progress-trend-note { order: 1; }
  .progress-screen > :is(.standard-score, #progress-empty, #progress-history, #compose-history, .progress-danger) { order: 2; }
  .progress-views { margin-bottom: 0; }
  #progress-trend-note:not(:empty) { margin-bottom: var(--space-8); }
  .practice-stats { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .practice-layout, .progress-spread { grid-template-columns: minmax(0, 1fr); }
  .progress-spread { gap: var(--space-8); }
  .trend-figure { grid-template-columns: minmax(0, 1fr); }
  .trend-legend { margin-bottom: var(--space-3); }
  .figure-value { font-size: var(--text-title); }
  .training-target { grid-template-columns: 1fr; }
  .training-target button { justify-self: start; }
  .daily-heading { flex-direction: column; }
  .friends-board-lists { grid-template-columns: minmax(0, 1fr); gap: var(--space-8); }
}

@media (max-width: 560px) {
  .ghost-result .race-result-row { grid-template-columns: 1fr 1fr; }
  .duel-scores { gap: var(--space-3); }
  .duel-side { grid-template-rows: auto var(--text-display) auto auto; }
  .duel-wpm { font-size: var(--text-display); }
  .duel-side[data-outcome="lost"] .duel-wpm { font-size: var(--text-title); }
  .duel-unit, .duel-accuracy { font-size: var(--text-small); }
  .duel-tally { font-size: var(--text-title); gap: var(--space-1); }
}

@media (max-width: 360px) {
  #result-wpm { font-size: var(--text-numeral-narrow); }
  /* Three tracks are about 76px here: values step down a size so "120.0 s" fits. */
  .run-card { padding: var(--space-4); }
  .run-card-facts dd { font-size: var(--text-lead); }
}

@media (max-width: 1000px) and (min-width: 761px) {
  .progress-stats:not(.practice-stats) { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

/* All five practice figures in one row from 1001 px. A unit too wide for its column ("412 finished" beside
   999) drops under its numeral instead of running into the next column. */
.practice-stats .figure-value { flex-wrap: wrap; }
@media (min-width: 1001px) {
  .practice-stats { grid-template-columns: repeat(5, minmax(0, 1fr)); }
}
