/* ---- base ---- */
* { margin: 0; padding: 0; box-sizing: border-box; }
:root {
  --accent: #3b6fe0;
  --init-c: #e08b3c;   /* initial (聲母) annotation */
  --final-c: #2f9e8f;  /* final (韻母) annotation */
  --done-c: #2f9e6b;
  --ctrl-c: #8a93b5;   /* control-key annotation */
  --u: 58px;           /* 1u keycap unit (fits four annotation lines) */
  --kgap: 6px;
}
html, body { height: 100%; }
body {
  font-family: "Microsoft JhengHei", "PingFang TC", "Noto Sans TC", sans-serif;
  background: linear-gradient(160deg, #e9edf5 0%, #dfe4ee 100%);
  color: #24272e;
  overflow: hidden;
}
.app { display: flex; height: 100vh; }

/* ---- sidebar ---- */
.sidebar {
  width: 300px; flex: none; display: flex; flex-direction: column;
  background: #ffffffd9; backdrop-filter: blur(8px);
  border-right: 1px solid #d5dae5;
}
.brand { display: flex; align-items: center; gap: 12px; padding: 20px 18px 14px; }
.brand-badge {
  width: 42px; height: 42px; border-radius: 10px; flex: none;
  background: #c53030; color: #fff; font-size: 24px; font-weight: 700;
  display: flex; align-items: center; justify-content: center;
}
.brand h1 { font-size: 17px; letter-spacing: .5px; }
.brand p { font-size: 12px; color: #7a8194; }
/* how much of the course is behind you */
.progress { padding: 0 18px 12px; }
.pg-head {
  display: flex; align-items: baseline; gap: 8px;
  font-size: 12px; color: #6a7288; margin-bottom: 6px;
}
.pg-head .pg-label { flex: 1; }
.pg-count { font-variant-numeric: tabular-nums; color: #3a4051; font-weight: 600; }
.pg-reset {
  border: 0; background: none; padding: 0; cursor: pointer;
  font-family: inherit; font-size: 11px; color: #a4acbf;
}
.pg-reset:hover { color: var(--accent); text-decoration: underline; }
.pg-bar { height: 6px; border-radius: 3px; background: #e2e6ef; overflow: hidden; }
.pg-fill {
  height: 100%; width: 0; border-radius: 3px;
  background: var(--accent); transition: width .35s ease;
}
.progress.all-done .pg-fill { background: var(--done-c); }
.progress.all-done .pg-count { color: var(--done-c); }

.nav { flex: 1; overflow-y: auto; padding: 6px 10px; }

/* group header: a fold toggle, and the biggest text in the list */
.nav .nav-group {
  display: flex; align-items: center; gap: 8px; width: 100%;
  border: 0; background: none; cursor: pointer; font-family: inherit;
  padding: 13px 10px 7px; margin-top: 4px;
  font-size: 15px; font-weight: 700; color: #2b3142; letter-spacing: .5px;
}
.nav .nav-group .chev {
  font-size: 11px; color: #97a0b6; transition: transform .16s ease;
  display: inline-block; width: 11px;
}
.nav .nav-group.collapsed .chev { transform: rotate(-90deg); }
.nav .nav-group .gl { flex: 1; text-align: left; }
.nav .nav-group .cnt {
  font-size: 11px; font-weight: 400; color: #8a93a8;
  background: #e7ebf4; border-radius: 9px; padding: 1px 8px;
}
.nav .nav-group:hover { color: var(--accent); }
.nav-list.collapsed { display: none; }

.nav .nav-item {
  display: flex; align-items: center; width: 100%; text-align: left;
  border: 0; background: none;
  padding: 10px 12px; margin: 2px 0; border-radius: 9px; cursor: pointer;
  font-family: inherit; font-size: 14px; color: #3a4051;
}
.nav .nav-item .no {
  display: inline-flex; align-items: center; justify-content: center;
  width: 22px; height: 22px; flex: none;
  border-radius: 6px; background: #e7ebf4; color: #6a7288; font-size: 12px; margin-right: 9px;
}
.nav .nav-item.done .no { background: #dbf0e4; color: var(--done-c); }
.nav .nav-item:hover { background: #eef2fa; }
.nav .nav-item.active { background: var(--accent); color: #fff; }
.nav .nav-item.active .no { background: #ffffff2e; color: #fff; }
.side-foot { padding: 14px 18px; border-top: 1px solid #e2e6ef; font-size: 12px; }
.side-foot a { color: var(--accent); text-decoration: none; margin-right: 14px; }
.legend { display: flex; gap: 10px; flex-wrap: wrap; color: #6a7288; margin-bottom: 10px; }
.legend span { display: flex; align-items: center; gap: 4px; }
.lg { width: 10px; height: 10px; border-radius: 3px; display: inline-block; }
.lg-init { background: var(--init-c); }
.lg-final { background: var(--final-c); }
.lg-ctrl { background: var(--ctrl-c); }
.legend .lg-note { color: #8a93a8; }

/* ---- main column ---- */
.main { flex: 1; display: flex; flex-direction: column; min-width: 0; }

/* ---- notepad screen ---- */
.screen-wrap { display: flex; justify-content: center; padding: 22px 20px 6px; }
.notepad {
  width: min(680px, 92%); border-radius: 10px; overflow: hidden;
  background: #fff; border: 1px solid #c9cfdd;
  box-shadow: 0 18px 40px -18px #3c476a55;
}
.np-title {
  display: flex; justify-content: space-between; align-items: center;
  padding: 8px 6px 8px 14px; font-size: 12.5px; color: #444; background: #f3f4f7;
  border-bottom: 1px solid #e4e7ee;
}
.np-btns i { font-style: normal; color: #666; padding: 2px 12px; font-size: 11px; }
.np-body {
  position: relative; height: 260px; padding: 14px 16px;
  font-size: 24px; line-height: 1.7; letter-spacing: 1px;
  font-family: "Microsoft JhengHei", "Noto Sans TC", sans-serif;
  word-break: break-all;
  overflow-y: auto; scrollbar-width: none;
}
/* Only the committed text has newlines to keep, and it is the only thing
   that may preserve them: pre-wrap on the container would also render the
   line breaks and indentation of index.html itself, which is what used to
   push the first line in by an editor tab. */
#committed { white-space: pre-wrap; }
.np-body::-webkit-scrollbar { display: none; }
/* Drill mode gives the top block less room: the notepad is a mirror of
   what the IME is doing, whereas the article and the keyboard are what the
   learner is actually reading. Everything it gives up goes to the board,
   which fitKeyboard() then draws bigger. */
body.drilling .screen-wrap { padding: 12px 20px 2px; }
body.drilling .np-body { height: 152px; font-size: 20px; padding: 10px 14px; }
body.drilling .np-title, body.drilling .np-status { padding-top: 5px; padding-bottom: 5px; }
body.drilling .drill-bar { margin-top: 12px; }

.np-status {
  display: flex; align-items: center; gap: 10px;
  padding: 6px 14px; font-size: 12px; color: #667; background: #f7f8fa;
  border-top: 1px solid #e4e7ee;
}
.np-right { margin-left: auto; }
.mode-badge {
  display: inline-flex; width: 22px; height: 22px; border-radius: 5px;
  align-items: center; justify-content: center; font-size: 13px; font-weight: 700;
  transition: background .25s;
}
.mode-badge.zh { background: var(--accent); color: #fff; }
.mode-badge.en { background: #59637a; color: #fff; }

/* composition rendering — one color for the whole composition, like the
   real IME (the blue "undecided tone" segment was dropped in 2026-07-29:
   most hosts never draw IME colors). Pending segments are the ones still
   shown as bopomofo. */
#composition .ch { border-bottom: 2px dotted #8a90a0; padding-bottom: 1px; }
#composition .ch.settled, #composition .ch.pending { color: #111; }
#composition .ch.anchor { background: #cfe4ff; border-radius: 3px; }
.caret {
  display: inline-block; width: 2px; height: 1.15em; background: #222;
  vertical-align: -0.18em; animation: blink 1.05s steps(1) infinite;
}
@keyframes blink { 50% { opacity: 0; } }

/* candidate window */
.cand-card {
  position: absolute; z-index: 5; min-width: 150px;
  background: #fff; border: 1px solid #c8cede; border-radius: 9px;
  box-shadow: 0 10px 26px -8px #2c355a4d;
  padding: 4px 4px 2px; font-size: 15px; line-height: 1;
  animation: pop .16s ease-out;
}
@keyframes pop { from { opacity: 0; transform: translateY(-4px); } }
.cand-row { display: flex; align-items: center; gap: 10px; padding: 4px 10px; border-radius: 6px; }
.cand-row.sel { background: #dcebff; }
.cand-row .cn { color: #9aa1b4; font-size: 13px; width: 12px; }
.cand-pager { text-align: right; color: #9aa1b4; font-size: 11px; padding: 4px 8px 3px; }

/* ---- caption bar ---- */
.caption-bar[hidden], .drill-bar[hidden] { display: none; }
.caption-bar {
  display: flex; align-items: flex-start; gap: 16px;
  width: min(680px, 92%); margin: 10px auto; flex: none; min-height: 64px;
}
.controls { display: flex; gap: 6px; padding-top: 4px; }
.controls button {
  width: 38px; height: 38px; border-radius: 10px; border: 1px solid #c9cfdd;
  background: #fff; color: #3a4051; font-size: 15px; cursor: pointer;
}
.controls button:hover { background: #eef2fa; }
.controls button#btnPlay { background: var(--accent); border-color: var(--accent); color: #fff; }
/* Nothing advances on its own, so the button that carries on has to say
   "this step is over" out loud. */
/* A pulsing ring rather than a colour swap: fading the background through
   its midpoint drags the glyph to mid-grey and the button reads as
   disabled at exactly the moment it wants to be pressed. */
@keyframes btn-blink {
  0%, 100% { box-shadow: 0 0 0 0 #3b6fe000; border-color: #c9cfdd; }
  50%      { box-shadow: 0 0 0 5px #3b6fe033; border-color: var(--accent); }
}
.controls button.blink {
  color: var(--accent);
  animation: btn-blink 1.15s ease-in-out infinite;
}
@media (prefers-reduced-motion: reduce) {
  .controls button.blink {
    animation: none; border-color: var(--accent); box-shadow: 0 0 0 3px #3b6fe033;
  }
}
.caption { flex: 1; min-width: 0; }
.cap-step { font-size: 12px; color: #7a8194; margin-bottom: 3px; }
.cap-text { font-size: 15px; line-height: 1.65; }
.cap-text b { color: var(--accent); }
.cap-text kbd {
  font-family: Consolas, monospace; background: #eceff6; border: 1px solid #cdd3e0;
  border-bottom-width: 2px; border-radius: 5px; padding: 0 6px; font-size: 13px;
}

/* ---- 3D keyboard ---- */
.kb-stage {
  flex: 1; min-height: 0; display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  perspective: 1400px; perspective-origin: 50% 20%;
  user-select: none; cursor: grab; padding-bottom: 8px;
}
.kb-stage.dragging { cursor: grabbing; }
.kb-tilt { transform-style: preserve-3d; transition: transform .12s linear; }
.kb-body {
  position: relative; transform-style: preserve-3d;
  background: linear-gradient(180deg, #2c2f37, #23252c);
  padding: 12px; border-radius: 10px;
  box-shadow: inset 0 1px 0 #ffffff14;
}
/* case thickness: bottom copy + four side faces folded down (-Z) */
.kb-under {
  position: absolute; inset: 0; border-radius: 10px;
  background: #101216; transform: translateZ(-18px);
}
.kb-front, .kb-back {
  position: absolute; left: 0; right: 0; height: 18px;
}
.kb-front {
  top: 100%; transform-origin: 50% 0; transform: rotateX(-90deg);
  background: linear-gradient(180deg, #22252c, #121419);
}
.kb-back {
  bottom: 100%; transform-origin: 50% 100%; transform: rotateX(90deg);
  background: #16181d;
}
.kb-left, .kb-right {
  position: absolute; top: 0; bottom: 0; width: 18px;
  background: linear-gradient(180deg, #1d2026, #14161b);
}
.kb-left  { right: 100%; transform-origin: 100% 50%; transform: rotateY(-90deg); }
.kb-right { left: 100%;  transform-origin: 0 50%;    transform: rotateY(90deg); }
.kb-row { display: flex; gap: var(--kgap); margin-bottom: var(--kgap); transform-style: preserve-3d; }
.kb-row:last-child { margin-bottom: 0; }

/* mechanical-style keycap: plate hole (.key) + cap skirt (.side) + top (.cap) */
.key {
  position: relative; height: var(--u); border-radius: 6px;
  background: #0c0e12; transform-style: preserve-3d;
  flex: none;
}
.key .side {
  position: absolute; inset: 0; border-radius: 6px;
  background: linear-gradient(180deg, #d7d9de, #a6aab4);
  transform: translateZ(13px);
  transition: transform .14s ease, background .14s ease;
}
.key .cap {
  position: absolute; inset: 3px; border-radius: 5px;
  background: linear-gradient(180deg, #fcfcfb, #e9eaec);
  box-shadow: inset 0 1px 0 #ffffff, inset 0 -3px 4px #0000000f;
  transform: translateZ(19px);
  transition: transform .14s ease, background .14s ease;
  display: flex; align-items: center; justify-content: center;
  color: #33363f; font-size: 14px;
}
.key:not(.wide) .cap .main { position: absolute; top: 4px; left: 7px; }
.key .cap .main { pointer-events: none; }
.key .cap .sub-i, .key .cap .sub-f, .key .cap .sub-c {
  position: absolute; font-size: 10px; line-height: 1; pointer-events: none;
  letter-spacing: .3px; text-align: right;
}
.key .cap .sub-i { top: 4px; right: 5px; color: var(--init-c); font-weight: 700; }
/* The vowel the initial is recited with -- ㄎ on its own is ㄎㄜ. Same size
   as the initial, told apart by weight rather than by size: ㄧ is a single
   horizontal stroke and shrinking it turns it into a dash. */
.key .cap .sub-i i {
  display: block; font-style: normal; font-weight: 600;
  margin-top: 2px; opacity: .92;
}
/* one line per reading: the pairs are too wide to sit side by side, and
   stacking them keeps every key's annotation in the same narrow column */
.key .cap .sub-f { bottom: 4px; right: 5px; color: var(--final-c); font-weight: 600; }
.key .cap .sub-f i { display: block; font-style: normal; }
.key .cap .sub-f i + i { margin-top: 2px; }
.key .cap .sub-c { bottom: 4px; left: 5px; color: var(--ctrl-c); font-size: 9px; }
.key.wide .cap { font-size: 11.5px; color: #6a6f7c; }
/* the keys this step pressed, held until the next step */
.key.lit .cap {
  background: linear-gradient(180deg, #dce8ff, #bcd3fb);
  color: #17335c;
  box-shadow: inset 0 1px 0 #fff, 0 0 0 2px #3b6fe085;
}
.key.lit .side { background: linear-gradient(180deg, #b9c9e6, #93a8cc); }

.key.pressed .side { transform: translateZ(5px); }
.key.pressed .cap {
  transform: translateZ(10px);
  background: linear-gradient(180deg, #cfe0ff, #b8d0fb);
  color: #17335c;
}
.kb-foot {
  margin-top: 14px; display: flex; align-items: center; gap: 12px;
  cursor: default;
}
.kb-hint { font-size: 12px; color: #8a93a8; }
/* Only a drill has a "next key" to give away, so the help level is only
   offered there; the tutorial needs its keyboard on and lit. */
.kb-mode {
  display: none; border-radius: 8px; border: 1px solid #c9cfdd;
  background: #fff; color: #3a4051; cursor: pointer;
  font-size: 12px; font-family: inherit; padding: 4px 10px;
}
.kb-mode:hover { background: #eef2fa; }
.kb-mode.off { color: #8a93a8; background: #f4f6fa; }
body.drilling .kb-mode { display: inline-block; }
/* Hidden board: the stage stops claiming the space it was filling, and the
   drag hint goes with it -- there is nothing left to drag. */
body.kb-hidden.drilling .kb-tilt,
body.kb-hidden.drilling .kb-hint { display: none; }
body.kb-hidden.drilling .kb-stage { flex: none; }

/* The board is scaled to fit by fitKeyboard() in app.js -- a media query
   cannot see how tall the panel above it happens to be. Shrinking --u
   instead would not work either: the annotations are sized in px, so the
   four lines on d and r would collide. */
@media (max-width: 1200px) { .np-body { font-size: 21px; } }

/* ---- typing drill ---- */
.drill-bar { width: min(680px, 92%); margin: 10px auto 0; flex: none; }
.drill-head { display: flex; align-items: center; gap: 10px; }
.drill-title { font-size: 15px; font-weight: 700; flex: none; }
.drill-meter {
  flex: 1; height: 6px; border-radius: 3px; background: #d9dfeb; overflow: hidden;
}
.drill-meter i { display: block; height: 100%; width: 0; background: var(--accent); transition: width .12s linear; }
.drill-count { font-size: 12px; color: #7a8194; flex: none; font-variant-numeric: tabular-nums; }
.drill-bar button {
  width: 28px; height: 28px; flex: none; border-radius: 8px; border: 1px solid #c9cfdd;
  background: #fff; color: #3a4051; cursor: pointer; font-size: 13px;
}
.drill-bar button:hover { background: #eef2fa; }
/* muted: the emoji stays legible, it just stops shouting */
.drill-bar button.off { color: #a8aebd; background: #f4f6fa; }
.drill-intro { font-size: 12.5px; line-height: 1.6; color: #6a7288; margin: 7px 0 8px; }
.drill-text {
  font-size: 20px; line-height: 1.75; letter-spacing: 1px;
  padding: 10px 14px; border-radius: 9px; background: #ffffffcc;
  border: 1px solid #dde2ec; color: #aeb5c4; word-break: break-all;
  /* exactly four lines, whole: one sentence per line would otherwise grow
     the panel until it squeezed the keyboard off the bottom. 1.75em is the
     line height, and the padding and border are inside the box. */
  max-height: calc(1.75em * 4 + 22px); overflow-y: auto; scrollbar-width: thin;
}
.drill-text .done { color: #2f9e8f; }
/* typed, but not cleanly: a wrong key was pressed somewhere in this
   character. Marked afterwards, never while it is being typed. */
.drill-text .done.slip { color: #c2662c; }
/* the line break, shown so the two Enters have something to land on */
.drill-text .nl { color: #b9c0d0; font-size: .8em; padding: 0 2px; }
.drill-text .nl.now { color: #fff; }
.drill-text .now {
  color: #fff; background: var(--accent); border-radius: 4px;
  padding: 0 2px; margin: 0 1px;
}
.drill-next { font-size: 13px; color: #6a7288; margin-top: 8px; min-height: 22px; }
.drill-next kbd {
  font-family: Consolas, monospace; background: var(--accent); color: #fff;
  border-radius: 5px; padding: 1px 9px; font-size: 13px; margin: 0 3px;
}
.drill-next .warn { color: #c53030; }
.drill-next .cheer { color: #2f9e8f; font-weight: 700; }
.drill-next .slip-count { color: #c2662c; font-weight: 700; }

/* the key the drill is waiting for */
.key.hint .cap {
  background: linear-gradient(180deg, #fff2c9, #ffd979);
  color: #6a4a05;
  box-shadow: inset 0 1px 0 #fff, 0 0 0 2px #f0a92e80;
}
.key.hint .side { background: linear-gradient(180deg, #e8cf95, #c9ab6b); }
/* Space is one key but two thumbs: only the half belonging to the free hand
   lights up, so the alternation is visible without a second annotation. The
   ring around the whole key stays -- it is still one key to find. */
.key.hint.hint-left .cap {
  background: linear-gradient(90deg, #ffd979 0 47%, #fbfbfa 53% 100%);
}
.key.hint.hint-right .cap {
  background: linear-gradient(90deg, #fbfbfa 0 47%, #ffd979 53% 100%);
}
.drill-warn { font-size: 12px; color: #a2683c; margin-top: 6px; }
