/* ==============================================================================
   styles.css — v2 元件樣式
   ==============================================================================
   ⚠ 本檔**不得出現任何色碼**。顏色一律走 tokens.css 的第 2 層語意 token。
     檢查方式：grep -nE "#[0-9a-fA-F]{3,6}|rgb\(" styles.css  應為空。
   ============================================================================== */

/* ZH: 純色用 background-color（長寫）而非 background 簡寫——
       簡寫會一併重設 background-image / -position 等，這裡不需要那些。
       純粹是精確性，不是為了繞過什麼。 */

*, *::before, *::after { box-sizing: border-box; }

/* ZH: **不要釘 px。** `100%` ＝ 使用者瀏覽器的預設字級。
 *     把瀏覽器預設調大的人多半是真的需要（擁有者裁定 2026-08-20），
 *     釘 16px 等於默默取消他的無障礙設定——而且他關掉 JS 時連 prefs.js 都救不了。
 *     v2 的字級全走 rem，所以這一行決定了整體大小。
 *     ⚠ 間距（--space-*）仍是 px，不會跟著放大：那個取捨已由
 *     .topbar 的 min-height + flex-wrap 承接（見該處註解）。 */
html { font-size: 100%; }

body {
    margin: 0;
    font-family: var(--font);
    font-size: 1rem;
    line-height: 1.65;
    color: var(--text);
    background-color: var(--surface-2);
}

/* ZH: 鍵盤使用者要看得見焦點。用 box-shadow 而非 outline，圓角才會跟著彎。 */
:focus-visible {
    outline: none;
    box-shadow: 0 0 0 3px var(--focus-ring);
    border-radius: var(--radius-sm);
}

/* ── 版面骨架 ───────────────────────────────────────────────────────── */
/* ZH: 內容先於外框（design-before-code 的 content-out）——
       三個畫面定案後只需要頂部一條，不需要側邊欄。 */
/* ZH: `min-height` + `flex-wrap` 而不是固定 `height: 56px`。
 *
 *   實測 375px 寬 × 字級 150%：內容 448px > 視窗 375px，**帳號鈕整個跑到畫面外**——
 *   而最需要放大字的人正是構不到自己設定的那個人（要橫向捲動才找得到）。
 *   固定高度時 flex 不換行，只會往外長。
 *
 *   放得下時（桌面 100%）行為與原本完全相同：min-height 56、不換行。
 */
.topbar {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    row-gap: var(--space-2);
    gap: var(--space-4);
    padding: var(--space-1) var(--space-5);
    min-height: 56px;
    background-color: var(--surface);
    border-bottom: 1px solid var(--hairline);
}
/* ZH: 換行時 spacer 不該再把右側推走，否則第二行只有一個孤零零的元素。 */
@media (max-width: 640px) {
    .topbar { gap: var(--space-2); padding-inline: var(--space-4); }
}
/* ZH: LOGO 是回首頁的連結（chrome.js 會把 span 換成 a）。
 *     版面規則：**只有 LOGO 在左，其餘全部靠右**，靠 .topbar__spacer 撐開。 */
.topbar__brand {
    font-weight: 700;
    color: var(--text-strong);
    letter-spacing: 0.01em;
    text-decoration: none;
    display: inline-flex;
    align-items: center;
    min-height: var(--tap-min);
}
.topbar__spacer { flex: 1; }

.page {
    max-width: var(--content-max);
    margin: 0 auto;
    padding: var(--space-6) var(--space-5) var(--space-8);
}

/* ⚠⚠ ZH: `[hidden]` 必須勝過任何 display 規則。
 *
 *   瀏覽器內建的是 `[hidden] { display: none }`，但**作者寫的 display 一律贏過它**
 *   （UA 樣式表的優先序最低）。這個檔裡到處都是 `display: flex/grid/block`，
 *   於是 `el.hidden = true` 設得下去、屬性也對，**元素卻照樣看得見**。
 *
 *   實測一次抓到五處都在這個坑裡：
 *     .account__menu   帳號選單收不回去（使用者回報的症狀）
 *     .notice          沒有公告時橫幅仍占一條
 *     .entry           文件庫入口在沒有內容時仍出現（Decision Log #30 的規則等於沒生效）
 *     .minor a         **「停止 Lab」在 Lab 沒執行時也看得到**
 *
 *   ⚠ 這件事**不會報錯**，而且 `el.hidden` 讀回來是 true ——
 *     用屬性驗證會全部通過。要驗**可見性**（getBoundingClientRect().height）。
 */
[hidden] { display: none !important; }

/* ── 層級 0 · 公告清單（條件式，沒有公告時整塊不存在）────────────────
 * ZH: 至多 7 則。排序由後端決定（置頂在前），這裡只負責把置頂**看得出來**。
 * ZH: ⚠ 置頂用「底色 + 文字徽章」兩個訊號。只給底色的話，高對比模式或
 *     色覺障礙的使用者就完全拿不到這個資訊 —— 而畫面看起來仍然「有標示」。
 * ──────────────────────────────────────────────────────────────── */
.news {
    margin-bottom: var(--space-5);
    background-color: var(--surface);
    border: 1px solid var(--hairline);
    border-radius: var(--radius-md);
    overflow: hidden;                 /* ZH: 讓子項的底色被圓角切齊 */
}
.news__h {
    margin: 0;
    padding: var(--space-3) var(--space-4) var(--space-2);
    font-size: 0.8125rem;
    font-weight: 700;
    letter-spacing: 0.04em;
    color: var(--text-muted);
}
.news__list { list-style: none; margin: 0; padding: 0; }
.news__item {
    display: flex;
    align-items: baseline;
    gap: var(--space-3);
    padding: var(--space-3) var(--space-4);
    border-top: 1px solid var(--hairline);
    font-size: 0.9375rem;
}
.news__item.is-pinned { background-color: var(--accent-soft); }
.news__date {
    flex: none;
    color: var(--text-muted);
    font-size: 0.875rem;
    white-space: nowrap;
    font-variant-numeric: tabular-nums;   /* ZH: 日期等寬，左緣才對得齊 */
}
.news__pin {
    flex: none;
    font-size: 0.75rem;
    font-weight: 700;
    padding: 0 var(--space-2);
    border-radius: var(--radius-sm);
    background-color: var(--accent);
    color: var(--on-accent);           /* ⚠ 黃底＝深字，由 token 決定 */
}
.news__title { color: var(--text-strong); min-width: 0; }
.news__item.is-pinned .news__title { font-weight: 700; }
.news__all {
    display: block;
    padding: var(--space-3);
    text-align: center;
    font-size: 0.875rem;
    color: var(--link);
    background-color: var(--surface-2);
    border-top: 1px solid var(--hairline);
}
.news__all:hover { background-color: var(--surface); }

/* ── 層級 1 · 額度 + 主要動作（主線的核心）────────────────────────── */
.primary-card {
    background-color: var(--surface);
    border: 1px solid var(--hairline);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-1);
    padding: var(--space-6) var(--space-5);
    text-align: center;
}
.balance__label { font-size: 0.875rem; color: var(--text-muted); }
.balance__value {
    font-size: 2.5rem;
    font-weight: 700;
    color: var(--text-strong);
    line-height: 1.2;
    font-variant-numeric: tabular-nums;   /* ZH: 數字等寬，輪詢更新時不會左右跳 */
}
.balance__unit { font-size: 1rem; font-weight: 400; color: var(--text-muted); margin-left: var(--space-1); }
/* ZH: v3.9 額度提示搬到按鈕下方。
 * ZH: `:empty` 時不要留上邊距 —— 額度正常時這一格是空的，
 *     留著邊距會讓卡片下緣多出一塊看不出原因的空白。 */
.balance__meta { font-size: 0.875rem; color: var(--text-muted); margin-top: var(--space-3); }
.balance__meta:empty { margin-top: 0; }
.balance__meta a { color: var(--link); }

/* ZH: 低於門檻（/my-balance 的 below 旗標）——視覺上必須不一樣。
       這不是新機制，是用後端已經算好的判定。 */
/* ZH: v3.8 #9 —— 額度有兩段:1=快用完(警示黃), 2=已用完(錯誤紅)。
 *     兩段給不同顏色是因為它們要求的行動不同:一個是「該去申請了」,
 *     一個是「你現在用不了」。同一個黃色會讓後者看起來還有得撐。
 *     ⚠ 顏色只是次要訊號 —— 文案本身已經說明是哪一段（WCAG 1.4.1）。
 *     對比:--error 對 --error-soft = 5.72,小字 AA(4.5) 過。(算的,不是估的) */
.primary-card[data-low="1"] { border-color: var(--warn); background-color: var(--warn-soft); }
.primary-card[data-low="1"] .balance__value { color: var(--warn); }
.primary-card[data-low="2"] { border-color: var(--error); background-color: var(--error-soft); }
.primary-card[data-low="2"] .balance__value { color: var(--error); }

/* 骨架（載入中）——保留高度，避免版面跳動 */
.skeleton {
    display: inline-block;
    min-width: 8ch; height: 1em;
    background-color: var(--surface-2);
    border-radius: var(--radius-sm);
    animation: pulse 1.4s ease-in-out infinite;
}
@keyframes pulse { 0%,100% { opacity: 1 } 50% { opacity: 0.55 } }
@media (prefers-reduced-motion: reduce) { .skeleton { animation: none } }

/* ── 按鈕 ───────────────────────────────────────────────────────────── */
.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-2);
    min-height: var(--tap-min);
    padding: 0 var(--space-5);
    font: inherit;
    font-weight: 600;
    border: 1px solid transparent;
    border-radius: var(--radius-md);
    cursor: pointer;
    /* ZH: `<a class="btn">` 會帶著 UA 的底線，於是一顆按鈕看起來像一條連結。
           在 <button> 上這行沒有作用，所以放在 .btn 裡是安全的。 */
    text-decoration: none;
    transition: background-color 0.15s ease, border-color 0.15s ease;
}
/* ZH: 要與上方 .btn--primary 疊成一組動作時用。primary 自己就是 width:100%，
       第二顆不跟著滿版的話會在滿版按鈕底下縮成一小塊，看起來像註腳而不是動作。 */
.btn--block { width: 100%; margin-top: var(--space-2); }
.btn--primary {
    background-color: var(--accent);
    color: var(--on-accent);          /* ⚠ 黃底＝深字、藍底＝白字，由 token 決定 */
    width: 100%;
    font-size: 1.0625rem;
    margin-top: var(--space-4);
}
.btn--primary:hover:not(:disabled) { background-color: var(--accent-hover); }
.btn:disabled { opacity: 0.55; cursor: not-allowed; }

.btn--ghost {
    background: transparent;
    color: var(--link);
    border-color: var(--hairline);
}
.btn--ghost:hover { background-color: var(--surface-2); }

/* ── 層級 3 的動作按鈕 ────────────────────────────────────────────
 * ZH: 這一組的成員全都是「動作」，原本卻是純文字連結：實測高 18–23px，
 *     連這個設計系統自己定的 --tap-min: 44px 都不到。
 *
 *     為什麼另開一種而不是直接用 --ghost：ghost 是層級 2。
 *     三層（實心 / 外框 / 小外框）要分得出來，全變同一種就等於沒有階層。
 *
 *     `.minor a` / `.handoff a` 用後代選擇器一起納入，而不是要求每個連結
 *     自己加 class —— 以後在那兩區加連結的人不必「記得」，
 *     漏掉就會是一個 23px 的可點目標，而那種東西沒有人會回報。
 * ──────────────────────────────────────────────────────────────── */
.btn--minor,
.minor a,
.minor button,
.handoff a {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: var(--tap-min);
    padding: 0 var(--space-4);
    border: 1px solid var(--hairline);
    border-radius: var(--radius-md);
    background: transparent;
    color: var(--link);
    font: inherit;
    font-size: 0.875rem;
    font-weight: 600;
    text-decoration: none;
    cursor: pointer;
}
/* ZH: hover 底色用 --surface（白）**不是** --surface-2 ——
 *     實測這一組全部坐在 --surface-2 上（頁面底色就是它），
 *     設成 --surface-2 等於 hover 完全看不出來。
 *     邊框轉成 --link（對底色 6.30）才是主要訊號，底色只是輔助的「浮起」。 */
.btn--minor:hover,
.minor a:hover,
.minor button:hover,
.handoff a:hover { background-color: var(--surface); border-color: var(--link); }
.btn--minor:focus-visible,
.minor a:focus-visible,
.minor button:focus-visible,
.handoff a:focus-visible { outline: var(--focus-ring); outline-offset: 2px; }

/* ZH: 主要動作按下後的過渡——V1 修正。
       實際流程會先開廠商登出頁再轉登入頁，中間有約 1 秒空窗；
       這一條讓本站在那段時間有話說，而不是「按了沒反應」。 */
.handoff {
    margin-top: var(--space-3);
    font-size: 0.9375rem;
    color: var(--text-muted);
}
/* ZH: ⚠ .handoff 裡的連結是**彈窗被瀏覽器擋下時唯一的出路**，原本高 18px，
 *     是全站最小的可點目標——最需要被點到的東西反而最難點。
 *     形狀見上方「層級 3 的動作按鈕」；這裡只補讓它脫離句子行高的間距。 */
.handoff a { margin-top: var(--space-2); }

/* ── 層級 2 · 引導流程 ───────────────────────────────────────────
 * ZH: 窄螢幕直向堆疊、720px 以上並排成一列。**不需要第二套 DOM** ——
 *     這是選這個版型（而不是畫成樹）最實際的理由：樹在手機上排不下，
 *     只能捲或另做折疊清單，等於維護兩份。
 *
 * ZH: 「已完成」不用 opacity 調淡 —— 那會把整格的對比一起拉低，
 *     連本來調到剛好通過 AA 的次要文字也一起犧牲。改用底色與字重表示。
 * ──────────────────────────────────────────────────────────────── */

.step {
    display: block;
    height: 100%;
    min-height: var(--tap-min);
    padding: var(--space-4) var(--space-5);
    background-color: var(--surface);
    border: 1px solid var(--hairline);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-1);
    color: inherit;
    text-decoration: none;
    transition: box-shadow 0.2s ease, border-color 0.2s ease;
}
.step:hover { box-shadow: var(--shadow-2); border-color: var(--accent); }
.step:focus-visible { outline: var(--focus-ring); outline-offset: 2px; }

/* ZH: 狀態標籤。空字串時整行收掉 —— 中間那兩步是偵測不到的，
 *     留一個空行會讓版面看起來缺了東西。 */
.step__state {
    display: block;
    font-size: 0.75rem;
    font-weight: 700;
    letter-spacing: 0.04em;
    color: var(--text-muted);
}
.step__state:empty { display: none; }
.step__title { display: block; font-weight: 700; color: var(--text-strong); font-size: 1.0625rem; }
.step__desc { display: block; font-size: 0.9375rem; color: var(--text-muted); margin-top: var(--space-1); }

/* ZH: 「現在在這」——左側粗邊是主要訊號（與 .entry--feature 同一個語彙）。 */


@media (min-width: 720px) {
    /* ZH: 並排。grid-auto-columns: 1fr 讓每格等寬，格數會隨「看得見的步驟數」
     *     自動變（文件庫沒內容時是 3 格）—— 不必為那個情況另寫規則。 */
}

/* ZH: 送過訓練 → 引導收起來之後，叫回來的那一行。 */

/* ── 清單卡（.entry）────────────────────────────────────────────────
 * ZH: 🔴 **不要因為 grep 不到就刪掉。** 這三個 class 沒有出現在任何 .html ——
 *     它們是 datasets.js / jobs.js / lab.js 用**樣板字串**產生的：
 *         <div class="entry__title">${esc(d.name)}</div>
 *     只掃 HTML 的話會得到「沒人用」這個結論，而那是錯的。
 *     （2026-08-22 我就是這樣把它刪掉又補回來的。）
 *
 * ZH: 首頁原本的 .entries / .entry--feature 才是真的死碼，已隨首頁改版移除。
 * ──────────────────────────────────────────────────────────────── */
.entry {
    display: block;
    text-align: left;
    width: 100%;
    background-color: var(--surface);
    border: 1px solid var(--hairline);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-1);
    padding: var(--space-4) var(--space-5);
    cursor: pointer;
    font: inherit;
    color: inherit;
    transition: box-shadow 0.2s ease, transform 0.2s ease, border-color 0.2s ease;
}
.entry:hover { box-shadow: var(--shadow-2); transform: translateY(-2px); border-color: var(--accent); }
.entry__title { font-weight: 700; color: var(--text-strong); font-size: 1.0625rem; }
.entry__desc { font-size: 0.9375rem; color: var(--text-muted); margin-top: var(--space-1); }

/* ── 層級 3 · 次要 ──────────────────────────────────────────────── */
.minor {
    margin-top: var(--space-6);
    padding-top: var(--space-4);
    border-top: 1px solid var(--hairline);
    font-size: 0.875rem;
    color: var(--text-muted);
    display: flex; gap: var(--space-4); flex-wrap: wrap;
}
/* ZH: .minor 內的連結／按鈕外觀見上方「層級 3 的動作按鈕」那一組。 */

/* ZH: .minor 裡的小標題（首頁的「隨時可以去」）。
 *     .minor 是 flex 排，所以標題用 flex-basis:100% 自己佔滿一行。
 * ZH: ⚠ **沒有動到 .minor 本身** —— 它有 6 個頁面在用。 */
.minor__h {
    flex-basis: 100%;
    margin: 0;
    font-size: 0.8125rem;
    font-weight: 700;
    letter-spacing: 0.04em;
    color: var(--text-muted);
}

/* ── 頂部列 · 中／英切換 ──────────────────────────────────────────
 * ZH: 放在頂部列而不是帳號選單裡（擁有者裁定）——切語言是**當下就想做**的事，
 *     不該先開一層選單。字級與顏色留在選單裡：那兩個設定一次就好。
 *
 * ZH: 兩顆都常駐、目前的那顆反白。不做「按一下切換到另一個語言」的單顆按鈕——
 *     那種按鈕上的文字到底是「現在是什麼」還是「按了會變成什麼」永遠講不清楚。
 *
 * ZH: 狀態（aria-pressed）由 prefs.js 的 applyLang 統一重畫，這裡只管外觀。
 * ──────────────────────────────────────────────────────────────── */
/* ZH: 2026-08-27 改成分段控制（segmented control）的樣子,與管理端
 *     「平台／MYAI」那個檢視滑條一致：灰底軌道 + 內嵌的藥丸。
 *     顏色不變 —— 選中的仍是 --accent（黃）,不是那邊的白色。
 *
 * ZH: 軌道用 --surface-2 而不是 --surface：內嵌藥丸要浮起來,
 *     底色跟它一樣的話就看不出「軌道」與「目前在哪一格」。
 */
.lang-switch {
    display: inline-flex;
    align-items: center;
    flex: none;
    margin-right: var(--space-2);
    padding: 2px;                      /* ZH: 藥丸與軌道之間的內縮 */
    border: 1px solid var(--hairline);
    border-radius: var(--radius-pill);
    background-color: var(--surface-2);
}
.lang-switch button {
    /* ZH: 頂部列很擠，所以比 --tap-min 小。可點高度靠上下 padding 撐到 32px，
           而它旁邊的帳號鈕本來就是同一個量級 —— 這一列整體是密集工具列，
           不是主要動作區。 */
    min-height: 32px;
    padding: 0 var(--space-3);
    border: 0;
    background: none;
    font: inherit;
    font-size: 0.8125rem;
    font-weight: 600;
    color: var(--text-muted);
    cursor: pointer;
    white-space: nowrap;
}
/* ZH: 分隔線拿掉 —— 內嵌藥丸本身就分得出兩格,再加一條線會跟藥丸的邊撞在一起。 */
.lang-switch button {
    border-radius: var(--radius-pill);
    transition: background-color 0.18s ease, color 0.18s ease;
}
/* ZH: 未選中的那格 hover 時只給很淡的底 —— 給 --surface-2 的話
 *     它會跟軌道同色,看起來像沒有反應。 */
.lang-switch button:hover:not([aria-pressed="true"]) {
    background-color: var(--surface);
    color: var(--text-strong);
}
.lang-switch button[aria-pressed="true"] {
    background-color: var(--accent);
    color: var(--on-accent);          /* ⚠ 黃底＝深字、藍底＝白字，由 token 決定 */
    box-shadow: var(--shadow-1);      /* ZH: 讓藥丸浮在軌道上,與檢視滑條一致 */
}
.lang-switch button:focus-visible { outline: var(--focus-ring); outline-offset: -2px; }

/* ── 行內訊息區塊（部分失敗不整頁死）────────────────────────────
   ZH: 兩個變體共用同一個框，只有顏色不同。**容器不換、只換 class** ——
       `.inline-error` 出現在 40 個地方，換掉基底 class 要動那 40 處
       而使用者看不出差別。

   ZH: 🔴 2026-08-28 之前只有 `.inline-error` 一種，於是「已儲存」「已複製」
       「目前沒有公告」這類**成功與空狀態訊息也是紅字紅底** —— 看起來像出事了。
       管理端的 `flash()`（註解明寫「只給成功訊息用」）與使用者端的空狀態
       全部中招，共 21 處。

   ZH: 成功用 `--accent-soft` 而不是自己發明一個綠色：這份設計的語意色
       只有 warn 與 error，調性是低飽和；accent-soft 是它自己的正向強調色，
       而且會跟著色系切換。實算對比 `--text` 對黃 6.92、對藍 6.67，
       都比錯誤樣式現有的 5.72 好。 */
.inline-error,
.inline-note {
    font-size: 0.875rem;
    border-radius: var(--radius-sm);
    padding: var(--space-2) var(--space-3);
    margin-top: var(--space-2);
}
.inline-error {
    color: var(--error);
    background-color: var(--error-soft);
}
.inline-note {
    color: var(--text);
    background-color: var(--accent-soft);
}

/* ── 色系切換（開發期展示用；上線擇一後可移除）─────────────────── */
.theme-switch { display: flex; gap: var(--space-1); }
.theme-switch button {
    font: inherit; font-size: 0.8125rem;
    padding: var(--space-1) var(--space-3);
    min-height: 32px;
    border: 1px solid var(--hairline);
    border-radius: var(--radius-pill);
    background-color: var(--surface);
    color: var(--text);
    cursor: pointer;
}
.theme-switch button[aria-pressed="true"] {
    background-color: var(--accent); color: var(--on-accent); border-color: var(--accent);
}

/* ==========================================================================
 * 登入 / GPU 引導（第二批畫面）
 * ========================================================================== */

/* ZH: 這兩個畫面只有一件事，內容比首頁窄一些，視線不用左右掃。 */
.page--narrow { max-width: 34rem; }

.topbar__back {
    color: var(--text-muted);
    text-decoration: none;
    font-size: 0.9rem;
    margin-right: var(--space-4);
    padding: var(--space-2);
    min-height: var(--tap-min);
    display: inline-flex;
    align-items: center;
}
.topbar__back:hover { color: var(--text); }

.hero__title {
    margin: 0 0 var(--space-3);
    font-size: 1.5rem;
    font-weight: 700;
    color: var(--text-strong);
    line-height: 1.3;
}
.hero__sub {
    margin: 0 0 var(--space-5);
    color: var(--text-muted);
    line-height: 1.7;
}

/* ── 摺疊區（管理者本機登入）────────────────────────────────────────
 * ZH: 預設收合，且**錯誤時不自動展開**（C2 修正）。展開與否只由使用者決定。 */
.fold {
    margin-top: var(--space-5);
    border-top: 1px solid var(--hairline);
    padding-top: var(--space-4);
}
.fold__summary {
    cursor: pointer;
    color: var(--text-muted);
    font-size: 0.9rem;
    padding: var(--space-2) 0;
    min-height: var(--tap-min);
    display: flex;
    align-items: center;
    /* ZH: `display: flex` 會讓 Chrome **拿掉 <summary> 的原生展開三角**，
           於是這一行看起來就是一段不能點的灰字——沒有人會去點它。
           flex 是為了 min-height + 垂直置中，不能不要，所以自己畫一個指示符。
           （login.html 的「管理者本機登入」本來也是這樣，一起修好。） */
    list-style: none;
}
.fold__summary::-webkit-details-marker { display: none; }
.fold__summary::before {
    content: "▸";                     /* ▸ */
    display: inline-block;
    margin-right: var(--space-2);
    transition: transform 0.15s ease;
}
details[open] > .fold__summary::before { transform: rotate(90deg); }
@media (prefers-reduced-motion: reduce) {
    .fold__summary::before { transition: none; }
}
.fold__summary:hover { color: var(--text); }
.fold__summary:focus-visible { outline: var(--focus-ring); outline-offset: 2px; }
.fold__body {
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
    padding: var(--space-4) 0 var(--space-2);
}
/* ZH: 原本 38px，差一點就到 --tap-min。它是連結不是動作，所以不做成按鈕，
 *     但可點區域仍要夠大 —— 用 inline-flex + min-height 撐開，外觀不變。 */
.fold__link {
    display: inline-flex;
    align-items: center;
    min-height: var(--tap-min);
    color: var(--link);
    font-size: 0.85rem;
    align-self: flex-start;
    padding: var(--space-2) 0;
}

/* ── 表單欄位 ─────────────────────────────────────────────────────
 * ZH: ⚠ `width:100%` + `border-box` 缺一不可。少了它們，兩個輸入框會因為
 *     各自的 padding 不同而**長度不一** —— v1.5 就是這樣被使用者一眼看出來的。 */
.field { display: flex; flex-direction: column; gap: var(--space-2); }
.field__label { font-size: 0.85rem; color: var(--text-muted); }
.field__input {
    width: 100%;
    box-sizing: border-box;
    min-height: var(--tap-min);
    padding: var(--space-3);
    font: inherit;
    color: var(--text);
    background-color: var(--surface);
    border: 1px solid var(--hairline);
    border-radius: var(--radius-sm);
}
.field__input:focus-visible { outline: var(--focus-ring); outline-offset: 1px; }

/* ── 三步驟預覽（純說明、不可點）───────────────────────────────────
 * ZH: 刻意不做成卡片或按鈕。它們不是入口，做得像入口會搶走主要動作的注意力
 *     （Von Restorff：一個畫面只能有一個被隔離的東西）。 */
.steps {
    list-style: none;
    margin: var(--space-5) 0 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
}
.steps__item {
    display: grid;
    grid-template-columns: auto 1fr;
    grid-template-areas: "n t" "n d";
    gap: 0 var(--space-3);
    align-items: baseline;
}
.steps__n {
    grid-area: n;
    width: 1.5rem; height: 1.5rem;
    display: inline-flex; align-items: center; justify-content: center;
    border-radius: 50%;
    background-color: var(--accent-soft);
    color: var(--text-strong);
    font-size: 0.8rem;
    font-weight: 600;
}
.steps__t { grid-area: t; color: var(--text-strong); font-weight: 600; }
.steps__d { grid-area: d; color: var(--text-muted); font-size: 0.9rem; line-height: 1.6; }

.footnote {
    margin-top: var(--space-5);
    color: var(--text-muted);
    font-size: 0.85rem;
}

/* ==========================================================================
 * 使用量明細
 * ========================================================================== */

/* 期間切換 —— 分段控制項，一次只有一個生效 */
.seg {
    display: flex;
    gap: 0;
    margin: var(--space-5) 0 var(--space-4);
    border: 1px solid var(--hairline);
    border-radius: var(--radius-sm);
    overflow: hidden;
    width: fit-content;
}
.seg button {
    font: inherit;
    font-size: 0.875rem;
    padding: var(--space-2) var(--space-4);
    min-height: var(--tap-min);
    border: 0;
    border-right: 1px solid var(--hairline);
    background-color: var(--surface);
    color: var(--text);
    cursor: pointer;
}
.seg button:last-child { border-right: 0; }
.seg button[aria-pressed="true"] {
    background-color: var(--accent);
    color: var(--on-accent);
    font-weight: 600;
}
.seg button:focus-visible { outline: var(--focus-ring); outline-offset: -2px; }

/* 三個數字 */
.stats {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
    gap: var(--space-4);
    margin-bottom: var(--space-4);
}
.stat__label { font-size: 0.8rem; color: var(--text-muted); }
.stat__value {
    font-size: 1.75rem;
    font-weight: 700;
    color: var(--text-strong);
    line-height: 1.2;
}
.stat__sub { font-size: 0.75rem; color: var(--text-muted); margin-top: var(--space-1); }

/* ZH: 說明性提示。用 --surface-2 而非警示色 —— 「樣本不足所以沒有對照」
 *     是設計上的正常結果，不是錯誤，配色不該讓人以為出事了。 */
/* ZH: .hint 的字級 0.8rem 會被 .btn--minor 的 font:inherit 繼承下去，
 *     按鈕字會比別頁小一號。給它自己的字級，不跟著容器縮。 */
.hint .btn--minor { font-size: 0.875rem; margin-top: var(--space-2); }
/* ZH: 空狀態的引導按鈕（文件庫的 #msg）——補間距。
   ZH: ⚠ 兩個變體都要列 —— 文件庫的空狀態 2026-08-28 起是 `.inline-note`，
       只寫 `.inline-error` 的話那顆按鈕會貼著上一行。 */
.inline-error .btn--minor,
.inline-note .btn--minor { margin-top: var(--space-3); }

.hint {
    font-size: 0.8rem;
    line-height: 1.7;
    color: var(--text-muted);
    background-color: var(--surface-2);
    border-radius: var(--radius-sm);
    padding: var(--space-3);
    margin: 0 0 var(--space-4);
}

.chart { margin-bottom: var(--space-5); }
.chart__title {
    font-size: 0.85rem;
    font-weight: 600;
    color: var(--text-muted);
    margin: 0 0 var(--space-2);
}
/* ZH: 固定高度是必要的 —— Chart.js 的 maintainAspectRatio:false 需要容器有高，
 *     否則 canvas 會塌成 0 高，圖表「沒畫出來」但也不報錯。 */
.chart__box {
    position: relative;
    height: 14rem;
    padding: var(--space-3);
    background-color: var(--surface-2);
    border-radius: var(--radius-sm);
}

/* 常駐導覽（§Chrome：Lab 與使用量常駐頂部，常用者不必每次經過引導頁）*/
.topnav { display: flex; gap: var(--space-2); align-items: center; }
.topnav a,
.topnav button {
    background: transparent;
    border: 0;
    font: inherit;
    cursor: pointer;
}
.topnav a,
.topnav button {
    color: var(--text-muted);
    text-decoration: none;
    font-size: 0.875rem;
    padding: var(--space-2) var(--space-3);
    min-height: var(--tap-min);
    display: inline-flex;
    align-items: center;
    border-radius: var(--radius-sm);
}
.topnav a:hover,
.topnav button:hover { color: var(--text); background-color: var(--surface-2); }
.topnav a:focus-visible,
.topnav button:focus-visible { outline: var(--focus-ring); outline-offset: 2px; }

/* ZH: 目前所在頁要看得出來。擁有者裁定：**只有目前頁有底色**，
 *     其餘維持透明文字。MYAI 是動作不是頁面，永遠不會是 current。 */
.topnav a[aria-current="page"] {
    color: var(--text-strong);
    font-weight: 600;
    background-color: var(--accent-soft);
}

/* ── 帳號選單（頂部列最右）──────────────────────────────────────── */
.account { position: relative; }
.account__toggle {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    min-height: var(--tap-min);
    padding: 0 var(--space-3);
    background: transparent;
    border: 1px solid var(--hairline);
    border-radius: var(--radius-md);
    font: inherit;
    font-size: 0.875rem;
    font-weight: 600;
    color: var(--text);
    cursor: pointer;
}
.account__toggle:hover { background-color: var(--surface-2); border-color: var(--link); }
.account__toggle:focus-visible { outline: var(--focus-ring); outline-offset: 2px; }
.account__caret { color: var(--text-muted); font-size: 0.75rem; }

.account__menu {
    position: absolute;
    top: calc(100% + var(--space-2));
    right: 0;
    z-index: 20;
    min-width: 13rem;
    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 高。 */
.account__menu a,
.account__menu button {
    display: flex;
    align-items: center;
    min-height: var(--tap-min);
    padding: 0 var(--space-3);
    border: 0;
    border-radius: var(--radius-sm);
    background: transparent;
    font: inherit;
    font-size: 0.9375rem;
    color: var(--text);
    text-align: left;
    text-decoration: none;
    cursor: pointer;
}
.account__menu a:hover,
.account__menu button:hover { background-color: var(--surface-2); }
.account__menu a:focus-visible,
.account__menu button:focus-visible { outline: var(--focus-ring); outline-offset: -2px; }
/* ZH: 登出是唯一不可逆的項目，用 --error 與其他項分開（文字本身也已說明，不只靠顏色）。 */
.account__logout { color: var(--error) !important; }

/* ── 帳號選單裡的顯示設定 ─────────────────────────────────────────── */
/* ZH: 放在這裡而不是另開設定頁：只有兩個設定，開一頁的成本遠大於收益。 */
.account__prefs {
    border-top: 1px solid var(--hairline);
    margin-top: var(--space-2);
    padding-top: var(--space-2);
}
.account__prefs-title {
    font-size: 0.75rem;
    color: var(--text-muted);
    padding: 0 var(--space-3) var(--space-1);
}
.account__prefs-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3);
    padding: var(--space-1) var(--space-3);
    font-size: 0.875rem;
    color: var(--text);
}
/* ZH: 標籤不折行。字級那一列的控制項比較多（A−／值／A+），
       會把標籤擠到只剩一個字寬，於是「字級」變成上下兩個字
       —— 實測 46px 高，而同組的「語言」「顏色」是 23px。 */
.account__prefs-row > span:first-child {
    white-space: nowrap;
    flex: none;
}
.account__seg { display: flex; gap: var(--space-1); }
/* ZH: 這幾顆比 44px 小是**知情的例外**：三顆並排在 13rem 寬的選單裡放不下 44px，
 *     而 WCAG 2.5.8 的 AA 門檻是 24×24（AAA 才是 44）。這裡取 32×32，
 *     並讓「目前值」那一格兼任還原鍵（省一顆按鈕、也讓中間那格更大）。 */
.account__seg button {
    min-width: 32px;
    min-height: 32px;
    padding: 0 var(--space-2);
    border: 1px solid var(--hairline);
    border-radius: var(--radius-sm);
    background: transparent;
    font: inherit;
    font-size: 0.8125rem;
    font-weight: 600;
    color: var(--text);
    cursor: pointer;
}
.account__seg button:hover { background-color: var(--surface-2); border-color: var(--link); }
.account__seg button:focus-visible { outline: var(--focus-ring); outline-offset: 1px; }
.account__seg button[aria-pressed="true"] {
    background-color: var(--accent-soft);
    border-color: var(--accent);
    color: var(--text-strong);
}
.account__seg-reset { min-width: 46px; }
.account__seg-value { font-variant-numeric: tabular-nums; }

/* ZH: 存不回帳號時的提示。畫面**不回滾**（他看到的就是他選的），
 *     但要講清楚「這台機器有效、沒存回帳號」。 */
.account__prefs-warn {
    margin: var(--space-2) var(--space-3) 0;
    padding: var(--space-2);
    background-color: var(--warn-soft);
    border-radius: var(--radius-sm);
    font-size: 0.75rem;
    line-height: 1.6;
    color: var(--text-strong);
}

.account__id {
    padding: var(--space-2) var(--space-3) var(--space-3);
    border-bottom: 1px solid var(--hairline);
    margin-bottom: var(--space-2);
}
.account__id-name { font-weight: 700; color: var(--text-strong); font-size: 0.9375rem; }
.account__id-sub { font-size: 0.8125rem; color: var(--text-muted); margin-top: 2px; }


/* 文件庫卡片 */
.cards {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(16rem, 1fr));
    gap: var(--space-4);
    margin-top: var(--space-4);
}
.card {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    padding: var(--space-4);
    background-color: var(--surface);
    border: 1px solid var(--hairline);
    border-radius: var(--radius-sm);
    text-decoration: none;
    color: inherit;
}
.card:hover { border-color: var(--accent); }
.card:focus-visible { outline: var(--focus-ring); outline-offset: 2px; }
/* ZH: 分類用文字標籤，不只用顏色（WCAG 1.4.1）——與公告的置頂標籤同一個理由。 */
.card__tag {
    align-self: flex-start;
    font-size: 0.7rem;
    font-weight: 600;
    color: var(--text-strong);
    background-color: var(--accent-soft);
    padding: 0 var(--space-2);
    border-radius: var(--radius-sm);
}
.card__title {
    font-size: 1rem;
    font-weight: 600;
    color: var(--text-strong);
    margin: 0;
    line-height: 1.4;
}
.card__desc {
    margin: 0;
    font-size: 0.875rem;
    color: var(--text);
    line-height: 1.7;
    flex: 1;
}
.card__foot { font-size: 0.75rem; color: var(--text-muted); }

/* 公告列表 */
.post {
    padding: var(--space-4) 0;
    border-bottom: 1px solid var(--hairline);
}
.post__head {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    font-size: 0.75rem;
    color: var(--text-muted);
    margin-bottom: var(--space-2);
}
/* ZH: 置頂用標籤而非顏色 —— 只靠顏色的話色盲使用者分不出來（WCAG 1.4.1）。 */
.post__pin {
    background-color: var(--accent);
    color: var(--on-accent);
    padding: 0 var(--space-2);
    border-radius: var(--radius-sm);
    font-weight: 600;
}
.post__title {
    font-size: 1.0625rem;
    font-weight: 600;
    color: var(--text-strong);
    margin: 0 0 var(--space-2);
    line-height: 1.4;
}
/* ZH: white-space: pre-wrap —— 公告是純文字（不用 innerHTML），
 *     沒有這個換行會全部黏成一段。 */
.post__body {
    margin: 0;
    color: var(--text);
    line-height: 1.8;
    white-space: pre-wrap;
}
.skeleton--line { display: block; height: 1.25rem; margin: var(--space-3) 0; }

/* 問題回報的診斷區 */
.diag {
    font-family: ui-monospace, "Consolas", monospace;
    font-size: 0.8rem;
    line-height: 1.7;
    color: var(--text);
    background-color: var(--surface-2);
    border-radius: var(--radius-sm);
    padding: var(--space-3);
    margin: 0;
    white-space: pre-wrap;
    word-break: break-all;
    user-select: all;
}
textarea.field__input { resize: vertical; min-height: 6rem; }

/* 鍵值列（開通確認）*/
.kv {
    display: flex;
    align-items: baseline;
    gap: var(--space-3);
    padding: var(--space-3) 0;
    border-bottom: 1px solid var(--hairline);
}
.kv__k { font-size: 0.8rem; color: var(--text-muted); min-width: 4.5rem; }
.kv__v { color: var(--text-strong); word-break: break-all; }

/* ZH: 密碼要能一鍵選取 —— 剪貼簿在非 https 或權限被拒時不可用，
 *     `user-select: all` 讓使用者點一下就整段選中（沿用 v1.5 的做法）。 */
.kv__v--pw {
    font-family: ui-monospace, "Consolas", monospace;
    font-size: 1.125rem;
    letter-spacing: 0.02em;
    user-select: all;
    background-color: var(--accent-soft);
    padding: var(--space-2) var(--space-3);
    border-radius: var(--radius-sm);
}

/* 問題回報：狀態徽章與管理者回覆（v3.4）*/
/* ZH: 狀態的主要訊號是**文字**（未處理／處理中／已解決），顏色只是次要強調
 *     —— WCAG 1.4.1。文字色用 --text-strong 對 --warn-soft＝14.35。
 *     ⚠ 這些數字是算出來的不是估的（上一次估錯過 6.31 vs 實際 5.37）。
 *     ⚠ 2026-08-27 更正：這裡原本寫「--warn 對 --warn-soft 只有 3.72，
 *       小字不到 AA 的 4.5」，那是**舊的 --warn（#a8730c）**的值。
 *       748dd84 為了過 AA 已把它調深成 #8f620a，現在是 4.87 —— 小字 AA 過。
 *       也就是說「不能拿 --warn 當文字色」這個限制已經不存在了；
 *       維持 --text-strong 是因為沒有理由為此動視覺，不是因為對比不足。 */
.rep-badge {
    font-size: 0.7rem;
    font-weight: 600;
    padding: 1px var(--space-2);
    border-radius: var(--radius-sm);
    border: 1px solid transparent;
    color: var(--text-strong);
    background-color: var(--surface-2);
}
/* ZH: 只有「未處理」給強調 —— 它是唯一代表「還在等」的狀態。
 *     邊框 --warn 對 --warn-soft 是 4.87，圖形元件門檻 3.0，過。
 *     不靠底色區分 open/in_progress：黃色系下 warn-050 與 yellow-050
 *     幾乎同色，看起來會像同一個狀態。 */
.rep-badge[data-status="open"] {
    background-color: var(--warn-soft);
    border-color: var(--warn);
}
.rep-badge[data-status="resolved"] { color: var(--text); }

.rep-reply {
    margin-top: var(--space-3);
    padding: var(--space-3);
    background-color: var(--accent-soft);
    border-radius: var(--radius-sm);
}
.rep-reply__head {
    font-size: 0.75rem;
    font-weight: 600;
    color: var(--text-strong);
    margin-bottom: var(--space-2);
}

/* 兩顆按鈕在同一張卡裡時要有間距 */
.primary-card .btn + .btn { margin-top: var(--space-3); }

/* ==========================================================================
   交給平台訓練（train.html）
   ========================================================================== */

/* ZH: 拖放區。它是 role="button" 的 div，所以 :focus-visible 一定要有——
       沒有的話鍵盤使用者按 Tab 過去完全看不出焦點在哪。
   ZH: 高度 ≥ 44px 遠遠有餘（WCAG 2.5.8 AAA 的觸控目標），這裡本來就很大。 */
.drop {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: var(--space-2);
    min-height: 8rem;
    padding: var(--space-5) var(--space-4);
    margin-bottom: var(--space-4);
    border: 2px dashed var(--hairline);
    border-radius: var(--radius);
    background-color: var(--surface-2);
    cursor: pointer;
    text-align: center;
    transition: border-color 0.15s, background-color 0.15s;
}
.drop:hover { border-color: var(--link); background-color: var(--surface); }
.drop:focus-visible { outline: var(--focus-ring); outline-offset: 2px; }
.drop--over { border-color: var(--accent); background-color: var(--surface); }
.drop--has { border-style: solid; border-color: var(--accent); }
.drop__icon { font-size: 1.5rem; color: var(--text-muted); line-height: 1; }
/* ZH: 摺疊區裡的選檔區。矮一點——它是次要路徑，不該和主要的上傳區搶視覺重量。 */
.drop--small { min-height: 4.5rem; padding: var(--space-4); margin-bottom: 0; }
.drop__main { color: var(--text-strong); font-weight: 600; word-break: break-all; }
.drop__sub { font-size: 0.85rem; color: var(--text-muted); }

/* ZH: 進度條。動畫在 prefers-reduced-motion 下關掉——
       這是進度指示，不是裝飾，關掉動畫仍然看得到寬度。 */
.bar {
    height: 0.5rem;
    border-radius: 999px;
    background-color: var(--surface-2);
    overflow: hidden;
    margin-bottom: var(--space-3);
}
.bar__fill {
    height: 100%;
    width: 0;
    background-color: var(--accent);
    transition: width 0.4s ease;
}
@media (prefers-reduced-motion: reduce) { .bar__fill { transition: none } }

/* ZH: 資料集列表的動作列。窄畫面時自己換行，不要把卡片撐破。 */
.ds__actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-2);
    margin-top: var(--space-3);
}
.ds__actions .footnote { margin: 0; }

/* ZH: 配額快滿時的警示色。這一頁的來訪者多半就是為了這件事。 */
.bar--warn .bar__fill { background-color: var(--warn); }

/* ZH: 每輪結果的表格。窄畫面時讓它自己橫向捲，不要把整頁撐寬。 */

/* ── 每輪結果：一次十列，其餘捲動（擁有者 2026-09-24）──────────────
 * ZH: 訓練最多 50 輪（train.js 的上限），整份攤開會把「下載模型」
 *     和訓練紀錄推到很下面 —— 而那兩個才是跑完之後真正要按的東西。
 *
 * ZH: 🔴 這裡的高度只是**保底**（JS 還沒跑、或它壞掉時用的）。
 *     精確的「剛好十列」由 train.js 的 fitEpochBox() 量完再設 ——
 *     因為列高不會跟著字級等比例變：字是 rem，而上下 padding
 *     （--space-2）是固定的 px。實測放大到 130% 時用 rem 算會變成
 *     看得到 11 列，而說明文字還寫著「一次顯示 10 輪」。
 * ZH: 保底值＝預設字級下量到的：一列 42px、表頭 39px → 28.6875rem。
 *
 * ZH: 表頭要 sticky —— 捲到第 30 列時還看得出哪一欄是正確率。
 *     ⚠ 它需要一個**不透明的背景**，否則捲上來的列會透出來疊在字上面。
 * ─────────────────────────────────────────────────────────────── */
#epochs-table {
    max-height: 28.6875rem;   /* ZH: 表頭 + 10 列（見上面的量測）*/
    overflow-y: auto;
    /* ZH: 捲動框要有邊界感，不然看起來像頁面被裁掉。 */
    /* ZH: ⚠ 底色要跟 sticky 表頭同一個 —— 表格的列本身是透明的，
     *     不給底色的話會變成「灰色的表配一條白色的表頭」。 */
    background-color: var(--surface);
    border: 1px solid var(--hairline);
    border-radius: var(--radius-sm);
    margin-bottom: var(--space-5);
}
/* ZH: 外框已經有下邊界了，表格自己那段外距要拿掉（不然框底多一塊空白）。 */
#epochs-table .tbl { margin-bottom: 0; }
#epochs-table .tbl thead th {
    position: sticky;
    top: 0;
    background-color: var(--surface);
}

.tbl {
    width: 100%;
    border-collapse: collapse;
    font-size: 0.9375rem;
    margin-bottom: var(--space-5);
}
.tbl th,
.tbl td {
    text-align: left;
    padding: var(--space-2) var(--space-3);
    border-bottom: 1px solid var(--hairline);
    font-variant-numeric: tabular-nums;
}
.tbl th { color: var(--text-muted); font-weight: 600; font-size: 0.85rem; }
.tbl td { color: var(--text); }

@media (max-width: 640px) {
    .page { padding: var(--space-5) var(--space-4) var(--space-7); }
    .balance__value { font-size: 2rem; }
    .hero__title { font-size: 1.3rem; }
    .chart__box { height: 12rem; }
    /* ZH: 每輪結果在窄畫面自己捲，不要把頁面撐寬（頁面橫捲是版面壞掉的訊號）。
     *     ⚠ 直向的 max-height 與 sticky 表頭在上面已經設好，這裡只加橫向。 */
    #epochs-table { overflow-x: auto; }
}

/* ==========================================================================
 * 初次登入設定（v3.8）
 *
 * ZH: 只在「這個帳號從來沒設定過」時出現一次。刻意**沒有關閉鈕、沒有點背景關閉**——
 *     校區與學系是分組統計的基礎資料,能跳過的話永遠會有一堆「未分類」。
 *
 * ZH: 掛在 <body> 底下的頂層,不是塞進某個容器裡 ——
 *     祖先只要有 transform / filter / overflow,fixed 定位就會以那個祖先為基準,
 *     於是遮罩蓋不滿整頁而且捲動時會跑掉。
 * ========================================================================== */
.onb {
    position: fixed;
    inset: 0;
    /* ZH: v4.19 手機的動態視口：100vh 在 iOS 含網址列高度，遮罩會比可見區高，
       按鈕被推到看不到／點不到的地方。支援 dvh 的瀏覽器用 dvh，不支援的維持 inset。 */
    height: 100dvh;
    z-index: 1000;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: var(--space-4);
    background: rgb(31 35 40 / 55%);
}
.onb__box {
    width: min(520px, 100%);
    max-height: calc(100vh - var(--space-6));
    max-height: calc(100dvh - var(--space-6));
    overflow-y: auto;                 /* ZH: 內容比視窗高時捲內層,不要讓整頁捲 */
    background: var(--surface);
    border-radius: var(--radius-lg);
    padding: var(--space-5);
    box-shadow: 0 12px 32px rgb(31 35 40 / 24%);
}
/* ZH: v4.19 遮罩開著時鎖住整頁捲動 —— 手機上底下的頁面還能捲的話，fixed 遮罩會跟著
   視口位移，按鈕的「看起來在哪」與「實際在哪」就對不上。:has 不支援的舊瀏覽器只是少了鎖。 */
html:has(.onb) { overflow: hidden; }
.onb__title { margin: 0 0 var(--space-2); color: var(--text-strong); font-size: 1.25rem; }
.onb__sub   { margin: 0 0 var(--space-4); color: var(--text-muted); font-size: 0.9rem; }
.onb__field { margin-bottom: var(--space-4); }
.onb__label { display: block; margin-bottom: var(--space-1); color: var(--text-strong); font-weight: 600; }
.onb__hint  { display: block; margin-top: var(--space-1); color: var(--text-muted); font-size: 0.85rem; }
/* ZH: v4.9 「找不到我的單位？」——刻意做得比 hint 更像連結（底線＋主色），
   因為它是一條**出路**而不是說明文字。做成灰色小字的話，卡在這一步的人
   會看不見它，然後隨便選一個對得上的單位交差。 */
.onb__help  { display: inline-block; margin-top: var(--space-1); color: var(--accent);
              font-size: 0.85rem; text-decoration: underline; }
.onb__select {
    width: 100%;
    padding: var(--space-2) var(--space-3);
    border: 1px solid var(--hairline);
    border-radius: var(--radius-sm);
    background: var(--surface);
    color: var(--text);
    font: inherit;
}
.onb__err { margin: 0 0 var(--space-3); color: var(--mcu-error-600); font-size: 0.9rem; }

/* ZH: 二次確認頁的對照表。送出之後這幾項就鎖住了,所以要把選到的值
 *     原樣攤開讓人看一眼 —— 手殘按錯的代價是等管理員核可解鎖。 */
.onb__review {
    margin-bottom: var(--space-4);
    padding: var(--space-3);
    background: var(--surface-2);
    border-radius: var(--radius-sm);
}
.onb__row { display: flex; gap: var(--space-3); padding: var(--space-1) 0; }
.onb__k { flex: 0 0 6em; color: var(--text-muted); }
.onb__v { color: var(--text-strong); font-weight: 600; }
/* ZH: 兩顆鈕直排。btn--block 本來就有 margin-top,所以只補間距不重設。 */
.onb__box .btn--block + .btn--block { margin-top: var(--space-2); }

/* ZH: 只給螢幕閱讀器的標題（v3.9）。
 * ZH: 用 clip-path 而不是 `display:none` 或 `visibility:hidden` ——
 *     那兩個會讓螢幕閱讀器**也讀不到**，等於這個標題不存在。
 *     也不要用 `text-indent: -9999px`：RTL 版面下它會跑到畫面右邊變成一條橫向捲軸。
 */
.sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
}

/* ==========================================================================
 * ZH: 資訊 icon 與說明泡泡（v3.9）—— 行為見 tip.js 的說明
 *
 * ZH: v3.9 從 web.css 搬過來（擁有者裁定 2026-08-30：管理端的說明小字
 *     也要收成 icon）。放在共用的 styles.css，兩端才會長一樣 ——
 *     各留一份的話，改了間距只有一邊會動。
 * ========================================================================== */
/* ZH: 🔴 `inline-flex` + `align-items: center` 是**第二層**的置中。
 *     第一層（見下面的 `h2:has(> .tip)`）讓這個外殼對齊文字；
 *     但外殼會被標題的**行高**撐高（實測 24px 的按鈕住在 28.1px 的殼裡），
 *     裡面的按鈕仍然靠基線排 —— 於是外殼正了、圓圈還是低 2px。
 * ZH: ⚠ 兩層都要。只做其中一層都還會偏，而偏的量會隨字級變 ——
 *     這正是它在每一種標題上看起來都「有點怪」的原因。
 * ZH: .tip__body 是絕對定位（脫離流），不會變成 flex item。 */
.tip { position: relative; display: inline-flex; align-items: center; }

/* ZH: 圓形的 i。可點區 24px（WCAG 2.5.8 AA）——
 *     它旁邊就是標題文字，做成 44px 會把行高撐開。 */
.tip__btn {
    width: 24px;
    height: 24px;
    margin-left: var(--space-1);
    padding: 0;
    border: 1px solid var(--hairline);
    border-radius: 50%;
    background: var(--surface-2);
    color: var(--text-muted);
    font: inherit;
    font-size: 0.75rem;
    line-height: 1;
    cursor: pointer;
    vertical-align: middle;
}
.tip__btn:hover { color: var(--text-strong); border-color: var(--link); }
.tip__btn:focus-visible { outline: var(--focus-ring); outline-offset: 2px; }
.tip__btn[aria-expanded="true"] { background: var(--accent-soft); border-color: var(--accent); }

/* ZH: 泡泡。定位在 icon 下方靠左，寬度固定 —— 跟著內容伸縮的話，
 *     一句話的泡泡會變成很寬的一條，兩句話的又擠成一柱。 */
/* ZH: ⚠ 這裡的 absolute 只是**沒有 JS 時的退路**。實際打開時 tip.js 會改成
 *     `position: fixed` 並算好座標 —— absolute 會被有 overflow 的祖先切掉
 *     （數據頁的表格就是，見 tip.js 的說明）。
 * ZH: 兩邊都留著是刻意的：JS 掛掉時泡泡仍然出得來，只是可能被切到。
 *     那比「完全看不到」好。 */
.tip__body {
    position: absolute;
    top: calc(100% + var(--space-1));
    left: 0;
    /* ZH: 🔴 要高於使用者端那條**固定的頂部列**（web.css，z-index: 30）——
     *     泡泡改成往上長之後會滑到頂部列底下，低於它就整個被蓋住。
     * ZH: 仍然低於小基（900）與初次設定彈窗（1000）。 */
    z-index: 40;
    width: max-content;
    max-width: min(20rem, 80vw);
    padding: var(--space-3);
    border: 1px solid var(--hairline);
    border-radius: var(--radius-md);
    background: var(--surface);
    box-shadow: var(--shadow-2);
    font-size: 0.8125rem;
    font-weight: 400;
    line-height: 1.6;
    color: var(--text);
    text-align: left;
    white-space: normal;
}
/* ZH: 靠右的 icon（例如卡片右上角）要讓泡泡往左長，否則會被視窗切掉。 */
.tip--right .tip__body { left: auto; right: 0; }

/* ZH: ⚠ `[hidden]` 會被作者的 display 宣告打敗（styles.css 有一條全域補丁），
 *     這裡不另外寫 display，靠那條補丁即可 —— 寫了反而多一份要維護的規則。 */

/* ZH: 一個 icon 裝多段說明（v3.9）。每段一個小標＋一句話。 */
.tip__body--wide { max-width: min(26rem, 86vw); }
.tip__item { display: block; }
.tip__item + .tip__item {
    margin-top: var(--space-3);
    padding-top: var(--space-3);
    border-top: 1px solid var(--hairline);
}
.tip__item b {
    display: block;
    margin-bottom: 2px;
    color: var(--text-strong);
    font-size: 0.8125rem;
}
/* ZH: 標題裡的 icon 與泡泡不要跟著標題變粗。
 * ZH: v3.9 原本只寫 `.hero__title .tip__btn`（使用者端的大標）。管理端的
 *     說明也收成 icon 之後，那些掛在 <h2>/<h3> 上的就漏掉了 ——
 *     改成不限定祖先：**icon 在任何地方都不該是粗體**，
 *     一個一個補標題選擇器只會漏掉下一個。 */
.tip__btn, .tip__body { font-weight: 400; }

/* ZH: 🔴 標題旁的 icon 垂直置中。
 *
 * ZH: 原本靠 `vertical-align: middle`，而那**不是**「置中於文字」——
 *     它對齊的是「基線 + x-height 的一半」，文字方塊的中心卻在
 *     「基線 + (ascent−descent)/2」。兩者的差距與字級成正比，
 *     所以每一種標題大小都會偏，而且**都偏同一個方向（偏低）**。
 *     實測 20.8px 的標題偏低 2.8px —— 在 24px 的圓圈上看得出來。
 * ZH: 沒有一個與字級無關的 `vertical-align` 值能修好它（差距還跟字體的
 *     ascent/descent 有關，而這裡中文與拉丁字用的是不同字體）。
 *     真正置中要靠 flex。
 * ZH: ⚠ `:has()` 不支援的瀏覽器會退回原本的樣子（偏低幾 px）——
 *     那是既有狀態，不是新的破壞。用 class 標記的話反而要靠
 *     「下一個加 icon 的人記得寫」，而靠記得的規則不是約束。
 * ZH: gap 不用寫 —— .tip__btn 自己帶 margin-left，兩個都給會變兩倍。
 */
h1:has(> .tip), h2:has(> .tip), h3:has(> .tip) {
    display: flex;
    align-items: center;
}
/* ZH: 使用者端的大標是置中的（.primary-card 內），變成 flex 之後要補回來。 */
.hero__title:has(> .tip) { justify-content: center; }


/* ── 到訪人次（v4.24）──────────────────────────────────────────────────
   ZH: 首頁最底下那一行。刻意低調：它是**背景資訊**，不是行動 ——
       做得太顯眼會跟上面那幾張入口卡搶注意力。
   ZH: flex + wrap：手機寬度會折行，不要讓三個數字擠成一坨或撐出橫捲軸。 */
.visits {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    justify-content: center;
    gap: var(--space-2) var(--space-5);
    margin: var(--space-6) 0 var(--space-4);
    padding-top: var(--space-4);
    border-top: 1px solid var(--mcu-grey-200);
    font-size: 0.875rem;
    color: var(--text-muted);
}
.visits__item { display: inline-flex; align-items: baseline; gap: var(--space-2); }
.visits__n {
    font-weight: 600;
    color: var(--mcu-grey-600);
    /* ZH: 等寬數字 —— 每分鐘重畫一次時，數字不會讓整行左右跳動。 */
    font-variant-numeric: tabular-nums;
}
.visits__note { flex-basis: 100%; text-align: center; font-size: 0.8125rem; }


/* ── GPU 狀態頁（v4.24）───────────────────────────────────────────────
   ZH: 一張卡一格。auto-fill + minmax：一張卡的時候不要撐滿整頁變成一條，
       三十張的時候也不要擠成看不清的格子。 */
.gpuwall {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
    gap: var(--space-3);
    margin: var(--space-3) 0 var(--space-5);
}
.gpucard {
    border: 1px solid var(--mcu-grey-200);
    border-left: 4px solid var(--mcu-grey-400);
    border-radius: var(--radius-2);
    padding: var(--space-3) var(--space-4);
    background: var(--mcu-white);
}
/* ZH: 🔴 狀態靠**左側色條 + 文字**兩層表達，不是只有顏色 ——
       色盲與黑白列印時只剩文字，那時它仍然要看得懂。 */
.gpucard--idle { border-left-color: var(--mcu-slate-500); }
.gpucard--job  { border-left-color: var(--mcu-chart-blue); }
.gpucard--lab  { border-left-color: var(--mcu-yellow-600); }
.gpucard--node-ok     { border-left-color: var(--mcu-slate-500); }
.gpucard--node-closed { border-left-color: var(--mcu-warn-600); }
.gpucard--node-off    { border-left-color: var(--mcu-grey-400); opacity: .75; }
.gpucard__top {
    display: flex; align-items: baseline; justify-content: space-between;
    gap: var(--space-2); flex-wrap: wrap;
}
.gpucard__idx  { font-weight: 700; }
.gpucard__badge { font-size: 0.875rem; color: var(--mcu-grey-600); }
.gpucard__name { margin-top: var(--space-1); font-size: 0.9375rem; }
.gpucard__meta { margin-top: var(--space-1); font-size: 0.875rem; color: var(--text-muted); }
.gpuwall__note { font-size: 0.875rem; color: var(--text-muted); }
.gs-mine { margin: var(--space-2) 0 var(--space-5); padding-left: 1.25rem; }
.gs-mine li { margin: var(--space-1) 0; }
