/* ============================================================
   typo — quiet typing practice
   Paper-white minimal theme, with an optional dark companion.
   Design rule: hierarchy comes from scale, weight and space —
   never from boxes, shadows or gradients.
   ============================================================ */

:root {
  /* JetBrains Mono carries the Latin; the CJK faces are only there so
     Chinese interface text inherits the same voice instead of falling
     through to an arbitrary default. Drill and quote text is ASCII,
     so it always lands on the mono face. */
  --mono: "JetBrains Mono", "Cascadia Mono", Consolas,
          "Microsoft YaHei", "PingFang SC", "Hiragino Sans GB",
          "Source Han Sans SC", "Noto Sans CJK SC", monospace;

  --fs: clamp(1.2rem, 1.05rem + 0.55vw, 1.45rem);
  --lh: 1.85;
  --lines: 3;

  --dur: 0.22s;
  --ease: cubic-bezier(0.22, 0.61, 0.36, 1);
}

/* ---------- paper white (default) ---------- */
:root,
[data-theme="light"] {
  --bg: #fbfbf9;
  --bg-soft: #f2f1ed;
  --fg: #2b2b29;
  --fg-mid: #6f6e69;
  --fg-dim: #a6a49e;
  --fg-faint: #d9d7d1;
  --line: #e7e5df;
  --accent: #1d9e75;
  --wrong: #d85a30;
  --wrong-bg: rgba(216, 90, 48, 0.1);
  --veil: rgba(43, 43, 41, 0.34);

  /* keyboard diagram — one tint per finger class, mirrored across hands */
  --f-pinky: #f6dcd3;
  --f-ring: #f8e7c7;
  --f-middle: #dae7f7;
  --f-index: #d5eee2;
  --f-line: #e0ded8;
}

/* ---------- dark companion ---------- */
[data-theme="dark"] {
  --bg: #16171a;
  --bg-soft: #1f2024;
  --fg: #e8e6e1;
  --fg-mid: #8b8a94;
  --fg-dim: #64656e;
  --fg-faint: #34353b;
  --line: #26272c;
  --accent: #4fd1a5;
  --wrong: #e5754f;
  --wrong-bg: rgba(229, 117, 79, 0.16);
  --veil: rgba(0, 0, 0, 0.6);

  --f-pinky: #3a2721;
  --f-ring: #382f1c;
  --f-middle: #1d2b3a;
  --f-index: #17322a;
  --f-line: #303138;
}

* { box-sizing: border-box; }

[hidden] { display: none !important; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  background: var(--bg);
  color: var(--fg);
  font-family: var(--mono);
  font-size: 16px;
  font-weight: 400;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  transition: background 0.35s ease, color 0.35s ease;
}

::selection { background: var(--fg-faint); color: var(--fg); }

:focus-visible {
  outline: 1.5px solid var(--accent);
  outline-offset: 3px;
  border-radius: 3px;
}

.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

.skip {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 99;
  padding: 10px 16px;
  background: var(--bg);
  color: var(--fg);
  font-size: 0.8rem;
  text-decoration: none;
}
.skip:focus { left: 12px; top: 12px; }

/* ============================================================
   top bar
   ============================================================ */

.bar {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 10;
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: 20px;
  padding: 26px clamp(20px, 5vw, 54px) 0;
  font-size: 0.78rem;
  transition: opacity 0.5s var(--ease);
}

.bar.is-dim { opacity: 0; pointer-events: none; }

.mark {
  color: var(--fg-faint);
  letter-spacing: 0.06em;
  user-select: none;
  transition: color 0.35s ease;
}

.modes {
  display: flex;
  gap: 22px;
  justify-self: center;
}

.modes button,
.params button {
  appearance: none;
  border: 0;
  background: none;
  padding: 0 0 3px;
  font: inherit;
  color: var(--fg-dim);
  cursor: pointer;
  border-bottom: 1px solid transparent;
  transition: color var(--dur) ease, border-color var(--dur) ease;
}

.modes button:hover,
.params button:hover { color: var(--fg-mid); }

.modes button.is-on {
  color: var(--fg);
  border-bottom-color: var(--accent);
}

.params {
  display: flex;
  gap: 18px;
  justify-self: end;
  min-width: 92px;
}

.params button.is-on {
  color: var(--accent);
  border-bottom-color: var(--accent);
}

.params .param-note {
  font-size: 0.78rem;
  color: var(--fg-faint);
}

/* ============================================================
   stage
   ============================================================ */

/* scoped to the element, not the class alone — the lesson list also
   uses <section> elements and would otherwise inherit this layout */
main.stage {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 96px clamp(20px, 6vw, 60px) 120px;
}

.typing,
.result {
  width: 100%;
  /* ch is measured against this element's own font-size, so the
     type scale has to be set here for the column to scale with it */
  font-size: var(--fs);
  max-width: 46ch;
}

/* ---------- words ---------- */

.words {
  height: calc(var(--fs) * var(--lh) * var(--lines));
  overflow: hidden;
}

.words-inner {
  position: relative;
  transition: transform 0.26s var(--ease);
}

.words-text {
  font-size: var(--fs);
  line-height: var(--lh);
  letter-spacing: 0.01em;
  word-spacing: 0.08em;
  color: var(--fg-dim);
  user-select: none;
  -webkit-user-select: none;
  transition: opacity 0.4s ease;
}

.ch {
  /* pre-wrap keeps a space span from collapsing to nothing while
     still allowing the line to break after it — plain `pre` would
     suppress every wrap opportunity and run the text off the edge */
  white-space: pre-wrap;
  transition: color 0.1s linear, background-color 0.1s linear;
}

.ch.ok { color: var(--fg); }

.ch.bad {
  color: var(--wrong);
  background: var(--wrong-bg);
  border-radius: 2px;
}

.caret {
  position: absolute;
  top: 0;
  left: 0;
  width: 2px;
  height: calc(var(--fs) * 1.02);
  margin-top: calc(var(--fs) * 0.06);
  background: var(--accent);
  border-radius: 1px;
  will-change: transform;
  transition: transform 0.08s linear;
}

.caret.is-idle {
  animation: caret-blink 1.1s steps(1) infinite;
}

@keyframes caret-blink {
  0%, 46% { opacity: 1; }
  50%, 100% { opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
  .caret.is-idle { animation: none; }
  .words-inner,
  .caret { transition: none; }
}

/* ---------- live readout ---------- */

/* ---------- timed run ---------- */

.tick {
  height: 1px;
  margin-top: 22px;
  background: var(--line);
  opacity: 0;
  transition: opacity var(--dur) ease;
}

.tick.on {
  opacity: 1;
}

.tick i {
  display: block;
  height: 100%;
  background: var(--accent);
  transform: scaleX(0);
  transform-origin: left center;
  /* the value only lands once a second, so the transition is what
     makes the bar read as continuous */
  transition: transform 1s linear;
}

/* focus left the input: dim the text so the pause is obvious */
.typing.is-idle .words {
  opacity: 0.18;
}

/* ---------- result source ---------- */

.res-source {
  margin: 16px 0 0;
  font-size: 0.76rem;
  color: var(--fg-dim);
  letter-spacing: 0.01em;
}

.readout {
  display: flex;
  gap: 26px;
  margin-top: 26px;
  font-size: 0.78rem;
  color: var(--fg-dim);
  transition: opacity 0.4s var(--ease);
}

.readout b {
  color: var(--fg);
  font-weight: 500;
  margin-right: 5px;
  font-variant-numeric: tabular-nums;
}

.hint {
  margin: 26px 0 0;
  font-size: 0.78rem;
  color: var(--fg-faint);
  transition: opacity 0.4s var(--ease);
}

.is-out { opacity: 0; }

/* ============================================================
   result
   ============================================================ */

.result {
  animation: rise 0.45s var(--ease) both;
}

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

.res-top {
  display: flex;
  align-items: baseline;
  gap: clamp(28px, 6vw, 64px);
  flex-wrap: wrap;
}

.res-big {
  margin: 0;
  display: flex;
  align-items: baseline;
  gap: 10px;
}

.res-big b {
  font-size: clamp(2.9rem, 7vw, 4.4rem);
  font-weight: 500;
  line-height: 1;
  letter-spacing: -0.025em;
  font-variant-numeric: tabular-nums;
}

.res-big span {
  font-size: 0.78rem;
  color: var(--fg-dim);
}

.res-best {
  font-size: 0.72rem;
  letter-spacing: 0.08em;
  color: var(--accent);
  align-self: center;
  margin-left: auto;
}

.res-chart {
  margin: 34px 0 30px;
}

.res-chart canvas {
  display: block;
  width: 100%;
}

.res-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 18px;
  margin: 0;
  padding-top: 22px;
  border-top: 1px solid var(--line);
  transition: border-color 0.35s ease;
}

.res-grid dt {
  font-size: 0.72rem;
  letter-spacing: 0.03em;
  color: var(--fg-dim);
}

.res-grid dd {
  margin: 7px 0 0;
  font-size: 1.1rem;
  font-weight: 500;
  font-variant-numeric: tabular-nums;
}

.res-actions {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 28px;
  margin-top: 32px;
}

/* Deliberately not buttons-in-boxes: two quiet lines of text with the
   key that triggers them sitting alongside. The border only appears on
   hover, so the result screen stays free of chrome. */
.act {
  appearance: none;
  border: 0;
  background: none;
  font: inherit;
  font-size: 0.82rem;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 3px 0;
  color: var(--fg-dim);
  cursor: pointer;
  border-bottom: 1px solid transparent;
  transition: color var(--dur) ease, border-color var(--dur) ease;
}

.act:hover {
  color: var(--fg);
  border-bottom-color: var(--fg-faint);
}

.act-primary {
  color: var(--accent);
}

.act-primary:hover {
  border-bottom-color: var(--accent);
}

.act kbd {
  font-family: inherit;
  font-size: 0.68rem;
  line-height: 1;
  padding: 3px 6px;
  color: var(--fg-faint);
  border: 0.5px solid var(--line);
  border-radius: 4px;
  transition: color var(--dur) ease, border-color var(--dur) ease;
}

.act:hover kbd {
  color: var(--fg-dim);
  border-color: var(--fg-faint);
}

.ghost {
  appearance: none;
  font: inherit;
  font-size: 0.78rem;
  padding: 9px 22px;
  border-radius: 999px;
  border: 1px solid var(--line);
  background: transparent;
  color: var(--fg-dim);
  cursor: pointer;
  transition: color var(--dur) ease, border-color var(--dur) ease;
}

.ghost:hover {
  color: var(--fg);
  border-color: var(--fg-dim);
}

/* ============================================================
   hidden input
   ============================================================ */

.typer {
  position: fixed;
  left: 50%;
  top: 50%;
  width: 2px;
  height: 2px;
  padding: 0;
  margin: 0;
  border: 0;
  opacity: 0;
  font-size: 16px;
  caret-color: transparent;
}

/* ============================================================
   tools
   ============================================================ */

.tools {
  position: fixed;
  right: clamp(14px, 3vw, 28px);
  bottom: clamp(14px, 3vw, 26px);
  display: flex;
  gap: 4px;
  z-index: 10;
  transition: opacity 0.5s var(--ease);
}

.tools.is-dim { opacity: 0; pointer-events: none; }

.tool {
  width: 34px;
  height: 34px;
  display: grid;
  place-items: center;
  border: 0;
  border-radius: 9px;
  background: none;
  color: var(--fg-faint);
  cursor: pointer;
  transition: color var(--dur) ease, background-color var(--dur) ease;
}

.tool:hover {
  color: var(--fg);
  background: var(--bg-soft);
}

.tool[aria-pressed="true"] { color: var(--accent); }
.tool[aria-pressed="false"] .wave { display: none; }

/* ============================================================
   panel
   ============================================================ */

.panel {
  width: min(540px, calc(100vw - 32px));
  padding: 0;
  border: 1px solid var(--line);
  border-radius: 14px;
  background: var(--bg);
  color: var(--fg);
  font-family: var(--mono);
  font-size: 0.82rem;
}

.panel::backdrop {
  background: var(--veil);
  backdrop-filter: blur(3px);
}

.panel-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 18px 22px;
  border-bottom: 1px solid var(--line);
}

.panel-head h2 {
  margin: 0;
  font-size: 0.82rem;
  font-weight: 500;
  letter-spacing: 0.02em;
}

.panel-close {
  border: 0;
  background: none;
  color: var(--fg-dim);
  cursor: pointer;
  padding: 2px;
  display: grid;
  place-items: center;
  transition: color var(--dur) ease;
}

.panel-close:hover { color: var(--fg); }

.panel-body {
  padding: 20px 22px;
  max-height: min(58vh, 460px);
  overflow-y: auto;
}

.pane + .pane { margin-top: 28px; }

.pane h3 {
  margin: 0 0 12px;
  font-size: 0.72rem;
  font-weight: 400;
  letter-spacing: 0.06em;
  color: var(--fg-dim);
}

.best-list {
  margin: 0;
  display: grid;
  gap: 9px;
}

.best-list > div {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
}

.best-list dt { color: var(--fg-mid); }
.best-list dd {
  margin: 0;
  font-weight: 500;
  font-variant-numeric: tabular-nums;
}

.hist-list {
  margin: 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: 2px;
}

.hist-list li {
  display: grid;
  grid-template-columns: 1fr auto auto auto;
  gap: 14px;
  align-items: baseline;
  padding: 7px 0;
  border-bottom: 1px solid var(--line);
  font-variant-numeric: tabular-nums;
  color: var(--fg-mid);
}

.hist-list li:last-child { border-bottom: 0; }
.hist-list .h-wpm { color: var(--fg); font-weight: 500; }
.hist-list .h-when { color: var(--fg-faint); font-size: 0.74rem; }

.empty {
  margin: 0;
  color: var(--fg-faint);
  font-size: 0.78rem;
}

.panel-foot {
  padding: 14px 22px;
  border-top: 1px solid var(--line);
  display: flex;
  justify-content: flex-end;
}

.ghost { border-color: transparent; }
.ghost:hover { border-color: var(--fg-faint); color: var(--wrong); }

/* the one affirmative button in the app: dismissing the tutorial */
.solid {
  appearance: none;
  font: inherit;
  font-size: 0.78rem;
  padding: 9px 22px;
  border-radius: 999px;
  border: 1px solid var(--accent);
  background: var(--accent);
  color: var(--bg);
  cursor: pointer;
  transition: opacity var(--dur) ease;
}

.solid:hover { opacity: 0.85; }

/* ============================================================
   lessons
   ============================================================ */

/* ---------- keyboard diagram ---------- */

/* Built from plain boxes, not SVG. An inline <svg> relies on the
   browser deriving an intrinsic ratio from the viewBox, and when it
   does not, `height: auto` collapses the whole figure to zero height.
   Flex boxes have no such failure mode. */
.hands-figure {
  display: grid;
  gap: 6px;
  margin-bottom: 20px;
}

.kb-row {
  display: flex;
  gap: 5px;
}

/* the stagger a real keyboard carries: each row sits a little further right */
.kb-row[data-row="home"] { padding-left: 14px; }
.kb-row[data-row="bottom"] { padding-left: 28px; }

.kb-key {
  flex: 1 1 0;
  min-width: 0;
  aspect-ratio: 1.28;
  position: relative;
  display: grid;
  place-items: center;
  border: 0.5px solid var(--f-line);
  border-radius: 6px;
  font-size: 0.78rem;
  line-height: 1;
  color: var(--fg-mid);
}

.kb-key.f-lp, .kb-key.f-rp { background: var(--f-pinky); }
.kb-key.f-lr, .kb-key.f-rr { background: var(--f-ring); }
.kb-key.f-lm, .kb-key.f-rm { background: var(--f-middle); }
.kb-key.f-li, .kb-key.f-ri { background: var(--f-index); }

.kb-key.home {
  border: 1px solid var(--accent);
  color: var(--fg);
}

/* the raised bar that lets a finger find home without looking */
.kb-key.bump::after {
  content: "";
  position: absolute;
  bottom: 4px;
  left: 50%;
  transform: translateX(-50%);
  width: 13px;
  height: 1.6px;
  border-radius: 1px;
  background: var(--fg-mid);
}

.hands-note {
  margin: 0 0 20px;
  font-size: 0.78rem;
  line-height: 1.8;
  color: var(--fg-mid);
}

.hands-note b {
  color: var(--fg);
  font-weight: 500;
}

.keyhelp {
  margin: 0;
  padding: 16px 0 0;
  list-style: none;
  border-top: 1px solid var(--line);
  display: grid;
  gap: 9px;
}

.keyhelp li {
  display: flex;
  align-items: center;
  gap: 12px;
  font-size: 0.78rem;
  color: var(--fg-mid);
}

.keyhelp kbd {
  font-family: inherit;
  font-size: 0.68rem;
  line-height: 1;
  padding: 3px 7px;
  min-width: 46px;
  text-align: center;
  color: var(--fg-mid);
  border: 0.5px solid var(--line);
  border-radius: 4px;
}

/* ---------- lesson list ---------- */

.lessons-progress {
  margin: 0 0 18px;
  font-size: 0.74rem;
  color: var(--fg-faint);
  transition: color var(--dur) ease;
}

.lessons-progress.is-full {
  color: var(--accent);
}

.stages {
  display: grid;
  gap: 28px;
}

.stage-head h3 {
  margin: 0;
  font-size: 0.74rem;
  font-weight: 400;
  letter-spacing: 0.06em;
  color: var(--fg-dim);
}

.stage-head p {
  margin: 5px 0 0;
  font-size: 0.74rem;
  color: var(--fg-faint);
}

.lesson {
  width: 100%;
  display: grid;
  grid-template-columns: 14px minmax(0, 1fr) auto;
  gap: 16px;
  align-items: center;
  padding: 11px 4px;
  border: 0;
  border-bottom: 0.5px solid var(--line);
  border-radius: 0;
  background: none;
  font: inherit;
  font-size: 0.82rem;
  color: var(--fg-mid);
  text-align: left;
  cursor: pointer;
  transition: color var(--dur) ease, background-color var(--dur) ease;
}

.lesson:hover {
  color: var(--fg);
  background: var(--bg-soft);
}

.stage-head + .lesson {
  margin-top: 10px;
}

.lesson-main {
  display: flex;
  align-items: baseline;
  gap: 14px;
  min-width: 0;
}

.lesson-name {
  color: var(--fg);
  /* fixed so the notes line up down the column */
  min-width: 6.5em;
}

.lesson-note {
  font-size: 0.76rem;
  color: var(--fg-dim);
}

.lesson-best {
  font-size: 0.76rem;
  color: var(--fg-faint);
  font-variant-numeric: tabular-nums;
}

.dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  border: 1px solid var(--fg-faint);
  justify-self: center;
}

.lesson.is-done .dot {
  background: var(--accent);
  border-color: var(--accent);
}

.lesson.is-next .dot {
  border-color: var(--accent);
}

/* ============================================================
   responsive
   ============================================================ */

@media (max-width: 640px) {
  .bar {
    grid-template-columns: 1fr auto;
    padding-top: 20px;
  }

  .mark { display: none; }

  .modes {
    justify-self: start;
    gap: 16px;
  }

  .params {
    justify-self: end;
    min-width: 0;
    gap: 12px;
  }

  main.stage { padding: 78px 20px 96px; }

  .res-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }

  .res-best { margin-left: 0; order: 3; flex-basis: 100%; }

  .readout { gap: 18px; }
}

@media (max-width: 560px) {
  /* the mode row and the parameter row no longer fit side by side
     once the labels are Chinese — stack them instead of squeezing */
  .bar {
    grid-template-columns: 1fr;
    justify-items: center;
    gap: 11px;
    padding-top: 18px;
  }

  .modes,
  .params {
    justify-self: center;
    min-width: 0;
  }

  main.stage { padding: 104px 20px 96px; }
}

@media (max-width: 380px) {
  .modes { gap: 12px; }
  .params { gap: 9px; }
  .bar { font-size: 0.72rem; }
}
