/* ==========================================================================
 * web.css — 使用者端專屬樣式（V1）
 *
 * ZH: 為什麼另開這一支：`styles.css` 是**與管理端逐位元組相同的共用檔**
 *     （`scripts/check_shared_ui_files.py` 守著）。把只有使用者端會用到的
 *     樣式塞進去，等於讓管理端也載入一堆永遠用不到的規則，
 *     而下一個看到的人會以為管理端也有這些元件。
 *     管理端有 `admin.css` 放專屬樣式，使用者端在此之前沒有對應的檔案。
 *
 * ZH: 目前只有小基（浮動助手）。以後其他使用者端專屬的樣式也放這裡。
 * ========================================================================== */

/* ── 小基：浮動助手 ────────────────────────────────────────────────────
 * ZH: 2026-08-28 從 V0.5 移植。**沒有沿用 V0.5 的 CSS，是照 V1 的 token 重寫的**：
 *     V0.5 那份是深色主題（`--bg-solid: #111118`、文字純白），
 *     直接搬過來會變成淺色平台上掛一個黑色面板。
 *     class 名稱一個都沒改，所以 JS 不需要跟著動。
 *
 * ZH: FAB 原本是寫死的紫色漸層（#6366f1 → #8b5cf6），與平台的黃／藍主題無關。
 *     改用 `--accent`，切換色系時它會跟著變。
 * ─────────────────────────────────────────────────────────────────────── */

/* ── 側欄模式（v3.8）────────────────────────────────────────────────
 * ZH: 擁有者 2026-08-28 裁定：不要小浮窗，要占版面 25~30% 的聊天室。
 *
 * ZH: 為什麼不會擠壓內容：`main` 是 `max-width: 720px` 置中，
 *     實測 2134px 視窗下**左右各空 707px**。側欄開在右邊是用掉本來就空的地方。
 *     開著時 `body` 加 padding-right，頂部列與內容（都在正常流裡）自己往左讓。
 *
 * ZH: 寬度用 clamp：28vw 落在擁有者要的 25~30% 之間，
 *     但兩端要夾住 —— 超寬螢幕上 30% 會變成一片空白的巨大聊天室，
 *     視窗窄時又會壓到內容。
 * ─────────────────────────────────────────────────────────────────── */
:root { --aibot-w: clamp(340px, 28vw, 560px); }

#aibot-root {
    position: fixed;
    right: var(--space-5);
    bottom: var(--space-5);
    z-index: 900;          /* ZH: 低於 modal（那些用 1000+），避免蓋住初次設定彈窗 */
}

/* ZH: 側欄開著時整頁讓位。加 transition 讓它不是瞬間跳掉。
 *
 * ZH: 🔴 class 掛在 `<html>` 而不是 `<body>` —— 這是**刻意的**。
 *     widget 的 JS 在 body 尾端才執行，等它加 class 的時候頁面已經畫好一次了，
 *     於是每換一頁都會看到內容「滑」出去讓位（擁有者 2026-08-28 回報）。
 *     改成由 <head> 裡的一小段 inline script 先把 class 加在 <html> 上，
 *     首次繪製時版面就已經是最終狀態 —— 沒有位移，自然也沒有動畫。
 *     transition 留著是給**使用者自己開關**時用的，那時候該有動畫。
 *
 * ZH: ⚠ 給未來要驗證這一段的人：這條 transition 會讓 `getComputedStyle`
 *     與 `getBoundingClientRect` 在轉場的那 0.18 秒內**回傳起始值**。
 *     我在做這個功能時被它騙了六次 —— 一路以為「padding 沒生效、側欄蓋到內容」，
 *     連注入 `!important` 都還是讀到 0px。
 *     量之前先 `el.style.transition = 'none'` 再強制一次 reflow，才會讀到真值。
 */
html.aibot-docked body { padding-right: var(--aibot-w); }
body { transition: padding-right 0.18s ease; }

@media (prefers-reduced-motion: reduce) {
    body { transition: none; }
}

/* ── 泡泡按鈕 ───────────────────────────────────────────────────────── */
/* ZH: v4.2 換造型（擁有者裁定 2026-09-01）：正圓＋💬 → 對話泡泡形＋🤖。
 *     右下角收小圓角 = 泡泡尾巴指向角落；圖示與面板頭像同一顆，
 *     「按這顆=找小基」的對應更直接。尺寸不動（56px，tap 目標夠大）。 */
#aibot-fab {
    width: 56px;
    height: 56px;
    border-radius: 18px 18px 4px 18px;
    border: 1px solid var(--hairline);
    cursor: pointer;
    background-color: var(--accent);
    color: var(--surface);
    box-shadow: 0 6px 18px rgba(0, 0, 0, 0.18);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 24px;
    line-height: 1;
    transition: transform 0.15s ease, box-shadow 0.15s ease;
}
#aibot-fab:hover { transform: translateY(-2px); box-shadow: 0 10px 24px rgba(0, 0, 0, 0.22); }
#aibot-fab:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
/* ZH: 面板開著時泡泡換成關閉的樣子（JS 會加 .aibot-open）。 */
#aibot-fab.aibot-open { background-color: var(--surface-2); color: var(--text-strong); }
.aibot-fab-icon { pointer-events: none; display: inline-flex; }

/* ── 面板 ───────────────────────────────────────────────────────────── */
/* ZH: 側欄本體 —— 貼右滿高。不是浮窗，所以不要圓角與大陰影，
 *     只留左邊一條分隔線，看起來像頁面的一部分。 */
#aibot-panel {
    position: fixed;
    top: 0;
    right: 0;
    bottom: 0;
    width: var(--aibot-w);
    display: flex;
    flex-direction: column;
    background-color: var(--surface);
    border-left: 1px solid var(--hairline);
    overflow: hidden;
}

/* ZH: 側欄開著的時候不需要泡泡 —— 面板自己有關閉鈕，
 *     兩個都在會讓「現在到底開著沒」變得含糊。 */
html.aibot-docked #aibot-fab { display: none; }
/* ZH: `.aibot-hidden` 是這個 widget 通用的隱藏 class（面板、檔案清單、附檔 chip 都用）。
 *     用 !important 的理由與 `[hidden]` 那條相同：作者寫的任何 display 宣告
 *     都會贏過沒有 !important 的規則，於是「該隱藏卻看得見」。 */
.aibot-hidden { display: none !important; }

/* ── 抬頭 ───────────────────────────────────────────────────────────── */
.aibot-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-2);
    padding: var(--space-3);
    border-bottom: 1px solid var(--hairline);
    background-color: var(--surface-2);
}
.aibot-title { display: flex; align-items: center; gap: var(--space-2); }
.aibot-title strong { display: block; font-size: 0.95rem; color: var(--text-strong); }
.aibot-title small { display: block; font-size: 0.75rem; color: var(--text-muted); }
.aibot-avatar { font-size: 22px; line-height: 1; }

#aibot-close {
    border: 0;
    background: none;
    cursor: pointer;
    font-size: 1rem;
    line-height: 1;
    padding: var(--space-1) var(--space-2);
    color: var(--text-muted);
    border-radius: var(--radius-sm);
}
#aibot-close:hover { color: var(--text-strong); background-color: var(--surface); }

/* ── 模式切換（客服／程式家教）────────────────────────────────────── */
.aibot-modes {
    display: flex;
    gap: var(--space-1);
    padding: var(--space-2) var(--space-3);
    border-bottom: 1px solid var(--hairline);
}
.aibot-mode-btn {
    flex: 1;
    border: 1px solid var(--hairline);
    background-color: var(--surface);
    color: var(--text);
    border-radius: var(--radius-sm);
    padding: 4px var(--space-2);
    font-size: 0.8rem;
    cursor: pointer;
}
/* ZH: 目前模式**不只靠顏色**——同時加粗、換底色（WCAG 1.4.1）。 */
.aibot-mode-btn.aibot-mode-active {
    background-color: var(--accent-soft);
    border-color: var(--accent);
    color: var(--text-strong);
    font-weight: 600;
}

/* ── 對話區 ─────────────────────────────────────────────────────────── */
.aibot-log {
    flex: 1;
    overflow-y: auto;
    padding: var(--space-3);
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    background-color: var(--surface-2);
}
.aibot-bubble {
    max-width: 85%;
    padding: var(--space-2) var(--space-3);
    border-radius: var(--radius-md);
    font-size: 0.88rem;
    line-height: 1.55;
    white-space: pre-wrap;
    overflow-wrap: anywhere;
}
.aibot-user {
    align-self: flex-end;
    background-color: var(--accent-soft);
    border: 1px solid var(--accent);
    color: var(--text-strong);
}
.aibot-assistant {
    align-self: flex-start;
    background-color: var(--surface);
    border: 1px solid var(--hairline);
    color: var(--text);
}
/* ZH: 「正在打字」用不透明度，不要用動畫閃爍 —— 會干擾閱讀，
 *     而且 prefers-reduced-motion 的使用者也不該看到。 */
.aibot-assistant.aibot-typing { color: var(--text-muted); font-style: italic; }
/* ZH: 錯誤用 --error（對 --error-soft 是 5.72，小字 AA 過；數字是算的不是估的）。 */
.aibot-assistant.aibot-error {
    background-color: var(--error-soft);
    border-color: var(--error);
    color: var(--error);
}

/* ZH: 「參考來源」是次要資訊，縮小但仍達 AA（--text 對 --surface）。 */
.aibot-sources {
    font-size: 0.75rem;
    color: var(--text-muted);
    margin-top: var(--space-1);
}
.aibot-sources a { color: inherit; }

/* ── 附加檔案 ───────────────────────────────────────────────────────── */
.aibot-attach { display: flex; align-items: center; gap: var(--space-2); padding: 0 var(--space-3) var(--space-2); }
.aibot-attach-btn {
    border: 1px solid var(--hairline);
    background-color: var(--surface);
    color: var(--text);
    border-radius: var(--radius-sm);
    padding: 2px var(--space-2);
    font-size: 0.78rem;
    cursor: pointer;
}
.aibot-file-chip {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    background-color: var(--surface-2);
    border: 1px solid var(--hairline);
    border-radius: var(--radius-pill);
    padding: 2px var(--space-2);
    font-size: 0.75rem;
    max-width: 60%;
}
.aibot-file-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.aibot-file-clear { border: 0; background: none; cursor: pointer; color: var(--text-muted); padding: 0 2px; }
.aibot-file-clear:hover { color: var(--error); }
.aibot-file-list {
    max-height: 180px;
    overflow-y: auto;
    border: 1px solid var(--hairline);
    border-radius: var(--radius-sm);
    background-color: var(--surface);
    margin: 0 var(--space-3) var(--space-2);
    font-size: 0.8rem;
}
/* ZH: 清單項目是 <button class="aibot-file-item">，狀態句是 .aibot-file-notice。 */
.aibot-file-item {
    display: block;
    width: 100%;
    text-align: left;
    border: 0;
    background: none;
    padding: var(--space-2);
    cursor: pointer;
    color: var(--text);
    font: inherit;
}
.aibot-file-item:hover { background-color: var(--surface-2); }
.aibot-file-notice { padding: var(--space-2); color: var(--text-muted); }

/* ── 輸入列 ─────────────────────────────────────────────────────────── */
.aibot-form {
    display: flex;
    align-items: flex-end;
    gap: var(--space-2);
    padding: var(--space-3);
    border-top: 1px solid var(--hairline);
    background-color: var(--surface);
}
#aibot-input {
    flex: 1;
    resize: none;
    min-height: 38px;
    max-height: 120px;
    border: 1px solid var(--hairline);
    border-radius: var(--radius-sm);
    padding: var(--space-2);
    font: inherit;
    font-size: 0.88rem;
    color: var(--text-strong);
    background-color: var(--surface);
}
#aibot-input:focus { outline: none; border-color: var(--accent); }
#aibot-send {
    border: 1px solid var(--accent);
    background-color: var(--accent);
    color: var(--surface);
    border-radius: var(--radius-sm);
    width: 38px;
    height: 38px;
    cursor: pointer;
    font-size: 1rem;
    line-height: 1;
    flex: 0 0 auto;
}
#aibot-send:disabled { opacity: 0.5; cursor: default; }

/* ── 窄視窗：退回浮動面板 ─────────────────────────────────────────
 * ZH: 1100px 以下時兩側的空白不夠塞側欄（1280px 時每邊只剩 280px），
 *     再 dock 下去就會壓到內容。這裡退回原本的浮動小窗。
 * ZH: 手機目前不開放連線（擁有者 2026-08-28），但**視窗拉窄是隨時會發生的**，
 *     所以這一段不是為了手機，是為了視窗。
 * ─────────────────────────────────────────────────────────────────── */
@media (max-width: 1100px) {
    html.aibot-docked body { padding-right: 0; }
    html.aibot-docked #aibot-fab { display: flex; }
    #aibot-panel {
        position: absolute;
        top: auto;
        bottom: 70px;
        width: 360px;
        max-width: calc(100vw - 2 * var(--space-5));
        height: 520px;
        max-height: calc(100vh - 140px);
        border: 1px solid var(--hairline);
        border-radius: var(--radius-lg);
        box-shadow: 0 16px 40px rgba(0, 0, 0, 0.18);
    }
}

@media (max-width: 480px) {
    #aibot-root { right: var(--space-3); bottom: var(--space-3); }
    #aibot-panel {
        width: calc(100vw - 2 * var(--space-3));
        height: calc(100vh - 120px);
        bottom: 66px;
    }
    #aibot-fab { width: 50px; height: 50px; font-size: 21px; }
}

/* ZH: 尊重「減少動態效果」的系統設定。 */
@media (prefers-reduced-motion: reduce) {
    #aibot-fab { transition: none; }
    #aibot-fab:hover { transform: none; }
}


/* ── 程式實驗室的 GPU 勾選（v3.9）────────────────────────────────
   ZH: 放在 web.css 而不是 styles.css —— 那是兩端逐位元組相同的共用檔，
       管理端沒有這個元素。 */
.lab-gpu {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    /* ZH: 整列可點 —— 只有那個 13px 的方框可點的話，觸控裝置上很難按中
       （--tap-min 是 44px，一個裸 checkbox 遠低於它）。 */
    min-height: var(--tap-min, 44px);
    cursor: pointer;
    font-size: 0.9375rem;
    color: var(--text);
}
.lab-gpu input { width: 18px; height: 18px; cursor: pointer; }

/* ZH: 初次設定對話框裡的語言切換（v3.9）。
 *     靠右，貼在標題上方 —— 那是視線進入這個框的第一個位置，
 *     而只看英文的人需要在讀懂任何一個字之前就找到它。
 * ZH: 為什麼不在 styles.css：那份與 admin 端逐位元組相同，
 *     這個對話框只有使用者端有（見 check_shared_ui_files）。 */
.onb__lang {
    display: flex;
    justify-content: flex-end;
    margin-bottom: var(--space-2);
}
/* ZH: .lang-switch 的 margin-right 是給頂部列用的，在這裡會把它推離右邊界。 */
.onb__lang .lang-switch { margin-right: 0; }

/* ZH: 初始密碼那一列尾端的「複製密碼」（v3.9）。
 *     `.kv` 是 flex + align-items:baseline —— 按鈕用 auto margin 推到最右，
 *     並改回 center 對齊：baseline 會讓按鈕的文字基線去對齊密碼的基線，
 *     而密碼有 padding 與較大字級，對出來按鈕會浮在半空中。
 * ZH: 窄畫面讓這一列可以折行，否則密碼（等寬、1.125rem）會把按鈕擠出畫面。 */
.kv--pw { flex-wrap: wrap; }
.kv__act {
    margin-inline-start: auto;
    align-self: center;
    flex: none;
}

/* ZH: 今日剩餘時間的警示色（v3.9）。門檻 30/10/5 由 lab.js 判定，
 *     這裡只負責上色 —— 判準寫在兩個地方遲早會漂開。
 * ZH: ⚠ 顏色是**次要**線索：數字本身與旁邊那句話都寫著剩幾分鐘（WCAG 1.4.1）。
 *     色盲的人不會因為看不出顏色而錯過這個資訊。 */
#m-remaining[data-warn="notice"] { color: var(--text); }
#m-remaining[data-warn="warn"],
#m-remaining[data-warn="urgent"],
#m-remaining[data-warn="over"] { color: var(--error); font-weight: 600; }

/* ==========================================================================
 * ZH: 首頁的分組卡片（B 方案，v3.9）
 *
 * ZH: 為什麼用 auto-fit 而不是固定欄數：手機自動變一欄，**不用做第二套版面**，
 *     而且之後多一條分支只是多一張卡 —— 這是選 B 的主要理由之一。
 *
 * ZH: 為什麼在 web.css 不在 styles.css：styles.css 與 admin 端逐位元組相同，
 *     這一區只有使用者端有（見 check_shared_ui_files）。
 * ========================================================================== */
.groups { margin-top: var(--space-6); }
.group + .group { margin-top: var(--space-5); }

.group__h {
    margin: 0 0 var(--space-2);
    font-size: 0.9375rem;
    font-weight: 700;
    color: var(--text-strong);
}

.group__cards {
    display: grid;
    gap: var(--space-2);
    /* ZH: 240px 是實測值 —— 再小的話「訓練你的第一個…」那種長標題會斷成三行。 */
    grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
}

.gcard {
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding: var(--space-3) var(--space-4);
    min-height: var(--tap-min);
    justify-content: center;
    border: 1px solid var(--hairline);
    border-radius: var(--radius-md);
    background-color: var(--surface);
    text-decoration: none;
    /* ZH: ⚠ 卡片是連結，但**不要**用 --link 的藍字：一整面藍字讀起來像清單，
     *     而這裡的層級是「標題 + 說明」。顏色交給裡面的兩個 span。 */
    color: inherit;
}
.gcard:hover { border-color: var(--accent); box-shadow: var(--shadow-2); }
.gcard:focus-visible { outline: var(--focus-ring); outline-offset: 2px; }

.gcard__t { font-weight: 600; color: var(--text-strong); }
.gcard__d { font-size: 0.8125rem; color: var(--text-muted); }

/* ==========================================================================
 * ZH: 額度卡的兩行補充說明統一成**文字**（擁有者裁定 2026-08-29）
 *
 * ZH: 原本一行是純文字連結（19px）、一行是外框按鈕（44px），看起來不成套。
 *
 * ZH: 🔴 只在額度卡內覆蓋，**不動全域的 `.handoff a`** ——
 *     那條規則有 7 個頁面在用，其中 gpu.html 那個「彈窗被擋時的備援連結」
 *     是使用者唯一的出路（styles.css 的註解記著它原本只有 18px）。
 *     全域改掉會把那個修正一起還原，而且沒有人會發現。
 *
 * ZH: ⚠ 外觀變成文字，但**可點高度不能跟著掉回 19px**。
 *     用上下 padding 撐到 25px（WCAG 2.5.8 AA 要 24×24）。
 *     inline 元素的垂直 padding 不會撐開行高，所以版面不動、只有命中區變大。
 *     達不到 --tap-min 的 44px 是刻意的：那個高度做不出「像一句話裡的連結」。
 * ========================================================================== */
#balance-card .balance__meta a,
#balance-card .handoff a {
    display: inline;
    min-height: 0;
    margin: 0;
    padding: 3px 2px;
    border: 0;
    border-radius: 0;
    background: none;
    font-weight: 400;
    font-size: inherit;
    color: var(--link);
    text-decoration: underline;
}
#balance-card .balance__meta a:hover,
#balance-card .handoff a:hover {
    background: none;
    color: var(--text-strong);
}

/* ZH: 兩行之間要小間隔 —— 它們是同一類東西（補充說明），
 *     用 --space-3 會看起來像兩個獨立區塊。 */
#balance-card .handoff { margin-top: var(--space-1); }

/* ==========================================================================
 * ZH: 區塊卡（v3.9）—— 與 .primary-card 同一個盒子，但**左對齊**。
 *
 * ZH: 為什麼不直接用 .primary-card：那個是置中的（給「一個大數字＋一顆按鈕」用），
 *     清單與說明文字置中會很難讀。
 * ZH: 為什麼放 web.css：styles.css 與 admin 端逐位元組相同，這個只有使用者端要。
 * ========================================================================== */
.panel {
    margin-top: var(--space-5);
    padding: var(--space-5);
    border: 1px solid var(--hairline);
    border-radius: var(--radius-lg);
    background-color: var(--surface);
}

/* ZH: 卡片內的次要動作（例如「關閉實驗室」）。
 * ZH: 做成文字不是按鈕 —— 卡片裡已經有一顆主要動作，兩顆實心鈕並排會分不出主次。
 *     但可點高度不能跟著掉：用上下 padding 撐到 24px 以上（WCAG 2.5.8 AA）。
 *     與首頁額度卡的兩行說明同一套處理。 */
.card-action { margin: var(--space-3) 0 0; }
.card-action a {
    display: inline;
    padding: 3px 2px;
    color: var(--link);
    font-size: 0.875rem;
    text-decoration: underline;
}
.card-action a:hover { color: var(--text-strong); }

/* ZH: 數字列包成卡片（v3.9）。`.stats` 自己帶 margin-bottom（給沒有盒子的
 *     頁面用的），包進來之後那段會變成盒子底部多出來的空白 —— 抵銷掉。
 * ZH: ⚠ 只在 .stats-card 裡抵銷。train.html 與 usage.html 的 .stats
 *     沒有盒子，那個 margin 在那兩頁仍然是對的。 */
.stats-card .stats { margin-bottom: 0; }

/* ZH: 三步驟列包成卡片（v3.9）。`.steps` 自帶 margin-top（給沒有盒子的頁面用），
 *     包進來之後那段會變成盒子頂部多出來的空白 —— 只在這張卡裡抵銷。 */
.steps-card .steps { margin-top: 0; }

/* ==========================================================================
 * ZH: 導覽列的分類下拉（v3.9）
 *
 * ZH: 放在 web.css 不放 styles.css —— styles.css 與 admin 端逐位元組相同，
 *     而管理端沒有這種下拉，寫過去等於在那邊留一堆用不到的規則。
 * ZH: 選單的隱藏靠 `hidden` 屬性 + styles.css 那條 `[hidden]{display:none!important}`
 *     全域補丁。⚠ 這裡的 `display: flex` 若沒有那條補丁會**贏過** UA 的
 *     `[hidden]{display:none}`，選單就永遠是打開的。
 * ========================================================================== */
.navmenu { position: relative; display: inline-flex; }

/* ZH: 倒三角是 ::after 不是文字 —— 寫進 textContent 的話，
 *     切語言時 prefs.js 重寫 textContent 會把它一起洗掉。 */
.navmenu__toggle::after {
    content: '\25BE';
    margin-left: 4px;
    font-size: 0.75rem;
    color: var(--text-muted);
}
/* ZH: 目前頁在這一組裡。與 .topnav a[aria-current] 同一個視覺語彙。 */
.navmenu.is-current .navmenu__toggle {
    color: var(--text-strong);
    font-weight: 600;
    background-color: var(--accent-soft);
}

.navmenu__menu {
    position: absolute;
    top: calc(100% + var(--space-2));
    left: 0;
    z-index: 20;
    min-width: 12rem;
    padding: var(--space-2);
    background-color: var(--surface);
    border: 1px solid var(--hairline);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-2);
    display: flex;
    flex-direction: column;
}
/* ZH: 選單項要 44px —— 下拉裡的項目最常被做成 28px 高。
 * ZH: ⚠ 選擇器刻意寫成 `.topnav .navmenu__menu a`（兩個 class）。
 *     只寫一個 class 的話與 styles.css 的 `.topnav a` 同分，
 *     要靠「web.css 載得比較晚」才贏 —— 那是會被檔案順序改動打翻的依賴。 */
.topnav .navmenu__menu a {
    display: flex;
    align-items: center;
    min-height: var(--tap-min);
    padding: 0 var(--space-3);
    border-radius: var(--radius-sm);
    color: var(--text);
    font-size: 0.9375rem;
    text-decoration: none;
    white-space: nowrap;
}
.topnav .navmenu__menu a:hover { background-color: var(--surface-2); }
.topnav .navmenu__menu a[aria-current="page"] {
    color: var(--text-strong);
    font-weight: 600;
    background-color: var(--accent-soft);
}
/* ZH: 最右邊那一組往左長 —— 見 chrome.js 裡 navmenu--right 的說明。 */
.navmenu--right .navmenu__menu { left: auto; right: 0; }

/* ZH: 首頁的引導卡（v3.9，擁有者裁定 2026-08-30）——「體驗大模型」。
 *
 * ZH: 為什麼是**低飽和**：這是引導不是警示。滿色的 --accent 在首頁會跟
 *     額度不足的紅卡、警告的黃卡搶同一個視覺強度，而那些是「出事了」，
 *     這一張只是「多數人從這裡開始」。
 * ZH: 為什麼不放在導覽列的下拉裡（那是這一版拿掉的）：下拉要點開才看得到，
 *     引導對「還沒點開任何東西的人」才有用 —— 首頁才是他一定會經過的地方。
 * ZH: ⚠ 顏色是**次要**線索：它旁邊的說明文字本身就講了這是什麼（WCAG 1.4.1），
 *     色盲或高對比模式下少掉這層底色，卡片一樣讀得懂。
 */
.gcard--lead {
    border-color: var(--accent);
    background-color: var(--accent-soft);
}
.gcard--lead:hover { background-color: var(--surface); }

/* ZH: 公告附件（v3.9）。做成按鈕（見 news.js），但視覺上要像可點的東西
 *     而不是主要動作 —— 一則公告可能有好幾個附件，全部做成實心鈕會蓋過內文。 */
.post__files { margin-top: var(--space-3); display: flex; flex-wrap: wrap;
               align-items: center; gap: var(--space-2); }
.post__files-label { font-size: 0.8125rem; color: var(--text-muted); }
.post__file {
    display: inline-flex;
    align-items: center;
    min-height: var(--tap-min);
    padding: 0 var(--space-3);
    border: 1px solid var(--hairline);
    border-radius: var(--radius-md);
    background: transparent;
    color: var(--link);
    font: inherit;
    font-size: 0.875rem;
    cursor: pointer;
}
.post__file:hover:not(:disabled) { background-color: var(--surface-2); border-color: var(--link); }
.post__file:focus-visible { outline: var(--focus-ring); outline-offset: 2px; }
.post__file:disabled { opacity: 0.6; cursor: default; }

/* ==========================================================================
 * ZH: 頂部列固定在畫面上（v3.9，擁有者裁定 2026-08-30）
 *
 * ZH: 用 `sticky` 不用 `fixed`：sticky 仍然佔著版面空間，所以底下的內容
 *     不必補一段 padding 去讓位。**而讓位的高度是會變的** ——
 *     這條列在窄視窗會折成兩三行（實測 375px 時高 175px），
 *     用 fixed 的話那個補償值得跟著算，算錯就是內容被蓋住或多一塊空白。
 *
 * ZH: 放 web.css 不放 styles.css —— styles.css 與管理端逐位元組相同，
 *     而擁有者只要求使用者端。管理端要的話再說。
 *
 * ZH: z-index 30 的位置：高於頁面內容（沒有 z-index），低於小基（900）
 *     與初次設定彈窗（1000）。導覽的下拉與泡泡是 20，它們在這一層**裡面**，
 *     所以照樣蓋得住內容。
 * ZH: .topbar 本來就有底色與下框線 —— 沒有底色的 sticky 會透出底下的字。
 * ========================================================================== */
.topbar { position: sticky; top: 0; z-index: 30; }

/* ZH: v4.1 首頁公告整列可點（news__go 是 <a>，吃掉整列） */
.news__go {
    display: flex;
    align-items: center;
    gap: 8px;
    width: 100%;
    color: inherit;
    text-decoration: none;
}
.news__go:hover .news__title { text-decoration: underline; }

/* ZH: v4.1 公告內嵌圖片。max-width 保版面；圓角與卡片一致。 */
.post__img-wrap { margin: 10px 0; }
.post__img {
    max-width: 100%;
    max-height: 420px;
    border-radius: 8px;
    display: block;
}

/* ZH: v4.2 公告條目清單（點了開彈窗）。head 變 <button> —— 清掉按鈕預設樣式、
 *     整列可點；chevron 是「›」示意可進入。 */
.post__head--btn {
    display: flex;
    align-items: center;
    gap: 8px;
    width: 100%;
    background: none;
    border: 0;
    padding: 0;
    font: inherit;
    color: inherit;
    text-align: left;
    cursor: pointer;
}
.post__head--btn .post__title { margin: 0; flex: 1 1 auto; min-width: 0; }
.post__head--btn:hover .post__title { text-decoration: underline; }
.post__chev { color: var(--text-muted); flex: 0 0 auto; }

/* ZH: v4.2 公告內容彈窗（.nmod）—— 鏡自管理端 .rmod（使用者端不載 admin.css，
 *     所以這裡養自己的一份；改樣式時兩邊獨立，這是刻意的：兩端的視覺語彙本就不同）。 */
.nmod {
    width: min(720px, calc(100vw - 2 * var(--space-5)));
    max-width: none;
    max-height: calc(100vh - 2 * var(--space-5));
    padding: var(--space-5);
    border: 1px solid var(--hairline);
    border-radius: var(--radius-lg);
    background-color: var(--surface);
    color: var(--text);
    overflow-y: auto;
    position: fixed;   /* ZH: 給 .nmod__x 定位基準 */
}
.nmod::backdrop { background: rgb(31 35 40 / 55%); }
.nmod__x { position: absolute; top: var(--space-4); right: var(--space-4); margin: 0; }
.nmod__meta { display: flex; align-items: center; gap: 8px; margin-bottom: var(--space-2); }
.nmod__title { margin: 0 0 var(--space-3); padding-right: 48px; /* ZH: 讓位給 ✕ */ }

/* ZH: v4.2 初次設定的身分單選（老師/職員，擁有者裁定 2026-09-02）。
 *     兩個大目標並排，radio 用原生的 —— 這個彈窗一輩子只看一次，
 *     不值得為它自畫控制項。 */
.onb__radios { display: flex; gap: var(--space-3); }
.onb__radio {
    flex: 1;
    display: flex;
    align-items: center;
    gap: var(--space-2);
    min-height: var(--tap-min);
    padding: 0 var(--space-3);
    border: 1px solid var(--hairline);
    border-radius: var(--radius-md);
    background-color: var(--surface);
    cursor: pointer;
}
.onb__radio:has(input:checked) {
    border-color: var(--accent);
    background-color: var(--accent-soft);
}

/* ZH: v4.3b 身分區的常用信箱行（在學號信箱下面）。 */
.account__id-contact {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    max-width: 240px;
}

/* ==========================================================================
 * ZH: v4.25 首頁：公告（整寬）→ 三主線（1→2→3）→ 到訪人次
 *     （老師回饋「資訊量太多」，擁有者草稿 2026-09-23）
 *
 * ZH: 🔴 這一段取代了 v4.7 的 `.home` 兩欄（公告｜功能卡 各 50%）。
 *     那個版面的已知代價寫在當時的註解裡：公告佔一半，功能卡一排就只放得下
 *     兩張（一半 ≈545px，而卡片的 minmax 是 240px）。現在功能卡只剩三條主線，
 *     它們要的是整頁寬度，所以兩欄那層容器整個拆掉。
 *
 * ZH: ⚠ **「沒有公告時不留空位」這個行為要保住** —— 那是當初選 flex 而不是 grid
 *     的唯一理由。現在公告是普通的 block 區塊，`hidden` 時 `display:none`
 *     自然不佔位，連規則都不需要。
 *
 * ZH: 首頁仍然比其他頁寬（`.page` 是 720px 的閱讀寬度）：三格並排要放得下
 *     標題與一句說明，實測 1160px 時每格約 350px，剛好不會把
 *     「從訓練 AI 辨識貓狗開始，約 20 分鐘」這種句子折成三行。
 * ========================================================================== */
.page--home { max-width: 1160px; }

/* ZH: v4.38 入口頁（gpu.html）拿掉了，原本的 .modes／.gpu-start 一起刪。 */

/* ZH: v4.36 訓練頁的體驗模式：三行「已經替你決定好的事」。 */
.easy {
    margin: var(--space-4) auto 0;
    max-width: 30rem;
    padding: var(--space-3) var(--space-4);
    border: 1px solid var(--hairline);
    border-radius: var(--radius-md);
    background-color: var(--surface-2);
    text-align: left;
}
.easy__row { display: flex; gap: var(--space-3); margin: var(--space-1) 0; }
.easy__k { flex: 0 0 6.5em; color: var(--text-muted); font-size: 0.875rem; }
.easy__v { flex: 1 1 auto; min-width: 0; font-weight: 600; color: var(--text-strong); }
.easy__row:last-child { margin-top: var(--space-2); }
.learn { margin-top: var(--space-4); text-align: left; }
/* ZH: v4.36 模型判斷錯的。資料欄可能很長（一整列表格或一整句），要能折行；
 *     外層可以橫向捲，免得手機上把整頁撐寬。 */
.mis-wrap { overflow-x: auto; }
.mis td:first-child { word-break: break-word; min-width: 12em; }
/* ZH: v4.36 上傳一張圖片試試看。預覽圖在左、判斷結果在右；手機上照樣左右排（圖只有 120px）。 */
.try { margin-top: var(--space-4); text-align: left; }
.try__out { display: flex; gap: var(--space-4); align-items: flex-start; margin-top: var(--space-3); }
.try__img {
    flex: 0 0 auto;
    width: 120px;
    height: 120px;
    object-fit: cover;
    border-radius: var(--radius-md);
    background-color: var(--surface-2);
}
.try__res { flex: 1 1 auto; min-width: 0; }
.try__label { font-weight: 700; color: var(--text-strong); margin: 0 0 var(--space-2); }
.try__row { display: flex; align-items: center; gap: var(--space-2); margin: var(--space-1) 0; }
.try__k { flex: 0 0 6em; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.try__bar { flex: 1 1 auto; height: 10px; border-radius: var(--radius-pill); background-color: var(--surface-2); overflow: hidden; }
.try__bar > span { display: block; height: 100%; background-color: var(--accent); }
.try__v { flex: 0 0 3em; text-align: right; font-variant-numeric: tabular-nums; }
.mis__img {
    display: block;
    width: 72px;
    height: 72px;
    object-fit: cover;
    border-radius: var(--radius-sm);
    margin-bottom: var(--space-1);
    background-color: var(--surface-2);
}

/* ZH: v4.36 訓練結果卡裡的模型檔說明（下載鈕下面）。程式碼區沿用 .diag，只限制寬度不溢出。 */
.model-help { margin-top: var(--space-3); text-align: left; }
.model-help .diag { overflow-x: auto; white-space: pre; }

/* ZH: 公告在首頁是整寬的一條。`.news` 本身的卡片外框在 styles.css（共用檔），
 *     這裡只負責「在首頁要多寬」—— 不動共用檔（它與管理端逐位元組相同）。 */
.news--wide { margin-bottom: var(--space-6); }

/* ── 三主線 ───────────────────────────────────────────────────────────
 * ZH: 三格等寬 + 格間的箭頭。用 grid 而不是 flex：箭頭那一格要能
 *     **只吃自己的寬度**，而三個主格要嚴格等寬（flex 會讓內容多的那格長一點，
 *     而三條主線的份量看起來就不一樣了）。
 */
.lines {
    display: grid;
    grid-template-columns: 1fr auto 1fr auto 1fr;
    align-items: stretch;
    gap: var(--space-2);
    margin-top: var(--space-5);
}

/* ZH: 盒子本身照 `.gcard` 的那一套（同一個平台不要有兩種卡片長相）——
 *     差別只在留白比較大：三條主線是這一頁的主角，它們要撐得起版面。 */
.line {
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
    padding: var(--space-5) var(--space-4);
    min-height: var(--tap-min);
    border: 1px solid var(--hairline);
    border-radius: var(--radius-md);
    background-color: var(--surface);
    text-decoration: none;
    /* ZH: ⚠ 與 `.gcard` 同一個判斷：這是連結但**不要**用 --link 的藍字 ——
     *     一整面藍字讀起來像清單，而這裡的層級是「編號＋標題＋說明」。 */
    color: inherit;
}
.line:hover { border-color: var(--accent); box-shadow: var(--shadow-2); }
/* ZH: focus 樣式不自己寫 —— styles.css:32 的全域 `:focus-visible` 已經給了
 *     box-shadow 的聚焦環。自己再寫一條只會多一個會漂掉的地方。 */

/* ZH: 圓章（v4.33 之前裡面是 1/2/3，現在是圖示）。
 * ZH: ⚠ 內容**不能**寫成 `color: var(--accent)` 的黃字 ——
 *     tokens.css:41 記著那個顏色對白底只有 1.67 對比。
 *     做成「黃底深色內容」的圓章才是這組 token 的正確配對（--on-accent）。
 * ZH: ⚠ 圖示比數字吃空間：28px 的章塞 16px 圖示只剩 6px 邊，線條會貼到圓邊。
 *     所以章放大到 32px、圖示 20px（實測 375px 與字級 150% 都還在一行內）。
 * ZH: ⚠ `flex: 0 0 auto` 是必要的 —— `.line` 是直排 flex，
 *     沒有它的話這一格會被 `align-items: stretch` 拉成整列寬，
 *     圓章變成一條黃色長條（寬度雖然寫死，高度仍會被壓）。 */
.line__n {
    display: inline-flex;
    flex: 0 0 auto;
    align-items: center;
    justify-content: center;
    width: 2rem;
    height: 2rem;
    border-radius: var(--radius-pill);
    background-color: var(--accent);
    color: var(--on-accent);
}
/* ZH: 圖示用 currentColor，所以上面那個 `color` 同時決定圖示顏色。 */
.line__n svg { width: 1.25rem; height: 1.25rem; display: block; }
.line__t { font-weight: 700; }
.line__d { font-size: 0.9375rem; color: var(--text-muted); }

.lines__arrow {
    align-self: center;
    /* ZH: 1.5rem 時這個字元只有 7px 寬，在兩張 355px 的卡片之間幾乎看不見
     *     （實測）。放大並加左右留白，讓它撐得出一個看得出來的欄。 */
    font-size: 1.75rem;
    line-height: 1;
    padding: 0 var(--space-1);
    color: var(--text-muted);
    /* ZH: 箭頭只是裝飾（HTML 那邊有 aria-hidden），不要被選取起來。 */
    user-select: none;
}

/* ZH: 窄畫面改直排。
 * ZH: 🔴 箭頭在這裡**藏起來**而不是轉向下：直排本身就表達得出先後，
 *     而一個朝右的「›」在直排裡指的是錯的方向。
 * ZH: 900px 這個斷點沿用舊的兩欄斷點 —— 三格各 350px 加間距約需 1100px，
 *     900px 以下每格會窄到 280px 以下，標題開始折成三行。
 */
@media (max-width: 900px) {
    .lines { grid-template-columns: 1fr; gap: var(--space-3); }
    .lines__arrow { display: none; }
}

/* ==========================================================================
 * ZH: v4.8 暫停使用的入口（GPU 功能關閉時，見 chrome.js 的 applyGpuGate）。
 *     刻意**不藏起來**：使用者要看得出「功能還在、只是現在不能用」，
 *     藏起來只會讓人以為東西不見了。
 * ZH: `pointer-events: none` 只擋滑鼠；真正讓它點不下去的是 chrome.js
 *     把 href 拔掉（不然鍵盤 Enter 與中鍵開新分頁還是進得去）。
 * ========================================================================== */
.is-off {
    opacity: 0.45;
    cursor: not-allowed;
    pointer-events: none;
    text-decoration: line-through;
}
/* ZH: 卡片式入口刪節號會很醜，改成整張淡掉就好。
 * ZH: ⚠ v4.25 首頁的三主線（.line）也是卡片 —— 漏掉它的話，GPU 功能暫停時
 *     那兩張卡會被畫上刪節號。新增卡片式入口時要記得加進這個選擇器。 */
.gcard.is-off,
.line.is-off { text-decoration: none; background-color: var(--surface-2); }

/* ==========================================================================
 * ZH: 手機版把頂部列右側整塊收進 ☰（v4.13，擁有者需求 2026-09-15）
 *
 * ZH: 在此之前 375px 寬的頂部列會擠成**三排**（LOGO／導覽／語言＋帳號），
 *     四個導覽鈕各自再折成兩行，實測佔掉約 300px 高 ——
 *     等於一開頁先看一整頁的導覽。
 *
 * ZH: 只包一層 `.topbar__tools`：桌面上它是普通的 flex 列（版面與包之前相同），
 *     手機上同一層變成 ☰ 底下展開的面板。不做「依寬度搬節點」，
 *     那要監聽 resize、要處理搬到一半選單正開著，而且搬完事件要重接。
 *
 * ZH: ⚠ 覆寫既有規則時**一律多帶一個 class**（`.topbar__tools .account__menu`），
 *     不靠「web.css 載得比 styles.css 晚」—— 那個依賴會被檔案順序改動打翻
 *     （同一份理由見上面 `.topnav .navmenu__menu a` 的註解）。
 * ========================================================================== */
.topbar__tools {
    display: flex;
    align-items: center;
    gap: var(--space-4);
}
/* ZH: ☰ 只在手機出現；桌面留一顆按不出東西的鈕只會讓人按一次才知道。 */
.topbar__burger { display: none; }

@media (max-width: 640px) {
    .topbar__burger {
        display: inline-flex;
        align-items: center;
        justify-content: center;
        min-width: var(--tap-min);
        min-height: var(--tap-min);
        padding: 0 var(--space-2);
        font-size: 1.25rem;
        line-height: 1;
        color: var(--text);
        background: transparent;
        border: 1px solid var(--hairline);
        border-radius: var(--radius-md);
        cursor: pointer;
    }
    .topbar__burger[aria-expanded="true"] {
        background-color: var(--surface-2);
        border-color: var(--link);
    }

    /* ZH: 面板錨在 .topbar 上。`.topbar` 是 sticky —— sticky 也是一種
     *     positioned，絕對定位的子元素就以它為準，不必另外加 position: relative。 */
    .topbar__tools {
        display: none;
        position: absolute;
        top: 100%;
        left: 0;
        right: 0;
        flex-direction: column;
        align-items: stretch;
        gap: var(--space-1);
        padding: var(--space-3) var(--space-4) var(--space-4);
        background-color: var(--surface);
        border-bottom: 1px solid var(--hairline);
        box-shadow: var(--shadow-2);
        z-index: 25;
        /* ZH: 字級放大或選單全開時可能比螢幕高 —— 給它自己捲，
         *     不要讓面板把頁面撐長（那會出現「關掉選單後頁面還是那麼長」）。 */
        max-height: calc(100vh - 56px);
        overflow-y: auto;
    }
    .topbar__tools.is-open { display: flex; }

    /* ── 面板裡的導覽改直排 ──────────────────────────────────────── */
    .topbar__tools .topnav {
        flex-direction: column;
        align-items: stretch;
        gap: var(--space-1);
        width: 100%;
    }
    .topbar__tools .topnav > a,
    .topbar__tools .navmenu__toggle {
        width: 100%;
        justify-content: flex-start;
    }
    .topbar__tools .navmenu { display: block; }
    /* ZH: 分類下拉在面板裡**就地展開**，不再浮一層 —— 手機上浮層會蓋住
     *     底下其他分類，而使用者是來找東西的，看得到全貌比較好按。
     *     左邊那條線是在說「這幾項屬於上面那一組」。 */
    .topbar__tools .navmenu__menu,
    .topbar__tools .account__menu {
        position: static;
        min-width: 0;
        border: 0;
        border-left: 2px solid var(--hairline);
        border-radius: 0;
        box-shadow: none;
        margin: var(--space-1) 0 var(--space-2) var(--space-3);
        padding: 0 0 0 var(--space-2);
    }
    /* ZH: 帳號選單原本靠 `.account { position: relative }` 定位，
     *     就地展開之後那個相對定位沒有用處，留著反而讓它變成新的錨點。 */
    .topbar__tools .account { position: static; }
    .topbar__tools .account__toggle {
        width: 100%;
        justify-content: space-between;
    }
    /* ZH: 語言切換不要撐滿 —— 它是兩顆小鈕，拉滿一整列會看起來像可以按整條。 */
    .topbar__tools .lang-switch { align-self: flex-start; }
}


/* ==========================================================================
 * ZH: 首次登入的引導導覽（v4.27，coach marks）—— 行為見 tour.js
 *
 * ZH: 🔴 **遮罩是四塊，不是一整片。** 中間那個洞要讓使用者**真的點得到**
 *     （v4.27 改成「導覽不幫忙跳頁，換頁由他自己點」）。
 *     一整片半透明遮罩會把點擊全吃掉 —— 畫面看起來一樣，但那張卡點不動，
 *     而「看得到、點不到」是這種引導最常見的壞掉方式。
 *
 * ZH: ⚠ 那也是為什麼**不用** `box-shadow: 0 0 0 9999px` 那個常見寫法：
 *     陰影不接點擊沒錯，但那圈陰影屬於高亮框自己，
 *     它蓋住的區域**不會**擋住點擊 —— 使用者可以點到導覽以外的任何東西，
 *     一點就跑掉、導覽卻還停在原地。四塊遮罩兩件事一起解決：
 *     擋住周圍、露出中間。
 *
 * ZH: 🔴 **z-index 1100。** 既有的分層是
 *       內容 → 下拉 20 → ☰ 面板 25 → topbar 30 → tip 40 → 訓練頁選方式 800 → 小基 900 → 初次設定 1000。
 *     導覽要圈得住**頂部列的導覽鈕**（其中一步就是請他點「首頁」），
 *     所以必須蓋過 30；與初次設定不會同時出現（tour.js 會等 `.onb` 消失）。
 *     ⚠ 這一段與 web.css 的小基、topbar 兩處註解、styles.css 的 tip 註解
 *     構成同一份分層約定 —— 之後再加層請一起更新那幾處。
 *
 * ZH: ⚠ 樣式放 web.css 不放 styles.css：後者與管理端逐位元組相同，
 *     而管理端沒有導覽功能。
 * ========================================================================== */
.tour__panel {
    position: fixed;
    z-index: 1100;
    background: rgb(31 35 40 / 55%);
    /* ZH: 遮罩要**吃掉**點擊（這是它的第二個工作）—— 所以不要 pointer-events:none。 */
}

/* ZH: 高亮框只是一圈邊，不負責變暗（變暗是四塊遮罩的事）。
 * ZH: ⚠ `pointer-events: none` 是關鍵：它畫在目標**上面**，
 *     不放行的話使用者點到的會是這個框，那張卡就永遠點不到。 */
.tour__ring {
    position: fixed;
    z-index: 1101;
    border: 3px solid var(--accent);
    border-radius: var(--radius-md);
    pointer-events: none;
    transition: all .15s ease;
}

/* ZH: v4.40 透明擋板（擁有者 2026-10-06）：純說明的步驟要「看得到、但點不到」。
 *     四塊遮罩留的那個洞只對「請你點它」的步驟有意義；純說明時洞開著，
 *     使用者點下去可能真的啟動實驗室或送出訓練單，導覽就斷在那裡。
 * ZH: ⚠ 透明但**要吃掉點擊** —— 所以不要 pointer-events:none（那是 ring 的事，
 *     ring 畫在目標上面只是裝飾，放行才對；這一塊的工作正好相反）。 */
.tour__guard {
    position: fixed;
    z-index: 1101;
    background: transparent;
}

.tour__box {
    position: fixed;
    z-index: 1102;
    width: min(28rem, calc(100vw - var(--space-5)));
    padding: var(--space-4);
    border-radius: var(--radius-lg);
    background-color: var(--surface);
    box-shadow: var(--shadow-2);
}
.tour__x {
    position: absolute;
    top: var(--space-2);
    right: var(--space-2);
    width: var(--tap-min);
    height: var(--tap-min);
    border: 0;
    background: none;
    font-size: 1.5rem;
    line-height: 1;
    color: var(--text-muted);
    cursor: pointer;
}
/* ZH: 標題右邊要留出 × 的位置，不然長標題會壓到那顆鈕。 */
.tour__t { margin: 0 var(--tap-min) var(--space-2) 0; font-size: 1.125rem; }
.tour__d { margin: 0; font-size: 0.9375rem; line-height: 1.7; }
.tour__note {
    margin: var(--space-2) 0 0;
    font-size: 0.875rem;
    color: var(--text-muted);
}
.tour__btns {
    display: flex;
    gap: var(--space-2);
    align-items: center;
    justify-content: flex-end;
    margin-top: var(--space-4);
}
/* ZH: ⚠ `.btn--primary` 在這個專案裡**自己就是 width:100%**（styles.css:231，
 *     它預設是「單獨一顆滿版主要動作」）。照那個寬度擺進這一列的話，
 *     它會吃掉整行、把「上一步」擠到卡片**外面** —— 實測到過。
 *     所以這裡把寬度交還給 flex。⚠ margin-top 也要歸零。 */
.tour__btns .btn { white-space: nowrap; }
.tour__btns .btn--ghost { flex: 0 0 auto; }
.tour__btns .btn--primary { flex: 1 1 auto; width: auto; margin-top: 0; }

/* ZH: 「請你自己點」那一步沒有前進鈕，改放這一行提示。
 * ZH: ⚠ 它要看起來**不像按鈕** —— 長得像按鈕的話使用者會點它，
 *     然後發現沒反應（要點的是圈起來的那個東西）。 */
.tour__wait {
    flex: 1 1 auto;
    font-size: 0.9375rem;
    font-weight: 600;
    color: var(--accent-hover);
    text-align: right;
}

/* ZH: 導覽開著時鎖住整頁捲動 —— 與初次設定同一個做法（styles.css 的
 *     `html:has(.onb)`）。⚠ tour.js 主動的 scrollIntoView 不受影響。 */
html.tour-open body { overflow: hidden; }

@media (max-width: 640px) {
    /* ZH: 手機把說明卡釘在底部 —— 跟著目標飄的話，在小螢幕上很容易
     *     被推出畫面外，而且會蓋住那個要他點的東西。 */
    .tour__box {
        left: var(--space-3) !important;
        right: var(--space-3);
        top: auto !important;
        bottom: var(--space-3);
        width: auto;
        transform: none !important;
    }
}

/* ==========================================================================
 * ZH: v4.38 訓練頁的全版遮罩三選一（擁有者 2026-10-05）。
 * ZH: 🔴 z-index 800：蓋過 topbar（30）與 tip（40），低於小基（900）——
 *     「不知道選哪個」正是會想問小基的時候。分層約定見上面 .tour__panel 的註解。
 * ZH: 三格等寬直排卡（桌機三欄、窄畫面一欄）。盒子沿用 .line 的留白與圓章：
 *     同一個平台的「大選項」長同一個樣子。
 * ========================================================================== */
.mpick {
    position: fixed;
    inset: 0;
    height: 100dvh;           /* ZH: 同 .onb：iOS 的 100vh 含網址列，按鈕會被推出可見區 */
    z-index: 800;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: var(--space-4);
    background: rgb(31 35 40 / 55%);
    overflow-y: auto;
}
.mpick[hidden] { display: none; }
html:has(.mpick:not([hidden])) { overflow: hidden; }

.mpick__box {
    position: relative;
    width: min(1040px, 100%);
    max-height: 100%;
    overflow-y: auto;
    padding: var(--space-5);
    border-radius: var(--radius-lg);
    background-color: var(--surface);
    box-shadow: var(--shadow-2);
    text-align: center;
}
.mpick__t { margin: 0; font-size: 1.5rem; }
.mpick__sub { margin: var(--space-2) 0 0; color: var(--text-muted); }
.mpick__x {
    position: absolute;
    top: var(--space-2);
    right: var(--space-2);
    width: var(--tap-min);
    height: var(--tap-min);
    border: 0;
    border-radius: var(--radius-pill);
    background: transparent;
    color: var(--text-muted);
    font-size: 1.5rem;
    line-height: 1;
    cursor: pointer;
}
.mpick__x:hover { background-color: var(--surface-2); color: var(--text-strong); }

.mpick__grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: var(--space-3);
    margin-top: var(--space-5);
    text-align: left;
}
.mpick__opt {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    min-height: 15rem;
    padding: var(--space-5) var(--space-4);
    border: 2px solid var(--hairline);
    border-radius: var(--radius-md);
    background-color: var(--surface);
    color: inherit;
    font: inherit;
    text-align: left;
    cursor: pointer;
}
.mpick__opt:hover { border-color: var(--accent); box-shadow: var(--shadow-2); }
/* ZH: 重新選擇時標出「現在是這個」—— 邊框加粗加色，不靠只有顏色（還有右上的字）。 */
.mpick__opt.is-current { border-color: var(--accent); background-color: var(--surface-2); }
.mpick__opt.is-current .mpick__name::after {
    content: "（目前）";
    font-weight: 400;
    font-size: 0.875rem;
    color: var(--text-muted);
}
:root[lang="en"] .mpick__opt.is-current .mpick__name::after { content: " (current)"; }
.mpick__name { font-size: 1.25rem; font-weight: 700; color: var(--text-strong); }
.mpick__who { color: var(--text-muted); }
.mpick__need {
    margin-top: auto;           /* ZH: 「要準備」貼底，三格的底線對齊 */
    padding-top: var(--space-2);
    border-top: 1px solid var(--hairline);
    font-size: 0.875rem;
    color: var(--text-strong);
}
.mpick__need > span:first-child { color: var(--text-muted); }
.mpick__foot { margin: var(--space-4) 0 0; }

/* ZH: 「目前的方式＋重新選擇模式」v4.38 搬到側欄，樣式見 .tr-side__mode。 */

@media (max-width: 760px) {
    .mpick { align-items: flex-start; padding: var(--space-3); }
    .mpick__box { padding: var(--space-4) var(--space-3); }
    .mpick__grid { grid-template-columns: 1fr; gap: var(--space-2); }
    .mpick__opt { min-height: 0; padding: var(--space-3) var(--space-4); }
}

/* ==========================================================================
 * ZH: v4.38 訓練頁側欄：目前的方式＋要練哪一種（擁有者 2026-10-05）。
 *
 * ZH: 擁有者要求兩件事：**主區塊維持置中**、**側欄不跟著捲動**。所以側欄是 position:fixed，
 *     left 由「主區塊的左緣」往左推：主區塊是 .page--narrow（34rem、置中），
 *     左緣 = 50% − 17rem；再減掉間距 1.5rem 與側欄寬 13.5rem ＝ 50% − 32rem。
 *
 * ZH: 🔴 放不放得下要分兩種情況算（量過）：
 *       小基沒停靠：需要 50vw ≥ 32rem＋邊距 → 視窗 ≥ 1100px。
 *       小基停靠  ：body 右邊讓出 --aibot-w（28vw），內容中心變成 (100% − 28vw)/2，
 *                   需要 0.72·vw ≥ 65rem → 視窗 ≥ 1445px。1280px 停靠時會蓋到主區塊。
 *     放不下就**退回表單上方的一列**（預設樣式），不要硬塞。
 *     停靠的那一條另外減掉 --aibot-w／2，不然側欄會比主區塊偏左半個小基寬。
 *
 * ZH: 沒有 z-index：遮罩（800）、小基（900）、頂部列（30，sticky 在最上面）都要蓋過它。
 * ========================================================================== */

/* ── 預設（放不下時）：表單上方一列 ───────────────────────────────── */
.tr-side { margin-bottom: var(--space-3); }
.tr-side__t {
    margin: 0 0 var(--space-2);
    font-size: 0.875rem;
    font-weight: 600;
    color: var(--text-muted);
}
.tr-side__mode {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-2);
    margin-bottom: var(--space-3);
    padding: var(--space-2) var(--space-3);
    border: 1px solid var(--hairline);
    border-radius: var(--radius-md);
    background-color: var(--surface);
}
.tr-side__cur { margin: 0; }
.tr-side__cur .tr-side__t { margin: 0; font-size: 0.875rem; }
.tr-side__cur b { color: var(--text-strong); }

.tr-side__tabs { display: flex; gap: var(--space-1); }
.tr-tab {
    display: flex;
    flex: 1 1 0;
    min-width: 0;
    align-items: center;
    justify-content: center;
    gap: var(--space-2);
    padding: var(--space-2) var(--space-1);
    border: 1px solid transparent;
    border-bottom: 3px solid transparent;
    border-radius: var(--radius-md);
    background: transparent;
    color: inherit;
    font: inherit;
    text-align: center;
    cursor: pointer;
}
.tr-tab:hover { background-color: var(--surface-2); }
.tr-tab[aria-selected="true"] {
    border-color: var(--hairline);
    border-bottom-color: var(--accent);
    background-color: var(--surface);
    box-shadow: var(--shadow-2);
}
/* ZH: 一列時三格平分寬度（375px 實測：帶圖示時總寬 430px，第三格被擠出畫面），
 *     圖示與範例名收起來，只留名字。 */
.tr-tab__i, .tr-tab__eg { display: none; }
.tr-tab__txt { min-width: 0; }
.tr-tab__name { display: block; font-weight: 700; font-size: 0.9375rem; color: var(--text-strong); }
/* ZH: v4.38「用自己的程式」的程式區塊：不可收合，用一個有底色的盒子跟上面的資料上傳分開。 */
.own {
    margin-top: var(--space-4);
    padding: var(--space-3) var(--space-4);
    border: 1px solid var(--hairline);
    border-radius: var(--radius-md);
    background-color: var(--surface-2);
    text-align: left;
}
.own__h { margin: 0 0 var(--space-2); font-size: 1rem; }

/* ── 放得下時：固定在主區塊左邊的直排側欄 ───────────────────────── */
/* ZH: ⚠ 下面兩段內容相同、只差前綴與 left（由 scratchpad 腳本產生）—— 改一段要改兩段。 */
@media (min-width: 1100px) {
    html:not(.aibot-docked) .tr-side {
        position: fixed;
        top: 5.5rem;
        left: calc(50% - 32rem);
        width: 13.5rem;
        max-height: calc(100dvh - 7rem);
        overflow-y: auto;
        margin: 0;
    }
    html:not(.aibot-docked) .tr-side .tr-side__mode {
        flex-direction: column;
        align-items: stretch;
        gap: var(--space-2);
        margin-bottom: var(--space-4);
        padding: var(--space-3);
    }
    html:not(.aibot-docked) .tr-side .tr-side__cur .tr-side__t { display: block; margin-bottom: 2px; }
    html:not(.aibot-docked) .tr-side .tr-side__cur b { font-size: 1.0625rem; }
    html:not(.aibot-docked) .tr-side .tr-side__tabs { flex-direction: column; }
    html:not(.aibot-docked) .tr-side .tr-tab {
        flex: 0 0 auto;
        justify-content: flex-start;
        align-items: flex-start;
        padding: var(--space-3);
        border-bottom-width: 1px;
        border-left: 3px solid transparent;
        text-align: left;
    }
    html:not(.aibot-docked) .tr-side .tr-tab[aria-selected="true"] { border-bottom-color: var(--hairline); border-left-color: var(--accent); }
    html:not(.aibot-docked) .tr-side .tr-tab__i {
        display: inline-flex;
        flex: 0 0 auto;
        align-items: center;
        justify-content: center;
        width: 2rem;
        height: 2rem;
        border-radius: var(--radius-pill);
        background-color: var(--surface-2);
        color: var(--text-muted);
    }
    html:not(.aibot-docked) .tr-side .tr-tab[aria-selected="true"] .tr-tab__i { background-color: var(--accent); color: var(--on-accent); }
    html:not(.aibot-docked) .tr-side .tr-tab__i svg { width: 1.25rem; height: 1.25rem; display: block; }
    html:not(.aibot-docked) .tr-side .tr-tab__eg { display: block; font-size: 0.8125rem; color: var(--text-muted); }
    html:not(.aibot-docked) .tr-side .tr-tab__name { font-size: 1rem; }
}
@media (min-width: 1445px) {
    html.aibot-docked .tr-side {
        position: fixed;
        top: 5.5rem;
        left: calc(50% - var(--aibot-w) / 2 - 32rem);
        width: 13.5rem;
        max-height: calc(100dvh - 7rem);
        overflow-y: auto;
        margin: 0;
    }
    html.aibot-docked .tr-side .tr-side__mode {
        flex-direction: column;
        align-items: stretch;
        gap: var(--space-2);
        margin-bottom: var(--space-4);
        padding: var(--space-3);
    }
    html.aibot-docked .tr-side .tr-side__cur .tr-side__t { display: block; margin-bottom: 2px; }
    html.aibot-docked .tr-side .tr-side__cur b { font-size: 1.0625rem; }
    html.aibot-docked .tr-side .tr-side__tabs { flex-direction: column; }
    html.aibot-docked .tr-side .tr-tab {
        flex: 0 0 auto;
        justify-content: flex-start;
        align-items: flex-start;
        padding: var(--space-3);
        border-bottom-width: 1px;
        border-left: 3px solid transparent;
        text-align: left;
    }
    html.aibot-docked .tr-side .tr-tab[aria-selected="true"] { border-bottom-color: var(--hairline); border-left-color: var(--accent); }
    html.aibot-docked .tr-side .tr-tab__i {
        display: inline-flex;
        flex: 0 0 auto;
        align-items: center;
        justify-content: center;
        width: 2rem;
        height: 2rem;
        border-radius: var(--radius-pill);
        background-color: var(--surface-2);
        color: var(--text-muted);
    }
    html.aibot-docked .tr-side .tr-tab[aria-selected="true"] .tr-tab__i { background-color: var(--accent); color: var(--on-accent); }
    html.aibot-docked .tr-side .tr-tab__i svg { width: 1.25rem; height: 1.25rem; display: block; }
    html.aibot-docked .tr-side .tr-tab__eg { display: block; font-size: 0.8125rem; color: var(--text-muted); }
    html.aibot-docked .tr-side .tr-tab__name { font-size: 1rem; }
}
