/* ══════════════════════════════════════════════════════════════════
   個人英文單字工具 · 樣式
   深淺雙主題（跟隨系統）。美式復古三色：深藍 #1C223A／米 #E5D4B6／橙 #F44E1C。
   強調色只有一處：橙＝「差一點」（near_miss），本工具的招牌狀態。

   兩版是同一套色相的一體兩面，不是各配各的：
     暗版＝深藍底、米字、橙塊（＝參考海報本身的長相）
     亮版＝米底、深藍字、橙塊（翻轉）

   ⚠️ 招牌色的 token 仍叫 `--amber*`。值已經從琥珀換成橙，但名字沒改——
   全站 47 處引用分散在三個 css 檔，改名只買到命名精確，卻要冒漏改一處
   就少一塊顏色的風險。要改名請一次改完並實機掃過三個主題狀態。
   ══════════════════════════════════════════════════════════════════ */

/* ── 設計變數 ───────────────────────────────────────────────── */
:root {
  color-scheme: light dark;

  --font-ui: -apple-system, BlinkMacSystemFont, "Helvetica Neue",
             "PingFang TC", "Heiti TC", sans-serif;
  --font-serif: "Iowan Old Style", "Palatino Linotype", Palatino,
                "Palatino LT STD", Georgia, "Times New Roman",
                "Songti TC", serif;
  --font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;

  /* 揭曉的正解字級：與例句（.sentence）同級。
     正解是要讀的內容，不是註解——比句子小反而要重新對焦（V 2026-07-27）。
     練習、程度評估、辨析卡三個畫面共用這一個值，改這裡就全部跟著動。
     判定短語（答對了／不對，再試一次／差一點）不吃這個 token，維持 15px：
     那是提示語不是正解，拉大會跟正解搶。 */
  --answer-size: 32px;

  /* 字級階梯（V 2026-07-29：「字有點小」）。全站只有這七階，地板 13px。
     改版前有 21 種字級、130 個宣告裡 72 個在 12.5px 以下——階數多就守不住，
     每個時期各寫各的，最後長出 10px 的側欄項目名。
     需要「更輕」的層級時用顏色與字重去分，**不要再往下砍字級**。 */
  --fs-xs:   13px;   /* 標籤、計數、輔助說明——這是地板 */
  --fs-sm:   15px;   /* 次要內文 */
  --fs-base: 17px;   /* 內文基準（body） */
  --fs-lg:   20px;   /* 母語說法、輸入框 */
  --fs-xl:   24px;   /* 區塊標題 */
  --fs-hero: 32px;   /* ＝ --answer-size，例句與正解 */

  /* 中性色：暖米底、深藍字。明度是解出來的不是挑的。

     ⚠️ 每個前景 token 都對**最不利的那個面**求解，不是對 --bg：
       淺色 → --bg-sunk（86%，最暗的面，側欄與頁尾用它）
       深色 → --surface-2（22%，最亮的面）
     中間調的字在越接近自己明度的底上對比越低，所以「對 --bg 算過就好」
     是錯的——2026-07-29 就這樣漏掉 25 種元件（側欄項目名 3.68:1、
     推薦度星星 3.06:1）。**改任何一個面的明度，前景全部要重算。** */
  --bg:         hsl(38 45% 93%);      /* 米的淡版。原色 #E5D4B6 當整頁底太重 */
  --bg-sunk:    hsl(38 47% 86%);      /* 側欄、頁尾這類下沉面 */
  --surface:    hsl(38 55% 97%);      /* 卡片。帶暖，不用純白才不會跟米底打架 */
  --surface-2:  hsl(38 40% 90%);
  /* 亮版走像素冒險風（V 2026-08-06 拍板）：底維持羊皮米白不動，
     前景從深藍改成墨褐——藍墨配米底是編輯感，暖墨配米底才是羊皮感。

     ⚠️ 換色相時明度不能照抄。同一個 HSL lightness，暖色相（35–45）的
     相對亮度比藍色相（228）高，直接沿用會讓對比整批掉下來。所以每個
     前景 token 的 L 都往下讓了 2–4 個點，讓對比維持在原本解出來的水準。 */
  --line:       hsl(35 22% 47%);      /* 3.0:1 —— 舊版 1.29:1 等於看不見 */
  --line-soft:  hsl(35 25% 76%);      /* 1.9:1 —— 舊版 1.12:1 */
  --ink:        hsl(35 18% 17%);      /* 12.0:1 墨褐 #362F27 的可讀版 */
  --ink-soft:   hsl(35 14% 27%);      /* 7.0:1 */
  --ink-faint:  hsl(35 12% 38%);      /* 4.5:1 —— 舊版 3.27:1 不到內文標準 */

  /* 招牌色：橙＝差一點。名字仍是 amber，見檔頭說明。
     ⚠️ 橙在米底上只有 2.42:1，**不可以拿來當內文顏色**——
     海報裡的橙是大色塊不是字。--amber 是壓深過的版本（4.6:1）才給文字用，
     --amber-line 是原色 #F44E1C，只做填色、外框與強調塊。 */
  --amber:      hsl(14 91% 38%);
  --amber-line: hsl(14 91% 53%);
  --amber-soft: hsl(14 80% 92%);

  /* 揭曉的正解專用綠（V 2026-08-02：「不夠明顯，要更亮的綠」）。
     ⚠️ **亮版與暗版要往相反方向調**——在米底上「更亮」＝對比更低＝更看不清楚。
     一路提亮到最鮮那版對比只剩 2.26:1，連大字的 3:1 都過不了。
     所以亮版走的是**拉飽和、明度不動**：45% → 70% 飽和，對比 5.30 → 4.24:1，
     看起來鮮很多而且還守得住內文的 4.5:1（底線與小字共用這顆）。
     不動全站的 --green：那顆給判定文字、統計、軌跡格用，它們是小字。 */
  --green-answer: hsl(150 70% 30%);

  /* 語意色：對著暖底重算，並把紅往紫紅移，跟橙拉開色相 */
  --green:      hsl(150 45% 30%);     /* 4.5:1 */
  --green-line: hsl(150 45% 38%);
  --green-soft: hsl(150 40% 91%);
  --red:        hsl(352 60% 45%);     /* 5.6:1，色相 352 離橙的 14 夠遠 */
  --red-line:   hsl(352 60% 50%);
  --red-soft:   hsl(352 60% 93%);

  --focus:      hsl(40 70% 24%);      /* 亮版像素風：焦點環改金褐，跟墨褐字同一家族 */

  /* 亮版像素冒險風的金（V 2026-08-06）。主要動作鍵與進度條的已完成段共用。
     字對金底 4.61:1、對 hover 的亮金 5.53:1，兩態都過內文標準——
     所以 hover 是**往亮走**不是往暗走：金底壓深會把字擠到 3.08:1。 */
  --gold:       hsl(43 62% 52%);      /* #D0A539 */
  --gold-hover: hsl(43 68% 60%);      /* #DEB754 */
  --gold-line:  hsl(40 66% 38%);      /* #A17621 外框，跟金底差 1.79:1 看得出邊 */
  --gold-ink:   hsl(35 25% 23%);      /* #493D2C 金底上的字 */

  /* 亮版頁尾的像素軟鍵。參考圖把「導覽／偷瞄」做成藍色工具鍵，
     「送出／繼續」才是橙色主鍵；兩者不可再共用同一顆金色 token。 */
  --pixel-blue:        hsl(210 66% 43%);
  --pixel-blue-hover:  hsl(210 67% 50%);
  --pixel-blue-line:   hsl(214 69% 24%);
  --pixel-blue-ink:    hsl(40 72% 92%);
  --pixel-orange:      hsl(31 78% 34%); /* 米字 4.96:1 */
  --pixel-orange-hover:hsl(31 78% 36%); /* 米字 4.53:1 */
  --pixel-orange-line: hsl(28 73% 29%);
  --pixel-orange-ink:  hsl(39 83% 93%);
  --track-rail:        hsl(35 28% 24%);
  --track-cell:        hsl(39 54% 84%);

  /* 進度列的 Mac Dock 式距離衰減；採 transitions.dev avatar-group 的
     transform token，節點放大與回復共用同一套速度／緩動。

     ⚠️ 2026-08-08 換過模型（V 拍板 C 案）。舊版是**指數衰減**
     `1 + (1.55-1) × .45^d`：尾巴無限長，d=3 還有 1.05 倍、永遠回不到 1.0。
     格高砍半之後那條尾巴會讓整條軌跡看起來都微微鼓起，山丘反而糊掉，
     所以改成**有限半徑的線性斜坡**——峰值落在 d=1、到 --avatar-reach 精準歸 1。
     峰值不在 d=0：勇者腳下那格是壓扁的落腳凹槽（見 .t-avatar--current），
     山頭本來就在他左右兩側。 */
  --avatar-dur: 320ms;
  --avatar-peak: 2;      /* d=1 的高度倍率。11px × 2 = 22px＝改版前的正常格高 */
  --avatar-reach: 3;     /* 第幾格回到正常高度。d ≥ 3 一律 1.0 */
  /* 題格的版面高（.progress .track .seg 用）。壓扁比與落腳格的下沉量都由它推算，
     所以格高只有這一處寫死，改這裡三邊一起動。 */
  --track-seg-h: 11px;
  /* 勇者腳下那格壓扁後的比例。靴底要站在壓扁格的上緣。
     2026-08-08：0.3（＝3.3px）→ 0.364。V 指定壓扁格至少留 4px：格高砍到 11px 後
     3.3px 在低密度（文法 3 題、格寬 181px）會細成一條鉛筆線。4 ÷ 11 = .364。
     ⚠️ 動到 --track-seg-h 就要重算這個值。 */
  --track-current-squash: .364;
  --avatar-ease-in: cubic-bezier(.22, 1, .36, 1);
  --avatar-ease-out: cubic-bezier(.34, 3.85, .64, 1);
  --track-dock-margin: 10px;
  /* 勇者腳底與腳下那格壓扁格的空隙。0 會變成切齊、踩在線上（V 2026-08-06）。 */
  --track-knight-gap: 3px;
  /* 騎士 sprite 的顯示高。.trk-mark--knight img 與 .track-wrap 的頭頂留白
     （--track-head）讀同一顆：留白＝身高扣掉沉進題格的部分，寫死兩份會讓
     換角色時頭頂被切或多留一段空白。 */
  --track-knight-h: 47px;
  /* 落腳格往下沉多少（亮暗版共用，見 .t-avatar--current）。
     V 2026-08-08 的驗收線是「**角色的底部**和左右格的底部切齊」，落腳格跟著他沉，
     所以推導是從靴底回推、不是從格子：
       靴底 ＝ 鄰格底線 ＝ 中心軸 ＋ 格高 × peak ÷ 2
       落腳格上緣 ＝ 靴底 ＋ --track-knight-gap
       位移量 ＝ 上緣 − 壓扁後自己的上緣（中心軸 − 格高 × squash ÷ 2）
              ＝ 格高 × (peak ＋ squash) ÷ 2 ＋ gap ＝ 16px
     結果是那格整個掉到底線**以下**，變成勇者腳邊的一小塊踏板——這是要的效果，
     不是算錯。**app.js 的 paintTrackMarks() 用同一條線定靴底錨點**（它直接算
     「鄰格底線 ＋ gap」，所以不必再讀 squash；改壓扁比時 CSS 自己吸收，錨點不動）。 */
  --track-current-drop: calc(
    var(--track-seg-h) * (var(--avatar-peak) + var(--track-current-squash)) / 2
    + var(--track-knight-gap));
  /* 題卡上方那一列的共用節拍（V 2026-08-08：「切換時上方沒有跳動或間距差異大」）。
     單字是 .practice-top（熟練度＋標籤），文法是 .gr-tags（程度／之前答錯過＋熟練度），
     版型不同但**必須讀同一組數字**，第一行字的上緣才會落在同一條線上：
       導覽列下緣 ＋ --card-meta-top ＋ --card-meta-h ＋ --card-meta-gap
     25px 是標籤自己的高度（13px 字 ＋ 上下 1px 內距 ＋ 1px 框＝24.8）取整；
     寫成固定高度是為了「有沒有標籤都一樣高」——不然單字揭曉後長出標籤、
     文法碰到沒標籤的題目，兩邊的第一行字各自會跳個 4px。 */
  --card-meta-top: 12px;
  --card-meta-h: 25px;
  --card-meta-gap: 10px;
  /* 窄版把這段收成 6px（原本只有文法那邊收，單字沒跟上）。改一顆值兩邊一起動。 */
  --duration-fast: 250ms;
  --ease-in-out: ease-in-out;

  /* `viewport-fit=cover` 讓頁尾背景延伸到滿版；主要操作內容則必須留在 iOS
     Safe Area 內。保留成變數，讓單字／文法頁尾與瀏覽器幾何測試共用。 */
  --safe-area-bottom: env(safe-area-inset-bottom, 0px);

  /* 熟練度（V 2026-08-06 定案）：亮點單一綠、未達灰，不再有等級色階。
     綠是像素概念圖那顆草綠，兩版各自解明度——亮版在羊皮上要壓深才看得見
     （3.26:1），暗版在夜藍上可以放亮（8.38:1）。 */
  --mastery-on:  hsl(95 52% 38%);
  --mastery-off: hsl(35 10% 62%);

  /* 頁尾上方那條山景帶的顏色（遮罩圖只存 alpha，顏色在這裡給）。 */
  --band-ink:    hsl(88 22% 40%);
  /* 山景帶的高度。捲動區底部的跑道從這個值推導（見 --area-pad-b），
     換圖時只改這一個數，兩邊不會漂掉。 */
  --band-h: 356px;
  /* 捲動區底部的跑道。山景的遮罩到 55% 才全不透明，所以「最濃、字最難讀」
     的就是它的底部 45%（160px）；再加一行字的呼吸＝200px。留這段可捲的空白，
     使用者能自己把最後幾行拉到山景之上的乾淨紙面上看（V 2026-08-09）。
     這是 padding 不是固定空白：內容短的時候容器根本捲不動，不會憑空多出捲軸。 */
  --area-run: calc(var(--band-h) * .45 + 40px);

  --cal-1: hsl(38 55% 32%);
  --cal-4: hsl(35 18% 26%);

  --radius: 10px;
  --radius-lg: 16px;
  --shadow: 0 1px 2px hsl(252 30% 20% / .05),
            0 8px 24px -14px hsl(252 40% 25% / .18);

  /* 動態：全域訊息列的進出場（transitions.dev 22-toast 的原始調校值）。
     進場比退場慢——「到達」要從容，「離開」不必拖。這個不對稱是刻意的。 */
  --toast-open: 350ms;
  --toast-close: 250ms;
  --toast-distance: 16px;
  --toast-blur: 2px;
  --toast-scale: 0.97;
  --toast-ease: cubic-bezier(0.22, 1, 0.36, 1);

  /* 動態：單字小卡。桌面是點擊觸發的，所以 17-tooltip 原本那 80ms 意圖延遲
     不套用——那是 hover 防誤觸用的，在點擊上只會變遲鈍。 */
  --wt-in: 150ms;
  --wt-out: 50ms;
  --wt-scale: 0.98;
  --wt-in-ease: ease-out;
  --wt-out-ease: ease-out;
  /* 手機底部抽屜（07-panel-reveal） */
  --wt-sheet-open: 400ms;
  --wt-sheet-close: 350ms;
  --wt-sheet-blur: 2px;
  --wt-sheet-ease: cubic-bezier(0.22, 1, 0.36, 1);

  /* 動態：句中重打打錯的回饋（transitions.dev 12-error-state-shake）。
     四段不對稱位移——右、左、過衝、歸位；過衝那一段是它比對稱來回好的原因。
     ⚠️ keyframes 的百分比停點是「各段累積時間 ÷ 總長」算出來的固定值，
     改 dur-a / dur-b 的比例就要回去重算，否則段落會跟總長對不上。 */
  --shake-distance: 6px;
  --shake-overshoot: 4px;
  --shake-dur-a: 80ms;
  --shake-dur-b: 60ms;
  --shake-ease: cubic-bezier(0.22, 1, 0.36, 1);

  /* 動態：文法批改的等待提示（transitions.dev 15-shimmer-text ＋
     04-text-states-swap）。字色是 V 2026-07-30 在四個候選裡挑的「丙」：
     底色走 --focus，亮帶走比 --ink 再深（亮版）／再亮（暗版）一格的墨。
     ⚠️ --shimmer-highlight 的兩個值是色票裡沒有的極端值，**只給這個元件用**，
     不要升格成通用 token——它們是為了讓亮帶掃得出來才存在的。
     亮版底色 7.11:1、暗版 9.79:1（對 --bg 實算）。比 --ink 的 11.9 低，
     這是 V 拍板時知道並接受的取捨：他要的是「顏色不一樣」。 */
  --shimmer-base: var(--focus);
  --shimmer-highlight: hsl(228 35% 10%);
  --shimmer-dur: 2000ms;
  --shimmer-band: 400%;
  --shimmer-ease: linear;
  --text-swap-dur: 150ms;
  --text-swap-translate-y: 4px;
  --text-swap-blur: 2px;
  --text-swap-ease: ease-in-out;
  --gwait-in: 250ms;
  --gwait-distance: 8px;
  --gwait-ease: cubic-bezier(0.22, 1, 0.36, 1);

  /* 鍵盤切換只補間題目位移；Root 與可視高度立即跟實際 viewport 同步。 */
  --kb-shift-dur: 250ms;
  --kb-shift-ease: cubic-bezier(0.22, 1, 0.36, 1);

  /* 資料長條專用的填色。**不要拿文字用的 --green/--amber/--red 來鋪長條**：
     那三個是照 4.5:1 的文字標準調的，鋪成大面積會過亮搶眼
     （V 2026-07-29 回報程度評估的頻率層長條）。大色塊不需要那麼高的強度，
     長度本身已經在表達數值了。招牌橙尤其要留給「差一點」那一個狀態。 */
  --green-fill: hsl(150 30% 45%);
  --amber-fill: hsl(28 45% 58%);
  --red-fill:   hsl(352 32% 62%);

  /* 第三種語意色：只給「形近型」那個徽章用（V 2026-08-10）。
     三型的處置完全不同，顏色要分得開：換字借招牌橙（他其實會，跟 near_miss
     同一個語意家族）、拼字用中性、形近需要一個既不是「對」也不是「錯」的第三色。
     色相 268 離橙 14、紅 352、綠 150 都夠遠。對米底 5.1:1，過內文標準。 */
  --purple:      hsl(268 45% 38%);
  --purple-line: hsl(268 45% 62%);
  --purple-soft: hsl(268 50% 95%);
}

/* 窄版把資訊列與題目之間收緊。單字與文法讀同一顆，所以只改這裡一處。 */
@media (max-width: 640px) {
  :root { --card-meta-gap: 6px; }
}

@media (prefers-color-scheme: dark) {
  :root {
    /* 暗版 A（V 2026-08-09）：取樣參考圖的深海軍藍與暖米白，不沿用舊紫黑底。 */
    --bg:         #0d1422;
    --bg-sunk:    #080d17;
    --surface:    #131826;
    --surface-2:  #1c2433;
    --line:       #56627a;
    --line-soft:  #32353e;
    --ink:        #eadabf;
    --ink-soft:   #d7cab1;
    --ink-faint:  #959186;

    --amber:      hsl(14 91% 62%);    /* 暗底上要提亮才讀得到 */
    --amber-line: hsl(14 91% 53%);    /* 原色 #F44E1C */
    --amber-soft: hsl(14 45% 19%);

    --green:      #8eb58d;
    --green-line: #6e9979;
    --green-soft: #17261f;
    --green-answer: #8eb58d;
    --red:        hsl(352 60% 68%);
    --red-line:   hsl(352 55% 50%);
    --red-soft:   hsl(352 35% 18%);

    --focus:      #a29abc;
    --mastery-on: #8eb58d;
    --mastery-off: #4f5053;

    --cal-1: hsl(38 45% 51%);
    --cal-4: hsl(38 45% 72%);

    --shadow: 0 1px 2px hsl(228 45% 3% / .5),
              0 10px 28px -16px hsl(228 45% 3% / .75);
    /* 等待提示的亮帶：暗底上要往上提才掃得出來（亮版是往下壓）。
       --shimmer-base 不必在這裡覆蓋——它寫的是 var(--focus)，
       上面剛把 --focus 換成米色，它就跟著翻了。 */
    --shimmer-highlight: hsl(38 45% 95%);
    --green-fill: hsl(150 28% 40%);
    --amber-fill: hsl(28 40% 48%);
    --red-fill:   hsl(352 28% 48%);

    /* 形近徽章的第三色。暗底要提亮才讀得到，底則要壓深——跟 --amber 同一種翻法。 */
    --purple:      hsl(268 55% 76%);
    --purple-line: hsl(268 45% 58%);
    --purple-soft: hsl(268 30% 20%);
  }
}

/* ── 基礎 ───────────────────────────────────────────────────── */
* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  max-width: 100%;
  overflow-x: hidden;          /* 不允許橫向捲動 */
}
/* clip 不會建立捲動容器，才不會弄壞側欄的 position: sticky */
@supports (overflow-x: clip) {
  html, body { overflow-x: clip; }
}

body {
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font-ui);
  font-size: 17px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

[hidden] { display: none !important; }

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

input, textarea, select {
  font: inherit;
  color: inherit;
}

a { color: var(--focus); }

:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
  border-radius: 4px;
}

.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;
}

/* ── 版面 ───────────────────────────────────────────────────── */
/* ── 導覽在上、內容在下（V 2026-08-02 拍板）─────────────────────────
   原本是 `display:flex` 的左右兩欄（78px 側欄 ＋ 內容）。手機上那 78px
   吃掉螢幕的 19%，句子被擠成四行；200 句實測，側欄整條讓開有 76.5% 的
   句子少一行，而只瘦成 52px 只有 24.5%——所以是整條讓開，不是瘦身。

   ⚠️ **`min-height` 不能換成 `height`**：主頁、單字庫、數據那幾個畫面靠
   body 捲動，寫死高度它們就捲不動了。只有練習與文法是「一個視窗高的
   三段式版面」，它們自己用 calc 扣掉導覽列（見 #screen-practice）。 */
.app {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
  min-height: var(--vvh, 100dvh);
}

/* 單字／文法本來就是一個視窗高、內部自行捲動的畫面。進入畫面時就固定
   App 的定位模式，不能等鍵盤過場才從一般流切成 fixed；iOS 的
   visualViewport.offsetTop 在動畫中有暫態值，拿它移動整個 App 會讓 Top Nav
   先掉到畫面中間再彈回。鍵盤只更新 --vvh，頂端永遠維持 0。 */
html.is-training-screen .app {
  position: fixed;
  top: 0;
  right: 0;
  left: 0;
  height: var(--vvh, 100dvh);
  min-height: 0;
  overflow: hidden;
}

/* iOS 的橡皮筋捲動不是一般 overflow：即使 App 本身 fixed，手指從頁尾或
   內容捲動區的邊界繼續拖，visual viewport 仍可能把整個 fixed shell 往上帶，
   看起來就像 Bottom Bar 可以被拉離底部。練習頁只允許中段內部捲動，root
   永遠鎖死；離開單字／文法後 class 會移除，主頁等長頁照常由 body 捲動。 */
html.is-training-screen,
html.is-training-screen body {
  width: 100%;
  height: 100%;
  overflow: hidden;
  overscroll-behavior: none;
}
html.is-training-screen body {
  position: fixed;
  inset: 0;
}

/* sticky top:0 ＝ 內容捲動時導覽列留在畫面上，跟從前側欄 sticky 是同一個用意。
   z-index 40：導覽列不該蓋住浮在內容上的元件。 */
.topnav {
  flex: 0 0 auto;
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
  align-items: center;
  gap: 12px;
  padding: 6px 16px;
  background: var(--bg-sunk);
  border-bottom: 1px solid var(--line-soft);
  position: sticky;
  top: 0;
  z-index: 40;
}

/* 三區結構：左識別／中央五鍵／右工具。左右欄使用相同 1fr，中央群的幾何中心
   才會永遠等於視窗中心，不因 DEV、帳號或主題鍵出現而漂移（V 2026-08-04）。 */
.topnav__identity {
  justify-self: start;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  min-width: 0;
}

.topnav__main {
  justify-self: center;
  display: flex;
  align-items: center;
  gap: 4px;
}

/* 招牌是按鈕（點了回單字練習），但不該長得像按鈕——
   平常維持原本的靜態外觀，只在 hover／focus 時提示可點 */
.topnav__mark {
  font-family: var(--font-serif);
  font-size: 17px;
  font-style: italic;
  letter-spacing: .01em;
  color: var(--ink-soft);
  padding: 4px 8px;
  border-radius: 6px;
  white-space: nowrap;
  user-select: none;
  transition: color .15s ease, background-color .15s ease;
}
.topnav__mark:hover { color: var(--ink); background: var(--surface-2); }

/* 環境／正式版號記號（V 2026-08-12）。HTTP 顯示 DEV，HTTPS 顯示正式版號，
   兩者都由 release.js 的單一版本來源決定。
   刻意**不用招牌橙**（--amber*）——那個顏色全站只代表 near_miss（見檔頭），
   多一個用途就少一分辨識度。這裡要的是「看得到」，不是「跳出來」。 */
.topnav__release {
  font-size: var(--fs-xs);
  font-weight: 600;
  letter-spacing: .06em;
  line-height: 1;
  padding: 3px 6px;
  border-radius: 4px;
  color: var(--ink-faint);
  background: var(--surface-2);
  border: 1px solid var(--line-soft);
  user-select: none;
}
/* DEV／Prod 版號都固定疊在 Logo 正下方，中央五鍵仍由三區 Grid 獨立置中。 */

/* 主題與帳號固定在右欄；不再用 auto margin 推動中央功能群。
   gap 8 → 4（V 2026-08-08：「間隔很大，很奇怪」）。這兩顆的按鈕本體已經縮到
   跟像素稿同尺寸（見 .autospeak-btn），沒有內距在中間墊，看到的距離就是這個 gap。 */
.topnav__tail {
  justify-self: end;
  display: flex;
  align-items: center;
  gap: 4px;
}

.empty-note { color: var(--ink-faint); font-size: 15px; }

/* 標籤常駐在 icon **下方**——取代原本的 title 屬性（瀏覽器原生 tooltip 延遲
   0.5–2 秒且時間不固定，是「慢又不一致」的根因）。V 2026-08-02 指定橫排之後
   文字仍然放圖示下面、不要移到右側，所以 flex-direction 維持 column。

   寬度改成內容撐開＋44px 觸控下限：直欄時代寫死 62px，橫排時五顆鍵要跟
   招牌、主題、帳號共用一列，寫死寬度在 402px 會擠爆。 */
.nav-btn {
  min-width: 44px;
  min-height: 44px;
  padding: 4px 8px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  border-radius: var(--radius);
  color: var(--ink-faint);
  position: relative;
  transition: background-color .15s ease, color .15s ease;
}
.nav-btn svg { width: 20px; height: 20px; display: block; }

/* ── 導覽共用高密度像素圖示（亮版 2026-08-07、暗版 2026-08-09）─────
   兩版都使用同一套 HD 像素稿；舊 inline SVG 留在 markup 作回退，但不顯示。 */
/* 主題切換鈕：亮／暗版都換成同規格的高清像素圖。原本 Unicode 字元 ☀／☾
   是字型字符，輪廓與按鈕框無法忠於 V 給的概念圖。 */
.theme-pick__png { display: block; }

/* 功能圖示在白框裡儘量放大（V 2026-08-08）。原本五顆各寫死一組寬高（÷4 的
   32×22／30×30／28×30／28×32／32×28），現在改成「塞進一個正方框、比例自己保持」：
   max-width ＋ max-height 同值，img 是替換元素，會自己等比縮到兩個上限內——
   寬圖吃滿寬、高圖吃滿高，不必為了改一次大小去查五張稿的長寬比。

   36px 是白框給得起的上限：白框＝.nav-btn 本體，觸控高度 44px、上下 padding 各 4px，
   剩下正好 36px。再大就要吃掉 padding，圖示會貼死框線。
   窄版每格約 44.7px 寬（見下面一列版的算式），36px 圖示之間還留約 8.7px，
   加上每張稿自己約 6% 的透明邊，實際看到的間距更寬，不會兩顆黏在一起。
   ⚠️ 這五張是 HD 稿（源檔是顯示尺寸的 3~4 倍），縮放交給 image-rendering: auto，
   所以尺寸不必是整數倍；右邊工具列那兩顆是 pixelated，規矩不同，見 .autospeak-btn。 */
.nav-btn__pixel {
  display: none;
  /* HD 稿縮到 slot：高密度原圖負責 Retina 細節，CSS 不放大版面。 */
  image-rendering: auto;
  width: auto;
  height: auto;
  max-width: var(--nav-icon, 36px);
  max-height: var(--nav-icon, 36px);
}
/* 30 ＝ 120px 畫布 ÷4，跟功能圖示同一個取樣密度，也小於它們的 36px 框
   （V 2026-08-08，理由見 .autospeak-btn）。清單裡那幾顆維持 22px。 */
.theme-pick__now .theme-pick__png { width: 30px; height: 30px; }
.theme-pick__item .theme-pick__png { width: 22px; height: 22px; }
.nav-btn > svg { display: none; }
.nav-btn__pixel { display: block; }

/* 像素圖示是實色的，不吃 currentColor，所以「未選中」不能靠變色表達，
   改用透明度。選中的那顆維持全實。 */
.nav-btn:not([aria-current]) .nav-btn__pixel { opacity: .78; }
.nav-btn__label {
  font-size: 13px;
  line-height: 1;
  letter-spacing: .03em;
  white-space: nowrap;
}
.nav-btn:hover { color: var(--ink-soft); background: var(--surface-2); }
.nav-btn[aria-current="page"] {
  color: var(--ink);
  background: var(--surface);
  box-shadow: inset 0 0 0 1px var(--line-soft);
}
/* 目前這一頁的指示條。直欄時代貼在右緣（豎線指向內容區），
   橫排要改成貼在**底緣**（橫線指向下方的內容區）——方向的語意要跟著版型走。 */
.nav-btn[aria-current="page"]::after {
  content: "";
  position: absolute;
  left: 50%;
  bottom: -3px;          /* 貼在鍵的下緣內側；再往下會被導覽列的邊框切掉 */
  transform: translateX(-50%);
  width: 20px;
  height: 2px;
  border-radius: 2px;
  background: var(--ink-soft);
}

/* ≤520px 的基準版型：兩列（第一列識別／工具，第二列五顆功能平均分配）。
   ⚠️ 這一段是**給真的擠不下的機型用的保底**（<360px：iPhone SE 一代、分割視窗）。
   一般手機請看下一段的一列版；那才是 V 要的版型。
   這不是另養一套導覽；同一組 DOM 只換 Grid 位置，入口與語意完全相同。 */
@media (max-width: 520px) {
  .topnav {
    grid-template-columns: repeat(5, minmax(44px, 1fr));
    grid-template-rows: auto auto;
    gap: 2px 0;
    padding: 5px 8px;
  }
  .topnav__identity {
    grid-column: 1 / 3;
    grid-row: 1;
    justify-self: start;
  }
  .topnav__mark { font-size: 15px; padding: 2px 6px; }
  .topnav__release { font-size: 9px; padding: 2px 4px; }
  .topnav__main {
    grid-column: 1 / 6;
    grid-row: 2;
    justify-self: stretch;
    display: grid;
    grid-template-columns: repeat(5, minmax(44px, 1fr));
    gap: 0;
  }
  .topnav__tail {
    grid-column: 4 / 6;
    grid-row: 1;
    justify-self: end;
    gap: 4px;
  }
}

/* 一般手機（≥360px）也是**一列**：識別／五顆功能／工具，跟寬版同一個順序
   （V 2026-08-08：「窄版的上方太厚，2 排太浪費空間了，必須改成一排」）。
   高度 118px → 71px。

   從前分兩列，是因為五顆鍵各撐 44px 寬時擠不進 375px。這裡改成中央欄吃掉剩餘
   寬度再五等分，min-width 交給 grid：375px 上每顆約 43px 寬（觸控高度仍是 44px），
   390px 以上更寬鬆。

   下限訂在 360px 不是隨手抓的：一顆鍵的可用寬
   ＝ (視窗 − 16 padding − 8 gap − 識別 43 − 工具 92) ÷ 5，
   要裝得下 36px 的圖示（見 .nav-btn__pixel）得有 340px。360 是留一點餘裕的整數，
   再窄就交給上面那段兩列版。
   ⚠️ 工具那兩顆已經是 120px 稿 ÷4 的最小合理尺寸，不要再為了擠空間縮它們。 */
@media (min-width: 360px) and (max-width: 520px) {
  .topnav {
    /* 回到寬版那組 `1fr auto 1fr`：左右兩欄等寬，中央群的幾何中心因此
       **正好是視窗中心**（V 2026-08-08：「主功能置中」）。從前窄版要另開
       五等分的 grid，是因為工具那兩顆橫排佔了 124px，剩下的寬度不夠；
       它們改成上下排（30px 一欄）之後，這套裝得下了。
       ⚠️ 上面那段 ≤520px 的兩列版仍然同時命中，所以欄／列／各區的 grid-column
       都要在這裡逐條蓋掉——只寫 grid-template-rows 是不夠的（實測會留著
       五等分欄，識別欄被擠到第一顆鍵底下）。 */
    grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
    grid-template-rows: auto;
    gap: 4px;
    padding: 4px 8px;
  }
  .topnav__identity { grid-column: 1; grid-row: 1; justify-self: start; }
  .topnav__main {
    grid-column: 2;
    grid-row: 1;
    justify-self: center;
    display: flex;
    gap: 4px;
  }
  /* 沒有標籤的窄版不需要 8px 的左右內距：五顆鍵仍各 44px（觸控下限由 min-width
     顧著），中央群因此收在 236px，左右兩欄才各分得到 57px 去放 43px 的識別欄。
     不收這 8px，中央群會脹到 276px、把「Lexis／DEV」擠到第一顆鍵底下（實測）。 */
  /* 右欄那顆「設定」也要收（2026-08-10）：它同樣是 .nav-btn，但不在 .topnav__main
     底下，只寫 .topnav__main 會讓它保持 8px 內距、比五顆兄弟寬 8px（實測 52 對 44）。 */
  .topnav__main .nav-btn,
  .topnav__tail .nav-btn { padding-inline: 2px; }
  /* 右欄 2026-08-10 起只剩「設定」一顆（V 拍板，概念稿 v02），
     所以不再需要上下排——原本疊成直欄是為了讓兩顆工具鍵只佔 30px 寬。

     置中不受影響：這組 grid 是 `1fr auto 1fr`，中央群的幾何中心恆等於視窗中心，
     與兩側欄寬無關，只要兩側**裝得下**自己的內容即可。實測 390px 時左右各分到
     (390 − 16 padding − 8 gap − 236 中央) ÷ 2 = 65px，右欄要 44px、左欄識別 43.4px，
     都有餘裕；下限 360px 時各 50px，仍然夠。 */
  .topnav__tail {
    grid-column: 3;
    grid-row: 1;
    justify-self: end;
  }
}

/* 360–389px 的單列版，左欄到中央五鍵只剩 54–69px。正式版號有 9 碼，沿用
   一般手機 9px 字級會在 360px 蓋到第一顆功能鍵約 13px；只縮 metadata，Logo、
   功能鍵與命中區都不動。390px 起已有足夠空間，回到一般手機字級。 */
@media (min-width: 360px) and (max-width: 389px) {
  .topnav__release {
    font-size: 7px;
    letter-spacing: 0;
    padding-inline: 2px;
  }
}

/* 文字只在 ≥834px 出現（V 2026-08-02 指定：iPad 直向的寬度）。
   402px 放不下五組「單字練習」這種四字標籤＋招牌＋主題＋帳號，
   所以窄螢幕純圖示——**無障礙不受影響**，.nav-btn 本來就自帶 aria-label。 */
.nav-btn__label { display: none; }
@media (min-width: 834px) {
  .nav-btn__label { display: block; }
  .nav-btn { padding: 5px 12px; }
  /* 這個斷點空間本來就寬鬆（標籤都放得下），整組圖示跟著放大一階
     （V 2026-08-08：「你判斷空間變夠時，可放大一些」）。
     兩個數字要**一起**改：工具那兩顆不得大過功能圖示，所以是 44 對 40，
     不是只把工具放大。40 ＝ 120px 畫布 ÷3，仍是整除、不破像素格。
     代價是導覽列高 76 → 84px（多的 8px 全在功能鍵身上，標籤位置不變）。
     ⚠️ 工具那兩顆的 40px 不能寫在這裡：.autospeak-btn__png 的基準規則在檔案
     更後面（≈2900 行），同特異度、後來者勝，寫這裡會被打回 30px（實測）。
     所以放在那一節旁邊，見「工具鍵：寬版放大一階」。 */
  .topnav { --nav-icon: 44px; }
}

.main {
  flex: 1 1 auto;
  min-width: 0;
  min-height: 0;      /* 沒有這行，練習畫面的內部捲動會被撐破 */
  display: flex;
  flex-direction: column;
}

.wrap {
  width: 100%;
  max-width: 720px;
  margin: 0 auto;
  padding: 0 24px;
}

.screen { flex: 1 1 auto; display: flex; flex-direction: column; }

.screen-head {
  padding-top: 40px;
  padding-bottom: 8px;
}
.screen-head h1 {
  font-size: 22px;
  font-weight: 600;
  letter-spacing: .04em;
  margin: 0 0 4px;
}
.screen-head p {
  margin: 0;
  color: var(--ink-faint);
  font-size: 15px;
}

/* ── 全域訊息列 ─────────────────────────────────────────────── */
/* 置中改用 margin-inline: auto，不再用 translateX(-50%)：
   transform 要整個讓給進出場動畫，兩邊搶同一個屬性會把 toast 甩到畫面左邊。 */
.toast {
  position: fixed;
  left: 0;
  right: 0;
  /* 讓開頁尾（V 2026-08-01 回報：提示疊在「往後／看答案」上，375px 更是整個
     埋進 240px 高的頁尾裡）。--foot-h 是 app.js 量出來的「現在看得到的那個
     頁尾」有多高，沒有頁尾的畫面是 0，加上去就退回原本的 28px。 */
  bottom: calc(var(--foot-h, 0px) + 28px);
  /* 預設仍貼頁尾上方：多數提示是送出／存檔這類頁尾動作的回應。 */
  margin-inline: auto;
  width: fit-content;
  max-width: min(560px, calc(100vw - 96px));
  background: var(--surface);
  color: var(--ink);
  /* 四邊同寬。左邊原本刻意加粗成 3px 當裝飾線，但這顆是置中浮出的圓角泡泡、
     不是靠左的橫幅——粗細不對稱在圓角上讀起來就是「邊框畫歪了」（V 2026-08-09）。 */
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 10px 16px;
  box-shadow: var(--shadow);
  font-size: 13px;
  z-index: 50;

  /* 這一段是收起狀態，用的是退場時鐘（250ms）；
     加上 .is-open 才切成進場時鐘（350ms）。一個 class 就拿到開關不對稱。 */
  opacity: 0;
  transform: translateY(var(--toast-distance)) scale(var(--toast-scale));
  filter: blur(var(--toast-blur));
  will-change: transform, opacity, filter;
  transition:
    opacity var(--toast-close) var(--toast-ease),
    transform var(--toast-close) var(--toast-ease),
    filter var(--toast-close) var(--toast-ease);
}
/* 頂列開關（自動唸、主題）的提示貼在導覽列下方，就在觸發鍵旁邊——
   原本一律丟到螢幕最底，按右上角卻要往最下面找，眼睛得追一整個畫面
   （V 2026-08-09）。--topnav-h 是 app.js 量出來的導覽列實高。 */
.toast.toast--top {
  top: calc(var(--topnav-h, 56px) + 12px);
  bottom: auto;
}

.toast.is-error {
  border-color: var(--red-line);
}
.toast.is-open {
  opacity: 1;
  transform: translateY(0) scale(1);
  filter: blur(0);
  transition:
    opacity var(--toast-open) var(--toast-ease),
    transform var(--toast-open) var(--toast-ease),
    filter var(--toast-open) var(--toast-ease);
}
@media (prefers-reduced-motion: reduce) {
  .toast { transition: none !important; }
}

/* iOS 換題時接住軟鍵盤的常駐輸入錨點。必須留在渲染樹中，但不可見、不可點、
   不可參與版面；新題畫好後焦點會立刻交回第一個正式輸入框。 */
.keyboard-hold-anchor {
  position: fixed !important;
  left: 0 !important;
  bottom: 0 !important;
  width: 1px !important;
  height: 1px !important;
  min-width: 0 !important;
  min-height: 0 !important;
  padding: 0 !important;
  border: 0 !important;
  opacity: 0.001 !important;
  pointer-events: none !important;
  z-index: -1 !important;
}

/* ══════════════════════════════════════════════════════════════
   練習畫面
   ══════════════════════════════════════════════════════════════ */
.practice {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
}

/* 頂部：五點熟練度 */
/* 熟練度那一列。高度與上下距離走共用節拍（見 :root 的 --card-meta-*），
   跟文法的 .gr-tags 對齊；原本是 padding-top 28 ＋ min-height 40，比文法那條
   低 16px，切過去就會看到整塊往上跳。 */
.practice-top {
  padding-top: var(--card-meta-top);
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 14px;
  min-height: calc(var(--card-meta-top) + var(--card-meta-h));
}
/* 沒有內容的欄位不能留在 flex 流裡：它本身寬 0，但前面那道 14px 的 gap 照算，
   整列的可見內容因此被推離視窗中心 7px（V 2026-08-08 回報「水平中心不一致」；
   文法那列是按需渲染，沒有這個問題）。.chips:empty 早就這樣處理，這裡補上另一個。 */
.practice-top__meta:empty { display: none; }

.mastery { display: flex; align-items: center; gap: 7px; }
.mastery__dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  /* 未達點吃 --mastery-off（各主題各自定義）。原本寫死無彩灰，
     是為了跟舊版藍→綠的熟練度色階分開；改單色之後灰階要隨主題調，
     不然亮版的中灰在羊皮上浮不出來。 */
  background: var(--mastery-off);
  transition: background-color .25s ease;
}
.mastery__dot.is-on { background: var(--ink-soft); }

.practice-top__meta {
  font-size: 13px;
  color: var(--ink-faint);
  letter-spacing: .04em;
}

/* 卡片本體 — A 版「讀物」排版：句子靠左當主角，字義降級為旁註。
   原本置中＋上下大量留白，空白沒有作用；改成有目的的閱讀版心。 */
.card-area {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  /* 內容短 → 置中；內容長到滿出來 → 自動退回貼齊上緣。
     沒有 safe 的話，置中會把溢出的部分同時推到上下兩側，而上面那半段
     在捲動原點之外（scrollTop 已經是 0 卻還在畫面上方），永遠捲不到——
     揭曉後內容一長，句子本身就被切掉了（V 2026-07-27 回報）。
     兩行都留著：不支援 safe 的瀏覽器至少還是置中。 */
  justify-content: center;
  justify-content: safe center;
  align-items: stretch;
  /* 只寫上下：左右一律沿用 .wrap 的版心留白。
     原本是 `padding: 8px 0 24px` 的簡寫，會把 .wrap 的 `0 24px` 一起蓋掉，
     題目與揭曉區就整個貼到螢幕邊——但 ≤640px 的 `.wrap { padding: 0 16px }`
     寫在後面又蓋了回來，於是只有平板／桌機看得到（V 2026-08-06 iPad 回報）。 */
  padding-block: 8px 24px;
}

.card {
  width: 100%;
  text-align: left;
}

/* B 版警示列：同一個錯誤重複出現時才顯示。
   一般的 SRS 只知道你又錯了——commemorate 錯 14 次都打同一個字，五個月沒人告訴他。 */
.repeat-warn {
  display: flex;
  align-items: baseline;
  gap: 8px;
  margin: 0 0 22px;
  padding: 9px 14px;
  border-left: 2px solid var(--amber-line);
  border-radius: 0 var(--radius) var(--radius) 0;
  background: var(--amber-soft);
  color: var(--amber);
  font-size: 13px;
  line-height: 1.6;
}
.repeat-warn b { font-weight: 600; }
.repeat-warn__n {
  flex: 0 0 auto;
  font-variant-numeric: tabular-nums;
  font-size: 13px;
  letter-spacing: .06em;
  opacity: .8;
}

.card__meta {
  display: flex;
  align-items: baseline;
  gap: 12px;
  margin-top: 18px;
  flex-wrap: wrap;
}
.card__gloss {
  /* V 2026-08-06 iPhone 回報「每次都要找一下」：15px 的中文擺在 24px 的英文
     句子底下已經很小，字色又淡一階，一眼掃過去會直接漏掉。答題時它是唯一的
     線索，不該退到背景裡——放大並拉回主文字色。

     18px 那一版還是被回報「要找一下」（V 2026-08-06 二次回報），再跳一階到
     --fs-xl。順帶把寫死的 18px 換成 token：18 不在字級階梯的七階上，
     檔頭那條「階數多就守不住」的規矩自己先破了不行。
     沒有一路開到 --fs-hero，是因為那是例句的級別——提示跟例句同大會互搶，
     它是線索不是主角。 */
  font-size: var(--fs-xl);
  font-style: italic;
  color: var(--ink);
  letter-spacing: .02em;
}
/* 詞性掛在中文提示前面：`n. 樹皮`。用正體、比中文淡一階——
   它是標記不是內容，不該跟提示本身搶。 */
.card__pos {
  /* 詞性跟著字義一起放大（V 2026-08-06、2026-08-17）：字義是 24px，
     詞性用 17px 保留主從關係，同時避免 v. 小到像雜訊。 */
  font-style: normal;
  font-size: var(--fs-base);
  color: var(--ink-faint);
  margin-right: 6px;
  letter-spacing: 0;
}
/* 其它義項：揭曉後才出現，比提示再小一階、再淡一階。
   它是補充，讀完提示才輪到它。
   2026-08-02 改成**接在字義右邊**（V）：原本 flex-basis:100% 讓它自己佔一行，
   而它通常只有「（也可作 v. 資助）」這麼短——為了幾個字多一整行不划算。
   放不下時 flex-wrap 自然會把它推到下一行，窄螢幕不會擠壞。 */
.card__others {
  margin-left: 8px;
  font-size: 13px;
  font-style: italic;
  color: var(--ink-faint);
  letter-spacing: .02em;
}
.card__kind {
  display: inline-block;
  font-size: 13px;
  letter-spacing: .08em;
  color: var(--ink-faint);
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 1px 9px;
}

/* ── 標籤 chips ────────────────────────────────────────────
   兩段曝光：答題前只掛狀態標籤（.chips 在 practice-top），
   屬性標籤等揭曉後才進 .chips--attr。分開放是因為前者會洩題。 */
.chips {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  align-items: center;
}
.chips:empty { display: none; }
.chips--attr { margin-top: 4px; justify-content: center; }

.chip {
  font-size: 13px;
  letter-spacing: .05em;
  color: var(--ink-faint);
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 1px 9px;
  background: var(--surface);
  white-space: nowrap;
}
/* 熟練度旁的 CEFR 是小標籤，不做成膠囊或近圓形。
   只壓這一列；揭曉內容裡的其它 chip 維持原本觸控尺寸。 */
#card-labels .chip {
  line-height: 17px;
  padding: 0 8px;
  border-radius: 6px;
}
/* 熟悉度標籤：中性但比屬性標籤重一階，因為它才是答題時該注意的 */
.chip--state { color: var(--ink-soft); border-color: var(--ink-faint); }
/* 風險：琥珀是本工具的招牌色，用在「這張要留意」剛好 */
.chip--warn {
  color: var(--amber);
  border-color: var(--amber-line);
  background: var(--amber-soft);
}
/* 來源：跟屬性區分開，讓「這個字打哪來的」自成一類 */
.chip--src {
  color: var(--focus);
  border-color: var(--focus);
  background: color-mix(in srgb, var(--focus) 6%, transparent);
}
.chip--act {
  cursor: pointer;
  color: var(--ink-soft);
}
.chip--act:hover { color: var(--ink); border-color: var(--ink-faint); }

/* ── 回看模式 ──────────────────────────────────────────────
   虛線框＋紫色狀態列，讓「我不在作答」一眼可辨。 */
.lookbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  border: 1px dashed var(--focus);
  background: color-mix(in srgb, var(--focus) 5%, transparent);
  border-radius: var(--radius);
  padding: 7px 14px;
  margin-bottom: 22px;
  font-size: 13px;
  color: var(--focus);
}
.lookbar__keys {
  color: var(--ink-faint);
  font-family: var(--font-mono);
  font-size: 13px;
}
.look-typed { font-size: 13px; color: var(--ink-soft); margin-top: 10px; }
.look-typed .d-bad {
  color: var(--red);
  text-decoration: line-through;
  text-decoration-thickness: 1px;
}
.look-answer {
  font-family: var(--font-serif);
  border-bottom: 2px solid var(--green-line);
  padding: 0 .12em 2px;
}

.sentence {
  font-family: var(--font-serif);
  font-size: 32px;
  line-height: 1.95;
  color: var(--ink);
  margin: 0;
  max-width: 30em;
  overflow-wrap: break-word;
  text-wrap: pretty;
}

/* 挖空的 inline input：寬度只由使用者打的字撐開，不洩漏答案長度 */
.blank {
  font-family: var(--font-serif);
  font-size: inherit;
  line-height: 1.2;
  color: var(--ink);
  background: transparent;
  border: 0;
  border-bottom: 2px solid var(--line);
  border-radius: 0;
  padding: 0 .12em 2px;
  margin: 0 .04em;
  width: 7ch;
  text-align: center;
  outline: none;
  transition: border-color .18s ease, color .18s ease, background-color .18s ease;
}
.blank::placeholder { color: transparent; }
.blank:focus { border-bottom-color: var(--ink-soft); }
.blank:focus-visible {
  outline: none;
  box-shadow: none;
  border-bottom-width: 3px;
}

.blank.is-near {
  color: var(--amber);
  border-bottom-color: var(--amber-line);
  background: var(--amber-soft);
}
.blank.is-good {
  color: var(--green-answer);
  border-bottom-color: var(--green-answer);
}
.blank.is-bad {
  color: var(--red);
  border-bottom-color: var(--red-line);
  text-decoration: line-through;
  text-decoration-thickness: 1px;
}

/* 揭曉後填回句子的正解 */
.answer-inline {
  font-family: var(--font-serif);
  color: var(--green-answer);
  border-bottom: 2px solid var(--green-answer);
  padding: 0 .12em 2px;
}
.answer-inline .d-bad { color: var(--red); font-weight: 600; }

/* 回饋區 */
.feedback {
  min-height: 96px;
  margin-top: 30px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
}

.hint {
  font-size: 15px;
  line-height: 1.7;
  border-radius: var(--radius);
  max-width: 30em;
}
/* 判定短語（答對了／不對再試一次）不加內距：它是中文翻譯的標頭，
   要貼著下面那句、左緣也要跟句子切齊，不是浮在空白裡的一塊。 */
.hint.is-good, .hint.is-bad { padding: 0; }
.hint.is-near {
  color: var(--amber);
  background: var(--amber-soft);
  border: 1px solid var(--amber-line);
  padding: 8px 14px;   /* 只有這一狀態有底色，才需要內距撐開 */
}
.hint.is-good { color: var(--green); }
.hint.is-bad { color: var(--red); }

.reveal {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  max-width: 34em;
}
/* 整句中文：答對時它就是這一格唯一的內容，不是註解（V 2026-07-27 回報：
   離「答對了」太遠、字又淡，視覺上找不到）。拉到 18px、吃滿 --ink，
   跟上面的判定短語（14px 綠字）拉開層級，但仍在正解 27px 之下。 */
.reveal__zh {
  font-size: 17px;
  color: var(--ink);
  line-height: 1.7;
  padding: 10px 0 2px;
}
.translation {
  width: 100%;
  border-top: 1px solid var(--line-soft);
  border-bottom: 1px solid var(--line-soft);
  padding: 8px 0 10px;
}
.translation__toggle {
  width: 100%;
  min-height: 30px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  color: var(--ink-soft);
  font-size: 13px;
  text-align: left;
}
.translation__toggle:hover { color: var(--ink); }
.translation__state {
  color: var(--ink-faint);
  font-size: 12px;
}
/* 暗版 A 的三個語意重點直接取樣參考圖：字義綠、翻譯標題紫、狀態藍。 */
:root[data-theme="dark"] .card__gloss { color: #8eb58d; }
:root[data-theme="dark"] .translation__toggle { color: #a29abc; }
:root[data-theme="dark"] .translation__toggle:hover { color: #b4acc8; }
:root[data-theme="dark"] .translation__state { color: #6780bf; }
/* 暗版揭曉區可讀性 A（V 2026-08-17）：顏色的對比已足夠，問題是輔助字
   長期停在 12–13px／400。短標籤升到 15px／500，方便掃讀；說明與註記
   同樣升到 15px、但維持 400，避免整片內容變成粗體。主句、目標字與亮版不動。 */
:root[data-theme="dark"] :is(
  .translation__toggle,
  .word-summary__pos,
  .morph__pos,
  .morph__cefr,
  .morph__form-k,
  .morph__usage-k,
  .extra-ex__k,
  .alts__k
) {
  font-size: var(--fs-sm);
  font-weight: 500;
}
:root[data-theme="dark"] .translation__state {
  font-size: var(--fs-xs);
  font-weight: 500;
}
:root[data-theme="dark"] .word-summary__gloss { font-size: var(--fs-base); }
:root[data-theme="dark"] :is(
  .card__others,
  .reveal__typed,
  .reveal__also,
  .morph__chip,
  .morph__note,
  .extra-ex__src,
  .extra-ex__zh,
  .more-ex__src,
  .more-ex__zh,
  .more-ex__none,
  .why,
  .alts__note
) {
  font-size: var(--fs-sm);
  font-weight: 400;
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme]) .card__gloss { color: #8eb58d; }
  :root:not([data-theme]) .translation__toggle { color: #a29abc; }
  :root:not([data-theme]) .translation__toggle:hover { color: #b4acc8; }
  :root:not([data-theme]) .translation__state { color: #6780bf; }
  :root:not([data-theme]) :is(
    .translation__toggle,
    .word-summary__pos,
    .morph__pos,
    .morph__cefr,
    .morph__form-k,
    .morph__usage-k,
    .extra-ex__k,
    .alts__k
  ) {
    font-size: var(--fs-sm);
    font-weight: 500;
  }
  :root:not([data-theme]) .translation__state {
    font-size: var(--fs-xs);
    font-weight: 500;
  }
  :root:not([data-theme]) .word-summary__gloss { font-size: var(--fs-base); }
  :root:not([data-theme]) :is(
    .card__others,
    .reveal__typed,
    .reveal__also,
    .morph__chip,
    .morph__note,
    .extra-ex__src,
    .extra-ex__zh,
    .more-ex__src,
    .more-ex__zh,
    .more-ex__none,
    .why,
    .alts__note
  ) {
    font-size: var(--fs-sm);
    font-weight: 400;
  }
}
.word-summary {
  width: 100%;
  display: flex;
  align-items: baseline;
  gap: 8px;
  flex-wrap: wrap;
  padding-top: 4px;
  line-height: 1.65;
}
.word-summary__lemma {
  font-family: var(--font-serif);
  font-size: 22px;
  font-weight: 600;
  color: var(--green-answer);
}
.word-summary__pos {
  font-size: 13px;
  font-style: italic;
  color: var(--ink-soft);
}
.word-summary__gloss { font-size: 15px; color: var(--ink); }
.word-summary__actions {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  margin-left: 2px;
  align-self: center;
}
.word-summary__actions .replay-btn { white-space: nowrap; }
.reveal__typed {
  font-family: var(--font-mono);
  font-size: 13px;
  color: var(--ink-faint);
  letter-spacing: .01em;
}
.reveal__typed .d-bad {
  color: var(--red);
  text-decoration: line-through;
  text-decoration-thickness: 1px;
}
/* 「你打的 X 也對，這張卡教的是 Y」（V 2026-08-01）。
   它不是提示也不是錯誤，是一句補充事實——所以走 ink-faint 這一層，
   跟下面「這樣說也可以」的註解同級，不跟 20px 的中譯搶。
   兩個字用 serif 撐出來，掃過去就知道是在比哪兩個字。 */
.reveal__also {
  font-size: 13px;
  line-height: 1.7;
  color: var(--ink-faint);
  max-width: 34em;
}
.reveal__also b {
  font-family: var(--font-serif);
  font-size: 15px;
  font-weight: 400;
  color: var(--ink-soft);
}
.reveal__answer {
  font-family: var(--font-serif);
  font-size: var(--answer-size);
  color: var(--ink);
}
.reveal__answer .d-bad { color: var(--red); font-weight: 600; }

/* 答完後的兩個去處：再聽一次 / 繼續。
   答對不再自動翻頁（V 2026-07-26），所以這一排要一眼看得到。 */
.answer-actions { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }

/* 三顆語音鍵自成一組，才搬得動（辨析卡會把它整組移到提問列上方）。
   間距與換行行為照抄 .answer-actions，搬走前後在一般卡上看起來一模一樣。 */
.answer-actions__audio { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; position: relative; }

/* 搬到卡片裡（句子底下）時要有自己的上下距離；留在頁尾時由 .answer-actions
   的 gap 管，所以只給 #card 底下的那份。 */
#card > .answer-actions__audio { margin: 14px 0 2px; }

/* 「還不能聽」的就地提示。全域 toast 釘在頁尾，而這三顆在辨析卡已經搬到
   句子底下——中間差了整個畫面高度，V 2026-08-01 回報「下方提示不明顯」。
   回饋要出現在動作發生的地方，所以這一則不走 toast，貼著按鈕自己講。

   視覺語言沿用 toast 的底色／圓角／陰影，但紅左邊框只表達「語音不可用」；
   一般設定成功通知已改用中性框，不再跟這則警示共用紅色語意。

   ⚠️ **不再自動淡出**（V 2026-08-02，iPhone 實測）。它最常講的那件事是
   「去哪裡下載語音」＝三個要照著做的步驟，2.4 秒讀不完。改成按 ✕ 或換卡
   才關，所以右上角有一顆真的關閉鍵。

   ⚠️ **一般卡改成佔位（in-flow），只有辨析卡維持絕對定位**（同一批改動）。
   從前一律絕對定位，理由是「不要把下面的東西往下推」，而它 2.4 秒就消失，
   蓋住一下無所謂。現在它會一直待著——實測在 402px 上整塊蓋住「照著上面
   打一次就放你走」那格重打輸入框，而那正是使用者接下來非做不可的事。
   一般卡的這則**只在揭曉後才可能出現**（答題前按語音鍵得到的是別句話），
   所以佔位不會製造「揭曉時跳動」，往下推是安全的。
   辨析卡不能比照：那裡三顆在**答題前**就擺著反灰，佔位會把提問與選項
   往下推，正是 §5.20／`0391eae` 修掉的那種跳動。 */
.audio-hint {
  flex: 1 0 100%;         /* 父層是 flex 列，要自己整條佔一行 */
  margin-top: 8px;
  max-width: 100%;
  background: var(--surface);
  color: var(--ink);
  border: 1px solid var(--red-line);
  border-left-width: 3px;
  border-radius: var(--radius);
  padding: 9px 44px 10px 12px;   /* 右邊留給 ✕ */
  box-shadow: var(--shadow);
  font-size: 13px;
  line-height: 1.5;
  position: relative;     /* ✕ 靠它定位 */
  z-index: 3;

  opacity: 0;
  transform: translateY(calc(var(--toast-distance) * -0.5)) scale(var(--toast-scale));
  filter: blur(var(--toast-blur));
  transition:
    opacity var(--toast-close) var(--toast-ease),
    transform var(--toast-close) var(--toast-ease),
    filter var(--toast-close) var(--toast-ease);
}
.audio-hint.is-open {
  opacity: 1;
  transform: translateY(0) scale(1);
  filter: blur(0);
  transition:
    opacity var(--toast-open) var(--toast-ease),
    transform var(--toast-open) var(--toast-ease),
    filter var(--toast-open) var(--toast-ease);
}
@media (prefers-reduced-motion: reduce) {
  .audio-hint { transition: none !important; }
}

/* 辨析卡：維持絕對定位、不佔位（理由見上面那段的第二個 ⚠️）。
   pointer-events 也維持 none——浮在選項上面時不可以吃掉點擊，
   只有 ✕ 例外（.audio-hint__x 自己開回 auto）。 */
#contrast-mount .audio-hint {
  position: absolute;
  top: 100%;
  left: 0;
  margin-top: 6px;
  pointer-events: none;
}

/* 內容分層：先講「怎麼回事」，再講「為什麼寧可沒聲音」，最後才是照著做的步驟。
   步驟一定要排成清單——擠成一段的話，使用者離開 app 去設定時記不住第幾步。 */
.audio-hint__body { display: block; }
.audio-hint__title { display: block; font-weight: 400; color: var(--ink); }
.audio-hint__why,
.audio-hint__note { display: block; color: var(--ink-soft); font-size: 12.5px; }
.audio-hint__why { margin-top: 2px; }
.audio-hint__note { margin-top: 6px; }
.audio-hint__steps {
  margin: 6px 0 0;
  padding-left: 18px;
  color: var(--ink);
  font-size: 12.5px;
}
.audio-hint__steps li { margin: 1px 0; }

/* 唯一吃得到指標事件的部分。44px 是觸控下限，不因為它是「一顆小 ✕」就縮水。 */
.audio-hint__x {
  position: absolute;
  top: 4px;
  right: 4px;
  width: 32px;
  height: 32px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: transparent;
  color: var(--ink-faint);
  font-size: 13px;
  line-height: 1;
  cursor: pointer;
  pointer-events: auto;
}
.audio-hint__x:hover { color: var(--ink); border-color: var(--focus); }
@media (pointer: coarse) {
  .audio-hint__x { width: 44px; height: 44px; }
  .audio-hint { padding-right: 56px; }
}

/* ── 更多例句（V 2026-08-02）──────────────────────────────────── */
.more-ex__btn {
  margin-top: 14px;
  min-height: 44px;
  padding: 0 16px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--surface);
  color: var(--ink-soft);
  font-size: 13px;
  font-family: inherit;
  transition: color .15s ease, border-color .15s ease;
}
.more-ex__btn:hover { color: var(--ink); border-color: var(--focus); }
.more-ex { margin-top: 12px; display: flex; flex-direction: column; gap: 14px; }
.more-ex__item { border-left: 2px solid var(--line-soft); padding-left: 12px; }
.more-ex__src {
  display: inline-block;
  font-size: 12px;
  color: var(--ink-faint);
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 1px 10px;
  margin-bottom: 6px;
}
.more-ex__en { font-size: 17px; line-height: 1.7; margin: 0; color: var(--ink); }
.more-ex__zh { font-size: 13px; color: var(--ink-soft); margin: 4px 0 0; }
.more-ex__none { font-size: 13px; color: var(--ink-faint); margin: 0; }

/* ── 標籤說明小卡（V 2026-08-02）────────────────────────────────────
   ⚠️ 可點的標籤跟一般標籤長得**完全一樣**，不給任何暗示（V 指定）。
   初版靠虛線底提示「這顆可以點」，結果膠囊變成上實下虛，看起來像沒畫完。
   點了有東西、沒點也不損失，所以不值得為它犧牲外觀。 */
.chip--tap {
  cursor: pointer;
  font: inherit;
  font-size: 12px;
  transition: color .15s ease, border-color .15s ease;
}
.chip--tap:hover { color: var(--ink); border-color: var(--focus); }
.chip--tap[aria-expanded="true"] { color: var(--ink); border-color: var(--focus); }
#card-labels .chip--tap {
  position: relative;
  padding: 0 8px;
  border-radius: 6px;
}
#card-labels .chip--tap::before {
  /* 看得見的 B2 保持緊湊；透明延伸區讓滑鼠與觸控仍有約 44px 高可點範圍。 */
  content: "";
  position: absolute;
  inset: -13px -6px;
}

.chip-pop {
  position: fixed;
  z-index: 58;              /* 在浮動元件（55）之上、遮罩（59）之下 */
  max-width: min(330px, calc(100vw - 16px));
  padding: 10px 13px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  box-shadow: var(--shadow);
  font-size: 12.5px;
  line-height: 1.75;
  color: var(--ink);
}
.chip-pop__t { display: block; font-weight: 400; margin-bottom: 3px; }
.chip-pop__b { display: block; color: var(--ink-soft); font-size: 12px; }

.continue-btn {
  font-size: 13px;
  color: var(--ink-soft);
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 5px 18px;
  background: var(--surface);
  transition: border-color .15s ease, color .15s ease;
}
.continue-btn:hover { color: var(--ink); border-color: var(--ink-faint); }

.replay-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 12.5px;
  color: var(--ink-faint);
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 3px 11px;
  background: transparent;
  transition: border-color .15s ease, color .15s ease;
}
.replay-btn:hover { color: var(--ink-soft); border-color: var(--ink-faint); }
/* 「有圖可看」這顆鍵：題卡與查義小卡是同一件事，視覺就該一樣（V 2026-08-09）。
   原本題卡是一圈幾乎看不見的細框（--line ＋ opacity .82），小卡是無框的淡圖示，
   兩邊都太弱、而且長得不一樣，暗版尤其認不出那是可以點的。

   統一成**有底色的實心圓鈕**、圖示走 --focus（全站的重點色，亮版金褐／暗版淡紫）：
   不是狀態色所以不會跟綠（正常運作）、琥珀（要留意）打架，單純是「這裡可以點開圖」。
   展開後整顆反白填滿，跟收合狀態一眼分得開。 */
.image-toggle,
.wt-shot {
  position: relative;
  flex: 0 0 auto;
  align-self: center;
  width: 30px;
  height: 30px;
  display: inline-grid;
  place-items: center;
  border: 1px solid var(--focus);
  border-radius: 999px;
  background: var(--surface-2);
  color: var(--focus);
  opacity: 1;
  transition: background-color .15s ease, color .15s ease, border-color .15s ease;
}
/* 觸控外擴：圓鈕只有 30px，實際可點範圍放到 44px。 */
.image-toggle::before,
.wt-shot::before {
  content: "";
  position: absolute;
  inset: -7px;
}
.image-toggle img { width: 16px; height: 16px; display: block; }
.wt-shot svg { width: 17px; height: 17px; display: block; }
.image-toggle:hover,
.wt-shot:hover {
  background: var(--focus);
  color: var(--bg);
}
.image-toggle[aria-expanded="true"],
.wt-shot[aria-expanded="true"] {
  background: var(--focus);
  border-color: var(--focus);
  color: var(--bg);
}
/* 題卡那顆是 <img>，吃不到 currentColor，只能用 filter 翻色。
   兩個版本的填色明暗相反：亮版 --focus 是深金褐 → 圖示翻白；
   暗版 --focus 是淡紫 → 圖示要翻黑，翻白會糊成一片。 */
.image-toggle[aria-expanded="true"] img,
.image-toggle:hover img { filter: brightness(0) invert(1); }
:root[data-theme="dark"] .image-toggle[aria-expanded="true"] img,
:root[data-theme="dark"] .image-toggle:hover img { filter: brightness(0); }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme]) .image-toggle[aria-expanded="true"] img,
  :root:not([data-theme]) .image-toggle:hover img { filter: brightness(0); }
}

.replay-btn svg { width: 15px; height: 15px; flex: 0 0 auto; }

/* 熱鍵只印給有實體鍵盤的人看——跟上面 .foot-key 同一條規矩（V 2026-08-01）。
   起因是辨析卡把這三顆搬到提問列上方之後，375px 上「慢速再聽一次 ⌘⇧'」
   一顆就撐滿一行、三顆疊成三列，把選項往下推了 127px；而 iPad／iPhone 上
   那個 ⌘ 根本按不出來，等於用最貴的版面印一行沒人用得到的字。
   藏掉的只有提示，**熱鍵本身照樣有效**。 */
.replay-btn kbd { display: none; }
@media (hover: hover) and (pointer: fine) {
  .replay-btn kbd { display: inline-block; }
}
/* 正在唸：讓他知道現在是機器在講，不是他該按第二次 */
.replay-btn.is-playing { color: var(--green); border-color: var(--green); }
.replay-btn.is-playing svg { animation: replay-pulse 1.1s ease-in-out infinite; }
@keyframes replay-pulse { 0%, 100% { opacity: 1; } 50% { opacity: .35; } }


/* 頑固卡辨義說明 */
.leech {
  width: 100%;
  max-width: 34em;
  text-align: left;
  background: var(--surface);
  border: 1px solid var(--line);
  border-left: 3px solid var(--amber-line);
  border-radius: var(--radius);
  padding: 14px 18px;
  box-shadow: var(--shadow);
}
.leech__title {
  font-size: 13px;
  letter-spacing: .06em;
  color: var(--amber);
  margin-bottom: 6px;
}
.leech__body {
  font-size: 17px;
  line-height: 1.85;
  color: var(--ink);
}
.leech__body em {
  font-family: var(--font-serif);
  font-style: italic;
}
.leech__close {
  margin-top: 10px;
  font-size: 13px;
  color: var(--ink-faint);
}

/* 底部列 */
.practice-foot,
.gr-foot {
  position: relative;               /* 亮版的背景山景帶定錨在這 */
  border-top: 1px solid var(--line-soft);
  background: var(--bg);
  padding: 0;
  /* 頁尾只接按鈕／軌跡操作，不接垂直拖動手勢。 */
  touch-action: none;
}

/* ── 亮版：頁尾上方的山景帶（V 2026-08-06 拍板）──────────────────
   **固定顯示尺寸、水平置中、不重複。** 視窗變寬只是看到更多，
   中間的東西大小不變——像 16:9 換成 32:9 的螢幕（V 的原話）。

   走過的兩條彎路，留著免得有人again：
   1. 做成 alpha 遮罩、顏色交給 CSS：想讓暗版沿用同一張，但這張圖的層次
      就在顏色裡（天空／遠山／中景／針葉／草坡五層），壓成單色等於把美感
      抽掉。日與夜本來就是兩張畫。
   2. background-size: cover：每個視窗寬度看到的像素大小都不一樣，
      像素風最忌諱這個；而且窄視窗只會裁到中央一小塊。

   現在用 V 2026-08-07 指定的單一連續生成山谷帶。
   所有 RWD 寬度共用同一張 5040×712 素材，固定等比顯示為 2520×356；
   整張地形、山線與植被由同一幅原稿產生，不再拼接窄版與左右延伸素材。
   拉寬只會多露左右內容，拉窄只會從兩側裁切；素材尺寸與垂直座標完全不變。

   上緣的淡出用 CSS 漸層遮罩，不烤進圖裡。背景沒有 RWD 素材斷點、沒有切換
   動畫，拖拉視窗時地平線與地景比例都不會移動。

   定錨在 .practice-foot 上緣、absolute，不佔任何版面高度。

   **z-index 一定要是負的**：這是背景風景，不是覆蓋層。頁尾是 position:relative
   的後方兄弟，`z-index: 0` 會讓它整片畫在捲動區的字上面——揭曉後的單字、片語
   籤與註解剛好落在帶子裡，被山蓋到幾乎看不見（V 2026-08-07 回報）。
   負值讓它退到 .app 那層堆疊脈絡的內容之下；.card-area 沒有底色，
   所以字浮在山景上，山景不再壓字。 */
:root[data-theme="light"] .practice-foot::before,
:root[data-theme="light"] .gr-foot::before,
:root:not([data-theme]) .practice-foot::before,
:root:not([data-theme]) .gr-foot::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 100%;
  height: var(--band-h);
  pointer-events: none;
  z-index: -1;
  opacity: .9;
  background-image: url("/icons/band-valley-canonical-light.png");
  background-size: 2520px var(--band-h);
  background-position: bottom center;
  background-repeat: no-repeat;
  image-rendering: pixelated;
  -webkit-mask-image: linear-gradient(to bottom, transparent 0%, #000 55%);
          mask-image: linear-gradient(to bottom, transparent 0%, #000 55%);
}
/* 上下留白與列距（V 2026-08-08：頁尾再薄一些，含三顆按鈕下方）。
   12/10/12 是勇者進駐前的數字；他下移之後頁尾本來就緊了，這段空白只是白佔高度。
   ⚠️ 文法頁尾的 .gr-foot__in 與 640px 斷點那份覆寫要一起改，三處數字必須相同——
   頁尾高度一變，定錨在它上緣的山景帶也跟著移，斷點之間差 2px 就會看到地平線跳。 */
.practice-foot__row {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 6px;
  padding-top: 6px;
  padding-bottom: 6px;
  padding-bottom: max(6px, var(--safe-area-bottom));
}

/* 所有寬度都維持同一個垂直層級：計數 → 軌跡 → 操作列。
   寬版不再把進度與操作拆成左右兩欄（V 2026-08-05）。 */
.progress { width: 100%; min-width: 0; flex: 0 0 auto; }
/* 「本輪 x / 30 ／ 待複習 n」那排。只出現在單字與文法的頁尾（各一處）。
   2026-08-08：margin-bottom 6px → 0。勇者下移後這排離他還有一段距離（V 回報），
   而 .track-wrap 自己的 --track-head 已經替頭頂留了 2.5px，這 6px 是多的。 */
.progress__label {
  font-size: 13px;
  color: var(--ink-faint);
  display: flex;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 0;
}
/* ── 今日軌跡：一格一題 ────────────────────────────────────
   取代原本的單線進度條。一個元件同時給進度感與歷史，
   ⌘PageUp／PageDown 就是在這條上移動——快捷鍵要有看得見的對應物。 */
.track-wrap { position: relative; }
.track {
  display: flex;
  gap: 2px;
  align-items: flex-end;
  height: 14px;
  /* 上下各撐 10px 的抓握區再用負 margin 收回來：視覺還是 14px 高，
     但手指（和滑鼠）有 34px 可以抓。touch-action 必須關掉，
     否則瀏覽器會先把橫向拖曳判成捲動，拖到一半被搶走。 */
  box-sizing: content-box;
  padding: 10px 0;
  margin: -10px 0;
  touch-action: none;
  cursor: pointer;
}

/* 視覺上仍是一格一題；互動改由整條單一 range 承接。這樣手機有完整 34px
   高的連續落點、鍵盤原生支援方向鍵，讀螢幕也不必逐格聽上百顆按鈕。 */
.track-control {
  position: absolute;
  z-index: 3;
  inset: -10px 0 auto;
  width: 100%;
  height: 34px;
  margin: 0;
  opacity: 0;
  cursor: pointer;
}
.track-wrap:focus-within .track {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
  border-radius: 3px;
}

/* 停留／拖曳時浮出那一題的單字。原生 title tooltip 延遲 0.5–2 秒
   且時間不固定，拖曳時完全跟不上，所以自己畫一個。 */
.track-tip {
  position: absolute;
  bottom: calc(100% + 10px);
  transform: translateX(-50%);
  z-index: 20;
  white-space: nowrap;
  pointer-events: none;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 7px;
  padding: 4px 10px;
  font-size: 13px;
  color: var(--ink-soft);
  box-shadow: var(--shadow);
}
/* ── 練習頁的進度條：像素冒險化（V 2026-08-06 拍板）─────────────
   **這是加法不是替換。** 格子的判定配色（綠／琥珀／紅）、當前格、回看格、
   拖曳與浮字全部沿用——那些是學習資訊，不能為了風格消失。
   這裡只做三件事：格子改成固定寬、外觀改像素、加一層 sprite。

   掛在 .progress 底下＝**練習頁與文法練習頁的頁尾那兩條**（`.gr-progress`
   也帶 `.progress`）。V 2026-08-07：「文法練習的進度條要和單字練習一樣規則」，
   兩條走同一份 CSS 與同一份 JS，不再各長各的。
   主頁的今日軌跡（單字／文法）不在 .progress 底下：那兩條畫的是一整天、
   動輒上百格，固定格寬會爆長，維持原本的等分填滿。 */
.progress .track-wrap {
  /* 路牌與城門是軌道外側的裝飾，不是首末題格。桌面容得下完整軌跡時讓它們
     向左右自然溢出；只有格子真的超過可視寬度時，JS 才加 is-scrollable，
     讓整組裝飾隨軌跡一起橫向捲動。 */
  overflow: visible;
  /* 勇者的頭頂高過題格，要先替他騰出這段留白。這個值同時是 range 控制項的
     起點，所以走同一個變數——兩邊各寫一次遲早會漂掉（見下一條）。

     ⚠️ 不是「一個身高」。他的靴底已經沉到鄰格底線（見 --track-current-drop），
     所以只有**超出題格版面上緣的那一截**需要留白：
       身高 − 靴底到中心軸（格高 × peak ÷ 2）− 中心軸到版面上緣（格高 ÷ 2）
     ＝ 47 − 11 − 5.5 ＝ 30.5px。2026-08-08 從 47px 收掉這 16.5px，頁尾同幅變薄
     （V：角色下移後這段不用留那麼多）。
     騎士圖是**零透明邊的緊裁圖**（實測 169×208 全滿），算出來多少就是頭頂到
     哪，沒有多餘的空氣可以再吃；+2.5px 是不讓髮尖貼死上面那排「本輪 x / 30」，
     順便把 30.5 湊成整數 33px——留白帶零點五時題格的版面上緣落在半像素上，
     而 app.js 是用 offsetTop（整數）推靴底錨點，會憑空多半格的落差（實測 0.5px）。
     ⚠️ 動到 --track-knight-h／--track-seg-h／--avatar-peak 都會改變這個值，
     這也是它寫成 calc 不寫死的原因。 */
  --track-head: calc(
    var(--track-knight-h)
    - var(--track-seg-h) * (var(--avatar-peak) + 1) / 2
    + 2.5px);
  padding-top: var(--track-head);
  scrollbar-width: none;
  /* 捲軸自己出現時不要把版面推高。
     2026-08-08：2px → 10px。鄰格的 scaleY 是以中心軸放大，向下溢出量＝
     (峰值高 − 版面高)/2；峰值 22px、版面 11px 時是 5.5px，2px 收不住，
     題數多觸發 is-scrollable（overflow-y:auto）時下緣會被裁掉。 */
  padding-bottom: 10px;
}
/* ⚠️ 基底的 .track-control 是 `inset: -10px 0 auto`，也就是貼著 .track-wrap
   的上緣。加了 padding-top 之後那個上緣在格子**上方 42px**——控制項整條
   浮在勇者頭頂，滑鼠點在彩色格子上其實誰也沒碰到（elementFromPoint 打到
   .seg），拖曳回看等於失效。主頁那兩條沒有這段留白，所以只有頁尾這兩條要修。
   2026-08-07 做文法軌跡對齊時發現，單字那條也一樣中招，一起修。 */
.progress .track-control { top: var(--track-head); }
/* 落腳格沉到底線以下之後，上面那 10px 收不住它：溢出量＝下沉量 ＋ 壓扁後的半高
   − 版面半高＝12.5px，題數多到觸發 is-scrollable（overflow-y:auto）時下緣會被
   裁掉。亮暗版現在共用勇者與落腳格，所以兩版都加高；+1px 是給次像素捨入的餘裕。 */
.progress .track-wrap {
  padding-bottom: calc(
    var(--track-current-drop)
    + var(--track-seg-h) * (var(--track-current-squash) - 1) / 2
    + 1px);
}
/* 題數多到要橫捲時只開橫向。**縱向必須是 hidden**（V 2026-08-08 錄影回報
   「拖動觀看時會上下跑」）：這個盒子縱向本來就有約 10px 的可捲量——城堡的
   `bottom: -10px` 是補它自己的透明底邊，那截透明像素照樣算進 scrollable overflow。
   `overflow-y: auto` 等於把那 10px 交給滑鼠滾輪／觸控板，一滾整條軌跡就往上跑、
   格子與勇者被盒子裁掉一半。hidden 只裁掉那截透明尾巴，看得見的東西都在框內
   （頭頂 2.5px、腳下踏板 1px 餘裕，見 --track-head／padding-bottom）。 */
.progress .track-wrap.is-scrollable {
  overflow-x: auto;
  overflow-y: hidden;
}
.progress .track-wrap::-webkit-scrollbar { display: none; }

/* 固定格寬：多的往右延伸、可左右滑動（V 指定）。
   等分填滿在 30 題時還行，題數一多每格會細到看不出顏色。 */
/* 格子是**正方形**（照概念圖 P5D／P4E，不是原本那種扁長條）。
   aspect-ratio:1 讓高度跟著寬度走：flex 撐滿可用寬度 → 每格自動是方的。
   flex-basis 12px ＋ shrink 0 ＝ 擠不下時停在 12px 溢出去捲動，不會細到看不見。 */
/* 左右保留端點圖示空間——不留的話起點路牌與終點城門會被切一半，
   騎士在第 0 題時也會跟它疊在一起（2026-08-06 實測）。
   sprite 的位置是用格子的實際幾何算的，所以 padding 一加就自動跟上。 */
/* min-width 要扣掉左右 padding：.track 是 content-box（基底規則為了上下
   抓握區刻意設的），直接寫 100% 會讓內容寬變成 100%+44px，終點城門就被
   推出畫面外（2026-08-06 實測）。 */
/* 兩端各留一個「裝飾保留區」：路牌與城堡住在裡面，題卡永遠不進來。
   中間剩下的固定長度才由題卡等分，所以第一格左緣與最後一格右緣的位置
   跟題數無關（V 2026-08-06）。app.js 讀同兩個變數來擺 sprite，改這裡就兩邊一起動。 */
.progress .track {
  /* 保留區不只要裝得下裝飾，還要讓「勇者站在第一／最後一格」時不會疊上去：
     勇者 38px 寬、格子最窄只有 12px，站在首格時會往左伸出格子外 13px
     （V 2026-08-06 在 iPhone／iPad 實測到與路牌相疊 7~9px）。
     推導（S＝保留區、w＝格寬、勇者半寬 19、留白 4）：
       左：S/2 ≥ 19 + 15.75 + 4 − w/2 → w=12 時 S ≥ 66
       右：G/2 ≥ 19 + 30 + 4 − w/2 → w=12 時 G ≥ 94 */
  --track-sign-zone: 66px;
  --track-gate-zone: 94px;
  min-width: calc(100% - var(--track-sign-zone));
  padding-left: var(--track-sign-zone);
  padding-right: 0;
}
/* 右側保留區用 flex 佔位，不用 padding-right：軌道橫向捲動時引擎不會把
   padding-right 算進 scrollWidth，保留區整個消失，城堡就被擺到最後幾格上面
   （V 2026-08-06 在 iPhone 實測到）。左側的 padding 在捲動內容之前，沒這個問題。
   扣掉一個 gap，讓實際淨空剛好等於 --track-gate-zone。 */
.progress .track::after {
  content: "";
  flex: 0 0 calc(var(--track-gate-zone) - 4px);
  align-self: stretch;
}
/* Dock 的外擴間距不准推動兩端：騎士走到第一或最後一題時，
   首末格會被自己的 margin 推走，第一格左緣就跟著跳。 */
.progress .track .seg:first-child { margin-left: 0; }
.progress .track .seg:last-child { margin-right: 0; }
.progress .track { gap: 4px; height: auto; align-items: center; }
/* 固定長度均分（V 2026-08-06 指定 A 案）：格子在軌道的固定長度內等分——
   3 題就是「這段長度 ÷ 3」的三條寬格，路牌與城堡因此永遠停在同一個位置，
   不會像從前那樣被三個小方塊擠在左邊。擠到每格窄於 12px 才停止收縮，
   軌道改往右延伸捲動。

   原本是 `flex: 1 0 12px` ＋ `max-width: 22px`：basis 12px 不縮、上限 22px，
   所以少題時每格停在 22px，右邊一大片空著。方格不是硬規格（V 2026-08-06 確認），
   寬格才符合「固定長度均分」。

   高度必須寫死：aspect-ratio: 1 會讓高度跟著寬度走，三條寬格會把頁尾撐到幾百 px 高。

   2026-08-08（V 拍板 C 案）：22px → 11px。題數多時每格會收縮到 min-width 的
   12px，配 22px 高就是一條直立長條；砍成 11px 之後 12×11 幾乎是正方形，
   也就是 V 要的「接近正方型」。**山丘不靠基準高表達**——角色左右的隆起
   由 --avatar-peak／--avatar-reach 的斜坡負責，改這個值只影響遠處的正常格。 */
.progress .track .seg {
  flex: 1 1 0;
  min-width: 12px;
  aspect-ratio: auto;
  height: var(--track-seg-h);
  max-width: none;
  margin-inline: var(--dock-margin, 0px);
  box-sizing: border-box;
}
/* 窄版原本另外寫死 50/52 的左右留白，是為了讓路牌與城門在首尾端仍完整可見。
   保留區改成 --track-sign-zone／--track-gate-zone 之後，兩端本來就剛好裝得下
   sprite，再另開一組數字只會讓窄版的裝飾位置跟寬版對不起來（app.js 讀的是
   變數不是 padding），所以這條移除，全斷點共用同一組保留區。 */

/* 亮暗版的題格、路牌、勇者與城門共用同一套幾何與素材；
   幾何與視覺都不可再另開金邊方格或連線版本。 */
.progress .track {
  isolation: isolate;
  position: relative;
}
/* 這三條原本寫 `height: auto`，是為了讓高度交給 aspect-ratio 決定。
   改成固定長度等分之後 aspect-ratio 已經拿掉，`auto` 會讓這些空 span 的高度
   直接變成 0——當前格因此整個消失，勇者腳下看不到東西（V 2026-08-06 回報）。
   現在一律沿用 .seg 的固定高度，這裡只負責辨識用的外框／底色。 */
/* scale() 會連寬度一起放大；寬格時那是幾十 px，會壓到旁邊與兩端的裝飾，
   所以只放大縱向。
   ⚠️ 2026-08-08：原本寫 `transform: scaleY(1.15)`，特異度（0,4,0）壓過下面
   .progress .track .t-avatar（0,3,0），是**取代**整條 transform 不是疊加——
   滑到山丘上的鄰格會連 Dock 的放大一起被吃掉。舊版落差小看不出來；格高砍半、
   峰值拉到 2 倍之後，那格會從 22px 直接塌到 12.65px。改成傳 --hover-boost
   進去相乘，Dock 的斜坡就留得住。 */
.progress .track .seg--done:hover { --hover-boost: 1.15; }

/* transitions.dev avatar-group：只接管節點的距離式放大；自己的寬度、色彩、
   margin 仍由進度列規格決定。 */
.t-avatar {
  --scale-y: var(--scale-active, 1);
  transform-origin: center;
  transform:
    translateY(var(--shift, 0px))
    scaleX(var(--scale-active, 1))
    scaleY(var(--scale-y, var(--scale-active, 1)));
  transition: transform var(--avatar-dur) var(--avatar-ease-in);
  will-change: transform;
}
/* 進度格不吃 Dock 的橫向放大（V 2026-08-06）。
   scaleX 在小方塊時代只讓當前格多 4~5px，看不太出來；改成「固定長度等分」之後
   3 題每格 200px 寬，1.55 倍就多出 110px，當前格會直接壓到路牌與城堡上，
   等分也不成立。橫向的疏密仍由 --dock-margin 表達，只拿掉放大。 */
.progress .track .t-avatar {
  transform:
    translateY(var(--shift, 0px))
    scaleY(calc(var(--scale-y, var(--scale-active, 1)) * var(--hover-boost, 1)));
}

/* 亮暗版共用勇者，因此腳下格共用落腳凹槽與下沉幾何。 */
.progress .track .t-avatar--current {
  --scale-y: var(--track-current-squash, .3);
  /* 落腳格往下沉，讓**勇者的底部**跟左右格的底部切齊（V 2026-08-08 兩次調整
     後的定案）。整條軌跡是 align-items:center，scaleY 又以中心軸放大，所以不推
     的話壓扁格浮在山丘的半腰、勇者整個站在鄰格上緣之上；沉下去之後他陷進前後
     兩座山丘之間，才是「站在格子中間」而不是「站在格子上面」。
     位移量的推導見 :root 的 --track-current-drop。 */
  --shift: var(--track-current-drop);
}

/* 顏色跟右邊還沒作答的格子一樣：這一題本來就還沒答完，深色是錯的語意
   （V 2026-08-06）。「我在這裡」已經由站在上面的勇者表達，不必再用顏色講一次。

   **但只有真的還沒答的那格才這樣**：2026-08-11 讓勇者跟著回看走之後，他會站到
   已作答的格子上，那格的對錯顏色不能被抹成「未作答」——那是它答過的紀錄，
   不是他站上去就消失。所以排除 .seg--done。 */
.progress .track .t-avatar--current:not(.seg--done) {
  background: var(--line-soft);
}

/* sprite 層。跟著 .track-wrap 一起捲，所以用 absolute 定在捲動內容座標系。 */
.track-marks {
  position: absolute;
  left: 0;
  top: 0;
  height: 100%;
  pointer-events: none;
  z-index: 2;
}
/* 對齊底部看的是**畫出來的內容**，不是圖檔邊界：兩張稿的底部透明邊厚度不同
   （路牌畫布 192 高留 10px、城堡留 32px），同樣的 bottom 會讓城堡浮高約 6px
   （V 2026-08-06 回報）。這裡各自把自己的透明邊補回去，城腳與路牌腳就落在同一條線。
   --sprite-pad-b ＝ 該圖底部透明邊 × 實際顯示縮放： 路牌 10/192×36、城堡 32/192×60。 */
.trk-mark {
  position: absolute;
  bottom: calc(-1 * var(--sprite-pad-b, 0px));
  transform: translateX(-50%);
  display: block;
  line-height: 0;
}
/* 進度條 sprite 改用高密度獨立稿；版面尺寸不變，細節由高密度取樣保留。 */
.trk-mark img {
  display: block;
  image-rendering: auto;
}
/* 城堡貼底：補回自己 32/192 的透明底邊（× 60px 顯示高＝10px），
   城腳就落在題格底線上。依 V 指示放大 1.25 倍：48 → 60px。 */
.trk-mark--gate { --sprite-pad-b: 10px; }
.trk-mark--gate img { width: 60px; height: 60px; }

/* 路牌改成「以城堡為對象、與城堡置中對齊」（V 2026-08-06），不再各自貼底。
   兩張稿的圖案在自己畫布裡本來就垂直置中，所以對齊中心＝對齊 img 方框中心：
   城堡中心離軌道底 = 60/2 − 10 = 20px；路牌半高 36/2 = 18px。 */
.trk-mark--signpost {
  --gate-center: 20px;
  bottom: calc(var(--gate-center) - 18px);
}
.trk-mark--signpost img { width: 31.5px; height: 36px; }
.trk-mark--knight img { width: auto; height: var(--track-knight-h); }
/* app.js 把 top 設在壓扁格的上緣，translateY(-100%) 讓腳底落在那條線上——
   剛好切齊、像踩在線上。再往上抬 --track-knight-gap，腳底與格子之間留一點空隙
   （V 2026-08-06）。用 transform 不動 app.js 算出來的錨點。 */
.trk-mark--knight {
  z-index: 3;
  bottom: auto;
  transform: translate(-50%, calc(-100% - var(--track-knight-gap, 3px)));
}

@media (prefers-reduced-motion: reduce) {
  .t-avatar { transition: none !important; }
}

.seg {
  flex: 1 1 auto;
  min-width: 2px;
  height: 8px;
  border-radius: 2px;
  background: var(--line-soft);
  border: 0;
  padding: 0;
  transition: height .15s ease, background-color .15s ease;
}
/* 三個顏色只講學習狀況，不講題型（V 2026-07-31）：
   綠＝一次答對　黃＝提醒後改對　紅＝沒有自己答對（含看了答案）。
   顏色語意的完整說明在 app.js 的 segClass()。
   舊的 .seg--revealed（看了答案專用的黃）與 .seg--contrast（辨析卡的紫）
   已移除——前者併進紅，後者是題型標記而且會蓋掉判定。 */
.seg--ok       { background: var(--green-line); }
.seg--bad      { background: var(--red-line); }
.seg--rescued  { background: var(--amber-line); }
.seg--done     { cursor: pointer; }
.seg--done:hover { height: 12px; }
/* 當前這題：最高、最深，一眼看得出「我在這裡」 */
.seg--current {
  height: 14px;
  background: var(--ink);
  border-radius: 3px;
}
/* 正在回看的那一格：勇者本身已經標示選取，只放高、不另加淺色邊框，
   並保留該題原本的綠／黃／紅。 */
.seg--looking {
  height: 14px;
  border-radius: 3px;
}

.speak-row { display: flex; gap: 8px; }

kbd {
  font-family: var(--font-mono);
  font-size: 13px;
  color: var(--ink-faint);
  border: 1px solid var(--line);
  border-bottom-width: 2px;
  border-radius: 4px;
  padding: 0 4px;
  background: var(--surface-2);
}

/* 頁尾軟鍵（soft keys）：送出／答案／提示／上一題／下一題。
   2026-07-26 這排原本只是文字提示，且 ≤720px 直接整排隱藏——
   隱藏掉的不只是提示，「答案」與「上一題」（當年叫看答案／回看）的唯一入口
   也跟著消失，
   手機上等於沒有這兩個功能。改成真按鈕，熱鍵印在自己身上（V 2026-07-27）。 */
.foot-keys {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
}
.foot-key {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-height: 30px;
  padding: 4px 10px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--surface);
  color: var(--ink-faint);
  font-size: 13px;
  white-space: nowrap;
  transition: color .15s ease, border-color .15s ease;
}
.foot-key:hover { color: var(--ink); border-color: var(--focus); }
.foot-key--primary {
  padding: 8px 18px;
  font-size: 15px;
  color: var(--surface);
  background: var(--ink);
  border-color: var(--ink);
  border-radius: 999px;
}
.foot-key--primary:hover {
  color: var(--surface);
  background: var(--ink);
  border-color: var(--ink);
  opacity: .88;
}
.foot-key--primary kbd {
  color: inherit;
  background: transparent;
  opacity: 1;
}
.foot-key--primary:disabled { opacity: 1; cursor: default; }
.foot-key__light-label { display: inline; }
.foot-key__dark-label { display: none; }

/* ── 亮暗共用：頁尾像素軟鍵（參考圖二）──────────────────────────
   保留既有按鈕 DOM／順序／快捷鍵與 44px 觸控下限；亮暗版共用同一視覺。
   八角切角、雙層內框與 1px 下壓位移讓它讀成同一套遊戲 UI。 */
.practice-foot .foot-key,
.gr-foot .foot-key {
  min-height: 36px;
  padding: 6px 14px;
  border: 2px solid var(--pixel-blue-line);
  border-radius: 4px;
  color: var(--pixel-blue-ink);
  background: var(--pixel-blue);
  clip-path: polygon(6px 0, calc(100% - 6px) 0, 100% 6px,
    100% calc(100% - 6px), calc(100% - 6px) 100%, 6px 100%,
    0 calc(100% - 6px), 0 6px);
  box-shadow:
    inset 0 0 0 2px hsl(207 83% 67% / .82),
    inset 0 -3px 0 hsl(214 63% 31% / .72),
    0 2px 0 hsl(29 42% 26% / .28);
  text-shadow: 0 1px 0 hsl(214 65% 20% / .68);
}
.practice-foot .foot-key:hover,
.gr-foot .foot-key:hover {
  color: var(--pixel-blue-ink);
  border-color: var(--pixel-blue-line);
  background: var(--pixel-blue-hover);
}
.practice-foot .foot-key:active,
.gr-foot .foot-key:active {
  transform: translateY(1px);
  box-shadow:
    inset 0 0 0 2px hsl(207 83% 67% / .72),
    inset 0 2px 0 hsl(214 63% 31% / .72);
}
.practice-foot .foot-key--primary,
.gr-foot .foot-key--primary {
  color: var(--pixel-orange-ink);
  background: var(--pixel-orange);
  border-color: var(--pixel-orange-line);
  box-shadow:
    inset 0 0 0 2px hsl(40 90% 71% / .84),
    inset 0 -3px 0 hsl(26 72% 36% / .72),
    0 2px 0 hsl(29 42% 26% / .28);
  text-shadow: 0 1px 0 hsl(27 70% 25% / .7);
}
.practice-foot .foot-key--primary:hover,
.gr-foot .foot-key--primary:hover {
  color: var(--pixel-orange-ink);
  background: var(--pixel-orange-hover);
  border-color: var(--pixel-orange-line);
}
.practice-foot .foot-key kbd,
.gr-foot .foot-key kbd {
  color: var(--pixel-blue-line);
  border-color: hsl(39 45% 78%);
  background: hsl(40 68% 91%);
  text-shadow: none;
}
.practice-foot .foot-key--primary kbd,
.gr-foot .foot-key--primary kbd {
  color: var(--pixel-orange-line);
}

/* 單字練習三鍵共用外型：依 V 2026-08-09 指定截圖，亮暗版共用同一套精細階梯切角、
   色票與文字格式。只覆寫單字頁尾的既有真按鈕；文法頁尾不在本段範圍。 */
:is(#key-back, #key-fwd, #key-reveal, #key-submit) {
  min-height: 44px;
  padding: 7px 16px;
  border-radius: 0;
  position: relative;
  isolation: isolate;
  justify-content: center;
  color: #f8e9c6;
  font-size: 16px;
  /* 指定附件的外切角占 44px 控制約五分之一：8px、每格 2px。 */
  clip-path: polygon(
    8px 0, calc(100% - 8px) 0,
    calc(100% - 8px) 2px, calc(100% - 6px) 2px,
    calc(100% - 6px) 4px, calc(100% - 4px) 4px,
    calc(100% - 4px) 6px, calc(100% - 2px) 6px,
    calc(100% - 2px) 8px, 100% 8px,
    100% calc(100% - 8px),
    calc(100% - 2px) calc(100% - 8px),
    calc(100% - 2px) calc(100% - 6px),
    calc(100% - 4px) calc(100% - 6px),
    calc(100% - 4px) calc(100% - 4px),
    calc(100% - 6px) calc(100% - 4px),
    calc(100% - 6px) calc(100% - 2px),
    calc(100% - 8px) calc(100% - 2px),
    calc(100% - 8px) 100%,
    8px 100%,
    8px calc(100% - 2px), 6px calc(100% - 2px),
    6px calc(100% - 4px), 4px calc(100% - 4px),
    4px calc(100% - 6px), 2px calc(100% - 6px),
    2px calc(100% - 8px), 0 calc(100% - 8px),
    0 8px,
    2px 8px, 2px 6px,
    4px 6px, 4px 4px,
    6px 4px, 6px 2px,
    8px 2px
  );
}
:is(#key-back, #key-fwd, #key-reveal, #key-submit)::before,
:is(#key-back, #key-fwd, #key-reveal, #key-submit)::after {
  content: "";
  position: absolute;
  pointer-events: none;
}
:is(#key-back, #key-fwd, #key-reveal, #key-submit)::before {
  inset: 2px;
  z-index: -2;
  background: var(--key-inner-line);
  clip-path: polygon(
    6px 0, calc(100% - 6px) 0,
    calc(100% - 6px) 2px, calc(100% - 4px) 2px,
    calc(100% - 4px) 4px, calc(100% - 2px) 4px,
    calc(100% - 2px) 6px, 100% 6px,
    100% calc(100% - 6px),
    calc(100% - 2px) calc(100% - 6px),
    calc(100% - 2px) calc(100% - 4px),
    calc(100% - 4px) calc(100% - 4px),
    calc(100% - 4px) calc(100% - 2px),
    calc(100% - 6px) calc(100% - 2px),
    calc(100% - 6px) 100%,
    6px 100%,
    6px calc(100% - 2px), 4px calc(100% - 2px),
    4px calc(100% - 4px), 2px calc(100% - 4px),
    2px calc(100% - 6px), 0 calc(100% - 6px),
    0 6px,
    2px 6px, 2px 4px,
    4px 4px, 4px 2px,
    6px 2px
  );
}
:is(#key-back, #key-fwd, #key-reveal, #key-submit)::after {
  inset: 4px;
  z-index: -1;
  background: var(--key-fill);
  clip-path: polygon(
    4px 0, calc(100% - 4px) 0,
    calc(100% - 4px) 2px, calc(100% - 2px) 2px,
    calc(100% - 2px) 4px, 100% 4px,
    100% calc(100% - 4px),
    calc(100% - 2px) calc(100% - 4px),
    calc(100% - 2px) calc(100% - 2px),
    calc(100% - 4px) calc(100% - 2px),
    calc(100% - 4px) 100%,
    4px 100%,
    4px calc(100% - 2px), 2px calc(100% - 2px),
    2px calc(100% - 4px), 0 calc(100% - 4px),
    0 4px,
    2px 4px, 2px 2px,
    4px 2px
  );
  box-shadow: inset 0 -3px 0 var(--key-shade);
}
:is(#key-back, #key-fwd) {
  min-width: 160px;
  padding-inline: 13px;
  font-size: 14px;
  --key-inner-line: #3c366c;
  --key-fill: #4c437d;
  --key-shade: #3d366d;
  background: #8c7daf;
  border-color: #443d67;
  box-shadow: 0 2px 0 #2c2847;
  text-shadow: 0 1px 0 rgb(40 29 62 / .75);
}
:is(#key-back, #key-fwd):hover {
  --key-fill: #433a72;
  color: #f8e9c6;
  background: #8c7daf;
  border-color: #443d67;
}
:is(#key-back, #key-fwd):active {
  box-shadow: 0 1px 0 #2c2847;
}

#key-reveal {
  min-width: 166px;
  --key-inner-line: #586545;
  --key-fill: #5f6c48;
  --key-shade: #525d3f;
  background: #8a9561;
  border-color: #555f43;
  box-shadow: 0 2px 0 #3f472f;
  text-shadow: 0 1px 0 rgb(42 50 24 / .78);
}
#key-reveal:hover {
  --key-fill: #566340;
  color: #f8e9c6;
  background: #8a9561;
  border-color: #555f43;
}
#key-reveal:active {
  box-shadow: 0 1px 0 #3f472f;
}

#key-submit {
  min-width: 160px;
  --key-inner-line: #7e4b33;
  --key-fill: #804f34;
  --key-shade: #70412e;
  background: #b27a4d;
  border-color: #452a1e;
  box-shadow: 0 2px 0 #4a3225;
  text-shadow: 0 1px 0 rgb(73 34 20 / .78);
}
#key-submit:hover {
  --key-fill: #78482f;
  color: #f8e9c6;
  background: #b27a4d;
  border-color: #452a1e;
}
#key-submit:active {
  box-shadow: 0 1px 0 #4a3225;
}
:is(#key-back, #key-fwd, #key-reveal, #key-submit):active::after {
  box-shadow: inset 0 2px 0 var(--key-shade);
}

:is(#key-back, #key-fwd) kbd {
  color: #f8e9c6;
  border-color: #756b94;
  background: #3c3667;
}
#key-reveal kbd {
  color: #f8e9c6;
  border-color: #768059;
  background: #4f593f;
}
#key-submit kbd {
  color: #f8e9c6;
  border-color: #9b6b47;
  background: #623927;
}

/* 沒有 data-theme 時仍可跟系統暗色，但暗版不繼承亮版山谷。 */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme]) .practice-foot::before { content: none; }
}
/* 熱鍵只印給有實體鍵盤的人看。
   （2026-08-02：「跳過」整顆拿掉了，它那條 Esc 提示跟著消失。） */
.foot-key kbd { display: none; }
@media (hover: hover) and (pointer: fine) {
  .foot-key kbd { display: inline-block; }
}

/* 極窄螢幕（≤400px）：頁尾不准吃掉半個畫面 ────────────────────────
   320px 實測（760px 高的視窗，單字練習）：頁尾原本 375px＝畫面的 49%，
   .foot-keys 自己 201px、五顆鍵各佔一行。原因是熱鍵提示——「往後 ⌘PageDown」
   整顆撐到 134px，而這裡可用寬度只有 210px（左邊 78px 導航軌先吃掉 24%），
   所以一行放不下第二顆。頁尾一高，吃 --foot-h 的浮動元件就被推進卡片區，
   那是 QA 看到「按鈕蓋住填答框」的真正來路——不是 FAB 定位錯，是頁尾太高。
   改完 240px＝32%，FAB 回到頁尾上方。

   刻意不做的兩件事：
   · 不縮 44px 觸控下限（下面 pointer: coarse 那條）——那是無障礙下限。
   · 不藏「在這個裝置上沒有別條路可以走」的鍵。2026-07-26 曾經 ≤720px 整排
     隱藏，結果「答案」與「上一題」在手機上等於沒有入口，是刻意修掉的回歸。
     ⚠️ 2026-08-02 收起「送出」不違反這條，兩個條件都成立才收：
     (a) 它在同一個裝置上有等價入口——軟鍵盤的 ↵ 與它走同一條 doPrimary()；
     (b) 一旦沒有等價入口就放回來——回看時它是唯一的出口鍵（手機沒有 Esc），
         所以 .is-look 會把它顯示回來。當年被藏掉的那兩顆兩個條件都不成立。
   所以改的只有熱鍵提示與間距：≤400px 印不下 ⌘PageDown 這種字，
   熱鍵本身照樣有效，只是不印出來。

   **順序有意義，不要往前搬**：這一段必須同時排在上面 hover/fine 那條
   （才蓋得掉 kbd 的 display）與下面 pointer: coarse 那條（才蓋得掉 padding）
   之後。三段選擇器權重相同，純靠後者勝出。

   已知限制：真手機（pointer: coarse）熱鍵本來就藏著，所以這段省不到那 125px，
   實測 270px → 268px，幾乎沒動。那邊的高度是「五顆 44px 的鍵在 210px 裡排不成
   一行」造成的：五顆就算縮到最小也要 264px，塞不進 210px，2 列是硬下限。
   要真的降下來得先讓 78px 導航軌在窄螢幕收起來——那是導航的設計決定，
   不在這個修正的範圍內。 */

/* 觸控：吃滿 44px 最小點擊區 */
@media (pointer: coarse) {
  .foot-key { min-height: 44px; padding: 0 14px; font-size: 13px; }
}

/* 窄版（≤540px）三顆共用像素頁尾鍵固定單排。範圍涵蓋手機導覽的 ≤520px，並多留
   20px 接住斷點邊界；桌面版 160／166／160px 在這段寬度會把送出擠到第二列。只把
   按鈕收回內容寬，44px 觸控高度、左右排序、像素切角與各主題色票都不變。 */
@media (max-width: 540px) {
  :root[data-theme] .practice-foot .foot-keys,
  :root:not([data-theme]) .practice-foot .foot-keys {
    flex-wrap: nowrap;
    gap: 6px;
  }
  :root[data-theme] .practice-foot .foot-key kbd,
  :root:not([data-theme]) .practice-foot .foot-key kbd {
    display: none;
  }
  :root[data-theme] :is(#key-back, #key-fwd, #key-reveal, #key-submit),
  :root:not([data-theme]) :is(#key-back, #key-fwd, #key-reveal, #key-submit) {
    flex: 0 0 auto;
    min-width: 0;
    padding-inline: 10px;
    font-size: 14px;
  }
  :root[data-theme] :is(#key-back, #key-fwd),
  :root:not([data-theme]) :is(#key-back, #key-fwd) {
    inline-size: clamp(96px, 30vw, 140px);
  }
  :root[data-theme] #key-reveal,
  :root:not([data-theme]) #key-reveal {
    inline-size: clamp(100px, 31vw, 144px);
  }
  :root[data-theme] #key-submit,
  :root:not([data-theme]) #key-submit {
    inline-size: clamp(64px, 22vw, 112px);
  }
}

@media (max-width: 400px) {
  .foot-key kbd { display: none; }
  /* 窄視窗的桌機也一樣印不下（真手機走 pointer: coarse 那條，本來就藏著）。
     位置跟 .foot-key 一起，是為了守住上面那段註解講的順序：這一段必須排在
     hover/fine 那條之後才蓋得掉它。 */
  .replay-btn kbd { display: none; }
  .foot-keys { gap: 6px; }
  .foot-key { padding: 0 10px; gap: 0; }
  :root[data-theme] :is(#key-back, #key-fwd, #key-reveal, #key-submit),
  :root:not([data-theme]) :is(#key-back, #key-fwd, #key-reveal, #key-submit) {
    min-width: 0;
    font-size: 14px;
  }

  /* 2026-07-31 這裡曾把 78px 的導航軌瘦成 52px 純圖示。2026-08-02 導覽整條
     搬到頂部之後它一點寬都不佔了，那段規則連同 `.nav-btn { width: 48px }`
     一起作廢——標籤的顯示條件改由上面 `@media (min-width: 834px)` 決定，
     不再跟這個斷點綁在一起（V 指定 834＝iPad 直向寬）。 */
}

/* ── 觸控裝置：主要動作靠右，拇指才碰得到（V 2026-07-31）──────────────
   iPad 834px 實測：送出原本在整排**最左邊**，右緣距螢幕右側 658px——
   單手拿著平板時右拇指根本搆不到，而最右邊那顆反而是「下一題」這種次要導航。
   手機也一樣：拇指的自然落點在右下角，那裡該放最常按的東西。

   兩件事一起做才有效：整排靠右對齊，以及把送出排到最後。
   只做對齊的話送出仍然是那一排的第一顆，還是離右緣最遠。
   換行時也對：靠右對齊讓每一列都貼右緣，送出在最後一列的最右＝右下角。

   上一題／下一題維持彼此的自然順序（上一題在前），只是整組讓到左邊去。
   回看模式下 syncFootKeys() 會把送出換成「回到現在」——它仍然是主要動作，
   仍然在最右，語意一致。

   **所有寬度都套，不只觸控**（V 2026-07-31 更正）。第一版鎖在 pointer: coarse，
   理由是「桌機滑鼠搆得到，由左至右＝由主到次讀起來自然」——那個理由讓同一個
   工具在兩種裝置上長出兩套邏輯，V 否決了：版面可以隨 RWD 調整，但主要動作
   在哪一側這種**原則**必須一致。
   而且文法畫面本來就是對的——#gr-submit 一直排在 .gr-foot__spacer 之後、
   在所有寬度都靠右。所以練習畫面才是那個例外，不是桌機需要例外。

   只涵蓋「頁尾動作列」這個脈絡（練習＋文法）。主頁卡片與單字庫的「加入」
   是不同模式（卡片 CTA、緊接輸入框的表單鍵），不套頁尾的靠右排序。 */
.practice-foot .foot-keys { justify-content: flex-end; width: 100%; }
/* 跟文法頁尾一致：歷史返回固定在左，當下主要動作留在右。 */
#key-back   { order: 1; margin-right: auto; }
#key-fwd    { order: 2; }
#key-reveal { order: 3; }
#key-submit { order: 4; }   /* 2026-08-02：#key-stuck 已移除，序號往前補 */

/* ── 觸控：44px 下限要套滿，不能只套軟鍵（V 2026-07-31 review）────────
   上面那條 `@media (pointer: coarse) { .foot-key { min-height: 44px } }`
   從 2026-07-27 就在，但它只管軟鍵。iPhone 390px 實測，其他互動元素全部漏掉：
     填答框 133×33 ／ 主題鍵 20×20 ／ 語音三顆 ×36 ／ 跳過 74×31 ／ 招牌 52×31
   填答框是其中最要緊的——**iOS Safari 上 focus() 沒有使用者手勢不會叫出鍵盤**，
   所以每一題他都得手動點那個框，那不是備援路徑而是主要路徑。 */
@media (pointer: coarse) {
  /* 填答框：靠上下 padding 撐開，不用 min-height——它是句子裡的 inline 元素，
     給高度會把整行的行高一起拉高。padding 撐開的是自己的點擊盒。 */
  .blank { padding-top: 6px; padding-bottom: 8px; }

  .replay-btn { min-height: 44px; }
  .topnav__mark { min-height: 44px; }

}

/* 空狀態 / 完成 */
.empty {
  text-align: center;
  padding: 60px 0;
  color: var(--ink-soft);
}
.empty h2 {
  font-family: var(--font-serif);
  font-size: 24px;
  font-weight: 400;
  margin: 0 0 10px;
  color: var(--ink);
}
.empty p { margin: 0 0 20px; font-size: 15px; }

/* ══════════════════════════════════════════════════════════════
   共用元件
   ══════════════════════════════════════════════════════════════ */
.btn {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font-size: 13px;
  border: 1px solid var(--line);
  background: var(--surface);
  color: var(--ink);
  border-radius: var(--radius);
  padding: 7px 16px;
  transition: border-color .15s ease, background-color .15s ease;
}
.btn:hover { border-color: var(--ink-faint); }
.btn:disabled { opacity: .5; cursor: default; }
.btn--primary {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--bg);
}
.btn--primary:hover { opacity: .88; border-color: var(--ink); }

/* 亮版像素冒險風的主要動作鍵（V 2026-08-06 拍板）。
   墨褐底在羊皮上讀起來接近黑，跟像素風的暖調對不上；概念圖裡這顆是金的。
   **只掛在亮版**——暗版這一輪不動，維持原本的墨底白字。 */
:root[data-theme="light"] .btn--primary,
:root:not([data-theme]) .btn--primary,
:root[data-theme="light"] .foot-key--primary,
:root:not([data-theme]) .foot-key--primary {
  background: var(--gold);
  border-color: var(--gold-line);
  color: var(--gold-ink);
}
:root[data-theme="light"] .btn--primary:hover,
:root:not([data-theme]) .btn--primary:hover,
:root[data-theme="light"] .foot-key--primary:hover,
:root:not([data-theme]) .foot-key--primary:hover {
  opacity: 1;
  background: var(--gold-hover);
  border-color: var(--gold-line);
  color: var(--gold-ink);
}

.field {
  width: 100%;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 8px 12px;
  color: var(--ink);
}
.field:focus { outline: none; border-color: var(--focus); box-shadow: 0 0 0 3px hsl(252 62% 54% / .14); }
textarea.field {
  min-height: 190px;
  resize: vertical;
  font-family: var(--font-serif);
  font-size: 17px;
  line-height: 1.9;
}

.row { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
.grow { flex: 1 1 200px; min-width: 0; }
.muted { color: var(--ink-faint); font-size: 13px; }
.mono { font-family: var(--font-mono); }
.serif { font-family: var(--font-serif); }

.table-wrap {
  overflow-x: auto;
  border: 1px solid var(--line-soft);
  border-radius: var(--radius);
  background: var(--surface);
  -webkit-overflow-scrolling: touch;
}
table {
  width: 100%;
  border-collapse: collapse;
  font-size: 13px;
  white-space: nowrap;
}
th, td {
  text-align: left;
  padding: 8px 12px;
  border-bottom: 1px solid var(--line-soft);
}
th {
  font-weight: 600;
  font-size: 13px;
  letter-spacing: .06em;
  color: var(--ink-faint);
  background: var(--surface-2);
  position: sticky;
  top: 0;
}
tbody tr:last-child td { border-bottom: 0; }
tbody tr:hover td { background: var(--surface-2); }
td.lemma { font-family: var(--font-serif); font-size: 15px; }
td.num { text-align: right; font-variant-numeric: tabular-nums; }

.pill {
  display: inline-block;
  font-size: 13px;
  border-radius: 999px;
  padding: 1px 8px;
  border: 1px solid var(--line);
  color: var(--ink-faint);
}
.pill--good { color: var(--green); border-color: var(--green-line); background: var(--green-soft); }
.pill--bad  { color: var(--red);   border-color: var(--red-line);   background: var(--red-soft); }

/* ── 加字畫面 ───────────────────────────────────────────────── */
.add-results { margin-top: 20px; display: flex; flex-direction: column; gap: 6px; }
.add-result {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 7px 12px;
  border: 1px solid var(--line-soft);
  border-radius: var(--radius);
  background: var(--surface);
  font-size: 13px;
}
.add-result__word { font-family: var(--font-serif); font-size: 15px; }

/* ── 數據畫面 ───────────────────────────────────────────────── */
.stat-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: 12px;
  margin-top: 8px;
}
/* 每一格一律**兩行**：標籤、數字（V 2026-08-10）。
   從前只有「已進長期記憶」多一行註解，而 grid 的列高吃最高的那一格，
   於是整列三格一起被撐高（實測 74px → 99px），中間空一塊。
   註解沒有消失，只是換了家：一句定義走右上角的 i（chip-pop），
   一份清單走右上角的 ›（進畫面，定義寫在那個畫面的標題下）。 */
.stat {
  position: relative;
  display: block;
  width: 100%;
  text-align: left;
  font: inherit;
  background: var(--surface);
  border: 1px solid var(--line-soft);
  border-radius: var(--radius-lg);
  padding: 18px 20px;
}
.stat--hero { border-color: var(--amber-line); }
.stat__label {
  font-size: 15px;
  color: var(--ink-faint);
  margin-bottom: 6px;
  letter-spacing: .03em;
  padding-right: 26px;                 /* 讓開右上角那顆記號 */
}
.stat__value {
  font-size: 30px;
  font-variant-numeric: tabular-nums;
  line-height: 1.2;
  color: var(--ink);
}
.stat--hero .stat__value { color: var(--amber); }

/* 可點的格子：整格都是命中區，記號只是視覺提示——只讓 14px 的記號可點的話，
   命中區遠小於手機 44px 的最小觸控目標。不可點的格子維持 div、不套這個 class，
   兩者外觀分得出來（§5.11：可點與不可點長得一樣是錯的）。 */
.stat--tap {
  cursor: pointer;
  border-color: var(--line);
  box-shadow: var(--shadow);
  transition: background-color .15s ease, border-color .15s ease;
}
.stat--tap:hover { background: var(--surface-2); }
/* 招牌格也可點時，橘框不能被 --tap 的灰框蓋掉——它是這一排唯一的重點標記，
   統一兩行之後也只剩顏色可以分主次了。 */
.stat--hero.stat--tap { border-color: var(--amber-line); }
.stat__mk {
  position: absolute;
  right: 14px;
  top: 13px;
  line-height: 1;
  color: var(--ink-faint);
}
.stat__mk--go { font-size: 22px; top: 10px; }
.stat__mk--i {
  width: 18px;
  height: 18px;
  border: 1px solid var(--line-soft);
  border-radius: 50%;
  font-family: var(--font-serif);
  font-style: italic;
  font-size: 14px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.stat--tap:hover .stat__mk { color: var(--ink); }

/* 鑽取子畫面的麵包屑。回去的目的地永遠是進來的地方（數據頁）。 */
.crumb { margin: 0 0 4px; }
.crumb__back {
  font: inherit;
  font-size: 13px;
  color: var(--ink-faint);
  background: none;
  border: 0;
  padding: 2px 0;
  cursor: pointer;
}
.crumb__back:hover { color: var(--ink); }

/* 分頁膠囊（還在犯／已解決） */
.chip-tab {
  font: inherit;
  font-size: 13px;
  border: 1px solid var(--line-soft);
  border-radius: 999px;
  padding: 3px 13px;
  color: var(--ink-faint);
  background: var(--surface);
  cursor: pointer;
}
.chip-tab.is-on {
  border-color: var(--line);
  color: var(--ink);
  background: var(--surface-2);
  font-weight: 600;
}


.section { margin-top: 34px; }
.section > h2 {
  font-size: 16px;
  font-weight: 600;
  letter-spacing: .05em;
  color: var(--ink-soft);
  margin: 0 0 10px;
}

/* ── 每週學習量折線（V 2026-07-27 拍板，取代日曆熱力圖）────────────
   色票沿用 --cal-*（--cal-4）。來源區分（匯入／本系統）已於 2026-08-10 拿掉，
   跟舊熱力圖「淡色＝匯入」是同一套語意，所以 designsystem 的色票不必動。
   SVG 用 viewBox 縮放，窄螢幕自己等比縮，不需要橫向捲動容器。 */
.weekly { width: 100%; }
.weekly svg { display: block; width: 100%; height: auto; overflow: visible; }
.weekly .grid { stroke: var(--line); stroke-width: 1; opacity: .55; }
.weekly .ylab,
.weekly .xlab { fill: var(--ink-faint); font-size: 13px; font-family: var(--font-ui); }
.weekly .ylab { text-anchor: end; }
.weekly .xlab { text-anchor: middle; }
.weekly .ln {
  stroke: var(--cal-1);
  stroke-width: 1.8;
  stroke-linecap: round;
  fill: none;
}
.weekly .ln--own { stroke: var(--cal-4); stroke-width: 2.2; }
/* 本週還沒過完：虛線＋空心點，免得末端那一段看起來像退步 */
.weekly .ln--partial { stroke-dasharray: 3 3; }
.weekly .dot { fill: var(--cal-1); }
.weekly .dot--partial { fill: var(--surface); stroke: var(--cal-4); stroke-width: 2; }
.weekly .hit { fill: transparent; }

.chart-legend {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 13px;
  color: var(--ink-faint);
  margin-top: 8px;
  flex-wrap: wrap;
}
/* 圖例的線段樣本：用 border-top 畫，跟 SVG 裡的線同色 */
.chart-legend .k {
  display: inline-block;
  width: 22px;
  height: 0;
  border-top: 2px solid var(--cal-1);
  margin: 0 2px;
}
.chart-legend .k--own { border-top-color: var(--cal-4); }

/* ── 頑強的對手（V 2026-08-10）─────────────────────────────────
   從前是數據頁下方的一行小字：`case　情況；案例　打成 instance　4 次`。
   那一行丟掉了最關鍵的東西——**例句**。`case ← instance` 這件事離開句子就
   判斷不出誰對誰錯，看完的合理反應是「instance 哪裡錯了？」而畫面答不出來。
   現在同一句挖空兩次，正解與他打的上下並排，差別在哪一格一眼就看到。 */
.leech-card {
  padding: 15px 17px;
  border: 1px solid var(--line-soft);
  border-radius: var(--radius-lg);
  background: var(--surface);
  margin-bottom: 10px;
}
.leech-card__h {
  display: flex;
  align-items: baseline;
  gap: 9px;
  flex-wrap: wrap;
  margin-bottom: 11px;
}
.leech-card__word { font-family: var(--font-serif); font-size: 19px; }
.leech-card__sent {
  font-family: var(--font-serif);
  font-size: 16px;
  line-height: 2.05;
  margin: 0 0 3px;
}
.leech-blank {
  border-radius: 5px;
  padding: 1px 7px;
  font-family: var(--font-mono);
  font-size: 14.5px;
}
.leech-blank--ok {
  background: var(--green-soft);
  color: var(--green-answer);
  box-shadow: inset 0 -2px 0 var(--green-answer);
}
.leech-blank--no {
  background: var(--red-soft);
  color: var(--red);
  text-decoration: line-through;
  text-decoration-thickness: 1.5px;
}
.leech-card__why {
  margin-top: 11px;
  padding: 10px 12px;
  background: var(--surface-2);
  border-radius: var(--radius);
  font-size: 13.5px;
  line-height: 1.72;
  color: var(--ink-soft);
}
.leech-card__meta { margin-top: 9px; font-size: 13px; color: var(--ink-faint); }
.leech-card__acts { margin-top: 12px; display: flex; gap: 8px; flex-wrap: wrap; }

/* 分型徽章。三型的處置與說詞完全不同，顏色要分得開：
   換字＝招牌橙（他其實會，跟 near_miss 同一個語意家族）、
   形近＝紫（干擾，跟對錯無關的第三種狀態）、拼字＝中性。 */
.badge {
  font-size: 13px;
  border-radius: 999px;
  padding: 1px 9px;
  border: 1px solid;
  line-height: 1.5;
}
.badge--swap { color: var(--amber); border-color: var(--amber-line); background: var(--amber-soft); }
.badge--confuse { color: var(--purple); border-color: var(--purple-line); background: var(--purple-soft); }
.badge--spell { color: var(--ink-soft); border-color: var(--line-soft); background: var(--surface-2); }

/* ── 載入中 ─────────────────────────────────────────────────── */
.loading {
  text-align: center;
  color: var(--ink-faint);
  font-size: 13px;
  padding: 48px 0;
}

/* ── 窄螢幕 ─────────────────────────────────────────────────── */
@media (max-width: 640px) {
  .wrap { padding: 0 16px; }
  .sentence { font-size: 24px; line-height: 1.95; }
  .practice-foot__row { justify-content: center; }
}

/* ── 尊重減少動態偏好 ───────────────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
    scroll-behavior: auto !important;
  }
}


/* ── 語音選擇（頁尾）──────────────────────────────────────────
   句子語音以現場合成為主，可切口音／性別／年齡，或每張隨機。 */
.voice-pick { display: flex; align-items: center; gap: 7px; }
.voice-pick select {
  font-size: 13px;
  color: var(--ink-soft);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 3px 8px;
  max-width: 148px;
}
.voice-now {
  font-size: 13px;
  color: var(--ink-faint);
  min-width: 8ch;
  font-variant-numeric: tabular-nums;
}
@media (max-width: 720px) { .voice-now { display: none; } }


/* 頁尾操作列永遠位於進度軌跡下方，寬版與窄版共用同一結構。 */
.speak-row {
  width: 100%;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
}
/* 可以縮：不然四顆軟鍵會撐成一條不換行的長列，窄螢幕直接溢出到畫面外。
   min-width:0 是必要的——flex 子項預設 min-width:auto，光給 flex-shrink 不會縮。 */
.foot-keys { flex: 1 1 auto; min-width: 0; }
.voice-pick { flex: 0 1 auto; min-width: 0; }
/* 語音口音選單：V 2026-08-03 收掉，全站固定美式女聲（見 voice.js 的 forcePool）。
   ⚠️ 一定要排在上面那條 `.voice-pick { display: flex }` **之後**——同權重靠順序，
   放前面會被蓋掉（實測踩過）。#voice-now 那段「現在唸的是誰」也一起收，
   固定嗓音之後它每次都印同一個名字。
   markup 留著不刪，日後要放開只要拿掉這三行。 */
.voice-pick, .voice-now { display: none; }
/* ── 揭曉後直接在句中原空格重打（V 2026-08-03）───────────────────
   正解已在下方以米色顯示，句中不用再複製一塊獨立輸入區。提示先住在原空格：
   點擊或開始輸入就清掉；送出不對時清空並把同一句提示帶回來。
   重打期間固定寬度，避免提示消失、開始打字時整句左右跳動。 */
.blank.is-retype {
  color: var(--ink);
  background: transparent;
  border-bottom-color: var(--amber-line);
  text-decoration: none;
  caret-color: var(--ink);
}
/* 固定寬度改由 JS 的 sizeBlank() 依示範答案長度算（dataset.retypeW）——
   空格裡現在放的是那個字本身，不再需要塞得下一整句提示的 250px。 */
.blank.is-retype:focus { border-bottom-color: var(--amber-line); }
/* 空格裡放的是示範答案本身，字體就跟著句子走（.blank 已是襯線 + inherit），
   只用橘色跟他自己打的字區分開。 */
.blank.is-retype::placeholder {
  color: var(--amber);
  opacity: 1;
}
/* 說明文字挪到空格右邊：UI 字體、小一級，不會被誤認成句子的一部分。 */
.retype-hint {
  font-family: var(--font-ui);
  font-size: clamp(13px, 3.5vw, 15px);
  font-weight: 600;
  color: var(--amber);
  margin-inline-start: .5em;
  white-space: nowrap;
}
.retype-hint.is-error { color: var(--red); }
.blank.is-retype.is-retype-error,
.blank.is-retype.is-retype-error:focus { border-bottom-color: var(--red-line); }
.blank.is-retype.is-retype-error::placeholder { color: var(--red); }

.blank.is-retype-shake {
  animation: retype-shake
    calc(var(--shake-dur-a) * 2 + var(--shake-dur-b) * 2) linear;
}
/* 每個停點自帶 timing-function，四段各自跑自己的曲線。
   停點＝累積時間 ÷ 總長（80,80,60,60 → 280ms）：28.57 / 57.14 / 78.57 / 100。
   改段長比例要一起重算，見 :root 的註解。 */
@keyframes retype-shake {
  0%      { transform: translateX(0);                                animation-timing-function: var(--shake-ease); }
  28.57%  { transform: translateX(var(--shake-distance));            animation-timing-function: var(--shake-ease); }
  57.14%  { transform: translateX(calc(var(--shake-distance) * -1)); animation-timing-function: var(--shake-ease); }
  78.57%  { transform: translateX(var(--shake-overshoot));           animation-timing-function: var(--shake-ease); }
  100%    { transform: translateX(0); }
}
@media (prefers-reduced-motion: reduce) {
  .blank.is-retype-shake { animation: none !important; transform: none !important; }
}


/* ── 詞形資訊（揭曉後）─────────────────────────────────────── */
.morph {
  /* 不拿分隔線隔開單字說明與詞形（V 2026-08-06 iPhone 回報）：able 和它的
     比較級本來就是同一件事，一條線加上下留白吃掉近 30px，還把該連著讀的
     兩行切斷。只留一點行距，讓它接著上一行往下讀。 */
  margin-top: 10px;
  display: flex;
  flex-direction: column;
  gap: 9px;
  text-align: left;
}
.morph__head { display: flex; align-items: baseline; gap: 9px; flex-wrap: wrap; }
.morph__lemma { font-family: var(--font-serif); font-size: 20px; font-weight: 600; }
.morph__pos {
  font-size: 13px; font-style: italic; color: var(--ink-soft);
}
.morph__cefr {
  font-size: 13px; letter-spacing: .08em; color: var(--ink-faint);
  border: 1px solid var(--line); border-radius: 999px; padding: 1px 7px;
}
.morph__forms { display: flex; gap: 14px; flex-wrap: wrap; }
.morph__form { display: flex; align-items: baseline; gap: 5px; }
.morph__form-k { font-size: 13px; color: var(--ink-faint); }
.morph__form-v { font-family: var(--font-serif); font-size: 15px; color: var(--ink-soft); }
.morph__usage { display: flex; align-items: baseline; gap: 7px; flex-wrap: wrap; }
.morph__usage-k { font-size: 13px; color: var(--ink-faint); flex: 0 0 auto; }
.morph__chip {
  font-family: var(--font-serif);
  font-size: 13px;
  color: var(--ink-soft);
  background: var(--surface-2);
  border: 1px solid var(--line-soft);
  border-radius: 5px;
  padding: 1px 7px;
}
.morph__note { font-size: 13px; color: var(--ink-faint); line-height: 1.65; }

/* 揭曉區與詞形資訊靠左，跟句子的閱讀版心對齊
   （原本置中，跟 A 版靠左排版對不上） */
.feedback { align-items: flex-start; text-align: left; }
/* align-items 也要跟著改：只設 text-align 的話，flex 子元素仍吃基本規則的
   align-items:center，中文翻譯會被推到版心中央，跟上面的「答對了」對不齊。 */
.reveal { text-align: left; align-items: flex-start; width: 100%; max-width: 34em; }
.morph { width: 100%; max-width: 34em; }
/* ── 延伸例句（揭曉後）────────────────────────────────────────
   同一個字換成 ACG／電影／影集情境的第二句，幫這個字黏得住。 */
.extra-ex {
  margin-top: 16px;
  padding: 12px 0 0;
  border-top: 1px solid var(--line-soft);
  width: 100%;
  max-width: 34em;
  text-align: left;
}
.extra-ex__head { display: flex; align-items: baseline; gap: 9px; margin-bottom: 6px; }
.extra-ex__k { font-size: 13px; color: var(--ink-faint); letter-spacing: .04em; }
.extra-ex__src {
  font-size: 13px; color: var(--ink-soft);
  border: 1px solid var(--line); border-radius: 999px; padding: 1px 8px;
}
.extra-ex__en {
  margin: 0 0 4px;
  font-size: 17px;
  line-height: 1.75;
  color: var(--ink-soft);
}
.extra-ex__zh { margin: 0; font-size: 13px; color: var(--ink-faint); line-height: 1.7; }


/* ── 首頁每組題數 ─────────────────────────────────────────── */
.goal-pick__k { font-size: 13px; color: var(--ink-faint); }

/* ── 今日結束的數據卡 ─────────────────────────────────────── */
.daily-sum {
  margin: 22px auto 0;
  max-width: 30em;
  text-align: left;
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: var(--surface);
  padding: 20px 22px;
}
.daily-sum__row {
  display: flex; align-items: baseline; justify-content: space-between;
  padding: 7px 0; border-bottom: 1px solid var(--line-soft);
}
.daily-sum__row:last-child { border-bottom: 0; }
.daily-sum__k { font-size: 13px; color: var(--ink-faint); }
.daily-sum__v {
  font-family: var(--font-serif); font-size: 20px; color: var(--ink);
  font-variant-numeric: tabular-nums;
}
.daily-sum__v.is-hero { color: var(--amber); }
.daily-sum__note { margin-top: 12px; font-size: 13px; color: var(--ink-faint); line-height: 1.7; }


/* ── 熟練度：藍（生疏）→ 青 → 綠（熟）漸層 ────────────────────
   琥珀維持只代表「差一點」，不拿來當熟練度，避免一個顏色兩種意思。 */
.mastery__label { font-size: 13px; color: var(--ink-faint); letter-spacing: .04em; }
/* 單色（V 2026-08-06）：到哪一級就亮幾點，顏色不再隨等級變。
   原本藍→青→綠的色階要人先記住「哪個顏色比較熟」才讀得懂，
   但點數本身已經在表達程度了——顏色再編一次碼是多餘的第二套語言。
   未達點維持無彩灰，亮點吃 --mastery-on（各主題各自定義）。 */
.mastery__dot.is-on.lv1,
.mastery__dot.is-on.lv2,
.mastery__dot.is-on.lv3,
.mastery__dot.is-on.lv4,
.mastery__dot.is-on.lv5 { background: var(--mastery-on); }
.mastery[title]:hover { cursor: help; }

/* ── 為什麼（詞形／辨析說明）───────────────────────────────── */
/* 說明框搬到中文提示的右邊（V 2026-07-28）。它補充的是「這裡為什麼是這個
   樣子」，貼著提示看才連得起來；原本掉在回饋區裡，跟提示隔著一整段。
   margin-top 拿掉——同一列上不需要，靠 .card__meta 的 baseline 對齊。
   放不下時 .card__meta 的 flex-wrap 會讓它自己掉到下一行。 */
.why {
  padding: 8px 12px;
  border-left: 2px solid var(--line);
  background: var(--surface-2);
  border-radius: 0 var(--radius) var(--radius) 0;
  font-size: 13px;
  line-height: 1.7;
  color: var(--ink-soft);
  max-width: 34em;
  text-align: left;
}
.reveal__confusion { width: 100%; box-sizing: border-box; }
/* 這樣說也可以（V 2026-07-28）：揭曉後列出同一句還能用哪些字、語感差在哪。
   刻意不做成卡片框——它是正解的註腳，不是另一則獨立內容。 */
.alts { text-align: left; max-width: 34em; }
.alts__k {
  display: block;
  font-size: 13px; letter-spacing: .06em;
  color: var(--ink-faint); margin-bottom: 6px;
}
.alts__row {
  display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap;
  padding: 3px 0;
}
.alts__w { font-size: 17px; color: var(--ink-soft); }
.alts__note { font-size: 13px; line-height: 1.7; color: var(--ink-faint); }

/* 「為什麼？問 Gina」那顆鍵與載入字樣隨 Gina 一起移除（V 2026-08-09）。 */

/* 單字與文法共用；題目頁不再各留一份設定。 */
.round-pick { display: inline-flex; align-items: center; gap: 5px; }
.round-pick input {
  width: 4.2em; font-size: 13px; text-align: center;
  color: var(--ink-soft); background: var(--surface);
  border: 1px solid var(--line); border-radius: 999px; padding: 3px 6px;
  font-variant-numeric: tabular-nums;
  appearance: textfield;
  -moz-appearance: textfield;
}
/* 題數只由直接輸入控制；原生 stepper 會把數字推離視覺中心。 */
.round-pick input::-webkit-inner-spin-button,
.round-pick input::-webkit-outer-spin-button {
  -webkit-appearance: none;
  margin: 0;
}
/* iOS Safari 聚焦到 font-size 不足 16px 的輸入框時會自動放大整頁，而且打完
   收鍵盤也**不會**縮回去，整個 App 就卡在放大狀態（V 2026-08-06 iPhone 回報）。
   唯一可靠的解法是把字級補滿 16px——meta viewport 的 maximum-scale 會連使用者
   自己的縮放一起擋掉，iOS 也早就不理它了。
   全站掃過只有這兩個題數框中招，桌機沒有這個行為所以維持原本的小字級。 */
@media (pointer: coarse) {
  .round-pick input { font-size: 16px; }
}

/* 單字庫裡的熟練度點點：小一號 */
.mastery--inline { display: inline-flex; gap: 4px; }
.mastery--inline .mastery__dot { width: 6px; height: 6px; }

/* ── 單字庫：資料表格用寬版心，不要跟閱讀版心共用 720px ──────────
   V 2026-07-27 回報「版面夠寬但左右看不完」——根因就是這裡。 */
.wrap--wide { max-width: 1180px; }

/* 加字列（已從獨立畫面併入單字庫）*/
/* ── 字典（V 2026-08-09「一個框，兩層答案」）─────────────────────
   卡片本體完全沿用查義小卡的 .wt-* 樣式，這裡只寫「它住在頁面裡」需要的殼。
   .wt-panel 的無前綴規則（.wt-top／.wt-word／.wt-gloss／.wt-senses／.wt-note／
   .wt-src／.wt-util／.wt-btn／.wt-msg）照樣吃得到，抽屜專屬的
   .wt-pop--sheet 那幾條吃不到也不需要。 */
.dict-slot {
  margin-bottom: 15px;
  padding-bottom: 15px;
  border-bottom: 1px dashed var(--line-soft);
}
.dict-slot__k {
  margin: 0 0 8px;
  font-size: var(--fs-xs);
  letter-spacing: .06em;
  color: var(--ink-faint);
}
/* 浮層是浮在句子上的，所以有陰影與實心底；頁面裡那張不該假裝自己浮著 */
.dict-slot .wt-panel--inline {
  padding: 14px 15px;
  border: 1px solid var(--line-soft);
  border-radius: var(--radius);
  background: var(--bg-sunk);
  box-shadow: none;
  max-width: 620px;
}
.dict-via {
  margin: 0 0 8px;
  font-size: var(--fs-xs);
  color: var(--amber);
}
/* 常用說法。列在卡片下面，一行一個——它們有中文，做成 chip 會兩行擠成一團 */
.dict-phrases {
  margin-top: 12px;
  padding-top: 10px;
  border-top: 1px solid var(--line-soft);
  max-width: 620px;
}
.dict-phrases__k {
  margin: 0 0 6px;
  font-size: var(--fs-xs);
  letter-spacing: .06em;
  color: var(--ink-faint);
}
.dict-phrase {
  display: flex;
  align-items: baseline;
  gap: 10px;
  width: 100%;
  padding: 5px 8px;
  border: 0;
  border-radius: 6px;
  background: none;
  color: var(--ink);
  font: inherit;
  font-size: var(--fs-sm);
  text-align: left;
  cursor: pointer;
}
.dict-phrase:hover { background: var(--surface-2); }
.dict-phrase__w {
  font-family: var(--font-serif);
  flex: 0 0 auto;
}
.dict-phrase__g {
  color: var(--ink-faint);
  font-size: var(--fs-xs);
  min-width: 0;
}

.dict-cands {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  margin-top: 12px;
}
.dict-cands__k {
  font-size: var(--fs-xs);
  color: var(--ink-faint);
  margin-right: 2px;
}
.dict-chip {
  display: inline-flex;
  align-items: baseline;
  gap: 6px;
  padding: 4px 10px;
  border: 1px solid var(--line-soft);
  border-radius: 999px;
  background: var(--surface-2);
  color: var(--ink);
  font: inherit;
  font-size: var(--fs-xs);
  cursor: pointer;
}
.dict-chip:hover { border-color: var(--line); }
.dict-chip__w { font-family: var(--font-serif); }
.dict-chip__g { color: var(--ink-faint); }
/* 片語值得標出來——它們是「拆開看會誤解」的那一批 */
.dict-chip.is-phrase .dict-chip__w { font-style: italic; }
/* 下層的標籤。跟字典那塊的小標同一個層級，讀起來才像一組對照 */
.list__k {
  margin: 0 0 8px;
  font-size: var(--fs-xs);
  letter-spacing: .06em;
  color: var(--ink-faint);
}

/* 單字庫的查詢結果是要讀的內容，不是 13px 輔助註解。桌機先提升一階，
   手機再提升一階；只作用在單字庫，避免把全站緊湊標籤一起放大。 */
#screen-list {
  --list-meta-size: 15px;
  --list-content-size: 16px;
}
#screen-list #list-count,
#screen-list #dict-status,
#screen-list #list-k,
#screen-list #list-status,
#screen-list .dict-slot__k,
#screen-list .dict-phrases__k,
#screen-list .dict-cands__k {
  font-size: var(--list-meta-size);
  line-height: 1.6;
}
#screen-list .dict-phrase { font-size: var(--list-content-size); }
#screen-list .dict-phrase__g { font-size: var(--list-meta-size); }
#screen-list .dict-chip {
  min-height: 42px;
  padding: 6px 12px;
  font-size: var(--list-content-size);
}
#screen-list .dict-chip__w { font-size: 20px; line-height: 1.3; }
#screen-list .dict-chip__g { font-size: var(--list-meta-size); }
#screen-list #list-table,
#screen-list #list-table th { font-size: var(--list-meta-size); }
#screen-list #list-table td.lemma { font-size: var(--list-content-size); }
#screen-list .stars,
#screen-list .stars--none { font-size: var(--list-meta-size); }

@media (max-width: 640px) {
  #screen-list { --list-meta-size: 16px; --list-content-size: 17px; }
  #screen-list .dict-chip { min-height: 48px; padding: 7px 12px; }
  #screen-list .dict-chip__w { font-size: 21px; }
}

.addbar {
  display: flex;
  align-items: center;
  gap: 10px;
  /* 換行是必要的，不是保險。少了它，加完字之後回報訊息（可能長達 500px）
     會把同一行的輸入框壓成 26px 的細縫——390px 實測，打不了字。 */
  flex-wrap: wrap;
  margin-bottom: 14px;
  padding: 12px 14px;
  border: 1px solid var(--line-soft);
  border-radius: var(--radius);
  background: var(--surface-2);
}

/* 單字庫工具列（V 2026-08-27）：搜尋獨占上排；新增在左下，排序仍守在右下。
   下排用內容寬度決定是否換行，不用 viewport 猜斷點：加字區至少需要約 430px
   才能同時容納完整提示、加入鍵與內距；不夠時排序自然退到下一行右側。 */
.list-search-row {
  display: flex;
  align-items: center;
  margin-bottom: 14px;
}
.list-search { display: block; width: 100%; }
.list-action-row {
  display: flex;
  flex-wrap: wrap;
  align-items: start;
  gap: 10px;
  margin-bottom: 14px;
}
.list-action-row .addbar {
  flex: 1 1 430px;
  margin-bottom: 0;
  min-width: 0;
}
.list-action-row .addbar .hintbox { container-type: inline-size; }
.list-sort-wrap {
  flex: 0 0 auto;
  display: flex;
  justify-content: flex-end;
  margin-left: auto;
  padding-top: 12px;          /* 與 .addbar 內的輸入框上緣對齊 */
}
/* 真的窄到連「輸入框＋加入」也放不下時，先讓按鈕換行，保留輸入內容寬度。
   排序早已由外層內容寬度自動換到下一行，不需要再養另一個固定斷點版型。 */
@media (max-width: 380px) {
  .list-action-row .addbar .grow { flex: 1 0 100%; }
}
/* 回報訊息會很長（新增 N、已存在 N、失敗清單、例句提示）。原本的 nowrap
   讓它變成一整塊不能折的 506px：窄版時自己溢出畫面右緣，最該看到的
   「失敗 X：…」剛好被切在外面看不到（§5.11 的靜靜沒反應同一類問題）。
   改成可折行、可換行，並固定在輸入列下方完整顯示。 */
.addbar .muted { font-size: 13px; flex: 1 1 240px; min-width: 0; }
/* 空的 live region 必須留在 accessibility tree，卻不能拿 240px 跟輸入框搶位。
   有回報文字時固定另起一行；訊息仍可折行，也不會反過來壓縮輸入框。 */
#add-status:empty {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}
#add-status:not(:empty) { flex-basis: 100%; }
/* 輸入框的提示要一段醒目、一段淡（V 2026-08-11，加字框與搜尋框都要）。
   原生 placeholder 只能單色，所以提示是一層蓋在輸入框上的假 placeholder：
   input 掛 placeholder=" "，:placeholder-shown 就是「還沒打字」的開關——
   程式塞值（加入失敗留下的字）也會自動跟上，不用 JS 同步。
   醒目色沿用 retype 提示那顆 --amber（兩個主題都照 4.5:1 調過）。 */
.hintbox { position: relative; }
.hintbox__ph {
  position: absolute;
  inset: 1px;                /* 讓出 .field 的 1px 邊框 */
  display: none;
  align-items: center;
  padding: 0 12px;           /* 對齊 .field 的水平 padding，字才不會左右漂 */
  color: var(--ink-faint);
  pointer-events: none;      /* 點提示＝點輸入框 */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* 醒目色分主題（V 2026-08-12）：亮版不能用 --amber——磚紅在米底上像錯誤
   警告。改金褐，跟亮版「加入」那顆金鍵同一家族，對 --surface 實算 4.7:1。
   暗版維持 --amber：橙鮭色在深海軍藍上是強調不是警告，V 只點名亮版要換。 */
.hintbox__ph b { color: hsl(40 66% 34%); font-weight: 600; }
:root[data-theme="dark"] .hintbox__ph b { color: var(--amber); }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme]) .hintbox__ph b { color: var(--amber); }
}
.field:placeholder-shown + .hintbox__ph { display: flex; }
/* 完整提示在 17px 時約需 330px。只有輸入框本身低於這個內容寬度時才連續縮字，
   以容器寬度而非 viewport 計算；一般與中寬版維持原字級，最小不低於 12px。 */
@container (max-width: 330px) {
  .list-action-row .addbar .hintbox__ph {
    font-size: clamp(12px, 5cqi, 17px);
  }
}
/* 搜尋框帶著單字庫自己的書本 icon（V 2026-08-11 指定 nav 那顆紅皮書；
   2026-08-12 加大——「上下可以接近頂到框，留一點就好」）。輸入框內高實測
   43.2px，icon 給 33.6×36：上下各留約 3.6px。1.2× 不是整數倍縮放，但書皮
   的線條多是 2 格粗，pixelated 下的不均勻看不太出來；要完全整齊的下一階
   是 45px 高，比框還高，塞不下。 */
#list-q {
  padding-left: 48px;        /* 8 + 33.6 icon + 6 呼吸 */
  padding-right: 48px;       /* 讓出右側清除鍵的 36px 命中區 */
  background-image: url(/icons/nav-list-2x.png);
  background-repeat: no-repeat;
  background-position: 8px center;
  background-size: 33.6px 36px;
  image-rendering: pixelated;
}
#list-q + .hintbox__ph {
  padding-left: 48px;
  padding-right: 48px;
}                                                /* 提示同時讓出 icon 與清除鍵 */
#list-q::-webkit-search-cancel-button {
  -webkit-appearance: none;
  appearance: none;
}
.list-search__clear {
  position: absolute;
  z-index: 1;
  top: 50%;
  right: 4px;
  width: 36px;
  height: 36px;
  transform: translateY(-50%);
  border: 0;
  border-radius: 6px;
  padding: 0;
  background: transparent;
  color: var(--ink-faint);
  font: inherit;
  font-size: 24px;
  line-height: 1;
  cursor: pointer;
}
.list-search__clear:hover { color: var(--ink); background: var(--surface-2); }
.list-search__clear:focus-visible {
  color: var(--ink);
  outline: 2px solid var(--focus);
  outline-offset: -2px;
}
.list-search__clear[hidden] { display: none; }

/* 顏色照 §5.31 反映語意：紅只留給真正的失敗。「已存在」是提醒不是錯誤，
   用主前景色與周圍的 muted 拉開對比——V 2026-07-27 要的「讓我知道加過了」
   目的保留，顏色讓給真正需要立即注意的那一段。 */
.add-status__dup { color: var(--ink); }
.add-status__fail { color: var(--red); }

/* 批次操作列：選了東西才出現 */
.bulkbar {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 10px;
  padding: 8px 12px;
  border: 1px solid var(--focus);
  border-radius: var(--radius);
  background: var(--surface-2);
}
.bulkbar__n { font-size: 13px; color: var(--ink); margin-right: 4px; }
.btn--sm { font-size: 13px; padding: 4px 12px; }
.btn--danger { color: var(--red); border-color: var(--red-line); }
.btn--danger:hover { background: var(--red-soft); }
.btn--ghost { color: var(--ink-faint); border-color: transparent; }

/* 勾選欄 */
.col-pick { width: 34px; text-align: center; }
.col-pick input { cursor: pointer; accent-color: var(--focus); }
tr.is-picked td { background: var(--surface-2); }
tr.is-suspended td:not(.col-pick) { opacity: .45; }

/* 中文欄裡的詞性標記 */
.pos-tag {
  margin-left: 7px;
  font-size: 13px;
  font-style: italic;
  color: var(--ink-faint);
}

/* 表格：讓中文與單字欄可以換行，其餘維持 nowrap */
#list-table td.lemma, #list-table td.gloss { white-space: normal; }
#list-table td.gloss { min-width: 13em; }

/* ── 頁尾常駐在視窗底部（V 2026-07-27）──────────────────────────
   揭曉後的內容很長（說明＋延伸例句＋詞形＋重打＋語音鍵），
   原本頁尾會被推到摺線以下，要捲很久才看得到進度與快捷鍵。

   做法：練習畫面固定成一個視窗高的三段式版面——
     practice-top（不動）／ card-area（自己捲）／ practice-foot（不動）
   內容再長也只捲中間那段，頁尾永遠在，也不會蓋住任何內容。

   先前試過 position: sticky，但 .practice 沒有可停靠的高度範圍，
   捲到底時頁尾反而跟著捲走了（2026-07-27 實測）。 */
/* --vvh：iOS 軟鍵盤彈出時由 app.js 的 fitPracticeViewport() 寫進來。
   沒有鍵盤時這個變數不存在，直接吃 100dvh。四個吃滿版高度的地方
   （.app / .topnav / #screen-practice / .grammar）走同一個變數，
   只縮其中一個會讓側欄或外框繼續撐到螢幕底。 */
/* --topnav-h 由 app.js 量出來寫進 <html>（跟 --foot-h 同一套做法）。
   導覽列從側欄搬到頂部之後，這個畫面不能再吃滿 100dvh——那會多出一個
   導覽列的高度、把頁尾頂到摺線以下。鍵盤彈出時導覽仍常駐，照實扣除。 */
#screen-practice {
  height: 100vh;
  height: calc(var(--vvh, 100dvh) - var(--topnav-h, 0px));
  overflow: hidden;
}
#screen-practice .practice-top,
#screen-practice .practice-foot { flex: 0 0 auto; }

/* ── 卡片靠上錨定：揭曉時句子不要跳（V 2026-07-31）────────────────────
   .card-area 原本是 `justify-content: safe center`：內容短就垂直置中，
   一旦長到超過容器，safe 會退回靠上——揭曉的瞬間正好跨過那條線，
   於是句子整個彈上去。**那一刻正是要把自己打的字跟正解對照的時候**，
   眼睛卻得重新找位置。實測跳動：iPad 321px／iPhone 100px／桌機 136px。

   **所有尺寸都套，不分裝置**。第一版只做觸控、桌機留著垂直置中，
   V 否決同一個工具長出兩套邏輯（跟送出靠右那次是同一條原則）。

   跳動歸零一定伴隨句子上移，這是本質的：置中的短內容與靠上的長內容
   位置本來就不同，那個差就是跳動。所以出題狀態的句子會移到「揭曉後它
   會在的位置」——桌機因此比從前高約 96px。最初用 6vh 避免貼頂；
   2026-08-03 V 實機回報手機與 iPad 都拉出過大空帶，改為 12–24px 的受控留白。

   這也順帶回答了「iPad 直向 70% 是空的」——空白現在全部落在句子**下方**，
   也就是揭曉內容要長出來的地方。不是浪費，是預留。 */
#screen-practice .card-area {
  justify-content: flex-start;
  /* 熟練度與題目只留一個短節拍；行高本身已有上緣留白，
     這裡不再疊一段明顯空帶。
     2026-08-08：clamp(0,.4vh,6px) → 共用節拍。原本這段隨視窗高度變（760px 高
     時是 3px），文法那邊卻是固定 10px，於是兩邊第一行字永遠差一點，而且差多少
     還跟視窗高有關。 */
  padding-top: var(--card-meta-gap);
}
#screen-practice .card-area {
  flex: 1 1 auto;
  min-height: 0;          /* 沒有這行，flex 子元素不會讓內部捲動生效 */
  overflow-y: auto;
  overscroll-behavior: none;
}
/* 捲動區的上下緣給一點呼吸，內容不要貼著邊界被切。
   底部不只是呼吸，是一段刻意留的跑道：內容末端本來停在頁尾上方 20px，
   正好落進山景最濃的地方，最後幾行跟山混在一起（V 2026-08-09 回報）。
   多留 200px 可捲的空白之後，要不要把最後幾行拉上乾淨紙面由使用者自己決定，
   而不是我們替他把山拿掉。 */
#screen-practice .card-area {
  --area-pad-b: var(--area-run);
  padding-bottom: var(--area-pad-b);
}

/* ── 軟鍵盤彈出：保留定位、只收底部資訊（V 2026-08-03 實機拍板）────────
   `.kb-open` 由 app.js 的 fitPracticeViewport() 掛在 <html> 上，跟 --vvh
   同一個判準（可視區比版面視窗矮 80px 以上＝鍵盤真的佔了位置）。

   iPhone 鍵盤彈出後可視區很短，但 Top Nav 與熟練度若整列消失，內容會突然
   上跳。兩列都常駐，只收進度，並把題目上留白縮到 8px。
   軟鍵依狀態只留「上一題＋答案」或「上一題＋下一題」。

   鍵盤一收 .kb-open 就拿掉，底部資訊與留白全部回來——是讓位，不是永久拿掉。 */
/* V 2026-08-03 實機確認：Top Nav 與熟練度列必須常駐，避免鍵盤彈出時
   畫面結構突然少兩列。僅縮短題目上方留白，把高度讓給輸入區。 */
/* 鍵盤彈出時山景本來就 display:none，跑道跟著收回原本的呼吸值：
   可視區已經很短，再留 200px 可捲空白只是擋路。 */
html.kb-open #screen-practice .card-area { padding-top: 0; --area-pad-b: 20px; }
html.kb-open #screen-grammar .gr-area { padding-bottom: 8px; }
html.kb-open .practice-foot .progress { display: none; }
html.kb-open .practice-foot { padding: 0; }
html.kb-open #screen-practice .practice-foot__row,
html.kb-open #screen-grammar .gr-foot__in {
  gap: 8px;
  padding-top: 8px;
  padding-bottom: 8px;
}
/* 鍵盤彈出時收掉山景帶（V 2026-08-08 實機回報）。
   它是 `bottom: 100%` 定錨在頁尾上緣的裝飾，頁尾被鍵盤推上去，整條 356px 的
   山就跟著爬到句子中間，變成壓在題目上的雜訊。這裡不是把它移回底部——鍵盤佔著
   下半螢幕，移回去也只是躲在鍵盤後面，直接收掉最乾淨。鍵盤一收就回來。
   ⚠️ 亮版那條 ::before 的特異度比這條高，但它沒宣告 display，所以這條吃得到。

   ⚠️ 一定要連 .kb-raised 一起收（V 2026-08-09 在 iPad 回報又被推上來）。
   .kb-open 只在「鍵盤開著**而且**剩下高度 < 600px」時掛，那是 iPhone 的緊湊模式；
   iPad 鍵盤開了還剩約 700px，走的是 .kb-raised 那條——只給頁尾加下外距、不縮 --vvh。
   頁尾一樣被推上去，山景帶當然也一樣要收，這條規則才是真的「不分寬窄的統一規則」。 */
html.kb-open .practice-foot::before,
html.kb-open .gr-foot::before,
html.kb-raised .practice-foot::before,
html.kb-raised .gr-foot::before { display: none; }

.is-kb-shifting { will-change: transform; }
@media (prefers-reduced-motion: reduce) {
  .is-kb-shifting { transform: none !important; }
}

/* iPad 的可視高度通常仍大於 600px：保留完整頁面與頁尾內容，只用鍵盤實際
   遮住的高度把置底功能列推到鍵盤上緣。這條路不套 .kb-open，避免再次觸發
   iPad 曾發生的整頁縮放、空白與焦點位移。文法頁尾共用同一套保護。 */
html.kb-raised #screen-practice .practice-foot,
html.kb-raised #screen-grammar .gr-foot {
  margin-bottom: var(--kb-inset, 0px);
}
/* 頁尾已被抬到軟鍵盤上緣時，Home Indicator 位於鍵盤底部，不在頁尾下方；
   此狀態沿用原本 6px，不重複加入實體螢幕的 bottom inset。 */
html.kb-raised #screen-practice .practice-foot__row,
html.kb-raised #screen-grammar .gr-foot__in {
  padding-bottom: 6px;
}

/* ── 單字庫的推薦度（五顆星）────────────────────────────────────
   星數＝這個字對 V 的實用度（工作／興趣領域加權），不是難度。
   顏色跟熟練度那套區隔開：熟練度是藍→綠的冷色，這裡用中性到暖的一階，
   避免兩個五格指標在同一列裡被看成同一件事。 */
.stars { display: inline-flex; gap: 1px; font-size: 13px; letter-spacing: 0; }
.stars--none { color: var(--ink-faint); font-size: 13px; }
/* 空星用 --line（3:1，非文字元素的標準），實星一律吃達 4.5:1 的前景 token。
   --amber-line 是填色用的原色橙，對底只有 3.4:1，不能拿來畫這麼小的字元。 */
.star { color: var(--line); }
.star.is-on { color: var(--ink-soft); }
.stars--lv4 .star.is-on { color: var(--ink); }
.stars--lv5 .star.is-on { color: var(--amber); }
#list-table td.utility { white-space: nowrap; }

/* ══════════════════════════════════════════════════════════════════
   主題切換（V 2026-07-27）
   原本只跟系統走（@media prefers-color-scheme）。加上站內切換：
     <html> 沒有 data-theme        → 跟系統（維持原本行為）
     <html data-theme="light">     → 強制淺色
     <html data-theme="dark">      → 強制深色

   刻意**不去改上面那段 media query**，只在檔尾追加覆寫——
   media query 不加權重，:root[data-theme] 是 (0,2,0) 一定壓得過 :root，
   所以系統是深色時 data-theme="light" 也蓋得住。
   這樣既不動核心變數段，也不會跟並行編輯這個檔的人撞在同一行。
   ══════════════════════════════════════════════════════════════════ */
:root[data-theme="dark"] {
  color-scheme: dark;
  --bg: #0d1422;
  --bg-sunk: #080d17;
  --surface: #131826;
  --surface-2: #1c2433;
  --line: #56627a;
  --line-soft: #32353e;
  --ink: #eadabf;
  --ink-soft: #d7cab1;
  --ink-faint: #959186;
  --amber: hsl(14 91% 62%);
  --amber-line: hsl(14 91% 53%);
  --amber-soft: hsl(14 45% 19%);
  --green: #8eb58d;
  --green-line: #6e9979;
  --green-soft: #17261f;
  --green-answer: #8eb58d;
  --red: hsl(352 60% 68%);
  --red-line: hsl(352 55% 50%);
  --red-soft: hsl(352 35% 18%);
  --focus: #a29abc;
  --mastery-on: #8eb58d;
  --mastery-off: #4f5053;
  --cal-1: hsl(38 45% 51%);
  --cal-4: hsl(38 45% 72%);
    --green-fill: hsl(150 28% 40%);
    --amber-fill: hsl(28 40% 48%);
    --red-fill:   hsl(352 28% 48%);
  --purple: hsl(268 55% 76%);
  --purple-line: hsl(268 45% 58%);
  --purple-soft: hsl(268 30% 20%);
}
:root[data-theme="light"] {
  color-scheme: light;
  --bg: hsl(38 45% 93%);
  --bg-sunk: hsl(38 47% 86%);
  --surface: hsl(38 55% 97%);
  --surface-2: hsl(38 40% 90%);
  --line: hsl(35 22% 47%);
  --line-soft: hsl(35 25% 76%);
  --ink: hsl(35 18% 17%);
  --ink-soft: hsl(35 14% 27%);
  --ink-faint: hsl(35 12% 38%);
  --amber: hsl(14 91% 38%);
  --amber-line: hsl(14 91% 53%);
  --amber-soft: hsl(14 80% 92%);
  --green: hsl(150 45% 30%);
  --green-line: hsl(150 45% 38%);
  --green-soft: hsl(150 40% 91%);
  --green-answer: hsl(150 70% 30%);
  --red: hsl(352 60% 45%);
  --red-line: hsl(352 60% 50%);
  --red-soft: hsl(352 60% 93%);
  --purple: hsl(268 45% 38%);
  --purple-line: hsl(268 45% 62%);
  --purple-soft: hsl(268 50% 95%);
  --focus: hsl(40 70% 24%);
  --gold: hsl(43 62% 52%);
  --gold-hover: hsl(43 68% 60%);
  --gold-line: hsl(40 66% 38%);
  --gold-ink: hsl(35 25% 23%);
  --mastery-on: hsl(95 52% 38%);
  --mastery-off: hsl(35 10% 62%);
  --band-ink: hsl(88 22% 40%);
  --cal-1: hsl(38 55% 32%);
  --cal-4: hsl(35 18% 26%);
  /* 資料長條專用的填色。**不要拿文字用的 --green/--amber/--red 來鋪長條**：
     那三個是照 4.5:1 的文字標準調的，鋪成大面積會過亮搶眼
     （V 2026-07-29 回報程度評估的頻率層長條）。大色塊不需要那麼高的強度，
     長度本身已經在表達數值了。招牌橙尤其要留給「差一點」那一個狀態。 */
  --green-fill: hsl(150 30% 45%);
  --amber-fill: hsl(28 45% 58%);
  --red-fill:   hsl(352 32% 62%);

  /* 第三種語意色：只給「形近型」那個徽章用（V 2026-08-10）。
     三型的處置完全不同，顏色要分得開：換字借招牌橙（他其實會，跟 near_miss
     同一個語意家族）、拼字用中性、形近需要一個既不是「對」也不是「錯」的第三色。
     色相 268 離橙 14、紅 352、綠 150 都夠遠。對米底 5.1:1，過內文標準。 */
  --purple:      hsl(268 45% 38%);
  --purple-line: hsl(268 45% 62%);
  --purple-soft: hsl(268 50% 95%);
}

/* ── 側欄底部的主題切換（V 2026-07-31 改版）────────────────────────
   一顆鍵顯示當前主題，點擊向上展開清單。原本是三顆 20×20 並排，
   太小（觸控下限 44px 差一倍以上）而且橫排塞不下更多選項。

   清單向**上**開：這顆鍵貼在側欄最底部，向下沒有空間。
   position: absolute 定錨在 .theme-pick（relative），寬度可以超出 52px 的
   窄側欄——.topnav 沒有設 overflow，不會被裁掉。
   z-index 56：疊在浮動元件（55）之上、遮罩（59）與面板（60）之下，
   所以面板開著的時候這個清單不會蓋在它上面。 */
.theme-pick {
  position: relative;
}
/* 登入身分／登出。跟主題鍵同一組視覺語言（44px 圓鍵、純圖示），
   因為它們都住在 52px 寬的側欄底部，行為也都是「點一下才展開資訊」。 */
.acct-btn {
  width: 44px; height: 44px;
  display: grid; place-items: center;
  border-radius: 999px;
  background: var(--surface-2);
  box-shadow: inset 0 0 0 1px var(--line-soft);
  color: var(--ink-soft);
  margin-top: auto;              /* 底部那組的第一顆，由它把整組推下去 */
  margin-bottom: 8px;
  transition: background-color .15s ease, color .15s ease;
}
/* 橫排：推到右邊那件事交給 .topnav__tail，這兩顆自己不再帶 auto margin。
   留著的話 margin-top:auto 在 row 方向失效、margin-bottom:8px 會把它們壓歪。 */
.acct-btn { margin: 0; }
.acct-btn:hover { color: var(--ink); background: var(--surface); }
.acct-btn:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
/* 沒開登入模式時這顆是 hidden（＝display:none），身上的 auto margin 跟著失效，
   底部那一格要交還給主題鍵，不然主題鍵會浮回導覽鍵旁邊。
   兩顆都掛 auto 不行——free space 會被兩個 auto margin 對半分，
   帳號鍵會停在側欄中間。
   ⚠️ 用 `~`（後續兄弟）不是 `+`：2026-08-08 在帳號鍵與主題鍵之間插了自動發音鍵，
   相鄰兄弟選擇器會直接失配。之後再往這一列插東西也不會壞。 */
.acct-btn[hidden] ~ .theme-pick { margin-top: 0; }

/* 自動發音開關（V 2026-08-08）。採 V 修訂核准的「號角喇叭」四狀態像素稿，
   與右側主題鍵同規格：亮／暗版各有開啟與靜音素材。
   PNG 畫布同為 120×120，且可見八角框 bbox 也和日／月素材一致，避免
   「CSS 同尺寸、視覺卻更大」；斜槓本身已能辨識關閉狀態。

   ── 尺寸：60 → 30px（V 2026-08-08：「聲音和視覺版 ICON，不管 RWD 什麼版本
   都不應比功能 ICON 大」）──
   從前是 120÷2＝60px，功能圖示卻是 HD 稿 ÷4，同一張畫布下來就大了一倍，
   在窄版尤其明顯。改成 ÷4＝30px：**與功能圖示同一個取樣密度**，也穩穩小於
   它們的 36px 框。
   ⚠️ 這兩顆是 image-rendering: pixelated，尺寸必須是 120 的整除數
   （30／40／60），否則像素會被拉成大小不一的格子——不要為了湊視覺大小
   隨手填 32 或 36。目前窄版 30、寬版 40（見 ≥834px 那段），兩邊都小於同斷點的
   功能圖示（36／44）。
   按鈕本體＝像素稿本身，不再外包一圈 44px 的透明觸控區：那圈內距會讓兩顆之間
   憑空多出 14px，就是 V 說的「間隔很大」。代價是觸控目標 30px 小於 44px 建議值，
   這是 V 指定的取捨（「他們偏小和不重要」），不是漏掉。 */
.autospeak-btn {
  width: 30px; height: 30px;
  display: grid; place-items: center;
  border-radius: 0;
  background: transparent;
  box-shadow: none;
  margin: 0;
  transition: filter .15s ease;
}
.autospeak-btn:hover { background: transparent; }
.autospeak-btn__png {
  display: block;
  width: 30px;
  height: 30px;
  image-rendering: pixelated;
  transition: transform .15s ease, filter .15s ease;
}
.autospeak-btn:hover .autospeak-btn__png { filter: brightness(1.06); }
.autospeak-btn:active .autospeak-btn__png { transform: translateY(1px); }
.autospeak-btn:focus-visible { outline: none; }
.autospeak-btn:focus-visible .autospeak-btn__png {
  filter: brightness(1.12) saturate(1.08);
}

/* 可見的方形像素稿就是按鈕本體，不能再畫一顆圓形底把方框包成雙層按鈕
   （V 2026-08-07）。尺寸與自動發音鍵同一組規矩，見 .autospeak-btn 的說明：
   2026-08-08 由 60px 收回 30px（寬版 40px），讓它不比功能圖示大。 */
.theme-pick__now {
  width: 30px; height: 30px;
  display: grid; place-items: center;
  border-radius: 0;
  background: transparent;
  box-shadow: none;
  color: var(--ink-soft);
  transition: color .15s ease;
}
.theme-pick__now:hover { color: var(--ink); background: transparent; }
.theme-pick__now[aria-expanded="true"] {
  background: transparent;
  color: var(--ink);
  box-shadow: none;
}
.theme-pick__icon {
  width: 30px;
  height: 30px;
  display: grid;
  place-items: center;
  font-size: 17px;
  line-height: 1;
}
.theme-pick__now .theme-pick__png {
  transition: transform .15s ease, filter .15s ease, outline-color .15s ease;
}
.theme-pick__now:hover .theme-pick__png { filter: brightness(1.06); }
.theme-pick__now[aria-expanded="true"] .theme-pick__png {
  transform: translateY(1px);
  filter: brightness(1.08);
}
.theme-pick__now:focus-visible { outline: none; }
.theme-pick__now:focus-visible .theme-pick__png {
  /* 焦點也只強調原方框本身；不加 outline 或 scale，避免形成第二層外框，
     也讓可見方框與 60px 按鈕本體始終完全重合。 */
  filter: brightness(1.12) saturate(1.08);
}

/* 橫排改成**向下**開、靠右對齊：這顆鍵現在在頂列最右，向上沒有空間，
   而 left:0 會讓清單溢出畫面右緣。 */
.theme-pick__menu {
  position: absolute;
  top: calc(100% + 8px);
  right: 0;
  z-index: 56;
  min-width: 132px;
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 4px;
  border-radius: var(--radius);
  border: 1px solid var(--line);
  background: var(--surface);
  box-shadow: var(--shadow);
}
.theme-pick__item {
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: 44px;              /* 清單項一律吃滿觸控下限，桌機也不縮 */
  padding: 0 10px;
  border-radius: 8px;
  color: var(--ink-soft);
  font-size: 13px;
  white-space: nowrap;
  transition: background-color .15s ease, color .15s ease;
}
.theme-pick__item:hover { background: var(--surface-2); color: var(--ink); }
.theme-pick__item[aria-pressed="true"] { color: var(--ink); font-weight: 600; }
.theme-pick__ico { font-size: 15px; line-height: 1; width: 1em; text-align: center; }
/* 當前項左側的圓點：不用打勾，因為之後加彩色主題時圓點可以直接吃該主題的色 */
.theme-pick__dot {
  width: 8px; height: 8px;
  border-radius: 50%;
  flex: 0 0 auto;
  background: transparent;
  box-shadow: inset 0 0 0 1px var(--line);
}
.theme-pick__item[aria-pressed="true"] .theme-pick__dot {
  background: var(--focus);
  box-shadow: none;
}

/* ── 工具鍵：寬版放大一階（V 2026-08-08）────────────────────────
   ≥834px 是標籤都放得下的寬版，空間夠，兩顆從 30 → 40px。
   40 ＝ 120px 畫布 ÷3，還是整除、不破像素格；同斷點的功能圖示是 44px，
   所以「工具不得大過功能」仍然成立（見 .autospeak-btn 的說明）。
   ⚠️ 必須寫在基準規則**後面**：同特異度時後來者勝，寫在 ≥834px 那段
   （檔案上半部）會被下面的 30px 打回去。 */
@media (min-width: 834px) {
  .autospeak-btn,
  .theme-pick__now,
  .theme-pick__icon { width: 40px; height: 40px; }
  .autospeak-btn__png,
  .theme-pick__now .theme-pick__png { width: 40px; height: 40px; }
}

/* ── 配圖（揭曉後）──────────────────────────────────────────────
   V 2026-07-27：「搞錯 Cabinet 和 Wardrobe，有照片就好記憶。」
   兩張並排時左邊是這個字（✓）、右邊是容易搞混的那個（✗）。 */
.shots {
  display: flex;
  gap: 14px;
  flex-wrap: wrap;
  margin-top: 16px;
  width: 100%;
  max-width: 34em;
}
/* 單張圖時**不要**撐滿閱讀版心。
   `.shots` 的 max-width 是 34em，字級從 15px 拉到 17px 之後那是 578px，
   配圖鎖 4:3 就變成 576×432，直接把下面的語音鍵推出畫面 18px
   （V 2026-07-29 回報「按鈕不見了」，熱鍵還能用就是因為按鈕還在、只是在畫面外）。
   em 寬度會跟著字級一起長，這種連動很容易漏掉。
   300px 也才回到 _rules.md §7 當初假設的量級（「約 250px 寬」）。 */
.shot {
  margin: 0;
  flex: 0 1 260px;
  max-width: 300px;
  min-width: 0;
  border: 1px solid var(--line-soft);
  border-radius: var(--radius);
  overflow: hidden;
  background: var(--surface);
}
.shot img {
  display: block;
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  background: var(--bg-sunk);
}
/* 「不是這個」那張刻意壓低存在感——它是反例，不該比正解搶眼 */
.shot--no img { filter: grayscale(.55) brightness(.82); }
.shot__cap {
  display: flex; align-items: baseline; gap: 6px;
  padding: 7px 10px 2px;
  font-size: 13px;
}
/* 勾與叉要一眼看得到是哪一張（V 2026-08-01）：跟旁邊的字同級時太細，
   在縮圖下方幾乎糊成一團。放大到 17px 並加粗——符號字通常沒有粗體字重，
   合成粗體不一定生效，所以再補一道 text-stroke 把筆畫實際撐厚。
   line-height 壓成 1，字放大了也不會把整條說明推高。 */
.shot__mark {
  font-size: 17px;
  font-weight: 700;
  line-height: 1;
  -webkit-text-stroke: .6px currentColor;
  color: var(--green);
}
.shot--no .shot__mark { color: var(--red); }
.shot__word { font-family: var(--font-serif); color: var(--ink); }
.shot--no .shot__word { color: var(--ink-faint); }
.shot__by {
  display: block;
  padding: 0 10px 8px;
  font-size: 13px;
  color: var(--ink-faint);
  text-decoration: none;
}
.shot__by:hover { text-decoration: underline; }

/* ══════════════════════════════════════════════════════════════════
   點句子裡的單字 → 查義小卡（V 2026-07-27）
   ------------------------------------------------------------------
   行為與狀態見 web/wordtap.js。這裡不定義任何新色票，全部取用 :root。
   窄螢幕（≤560px）自動從錨定小卡切成底部抽屜——375px 放不下 266px 的
   浮動卡又不遮住句子，而且手指的落點誤差比滑鼠大。
   ══════════════════════════════════════════════════════════════════ */

/* 可點的字：平常只有極輕的底線，滑過才亮起來。
   整句都是可點狀態會變成雜訊，所以預設幾乎看不出來。 */
.wt-w {
  border-radius: 3px;
  padding: 0 .06em;
  cursor: pointer;
  box-decoration-break: clone;
  -webkit-box-decoration-break: clone;
  transition: background-color .12s ease;
}
.wt-w:hover { background: var(--surface-2); }
.wt-w.is-on {
  background: color-mix(in srgb, var(--focus) 22%, transparent);
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--focus) 34%, transparent);
}
@supports not (color: color-mix(in srgb, red 50%, blue)) {
  .wt-w.is-on { background: var(--surface-2); box-shadow: 0 0 0 1px var(--line); }
}

/* ── 小卡本體 ───────────────────────────────────────────────── */
.wt-pop {
  position: fixed;
  z-index: 60;
  width: 266px;
  max-width: calc(100vw - 20px);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow);
  padding: 14px 16px 13px;
  font-size: 13px;
  line-height: 1.6;
}
/* 片語卡（V 2026-07-28）：點 took 時右邊再給一張 take up。
   同一個彈出層裡兩塊面板，不是兩個各自定位的浮層——並排這件事交給 flex，
   翻面與邊界處理仍然只有一份。窄畫面自動改成上下堆疊。 */
.wt-panes { display: flex; gap: 14px; align-items: flex-start; }
.wt-panel { flex: 1 1 0; min-width: 0; }
.wt-pop--pair { width: 546px; }
.wt-panel--phr {
  border-left: 1px solid var(--line);
  padding-left: 14px;
}
.wt-panel__k {
  font-size: 13px; letter-spacing: .06em;
  color: var(--ink-faint); margin-bottom: 6px;
}
@media (max-width: 620px) {
  .wt-pop--pair { width: 266px; }
  .wt-panes { flex-direction: column; gap: 12px; }
  .wt-panel--phr {
    border-left: 0; padding-left: 0;
    border-top: 1px solid var(--line); padding-top: 12px;
  }
}

.wt-arrow {
  position: absolute;
  width: 11px; height: 11px;
  top: -6px;
  background: var(--surface);
  border-left: 1px solid var(--line);
  border-top: 1px solid var(--line);
  transform: rotate(45deg);
}
.wt-pop.is-up .wt-arrow {
  top: auto; bottom: -6px;
  border: 0;
  border-right: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
}
.wt-grab { display: none; }

/* padding-block 不能省（V 2026-08-09 回報發音鍵框被切邊）：
   那顆鍵 27px 高、比 20px 的字高，所以**它自己就是這一列的上緣**、跟內容區頂邊
   零間隙。抽屜版的 .wt-panes 有 overflow-y: auto，貼齊的那 3px 就被捲動容器切掉。 */
.wt-top {
  display: flex;
  align-items: baseline;
  gap: 8px;
  flex-wrap: wrap;
  padding-block: 3px;
}
.wt-word {
  font-family: var(--font-serif);
  font-size: 20px;
  line-height: 1.3;
  color: var(--ink);
  overflow-wrap: anywhere;
}
.wt-pos {
  font-size: 13px;
  color: var(--ink-faint);
  border: 1px solid var(--line);
  border-radius: 4px;
  padding: 0 5px;
  line-height: 16px;
}
.wt-base { font-size: 13px; color: var(--ink-faint); }
.wt-gloss { font-size: 15px; color: var(--ink); margin-top: 7px; line-height: 1.5; }
/* 詞性排在中文左邊（V 2026-08-09）。不再用標題列那種外框標籤——它現在是
   解釋的一部分，樣式比照字典：斜體、比中文淡一階、右邊留一個字距。 */
.wt-gloss-pos {
  margin-right: 6px;
  color: var(--ink-faint);
  font-family: var(--font-serif);
  font-style: italic;
}

/* 全部義項列出（V 2026-08-09）。句中用到的那一個排最前面、維持正常字色並帶
   「這句」標記；其餘降一階當參考——這裡是練習不是查字典，一眼要認得出重點。 */
.wt-senses { margin-top: 7px; }
/* 義項是平等的，所以全部用 --ink（2026-08-10）。從前只有帶「這句」標記那一項
   是 --ink、其餘 --ink-soft，因為那時假設有一個「當下這句在用的」義項比較重要；
   那個標記已證實是錯的（判的是主義項不是句中那個，見 wordtap.js 的 glossList），
   拿掉之後就沒有理由再把其他義項壓暗——查 bat 的人要讀的是三個意思，不是一個
   亮的加兩個灰的。`.wt-sense--here` 與 `.wt-here` 隨標記一起移除。 */
.wt-sense {
  font-size: 15px;
  line-height: 1.55;
  color: var(--ink);
}
.wt-note { font-size: 13px; color: var(--ink-soft); margin-top: 6px; line-height: 1.7; }

.wt-src {
  display: flex; align-items: center; gap: 6px;
  font-size: 13px; color: var(--ink-faint); margin-top: 9px;
}
.wt-dot {
  width: 4px; height: 4px; border-radius: 50%;
  background: var(--ink-faint); flex: 0 0 auto;
}
.wt-src--bank .wt-dot, .wt-src--done .wt-dot { background: var(--green); }
.wt-src--gen .wt-dot { background: var(--focus); }
.wt-src--wait .wt-dot { animation: wt-pulse 1.1s ease-in-out infinite; }
@keyframes wt-pulse { 0%, 100% { opacity: .3; } 50% { opacity: 1; } }

/* 推薦度：畫在「＋ 加入單字庫」的正上方——做這個決定的地方就在按鈕旁邊。
   只在還沒收進單字庫時出現（已經在庫裡的字看的是熟練度，不是值不值得收）。 */
.wt-util {
  display: flex; align-items: center; flex-wrap: wrap; gap: 6px;
  font-size: 13px; color: var(--ink-faint);
  margin-top: 9px;
}
.wt-util__k { letter-spacing: .02em; }
.wt-util__v { color: var(--ink-soft); }
/* 熟練度是藍→綠的學習狀態，推薦度是「值不值得收」——不同的問題，不共用顏色 */
.wt-dots--util i.on { background: var(--ink-soft); }
.wt-tag {
  font-size: 13px; line-height: 15px;
  padding: 0 5px; border-radius: 4px;
  border: 1px solid var(--line); color: var(--ink-faint);
}

.wt-dots { display: inline-flex; gap: 3px; margin-left: 2px; }
.wt-dots i {
  width: 4px; height: 4px; border-radius: 50%;
  background: var(--line);
}
.wt-dots i.on { background: var(--green); }

/* 讀取中的骨架條 */
.wt-skel {
  display: inline-block;
  height: .72em;
  border-radius: 3px;
  background: linear-gradient(90deg, var(--surface-2), var(--line-soft), var(--surface-2));
  background-size: 200% 100%;
  animation: wt-shim 1.3s linear infinite;
}
@keyframes wt-shim { from { background-position: 200% 0; } to { background-position: -200% 0; } }

/* ── 加入單字庫 ─────────────────────────────────────────────── */
.wt-btn {
  margin-top: 12px;
  width: 100%;
  display: flex; align-items: center; justify-content: center; gap: 6px;
  height: 32px;
  border-radius: 8px;
  font-size: 13px;
  letter-spacing: .02em;
  border: 1px solid var(--focus);
  color: var(--focus);
  background: transparent;
  transition: background-color .12s ease;
}
.wt-btn:hover:not(:disabled) { background: var(--surface-2); }
/* 已在庫裡：反灰、按不下去。這是 V 指定的狀態。 */
.wt-btn.is-done, .wt-btn:disabled {
  border-color: var(--line);
  color: var(--ink-faint);
  background: var(--surface-2);
  cursor: default;
}
.wt-msg { margin-top: 8px; font-size: 13px; color: var(--red); }

/* ── 窄螢幕：底部抽屜 ───────────────────────────────────────── */
.wt-scrim {
  position: fixed;
  inset: 0;
  z-index: 55;
  background: hsl(252 30% 4% / .38);
}
.wt-pop--sheet {
  left: 0; right: 0; bottom: 0; top: auto;
  width: auto; max-width: none;
  border-radius: 18px 18px 0 0;
  border-left: 0; border-right: 0; border-bottom: 0;
  padding: 10px 18px calc(20px + env(safe-area-inset-bottom, 0px));
  box-shadow: 0 -12px 30px -20px hsl(252 40% 2% / .9);
  /* 內容多就往上長，最高佔畫面下方 3/4，超過的部分在卡內捲（V 2026-08-08）。
     沒有上限時字義＋配圖＋例句會把卡片頂出畫面，最上面那個字反而看不到。
     dvh 不是 vh：手機網址列縮起來時 vh 還停在舊高度，抽屜會超過 3/4。 */
  max-height: 75dvh;
  display: flex;
  flex-direction: column;
}
/* 捲的是內容區，不是整張卡——把手要留在原地，不然一捲就沒有可以往下拉關掉的
   地方。⚠️ min-height: 0 不能省：flex 子項預設 min-height:auto，內容再長也不肯
   縮，max-height 會被整個撐破、又變回頂出畫面那個樣子。 */
.wt-pop--sheet .wt-panes {
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
}
.wt-pop--sheet .wt-arrow { display: none; }
.wt-pop--sheet .wt-grab {
  display: block;
  width: 34px; height: 4px;
  border-radius: 999px;
  background: var(--line);
  margin: 0 auto 12px;
}
.wt-pop--sheet .wt-btn { height: 40px; font-size: 13px; }

/* ── 小卡的進出場 ─────────────────────────────────────────────
   桌面＝transitions.dev 17-tooltip；手機抽屜＝07-panel-reveal。
   上面全是版面規則，這一段只管動態，兩邊刻意不混在一起寫。
   小卡的位置是 place() 用 left/top 給的，transform 完全空著，所以動畫可以獨佔它。 */
.wt-pop {
  opacity: 0;
  transform: scale(var(--wt-scale));
  /* 從被點的那個字長出來，不是從卡片中線。origin-x 由 place() 算箭頭時一併給。 */
  transform-origin: var(--wt-origin-x, 50%) 0;
  will-change: transform, opacity;
  transition:
    opacity var(--wt-out) var(--wt-out-ease),
    transform var(--wt-out) var(--wt-out-ease);
}
/* 翻到字上方時，長出來的起點也要跟著翻到卡片底部 */
.wt-pop.is-up { transform-origin: var(--wt-origin-x, 50%) 100%; }
.wt-pop.is-open {
  opacity: 1;
  transform: scale(1);
  transition:
    opacity var(--wt-in) var(--wt-in-ease),
    transform var(--wt-in) var(--wt-in-ease);
}

/* 抽屜要整片從畫面外滑進來，所以位移用 100% 而不是固定 px */
.wt-pop--sheet {
  transform: translateY(100%);
  filter: blur(var(--wt-sheet-blur));
  will-change: transform, opacity, filter;
  transition:
    opacity var(--wt-sheet-close) var(--wt-sheet-ease),
    transform var(--wt-sheet-close) var(--wt-sheet-ease),
    filter var(--wt-sheet-close) var(--wt-sheet-ease);
}
.wt-pop--sheet.is-open {
  transform: translateY(0);
  filter: blur(0);
  transition:
    opacity var(--wt-sheet-open) var(--wt-sheet-ease),
    transform var(--wt-sheet-open) var(--wt-sheet-ease),
    filter var(--wt-sheet-open) var(--wt-sheet-ease);
}
.wt-scrim {
  opacity: 0;
  transition: opacity var(--wt-sheet-close) var(--wt-sheet-ease);
}
.wt-scrim.is-open {
  opacity: 1;
  transition-duration: var(--wt-sheet-open);
}

@media (prefers-reduced-motion: reduce) {
  .wt-skel, .wt-src--wait .wt-dot { animation: none; }
  .wt-pop, .wt-scrim { transition: none !important; }
}

/* ══════════════════════════════════════════════════════════════════
   查義小卡：圖片與例句（V 2026-08-08，概念稿 2026-08-08_Lexis_word-card-
   concepts_v01.html 核准後就是規格）
   ══════════════════════════════════════════════════════════════════ */

/* 圖片圖示。**跟隔壁的「唸這個字」共用同一組尺寸與狀態**（V 選甲案）：
   卡片只有 266px 寬，兩顆並排的鍵長得不一樣，比「跟練習畫面那顆長得不同」
   更刺眼。行為（有圖才出現、點了才下載）在 wordtap.js。 */
/* .wt-shot 的外觀併進上面那組統一規則（.image-toggle, .wt-shot），
   這裡不再各寫一份，免得兩邊又長歪。 */

.wt-shots { margin-top: 9px; }
.wt-shot-fig { margin: 0; }
.wt-shot-fig + .wt-shot-fig { margin-top: 8px; }
/* 高度**一定要有上限**，寬度才不是唯一的約束：配圖多半是正方形，在 375px
   的底部抽屜裡會長到 339px 高，再加上例句就把卡片頂出畫面外，連最上面那個
   字都看不到（2026-08-08 實測 iPhone 尺寸）。用 width:auto ＋ max-height
   等比縮，不用 object-fit 裁切——配圖是用來解釋字義的，裁掉一半就講不清楚。 */
.wt-shots img {
  display: block;
  margin: 0 auto;
  width: auto;
  max-width: 100%;
  max-height: 240px;
  border-radius: 8px;
  border: 1px solid var(--line-soft);
}
.wt-pop--sheet .wt-shots img { max-height: 34vh; }
/* CC 授權的出處。自己生成的圖不掛（見 wordtap.js shotsBox）。 */
.wt-by {
  display: block;
  margin-top: 4px;
  font-size: var(--fs-xs);
  color: var(--ink-faint);
}
.wt-by:hover { text-decoration: underline; }

/* 圖還沒生（V 2026-08-21）：庫外的字排進配圖佇列、但每日鏈還沒補到它。
   佔的是圖片本來的位置，但**刻意比圖矮很多**——它是一句說明，不是佔位圖，
   撐成 240px 高會讓整張卡看起來壞掉。虛線邊框是「這裡之後會有東西」的
   慣用語彙，跟實線的圖片框分得開。 */
.wt-shot-wait {
  margin-top: 9px;
  padding: 12px 10px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  text-align: center;
  border: 1px dashed var(--line);
  border-radius: 8px;
  background: var(--surface-2);
  color: var(--ink-soft);
  font-size: var(--fs-xs);
  line-height: 1.7;
}
/* 副標只降一階顏色，**不降字級**——13px 是設計系統寫明的地板（--fs-xs），
   再小就不是「輔助說明」而是看不到。 */
.wt-shot-wait__sub { color: var(--ink-faint); }

/* 看例句：一列安靜的展開列，不是第二顆主鍵——底下已經有「加入單字庫」，
   兩顆一樣重會分不出哪個是主要動作。 */
.wt-more {
  margin-top: 10px;
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 6px;
  height: 24px;
  padding: 0;
  background: transparent;
  border: 0;
  font-size: var(--fs-xs);
  color: var(--ink-faint);
  letter-spacing: .02em;
  cursor: pointer;
}
.wt-more:hover { color: var(--ink); }
.wt-more[aria-expanded="true"] { color: var(--ink); }
/* 箭頭用 SVG 不用「▾」字元：卡片其它圖示都是 SVG，字元版在不同平台粗細
   不一，13px 的 --ink-faint 上會細到像一顆髒點（2026-08-08 概念稿實測）。 */
.wt-more__chev {
  display: block;
  width: 13px; height: 13px;
  flex: 0 0 auto;
  transition: transform .15s ease;
}
.wt-more[aria-expanded="true"] .wt-more__chev { transform: rotate(180deg); }

.wt-ex {
  margin-top: 7px;
  padding: 9px 11px;
  background: var(--surface-2);
  border-radius: 8px;
}
.wt-ex__en {
  font-family: var(--font-serif);
  font-size: var(--fs-sm);
  color: var(--ink);
  line-height: 1.55;
}
/* 目標字**只加粗、不給顏色**：琥珀是「差一點」、綠是正解，借過來會講錯話。 */
.wt-ex__en b { font-weight: 600; }
.wt-ex__zh {
  margin-top: 5px;
  font-size: var(--fs-xs);
  color: var(--ink-soft);
  line-height: 1.6;
}

/* ── 深入了解（V 2026-08-11 圖二規格）──────────────────────────
   收合段的視覺整組沿用「看例句」：同一顆 .wt-more 展開列、同一塊
   surface-2 內容盒。列與列之間排的是異質資訊（占比、字形、字源…），
   所以每列帶一個 --ink-faint 的小標（.wt-deep__k）。 */
.wt-ipa {
  font-size: var(--fs-xs);
  color: var(--ink-faint);
  letter-spacing: .01em;
  align-self: baseline;
}
.wt-deep {
  margin-top: 7px;
  padding: 9px 11px;
  background: var(--surface-2);
  border-radius: 8px;
  display: flex;
  flex-direction: column;
  gap: 7px;
}
/* 浮層裡的深度段自己捲：桌面 .wt-pop 沒有 max-height，深度五節全開會把
   卡片撐得比視窗高、「加入單字庫」按不到（覆核 2026-08-12 抓到）。
   只限浮層——單字庫的 inline 卡在頁面裡，整頁本來就能捲。 */
.wt-pop .wt-deep { max-height: 40vh; overflow-y: auto; }
.wt-deep__row {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 4px 6px;
  font-size: var(--fs-xs);
  color: var(--ink-soft);
  line-height: 1.6;
}
.wt-deep__k {
  flex: 0 0 auto;
  color: var(--ink-faint);
  letter-spacing: .02em;
}
/* 義項情境：接在釋義後面、降一階。跟 .wt-sense 同列。 */
.wt-deep__scene { margin-left: 6px; font-size: var(--fs-xs); color: var(--ink-faint); }
.wt-deep .wt-sense { font-size: var(--fs-sm); }
.wt-deep__ex { padding-top: 2px; }
.wt-deep__ex .wt-ex__en { font-size: var(--fs-sm); }
.wt-deep__wait { font-size: var(--fs-xs); color: var(--ink-faint); }
.wt-deep .wt-dots--util { position: relative; top: 1px; }

@media (prefers-reduced-motion: reduce) {
  .wt-more__chev { transition: none; }
}

/* 單字庫裡的可點單字（V 2026-08-08）。沿用句子裡那顆 .wt-w，只多一條
   底線——表格裡的一個字看不出可以點，句子裡靠上下文就知道。 */
.col-lemma-tap { border-bottom: 1px solid var(--line-soft); }

/* ══════════════════════════════════════════════════════════════
   文法練習（V 2026-07-27）
   ------------------------------------------------------------------
   版面刻意跟練習畫面同一套：滿版高度、內容區可捲、操作列釘在底部。
   兩個畫面的肌肉記憶要能互通，不要為了新功能另創一種佈局。
   ══════════════════════════════════════════════════════════════ */
.grammar {
  height: 100vh;
  /* 跟 #screen-practice 同一條算式：導覽列搬到頂部之後要扣掉它 */
  height: calc(var(--vvh, 100dvh) - var(--topnav-h, 0px));
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
.gr-area {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  /* 出題第一幀就放在批改後固定原題的位置：Top Nav 下 12px。
     寬版不再先垂直置中、審批時再上推約 200px（V 2026-08-05）。 */
  justify-content: flex-start;
  /* 上緣走共用節拍，跟單字的 .practice-top 讀同一顆 token。
     下緣走跟單字同一條跑道——文法頁尾上方是同一條山景，同樣會把最後幾行吃掉。 */
  padding: var(--card-meta-top) 24px var(--area-run);
  overscroll-behavior: none;
}
.gr-loading { color: var(--ink-faint); font-size: 15px; }

.gr-card { width: 100%; max-width: 34em; margin: 0 auto; }
.gr-prompt { flex: 0 0 auto; }

/* 只有作答中的卡片貼頂；載入、回看與收工維持原本的垂直置中狀態。 */
.gr-loading,
#gr-lookback,
.gr-area > .gr-done { margin-block: auto; }

/* 批改後固定原題與使用者答案；只有下方教學內容捲動。 */
.gr-area.is-result-split {
  justify-content: flex-start;
  overflow: hidden;
  padding-top: 12px;
  padding-bottom: 0;
}
.gr-card.is-revealed {
  display: flex;
  flex-direction: column;
  height: 100%;
  min-height: 0;
}
.gr-card.is-revealed .gr-teaching {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: none;
  -webkit-overflow-scrolling: touch;
  margin-top: 18px;
  padding: 16px 4px 24px 0;
  border-top: 1px solid var(--line-soft);
}
.gr-card.is-revealed .gr-teaching > .gr-result,
.gr-card.is-revealed .gr-teaching > .gr-wait {
  margin-top: 0;
  padding-top: 0;
  border-top: 0;
}

/* 高度與下方距離走共用節拍（見 :root 的 --card-meta-*），跟單字的 .practice-top
   對齊。min-height 從 20 改成 25：沒有標籤的題目原本整列會矮 4px，第一行字跟著上移。 */
.gr-tags {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  gap: 8px 12px;
  margin-bottom: var(--card-meta-gap);
  min-height: var(--card-meta-h);
}
.gr-tag {
  font-size: 13px;
  letter-spacing: .04em;
  color: var(--ink-faint);
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 1px 9px;
}
/* 只說「之前錯過」，不說錯在哪——說了等於在答題前給提示（同 §5.7） */
.gr-tag--again { color: var(--amber); border-color: var(--amber-line); }
.gr-mastery {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  white-space: nowrap;
}
.gr-mastery__dots { gap: 7px; }

/* 中文題目是這個畫面的主角，字級對齊練習畫面的例句 */
.gr-zh {
  font-family: var(--font-serif);
  font-size: var(--answer-size);
  line-height: 1.55;
  color: var(--ink);
  margin: 0 0 18px;
}

.gr-input {
  width: 100%;
  font-family: var(--font-serif);
  font-size: 20px;
  line-height: 1.6;
  color: var(--ink);
  background: var(--surface);
  border: 1.5px solid var(--line);
  border-radius: var(--radius);
  padding: 10px 14px;
  resize: vertical;
  transition: border-color .15s ease;
}
.gr-input:focus { outline: none; border-color: var(--focus); }
.gr-input:disabled { background: var(--bg-sunk); color: var(--ink-soft); }

/* 手機文法題從第一幀就使用鍵盤狀態的靠上幾何。點輸入框時只會少掉頁尾
   次要資訊，不再把原題從置中位置整段推上去。 */
@media (max-width: 640px) {
  #screen-grammar .gr-area {
    justify-content: flex-start;
    padding-top: var(--card-meta-top);
    padding-bottom: var(--area-run);
  }
  #screen-grammar .gr-area:not(.is-result-split) { overflow-y: auto; }
  /* 窄版把資訊列到題目的距離收成 6px——這條改由 :root 的 --card-meta-gap
     統一調（見那顆 token 上方的 640px 覆寫），單字那邊才會跟著一起收，
     不會又變成一邊 6px、一邊 10px。 */
  #screen-grammar .gr-zh {
    font-size: clamp(24px, 7.5vw, 30px);
    line-height: 1.35;
    margin-bottom: 10px;
  }
  #screen-grammar .gr-input {
    height: 116px;
    min-height: 116px;
    padding-top: 7px;
    padding-bottom: 7px;
    resize: none;
  }
}

/* 鍵盤打開後只保留操作鍵，計數與軌跡等鍵盤收起再出現。
   幾何與上面的手機初始狀態一致，避免點擊時再排一次版。 */
html.kb-open #screen-grammar .gr-area {
  justify-content: flex-start;
  padding-top: 12px;
  padding-bottom: 8px;
}
html.kb-open #screen-grammar .gr-area:not(.is-result-split) { overflow-y: auto; }
html.kb-open #screen-grammar .gr-tags { margin-bottom: 6px; }
html.kb-open #screen-grammar .gr-zh {
  font-size: clamp(24px, 7.5vw, 30px);
  line-height: 1.35;
  margin-bottom: 10px;
}
html.kb-open #screen-grammar .gr-input {
  height: 116px;
  min-height: 116px;
  padding-top: 7px;
  padding-bottom: 7px;
  resize: none;
}
html.kb-open #screen-grammar .gr-progress { display: none; }
html.kb-open #screen-grammar .gr-foot__in {
  gap: 8px;
  padding-top: 8px;
  padding-bottom: 8px;
}

/* ── 批改中的等待提示（V 2026-07-30 拍板「B ＋ 丙」）──────────────
   批改走 Opus，中位 7.8 秒、偶爾 12 秒。在這段時間裡，畫面上原本唯一的
   變化是頁尾那顆鍵變成「批改中…」——視線焦點在自己剛打的句子上，回饋卻在
   最右下角，等於沒有回饋。

   刻意跟 .gr-result 用同一組 margin-top／padding-top／上框線：判定就從
   這個槽換上來，眼睛不用重新找位置。
   主句吃 --fs-lg（20px）而不是判定短語的 15px：等待期間畫面上沒有正解，
   它是那一刻唯一該看的東西，所以不受 §5.15「提示語不跟正解搶」的約束。 */
.gr-wait {
  margin-top: 18px;
  padding-top: 16px;
  border-top: 1px solid var(--line-soft);
  opacity: 0;
  transform: translateY(var(--gwait-distance));
  transition: opacity var(--gwait-in) var(--gwait-ease),
              transform var(--gwait-in) var(--gwait-ease);
}
.gr-wait.is-open { opacity: 1; transform: translateY(0); }
.gr-wait__line { font-size: var(--fs-lg); line-height: 1.5; margin: 0; }
/* 副句用 --ink-soft 而不是 --ink-faint：它要能讀，不是註腳 */
.gr-wait__note {
  font-size: var(--fs-xs);
  line-height: 1.7;
  color: var(--ink-soft);
  margin: 8px 0 0;
}

/* transitions.dev · 15-shimmer-text —— 原封不動，只有顏色接本站 token。
   兩層構造：底層是正常的字（--shimmer-base），::before 用 attr(data-text)
   複製同一串字、把漸層 clip 到字上，再掃 background-position。
   ⚠️ 換字時 textContent 與 data-text 必須一起改，漏一邊會兩層字疊著。 */
.t-shimmer {
  position: relative;
  display: inline-block;
  color: var(--shimmer-base);
}
.t-shimmer::before {
  content: attr(data-text);
  position: absolute;
  inset: 0;
  pointer-events: none;
  background-image: linear-gradient(
    90deg,
    transparent          0%,
    transparent         40%,
    var(--shimmer-highlight) 50%,
    transparent         60%,
    transparent        100%
  );
  background-size: var(--shimmer-band) 100%;
  background-repeat: no-repeat;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  -webkit-text-fill-color: transparent;
  animation: t-shimmer var(--shimmer-dur) var(--shimmer-ease) infinite;
}
@keyframes t-shimmer {
  0%   { background-position: 100% 0; }
  100% { background-position: 0% 0; }
}

/* transitions.dev · 04-text-states-swap —— 換句用。舊句往上淡出帶模糊，
   新句從下面回到原位。三段式由 grammar.js 驅動，中間那次 reflow 不能省。 */
.t-text-swap {
  display: inline-block;
  transform: translateY(0);
  filter: blur(0);
  opacity: 1;
  transition:
    transform var(--text-swap-dur) var(--text-swap-ease),
    filter    var(--text-swap-dur) var(--text-swap-ease),
    opacity   var(--text-swap-dur) var(--text-swap-ease);
  will-change: transform, filter, opacity;
}
.t-text-swap.is-exit {
  transform: translateY(calc(var(--text-swap-translate-y) * -1));
  filter: blur(var(--text-swap-blur));
  opacity: 0;
}
.t-text-swap.is-enter-start {
  transform: translateY(var(--text-swap-translate-y));
  filter: blur(var(--text-swap-blur));
  opacity: 0;
  transition: none;
}

@media (prefers-reduced-motion: reduce) {
  .gr-wait { transition: none !important; }
  .t-shimmer::before { animation: none !important; }
  .t-text-swap { transition: none !important; }
}

/* ── 批改結果 ── */
.gr-result {
  margin-top: 18px;
  padding-top: 16px;
  border-top: 1px solid var(--line-soft);
}
.gr-verdict { font-size: 15px; font-weight: 600; margin: 0 0 12px; }
.gr-result.is-good .gr-verdict { color: var(--green); }
.gr-result.is-near .gr-verdict { color: var(--amber); }
.gr-result.is-bad  .gr-verdict { color: var(--red); }

.gr-err {
  margin: 0 0 14px;
  padding: 10px 14px;
  background: var(--surface-2);
  border-left: 3px solid var(--red-line);
  border-radius: var(--radius);
}
.gr-err__dim {
  font-size: 13px;
  letter-spacing: .04em;
  color: var(--red);
}
.gr-err__fix {
  font-family: var(--font-serif);
  font-size: 20px;
  margin: 4px 0 0;
}
.gr-err__was { color: var(--ink-faint); text-decoration-color: var(--red-line); }
.gr-err__why { font-size: 13px; color: var(--ink-soft); margin: 6px 0 0; line-height: 1.7; }

/* 母語說法跟參考譯是兩件事：前者是「實際會怎麼講」，後者只是對照用 */
.gr-native {
  margin-top: 16px;
  padding: 12px 14px;
  background: var(--green-soft);
  border-left: 3px solid var(--green-line);
  border-radius: var(--radius);
}
.gr-native__head { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.gr-native__label { font-size: 13px; letter-spacing: .04em; color: var(--green); }
/* 掛在母語例句列的讀音鍵，比工具列上的原尺寸小一號 */
.gr-native .replay-btn { padding: 3px 10px; font-size: 13px; }
.gr-native .replay-btn svg { width: 13px; height: 13px; }
.gr-native__en {
  font-family: var(--font-serif);
  font-size: 20px;
  margin: 4px 0 0;
  color: var(--ink);
}
.gr-native__note { font-size: 13px; color: var(--ink-soft); margin: 6px 0 0; line-height: 1.7; }

.gr-ref { font-size: 13px; color: var(--ink-faint); margin: 12px 0 0; }
.gr-again { font-size: 13px; color: var(--amber); margin: 10px 0 0; }

/* ── 收工畫面 ── */
.gr-done { width: 100%; max-width: 30em; margin: 0 auto; }
.gr-done__head { font-size: 17px; margin: 0 0 16px; }
.gr-done__rows {
  border: 1px solid var(--line-soft);
  border-radius: var(--radius-lg);
  background: var(--surface);
  padding: 4px 18px;
  margin-bottom: 18px;
}
.gr-done__row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  padding: 11px 0;
  border-bottom: 1px solid var(--line-soft);
  font-size: 15px;
  color: var(--ink-soft);
}
.gr-done__row:last-child { border-bottom: 0; }
.gr-done__row b { font-family: var(--font-serif); font-size: 20px; color: var(--ink); }

/* ── 一輪結束的檢討（V 2026-07-28）──
   統計那張表講「做了多少」，這一塊講「哪裡弱」。刻意不進上面那個表格框：
   表格是數字，這裡是話，混在一起兩邊都難讀。 */
.gr-review { margin-bottom: 18px; text-align: left; }
.gr-review__weak {
  display: flex; align-items: center; flex-wrap: wrap; gap: 8px;
  margin-bottom: 12px;
}
.gr-review__k { font-size: 13px; color: var(--ink-faint); }
.gr-review__dim {
  display: inline-flex; align-items: baseline; gap: 4px;
  font-size: 13px; line-height: 20px;
  padding: 0 8px; border-radius: 999px;
  border: 1px solid var(--amber-line); background: var(--amber-soft);
  color: var(--amber);
}
.gr-review__dim i { font-style: normal; opacity: .75; }
.gr-review__wait { font-size: 13px; color: var(--ink-faint); margin: 0; }
.gr-review__sum {
  font-size: 15px; line-height: 1.75; color: var(--ink); margin: 0 0 8px;
}
.gr-review__focus {
  margin: 0; padding-left: 1.2em;
  font-size: 13px; line-height: 1.8; color: var(--ink-soft);
}

/* ── 底部操作列 ── */
.gr-foot {
  flex: 0 0 auto;
  border-top: 1px solid var(--line-soft);
  background: var(--bg);
  /* 與單字頁尾相同：可以點、可以拖軌跡，但不得拉動整個 viewport。 */
  touch-action: none;
}
/* 所有寬度跟單字頁尾一致：本輪計數 → 完整軌跡 → 操作鍵。
   寬版也不把進度與按鍵拆成左右兩欄（V 2026-08-05）。 */
.gr-foot__in {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  /* 與 .practice-foot__row 同一組數字，見那邊的說明。 */
  gap: 6px;
  padding-top: 6px;
  padding-bottom: 6px;
  padding-bottom: max(6px, var(--safe-area-bottom));
}
.gr-foot__actions {
  width: 100%;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 10px 14px;
}
/* 單字與文法都保留主鍵；操作列高度固定，避免切換時頁尾上下跳動。 */
.speak-row,
.gr-foot__actions { min-height: 42px; }
.gr-progress { width: 100%; min-width: 0; }
/* 撐開器只在放得下的時候起作用；換行之後它會自己佔一行，所以收掉 */
.gr-foot__spacer { flex: 1 1 auto; }
.gr-foot__count { font-size: 13px; color: var(--ink-faint); white-space: nowrap; }
@media (max-width: 640px) {
  /* 窄螢幕仍是同一套層級；操作列自己換行，撐開器不參與。 */
  .gr-foot__spacer { display: none; }
  .practice-foot__row,
  .gr-foot__in {
    /* 與寬版同一組數字：頁尾若在 640px 斷點差幾 px，定錨在它上緣的
       山景也會跟著移，違反「只裁左右、地平線不動」。 */
    gap: 6px;
    /* 這裡要重申上下 padding：前面的窄版 `.wrap { padding: 0 16px; }`
       會用 shorthand 把單字頁尾的上下 padding 清成 0。 */
    padding-top: 6px;
    padding-bottom: 6px;
    padding-bottom: max(6px, var(--safe-area-bottom));
  }
  .gr-foot__actions { gap: 8px 10px; }
  #gr-submit { margin-left: auto; }
}
.gr-btn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 8px 18px;
  font-size: 15px;
  color: var(--surface);
  background: var(--ink);
  border-radius: 999px;
}
.gr-btn:disabled { opacity: 1; cursor: default; }
.gr-btn kbd {
  font-family: var(--font-mono);
  font-size: 13px;
  color: inherit;
  background: transparent;
  opacity: 1;
}

/* ══════════════════════════════════════════════════════════════
   主頁：今日總覽（V 2026-07-27）
   ------------------------------------------------------------------
   招牌點下去的地方。單字與文法並排，因為這是一天的全貌；
   收工小結只有這一份，練習畫面不再自己畫一塊。
   ══════════════════════════════════════════════════════════════ */
/* 捲動跟單字庫／數據同一套：內容長就讓視窗捲，畫面自己不另開捲軸 */
/* 兩張卡並排要比閱讀版心寬一點，但別寬到像資料表格 */
.wrap--home { max-width: 900px; padding-bottom: 64px; }

.home__head { padding: 56px 0 26px; text-align: center; }
.home__title {
  font-family: var(--font-serif);
  font-size: 32px;
  font-weight: 400;
  margin: 0 0 8px;
  color: var(--ink);
}
.home__sub { margin: 0; font-size: 13px; color: var(--ink-faint); }

.home__grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 16px;
  align-items: start;
}
@media (max-width: 760px) {
  .home__grid { grid-template-columns: 1fr; }
  .home__head { padding-top: 32px; }
}

.home-card {
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: var(--surface);
  padding: 18px 22px 20px;
  display: flex;
  flex-direction: column;
}
.home-card__h {
  font-size: 13px;
  font-weight: 600;
  letter-spacing: .08em;
  color: var(--ink-faint);
  margin: 0 0 6px;
}
.home-card__act {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  margin-top: 16px;
}
.home-card__off { margin: 10px 0; font-size: 13px; color: var(--ink-faint); }

/* 主頁裡的數據列不要再包一層框——外面那張卡已經是框了 */
.daily-sum--flat {
  margin: 0;
  max-width: none;
  border: 0;
  border-radius: 0;
  background: none;
  padding: 0;
}

/* 今日軌跡：練習畫面那條畫的是「本輪」，這條畫的是一整天 */
.home__track { margin-top: 24px; }
.home__track-k {
  font-size: 13px;
  letter-spacing: .06em;
  color: var(--ink-faint);
  margin-bottom: 8px;
}
.home__note {
  margin: 22px 0 0;
  font-size: 13px;
  line-height: 1.75;
  color: var(--ink-faint);
  text-align: center;
}

/* 站在主頁時招牌要亮著——它現在是一個真的目的地，不只是裝飾 */
.topnav__mark[aria-current] { color: var(--ink); background: var(--surface-2); }

/* 收工那一句。跟副標分開一行：副標是事實（日期、連續天數），
   這一行是觀察，語氣不同就該有自己的位置。
   琥珀色是招牌色——它只在真的有話講時才出現，所以值得用它。 */
.home__remark {
  font-size: var(--fs-sm);
  line-height: 1.7;
  color: var(--amber);
  margin: 10px auto 0;
  max-width: 30em;
}

/* 查義小卡的發音鍵。只有圖示不放字——小卡很窄，四個中文字會把詞性
   與「句中作 X」擠到換行（V 2026-07-29 指定用 icon）。
   對齊用 align-self: center：.wt-top 是 baseline 對齊，圖示沒有基線，
   不覆寫會被吊在字的下緣。 */
.wt-speak {
  align-self: center;
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border-radius: 8px;
  color: var(--ink-faint);
  transition: color .15s ease, background-color .15s ease;
}
.wt-speak svg { width: 17px; height: 17px; display: block; }
.wt-speak:hover { color: var(--ink); background: var(--surface-2); }
/* 正在唸＝正常運作，用綠色。琥珀是全站「這裡要留意」的招牌色（差一點、句型提示），
   拿來標示發音中會被讀成警告（V 2026-08-09）。句子那顆 .replay-btn.is-playing
   早就是綠的，這裡跟它對齊。 */
.wt-speak.is-playing { color: var(--green); background: var(--green-soft); }

/* 查義小卡的句型區塊。把 note_zh 裡的句型抽出來單獨框起來——
   V 2026-07-29：點 managed 時 note 已經寫了「manage to do sth＝…」，
   資訊是對的，但被當成一段文字，看不出那是一個要整組記的單位。
   用琥珀左條：跟「差一點」同一個招牌色，語意上都是「這裡要留意」。 */
.wt-pat {
  margin-top: 8px;
  padding: 8px 11px;
  background: var(--amber-soft);
  border-left: 3px solid var(--amber-line);
  border-radius: 8px;
}
.wt-pat__k {
  font-size: var(--fs-xs);
  letter-spacing: .06em;
  color: var(--amber);
  margin-right: 8px;
}
.wt-pat__v {
  font-family: var(--font-mono);
  font-size: var(--fs-sm);
  color: var(--ink);
  overflow-wrap: anywhere;
}
.wt-pat__zh { font-size: var(--fs-xs); color: var(--ink-soft); margin-top: 4px; line-height: 1.7; }

/* ══════════════════════════════════════════════════════════════
   設定頁（V 2026-08-10 拍板，概念稿 v02 的 A 攤平版）

   單層攤平、區段標題分組。刻意不做分頁也不做子頁：項目只有個位數，
   分頁會比內容還多，兩層則讓最常改的那兩項每次多兩個動作。

   卡片與列的視覺沿用字庫／數據那套（--surface ＋ --line-soft ＋ --radius），
   不另立一套設定專用的外觀語言。
   ══════════════════════════════════════════════════════════════ */
.set-wrap { padding-bottom: 56px; }
.set-sec { margin-bottom: 18px; }
.set-sec__h {
  font-size: var(--fs-xs);
  letter-spacing: .08em;
  color: var(--ink-faint);
  margin: 0 0 6px;
  padding-left: 2px;
  font-weight: 600;
}
.set-card {
  background: var(--surface);
  border: 1px solid var(--line-soft);
  border-radius: var(--radius);
  overflow: hidden;
}
.set-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 11px 12px;
  border-bottom: 1px solid var(--line-soft);
  font-size: var(--fs-sm);
  color: var(--ink);
}
.set-row:last-child { border-bottom: 0; }
/* 控制項自己佔一整列（學習路徑的 chip 群）：預設的 .set-row 是左右兩欄，
   五顆帶圖示的 chip 塞右欄會把說明文字擠爆。 */
.set-row--block { display: block; }
.set-row.is-off { opacity: .55; }
.set-row__l { min-width: 0; }
.set-row__k { display: block; }
/* 說明文字一律在標題下方、不塞到右邊：右邊是控制項的位置，
   兩者搶同一欄時窄螢幕會把控制項擠掉（字庫那邊踩過）。 */
.set-row__note {
  display: block;
  font-size: var(--fs-xs);
  color: var(--ink-faint);
  margin-top: 2px;
  line-height: 1.5;
}
.set-row__v { color: var(--ink-faint); font-size: var(--fs-xs); white-space: nowrap; }

/* 三選一／二選一。用 aria-pressed 表達狀態，不用 class——
   螢幕閱讀器讀得到，CSS 也直接吃得到屬性選擇器，不必兩邊同步。 */
.set-seg {
  display: flex;
  border: 1px solid var(--gold-line);
  border-radius: 999px;
  overflow: hidden;
  flex: 0 0 auto;
}
.set-seg__b {
  padding: 4px 12px;
  font-size: var(--fs-xs);
  color: var(--ink-soft);
  background: transparent;
  border: 0;
  cursor: pointer;
  white-space: nowrap;
  min-height: 32px;
}
.set-seg__b + .set-seg__b { border-left: 1px solid var(--gold-line); }
.set-seg__b[aria-pressed="true"] {
  background: var(--gold);
  color: var(--gold-ink);
  font-weight: 600;
}
.set-seg__b:focus-visible { outline: 2px solid var(--focus); outline-offset: -2px; }

/* ── 學習路徑（2026-08-26，V 核准概念稿）──────────────────────────────
   刻意不用 .set-seg：那是 2–3 選一的膠囊，五個帶圖示的選項擠在一列會爆版。
   改成會換行的 chip，窄螢幕自然折行；狀態一樣用 aria-pressed 表達。 */
.path-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 10px;
}
.path-chip {
  padding: 6px 14px;
  font-size: var(--fs-xs);
  color: var(--ink-soft);
  background: transparent;
  border: 1px solid var(--gold-line);
  border-radius: 999px;
  cursor: pointer;
  white-space: nowrap;
  min-height: 34px;
}
.path-chip[aria-pressed="true"] {
  background: var(--gold);
  color: var(--gold-ink);
  font-weight: 600;
}
.path-chip:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
.path-chip[disabled] { opacity: .5; cursor: default; }

.path-meta {
  margin-top: 10px;
  font-size: var(--fs-xs);
  color: var(--ink-faint);
  line-height: 1.6;
}
.path-bar {
  margin-top: 6px;
  height: 8px;
  border-radius: 999px;
  background: var(--line-soft);
  overflow: hidden;
}
.path-bar__fill {
  display: block;
  height: 100%;
  background: var(--gold);
  transition: width .25s ease;
}

.set-sw {
  width: 44px;
  height: 26px;
  border-radius: 999px;
  border: 1px solid var(--line-soft);
  background: hsl(35 12% 68%);
  position: relative;
  flex: 0 0 auto;
  cursor: pointer;
  transition: background-color var(--duration-fast) var(--ease-in-out);
}
.set-sw__dot {
  position: absolute;
  top: 2px;
  left: 2px;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: #fff;
  transition: transform var(--duration-fast) var(--ease-in-out);
}
.set-sw[aria-checked="true"] { background: var(--green); border-color: var(--green-line); }
.set-sw[aria-checked="true"] .set-sw__dot { transform: translateX(18px); }
.set-sw:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }

.set-btn {
  padding: 6px 14px;
  font-size: var(--fs-xs);
  border-radius: var(--radius);
  border: 1px solid var(--line-soft);
  background: var(--surface-2);
  color: var(--ink);
  cursor: pointer;
  white-space: nowrap;
  min-height: 32px;
}
.set-btn:hover { background: var(--bg-sunk); }
.set-btn:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
.set-btn--danger { color: var(--red); border-color: var(--red-line); }

/* DEV 專用的視窗尺寸讀數（V 2026-08-10）。
   用途：對齊「真機」與「DevTools 模擬」兩個環境——他在 iPhone 17 上看到數據頁
   兩欄、DevTools 的 iPhone 17 預設（402×874）卻是一欄，差別只是寬度不同
   （`.stat-grid` 的翻欄門檻實測 424px），但沒有數字就無從對齊。

   ⚠️ 一定要 position: fixed。寫進 .topnav__release 會撐寬識別欄、導覽列 71 → 81px，
   等於為了量測而改變被量測的對象（實測踩過）。
   只有 body.is-dev 才顯示，正式站（HTTPS）永遠看不到。 */
.dev-size { display: none; }
body.is-dev .dev-size {
  display: block;
  position: fixed;
  left: 6px;
  bottom: 6px;
  z-index: 90;
  padding: 2px 7px;
  border-radius: 999px;
  font: 11px/1.5 var(--font-mono);
  color: var(--ink-faint);
  background: var(--surface);
  border: 1px solid var(--line-soft);
  opacity: .72;
  pointer-events: none;          /* 絕不擋到底下的操作 */
  user-select: none;
}
