@import url("fonts.css");   /* ⚠ 必須在最上面。由 工具/build_deck.py 產生，缺了就掉回系統字型 */

/* ===========================================================================
   高二歷史第二冊《中國與東亞》投影片　共用樣式
   建立 2026-08-09（老師定字型：內文＝日星宋體／中標＝鐵宋Plus／大標＝蘭陽明體W6）
   修訂 2026-08-10（字級改用 cqh 真的跟著畫面縮放；加入每章佈景主題層）
   ---------------------------------------------------------------------------
   ⚠ 字級一律以「畫面高的百分比」定義（cqh ＝ container query height，
      1cqh ＝ .slide 高度的 1%）。列印時 .slide 是 190.5mm ＝ 720px，
      所以下面註解的 px 值就是印出來的實際大小。
      ⛔ 不要改回固定 px：那樣在瀏覽器預覽時字不會跟著縮放，
         會誤判「這樣已經夠大了」，投出來卻後排讀不到。
   =========================================================================== */

:root{
  /* ⛔⛔ **這一行不可以拿掉，也不可以改成 `light`。**（2026-08-12 老師：
     「我從來沒有想要支持過深色瀏覽器主題，所以，你可以讓 css 強制支持亮色」）

     沒有它的話，Chrome 的「自動深色」會**演算法反轉整份投影片**——
     那不是我們寫的主題，是瀏覽器自己猜的。實測反轉後：
       · 一般文字 15.61:1 → 15.52:1（沒事，它處理文字還行）
       · 灰底＋次要文字 5.49:1 → 6.37:1（也沒事）
       · **螢光筆填色 `#ffd84d` → `#493200`**　← 壞的是這個
     螢光筆對頁底的對比兩邊都是 1.25:1／1.29:1，**數字一樣但這正是陷阱**：
     亮色下它靠的是彩度（飽和亮黃壓在近白的紙上），翻成深褐壓在近黑上，
     明暗差沒了、彩度也沒了，「這段是原文／關鍵詞」這個訊號整個消失。

     ⚠⚠ **一定要寫 `only light`，寫 `light` 是沒用的**（實測，2026-08-12）：
       | 什麼都不寫                              | ❌ 被反轉 |
       | `color-scheme: light` ＋ `<meta>`       | ❌ **還是被反轉** |
       | 只寫一段 `@media (prefers-color-scheme:dark)` | ❌ 還是被反轉 |
       | `color-scheme: only light`              | ✅ 擋得住（但有前提，見下） |
     `only` 這個關鍵字的意思就是「不准瀏覽器覆寫」，少了它等於沒說。

     ⛔⛔ **`only light` 有一個前提，手機 Edge 踩得到**（2026-08-13 追查）：
     它只在瀏覽器**同時回報 `prefers-color-scheme: dark`** 的時候有效。
     一旦湊出「強制深色開著、但偏好回報成亮色」，Chromium 會走一條專門留給
     Android WebView `FORCE_DARK_ONLY` 的分支，`only` **整個被跳過**——
     見 Blink `computed_style.cc` 的 `SetUsedColorScheme()`：
         dark_scheme = (has_dark && prefers_dark)
                    || (has_dark && !has_light)
                    || (force_dark && !has_only)     ← only 在這裡擋下來
                    || (force_dark && !prefers_dark) ← 這條沒看 only
     實測（頁底 #f4f3ef）：強制深色＋偏好深色 → rgb(244,243,239) ✅；
     強制深色＋偏好亮色 → **rgb(37,36,33)**，跟完全沒寫 `only light` 一樣。
     ⛔ 四種寫法在那條分支下**全部**變深色（`light dark` 走第三條、
        `only dark` 走第二條）。⛔ 不要再花時間試 `color-scheme` 的別種寫法。
     Chrome 手機版踩不到，因為它的「將網站變暗」是掛在深色主題底下的**子勾選**，
     兩者永遠一起動；Edge 那個開關跟 App 主題是**分開**的。

     ⭐⭐ **所以整頁變深色是擋不住的，我們改成「幫每個主題想好深色方案」**
     （老師 2026-08-13：「我們不主動創造深色主題，但我們得幫每個主題想好深色方案」）。
     頁面被塗黑我們認了，要守住的是**螢光筆在深色頁上仍然是「淺底＋深字」**。
     做法見下面〈螢光筆的強制深色對策〉，實測結果是深色下畫出來的像素
     跟正常模式**完全相同**。完整紀錄在 docs/投影片製作.md 的〇之二。

     ⭐ 這一行**不影響**我們自己的 `[data-theme]`。哪天真要做深色主題，
        用工具列上的按鈕切 `data-theme`，⛔ 不要綁 `prefers-color-scheme`
        ——投影片會因為老師筆電晚上切深色就在上課當下自己變黑，
        而台上不會馬上發現。 */
  color-scheme: only light;

  /* ── 邏輯畫布 ───────────────────────────────────────────── */
  --slide-w: 1280px;
  --slide-h: 720px;
  --safe:    5%;          /* 安全邊界：投影機常裁邊，內容不要壓到最外圈 */

  /* ── 顏色 ────────────────────────────────────────────────
     ⛔⛔ 2026-08-10 老師定案：**不要黑底白字。**
        「這種投影片非常不適合大螢幕閱讀」——說得對，而且對我們特別對：
        **我們的內文是宋體。**宋體的橫畫又細又長，白字放在黑底上，
        亮的筆畫會往暗處暈開（halation），細橫畫會先糊掉甚至消失；
        字級愈小愈嚴重。加上教室是亮的、電視是自體發光，
        整面暗screen 的亮度低於環境，眼睛會去適應房間而不是螢幕。

        ⭐ 唯一允許的深色頁是**斷語頁**（老師 2026-08-08 定義它就是「反白」），
           用 `.invert`，而且**一份投影片最多兩張**（頭一張、尾一張）——
           那條上限本來就在 `outline/版面元素.md` 第二節，剛好就是這裡要的數量限制。

     ⭐ 底下這組「可以換」，每章佈景主題會蓋掉它們。 */
  --bg:     #f4f3ef;      /* 底：微暖的米白。⚠ 不要用純白 #fff，大螢幕會刺眼 */
  --card:   #ffffff;      /* 卡片／區塊：比底再亮一階 */
  --line:   #c8c6c0;      /* 分隔線、外框（裝飾用，近看的）*/
  --dim:    #5c5c66;      /* 次要文字 5.95:1 */
  --accent: #5c5c66;      /* 章名頁的裝飾色（章名頁以外別用）*/
  /* ⭐ 強調底色（`.q code` 那種淺色塊）。2026-09-01 從各頁抽上來——**25 張寫的
     一字不差**，而且它本來就該在共用檔裡：另外 3 張投影片的註解記著
     「原本寫 `fill:var(--hl)`，而 **deck.css 沒有 `--hl`**」⇒ 它們是各自
     改寫成別的值繞過去的。⭐ 現在共用檔有了，那種繞法不必再做。
     ⚠ 值本身跟著 `--accent` 走，所以換主題會一起變。 */
  --hl: color-mix(in srgb, var(--accent) 12%, transparent);

  /* ── 字型 ───────────────────────────────────────────────── */
  /* 前三個都是使用者自行安裝的字型，學校電腦沒有 →
     由 fonts.css 的 @font-face 內嵌，見 工具/build_deck.py。
     ⚠ 三個主字型都有缺字（石峁的「峁」、日文新字體「当・観」等），
       stack 最後一定要接 Noto Serif CJK TC，讓缺字掉到一個「同樣是宋體」
       的備援，而不是掉到瀏覽器預設的黑體。
     ⛔ **不要把安裝版的英文家族名（rixingsong-1.0／jf-lanyangming-2.0／
        MetroSungPlus-TC）加回 stack。**那三個名字指向這台電腦上安裝的
        原始 .otf，是 CFF 輪廓——子集裡沒有的字一掉到它們身上，
        Chrome 就又把那些字嵌成 Type 3，等於白做。實測踩過一次。 */
  /* ⭐⭐ **每一條堆疊的最後都接 `"Jigmo2"`，這是刻意的**（2026-08-19 老師定：
       「編譯器是不是也該去自動處理需要 ext 區的字」→ 改成不用寫任何標記）。
     ⚠ Jigmo2 的子集裡**只有第 2 平面的字**（`FACES` 的 `min_cp=0x20000`），
       所以擺在最後不會搶走任何一個常用字，只在前面每一款都沒有那個字時才出手。

     ⛔⛔ **少了它不是「變成空白方框」，是 Chrome 自己去翻系統字型**——
       2026-08-19 實測：`郡和縣.html` 拿掉 `class="extb"` 再印 PDF，
       嵌進去的變成 `FAAAAA+PMingLiU-ExtB`（**微軟的字型**），有包則是
       `FAAAAA+Jigmo2`（CC0）。兩個後果都很糟而且**在這台機器上完全看不出來**：
         ① 交出去的 PDF 散布了微軟字型（`CLAUDE.md` 明文禁止）
         ② 學生的手機／Mac 沒有 PMingLiU-ExtB ⇒ 那裡才真的是空白方框
     ⭐ 所以現在**作者什麼都不用寫**，`.extb` 只留給「想強制指定」的場合。
     ⚠ 代價：Jigmo2 是**明朝體**，所以黑體段落（`--f-ui`）裡的擴充區字會是宋體。
       沒有黑體的第 2 平面字型可用，而宋體贏空框。 */
  --f-display: "jf蘭陽明體2.0","Noto Serif CJK TC","Jigmo2", serif;   /* 大標題 W6 */
  --f-head:    "鐵宋Plus","Noto Serif CJK TC","Jigmo2", serif;        /* 中標題 */
  --f-body:    "日星宋體 1.0","Noto Serif CJK TC","Jigmo2", serif;    /* 內文・引文 */
  /* ⚠ 微軟正黑體同樣**沒有打包**（不可散布），沒裝的機器會掉到 Noto 黑。
     Noto Sans CJK TC 有打包，所以放在鏈的最後才可控。 */
  --f-ui:      "微軟正黑體","Microsoft JhengHei","Noto Sans CJK TC","Jigmo2", sans-serif;  /* 註解・出處・數字 */
  /* ⭐ 老師 2026-08-12：「楷體不要，中文和漢字引文都用凝書體」。
     凝書體＝justfont creamfont（字型檔名 creamfont-3.1.otf），
     justfont 說它「融合楷體的韻味和明體的書卷氣」，比標楷體更接近內文的宋體。
   ⚠ 授權：fsType=0（Installable）→ **允許自由嵌入文件並子集化**，所以 PDF 沒問題。
     ⛔ 但條款明寫「not allowed to sell or give the font to anyone else」，
        **不可以打包成 woff2 放進 `投影片/`**——那等於把字型檔給人。
        因此網頁版在沒裝這套字的機器上會掉到備援（跟標楷體以前一樣）。 */
  /* ⚠ fallback 鏈的最後一定要接 Noto Serif CJK TC——它是**有打包**的那一個。
     少了它，沒裝凝書體／標楷體的機器會掉到系統的通用 serif（長相不可控）。 */
  --f-kai:     "凝書體 3.1","creamfont-3.1","標楷體","DFKai-SB","Noto Serif CJK TC","Jigmo2", serif;   /* 漢文引文 */
  /* ⭐⭐ 擴充區的字（CJK Ext-B 以上，U+20000 起）：`--f-extb`
     ⭐ 老師 2026-08-18 定案：**「我的中文 ext 區就是 Jigmo2」**。
     ⚠ 專案原本打包的十款字型**一個擴充區的字都沒有**（Noto CJK 收到 Ext-A 為止），
       所以講文字學、抄《說文》的頁一定會撞到——不處理就是一個空白方框。

     ⭐⭐ **Jigmo2 是打包進 fonts/ 的**（`FACES` 裡 `optional=True` ＋ `min_cp=0x20000`），
       所以**手機、平板、Mac、學生的任何機器都看得到**，PDF 也完整——
       不必依賴「這台剛好裝了什麼」。授權 **CC0 1.0**（比 OFL 還鬆，可自由散布）。
       只打包投影片真的用到的字：目前整門課一個 𥄉，1,276 bytes（實測）。
       ⚠ 作者 Koichi Kamichi ＝ GlyphWiki／花園明朝那個人，Jigmo 是花園明朝的後繼
       （花園 2017 之後停更）。⭐ 2026-08-18 之前這裡是花園明朝，已經整個換掉，
       連來源檔都刪了——**不要再把 HanaMinB／Hanazono Mincho 加回來**。

     ⭐ 後面那兩個是**沒打包時的救生索**（來源檔沒帶到、`deck fonts` 跳過了）：
       **細明體 Ext-B**，Windows 內建。實測 mingliub.ttc 四款全部含 U+25309 𥄉。
       ⛔ **不可以只寫「細明體」**——`MingLiU`（沒有 `-ExtB`）一個擴充區的字都沒有。
       ⚠ 落到這一層的話**手機上仍然是空框**（Android／iOS 兩個都沒有）。 */
  --f-extb:    "Jigmo2","MingLiU-ExtB","PMingLiU-ExtB", serif;

  /* 日／韓／簡：統一用 Noto Serif CJK，同一套設計橫跨 CJK，混排不會有一塊胖一塊瘦。
     ⚠ 微軟的 MS Mincho / Batang 這台沒裝，不要寫進 stack。 */
  --f-ja:      "Noto Serif CJK JP","Jigmo2", serif;
  --f-ko:      "Noto Serif CJK KR","Jigmo2", serif;
  --f-sc:      "Noto Serif CJK SC","Jigmo2", serif;

  /* ⭐⭐ 古文字（甲骨）：`--f-gu`（老師 2026-08-23 定）
     ⛔⛔⛔ **這一條絕對不可以接進上面任何一條 stack 的 fallback。**
       Oracular 的甲骨「田」就掛在 U+7530——跟一般的「田」同一個碼位。
       接進去的話**整份投影片的田／男／王都會變成甲骨文**，
       而這台機器上看起來只是「字型怪怪的」，⛔ 不會報錯、不會有紅字。
     ⭐ 它只給 `.gu` 用，而 `.gu` 是作者**明確標記**才會有的。
     ⭐ 第二道防線在 `build_deck.py`：Oracular 的子集裡**只有 `.gu` 標過的字**
       （`only_class="gu"`），所以就算這裡寫錯，別的字也渲染不出甲骨形。
     ⚠ fallback 接 `--f-body` 的那一串：Oracular 沒有的字（例如「稅」，
       甲骨文本來就沒有這個字）會掉回一般宋體，**看得出來是楷書**，
       這正是我們要的——⛔ 缺甲骨字形不可以靜靜變成別的東西。 */
  --f-gu:      "Oracular","日星宋體 1.0","Noto Serif CJK TC","Jigmo2", serif;

  /* ⭐⭐ 小篆：`--f-zhuan`（老師 2026-08-24 定）
     ⛔⛔ **跟 `--f-gu` 同一條規矩：絕對不可以接進上面任何一條 stack 的 fallback。**
       崇羲的篆書「稅」也掛在 U+7A05，接進去整份投影片的字都會變篆書。
     ⭐ 它只給 `.zhuan` 用，子集裡也只有 `.zhuan` 標過的字（`only_class="zhuan"`）。
     ⚠ class 取 `zhuan` 不取 `zh`——**`.zh` 在本檔已經用掉 6 處**。
     ⚠⚠ **授權要求「表彰原作者姓名」** ⇒ ⛔ 用到這個 class 的頁，
       **info drawer 一定要標**：崇羲篆體／王心怡・季旭昇／中研院小學堂／CC-BY-ND-3.0-TW。
       ⭐ 老師 2026-08-24 定：**Oracular 也一樣要標**（OFL 雖不強制，但一併標）。 */
  --f-zhuan:   "ChongXi Seal","日星宋體 1.0","Noto Serif CJK TC","Jigmo2", serif;

  /* ── 字級（% of 畫面高 → 印出來的 px）──────────────────────
     ⛔ 這幾個值不隨佈景主題改。後排讀不讀得到跟主題無關。 */
  --t-display: 8.5cqh;   /* 61px  章名・段名 */
  --t-h1:      5.5cqh;   /* 40px  頁標題 */
  --t-h2:      4.0cqh;   /* 29px  子標 */
  --t-quote:   3.8cqh;   /* 27px  課本逐字引文（比內文大，因為它是主角）*/
  --t-body:    3.5cqh;   /* 25px  內文 */
  --t-min:     3.2cqh;   /* 23px  ⚠ 學生必須讀到的字，一律不得小於此 */
  --t-note:    2.6cqh;   /* 19px  註解・出處（不要求後排讀到）*/
  --t-cap:     2.4cqh;   /* 17px  圖說 ⚠ 絕對下限，再小就不要放 */

  /* ── 線寬（2026-08-10 建立）─────────────────────────────────
     ⛔ 一律用 cqh，不要用 px。理由跟字級同一個：
        `1px` 是「邏輯畫布 1280×720 的一格」，跟真正投出來多粗無關。

     ⚠ **1px 在 1080p 面板上最糟**：1280→1920 是 1.5 倍，一條 1px 的線
        變成 1.5 個實體像素，被抗鋸齒抹成一條半透明的灰霧；
        4K 面板是剛好 3 倍反而銳利。**我們不知道教室那台是哪種，就照最糟的設計。**

     下面的 mm 是換算到 **70 吋螢幕（畫面高 87.2 cm）** 的實際粗細，
     以及在 5.6 公尺處的視角（3 弧分以上才看得舒服）。 */
  --w-hair:   0.28cqh;  /* 2.0px　2.4mm　1.5′　次要格線、刻度導線 ⚠ 絕對下限 */
  --w-line:   0.40cqh;  /* 2.9px　3.5mm　2.1′　一般分隔線、卡片外框 */
  --w-strong: 0.60cqh;  /* 4.3px　5.2mm　3.2′　軸線、引文左標線、要學生看見的框 */

  /* ⚠ 線的**顏色**比粗細更關鍵。--line 對底只有 1.73:1，
     近看沒問題，投在教室後排會直接消失。
     結構線（軸線、外框、要學生看見的邊界）改用 --line-strong（3.06:1，
     過 WCAG 對圖形元件的 3:1）。--line 留給近看的裝飾框。 */
  --line-strong: #8a8880;

  /* ── 行高・字距 ─────────────────────────────────────────── */
  --lh-body:  1.75;    /* 中文需要比西文寬的行距 */
  --lh-head:  1.35;
  --ls:       .02em;   /* 投影距離下略開字距較好認 */
}

/* ===== 畫布 ============================================================== */
*{ box-sizing:border-box; margin:0; padding:0 }

html{ background:var(--bg) }
body{
  background:var(--bg); color:var(--ink);
  font-family:var(--f-body);
  line-height:var(--lh-body); letter-spacing:var(--ls);
  -webkit-print-color-adjust:exact; print-color-adjust:exact;
}

/* 單頁預覽用：維持 16:9、置中、兩側留黑。
   container-type:size 是字級 cqh 的參照點——拿掉字就不會縮放了。 */
.slide{
  position:relative; width:100vw; height:100vh;
  max-width:calc(100vh * 16 / 9); max-height:calc(100vw * 9 / 16);
  margin:0 auto; background:var(--bg); overflow:hidden;
  container-type:size;
}
.safe{ position:absolute; inset:var(--safe); }

/* ===== 字級 class ======================================================== */
.display{ font-family:var(--f-display); font-weight:600;
          font-size:var(--t-display); line-height:var(--lh-head) }
.h1{ font-family:var(--f-head); font-size:var(--t-h1); line-height:var(--lh-head) }
/* ⚠ 子標舊版是 color:var(--hi)（黃字）。淺底上黃字只有 1.25:1，讀不到。
   改用 --accent（章節色），需要更醒目就用螢光筆 <em> 包起來。 */
.h2{ font-family:var(--f-head); font-size:var(--t-h2); line-height:var(--lh-head);
     color:var(--accent) }
.body{ font-size:var(--t-body) }
.note{ font-family:var(--f-ui); font-size:var(--t-note); color:var(--dim) }
.cap{  font-family:var(--f-ui); font-size:var(--t-cap);  color:var(--dim) }

/* ⭐ 裝飾性文字：**不受字級下限限制**，大小由該頁自己決定（老師定，2026-08-10）。
   下限存在的理由是「最後一排要讀得到」，所以只管**要學生讀的字**。
   有些字放上去不是給人讀的，是讓版面「像真的」——
   例：SNS 卡片上的時間戳。拿掉它那張卡就不像 X 的貼文了，
   但沒有人需要從最後一排讀出「12:00」。

   ⚠ 判準（免得什麼字都自稱裝飾）：
      **把這個字整個拿掉，這頁還講得下去嗎？講得下去，才是裝飾。**
      它一旦承載了學生需要的資訊，就不是裝飾，要拉回 3.2%（--t-min）以上。
   ⚠ 裝飾字不可以用黃或藍——那兩色是語意色，會讓學生以為它有意思。
   ⚠ 可以小到看不清楚，但不可以小到變成髒點。看起來像雜訊就是太小了。 */
.deco{ font-family:var(--f-ui); color:var(--dim); letter-spacing:0 }

/* 課本逐字引文：比內文大、左側黃線、必附印刷頁碼 */
.quote{
  font-size:var(--t-quote); border-left:var(--w-strong) solid var(--hi-edge);
  padding-left:.7em; margin:.6em 0;
}
.quote .src{ display:block; font-family:var(--f-ui); font-size:var(--t-note);
             color:var(--dim); margin-top:.45em; letter-spacing:0 }

/* ===== 強調 ============================================================== */
/* 黃＝原文／關鍵詞　藍＝翻譯或我們加的話。兩色不可混用。 */
/* 螢光筆：淺底上就是真的螢光筆——整塊塗滿，字維持深色。
   ⚠ 不要改成黃色文字。黃字在淺底上讀不到。 */
/* ⚠ 螢光筆的**字色不可以跟著底色翻**。填色永遠是黃的，所以字永遠要深。
   實測踩過：`.invert` 斷語頁上 color:var(--ink) 會變成亮字疊在黃塊上＝1.2:1，看不見。 */
/* ⚠ 螢幕上這三個顏色會被「反著寫的那一份」蓋掉（見下面〈螢光筆的強制深色對策〉，
   在語意色守門那塊後面）。這裡是**基準**：列印、以及不支援相對色語法的瀏覽器吃的就是它。 */
em{ font-style:normal; color:#1a1a1f;
    background:var(--hi); box-shadow:0 0 0 .12em var(--hi);
    border-bottom:var(--w-hair) solid var(--hi-edge) }
.zh{ color:var(--tr) }                       /* 翻譯 */
/* ⭐ 螢光筆長在翻譯裡：字改用翻譯藍，「藍＝翻譯」在螢光筆裡面也還成立
   （老師實測：深藍 #0f5f8f 疊黃 #ffd84d ＝ 4.97:1，過 AA 的 4.5）。
   ⚠ 2026-08-13 從單頁 CSS（封面_SNS回覆.html）搬上來的。單頁不可以自己寫，
     因為網頁版會對 `em` 套 filter，顏色必須先反寫——那是這一層的事。 */
.zh em{ color:var(--tr) }
.dot b{ font-weight:inherit;                 /* 圈點（傍点）*/
        text-emphasis:filled dot var(--hi-edge);
        -webkit-text-emphasis:filled dot var(--hi-edge);
        text-emphasis-position:over right }
.warn{ color:var(--warn) }

/* ⚠ <code> 一定要指定字型。預設的 monospace 只管西文，中文會掉到
   Chrome 的 CJK 備援（這台是 Noto Sans TC 的**可變**版）——可變字型
   會被嵌成 Type 3，檔案腫、小字糊。實測踩過一次。 */
code,kbd,samp{ font-family:"Consolas",var(--f-ui),monospace; letter-spacing:0 }

/* ⛔ 三個宋體都只有一個字重，沒有真正的粗體。
   對它們寫 font-weight:700 或用 <b> 包起來，Chrome 會**自己畫一個假粗體**，
   而假粗體同樣會被嵌成 Type 3。要強調就用黃螢光筆 <em> 或圈點 .dot，
   不要加粗。（--f-ui 微軟正黑體有真的粗體，加粗沒問題。）*/

/* ===== 語言切換：各語言各自的宋體，字面對齊 ============================= */
:lang(ja){ font-family:var(--f-ja) }
:lang(ko){ font-family:var(--f-ko) }
:lang(zh-CN),:lang(zh-Hans){ font-family:var(--f-sc) }

/* ===== 直書（漢文引文用；橫書引文也吃 --f-kai）================================================ */
.vert{ writing-mode:vertical-rl; font-family:var(--f-kai);
       line-height:2.1; border-right:var(--w-strong) solid var(--hi-edge); padding-right:.6em }
.vert em{ color:#1a1a1f; box-shadow:0 0 0 .12em var(--hi);
          border-bottom:0; border-right:var(--w-hair) solid var(--hi-edge) }

/* ===== ruby（注音／振り仮名／韓文讀音）================================= */
ruby{ ruby-position:over }
rt{ color:var(--tr); font-size:.42em; letter-spacing:.05em }

/* ===========================================================================
   ⭐ 每章佈景主題
   ---------------------------------------------------------------------------
   用法：在該章的頁面最上面寫一行
       <body data-theme="ch3">
   （寫在 <html data-theme="ch3"> 也一樣有效。沒寫就是預設的深灰。）

   ⛔ 主題只能動這六個：--bg／--card／--line／--line-strong／--dim／--accent，
      外加該章大標題想換字型的話 --f-display。
      **字級與語意色不在裡面**——字級穩定是這疊投影片可讀性的底線，
      理由見本檔最後一段。

   ⚠⚠ 調主題色時有五件事會默默出包，改完務必跑一次：
           python3 工具/build_deck.py theme
       它會把每個主題的對比度算出來，不合格的直接列出來。

   一、**底色不能變亮。** 黃 #ffd84d 和藍 #7fd4ff 是固定的，
       底色一亮，這兩個顏色就浮不出來——而它們是「原文／翻譯」的分辨依據。
   二、**底色不要偏黃或偏藍。** 偏黃的底配黃螢光筆，等於沒標；偏藍同理。
       想做暖色章（例如第2章）就往紅褐走，不要往黃走。
   三、**--card 與 --bg 的差距要夠大。** 教室投影機的暗部對比遠差於螢幕，
       螢幕上分得出來的兩階深灰，投出來會糊成同一塊，卡片邊界就消失了。
   四、**--dim 是註解與圖說的顏色，不要跟著底色一起壓暗。**
       它已經是全頁最小的字（2.4–2.6%），再降對比就真的看不到了。
   五、**照片頁不吃主題。** 馬賽克頁與 SNS 頁的照片底是純黑，
       那是為了不讓底色染到照片。換主題時那兩頁會跟其他頁不同調，
       這是刻意的，不是漏改。
   =========================================================================== */

/* ⚠ 底下六組是 Claude 配的**預設值，不是定案**，老師可以整組換掉。
   改完跑 `python3 工具/build_deck.py theme` 驗一次就好。

   ⭐ 這裡有一個不明顯的限制，是檢查器算出來的：
      **裝飾色 --accent 不能用黃系（約 48°）也不能用藍系（約 197°）**，
      因為那兩個色相已經被「原文」與「翻譯」佔走了。
      裝飾色一旦落在附近，學生會以為那個顏色也有意思。
      所以六章分別走 靛／褐／綠／紫／洋紅／紅，剛好繞開黃藍。 */

[data-theme="ch1"]{            /* 第1章 國家的統治　靛 */
  --bg:#f0f2f6; --card:#ffffff; --line:#c3c8d2; --line-strong:#838a99; --dim:#575d68; --accent:#4a4ac0;
}
[data-theme="ch2"]{            /* 第2章 社會組織　　褐 */
  --bg:#f6f2ee; --card:#ffffff; --line:#d2c8bd; --line-strong:#998a7a; --dim:#665c52; --accent:#8f5330;
}
[data-theme="ch3"]{            /* 第3章 近代以前人群移動　綠 */
  --bg:#eff5f1; --card:#ffffff; --line:#c2d2c8; --line-strong:#788f82; --dim:#52665a; --accent:#2f8a5c;
}
[data-theme="ch4"]{            /* 第4章 近代以後人群移動　紫 */
  --bg:#f3f0f6; --card:#ffffff; --line:#cdc4d4; --line-strong:#8d829b; --dim:#5e5468; --accent:#7c4aa0;
}
[data-theme="ch5"]{            /* 第5章 傳統與現代的交會　洋紅 */
  --bg:#f7f0f4; --card:#ffffff; --line:#d4c2cd; --line-strong:#9b8292; --dim:#685260; --accent:#a63472;
}
[data-theme="ch6"]{            /* 第6章 戰爭與和平　　紅 */
  --bg:#f7f0f0; --card:#ffffff; --line:#d6c3c3; --line-strong:#9c8383; --dim:#685252; --accent:#b03038;
}

/* ══════════════════════════════════════════════════════════════════
   ⭐⭐ 技術教學型專用主題：老式終端機　`data-theme="term"`（老師 2026-08-11）

   > 老師：「這類技術教學型的投影片都應該用新的佈景主題，我想給他的主題是
   >        **老式終端機，淡白底加上英文等寬黑字**」
   > 字型由老師定：「技術教學的字體全部都改用黑體，noto 黑就可以了，
   >                不過**英文用 ibm plex**，這好像也可以當作終端機字體」

   ⭐ 這是**第一個會換字型的主題**。在它之前，主題只換那六個顏色。
   ⭐ 用法：該頁寫 `<body data-theme="term">`。
      編譯器會把它搬到 `<section data-theme="term">`，所以**單張 HTML、PDF、
      編譯後的網頁版三處一致**。⛔ 不要只寫在某個內層 div 上，PDF 會對、網頁會歪。

   ⭐⭐ **這個主題底下 `<b>` 是安全的。**全專案那條「⛔ 宋體不要加粗」的理由是
      三個宋體只有一個字重、Chrome 會畫假粗體 → Type 3。
      IBM Plex Mono 與 Noto Sans CJK **都有真的粗體**，所以這裡加粗沒有那個問題。

   ⚠ **字元格不是 2:1。**IBM Plex Mono 拉丁 0.60em、Noto Sans CJK 漢字 1.00em，
      比是 1.67。所以**方框線一律用 CSS border 畫，不可以用 `┌─┐` 這種字元排**——
      中英混排一定歪，而且是投出來才發現。

   ⚠ 顏色只有石墨灰可用：黃被「課本原文」佔走、藍被「翻譯」佔走、
      綠被「交流道」佔走、琥珀屬於黃系。**而這剛好就是老師要的「黑字」。**
   ══════════════════════════════════════════════════════════════════ */
[data-theme="term"]{
  /* ⚠⚠ 2026-08-12 重調。**舊值 #f7f7f5 對預設只差 1.035×，肉眼分不出來**——
     等於整個主題只剩「宋體換黑體」，老師一眼就看穿了。
     ⛔ 教訓：`theme` 檢查器擋的是「主題偷偷翻成深色」，**不是要主題長得像預設**。
        「檢查穩過」不能拿來當配色做對了的證據。 */
  --bg:#e7e9e3;          /* 灰綠紙。跟預設差 1.10×，看得出來；內文仍有 14.2:1 */
  --card:#f7f8f5;        /* 比底亮一階（1.19×），區塊才浮得起來 */
  --line:#b4b4ae;        /* ⚠ 舊值 #cfcfca 對底只有 1.09×，投出去等於沒有這條線 */
  --line-strong:#5f625c;
  --dim:#4e514c; --accent:#2f322d;      /* 石墨，無彩度 */

  /* ⭐ 行距與字距也換——終端機是密的，書才是鬆的。
     ⚠ 這兩個不在「六個顏色」裡，但也不在 LOCKED（字級與語意色）裡，可以改。 */
  --lh-body:1.55;
  --ls:0;

  --f-display:"IBM Plex Mono","Noto Sans CJK TC","Jigmo2",monospace;
  --f-head:   "IBM Plex Mono","Noto Sans CJK TC","Jigmo2",monospace;
  --f-body:   "IBM Plex Mono","Noto Sans CJK TC","Jigmo2",monospace;
  --f-ui:     "IBM Plex Mono","Noto Sans CJK TC","Jigmo2",monospace;
  --f-kai:    "IBM Plex Mono","Noto Sans CJK TC","Jigmo2",monospace;
  --f-ja:     "IBM Plex Mono","Noto Sans CJK JP","Jigmo2",monospace;
  /* ⭐ 韓／簡故意接 TC，不另外打包 KR、SC 兩個子集。
     Noto Sans CJK 各地區版**收的字是同一套**（TC 版也有 11172 個諺文、189 個假名），
     差別只在漢字取哪個地區的字形。技術教學目前沒有韓文與簡體，
     真的加了字，下次跑 `fonts` 會自動把它收進 TC 的子集裡——**不會靜靜掉字型**。 */
  --f-ko:     "IBM Plex Mono","Noto Sans CJK TC","Jigmo2",monospace;
  --f-sc:     "IBM Plex Mono","Noto Sans CJK TC","Jigmo2",monospace;
}

/* ⛔ `<code>` 預設 stack 的第一順位是 Consolas（沒有打包，靠這台剛好有裝）。
   在終端機主題底下整份本來就是等寬，`<code>` 直接吃主題字型就好。 */
:is([data-theme="term"],[data-theme="crt"]) code,
:is([data-theme="term"],[data-theme="crt"]) kbd,
:is([data-theme="term"],[data-theme="crt"]) samp{ font-family:var(--f-ui) }

/* ══════════════════════════════════════════════════════════════════
   ⭐⭐ nano 版型　`.nano-top` ／ `.nano-status` ／ `.nano-keys`（老師 2026-08-12）

   > 老師看了線上版：「你的終端機佈景主題怎麼就像是把原本的明體改成黑體而已」
   > → 先加了一條標題列，老師再說：「**其實我覺得是整個版型的問題，你應該考慮用
   >   nano 的排版方式，這樣路徑和頁碼都可以按照 nano 的模式來改**」

   ⭐ **為什麼 nano 有效**：中文在任何字型底下都是等寬全形，「終端機感」在 CJK 上
      **拿不到字型紅利**。nano 的辨識度不在字型，在那三塊固定的框——
      反白標題列、置中的方括號狀態列、底下兩排快捷鍵。**一眼就認得出來。**

   ⭐ 三塊各自對上我們本來就要的東西，所以不是純裝飾：
        nano 的檔名     → 這一張的名字（本來的 .h1，拿掉不重複）
        nano 的狀態列   → 頁碼 `[ 第 8 張，共 13 張 ]`
        nano 的快捷鍵列 → RAG 四步 `^1 概覽 ^2 提取 ^3 分析 ^4 驗證`，當前反白
                          ＋ 這一張真的有的功能（`^G 補充` 開抽屜、`^R 回緒論` 走交流道）

   ⚠ **反白條是刻意的例外。**「⛔ 不要黑底白字」講的是**整頁**深底（理由是宋體細橫畫
      在大螢幕上會暈開）——這裡是幾條 17px 高的橫帶、而且字是等寬黑體，不適用。
      ⛔ 但**只有這三塊**可以反白，內容區一律淺底深字。

   ⛔ **不放裝飾字。**早期版本左邊寫 `GNU nano 8.1`、右邊寫 `Modified`，已經拿掉——
      老師 2026-08-12：「**學生看不懂也不好玩**」。
   ⭐ **左邊那一格改成「史料分析練習」**（老師 2026-08-12）。
      nano 的左上角本來就是「你在哪個程式裡」；換成「你在做什麼」，
      每一張都在提醒學生這節課的性質——比版本號有用。
      ⚠ 這是**整份固定的標籤**，不是每張各自的字；每張自己的名字在中間。

   寫法：
       <div class="nano-top">拿掉無用的資料</div>
       <div class="slide-body"> …內容… </div>
       <div class="nano-foot">
         <div class="nano-status">第 8 張，共 13 張</div>
         <div class="nano-keys">
           <b class="now">^2</b>提取 …
         </div>
       </div>
   ══════════════════════════════════════════════════════════════════ */
[data-theme="term"]{
  /* nano 那三塊的灰階。⛔ 老師 2026-08-12：「**其實不用反白條啊，用灰色底＋黑色即可**」 */
  --nano-bar:#d3d6cf;    /* 條的底，對頁底 1.20×，黑字 11.8:1 */
  --nano-now:#a8aba3;    /* 當前步驟，對條再深 1.58×，黑字 7.4:1 */
  --pen:#c3c7bb;         /* ⭐ 螢光筆的填色，對頁底 1.45×、黑字 9.7:1（見下面〈終端機的螢光筆〉）*/
}

[data-theme="term"] .nano-top{
  position:absolute; left:0; right:0; top:0;
  padding:.8cqh 4.5%;
  background:var(--nano-bar); color:var(--ink);
  font-family:var(--f-ui); font-size:var(--t-cap); letter-spacing:.04em;
  /* ⭐ 三欄格：左標籤／置中的名字／右邊留空。
     ⚠ 用 grid 不用 flex——flex 只有兩個項目時，中間那個會在「剩下的空間」
       置中，看起來偏右。grid 的 1fr auto 1fr 才是**對整條**置中。 */
  display:grid; grid-template-columns:1fr auto 1fr; align-items:baseline; gap:1em;
}
[data-theme="term"] .nano-top > i{
  font-style:normal; justify-self:start; color:var(--dim);
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
[data-theme="term"] .nano-top > span{
  justify-self:center; font-weight:700;
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap; max-width:100%;
}
/* ⛔ 老師 2026-08-12：「**不用裝飾！那邊寫這個投影片的名稱**」
   ——所以沒有 `GNU nano 8.1`、也沒有 `Modified`，只有這一張的名字。 */

[data-theme="term"] .nano-foot{
  position:absolute; left:0; right:0; bottom:0;
  font-family:var(--f-ui); font-size:var(--t-cap); letter-spacing:.04em;
}
/* 狀態列：nano 的訊息是置中、包在方括號裡 */
[data-theme="term"] .nano-status{
  text-align:center; color:var(--dim); padding:.5cqh 0 .9cqh;
}
/* ⚠ 沒有快捷鍵的那幾張（封面、學習目標）不要留一條空灰帶——
   那一張直接不要寫 `.nano-keys`，狀態列自己補一點底部留白。 */
[data-theme="term"] .nano-status:last-child{ padding-bottom:1.8cqh }
[data-theme="term"] .nano-status::before{ content:"[ " }
[data-theme="term"] .nano-status::after{ content:" ]" }

/* 快捷鍵列。⛔ 老師 2026-08-12：「**不用快捷鍵第二排**」——只有一排。 */
[data-theme="term"] .nano-keys{
  display:flex; flex-wrap:wrap; gap:.35em 2.1em;
  padding:.6cqh 4.5% 1.0cqh; color:var(--ink);
  background:var(--nano-bar);
}
[data-theme="term"] .nano-keys > span,
[data-theme="term"] .nano-keys > button{
  display:flex; align-items:baseline; gap:.45em;
  border:0; background:none; padding:0; margin:0;
  font:inherit; color:inherit; letter-spacing:inherit;
}
/* ⭐ 能開抽屜的那幾格是真的按鈕——nano 底下那排本來就是「可以按的東西」。
   所以各頁不用再放一顆 ⓘ。⚠ 列印時按鈕照樣印出來，等於幫 PDF 標出抽屜有哪些。 */
[data-theme="term"] .nano-keys > button{ cursor:pointer }
[data-theme="term"] .nano-keys > button:hover b,
[data-theme="term"] .nano-keys > button:focus-visible b{ background:var(--nano-now) }
[data-theme="term"] .nano-keys b{
  font-weight:700; padding:.05em .4em;
  background:var(--bg); color:var(--ink);
}
/* 當前步驟：比條再深一階的灰，字仍然是黑的 */
[data-theme="term"] .nano-keys > span.now{
  background:var(--nano-now); padding:.05em .5em; margin:-.05em -.1em;
}
[data-theme="term"] .nano-keys > span.now b{ background:var(--bg) }

/* 內容區：夾在標題列與底下那組之間。各頁用它定位，不必自己算 top/bottom。 */
/* ⚠ 這兩個數字是量出來的：三塊 chrome 合計約 85px＝11.8%（標題列 4.0、
   狀態列 3.75、快捷鍵列 4.0）。留一點餘裕給行高，抓 6%／13%。
   ⛔ 各頁不要自己算 top/bottom，用這個容器。 */
[data-theme="term"] .slide-body{
  position:absolute; left:4.5%; right:4.5%; top:6%; bottom:13%;
}

/* ══════════════════════════════════════════════════════════════════
   終端機的螢光筆（`term` 與 `crt` 共用）

   ⛔⛔ 技術教學不用黃螢光筆（老師 2026-08-12：「技術教學根本用不到黃色螢光筆，
   真的有必要，打底線即可」）。
   ⭐ 這樣做**同時保護了語意色**：黃色在歷史那份專屬「課本原文」，
      不會因為技術這份也用而被稀釋。
   ⚠ 這裡改的是 `em` 的**樣式**，不是 `--hi` 那個變數——語意色守門仍然成立。

   ⭐⭐ 2026-09-03 老師：「我們要幫 nano 設計一種螢光筆機制，**用底線＋noto 的粗體**
      好了」，看過只有底線的版本之後補一句：「**em 好啊，但是我覺得要讓他更螢光筆**」
      ⇒ 三樣一起：**灰色填色（筆畫）＋ 下緣一條線（筆跡邊）＋ 粗體**。

   ⚠ **顏色只有石墨灰可用**：黃被「課本原文」佔走、藍被「翻譯」、綠被「交流道」。
      所以填色 `--pen` 是**比頁底深 1.45× 的灰**，靠明度不靠彩度。

   ⛔ **粗體要寫 700，⛔ 但那不是假粗體。**打包的是 `Noto Sans CJK TC`／
      `IBM Plex Mono` 的 **SemiBold（600）**，`@font-face` 宣告的是 `600 700`
      ⇒ 700 會對到真的那一張字面。⚠ 如果哪天字型清單少了 600 那兩個檔，
      Chrome 會自己畫假粗體，而假粗體在 PDF 裡會被嵌成 Type 3（檔案腫、小字糊）。

   ⭐ **不會撐爆版面**：CJK 在 Noto Sans CJK 是全形等寬、西文在 Plex Mono 是等寬，
      **粗體跟一般體的字寬一樣** ⇒ 換行位置不變。（2026-09-03 量過 tech101 十三張。）

   ⚠ 強制深色下**不必特別處理**：`--pen` 是灰的，跟頁底一起被翻，
      翻完仍然是「一塊跟頁底不同明度的帶子＋反差夠的字」。
      ⛔ 黃色不行才要下面那套 filter 對策——那是**顏色**壞掉，不是明度壞掉。
   ══════════════════════════════════════════════════════════════════ */
:is([data-theme="term"],[data-theme="crt"]) em{
  color:var(--ink); font-weight:700;
  background:var(--pen);
  box-shadow:0 0 0 .12em var(--pen);
  border-bottom:var(--w-strong) solid var(--line-strong);
  padding-bottom:.06em;
}

/* ══════════════════════════════════════════════════════════════════
   介面示意圖 `.app`（inline SVG）　2026-08-13 從各頁的 <style> 搬上來

   老師 2026-08-12：「不用等截圖」——所以介面是**畫**的，不是螢幕截圖。
   ⛔ **示意不是截圖**：不畫按鈕字樣、模型名稱、版本號，那些一換學期就過期。
   ⚠ SVG 要吃 flex 的尺寸，不然會用固有大小溢出（實測踩過）。
     ⭐ 尺寸（`flex`／`width`／`height`／`font-size`）留在各頁自己寫，
        因為每頁的版面不一樣；**顏色一律寫在這裡**。

   ⛔⛔ 為什麼一定要搬上來（2026-08-13）：這組規則本來在 4 頁各抄一份，
      而下面〈強制深色〉那半段**只寫一份是不夠的**——單頁 CSS 編譯後帶
      `#sNN` 前綴，特異度 (1,1,1) 永遠贏過這裡，於是「補了深色方案卻沒有生效」，
      而且只有在別人開了強制深色的手機上才看得出來。
   =================================================================== */
[data-theme="term"] .app{ display:block }
/* 外框線：`fill:none` ＋ 深色的線 */
[data-theme="term"] .app .fr rect,
[data-theme="term"] .app .fr line,
[data-theme="term"] .app line{ fill:none; stroke:var(--line-strong);
  stroke-width:1.6; vector-effect:non-scaling-stroke }
/* 面板與分層（全部是**亮的**填色，見下面的深色方案） */
[data-theme="term"] .app rect{ fill:var(--card) }
[data-theme="term"] .app rect.bar,
[data-theme="term"] .app rect.you{ fill:var(--nano-bar); stroke:none }
[data-theme="term"] .app rect.hi{ fill:var(--nano-now); stroke:none }
[data-theme="term"] .app rect.code,
[data-theme="term"] .app rect.side{ fill:var(--bg); stroke:none }
/* 字（暗的） */
[data-theme="term"] .app .t{ font-family:var(--f-ui); fill:var(--ink) }
[data-theme="term"] .app .t.s,
[data-theme="term"] .app .t.d{ fill:var(--dim) }
[data-theme="term"] .app .t.c{ text-anchor:middle }
/* 步驟編號：深色圓 ＋ 亮字 */
[data-theme="term"] .app .no circle{ fill:var(--accent); stroke:none }
[data-theme="term"] .app .no text{ font-family:var(--f-ui); font-weight:700;
  fill:var(--bg); text-anchor:middle }

/* ── `.app` 的深色方案（老師 2026-08-13 回報：強制深色下是灰底白字）──
   ⭐ 這裡要的**跟螢光筆相反**：螢光筆要「不准變」，示意圖要「**跟著頁面一起變深**」
      （老師：「讓他的 svg 繪圖區在深色下得是深灰底白字」）。

   規則只有一條，因為 SVG 的 `fill` 走的是「文字」那條門檻（暗的才翻，150）：
       · **暗的**東西（外框線、字、步驟圓）→ ⭐ 什麼都不用做，強制深色自己翻成亮
       · **亮的**填色（--card 247、--bg 231、--nano-bar 212、--nano-now 169）
         → ⛔ 它不會被翻，得我們自己來：**反寫 ＋ `filter:invert(1)`**
           （反寫後變暗 → 強制深色把它翻亮 → 我們的 filter 再翻回暗 ＝ 深底）
   實測：正常模式跟現在一模一樣；強制深色下面板 → rgb(0,0,0) 深底，字仍是白的。
   ⚠ `.fr` 那組要 `filter:none`：它的線是**暗的**，套下去反而會在深色底上消失。 */
@media screen{
  @supports (color: rgb(from red r g b)){
    [data-theme="term"] .app rect{
      fill:rgb(from var(--card) calc(255 - r) calc(255 - g) calc(255 - b));
      filter:invert(1) }
    [data-theme="term"] .app rect.bar,
    [data-theme="term"] .app rect.you{
      fill:rgb(from var(--nano-bar) calc(255 - r) calc(255 - g) calc(255 - b)) }
    [data-theme="term"] .app rect.hi{
      fill:rgb(from var(--nano-now) calc(255 - r) calc(255 - g) calc(255 - b)) }
    [data-theme="term"] .app rect.code,
    [data-theme="term"] .app rect.side{
      fill:rgb(from var(--bg) calc(255 - r) calc(255 - g) calc(255 - b)) }
    /* ⛔ 外框線是**暗的**，不可以跟著翻（翻了會在深色底上消失）。
       ⚠⚠ 這裡一定要 `:not([class])`。示意圖的標記規律是
           **沒有 class 的 `<rect>` ＝ 外框（`fill:none`），有 class 的 ＝ 填色**，
           而填色的那幾個（`.bar`／`.side`／`.code`）**就放在 `<g class="fr">` 裡面**。
       少了 `:not([class])` 就會把它們一起豁免掉，於是：正常模式下面板變成深的、
       強制深色下反而變回亮的——2026-08-13 就是這樣錯過一次。 */
    [data-theme="term"] .app .fr rect:not([class]){ filter:none }
    /* 步驟編號的字是**亮的**（--bg），跟面板同一類，要自己翻；
       圓圈是暗的 accent，強制深色會自己把它翻亮，剛好還是「深字亮圓」。 */
    [data-theme="term"] .app .no text{
      fill:rgb(from var(--bg) calc(255 - r) calc(255 - g) calc(255 - b));
      filter:invert(1) }
  }
}

/* ── 終端機的版面語彙（老師 2026-08-11 選定「加上版面語彙」）──────────
   ⭐ 全部只在 term 主題底下生效，其他章一個字都不受影響。
   ⭐ 提示符是用 `::before` 畫上去的，**投影片裡的文字一個字都不動**——
      所以老師逐字給的 prompt 保持原樣，`順序.md` 也不會被 `$` 污染。 */

/* ⛔ 直角。終端機沒有圓角——各頁自己寫的 border-radius 也要拿掉，
   頁內選擇器（編譯後是 `#sNN .x`）的優先度比這裡高，這條蓋不掉它。 */
:is([data-theme="term"],[data-theme="crt"]) .t-box, :is([data-theme="term"],[data-theme="crt"]) .t-cmd{ border-radius:0 }

/* ── 提示符 ────────────────────────────────────────────────────
   ⭐ `$` 和 `>` 是 `::before` 畫上去的，**投影片裡的文字一個字都不動**。
      所以老師逐字給的 prompt 保持原樣，`順序.md` 也不會被符號污染。
   ⛔ 內容要包成一個元素（`<span>`／`<div>`），不要直接放裸文字——
      裸文字在 flex 裡是匿名項目，換行與縮排會不受控。 */
:is([data-theme="term"],[data-theme="crt"]) .t-sh,
:is([data-theme="term"],[data-theme="crt"]) .t-cmd,
:is([data-theme="term"],[data-theme="crt"]) .t-out{ display:flex; align-items:baseline; gap:.5em }
:is([data-theme="term"],[data-theme="crt"]) .t-sh > *,
:is([data-theme="term"],[data-theme="crt"]) .t-cmd > *,
:is([data-theme="term"],[data-theme="crt"]) .t-out > *{ flex:1 1 auto; min-width:0 }

:is([data-theme="term"],[data-theme="crt"]) .t-sh::before,
:is([data-theme="term"],[data-theme="crt"]) .t-cmd::before{
  content:"$"; flex:none; color:var(--accent); font-weight:700;
}
:is([data-theme="term"],[data-theme="crt"]) .t-out::before{ content:">"; flex:none; color:var(--dim) }

/* 你打給機器的一行，外面加一個框（`.t-sh` 是只有提示符、不加框的版本） */
:is([data-theme="term"],[data-theme="crt"]) .t-cmd{
  border:var(--w-line) solid var(--line-strong); background:var(--card);
  padding:1.4cqh 1.2cqw;
}

/* 單線方框。標題咬在框線上，像終端機的視窗 */
:is([data-theme="term"],[data-theme="crt"]) .t-box{
  border:var(--w-line) solid var(--line-strong); background:var(--card);
  padding:1.6cqh 1.2cqw; position:relative;
}
:is([data-theme="term"],[data-theme="crt"]) .t-box > .t-hd{
  position:absolute; top:0; left:1.2cqw; transform:translateY(-55%);
  background:var(--bg); padding:0 .5em;
  font-family:var(--f-ui); font-size:var(--t-cap); color:var(--dim);
  letter-spacing:.06em;
}

/* 方塊游標。⛔ 不做閃爍：印 PDF 會停在隨機的一格，而且看不出是哪一格；
   ⭐ 順帶，播放器的動畫閘會放行無限迴圈的動畫，所以閃爍其實不會卡住換頁——
      不做的理由純粹是「PDF 印不出一致的結果」。 */
:is([data-theme="term"],[data-theme="crt"]) .t-caret::after{
  content:"\2588"; color:var(--accent); margin-left:.12em;
}

/* ══════════════════════════════════════════════════════════════════
   ⭐⭐ 第二種終端機：**CRT／IBM 3270**　`data-theme="crt"`（老師 2026-09-01）

   > 老師：「老式終端機我覺得現在的 nano 模式不好玩，我想要 CRT 風格，
   >        老式 IBM 終端機的效果的」
   > ＋（同一天，看過深色草稿之後）**「但是我不要深色的，深色投影很糟，
   >    你還是可以用灰色的」**

   ⭐ 跟 `term` 的關係：**同一種文法、不同一台機器**。
     `.t-cmd`／`.t-sh`／`.t-out`／`.t-box`／`.t-caret`／`em`（底線式螢光筆）
     兩個主題**共用**（上面那批 `:is(...)`）；
     ⛔ nano 專屬的 `.nano-*`／`.app`／`.slide-body` **不共用**——
        那是 nano 編輯器的版面，不是終端機的。CRT 有自己的一套（見下面 `.crt-*`）。

   ⛔⛔ **不是深色的。**綠底黑字的 CRT 很好看，但**教室投影機的暗部對比很差**，
      整片深色還會把環境光全反射回來 ⇒ 後排等於看一片灰。
      ⇒ 這裡的作法是：**底色是映像管的灰玻璃，CRT 感靠質地不靠底色深**。
        辨識度來自四樣東西：掃描線、映像管暈邊、等寬字的雙亮度、
        以及底部那條 IBM 3270 的狀態列（OIA）。
      ⚠ 這也讓它**不必動 `build_deck.py` 的主題檢查器**——
        `#d8dad6` 對預設只差 1.27×（上限 1.35×），
        比 nano term 的 1.10× 深一階、看得出是另一台機器。

   ⭐ 用法：該頁寫 `<body data-theme="crt">`。
      ⛔ 一定要寫在 `<body>`（或 `<html>`）上，⚠ 寫在內層 div 的話
         單張與 PDF 會對、**編譯後的網頁版會靜靜失效**（理由同 term，2026-08-11 修過）。
   ══════════════════════════════════════════════════════════════════ */
[data-theme="crt"]{
  /* ⭐ 底：映像管沒通電時的灰玻璃，帶一點點綠。
     ⚠ 彩度只有 0.016 ⇒ 眼睛看是灰的，⛔ 不會被當成語意色的綠。 */
  --bg:#d8dad6;          /* 對預設 1.27×（上限 1.35×）*/
  --card:#e6e8e4;        /* 「亮起來」的區域，對底 1.14× */
  --line:#a9ada8;
  --line-strong:#5a615b; /* 對底 4.53:1（門檻 3.0）*/
  --dim:#4a534c;         /* 對底 5.67:1 */
  --accent:#2a352c;      /* 石墨帶一點磷光綠。⚠ HLS 飽和度 0.12 < 0.15
                            ⇒ 撞色檢查不會判它是「綠」，⛔ 語意色沒有被稀釋 */

  /* 終端機是密的，書才是鬆的（跟 term 同一個理由）*/
  --lh-body:1.5;
  --ls:0;

  /* ⛔ 字型要**整組重新宣告**，不可以只寫幾個——
     `font-family` 是繼承下來的算好的值，不是變數（deck.css 下面那一大段記過）。 */
  --f-display:"IBM Plex Mono","Noto Sans CJK TC","Jigmo2",monospace;
  --f-head:   "IBM Plex Mono","Noto Sans CJK TC","Jigmo2",monospace;
  --f-body:   "IBM Plex Mono","Noto Sans CJK TC","Jigmo2",monospace;
  --f-ui:     "IBM Plex Mono","Noto Sans CJK TC","Jigmo2",monospace;
  --f-kai:    "IBM Plex Mono","Noto Sans CJK TC","Jigmo2",monospace;
  --f-ja:     "IBM Plex Mono","Noto Sans CJK JP","Jigmo2",monospace;
  --f-ko:     "IBM Plex Mono","Noto Sans CJK TC","Jigmo2",monospace;
  --f-sc:     "IBM Plex Mono","Noto Sans CJK TC","Jigmo2",monospace;

  --pen:#b4b8b3;         /* ⭐ 螢光筆的填色，對頁底 1.45×、黑字 8.5:1（跟 term 同一個算法）*/

  /* CRT 自己的兩個值 */
  --crt-scan:rgba(30,40,32,.055);   /* 掃描線 */
  --crt-edge:rgba(30,40,32,.16);    /* 映像管暈邊 */
}

/* ── 映像管：掃描線 ＋ 暈邊 ────────────────────────────────────────
   ⛔⛔ **不可以用 filter 或 blur 做輝光。**列印時 filter 會把整塊光柵化，
      而這裡蓋的是整張投影片 ⇒ 那會變成一張滿版點陣圖（檔案暴增、放大就糊）。
      ⭐ 這裡全部是漸層 ⇒ 印進 PDF 仍然是向量。

   ⚠ 掃描線的週期是 **0.8cqh**（720px 高時約 5.8px），⛔ 不要再細——
     投影機的實體像素跟它會打出摩爾紋，⚠ 而那在螢幕上完全看不出來。
   ⚠ 用 cqh 不用 px：`.slide` 是 container，換解析度時線距才跟著縮。 */
/* ⛔⛔ **一定要下在 `.slide` 上，不可以下在帶主題的那一層。**
   `.slide` 自己有 `background:var(--bg)`（不透明）⇒ 畫在 `<body>`／`<section>`
   上的質地會**整片被它蓋掉**。⚠ 症狀是「版面全對、顏色全對，只有質地不見了」，
   截圖上跟沒寫這段一模一樣——2026-09-01 實測踩過一次。
   ⭐ `[data-theme="crt"] .slide` 兩條路都涵蓋：單張是 `<body>`，編譯後是 `<section>`。 */
[data-theme="crt"] .slide,
.slide[data-theme="crt"]{
  background-color:var(--bg);
  background-image:
    /* 掃描線 */
    repeating-linear-gradient(to bottom,
      var(--crt-scan) 0 .28cqh, transparent .28cqh .8cqh),
    /* 暈邊：四角壓暗，中間乾淨——映像管的曲面 */
    radial-gradient(120% 130% at 50% 50%,
      transparent 0 58%, var(--crt-edge) 100%);
}

/* ⭐ 螢幕作用區的內框。⚠ 用 ::after 不佔文件流，版面完全不受影響。
   ⛔ `pointer-events:none` 不可以拿掉——它蓋在整張上面，
      不擋掉的話抽屜頁籤與 ⓘ 按鈕會點不到（⚠ 而且畫面上看不出來，只是按了沒反應）。 */
[data-theme="crt"] > .slide::after,
.slide[data-theme="crt"]::after{
  content:""; position:absolute; inset:2.6cqh 2.0cqw;
  border:var(--w-hair) solid var(--line-strong);
  pointer-events:none;
}

/* ── CRT 的版面語彙　`.crt-*` ──────────────────────────────────────
   ⭐ 換掉 nano 的那一套。這幾個都是 IBM 3270 螢幕上真的有的東西。 */

/* ① `.slide-body`：內容區。⚠ 底部留給 OIA（比 nano 的 13% 少，OIA 只有一行）。 */
[data-theme="crt"] .slide-body{
  position:absolute; left:4.5%; right:4.5%; top:6%; bottom:10%;
}

/* ② `.crt-oia`：**Operator Information Area**——3270 螢幕最下面那條狀態列。
   ⭐ 這是這個主題最好認的東西，也是它跟 nano 版最大的差別。
      真機的排法：左邊是機型與連線指示、中間是訊息、右邊是游標位置 `列/欄`。
   ⚠ 它是**裝飾**（deck.css `.deco` 的判準：整個拿掉這頁照樣講得下去）
      ⇒ 字級不受 `--t-min` 下限限制。⛔ 但也不可以塞學生要讀的資訊進去。

   用法：
     <div class="crt-oia"><span>A</span><b>READY</b><span>04/017</span></div> */
/* ⚠ 預設是**絕對定位**（`.fix`），因為各頁的版面五花八門（有 flex 置中的封面、
   也有底部已經有 `.ask` 的頁），⛔ 用 `margin-top:auto` 會跟頁面自己的 auto 邊界
   互相搶剩餘空間、把內容推到奇怪的位置。
   ⛔⛔ 用 `.fix` 的頁**一定要自己留下方的 padding（約 11cqh）**——
      不留的話 OIA 會壓在內容上，⚠ 而 `.slide` 是 overflow:hidden，
      被壓到的那一截是**靜靜被裁掉的** ⇒ 改完一定要跑 `deck overlap`。 */
[data-theme="crt"] .crt-oia.fix{
  position:absolute; left:4.5%; right:4.5%; bottom:3.4cqh;
}
[data-theme="crt"] .crt-oia{
  display:flex; align-items:center; gap:1.2em;
  border-top:var(--w-hair) solid var(--line-strong);
  padding-top:.7cqh;
  font-family:var(--f-ui); font-size:2.0cqh; letter-spacing:.10em;
  color:var(--dim);
}
/* 左邊的連線指示：3270 用一個實心方塊 ＋ 機型代號 */
[data-theme="crt"] .crt-oia > span:first-child::before{
  content:"\2588\00a0"; color:var(--accent);
}
[data-theme="crt"] .crt-oia > b{ font-weight:400; color:var(--accent) }
/* 右邊的游標位置靠右 */
[data-theme="crt"] .crt-oia > span:last-child{ margin-left:auto }

/* ③ 雙亮度（3270 的 normal／high intensity 欄位）。
   ⚠ 真機是靠亮度分，這裡在灰底上是**靠深淺分**——同一件事反過來。 */
[data-theme="crt"] .crt-hi{ color:var(--ink); font-weight:700 }
[data-theme="crt"] .crt-lo{ color:var(--dim) }

/* ④ `.crt-f`：3270 的欄位列。⭐ 標籤與值之間那排點點是它的招牌。
   ⛔ 點點用 `repeating-linear-gradient` 畫，**不要用 `.....` 打出來**——
      IBM Plex Mono 的拉丁字寬 0.60em、漢字 1.00em，中英混排時點數會對不齊
      （跟 deck.css 那條「方框線不可以用 ┌─┐ 排」同一個理由）。 */
[data-theme="crt"] .crt-f{
  display:flex; align-items:baseline; gap:.6em;
  font-size:var(--t-min); line-height:1.9;
}
[data-theme="crt"] .crt-f > .k{ flex:none; color:var(--dim) }
[data-theme="crt"] .crt-f > .dots{
  flex:1 1 auto; min-width:2em; align-self:center; height:.28cqh;
  background:repeating-linear-gradient(to right,
    var(--line-strong) 0 .18cqh, transparent .18cqh .72cqh);
}
[data-theme="crt"] .crt-f > .v{ flex:none; color:var(--ink); font-weight:700 }

/* ⑤ `.crt-rev`：反白欄位。3270 用它標「這裡要你輸入」。
   ⚠ 這是整個主題**唯一**的深色塊，⭐ 只該用在一小段字上，
     ⛔ 不要拿它當整段的底——那就變回「深色投影」了，而那正是老師否掉的。 */
[data-theme="crt"] .crt-rev{
  background:var(--accent); color:var(--bg);
  padding:.05em .35em;
}

/* ══════════════════════════════════════════════════════════════════
   ⛔⛔ 帶主題的元素要**自己重新宣告 font-family**（2026-08-12 修）

   主題區塊只換「變數」。變數是在**用到它的那個元素**上解析的，
   而 `font-family` 是**繼承**下來的**算好的值**——兩件事。

   所以編譯後的網頁版會這樣壞：
     `.reveal{ font-family:var(--f-body) }` 在 section **外面** → 解析成預設主題的宋體；
     `<section data-theme="term">` 只換掉 section 底下的變數，**沒有換 section 自己的
     font-family**，於是整片內容繼承到宋體。實測：
       .reveal                 = 日星宋體
       section[data-theme=term] = 日星宋體   ← 主題等於沒生效
       .nano-top               = IBM Plex Mono（那條規則自己有寫 font-family）

   ⚠ **單張 HTML 不會壞**，因為那裡 `data-theme` 寫在 `<body>` 上，
     跟 `font-family:var(--f-body)` 是同一個元素。
     **所以 PDF 全部正常、只有網頁版是明體，而且老師是在線上才發現的。**

   ⭐ 這一條對「以後任何新主題」都成立，不是 term 專用。
   ══════════════════════════════════════════════════════════════════ */
[data-theme]{ font-family:var(--f-body) }

/* ── 打字機（老師 2026-08-12）────────────────────────────────────
   ⭐ 打字時 `.ai` 加上 `.tw-run`：**排好的內容留在原地但隱形**（位置照樣佔著），
      `pre.tw` 疊在它上面逐字打 markdown。打完移除 pre、拿掉 class，內容現身。
   ⛔ `pre` 一定要放在 `.ai` **裡面**——放外面的話絕對定位會對到整張投影片，
      字會跑到左上角疊在 prompt 氣泡上（2026-08-12 實測踩過）。
   ⛔ 用 visibility 不用 display:none：位置要先佔住，否則換手瞬間整頁會跳。
   ⚠ 只在編譯後的 deck 出現：單張 HTML 不載播放器，內容直接就是排好的。

   ⛔⛔ **隱形一定要下在 `.ai` 自己身上，不可以只下給子元素**（2026-08-13 修）。
      舊版寫 `[data-md].tw-run > *:not(pre.tw){visibility:hidden}`，那只選得到
      **元素**子節點——而 `.ai` 裡的字大多是**裸文字節點**，例如
      「這張表共有 <em>16 個欄位</em>，我一個一個說明。最要緊的三個：」
      的 `<em>` 被藏起來、前後那兩截白話**照樣顯示**。
      於是打字機在上面打、原文在下面透出來，兩份字疊在一起（老師：
      「幾乎每個打字機效果區域下方都被疊上靜態文字區」）。
      visibility 會繼承，所以下在自己身上連文字節點一起藏，再把 `pre.tw`
      單獨打開。⚠ `::before`（AI 的 ◆）也要單獨打開，否則打字時它會消失、
      打完又蹦回來。
   ⚠ **這種壞法在單張 HTML 與 PDF 上完全看不出來**：那兩邊不載播放器，
      沒有 `.tw-run` 這個 class，永遠是排好的內容。

   ⛔⛔ **`!important` 不可以拿掉。**reveal 自己有一條
      `.reveal .fragment.visible{ opacity:1; visibility:inherit }`，
      三個 class 的特異度贏過我們這條的兩個——而 `.ai` 幾乎都同時是 `.fragment`
      （逐項出現）。不加的話會停在**半修好**：不是 fragment 的那幾張正常，
      是 fragment 的照樣疊字，而那正是大多數的頁。

   ⛔⛔ **`transition:none` 也不可以拿掉，而且這一條最難自己看出來。**
      reveal 給 `.fragment` 的是 `transition: all .2s`，而 CSS 規定
      `visibility` 在轉場中**一律先當作 visible**——所以我們把它設成 hidden
      之後，整段原文照樣顯示（實測 3.5 秒後仍是 visible）。
      ⚠ 而且它**只在「動畫沒被關掉」的瀏覽器上壞**：headless Chrome 預設回報
      `prefers-reduced-motion: reduce`，reveal 就把轉場關了、於是隱形立刻生效。
      **不加 `--force-prefers-no-reduced-motion` 去截圖，看到的是修好的假象，
      老師的電腦上卻是壞的。**（2026-08-13 就是這樣差點誤判） */
[data-md].tw-run{ position:relative; visibility:hidden !important; transition:none !important }
[data-md].tw-run > pre.tw,
[data-md].tw-run::before{ visibility:visible }
/* ⚠ `padding-left:inherit` 不是裝飾：`inset:0` 對到的是**內距框**，所以打字機
   會從最左邊開始打，正好壓在 `.chat .ai::before` 那顆 ◆ 上，而且打完換手時
   整段字會往右跳 1.6em。`inherit` 取的是父層**算好的**值（`1.6em` 早就折成 px），
   所以不會被 `pre` 自己較小的 font-size 拉歪。沒有內距的父層就是 0，不影響。 */
pre.tw{
  position:absolute; inset:0; margin:0; overflow:hidden; padding-left:inherit;
  font-family:var(--f-ui); font-size:var(--t-min); line-height:1.5;
  letter-spacing:0; color:var(--dim); white-space:pre-wrap;
}
pre.tw::after{ content:"\2588"; color:var(--accent) }

/* ⛔⛔ 語意色守門：放在所有主題之後，所以主題改不動它們。
   黃＝課本原文／關鍵詞，藍＝翻譯或我們加的話。
   這兩個顏色是**意思**不是裝飾——第3章把黃換成綠，
   學生前兩章建立的「黃＝原文」就整個作廢，而且他不會知道規則變了。
   要動這幾行，先問老師。

   ⚠ 2026-08-10 換成淺底之後，這兩個色的**用法**變了（意思沒變）：
     ・黃 #ffd84d **只能當螢光筆的填色，不可以當文字色**（對淺底只有 1.25:1）。
       它在淺底上靠的是彩度不是明度——就跟真的螢光筆畫在紙上一樣。
     ・黃色系的**線與邊**（引文左標線、螢光筆下緣）另用 --hi-edge（3.44:1）。
     ・藍 --tr 從 #7fd4ff 改成 #0f5f8f。**淺底上 #7fd4ff 只有 1.48:1，完全讀不到。**
       色相仍在同一族（197°→202°），所以「藍＝翻譯」這個約定沒有斷。
       ⛔ 這是換底色逼出來的，不是重新設計。 */
:root,[data-theme]{
  --ink:     #1a1a1f;     /* 一般文字 15.61:1 */
  --hi:      #ffd84d;     /* 螢光筆填色（⚠ 不可當文字色）*/
  --hi-edge: #a67c00;     /* 黃色系的線與邊 3.44:1 */
  --tr:      #0f5f8f;     /* 翻譯／我們加的話 6.19:1 */
  --warn:    #b3261e;     /* 警示 5.89:1 */
  --ramp:    #1d7a4c;     /* ramp 型投影片 4.63:1。⛔ 語意色，主題不可改 */
  /* ⭐ diff 版型的「＋新增」。⚠ **跟 --ramp 同值但語意不同**（那是交流道，
     這是「這一國加了什麼」）⇒ ⛔ 不要合併成一個變數，換主題時兩者可以分開走。
     ⚠ 刪除色直接用 --warn，⛔ 不另外開一個。 */
  --diff-add: #1d7a4c;

  /* ⭐⭐ **四國的識別色**（老師 2026-09-01：diff 的 header「會給和 git fork
     同樣的底色（白字）」）。⚠ 五個值都是**先算過白字對比**才定的（AA 要 4.5）：
       中國 10.44　日本 7.05　韓國 5.21　台灣 5.94　越南 8.25
     ⛔ 韓國原本是 `#b8661a`，白字只有 **4.24:1 不合格**，往深處調成 `#a35a15`。
     ⭐ 中國是 main 分支，刻意用**中性的墨灰**——有顏色的才是「改動」。
     ⚠ 越南現在還沒有頁面用到，先留著（這門課是中日韓越四國）。 */
  --nat-cn: #3f3f46;
  --nat-jp: #7d3f8f;
  --nat-kr: #a35a15;
  --nat-tw: #1d6b8c;
  --nat-vn: #8a2f39;
}

/* ══════════════════════════════════════════════════════════════════
   螢光筆的強制深色對策：**反著寫，再用 filter 翻回來**（老師 2026-08-13）

   > 老師：「有沒有一種可能是螢光筆用 css 的 filter 來做？文字就被 burn 上去？」

   解的是這件事：學生在手機 Edge 開「強制網頁深色」時，`color-scheme: only light`
   擋不住（理由見本檔開頭的 `:root`），整份投影片會被演算法反轉。反轉後**只有
   螢光筆真的壞掉**——`#ffd84d` → `#493200` 深褐壓在近黑上，「這段是原文／關鍵詞」
   的訊號整個消失。

   ⭐ 為什麼這樣寫有效（實測 2026-08-13，不是巧合）：
   Chromium 的強制深色有兩個**編譯期寫死**的門檻
   （`dark_mode_settings_builder.cc`：前景 150、背景 205）——
       · 文字亮度 **低於 150** 才翻　　· 背景亮度 **高於 205** 才翻
   把顏色反著寫之後，底的亮度≈43、字≈228，兩邊都落在「不翻」的區間，
   強制深色**直接跳過這一塊**；我們自己的 `filter:invert(1)` 再把整塊翻成正常的黃。
   實測結果與正常模式**位元相同**：底 rgb(255,216,77)、字 rgb(26,26,31)。
   ⭐ reveal 的 `transform:scale()` 放大 3 倍不糊；跨行三行同一個黃、沒有接縫。

   ⛔⛔ **一定要包在 `@media screen` 裡。**實測直接套上去，Chrome 印 PDF 時
   會把整塊 `filter` 光柵化成 300ppi 的圖：`pdftotext` 撈不到那幾個字、
   放大就糊——而老師上課就是**放大 PDF** 在用的。包起來之後實測乾淨
   （文字仍是文字、`pdfimages` 一張圖都沒有、顏色正常）。
   ⭐ 列印本來也不需要：Blink 在 `Document::Printing()` 時會先把強制深色關掉。

   ⛔⛔ **這一塊必須放在所有動到 `em` 顏色的規則之後**（基準 `em`、`.vert em`、
   `[data-theme="term"] em`）。搬到前面的話 `.vert em` 會把顏色蓋回正的、
   `filter` 卻蓋不掉 ＝ 直排引文的螢光筆變成深藍底淺字，
   而且**只有在別人開了強制深色的手機上看得到**。deck_compile.py 有自我測試盯順序。

   ⛔ `[data-theme="term"] em` 要 `filter:none`：技術那份的螢光筆是「沒有底色、
   只有底線」，套下去會變成把字反轉。
   ⛔ 別在 `em` 裡放絕對定位的東西——`filter` 會建立 stacking context 與 containing block。
   ⚠ `@supports` 是防呆：不支援相對色語法的瀏覽器整段跳過，吃上面那份基準色
   （會被強制深色翻，但至少不會變成沒有底色的反白字）。
   ⚠ 這只救螢光筆。頁底照樣被翻成近黑，整份還是深色的——**沒有辦法**。 */
@media screen{
  @supports (color: rgb(from red r g b)){
    em{
      /* 反著寫的三個顏色。⭐ 在 `em` 自己身上算，所以 `.invert` 改過的
         `--hi-edge` 也跟得上，不會有第二份要同步。 */
      --_hi:   rgb(from var(--hi)      calc(255 - r) calc(255 - g) calc(255 - b));
      --_edge: rgb(from var(--hi-edge) calc(255 - r) calc(255 - g) calc(255 - b));
      --_ink:  rgb(from #1a1a1f        calc(255 - r) calc(255 - g) calc(255 - b));
      color:var(--_ink);
      background:var(--_hi);
      box-shadow:0 0 0 .12em var(--_hi);
      border-bottom-color:var(--_edge);
      filter:invert(1);
    }
    /* 直排：邊在右邊、沒有下緣線，其餘同上 */
    .vert em{ color:var(--_ink); box-shadow:0 0 0 .12em var(--_hi);
              border-right-color:var(--_edge) }
    /* 翻譯裡的螢光筆（見上面 `.zh em`） */
    .zh em{ color:rgb(from var(--tr) calc(255 - r) calc(255 - g) calc(255 - b)) }
    /* ⛔ 技術那份沒有底色，不可以翻 */
    :is([data-theme="term"],[data-theme="crt"]) em{ filter:none }
    /* ⚠ `[data-theme="term"] em` 與 `.zh em` 的特異度**一樣**是 (0,1,1)，
       上面那條反寫版寫在後面就會贏——term 沒有 filter，套下去是淺字疊淺底，
       整個字消失。所以這裡要用 (0,2,1) 把它壓回來。 */
    :is([data-theme="term"],[data-theme="crt"]) .zh em{ color:var(--tr) }
  }
}

/* ══════════════════════════════════════════════════════════════════
   `ramp` 型投影片（白話：交流道）　老師 2026-08-11

   > 老師：「我要多一種內容類型是……這種內容出現的時候會告訴 user 和撥放器，
   >        user 該去看其他的投影片了……其實這種投影片就是一個『投影片交流道』」

   ⛔ **不叫 fork。**git 的 fork 是「分家不回來」，這個是「下匝道、看完可以回來」，
      意思相反；而且這門課的學生要碰版控，同一個字兩個意思是自找麻煩。
      ⭐ **開發規範裡一律寫「`ramp` 型投影片」**，對學生才說「交流道」。

   ⭐ **連結是投影片自己寫的真 `<a>`**，不是播放器畫上去的。所以：
        · 單獨開這一頁 → 就是一般連結，點了會跳
        · 在播放器裡   → deck.js 攔下來，改成播放器內部換頁，不重載
        · 印成 PDF     → 連結文字照樣印出來（也是 PDF 的真超連結）

   寫法：
       <nav class="ramp">
         <span>接下來去看</span>
         <a href="某頁.html">日本這條線</a>
         <a href="另一頁.html">朝鮮這條線</a>
       </nav>
   ══════════════════════════════════════════════════════════════════ */
.ramp{
  position:absolute; left:var(--safe); right:var(--safe);
  bottom:calc(var(--safe) * .75);
  display:flex; flex-wrap:wrap; gap:.55em; align-items:center;
  font-family:var(--f-ui);
}
.ramp > span{                       /* 引導詞 */
  font-size:var(--t-min); color:var(--dim); margin-right:.2em;
}
.ramp > a{                          /* ⚠ 學生要讀要點的，用內文級不能再小 */
  font-size:var(--t-body); line-height:1.35; text-decoration:none;
  color:var(--ramp); background:#fff;
  border:var(--w-line) solid var(--ramp); border-radius:.4em;
  padding:.3em .8em .35em;
}
.ramp > a::after{ content:" ›"; font-weight:400 }
.ramp > a:hover, .ramp > a:focus-visible{ background:var(--ramp); color:#fff }

/* ══════════════════════════════════════════════════════════════════
   學習目標 `.goals`　老師 2026-08-11

   > 老師：「cover 後再插入一張『學習目標』投影片，**這應該變成一種新的版面元素**」

   ⭐ **它是一個具名的型，不是某一頁的私有樣式**——以後每一份技術教學的第 2 張
      都用同一個，學生翻到這個版面就知道「這是在講這一節要帶走什麼」。
   ⭐ 版型由老師 2026-08-11 從三個候選裡選定：**直式大字清單**。

   寫法：
       <div class="goals">
         <div class="goal">
           <span class="no">一</span>
           <span class="t">認識試算表
             <span class="s">行、列、儲存格；一列到底代表什麼</span>
           </span>
         </div>
         …
       </div>

   ⚠ **目標的字級是內文的兩倍（6.4cqh），所以一張最多放三、四條。**
      條數多到要縮字才排得下，就表示那不是「目標」是「大綱」——大綱不放這裡。
   ⚠ `.s`（小字說明）可以省略。省略時 `.t` 自己會置中對齊得比較好看。
   ⭐ 要逐條出現就在 `.goal` 上加 `class="goal fragment"`，不必寫 JS。
   ══════════════════════════════════════════════════════════════════ */
/* ⚠ `justify-content:center`：條數少的時候（兩條就會）整塊要在版面中間，
   不然上面擠著、下面空一大片。條數多到填滿容器時這一行沒有作用。 */
.goals{ display:flex; flex-direction:column; justify-content:center; gap:4.6cqh }
.goals > .goal{ display:flex; gap:1.5cqw; align-items:baseline }
.goals .no{
  flex:none; min-width:1.7em;
  font-family:var(--f-display); font-weight:600;
  font-size:6.0cqh; line-height:1.25; color:var(--dim);
}
.goals .t{ font-size:6.0cqh; line-height:1.3 }
.goals .s{
  display:block; margin-top:1.1cqh;
  font-family:var(--f-ui); font-size:var(--t-min); color:var(--dim);
  letter-spacing:0; line-height:1.6;
}

/* ══════════════════════════════════════════════════════════════════
   ⭐⭐ 對話框頁（prompt 頁）`.chat`　老師 2026-08-08 #115／#130 ＋ 2026-08-11 補充

   > #115「**prompt 都該有專用版面**，我們會給 promot，以及告訴學生**這個步驟的名稱**，
   >      並且 promot 頁面會**附上簡化的 ai 預期回覆**」
   > #130「其實你到時候可以**模仿真的 llm 對話框**」
   > 2026-08-11「**rag 一個步驟一張投影片，但是要顯示步驟在 header 區塊**」

   ⭐ **模仿真的 LLM 對話框**——學生螢幕上就長那樣，投影片跟他們的視窗直接對得上。
      所以是**垂直流**（使用者訊息在上、回覆在下），不是左右分欄。

   ⭐ **四個步驟沿用高一那份講義**（老師自己寫的，學生高一用過）：
      ［概覽］→［提取］→［分析］→［驗證］。**一個步驟一張投影片。**
      ⚠ ［驗證］有連續兩問時仍然是兩張，header 同一格反白，只差「第一問／第二問」——
        **不要讓它看起來像新步驟**，學生的認知負擔是 4，不是 6。

   ⭐⭐ **prompt 一定要比回覆大。**這一份的主張是「AI 做第二步，人做第三步」——
      prompt 是人的動作，回覆是機器的動作。回覆一旦排得比 prompt 醒目，
      投影片講的就變成「看 AI 產出了什麼」，**跟我們要講的正好相反**。
      （對話框版式天然幫我們做到：使用者訊息有底色，AI 回覆是素的。）

   ⛔ **不要用真截圖。**字太小（那是給一個人看的）、雜訊多（側邊欄按鈕模型名）、
      會過期（換學期或換工具就對不上）。**模仿的是樣式，不是尺寸。**

   ⛔ **每一張都要有「⚠ 示意，實際措辭不同」。**排成投影片以後會長得像標準答案，
      學生會拿它對，發現不一樣就以為自己做錯。

   寫法：
       <div class="hd">
         <nav class="steps"><span>概覽</span><span class="now">提取</span>
                            <span>分析</span><span>驗證</span></nav>
         <div class="h1">這一張的名字</div>
       </div>
       <div class="chat">
         <div class="you"><span>你打給 AI 的話（逐字）</span></div>
         <div class="ai">簡化的預期回覆</div>
       </div>
       <div class="chat-warn">⚠ 示意，實際措辭不同</div>
   ══════════════════════════════════════════════════════════════════ */
/* ⚠ 舊的 `.steps` 徽章列已刪（2026-08-12）：步驟改由 nano 的快捷鍵列表示。
   ⛔ 留著會跟『可以自己上網裝』那頁自己的 `.steps`（安裝四步）撞名。 */

.chat{ display:flex; flex-direction:column; gap:2.2cqh }

/* 使用者訊息：靠右、有底色、**全頁最大字** */
.chat .you{ align-self:flex-end; max-width:74%;
            background:var(--card); border:var(--w-line) solid var(--line-strong);
            padding:1.6cqh 1.4cqw; font-size:5.0cqh; line-height:1.5 }

/* AI 回覆：靠左、無底色、**明顯小一號** */
.chat .ai{ align-self:flex-start; max-width:86%;
           font-family:var(--f-ui); font-size:3.3cqh; line-height:1.6;
           letter-spacing:0; padding-left:1.6em; position:relative }
.chat .ai::before{ content:"\25C6"; position:absolute; left:0; top:.05em;
                   color:var(--dim); font-size:.8em }

/* ⛔ 每一張都要有 */
.chat-warn{ font-family:var(--f-ui); font-size:var(--t-cap); color:var(--dim);
            letter-spacing:0; text-align:right }

/* ⭐ 斷語頁：唯一允許的深色頁。老師 2026-08-08 定義它就是「反白，全螢幕置中，
   無其他元素」，2026-08-10 定「不要黑底白字」之後，它就是那個刻意的例外。
   ⛔ **一份投影片最多兩張**（頭一張、尾一張）。它有效是因為稀有；
      每段都放一張，它就從謎底變成分隔線，學生會學會「反白＝要換段了」然後不再讀它。
   用法：<div class="slide invert"> */
.invert{
  --bg:#0d0d10; --card:#17171c; --line:#3a3a46; --line-strong:#5e5e70;
  --dim:#8a8a96; --ink:#f2f2f5; --tr:#7fd4ff; --hi-edge:#ffd84d;
  background:var(--bg); color:var(--ink);
}

/* ===== 輸出 PDF：16:9 滿版，文字保持向量 ================================ */
@page{ size:338.667mm 190.5mm; margin:0 }   /* 13.333in × 7.5in ＝ 1280×720 CSS px */
@media print{
  /* ⚠ body 用 min-height 不是 height——有抽屜的頁要能長成好幾頁（見下方 .drawer）。
     寫死 height 的話第二頁以後會被截掉，而且**印出來才會發現**。 */
  html,body{ width:338.667mm; height:auto; min-height:190.5mm;
             margin:0; padding:0; background:var(--bg) }
  .slide{ width:338.667mm; height:190.5mm; max-width:none; max-height:none; margin:0 }
  .noprint{ display:none }                  /* 給老師看的備註不進 PDF */
}

/* ══════════════════════════════════════════════════════════════════
   講者備註 `<aside class="notes">`

   ⛔⛔ **一定要在這裡藏起來，不能只靠播放器。**
   編譯出來的 deck 有自己的 `.reveal aside.notes{display:none}`，
   但**單張 HTML 沒有那條**——而 `pdf --all` 正是從單張 HTML 印的。
   少了這一條的後果有兩個，而且都不會報錯：
     ① 單張開起來，備註直接印在版面上、壓到標題（`check_overlap.py` 會抓到）
     ② **備註跟著印進 PDF**，上課投出來全班都看得到
   （2026-08-11 做技術投影片時踩到——在那之前沒有任何一頁用過這個 API。）

   ⭐ `display:none` 不影響 reveal 的講者備註視窗，它讀的是 innerHTML。
   ══════════════════════════════════════════════════════════════════ */
aside.notes{ display:none }

/* ══════════════════════════════════════════════════════════════════
   ⚠⭐⛔ 符號 `.sym`：**不包起來，整段中文會在 PDF 裡消失**（2026-08-12）

   ⛔⛔ 這是目前找到最惡劣的一個「安靜壞掉」：螢幕上完全正常，
        印出來**整個段落的非粗體中文一個字都沒有**——PDF 裡連文字物件都不存在，
        不是白字。`pdftotext` 也抽不到。發現時全案 **22 段、9 頁**是空的。

   **成因**：⚠(U+26A0)、⭐(U+2B50)、⛔(U+26D4)、✅(U+2705)、⬜(U+2B1C)、❌(U+274C)
   預設是**表情符號呈現**。它跟中文在同一個文字段（text run）裡時，
   Chrome 會用彩色表情字型去 shape 整段，而那個字型沒有中文字符 → 整段畫不出來。
   ⚠ 符號在**開頭或中間都一樣**會壞。

   ⚠ **不只表情符號。**`≈`(U+2248) 也會壞，而它不是表情符號。用 `msjh.ttc` 的
     cmap 實測，真正的判準是「**這個字元不在內文字型裡**」——微軟正黑體沒有
     ⚠⭐⛔✅❌⬜≈⋯≥−ⓘ❓ 這些，一律要包。

   **修法**：把符號包進 `.sym`，並**明確指定一套有這些字的字型**，
   讓它自己成為一個文字段，不必靠 run 內的 fallback：
       <span class="sym">⚠</span> 後面的中文
   ⛔ **裸 `<span>` 沒有用**（實測），⛔ **把 `font-variant-emoji:text` 下在
      `.drawer`／`.drawer *`／`p` 上也沒有用**——要「只包住符號」＋換字型。
   ⭐ 符號寫在 `<b>` 裡本來就不會壞（粗體自成一段），所以舊頁面有些沒事。
   ⭐ 工具：`工具/wrap_sym.py`（備課專案裡）會自動包，且不碰註解與 `<style>`。

   ⚠ 這條規則在**單張 HTML 與編譯後都要在**，所以放在 deck.css，不是各頁自己寫。
   ══════════════════════════════════════════════════════════════════ */
.sym{ font-family:"Segoe UI Symbol","Segoe UI Emoji","Noto Sans Symbols 2",
                  "Noto Sans Symbols","Noto Sans CJK TC",sans-serif;
      font-variant-emoji:text }

/* 🈶 擴充區的字 `.extb` ── **平常不用寫**（2026-08-19 起）

   ⭐ 每一條字型堆疊的最後都接了 `"Jigmo2"`（見上面 `--f-display` 前那段），
     所以 CJK Ext-B 以上（U+20000 起）的字**直接打出來就會對**，
     單張 HTML、編譯後的 deck、PDF 三條路都一樣。

   ⚠ 這條 class 留著是給「想強制指定」用的：
       <span class="extb">𥄉</span>
   ⛔⛔ **2026-08-19 以前它只定義在 `郡和縣.html` 自己的 `<style>` 裡**，
     不在任何共用樣式中——⇒ 別的頁照文件寫 `class="extb"` 是**完全沒有效果**的，
     跟 08-18 抓到的 `.pg` 一模一樣。現在 base 在這裡了。 */
.extb{ font-family:var(--f-extb) }

/* ══════════════════════════════════════════════════════════════════
   ⭐⭐ 古文字（甲骨）`.gu`（老師 2026-08-23 定）

       <span class="gu">田</span>     ← 這個「田」會渲染成甲骨字形

   ⭐ 字型是 **Oracular**（镜原甲骨數字平台，**OFL 1.1**，可散布可子集化）。
   ⛔⛔ **只有掛了 `.gu` 的字才進 Oracular 的子集**（`build_deck.py` 的
     `only_class="gu"`）⇒ 沒標的字**根本不在字型檔裡**，這是防呆的第二道。
   ⚠ Oracular 沒有的字會掉回一般宋體（`--f-gu` 的 fallback）——
     ⭐ **那是刻意的**：例如「稅」在甲骨文裡本來就沒有這個字，
     它顯示成楷書的「稅」正好把「這個字是後來才造的」講出來了。
   ⚠ 字級：甲骨字形的視覺重心跟楷書不同，⭐ 並排時通常要**放大一點**才平衡，
     ⛔ 但不要用 vw/vh（見本檔開頭那條），要用 cq 單位或 em。
   ══════════════════════════════════════════════════════════════════ */
.gu{ font-family:var(--f-gu) }

/* ⭐⭐ 小篆 `.zhuan`（老師 2026-08-24 定）
       <span class="zhuan">稅</span>     ← 這個「稅」會渲染成小篆
   ⭐ 字型是**崇羲篆體**（中研院小學堂，王心怡・季旭昇，依大徐本《說文解字》書寫，
     11,612 字）。⚠ **授權 CC-BY-ND-3.0-TW，明文要求「表彰原作者姓名」**
     ⇒ ⛔ **用到這個 class 的頁，info drawer 一定要標出處與授權。**
   ⛔ 跟 `.gu` 同一組規矩：只有 `--f-zhuan` 用它，⛔ 不接進別的 stack；
     子集裡只有 `.zhuan` 標過的字。
   ⚠ 崇羲**沒有「徭」「繇」**——⭐ 那不是缺字，是《說文》本來就不收「徭」。
     缺的字會掉回一般宋體，⭐ 那正好把「這個字更晚」講出來。 */
.zhuan{ font-family:var(--f-zhuan) }

/* ══════════════════════════════════════════════════════════════════
   📝 備課筆記 `.note`：**編譯後不可以出現**（2026-08-12）

   ⚠ 這是一個「桌機剛好遮住、手機直向才露餡」的坑，發現時 17 張都中。

   `.note` 是寫在 `.slide` **外面**的備課筆記（待裁示、查證過程、X 貼文網址、
   上課應對）。單張 HTML 開起來時它就該顯示——那是它存在的理由。
   但編譯進 deck 之後它還在 `<section>` 裡，而**以前沒有任何規則藏它**：

     · 桌機：投影片填滿高度 → 筆記被擠出畫面下緣 → `.reveal{overflow:hidden}` 切掉
     · 手機直向：1280×720 只縮到 216px 高、置中之後下面空一大片
       → **筆記整段掉進那片空白裡，學生看得到**

   ⛔ 站台密碼是要發給學生的，而筆記裡有「⬜ 待老師裁示」與查證過程。
      這不是版面問題，是內容外洩。

   ⭐ 只在 `.reveal` 底下藏，所以**單張 HTML 照舊顯示**，作者的工作流不變。
   ⭐ PDF 不受影響：`pdf --all` 從單張印，而 `@page` 只裁 `.slide` 那一頁
      （實測 封面_SNS回覆.pdf 是 1 頁、抽不到筆記文字）。

   ⛔⛔ **選擇器一定要是 `section > .note`，不可以寫成 `.reveal .note`。**
        （2026-08-12 第一版就是寫成後者，隔天才抓到。）

        `.note` 這個字在各頁**同時是兩種東西**：
          ① 頁面層的備課筆記——寫在 `.slide` **外面**，編譯後是 `<section>` 的直接子元素
          ② 投影片自己的說明小字——寫在 `.slide` **裡面**，一定包在
             `.hd`／`.say`／`.col`／`.side`／`.tl`／`.call` 之類的容器底下
        實測東亞課程收進 `順序.txt` 的那 18 頁：①**只有 1 個**（封面_SNS回覆），
        ②**有 16 個**。`.reveal .note` 兩種一起藏 →
        **16 張投影片的說明小字在編譯後整段消失**，
        而單張 HTML 與 PDF 全都正常——又是一個只有網頁版才壞的坑。

   ⚠ 為什麼不能改寫成 `.reveal .slide .note` 把它反白回來：
     **reveal 自己會在 `<div class="reveal">` 上掛一個 `slide` class**（那是轉場模式的
     名字），所以 `.slide` 這個字在編譯後同時指兩個東西，
     連 `closest('.slide')` 對頁面層的筆記都會成立。用它當判準一定出錯。
   ══════════════════════════════════════════════════════════════════ */
.reveal section > .note{ display:none }

/* ══════════════════════════════════════════════════════════════════
   抽屜 `.drawer`（老師 2026-08-11）

   > 老師：「我們給一個協定，投影片可以對外呼叫 drawer，
   >        那些被 drawer 丟出去的內容在最後輸出 PDF 的時候也會變成單獨的分頁」

   ⭐ **同一段內容，兩種形狀**：
        · 網頁上 → 一個**蓋滿整張投影片、由上往下滑入**的面板，要看才拉開，不佔版面
                   （老師 2026-08-13 定的；2026-08-13 之前是從右邊拉出來的窄面板。
                    ⚠ 樣式寫在播放器那邊：`deck_compile.py` 的 `.drawer.dw-open`。
                    這裡只負責「平常藏起來」與「列印時自成一頁」）
        · PDF 裡 → **自己一頁**，接在母投影片後面（PDF 沒有「拉開」這種事）

   ⛔⛔ **抽屜要寫在 `.slide` 外面，當它的兄弟，不可以放在裡面。**
        `.slide` 是 `overflow:hidden` ＋ 固定 190.5mm 高——放在裡面會被夾住，
        `break-before:page` 完全不生效，而且**印出來才會發現**（實測踩過）。

   寫法：
       <div class="slide">
         …主要內容…
       </div>
       <aside class="drawer" data-title="條約體系的完整條文">
         …補充內容…
       </aside>

   ⭐ 可以有好幾個抽屜，右緣會排成好幾個標籤。
   ⭐ 頁內程式也可以自己開：`deck.drawer("條約體系的完整條文")`。
   ══════════════════════════════════════════════════════════════════ */

/* ⚠⚠ **抽屜裡預設用內文字型，這不是美感選擇，是為了不要印成空白。**
   實測：字型如果**只有抽屜裡在用**，母投影片沒用到，Chrome 印 PDF 時
   那個 @font-face 還在 `font-display:block` 的期間（文字隱形），
   **整段就印成全白**，而且螢幕上完全正常，你只會在 PDF 裡發現。
   `--f-body` 綁在 `body` 上，一定載得到，所以拿它當抽屜的預設。
   ⛔ 要在抽屜裡換別的字型（例如 `--f-head`）**必須確認母投影片也用了同一個**。
      實測：母頁也用了 → 印得出來；母頁沒用 → 印成空白。 */
/* ══════════════════════════════════════════════════════════════════
   📖 課本頁碼　`.pg`　（老師 2026-08-11 指定放右上）

   > 老師 2026-08-18：「引文頁碼是放在右上角的，不用一條一條引用引自哪裡，
   >   刪掉現在那些引用文字，還可以放大整個投影片可用區域」

   ⛔⛔ **這條 2026-08-18 才搬進 deck.css。**在那之前它**只存在 `漫畫.css` 裡**，
   而本檔上面 `.info` 那一節卻一直在教人「要插在 `.pg` 之前」——
   ⇒ 沒有用漫畫佈景的頁面寫了 `<div class="pg">` 會**完全沒有樣式**（一坨裸字），
     而且螢幕上就看得到，只是沒有人去寫過。⭐ 現在 base 在這裡，
     `漫畫.css` 那份只多加手繪外框，兩邊不衝突（它載得比較後面）。

   用法：
     <div class="pg"><b>20</b><b>21</b></div>      ← 小字「課本頁碼」由 CSS 自己加
   ⭐ 數字可以有好幾個，`.pg b + b` 會自動補「・」。
   ⭐ 位置在**右上**（老師 2026-08-11 從左上改過來的）：標題是從左邊開始讀的，
     頁碼擺左上會先撞到眼睛。
   ⚠ 用 `float:right` 不用 absolute——頁碼有一個數字還是三個寬度不一樣，
     絕對定位就得替標題硬寫 padding，數字一多就壓到字（實測「29・30」壓到標題）。
   ⭐ 放在 flex 容器（例如頁面自己的 `.infos`）裡也可以，float 會被忽略，排版照樣對。
   ══════════════════════════════════════════════════════════════════ */
.pg{
  float:right; margin:0 0 .4cqh 1.1cqh;
  display:flex; align-items:baseline; gap:.34em;
  border:var(--w-hair) solid var(--line-strong); border-radius:.6cqh;
  padding:.35cqh 1.0cqh .5cqh;
  background:var(--card);
}
.pg::before{
  content:"課本頁碼";
  font-family:var(--f-ui); font-size:2.0cqh; color:var(--dim);
  letter-spacing:.04em; align-self:center;
}
.pg b{
  font-family:var(--f-head); font-weight:400;
  font-size:4.0cqh; line-height:1; color:var(--ink);
}
.pg b + b::before{ content:"・"; color:var(--dim); font-size:.6em; margin:0 .05em }

/* ⚠ `.pg` 插在頁首正前方時，容器要加 `class="haspg"`，讓頁首後面那一段清掉浮動，
   否則第三個元素（副標、圖）也會跟著往內縮。
   ⚠ 頁碼在右上 ⇒ 一定要 `clear:right`；`clear:left` 清不掉右浮動。 */
.haspg > .pg + * + *{ clear:right }
.hd.offset{ padding-right:21cqh }   /* 手動出口：不想用 float 時自己留右邊距 */

/* ══════════════════════════════════════════════════════════════════
   ⓘ 補充按鈕　`.info`　（老師 2026-08-11 指定）

   > 「那個年代問題和原本投影片裡的註解都放到投影片右上角給一個 fa-info 的按鈕，
   >   把他們都交給外部的 drawer 打開，這樣投影片的可用版面空間就可以更大了」

   用法——⛔ **要插在 `.pg`（課本頁碼）之前**，兩個都靠 float 貼右上角：

     <div class="hd haspg">
       <button class="info" type="button" onclick="deck.drawer('註解與出處')">…</button>
       <div class="pg"><b>8</b></div>
       <div class="h1">標題</div>
       …
     </div>
     <aside class="drawer" data-title="註解與出處"> … </aside>   ← ⛔ 要在 .slide 外面

   ⚠ **順序不可以顛倒**：`.haspg > .pg + *` 那條規則靠「頁碼的下一個元素就是頁首」，
     把 `.info` 插到 `.pg` 後面會讓標題不讓開、直接壓在頁碼上。

   ⭐ 圖示是**手畫的 inline SVG**，不是 Font Awesome。
     FA 只在播放器載；為了一顆圖示替每一頁 PDF 塞一套圖示字型不划算，
     而且本專案規定示意圖一律 SVG。外觀是一樣的 ⓘ。

   ⭐ **列印時整顆按鈕消失**（`.noprint`）——PDF 裡抽屜本來就自成一頁，
     再印一顆「點我看更多」的按鈕沒有意義。
   ══════════════════════════════════════════════════════════════════ */
.info{
  float:right; margin:0 0 .4cqh .9cqh;
  width:6.6cqh; height:6.6cqh; padding:0;
  border:var(--w-line) solid var(--line-strong); border-radius:50%;
  background:var(--card); color:var(--dim); cursor:pointer;
  display:flex; align-items:center; justify-content:center;
}
.info:hover, .info:focus-visible{ color:var(--ink); border-color:var(--ink) }
/* ⛔ `position:static; inset:auto` 這兩行不可以拿掉。
   滿版地圖那類頁面自己會寫 `svg{ position:absolute; inset:0; width:100%; height:100% }`，
   那條也會打到按鈕裡的圖示 SVG——**圖示會飛到投影片正中央疊在標題上，
   按鈕只剩一個空圈**（實測，`東亞次分區與遺址` 就是這樣）。
   width/height 靠 `.info svg` 的較高優先權蓋掉了，position 也要一起蓋。 */
.info svg{
  position:static; inset:auto;
  width:60%; height:60%; display:block; fill:currentColor;
}
@media print{ .info{ display:none } }

.drawer{ display:none; font-family:var(--f-body) }
.drawer-title{ font-family:var(--f-body) }

/* ⭐⭐ 印出來接在母投影片後面，**裝不下就自己續頁**（老師 2026-08-16：
      「可以像是學術書的書末附錄區一樣自動換頁啊」）。

   ⛔ 2026-08-16 之前這裡是 `height:190.5mm; overflow:hidden; break-inside:avoid`，
      也就是**鎖死一頁、多的字直接剪掉**。實測 40 段的抽屜只印出 8 段，
      掉了 32 段（80%）**而且一個錯都不報**——網頁上照樣捲得到，
      `check_overlap.py` 也看不到它（抽屜在螢幕上是 display:none，探針第一關就濾掉）。
      唯一的發現方式是把 PDF 印出來翻那一頁。

   ⭐ body 那邊的地基本來就鋪好了（見上面 `html,body{ min-height:190.5mm }`），
      被鎖住的只有這個框自己。

   ⛔⛔ **`container-type:size` 一定要拿掉，這是能不能續頁的唯一關鍵。**
      它會連帶套用 `contain:size`＝「這個框的大小不看內容」。
      實測（2026-08-16，40 段測試頁）：
        · 保留它 ＋ 改成 min-height → **抽屜整個塌成 0 高，一段都沒印出來**
        · 拿掉它                    → 7 頁，40 段全在
      ⚠ 代價是 `cqh` 在列印時的參照點換了，字會變大約 22%：
        以前 ＝ 抽屜**內容框**的高（720 − 上下 padding 64×2 ＝ 592px）→ `--t-body` 20.7px
        現在 ＝ 沒有 size 容器，退回頁面框 720px            → `--t-body` 25.2px
      ⭐ 這個改變是**修正不是退步**：抽屜本來就該跟母投影片同字級，
        而舊的 20.7px 讓 `--t-min`（宣告是「學生必須讀到的字，一律不得小於 23px」）
        在抽屜裡只有 18.9px——**專案自己的下限被自己違反了很久沒人發現**。

   ⛔ **`box-decoration-break:clone` 不可以拿掉。**`@page` 的 margin 是 0，
      而 padding 預設只長在**第一片的上緣與最後一片的下緣**。
      實測沒有它的話第 3 頁以後上緣 28px／下緣 711px（紙高 720）＝ 字幾乎貼著紙邊；
      加了它每一頁都是上 48px／下 599px。
   ⚠ **續頁不會重印標題。**Chrome 不支援 `position:running()` ＋ `@top-center`
      那套跑馬頭，CSS 這條路沒有解。要每頁都有標題就**自己拆成兩個 `.drawer`**，
      各給一個 `data-title`（右緣也會多一個頁籤）。 */
@media print{
  .drawer{
    display:block; break-before:page; page-break-before:always;
    width:338.667mm; min-height:190.5mm;
    padding:5% 6%; background:var(--bg);
    container-type:normal;              /* ⛔ 不可以改回 size，理由見上面 */
    -webkit-box-decoration-break:clone; box-decoration-break:clone;
    orphans:2; widows:2;                /* 續頁不要只有孤零零一行 */
  }
  /* 標題後面不換頁——不然標題會單獨留在上一頁的最下面 */
  .drawer-title{ break-after:avoid; page-break-after:avoid }

  /* ⭐ 掛了 `.noprint` 的抽屜不進 PDF（老師 2026-08-20：
        「這些課本內容連結不需要印出 PDF」）。

     ⛔⛔ **一定要寫成 `.drawer.noprint`，不可以只靠上面那條 `.noprint`。**
        兩條都是 (0,1,0) 同權重，而這個區塊的 `.drawer{display:block}`
        寫在**後面** ⇒ 只寫 `.noprint` 會輸，抽屜照樣印出來。
        `.drawer.noprint` 是 (0,2,0)，贏得了。 */
  .drawer.noprint{ display:none }
}

/* ── 抽屜裡的「出處清單」版型 ────────────────────────────────────
   一則＝頁碼＋在課本的哪裡／逐字引文／一句話。
   ⭐ 放在 `deck.css` 不放 `deck-player.css`：這是**抽屜的內容**，
      單張 HTML 打開來也要長這樣，跟有沒有播放器無關。 */
.srclist{ font-family:var(--f-body) }
.src-row{ margin:0 0 1.1em; padding-bottom:.9em;
          border-bottom:var(--w-hair) solid var(--line) }
.src-row:last-child{ margin-bottom:0; padding-bottom:0; border-bottom:none }
.src-hd{ font-family:var(--f-ui); font-size:var(--t-note); color:var(--dim) }
/* ⛔ 宋體不加粗（會產生 Type 3 字型），所以頁碼靠級數與顏色跳出來，不靠 weight。 */
.src-hd b{ font-weight:400; color:var(--ink); font-size:1.3em }
/* ⚠ 引文靠左側標線標示，**不要用「」**：課本原句本身常常帶引號，
   外面再包一層會變成「…設立「國子監」」這種嵌套。 */
.src-q{ margin:.28em 0 .22em; line-height:1.5;
        padding-left:.7em; border-left:var(--w-line) solid var(--line-strong) }
.src-l{ font-family:var(--f-ui); font-size:var(--t-note); color:var(--dim) }

/* 抽屜的標題：**作者自己寫一個 `<h2 class="drawer-title">`**，不要靠 CSS 生成。
   ⚠ 試過 `::before{ content:attr(data-title) }`，**列印時不穩定**——
     實測那一整頁畫出來是全白的（`pdftoppm` 出來 949 bytes）。原因沒追到底，
     但既然作者本來就會想控制標題長相，寫成真的元素更直接也更可控。
   ⭐ 網頁上的頁籤名：有 `data-title` 就用它，沒有就抓第一個標題的文字。 */
.drawer-title{
  font-size:var(--t-h2); font-weight:400;
  color:var(--dim); margin-bottom:.7em;
  border-bottom:var(--w-line) solid var(--line-strong); padding-bottom:.3em;
}
/* ⛔⛔ `⤷` **一定要明寫符號字型**，理由跟上面 `.sym` 那一段一模一樣
   （2026-08-14 實測抓到，而且已經壞了一段時間沒人發現）：
   U+2937 中文字型多半沒有，Chrome 會拿**彩色表情字型**去 shape 整個 text run，
   而那套字型沒有中文 → **整行抽屜標題在 PDF 裡消失**
   （不是白字，是連文字物件都沒有，`pdftotext` 也抽不到）。
   ⚠ 這一條比 `.sym` 更陰，兩個原因：
     ① `::before` 的內容是 CSS 生成的，`工具/wrap_sym.py` **永遠包不到**；
     ② **不是每一頁都壞**——fallback 撿到哪一套字型，跟該頁已經載了哪些字型有關。
        實測 2026-08-14：31 份逐頁 PDF 裡 **14 頁的抽屜標題是空的**
        （封面、東亞的邊界、饅頭、數千年vs百年⋯），另外 23 頁剛好撿到
        Cambria Math 就正常。**「有時候對」比「一直錯」更難發現。**
   ⚠ 螢幕上完全正常，只有印出來才看得到。 */
.drawer-title::before{ content:"⤷ ";
  font-family:"Segoe UI Symbol","Segoe UI Emoji","Noto Sans Symbols 2",
              "Noto Sans Symbols","Noto Sans CJK TC",sans-serif;
  font-variant-emoji:text }

/* ══════════════════════════════════════════════════════════════════
   抽屜裡的排版　`.dnote`（段落）／`.dq`（引文塊）／`.dtb`（對照表）
   （2026-09-01 從各頁的 `<style>` 抽上來）

   > 老師：「我的目標是讓共通元件能直接挪用」

   ⭐ **為什麼抽**：這三個原本在 **54／24／25 張**投影片裡各寫一份，
      而且**差異全部只在小數點**——`.dnote` 的 margin 有 1.2／1.4／1.5cqh 三種、
      `.dtb` 的字級有 2.6／2.65／2.7cqh 三種、`.dq` 有 2.9／2.95／3.0cqh 三種。
      ⇒ 想調抽屜的字級，以前要改 54 個地方，⚠ 而且沒有人知道有 54 個。

   ⚠⚠ **那些小數點不是需求，是走鐘**：方向清一色往下（愈晚做的頁字愈小），
      也就是「這一頁塞不下 → 把字調小一點」。⛔ 那正是「版面裝不下不可以把字改小」
      要防的事，而且在抽屜裡**完全沒有必要**——抽屜的長度本來就沒有上限，
      螢幕上會捲、PDF 裝不下會自動續頁（見上面那一節）。

   ⭐ **實測 359 個 `.dnote`／`.dq`／`.dtb` 全部在 `<aside class="drawer">` 裡，
      0 個例外** ⇒ 這裡刻意寫成裸的 class，跟 `.drawer-title` 同一個慣例。

   ⛔⛔ **頁內要覆寫就只寫差的那一行**，⛔ 不要整份重抄——重抄等於這一節不存在。
      ⚠ 而且覆寫**必須用裸的 class**（`.dnote{ margin:0 0 1.5cqh }`），
      ⛔ 不可以寫成 `.drawer .dnote`：單張 HTML 底下裸的 (0,1,0) 靠**順序**贏
      （`<style>` 在 `<link>` 後面），而 `.drawer .dnote` 是 (0,2,0)，**會反過來
      贏過頁內** ⇒ 單張跟編譯後長得不一樣（編譯後頁內會被加上 `#sNN` 前綴，
      變成 (1,1,0) 又贏回來）。⚠ 這種不一致只有比對兩條路才看得出來。
   ══════════════════════════════════════════════════════════════════ */
.dnote{ font-size:3.0cqh; line-height:1.75; margin:0 0 1.4cqh }

/* 引文塊：淺底 ＋ 左邊一條粗線 */
.dq{ font-size:3.0cqh; line-height:1.7; margin:0 0 1.4cqh;
     padding:.6em .9em; background:var(--card);
     border-left:var(--w-strong) solid var(--line-strong) }

/* ⚠⚠ `.kai` 是漢文引文的楷體。**用它之前先確認母投影片也用了 `--f-kai`**
   ——理由與上面那條「抽屜裡預設用內文字型」一模一樣：字型只有抽屜在用的話，
   Chrome 印 PDF 時那個 @font-face 還在 `font-display:block` 的隱形期，
   ⛔ **整段印成全白**，而螢幕上完全正常。
   ⚠ 2026-09-01 實測：課程端 4 張用了 `.dq .kai`，其中 **3 張的母頁沒有用到楷體**，
   ⭐ 但 `deck pdf` 印出來**四張的楷體引文全部都在**（`pdftotext` 抽得到）
   ⇒ 目前這條管線擋得住（`deck fonts` 會把楷體包進去）。
   ⛔ **但這條警告不要拿掉**——它是拿別的字型實測踩到才寫的，換一個沒被打包的字型照樣會白。 */
.dq .kai{ font-family:var(--f-kai) }

/* 對照表。⚠ **欄寬留在頁面上**（`.dtb th{ width:22% }`）——那是內容決定的，
   實測 20%／22%／24% 三種都有，⛔ 不該由共用檔說了算。 */
.dtb{ width:100%; border-collapse:collapse;
      font-size:2.7cqh; line-height:1.5; margin:0 0 1.4cqh }
.dtb th, .dtb td{ padding:.45em .3em; vertical-align:top; text-align:left;
                  border-bottom:var(--w-hair) solid var(--line) }
.dtb th{ font-weight:400; color:var(--dim) }

/* ══════════════════════════════════════════════════════════════════
   🔄 翻卡：**只有「怎麼翻」在這裡，「翻出來多大」不在**
   （2026-09-01 從各頁抽上來）

   > 老師：「我同意」（把翻卡的機制層抽成共用）

   ⭐ 用法：一個 `.flip` 容器裝兩面（`.front`／`.back`，都掛 `.face`），
      加上 `.on` 就翻過去；`.ret` 是「返回」鈕，只有翻開時才出現。

   ⭐⭐ **這一節刻意只收 `.flip.on` / `.flip:not(.on)` 開頭的規則**——
      也就是「狀態一變、誰該露臉」這件事。實測（2026-09-01，某課程）：

      | `.flip.on .front{transform:rotateY(180deg)}` | 9 張 **1 種寫法** |
      | `.flip:not(.on) .ret{display:none}`          | 6 張 **1 種寫法** |
      | `.flip.on .back{display:flex;…}`             | 9 張 3 種，主流 7/9 |
      | ⛔ `.flip` 自己                              | 11 張 **7 種**（`inset:0` vs `left:3%;top:15%;width:94%`…） |
      | ⛔ `.ret` 自己                               | 10 張 **7 種**（按鈕大小與配色各異） |

   ⇒ **翻的方式是同一件事，翻出來多大、卡片裡怎麼排、返回鈕長什麼樣，
      是那一頁的內容決定的。**⛔ 後兩者不要往這裡搬。

   ⚠ 頁面要覆寫這幾條，**選擇器要一樣長**（`.flip.on .back{…}`），
      ⛔ 寫成裸的 `.back{…}` 蓋不過——共用檔這條是 (0,3,0)。
   ⚠ ⛔ 這裡不寫 `.back{display:none}` 那條裸的基礎樣式：實測有一張
      （`教忠教孝`）的 `.back` 是別的意思、而且沒有 `.flip`，
      收進來會把那一頁的東西藏掉。
   ══════════════════════════════════════════════════════════════════ */
.flip.on .front{ transform:rotateY(180deg) }
.flip.on .back{ display:flex; align-items:center; justify-content:center }
.flip:not(.on) .ret{ display:none }

/* ⭐ 地形底圖的濃度基準（`terrain` 抽出來的彩色 PNG 直接貼上去偏灰）。
   ⚠ 各頁可以自己調——底圖本來就有的飽和度不一樣，實測 13 張裡有
      1.35／1.28／1.22 幾種。⭐ 這裡放的是**基準值**，⛔ 不是要大家一致。 */
.terrain{ filter:saturate(1.35) }

/* ══════════════════════════════════════════════════════════════════
   🔀 diff 版型：**中國是 main 分支，其他國家是改動**
   （2026-09-01 從 5 張抽上來）

   > 老師：「我們投影片中有很多用 git 表示 diff 的版型」

   ⭐ 這是這門課的核心比喻，所以**顏色帶著意義**：
      紅＝這一條被改掉了、綠＝這一國加的、一國一個邊色。
      ⛔ 各頁不一致的話學生會誤讀，⇒ 語意層一定要共用。

   ⭐⭐ **這一節只收「什麼顏色代表什麼」**，實測 5 張**完全一致**的就是這幾條：

   | ⛔ 版面層（各頁不同，留在頁面） | `.diff` 的**欄數**（實測 2／3／4 欄都有）、`.git` 圖示欄寬（12.5／13.5／15.5%）、`.dl` 字級（2.5／3.2cqh）、`.dm` 邊距 |
   |---|---|

   HTML 長這樣：

       <div class="diff">
         <div class="dcol cn">                      ← 一國一欄
           <div class="dhd"><span class="dnm">中國</span>
                            <span class="dtag">原版 main</span></div>
           <div class="dbody">
             <div class="dk">考試方式</div>          ← 欄位名
             <div class="dl base">…</div>            ← 基準行
           </div></div>
         <div class="dcol jp">…
             <div class="dl del"><span class="dm">−</span>…</div>
             <div class="dl add"><span class="dm">+</span>…</div>

   ⬜ **國別的邊色還沒收進來**，因為現場不一致、要老師先裁示：
      日本 `#7d3f8f` 紫 6/6 ✅；⛔ 韓國有橘 `#b8661a`（5 張）與綠 `#1d7a4c`（1 張）
      兩種；⛔ 台灣兩張各用藍 `#1d6b8c` 與橘 `#b8661a`（＝韓國那個橘）。
   ══════════════════════════════════════════════════════════════════ */
.dl.base{ padding-left:0; text-indent:0 }
.dl.del{ color:var(--dim); text-decoration:line-through;
         text-decoration-color:var(--warn); text-decoration-thickness:.14em }
.dl.del .dm{ color:var(--warn); text-decoration:none; display:inline-block }
.dl.add .dm{ color:var(--diff-add) }
.git svg{ width:100%; height:100%; display:block }

/* ── diff 的框：一欄＝一個分支 ─────────────────────────────────────
   > 老師 2026-09-01：「各個 diff 應該是都做成一個類似 tmux 的框，
   >   然後 header 會給和 git fork 同樣的底色（白字），這樣每個 diff 區塊才完整」

   ⭐ 一欄長這樣：**整圈框線 ＋ 實心 header（國別色底、白字）＋ 內文區**。
   ⛔ `overflow:hidden` 不可以拿掉——header 的底色要靠它被圓角切齊。
   ⚠ 這一節管的是「框長什麼樣」；⛔ **欄數、字級、內距仍然留在頁面**
      （實測 `.diff` 有 2／3／4 欄、`.dl` 有 2.5 與 3.2cqh 兩種）。 */
/* ⭐⭐ **三欄的 header 要等高**，否則內文起跑線不一樣，⛔ 橫向對照就讀不成了
   ——而橫向對照正是這個版型存在的理由。
   ⇒ 用 **subgrid**：`.diff` 定兩列（header／body），每一欄跨這兩列並沿用外層的列高。
   ⚠ **欄數仍然在頁面上**（實測 2／3／4 欄都有），這裡只定「列」。
   ⚠ 萬一哪天 subgrid 不能用，退化成各欄自己的 grid：框還在、只是 header 不等高。 */
.diff{ grid-template-rows:auto 1fr }
.dcol{ --nat:var(--line-strong);
       min-width:0; display:grid; grid-row:span 2; grid-template-rows:subgrid;
       border:var(--w-line) solid var(--nat);
       border-radius:.35em }
.dcol.cn{ --nat:var(--nat-cn) }
.dcol.jp{ --nat:var(--nat-jp) }
.dcol.kr{ --nat:var(--nat-kr) }
.dcol.tw{ --nat:var(--nat-tw) }
.dcol.vn{ --nat:var(--nat-vn) }

/* ⛔⛔ **`.dcol` 不可以用 `overflow:hidden` 把 header 的底色切齊圓角。**
   2026-09-01 做這一版時當場踩到：那樣裝不下的內容會**被框靜靜裁掉**，
   而 `check_overlap` **看不到**——它量的是有沒有溢出 `.slide`，
   ⚠ 框內部的裁切不在它的視野裡 ⇒ 等於自己造了一個新的「安靜壞掉」。
   ⭐ 改成 header 自己帶上緣圓角，內容裝不下就**露出來給人看見**。 */
.dhd{ background:var(--nat); color:#fff;
      display:flex; align-items:baseline; gap:.7em; flex-wrap:wrap;
      padding:.40em .7em;
      border-radius:calc(.35em - var(--w-line)) calc(.35em - var(--w-line)) 0 0 }
.dhd .dnm{ color:#fff }                      /* 國名 */
/* ⛔ header **只放國名，不放副標**（老師 2026-09-01：「刪掉 diff 區塊 header 的
   『副標題』，這樣排版就會夠用了」）。⭐ 「誰是 main」靠**顏色**講：
   中國是中性的墨灰，有顏色的才是改動。
   ⚠ `.dtag` 的白字規則留著是為了萬一要加回來，⛔ 但現在沒有頁面在用。 */
.dhd .dtag{ color:rgba(255,255,255,.86) }
.dbody{ flex:1 1 auto; min-height:0; padding:.5em .7em .7em }

/* ══════════════════════════════════════════════════════════════════
   🧩 各頁抄最多次的幾條（2026-09-01 抽上來）

   ⭐ 共同點：**都是「某個容器**裡面**的東西」**——容器自己各頁不同
      （`.punch` 46 張有 14 種寫法、`.q` 14 張 8 種、`.cols` 15 張 8 種），
      ⛔ 所以只收子規則，容器留在頁面。⚠ 這樣沒有 `.punch`／`.q`／`.cols`
      的頁完全不受影響。
   ══════════════════════════════════════════════════════════════════ */

/* 滿版底圖。⚠ 實測 15 張寫的一字不差，而且**沒有任何一頁**有直接子 svg 卻不要滿版。
   ⛔ 不可以寫成裸的 `svg{…}`：頁內 CSS 編譯後會加 `#sNN` 前綴，
      優先權壓過 `.info svg`，ⓘ 按鈕裡的圖示會被吸到標題後面（2026-08-13 踩過）。 */
.slide > svg{ position:absolute; inset:0; width:100%; height:100% }

/* 右上角好幾顆 ⓘ／頁碼並排時，把它們的 float 收掉交給 flex 排。
   ⚠ `.infos` 自己的定位留在頁面（右上、右下、貼著標題都有）。 */
.infos .info, .infos .pg{ float:none; margin:0 }

/* 多欄容器裡的每一欄。⚠ `.cols` 自己（幾欄、間距）留在頁面。 */
.cols > *{ flex:1 1 0; min-width:0 }

/* 斷句／重點句裡的 `<em>`：⛔ **不要黃螢光**，改用主題的強調色。
   ⚠ 上面 `--accent` 的註解寫著「章名頁以外別用」，但實測 **22 張**的
      `.punch em` 就是在用它 ⇒ ⬜ 那條註解跟現況不符，等老師確認要改哪一邊。 */
.punch em{ font-style:normal; color:var(--accent); font-weight:700 }

/* 提問區塊裡的粗體與行內程式碼 */
.q b{ color:var(--accent) }
.q code{ background:var(--hl); padding:0 .22cqw }

/* ⭐⭐ **技術教學那條線的抽屜小一級**（`data-theme="term"`，2026-09-01）。

   > 老師：「為什麼 tech 版和 hist 版也在共有元件？」

   ⭐ 技術頁的抽屜裝的是 prompt 與程式碼，比歷史頁的註解密 ⇒ 它本來就該有自己的一組值。
   ⚠ 這不是新規定，是**把既有的現況搬上來**：實測課程端 15 張還留著頁內 `.dnote` 的，
      **全部**是 `data-theme="term"`，而且其中 11 張用的就是下面這組。
   ⛔ 剩下 4 張用的是歷史線那組（3.0cqh／1.5cqh）——**同一條線內部兩套值**，
      那是走鐘，一起收進來。

   ⭐ 為什麼掛在 `[data-theme="term"]` 而不是讓各頁 override：主題是**整條線**的屬性，
      寫在這裡，以後新做的技術頁自動就對；靠各頁 override 則是每新增一頁就再抄一次。
   ⚠ 優先權：`[data-theme="term"] .dnote` 是 (0,2,0)，蓋得過上面的 (0,1,0)；
      而頁面真的要再覆寫時，編譯後的 `#sNN .dnote` (1,1,0) 仍然贏得了它。
      ⛔ 但單張 HTML 底下頁內是 (0,1,0)、會輸——⇒ **技術頁不要再寫頁內 `.dnote`**，
      要調就調這裡。 */
[data-theme="term"] .dnote{ font-size:2.9cqh; line-height:1.7; margin:0 0 1.2cqh }

/* ══════════════════════════════════════════════════════════════════
   ❓ 互動問題　`.quiz`（老師 2026-08-12）

   > 老師：「加入新功能：互動問題，支持單選、複選，這算是一個課堂互動功能，
   >        就先存到 localstorage（還沒想到其他用途），
   >        問題會像是 info 區塊一樣放在 pdf 輸出」
   > 老師（追問後定案）：「是 info 的 drawer 模式，但是由下到上彈出」

   ⭐ **跟抽屜同一套規矩，只有彈出方向不一樣**：
        · 網頁上 → 由下往上彈出的面板（抽屜是從右邊拉出來）
        · PDF 裡 → **自己一頁**，接在母投影片後面

   ⛔⛔ **`.quiz` 要寫在 `.slide` 外面，當它的兄弟。**理由跟抽屜一模一樣：
        `.slide` 是 `overflow:hidden` ＋ 固定 190.5mm 高，放在裡面
        `break-before:page` 完全不生效，而且**印出來才會發現**。
        編譯器會擋（`deck_compile.py` 的 `wire_quiz`），但單張模式沒人擋。

   寫法：
       <div class="slide">
         <div class="hd haspg">
           <button class="ask" type="button" onclick="deck.quiz('隨堂問題')">…</button>
           <div class="pg"><b>8</b></div>
           <div class="h1">標題</div>
         </div>
         …
       </div>
       <aside class="quiz" data-title="隨堂問題" data-type="single" data-answer="b">
         <h2 class="q-title">隨堂問題</h2>
         <p class="q-ask">下列哪一個最早？</p>
         <ol class="q-opts">
           <li data-key="a">甲</li>
           <li data-key="b">乙</li>
         </ol>
         <div class="q-why">因為……</div>
       </aside>

   ⭐ `.ask` 按鈕是**選填的**：播放器本來就會在畫面下緣長一個「❓ 隨堂問題」的
     頁籤出來，所以不放按鈕也叫得出來。要放就跟 `.info` 同一個位置、同樣規矩
     （⛔ 插在 `.pg` 之前）。

   ⚠ **內層一律用 `q-` 開頭，不要叫 `quiz-`。**編譯器與文字版都是用
     `class="…\bquiz\b…"` 找這個區塊的，而 `\b` 認得 `quiz-title` 裡的 `quiz`
     ——那會讓標題被當成另一個問題區塊。抽屜是靠「只認 aside/div」躲過同一個坑。

   ⛔ **列印時不透露答案。**`.q-why`、正解標記、作答痕跡在 PDF 裡預設全部不印
     ——印出來的 PDF 是老師上課投出去的備援，先把答案印在上面等於這題白出。
     要印解答版：在 `.quiz` 上加 `data-print-answer="1"`。
   ══════════════════════════════════════════════════════════════════ */

/* ❓ 按鈕：跟 `.info` 同一個模子，只換圖示。

   ⛔⛔ **選擇器一定要寫 `button.ask`，不可以只寫 `.ask`**（2026-08-13 修）。
   `ask` 是內容作者很自然會用的類名——高二歷史那份就有兩頁早就在用
   `<div class="ask">` 當「反問區塊」（饅頭、技術_設定專案目錄）。
   只寫 `.ask` 的話，`width:6.6cqh` ＋ `float:right` ＋ `border-radius:50%`
   會把那條滿版的結論帶壓成一顆 47px 的圓鈕，**而且是整條跑出畫面左緣**。
   ⚠ 頁面自己的 `.ask{position:absolute;left:…;right:…}` 擋不住這些屬性，
     因為它沒有覆寫 width／float／display。
   ⚠ 編譯器的 `#sNN` 前綴只解決**頁與頁之間**的撞名，`deck.css` 是全域的，
     單張 HTML 與編譯後都會吃到。
   ⛔ `position:static; inset:auto` 跟 `.info svg` 一樣不可以拿掉，
      理由見上面 `.info svg` 那段（滿版地圖頁會把圖示吸走）。 */
button.ask{
  float:right; margin:0 0 .4cqh .9cqh;
  width:6.6cqh; height:6.6cqh; padding:0;
  border:var(--w-line) solid var(--line-strong); border-radius:50%;
  background:var(--card); color:var(--dim); cursor:pointer;
  display:flex; align-items:center; justify-content:center;
}
button.ask:hover, button.ask:focus-visible{ color:var(--ink); border-color:var(--ink) }
button.ask svg{
  position:static; inset:auto;
  width:60%; height:60%; display:block; fill:currentColor;
}
@media print{ button.ask{ display:none } }

/* ⚠⚠ 字型鎖 `--f-body`，這不是美感選擇。跟抽屜同一個坑：字型如果**只有這一區
   在用**、母投影片沒用到，Chrome 印 PDF 時那個 @font-face 還在
   `font-display:block` 的期間（文字隱形），**整頁印成全白**，而螢幕上完全正常。
   `--f-body` 綁在 body 上，一定載得到。 */
.quiz{ display:none; font-family:var(--f-body) }

.q-title{
  font-family:var(--f-body);
  font-size:var(--t-h2); font-weight:400;
  color:var(--dim); margin-bottom:.7em;
  border-bottom:var(--w-line) solid var(--line-strong); padding-bottom:.3em;
}
/* ⛔ 跟 `.drawer-title::before` 同一個坑，同一個解法：`❓` 是表情符號，
   不明寫字型的話它會把整行題目標題一起拖進彩色表情字型，**在 PDF 裡整行消失**。
   ⚠ 目前抽查到的頁面剛好都沒中，但那是 fallback 撿對了字型，不是這裡沒風險。 */
.q-title::before{ content:"❓ ";
  font-family:"Segoe UI Symbol","Segoe UI Emoji","Noto Sans Symbols 2",
              "Noto Sans Symbols","Noto Sans CJK TC",sans-serif;
  font-variant-emoji:text }
.q-ask{ margin:0 0 .8em; line-height:var(--lh-body) }

.q-opts{ list-style:none; margin:0; padding:0;
         display:flex; flex-direction:column; gap:.45em }
/* 選項前面那個空心圈／空心方塊是 `::before` 畫的——**投影片裡的文字一個字都沒動**，
   所以文字版（順序.md）與 PDF 讀起來都是乾淨的題目。 */
.q-opts > li{
  position:relative; padding:.35em .6em .35em 2.2em;
  border:var(--w-line) solid var(--line); border-radius:.3em;
  cursor:pointer; line-height:var(--lh-body);
}
.q-opts > li::before{
  content:"○"; position:absolute; left:.7em; top:.35em; color:var(--dim);
}
.quiz[data-type="multi"] .q-opts > li::before{ content:"☐" }
.q-opts > li:hover{ border-color:var(--line-strong) }
.q-opts > li.q-pick{ border-color:var(--tr); background:var(--card) }
.q-opts > li.q-pick::before{ content:"●"; color:var(--tr) }
.quiz[data-type="multi"] .q-opts > li.q-pick::before{ content:"☑" }
/* 評分之後：正解標出來、選錯的那一個淡掉。⭐ 只有 `data-answer` 有寫才走到這裡。 */
.quiz.q-graded .q-opts > li.q-right{ border-color:var(--tr) }
.quiz.q-graded .q-opts > li.q-right::after{ content:" ✔"; color:var(--tr) }
.quiz.q-graded .q-opts > li.q-wrong{ border-color:var(--dim); opacity:.72 }
.quiz.q-graded .q-opts > li.q-wrong::after{ content:" ✘"; color:var(--dim) }

/* 送出／重做／答對答錯。⭐ 這一列是 quiz.js 執行期長出來的，不寫在投影片裡。 */
.q-bar{ display:flex; align-items:center; gap:.8em; flex-wrap:wrap; margin-top:.9em }
.q-bar:empty{ margin-top:0 }
.q-send, .q-again{
  font:inherit; font-family:var(--f-ui); font-size:.85em; cursor:pointer;
  padding:.3em .9em; border-radius:.3em;
  border:var(--w-line) solid var(--line-strong);
  background:var(--card); color:var(--ink);
}
.q-send[disabled]{ opacity:.45; cursor:default }
.q-mark{ font-family:var(--f-ui); font-size:.9em }
.q-mark.ok{ color:var(--tr) }
.q-mark.no{ color:var(--dim) }
.q-nostore{ font-family:var(--f-ui); font-size:.75em; color:var(--dim) }

/* 解說：答完才出現。⚠ 用 `display:none` 不是 `visibility`——沒作答時
   它不該佔位置，否則面板一開就先空一大塊。 */
.q-why{ display:none; margin-top:.9em; padding-top:.7em;
        border-top:var(--w-line) solid var(--line); color:var(--dim);
        line-height:var(--lh-body) }
.quiz.q-graded .q-why{ display:block }

@media print{
  /* ⭐ 印出來就是接在母投影片後面的獨立頁，**跟抽屜同一套規則，含自動續頁**。
     ⛔ 這一段跟 `.drawer` 那一段必須一起改——2026-08-16 之前兩邊都是
        「鎖死一頁 ＋ overflow:hidden」，選項一多就靜靜被剪掉。
        為什麼要 `container-type:normal` 與 `box-decoration-break:clone`，
        整套理由寫在上面 `.drawer` 的 `@media print` 前面，不重複。
     ⚠ 題目續頁尤其要小心：被剪掉的通常是**最後幾個選項**，
        而少一個選項的題目看起來仍然是一題正常的題目。 */
  .quiz{
    display:block; break-before:page; page-break-before:always;
    width:338.667mm; min-height:190.5mm;
    padding:5% 6%; background:var(--bg);
    container-type:normal;
    -webkit-box-decoration-break:clone; box-decoration-break:clone;
    orphans:2; widows:2;
  }
  /* 題幹跟第一個選項不要被拆到兩頁 */
  .q-title, .q-ask{ break-after:avoid; page-break-after:avoid }
  .q-opts > li{ break-inside:avoid; page-break-inside:avoid }
  /* ⛔ 不印答案，也不印作答痕跡。PDF 是要投出去的。 */
  .q-bar{ display:none }
  /* ⭐ 解答版：`data-print-answer="1"` 才印正解與解說。
     ⚠ 正解靠的是 quiz.js 掛的 `.q-key`（跟有沒有人作答無關）——
       所以**這一頁一定要載 quiz.js**，不然這個屬性寫了等於沒寫。 */
  .quiz[data-print-answer="1"] .q-why{ display:block }
  .quiz[data-print-answer="1"] .q-opts > li.q-key{ border-color:var(--tr) }
  .quiz[data-print-answer="1"] .q-opts > li.q-key::after{ content:" ✔"; color:var(--tr) }
  .quiz:not([data-print-answer="1"]) .q-why{ display:none }
  .quiz:not([data-print-answer="1"]) .q-opts > li{
    border-color:var(--line); background:none; opacity:1 }
  .quiz:not([data-print-answer="1"]) .q-opts > li::before{ content:"○"; color:var(--dim) }
  .quiz:not([data-print-answer="1"])[data-type="multi"] .q-opts > li::before{ content:"☐" }
  .quiz:not([data-print-answer="1"]) .q-opts > li::after{ content:none }
  /* ⛔⛔ 上面那條擋不住 `.quiz.q-graded .q-opts > li.q-right::after`——
     那條在 @media print 外面，權重 (0,4,2) 比它的 (0,3,2) 高，
     有人作答過再列印就會把答案印出去。這一條同權重又排在後面，才蓋得掉。 */
  .quiz:not([data-print-answer="1"]).q-graded .q-opts > li::after{ content:none }
}

/* ══════════════════════════════════════════════════════════════════
   動畫的最終狀態（2026-08-11 老師定：「如果 user 要列印，就會通知內容區
   立刻進入動畫的最終狀態」）

   ⭐⭐ 作法上的硬規矩：**元素的靜態 CSS 就是動畫「結束」的樣子**，
        `@keyframes` 只負責「從別的樣子過來」（寫 from，不要寫 to）。
        這樣把 animation 關掉，剩下的自然就是最終狀態，不必再維護第二份。

   ⛔ 反過來寫（靜態是起點、動畫跑到終點）會害你**印出來永遠是起點**，
      而且在螢幕上看起來完全正常，你不會發現。
   ══════════════════════════════════════════════════════════════════ */

/* ① 列印時一律停掉動畫。**這條不需要 deck.js**——
      headless Chrome 的 --print-to-pdf 走的就是 print media，
      所以就算某頁忘了載 deck.js，CSS 動畫還是會停在最終狀態。 */
@media print{
  *, *::before, *::after{
    animation: none !important;
    transition: none !important;
  }
}

/* ② deck.js 掛上來的旗標：?final=1、Ctrl+P、或播放器要求列印時。
      這條管的是「螢幕上也要立刻跳到最終狀態」的情況。 */
:root[data-deck-final] *,
:root[data-deck-final] *::before,
:root[data-deck-final] *::after{
  animation: none !important;
  transition: none !important;
}

/* ══════════════════════════════════════════════════════════════════
   身分：投影片內容跟著「老師／學生」變（老師 2026-09-03）

   > 「我想讓投影片內容根據權限（老師／學生）出現改變」

       <aside data-role="teacher">只有老師看得到</aside>
       <p     data-role="student">不是老師的人都看得到</p>

   ⛔⛔⛔ **這不是權限，是「顯示不顯示」。**老師版的內容**還在每一個學生
      下載到的那份 HTML 裡**——`Ctrl`＋`U` 就看得到。
      ⇒ ⛔ 不可以放正解、成績、還沒公布的名單、任何被看到會出事的東西。
      ⭐ 真的要藏就別寫進投影片：用 `deck.me` 判斷身分之後去打一支
        **自己驗身分的 API**（`/__api/admin/quiz` 那條路已經有樣板可抄）。
      ⚠ 完整說明在 `docs/API-身分與權限.md`。

   ⭐ `student` 的語意是「**不是老師的人**」，⛔ 不是「登入的學生」。
      那是刻意的：沒有身分的三種場合（單張預覽、工作目錄 caddy、PDF）
      拿到的都是同一份、安全的**基本版**。

   ⛔⛔ **預設的方向不可以顛倒。**`teacher` 那一段預設就是藏著的，
      拿到身分才顯示——⚠ 反過來寫（先顯示、拿到身分再藏）的話，
      **學生會看到老師版閃一下**，而身分是 `/__api/me` 非同步回來的，
      那個空窗有好幾百毫秒。⭐ 投影出來的話，閃的是整間教室都看得到的那一下。

   ⛔⛔ **要顯示的那一邊一個 `display` 都不可以寫。**
      寫 `display:revert`／`block` 會把作者自己的 `flex`／`grid` 蓋掉，
      ⚠ 而那在畫面上是「版面突然塌了」，跟身分完全看不出關係。

   ⛔ `!important` 不可以省：頁內 CSS 被編譯器加了 `#sNN` 前綴（id 的優先度），
      沒有它的話頁面自己的規則會贏，⚠ 那一段就藏不住。
   ══════════════════════════════════════════════════════════════════ */

/* ⭐ 旗標掛在 `:root`（＝`<html>`），⛔ 不是 body——理由跟上面那個
   `data-deck-final` 一模一樣：`deck.js` 是在 `<head>` 裡執行的，
   ⚠ 那時候 `document.body` **還不存在**。 */

/* ① 沒有老師身分（＝預設）：老師版藏起來。 */
:root:not([data-deck-role="teacher"]) [data-role="teacher"]{ display:none !important }

/* ② 有老師身分：學生版藏起來。 */
:root[data-deck-role="teacher"] [data-role="student"]{ display:none !important }

/* ③ ⛔⛔ 列印時**一律用學生版**，不管現在登入的是誰。
      ⚠ 理由跟 `data-print-answer` 一樣：2026-08-16 起 PDF 是**任何人打開
      播放器按一下**就印得出來的，而印出來那一份就是會被發下去的東西。
      ⇒ ⛔ 沒有「印出老師版」的選項。要的話得先想清楚誰印得出來。
      ⭐ 這一條**非在 deck.css 不可**：`deck pdf --all` 走的是 `file://` 的
        單張 HTML，根本不載 `deck-player.css`。 */
@media print{
  [data-role="teacher"]{ display:none !important }
}

/* ══════════════════════════════════════════════════════════════════
   🔒 鎖定頁（老師 2026-09-27）

   > 老師：「如果這一頁還沒有解鎖，用戶應該會看到：本頁內容已被鎖定，
   >        你需要完成下面的條件才會解鎖／列印的時候也會看到鎖定狀態」

   作者寫在 `.slide` 上（⛔ 不是 body——這樣單張、播放器、PDF 是同一個元素，
   編譯器不必搬）：

       <div class="slide" data-lock="蘇聯副本"
            data-lock-cond="在「大和撫子」那一頁選錯……">

   別頁用 `deck.unlock("蘇聯副本")` 解鎖；解鎖後播放器在 `.slide` 掛 `data-unlocked`。

   ⭐ 三個場合：
     · 單張 HTML（作者預覽）：⛔ **不蓋**，不然作者看不到自己在做的頁。
     · 播放器：沒解鎖就蓋（開關在 `deck-player.css`，那支只有播放器載）。
     · 列印：沒解鎖就蓋。⭐ 這條非在 deck.css 不可——`deck pdf --all` 走的是
       單張 HTML，根本不載 `deck-player.css`；而那條路沒有 localStorage
       ⇒ 一律印成鎖定狀態。播放器按 PDF 則照「這台機器解鎖了沒」印。
   ⚠ 用 `::before`／`::after` 而不是另插元素：沒有 JS 也蓋得住（PDF 那條路
     不一定載 deck.js）。`::after` 會蓋掉 crt 主題的掃描線——鎖定時本來就看不到內容。
   ⛔ `!important`：頁內 CSS 被加了 `#sNN` 前綴，權重比這裡高。
   ══════════════════════════════════════════════════════════════════ */
.slide[data-lock]:not([data-unlocked])::before,
.slide[data-lock]:not([data-unlocked])::after{ display:none }
.slide[data-lock]:not([data-unlocked])::before{
  content:"🔒\A本頁內容已被鎖定，你需要完成下面的條件才會解鎖" !important;
  white-space:pre-line !important;
  position:absolute !important; inset:0 !important; z-index:2147483000 !important;
  align-items:flex-start; justify-content:center; text-align:center;
  padding-top:24cqh !important;
  background:var(--bg) !important; color:var(--ink) !important;
  font-family:var(--f-head); font-size:5.2cqh; line-height:1.6;
  pointer-events:auto;
}
.slide[data-lock]:not([data-unlocked])::after{
  content:attr(data-lock-cond) !important;
  white-space:pre-line !important;
  /* ⛔ `bottom:auto` 不可以省：crt 佈景的 `.slide::after` 用 `inset` 四邊定死，
     只蓋上左右三邊的話條件框會一路撐到頁底。 */
  position:absolute !important; left:18% !important; right:18% !important; top:58cqh !important;
  bottom:auto !important;
  z-index:2147483001 !important;
  padding:2.4cqh 3cqh !important; text-align:left;
  border:var(--w-line) dashed var(--line-strong) !important;
  background:var(--card) !important; color:var(--ink) !important;
  font-family:var(--f-body); font-size:var(--t-body); line-height:var(--lh-body);
}
@media print{
  .slide[data-lock]:not([data-unlocked])::before{ display:flex !important }
  .slide[data-lock]:not([data-unlocked])::after{ display:block !important }
}
