/* ============================================================================
   Verquil app — Spectra components (frontend baton).
   Authority = DESIGN.md + design/spectra/app.html (visual truth source).
   Tokens only — no bare hex/px for spacing (optical ≤2px annotated). Component
   font sizes ≤ --fs-sm follow the mock's dense-data scale (mock is authority).
   ============================================================================ */

body{font-size:var(--fs-sm)}

/* ---- app shell: sidebar + main ------------------------------------------- */
.app{display:grid;grid-template-columns:238px 1fr;min-height:100vh}

/* sidebar
   fix（老闆回報 2026-08-13「左邊面板沒固定，會隨右邊內容變多一起滑」）：grid item 預設
   align-self:stretch 會把側欄拉到與 main 等高 ⇒ sticky 沒有可黏的餘裕、整條跟著捲。
   align-self:start + height:100vh 讓它只佔一個視窗高，position:sticky 才生效
   （MDN position sticky／CSS-Tricks “position sticky with CSS Grid”）；側欄自身內容
   超過視窗時由 overflow-y:auto 自己捲，Sign out 不會被擠到看不見。不用 fixed、不用 JS。 */
.side{background:var(--surface);border-right:1px solid var(--hairline);padding:var(--s5) var(--s4);display:flex;flex-direction:column;
  position:sticky;top:0;align-self:start;height:100vh;overflow-y:auto}
.side .brand{display:flex;align-items:center;gap:9px; /* optical */ padding:6px 8px var(--s6)}
.side .brand svg{width:26px;height:24px;color:var(--accent)}
.side .brand .wm{font-family:var(--f-display);font-weight:600;font-size:19px;letter-spacing:-.015em;color:var(--ink)}
.navg{display:flex;flex-direction:column;gap:2px /* optical */}
.navg a{display:flex;align-items:center;gap:var(--s3);padding:9px 11px; /* optical */ border-radius:9px;color:var(--ink-2);font-weight:500}
.navg a svg{width:18px;height:18px;flex:none;color:var(--ink-muted)}
.navg a:hover{background:var(--surface-2);color:var(--ink)}
.navg a.on{background:var(--surface-3);color:var(--ink)}
.navg a.on svg{color:var(--accent)}
.side .sep{height:1px;background:var(--hairline);margin:var(--s5) 6px /* optical */}
.credits{margin-top:auto;background:var(--surface-2);border:1px solid var(--hairline);border-radius:var(--r-md);padding:var(--s4)}
.credits .lab{font-family:var(--f-mono);font-size:11px;color:var(--ink-faint);letter-spacing:.06em}
.credits .n{font-family:var(--f-display);font-weight:700;font-size:26px;font-variant-numeric:tabular-nums;margin:var(--s1) 0 2px /* optical */}
.credits .sm{font-family:var(--f-mono);font-size:11px;color:var(--ink-faint)}
.side .side-foot{margin-top:auto} /* 後台側欄：次要動作沉底，與 App 側欄（.credits margin-top:auto）同語彙；分隔線一律由 .sep 收束主選單，不掛在沉底那組上 */
.side form.signout{margin-top:var(--s4)}
.signout button{display:flex;align-items:center;gap:var(--s3);width:100%;padding:9px 11px; /* optical */ border-radius:9px;color:var(--ink-muted);font-weight:500}
.signout button svg{width:18px;height:18px;flex:none}
.signout button:hover{background:var(--surface-2);color:var(--ink)}

/* main column + topbar */
.main{display:flex;flex-direction:column;min-width:0}
.topbar{display:flex;align-items:center;gap:var(--s4);padding:0 var(--s6);height:60px;border-bottom:1px solid var(--hairline)}
/* fix（老闆 2026-08-13「上面標題字太多會擠壓到右邊狀態」）：麵包屑吃下整個超長檔名後換行，
   把右邊的狀態 chip 擠成直排。長標題一律單行截斷（所有斷點，不只手機）、狀態與頭像 flex:none
   永不被壓縮；完整標題由 <b title=…> 補回。min-width:0 是必要條件（flex item 預設 auto，
   內容多寬就多寬，overflow:hidden 也救不了）。 */
.crumb{flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-family:var(--f-mono);font-size:var(--fs-meta);color:var(--ink-faint)}
.crumb b{color:var(--ink);font-weight:500}
/* 麵包屑連結：底線＝不依賴顏色的可辨識線索（WCAG 1.4.1；axe link-in-text-block）。
   同區塊文字（分隔的「/」）與連結同色 ⇒ 沒有底線就只剩「位置」可辨，axe 判 incomplete。
   deque 允許「3:1 對比 + hover/focus 有變化」的替代解，但那條路 axe 仍要求人工複驗、
   incomplete 不會消，且改色會動到既有 mono/faint 的視覺語彙 ⇒ 取底線。
   髮線色底線＋2px offset ＝ `.bo-tbl a` 已在用的既有語彙，非新樣式。
   選擇器用 `.crumb a` 而非 `.crumb .pre a`：前台詳情頁的連結多包一層 `.pre`，後台的
   `<a>` 則是 `.crumb` 直接子元素 —— 綁前者只修得到前台一處，後台麵包屑照樣只剩顏色可辨。
   判準相同就該用通用特徵涵蓋，不逐處列舉（列舉必漏，2026-08-16 codex 抓到）。 */
.crumb a{text-decoration:underline;text-decoration-color:var(--hairline-2);text-underline-offset:2px}
.crumb a:hover{color:var(--ink-2);text-decoration-color:currentColor}
.topbar .acct{flex:none;display:flex;align-items:center;gap:var(--s4)}
/* 點數餘額（手機）：≤820 側欄的 CREDIT BALANCE 面板消失，餘額原本掛在 .mnav 末端 —— 而 .mnav 是
   overflow-x:auto 的橫向捲動列，實測 390/430 時整個 "29 cr" 落在捲動內容之外（scrollWidth 450 > 390），
   要滑才看得到。錢不能藏在捲動條後面 ⇒ 改掛永不捲動的 topbar 帳號區，緊鄰頭像＝「你的帳戶」那一束。
   ≥821 由側欄面板負責，這裡不出現（同一個數字不重複兩次）。 */
.topbar .bal{display:none;font-family:var(--f-mono);font-size:var(--fs-meta);color:var(--ink-2);white-space:nowrap}
@media (max-width:820px){ .topbar .bal{display:inline} }
.status{display:inline-flex;align-items:center;gap:7px; /* optical */ flex:none;white-space:nowrap;font-family:var(--f-mono);font-size:12px}
.status .d{width:7px;height:7px;border-radius:50%;background:currentColor}
.status.done{color:var(--ok)}
.status.done .d{box-shadow:0 0 0 3px var(--accent-soft)}
.status.run{color:var(--info)}
.status.run .d{animation:pulse var(--dur-slow) var(--ease-inout) infinite alternate}
.status.fail{color:var(--danger)}
.status.wait{color:var(--ink-faint)}
@keyframes pulse{from{opacity:1}to{opacity:.35}}
.avatar{width:30px;height:30px;border-radius:50%;background:var(--surface-3);color:var(--accent);display:flex;align-items:center;justify-content:center;font-weight:700;font-size:12px;border:1px solid var(--hairline-2)}

/* overflow:auto 曾寫在這裡＝死宣告：沒有固定高度祖先，它永遠不會產生自己的捲軸（頁面整體捲）。 */
.editor{padding:var(--s6);min-width:0;flex:1;width:100%;max-width:960px}
.editor.bo{max-width:1400px} /* 後台是資料面不是閱讀面：不沿用編輯器的 960px 閱讀寬 */
/* Dashboard 檔案列表也吃 960px：2026-08-20 定案「單一頁寬」。曾放寬到 1400px 只給列表，
   結果 1440 下列表比上方的上傳區／篩選列突出 178px、1280 下只突出 18px ——「差一點點沒對齊」
   會被讀成 render bug。檔名欄要的寬度改從 meta 欄拿回來（短日期格式 −68px、狀態欄 −57px），
   不是把整條列表推出頁面的尺規之外。 */
/* 但摘要區與表單仍是閱讀面：釘回原本的寬度，放寬只給表格用（否則 KPI 標籤會開始折行） */
/* 🔴 **頁首尺規只有一條 960px**（2026-09-03，issue #258 修掉 #246 的倒退）：
   #246 把頁級說明段改成 `80ch`（14px 下＝640px），結果是老闆在真機看到的
   「兩段都只佔半頁寬、還黏在一起」—— 1202px 的內容欄上，兩段各佔 640px 的**裸文字**
   讀起來像沒排完的版，而不是像註記。
   裁決：`.editor.bo` 的**直接子元素**通知段（`.form-err`／`.bo-note`）與 `.bo-facts`／
   `.bo-filters` 共用同一條 960px 右緣 —— 頁首那一疊東西對齊成一塊，就不會有「半頁寬」的錯覺。
   ⚠️ **不再對它套字元數上限**：WCAG 2.2 SC 1.4.8 是 **AAA**、要求的是「有機制可調」而非
   作者端硬限寬，且它管的是 blocks of text（定義＝more than one sentence）；
   這裡是 1–3 行的頁首通知，不是閱讀區塊。真正的閱讀面（面板內 `.bo-note`、`.bo-panel>.why`、
   前台 `.hint`）維持各自既有的窄尺規，那條沒動。判準全文見 DESIGN.md §6.3.9。
   （版式紅線＝**≥1024 桌機**下通知段超過 3 行就該收進面板；390 那種窄欄必然排 5–6 行，
   那是 reflow 不是違規 —— 這條在 1440 的截圖上數，不在手機上數。） */
.editor.bo :is(.bo-facts,.bo-filters),.editor.bo > :is(.form-err,.bo-note){max-width:960px}
.editor > h1{font-family:var(--f-display);font-weight:700;font-size:22px;letter-spacing:-.01em;margin-bottom:var(--s5)}

/* 內頁精簡 footer（可達性稽核 2026-08-01：已登入者要連得到法務頁；.editor flex:1 把它推到底） */
.app-foot{display:flex;align-items:center;flex-wrap:wrap;gap:var(--s2) var(--s4);padding:var(--s3) var(--s6);border-top:1px solid var(--hairline);font-family:var(--f-mono);font-size:11px;color:var(--ink-faint)}
/* 換行落在**項目之間**，不落在項目**裡面**（landing 的 `.foot-in .r>*` 同一條，2026-09-02 補到這裡）：
   加第三個法務連結後，390 的 es／ja 每一項都從內部斷開（實測項目高 18→35px，
   「© 2026 Verquil」被劈成兩行）—— 老闆最恨的那種「Sign／in」。nowrap ＋ wrap 之後
   項目整塊換行，390 五語皆為單行字。 */
.app-foot > *{white-space:nowrap}
.app-foot a{color:var(--ink-faint)}
.app-foot a:hover{color:var(--ink)}
.app-foot span{margin-left:auto}

/* flash messages */
.flash{display:flex;flex-direction:column;gap:var(--s2);margin-bottom:var(--s5)}
/* 通知塊＝全站一種語彙（2026-09-03 合併）：底色 `--surface`、四邊髮線、**左緣 2px 色條**帶語意。
   `.form-err` 曾在 `.card` 與 `.bo-form` 各寫一份逐字相同的配方，而 flash 又自成一套 ——
   三份會各自漂移。合成一份後，警示塊在任何位置（登入卡／後台表單／後台頁首）長得一樣。
   語意差別由左色條承載：中性 `--accent`、失敗／警示 `--danger`（7.4:1 實色，不是半透明髮線）。
   旁邊的中性註記 `.bo-note` 刻意**沒有圍欄**（裸文字＋`--ink-muted`）⇒ 兩者一眼分得出來，
   不必靠段落間距去猜哪一段是哪一段。 */
.flash p,.form-err{border:1px solid var(--hairline-2);border-left:2px solid var(--accent);
  border-radius:var(--r-sm);background:var(--surface);padding:var(--s3) var(--s4);
  font-size:var(--fs-sm);line-height:1.6;color:var(--ink-2)}
.flash p.error,.form-err{border-left-color:var(--danger);color:var(--danger)}
.form-err{margin-bottom:var(--s4)}

/* ---- upload bar (DESIGN §4 / §6.2) --------------------------------------
   2026-08-20：從 351px 高的大方塊改成一條緊湊控制列。理由＝這頁的高頻行為是「回來找既有
   逐字稿」，上傳是低頻；Material 3 的 FAB／Apple HIG 的 prominent button 都是把「建立」做成
   緊湊高強調控制項，沒有任何官方系統把建立區塊做成固定佔住首屏的大面積。實測 1440×1100
   首屏可見列數 7 → 15。
   拖放不但沒退化，還放大了：放置目標由這條 bar 擴成整份文件（app.js 綁 document），
   bar 只負責顯示 .drag 回饋；虛線邊框留著＝這裡仍是「可以丟東西進來」的地方。 */
.upbar{display:flex;flex-wrap:wrap;align-items:center;gap:var(--s2) var(--s3);
  border:1.5px dashed var(--control-border);border-radius:var(--r-md);background:var(--surface);
  padding:var(--s3) var(--s4);margin-bottom:var(--s5);
  transition:border-color var(--dur-fast) var(--ease-out),background-color var(--dur-fast) var(--ease-out)}
.upbar.drag{border-color:var(--accent);background:var(--surface-2)}
.upbar > svg{width:20px;height:20px;flex:none;color:var(--ink-muted)}
.upbar.drag > svg{color:var(--accent)}
.upbar .hd{font-size:var(--fs-sm);font-weight:600;color:var(--ink)}
/* 控件組靠右收成一束：組內 s3、與左側說明之間靠 margin-left:auto 呼吸（欄軌不對位的地方，
   靠「成束」而不是靠平均分布，才不會出現說不出理由的空洞）。 */
.upbar .up-ctl{margin-left:auto;display:flex;flex-wrap:wrap;align-items:center;gap:var(--s3);min-width:0}
.upbar .up-ctl > label{font-size:var(--fs-sm);color:var(--ink-2);white-space:nowrap}
/* fix 2026-08-20：選了檔案後版面大跳（長 CJK 檔名自己換兩行、把「上傳並轉錄」擠成單獨一行）。
   檔名接手左側提示文字的位置（選完之後「把檔案丟這裡」已無用），單行截斷、完整名走 title
   ⇒ 控件束寬度不變、選檔前後 bar 高度零位移。:has 不支援時提示文字留著、檔名照樣截斷（窄但不破版）。 */
.upbar .picked{flex:1 1 0;min-width:0;margin:0;font-family:var(--f-mono);font-size:var(--fs-meta);color:var(--ink-2);
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.upbar:has(.picked:not(:empty)) .hd{display:none}
/* 這格偶爾放的是一整句 fallback 說明（見 app.js drop_unsupported）＝要能換行，截斷會吃掉解法 */
.upbar .picked.msg{flex-basis:100%;white-space:normal;color:var(--ink)}
/* 計費／上限／自動偵測涵蓋語言＝次級資訊，收成第二行的 mono 註腳（不得消失，只降層次）。 */
.upbar .up-meta{flex-basis:100%;display:flex;flex-wrap:wrap;align-items:baseline;gap:var(--s1) var(--s4);
  font-family:var(--f-mono);font-size:var(--fs-meta);color:var(--ink-faint)}
/* pretty＝擋末行孤詞（640px 實測：auto 會把 “matters” 單獨丟到第二行，pretty 不會） */
.upbar .up-meta > span{text-wrap:pretty}
/* 這個揭示連結曾用 class="hint"，撞上頁級段落的 `.hint{margin-bottom:var(--s5);max-width:36em}`
   ⇒ bar 底部憑空多 24px（老闆看到的「右邊跟下面間距和左邊上面不同寬」）。改成自己的類名，
   不靠覆寫收拾 —— 覆寫會在下次有人動 .hint 時再壞一次。 */
.upbar .up-hint{min-width:0}
/* 水平不留 padding：這行的節奏由 flex gap 決定，多 4px 會讓它和左側註腳對不上；
   min-height 24px 仍滿足 WCAG 2.5.8 的可點目標。 */
.upbar .up-hint summary{display:inline-flex;align-items:center;min-height:24px;cursor:pointer;
  color:var(--ink-muted);list-style:none;text-decoration:underline;text-decoration-style:dotted;text-underline-offset:3px}
.upbar .up-hint summary::-webkit-details-marker{display:none}
.upbar .up-hint summary:hover{color:var(--ink-2)}
.upbar .up-hint[open]{flex-basis:100%}
/* 行長取 WCAG 2.2 SC 1.4.8 建議的量（拉丁 80 字元、CJK 40 字）當**設計尺**。
   ⚠️ 那條 SC 是 **AAA**、且要求的是「**有機制可調**」而非作者硬限寬（見 DESIGN.md §6.3.9），
   這裡是自願採用它的數量級，不是在宣稱合規。
   兩種文字要用不同的尺規 —— 這段是等寬字（--f-mono），**只有在等寬字下 1ch 才真的等於 1 個字元**
   （12.5px 字級下實測 8px）⇒ 80ch=640px 在這裡確實排 80 字元；CJK 全形字寬是 1em（實測 12.5px）＝ 1.56ch，用 ch 量 CJK 會系統性偏窄四成
   （40 字只會給到 25 字），故 CJK 語系改用 40em=500px。孤字一律交給 text-wrap:balance 處理，
   不再靠縮 max-width 去治（#125 的 58ch 就是那樣長出來的：實測只排到 25 字／52 字元，比上限窄 35%）。 */
.upbar .up-hint p{margin:var(--s2) 0 0;max-width:80ch;line-height:1.7;text-wrap:balance}
.upbar .up-hint p:lang(zh),.upbar .up-hint p:lang(ja),.upbar .up-hint p:lang(ko){max-width:40em}
/* 窄一點的視窗：控件束已佔掉整行的大半，檔名再擠在同一行只剩七八個字＝截斷得沒有資訊量。
   讓控件束換到第二行、檔名跟著圖示留在第一行（把檔名自己推到第二行會讓圖示孤零零佔滿一行）。 */
@media (max-width:1100px){
  .upbar:has(.picked:not(:empty)) .up-ctl{flex-basis:100%;margin-left:0}
}
/* native input visible by default (progressive enhancement); JS swaps in the button */
.upbar input[type=file]{color:var(--ink-2);font-size:var(--fs-sm);max-width:100%}
/* fix: 無 JS fallback 的原生「選擇檔案」鈕曾是瀏覽器預設灰鈕 — ::file-selector-button 對齊 .btn-ghost（36px 控件列，DESIGN.md §4.1） */
.upbar input[type=file]::file-selector-button{appearance:none;background:transparent;color:var(--ink);border:1px solid var(--hairline-2);border-radius:var(--r-sm);padding:var(--s2) var(--s4);min-height:36px;font:inherit;font-weight:500;font-size:var(--fs-sm);margin-right:var(--s3);cursor:pointer}
.upbar input[type=file]::file-selector-button:hover{border-color:var(--control-border);background:var(--surface-2)}
/* 同上：由 enhanceDropzone() 裝好「選擇檔案」鈕之後才隱藏原生 input（`.up-live` 是它設的）。
   用 `.js` 判會在 app.js 掛掉時同時發生「原生 input 被藏」＋「#pick-btn 還是 hidden」
   ＝上傳入口整個消失。 */
.upbar.up-live input[type=file]{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}

/* ---- 手機上傳入口（≤640；DESIGN §6.2「手機不做拖放隱喻」） -------------------
   問題：桌機那條 bar 到手機只是被擠成一疊 —— 虛線框＋「把檔案拖到這裡」在手機上是空的隱喻
   （沒有檔案總管可以拖），而它連同說明文字吃掉 285px（390×844 的 34%），列表被推到摺線下，
   首屏只看得到 1 列。Apple HIG（Layout）明文最重要的內容放頂端、別被次要細節擠掉；
   這頁最重要的內容是任務列表，上傳是低頻動作。
   做法：①拖放的「視覺承諾」收掉（實線框、無下載箭頭 icon、無 "Drop … here"），但功能不動
   —— 真的有人用滑鼠／觸控板拖檔進來時，.drag 狀態才把虛線＋accent 亮出來（affordance 由
   常駐廣告改成狀態揭示）。②控件排成 [語言][選擇檔案] 一列。③「上傳並轉錄」只在選好檔案後
   出現，成為整行滿寬的主要動作 —— 兩顆按鈕不再同時並列爭權，且順手解掉「沒選檔就按送出」
   那顆必然失敗的死鈕（file input 在 JS 模式是 clip 隱藏的，原生驗證泡泡沒地方長）。 */
/* 手機把頁標題交還給 app bar（≤640）：dashboard／Vocabulary／Settings 三頁的 `<h1>` 與 topbar
   麵包屑是**逐字相同的同一句**，加上 .mnav 裡那顆 on 狀態的 pill，同一個字在首屏出現三次、
   吃掉 59.2px（35.2 標題＋24 下距）＝半列任務。Material 3 的 top app bar 與 iOS navigation bar
   的官方形態都是「螢幕標題住在 bar 裡」，內文不再重複一次。
   `<h1>` 留在 DOM（螢幕閱讀器的標題導覽、文件大綱不變），只是視覺上不再佔位。
   `:not(.bo)` ＝ 後台（ops）不受影響；job_detail 的 h1 在 .jhd 裡（editor.css），不是 .editor 的直接子代。
   ⚠️ 要退回只需刪掉這一條規則。 */
@media (max-width:640px){
  .editor:not(.bo) > h1{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}
}

@media (max-width:640px){
  .upbar{border-style:solid;border-width:1px;border-color:var(--hairline-2);
    gap:var(--s2) var(--s3);margin-bottom:var(--s4)}
  /* 真的有檔案被拖進來時才恢復「這裡可以放」的樣子 */
  .upbar.drag{border-style:dashed;border-color:var(--accent)}
  /* 下載箭頭 icon 與 "Drop video or audio files here" 都只為拖放而存在 ⇒ 手機不出現。
     控件自己就說得清楚（Choose files），不需要一個指向不存在動作的標題。 */
  .upbar > svg,.upbar .hd{display:none}
  .upbar .picked{flex-basis:100%}
  .upbar .up-ctl{flex-basis:100%;margin-left:0;gap:var(--s2)}
  /* 「Spoken language」在手機收成 mono eyebrow 自成一行（沿用 .uq 標題那套儀器標籤語彙）：
     select 只顯示「Auto detect」，沒有標籤就看不出是在選什麼；藏起來省的 18px 不值得這個歧義。 */
  .upbar .up-ctl > label{flex-basis:100%;font-family:var(--f-mono);font-size:11px;
    letter-spacing:var(--tracking-wide);text-transform:uppercase;color:var(--ink-faint)}
  /* 140px 基準在 360 會擠不下兩顆（140+140+8 > 278）⇒ 120 才能在最窄的手機仍並排一列 */
  .upbar #id_language,.upbar #pick-btn{flex:1 1 120px;min-width:0}
  .upbar #upload-btn{flex-basis:100%}
  /* 無 JS：原生 file input 是唯一的挑檔入口 ⇒ 自成一行、送出鈕照樣滿寬可見（下面的狀態切換
     只掛 .js，關掉 JS 什麼都不會被藏起來）。 */
  .upbar input[type=file]{flex-basis:100%}
  /* 觸控目標：WCAG 2.2 SC 2.5.8 只要求 24×24，這裡照 Apple HIG 44pt／Material 48dp 的建議值走。
     （.btn-*／.field-select 已由檔尾「≤640 控件規格」統一撐到 44，這裡只補原生 file 按鈕。） */
  .upbar input[type=file]::file-selector-button{min-height:44px}
  .upbar .up-meta{flex-direction:column;gap:var(--s1)}
  /* 孤行策略按「這個語言的斷點落在哪裡」分四種，每一種都是兩引擎實測選出來的，不是一體適用：
     ・拉丁＝維持全域 `pretty`：擋末行孤詞，且不會像 balance 把分隔點「·」推到行尾。
     ・中文（無詞距、引擎也沒有詞模型）＝`balance`：pretty 在 Chromium 會留下「路下進行」四字孤行，
       balance 把兩行拉等長（實測 11 字／14 字）。
     ・日文＝`pretty` ＋ 下面的 `word-break:auto-phrase`：詞完整性交給引擎的詞模型，
       再疊 balance 反而會把「·」推到第二行行首（Chromium 實測）。
     ・韓文＝`word-break:keep-all` ＋ `balance`（2026-08-21 修正；原本被歸到「有詞距＝pretty」那類，
       但 pretty 對韓文無效，實測違反本案的 CJK 判準②「末行不得 ≤4 字」）。 */
  .upbar .up-meta > span:lang(zh){text-wrap:balance}
  /* 🔴 韓文：`pretty` 在 Chromium 對韓文完全不作用 —— .rate 末行在 390 只剩「차감」2 字、
     360 剩「만 차감」（WebKit 無此問題）＝違反 §7 條 14 的判準②。兩件事各修一半、缺一不可：
     ・`word-break:keep-all`：韓文預設可在任兩個諺文音節之間斷（UAX #14），說明段實測被斷成
       「…없습／니다.」＝詞被劈開；keep-all 讓斷點只落在空格（MDN：CJK 不斷、非 CJK 同 normal），
       修好末行「감지로는 전사할 수 없습니다.」。但它救不了 .rate（末行仍是「때만 차감」4 字）。
     ・`text-wrap:balance`：把 .rate 兩行拉等長 ⇒ 末行「작업이 완료될 때만 차감」11 字。單獨用 balance
       則會在「작업이」中間斷字（keep-all 才擋得住）。
     兩引擎 390／360／430 實測：bar 高 219.6／219.6／199.6 前後不變、水平溢出 0。
     🔴 **只掛 `.up-meta > span`，不要順手擴到 `.up-hint p`**：說明段展開後在 ko 本來就 0 違規，
     加了 keep-all 反而把「—」推到行首＝製造出判準①的違規（390／360 兩引擎實測）。 */
  .upbar .up-meta > span:lang(ko){word-break:keep-all;text-wrap:balance}
  /* 🔴 計費率（.rate）在手機一樣顯示 —— 老闆 2026-08-20 裁決：價格資訊不參與密度取捨。
     它是「使用者按下上傳前，決定他會被收多少錢的規則」，把它藏在使用者正要付費的那個動作旁邊
     是透明度取捨、不是密度取捨。代價（+40px、390 由 3 列變 2 列）由下面的 h1 折疊補回來。 */
  /* balance：390 下這句剛好排兩行，不設會把「cover?」單獨丟到第二行（拉丁孤詞）。
     可點區（padding 撐 44px ＋ 等量負 margin 拉回版面）統一在檔尾的「≤640 控件規格」段。 */
  .upbar .up-hint summary{text-wrap:balance}
  /* 日文限定的詞邊界斷行（CSS Text 4 `word-break:auto-phrase`，Chromium 119+／Edge／Chrome Android；
     WebKit 26.5 實測 computed value 落回 `normal`＝純漸進增強，版面 byte-identical）。
     🔴 **只掛 :lang(ja)，因為實測它對中文完全沒作用**：Chromium 151 對同一句 zh-Hant 開與不開
     斷行位置一模一樣（引擎的詞模型是日文的）。對 zh 宣告它＝宣稱強過實作，故不宣告。
     ja 實測（390，兩引擎、開關各一次）：Chromium 由「1 クレジッ/ト」「再/開」修正成
     「1 クレジット」「アップロードは/再開できないため」，行數／bar 高 219.6／列表頂端 425.8／
     首屏 3 列全部不變、水平溢出 0；WebKit 前後完全相同。 */
  .upbar .up-meta > span:lang(ja),.upbar .up-hint p:lang(ja),.upbar .up-hint summary:lang(ja){word-break:auto-phrase}
  @supports selector(:has(*)){
    /* `.picked` 除了檔名，也會被 JS 塞「此處不支援拖放」這類訊息（`.msg`）——那時並沒有檔案，
       若只判 :empty，送出鈕會在沒選檔時現身＝本輪要消滅的死鈕又回來。故一併排除 .msg。 */
    /* `.up-live`（不是 `.js`）：`.picked` 的內容是 app.js 填的 ⇒ app.js 掛掉時永遠是空的，
       用 `.js` 判會讓送出鈕永久消失＝連無 JS fallback 都不如。 */
    .upbar.up-live:not(:has(.picked:not(:empty):not(.msg))) #upload-btn{display:none}
  }
}

/* fix: .btn-* 的 display:inline-flex 會蓋掉 [hidden] 的 UA display:none（無 JS 時 #pick-btn 曾照樣現身）*/
[hidden]{display:none!important}

/* buttons */
.btn-pri,.btn-ghost{display:inline-flex;align-items:center;justify-content:center;gap:var(--s2);border-radius:var(--r-sm);padding:var(--s2) var(--s4);font-weight:500;font-size:var(--fs-sm);min-height:36px; /* optical: ≥24px control, dense UI */ white-space:nowrap; /* 窄表格欄裡的動作鈕（如 Retry）文字曾被折成兩行 */ transition:background-color var(--dur-fast) var(--ease-out),border-color var(--dur-fast) var(--ease-out),transform var(--dur-fast) var(--ease-out)}
.btn-pri{background:var(--accent);color:var(--on-accent);border:1px solid var(--accent);font-weight:600}
.btn-pri:hover{background:#4fe8cb;transform:translateY(-1px)} /* hover mint 一階亮，DESIGN §4 */
.btn-pri:disabled{opacity:.5;cursor:not-allowed;pointer-events:none}
.btn-ghost{background:none;color:var(--ink);border:1px solid var(--control-border)}
.btn-ghost:hover{border-color:var(--accent)}
.btn-pri svg,.btn-ghost svg{width:15px;height:15px;flex:none}

/* ---- dashboard toolbar: search + status filter (GET form, works without JS) - */
.filters{display:flex;flex-wrap:wrap;align-items:center;gap:var(--s3);margin-bottom:var(--s4)}
.filters .input{flex:1 1 240px;width:auto;min-width:0;max-width:340px}
.filters .field-select{flex:none}
/* 窄螢幕：三個控件收進同一列（原本搜尋框獨佔一行、select＋按鈕第二行＝92.8px，佔掉近一整列任務）。
   （字級 16px／可點區 44px 不寫在這裡 —— 全站前台一次收口在檔尾的「≤640 控件規格」段。） */
@media (max-width:640px){
  .filters{gap:var(--s2)}
  .filters .input{flex:1 1 0;min-width:0;max-width:none}
  /* select 的寬度由最長選項決定（en 的 Processing／zh 的處理中），不設上限會把搜尋框壓到
     只剩十個字元。四成寬是兩者都放得下、又還給搜尋框多數空間的分點。 */
  .filters .field-select{flex:0 1 auto;min-width:0;max-width:40%}
  .filters .btn-ghost{flex:none;width:44px;padding:0}
  .filters .btn-ghost .btn-txt{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}
}

/* ---- 「這次上傳」面板（XHR 逐檔進度＋收件後的狀態；無 JS 時整區不存在） ----
   傳完不清掉：它就是新檔的去向，與使用者的搜尋／篩選／頁碼無關（verquil#332）。 */
.uq{margin-bottom:var(--s6)}
.uq-hd{display:flex;align-items:center;justify-content:space-between;gap:var(--s3);margin-bottom:var(--s3)}
.uq-hd h2{font-family:var(--f-mono);font-size:11px;font-weight:400;letter-spacing:var(--tracking-wide);text-transform:uppercase;color:var(--ink-faint)}
/* 收掉面板是使用者的決定（M3：持續性狀態不自己消失）⇒ 鈕小、不與主動作競爭。 */
.uq-clear{min-height:28px;padding:var(--s1) var(--s3);font-size:var(--fs-meta)}
.uq-list{list-style:none;margin:0;padding:0;border:1px solid var(--hairline);border-radius:var(--r-md);background:var(--surface);overflow:hidden}
.uq-row{display:flex;align-items:center;gap:var(--s4);padding:var(--s3) var(--s4);border-bottom:1px solid var(--hairline)}
.uq-row:last-child{border-bottom:0}
.uq-row .nm{flex:1;min-width:0;font-weight:500;color:var(--ink);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.uq-row a.nm:hover{color:var(--accent)}
.uq-row progress{width:132px;height:6px;flex:none;appearance:none;border:0;background:var(--surface-3);border-radius:var(--r-pill);overflow:hidden}
.uq-row progress::-webkit-progress-bar{background:var(--surface-3)}
.uq-row progress::-webkit-progress-value{background:var(--accent)}
.uq-row progress::-moz-progress-bar{background:var(--accent)}
.uq-row .pct{font-family:var(--f-mono);font-size:var(--fs-meta);color:var(--ink-faint);min-width:40px;text-align:right;font-variant-numeric:tabular-nums}
.uq-row .st{font-family:var(--f-mono);font-size:var(--fs-meta);color:var(--ink-muted)}
/* 傳完就不再需要進度數字（Chanel 原則：留一個訊號就好）——之後那一欄講的是轉錄狀態。
   `uploaded`＝收件成功、轉錄中（不是終態）；終態只有 `completed`／`failed`。 */
.uq-row:is([data-state="uploaded"],[data-state="completed"],[data-state="failed"]) :is(progress,.pct){display:none}
/* 狀態色只給真的終態；轉錄中維持 --ink-muted ⇒ accent／ok 不因為每次上傳而變得廉價。 */
.uq-row[data-state="completed"] .st{color:var(--ok)}
.uq-row[data-state="failed"] .st{color:var(--danger)}
/* 完成後檔名就是去向入口（點進稿頁）：底線讓它一眼是連結，不只靠顏色。 */
.uq-row a.nm{text-decoration:underline;text-underline-offset:2px}
.uq-row .retry{min-height:28px;padding:var(--s1) var(--s3);font-size:var(--fs-meta)}

/* ---- toast（完成提示；不擋操作、可關） ------------------------------------ */
/* bottom 加 safe-area：iPhone home indicator 底下的 toast 會被切掉一截 */
.toaster{position:fixed;right:var(--s5);bottom:calc(var(--s5) + env(safe-area-inset-bottom,0px));z-index:20;max-width:min(420px,calc(100vw - var(--s6)))}
.toast{display:flex;align-items:center;gap:var(--s3);background:var(--surface-2);border:1px solid var(--hairline-2);border-left:2px solid var(--accent);border-radius:var(--r-md);box-shadow:var(--shadow-float);padding:var(--s3) var(--s4);color:var(--ink-2);font-size:var(--fs-sm)}
.toast .btn-ghost{min-height:30px;padding:var(--s1) var(--s3);font-size:var(--fs-meta)}
.toast .x{color:var(--ink-faint);font-size:18px;line-height:1;padding:var(--s1) var(--s2);min-height:24px}
.toast .x:hover{color:var(--ink)}
/* ---- file list (dashboard) ------------------------------------------------ */
/* 欄位對齊用 grid（flex 的 flex:1 只讓檔名欄伸縮，時間／狀態／點數逐列漂移＝資料密集大忌）。
   欄序＝檔名 / 建立時間 / 狀態 / 點數 / 改名；跨欄的附加列（失敗原因、展開的改名表單）
   用 grid-column:1/-1 掉到下一列，不與第一列內容重疊。欄軌寫在 --jcols，斷點只改這一個值。 */
/* meta 欄一律 auto（＝「跟內容一樣寬」）。不要改成 min-content：跨欄元素並不會撐胖 intrinsic 欄——
   .why 與展開的動作組都用 grid-column:1/-1，而依 CSS Grid §12.5.1，跨到 flexible 軌（第一欄 1fr）的
   item 其貢獻不分配給 intrinsic 軌。2026-08-20 實測 auto／min-content／max-content 三者的軌寬完全相同
   （192/135/88/156px），min-content 是零收益、卻會在「內容含空白且沒有 nowrap」時把標籤壓成兩行。 */
.joblist{list-style:none;margin:0;padding:0;border:1px solid var(--hairline);border-radius:var(--r-md);background:var(--surface);overflow:hidden;
  --jcols:minmax(0,1fr) auto auto auto auto}
.jrow{display:grid;grid-template-columns:var(--jcols);align-items:center;gap:var(--s2) var(--s4);padding:var(--s3) var(--s4);border-bottom:1px solid var(--hairline)}
/* 每個 <li> 各自成 grid ⇒ 欄寬逐列自己算，時間／點數／狀態會逐列漂移（第一版截圖就是這樣）。
   subgrid 讓所有列共用 <ul> 的欄軌＝真正對齊，同時保留 li 的邊框／hover 盒子。
   不支援 subgrid 的舊瀏覽器落回上面的逐列 grid（版面仍可讀，只是欄不對齊）。 */
@supports (grid-template-columns:subgrid){
  .joblist{display:grid;grid-template-columns:var(--jcols)}
  .jrow{grid-column:1/-1;grid-template-columns:subgrid}
}
/* 第一行的欄位一律釘在 row 1：失敗原因跨欄掉到第二列後，Rename 曾被擠到第三列＝整列多出一段空白 */
.jrow > *{grid-row:1}
.jrow:last-child{border-bottom:0}
.jrow:hover{background:var(--surface-2)}
/* 可點目標 ≥24×24 CSS px（WCAG 2.2 SC 2.5.8 Target Size (Minimum)，AA）。
   檔名是列裡「獨立儲存格中的動作」，不是句子中的連結 ⇒ 不主張 Inline 例外。
   行高只有 22.4px ⇒ 靠 padding-block 把 bounding box 撐到 24（負 margin 抵掉行高變化，列高不變）。*/
.jrow .nm{min-width:0;font-weight:500;color:var(--ink);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
  padding-block:2px;margin-block:-2px}
.jrow .nm:hover{color:var(--accent)}
.jrow > time{font-family:var(--f-mono);font-size:var(--fs-meta);color:var(--ink-faint);font-variant-numeric:tabular-nums;white-space:nowrap}
.jrow .cr{font-family:var(--f-mono);font-size:var(--fs-meta);color:var(--ink-faint);font-variant-numeric:tabular-nums;text-align:right;white-space:nowrap}
/* 失敗原因是 blocks of text，同樣吃上面那把雙尺規（SC 1.4.8 的量、當設計尺用；見 .up-hint 那段）。
   2026-08-20 QA 逐則造件實測：不設上限時，empty_transcript_multi 等 4 則在 1440 就是多行、
   每行 100–107 字元（超上限 34%）——「只有 82 字元且單行」那個判斷只成立於 dev db 現有的少數列。
   孤字用 balance 不用 pretty：QA 逐則實測兩者行數完全相同（列高零差異），但 pretty 只避免「單字孤行」，
   `try again.` 這種兩個詞它不管 ⇒ 末行由 2 個詞變 6–9 個詞，balance 純賺。 */
.jrow .why{font-size:var(--fs-meta);color:var(--danger);font-family:var(--f-mono);max-width:80ch;text-wrap:balance}
.jrow .why:lang(zh),.jrow .why:lang(ja),.jrow .why:lang(ko){max-width:40em}
.jrow > .why{grid-column:1/-1;grid-row:2}
/* 全文命中片段（verquil#198 ②）：與 .why 同一條「列級註記」規則——滿寬、第 2 列；兩者同時存在時
   .why 排第 3 列（失敗的 job 不會有逐字稿，實務上不共存，規則仍寫全）。單行截斷：片段是「定位用」的
   一瞥，不是閱讀用，展開讀全文的動作就是點它。<mark> 用 accent-soft 底、不換字重（字重會讓列高抖）。 */
.jrow > .snip{grid-column:1/-1;grid-row:2;min-width:0;font-size:var(--fs-meta);color:var(--ink-muted);
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;text-decoration:none}
.jrow > .snip ~ .why{grid-row:3}
/* 可讀版排隊註記（ADR 0046）：與 .why 同一條「列級註記」規則，但**刻意不是紅色** ——
   排隊不是失敗。紅色留給真的壞掉的東西，否則使用者會學會忽略紅字（假紅燈比沒有更糟）。 */
.jrow .rqn{font-size:var(--fs-meta);color:var(--ink-muted);font-family:var(--f-mono)}
.jrow > .rqn{grid-column:1/-1;grid-row:2}
.jrow > .snip ~ .rqn{grid-row:3}
.jrow > .snip:hover{color:var(--ink)}
.snip mark,.say mark{background:var(--accent-soft);box-shadow:0 0 0 1px var(--accent-soft);color:var(--accent);border-radius:2px;padding:0 1px}
/* 動作組＝次要動作，收在列尾當安靜的 mono 連結。
   🔴 末欄必須只放「一個」grid item：兩個東西各自寫 grid-column:-2/-1 會座標完全相同、疊在一起
   （2026-08-20 正式站 bug：Rename 與 Delete 疊成「Bealame」，其中一個還是不可逆刪除）。
   同日老闆定案把刪除搬到詳情頁（見 job_detail.html .jdel）⇒ 列尾現在只有改名一個動作。
   容器仍然是必要的、不是殘留 —— 2026-08-20 就地拆掉它實測（details 直接當 .jrow 子元素）：
   收合態不變（1440 summary 都是 x=1327 w=64），但展開態三處壞掉——
   (1) summary 從列尾跳到日期欄（x=1327 w=64 → x=736 w=192），meta 三欄塌到同一個 x=1199，
       時間與點數方框完全相同（192×20）＝疊印；成因是 .rename[open] 自己寫 grid-column:1/-1，
       少了包裝就直接拿列的欄軌去跨欄；
   (2) 失敗原因從表單上方（y=668）掉到表單下方（y=721）＝先看到輸入框才看到失敗原因；
   (3) ≤640 版失去 .jrow .jacts{margin-left:auto} 的靠右錨點，Rename 從 x=285 滑到 x=41。 */
.jrow .jacts{grid-column:-2/-1;justify-self:end; /* 末欄，欄數隨斷點變也不用改 */display:flex;align-items:center;gap:var(--s3);min-width:0}
/* 展開時整組跨欄（純 <details>，無 JS 可用）。:has 是必要的：展開狀態在子層，但要改的是容器
   的 grid 定位。不支援 :has 的瀏覽器落回「表單擠在末欄」＝窄但可用，不破版。
   展開態的目標：summary 留在原本的右側 x 位置（視線錨點不跳），只有表單掉到下一列。
   做法＝容器跨欄後自成「1fr ＋ 動作欄」的 grid，展開的 details 跨滿兩欄並用 column subgrid，
   於是它的 summary 仍落在右側那一欄，form 落到它自己的第二列。
   （試過讓動作組留在 meta 那一列 —— 跨欄的動作組會把 meta 的欄軌壓垮、檔名／時間／點數互相重疊，不可行。）*/
.jrow .jacts:has(details[open]){grid-column:1/-1;grid-row:auto;justify-self:stretch;
  display:grid;grid-template-columns:1fr auto;align-items:start;column-gap:var(--s3);row-gap:var(--s2)}
.jrow .rename{font-family:var(--f-mono);font-size:var(--fs-meta);min-width:0}
.jrow .rename summary{color:var(--ink-faint);cursor:pointer;list-style:none;padding:var(--s1) var(--s2);border-radius:var(--r-xs);min-height:24px;white-space:nowrap}
.jrow .rename summary::-webkit-details-marker{display:none}
.jrow .rename summary:hover{color:var(--ink)}
/* 展開中的動作：盒子跨滿容器（才有第二列可放表單），版位交給 subgrid。 */
.jrow .rename[open]{grid-column:1/-1;grid-row:1;display:grid;grid-template-columns:subgrid;row-gap:var(--s2);align-items:center}
/* summary 留在右側動作欄；展開中＝被按下的頁籤（主文字色＋淡底），和收合態分得出來 */
.jrow .rename[open] > summary{grid-column:2;grid-row:1;color:var(--ink);background:var(--surface-2)}
/* 表單掉到第二列。要寫兩條：新引擎（Chrome 131+／Safari 18.4+）把 details 的內容包進 UA 的
   ::details-content 盒子 ⇒ 真正的 grid item 是它、不是 form；舊引擎沒有那層，form 自己就是 item。
   兩條並存＝兩種引擎都對得上，缺任一條就會出現「表單和 summary 疊在同一列」。 */
/* 表單就排在 summary 左邊（同一列），不掉到第二列：展開只長高一列，輸入框也不會和
   「Rename」隔著一段空白各據一方。要寫兩條：新引擎（Chrome 131+／Safari 18.4+）把 details
   內容包進 UA 的 ::details-content 盒子 ⇒ 真正的 grid item 是它、不是 form；舊引擎沒有那層。 */
.jrow .rename[open]::details-content{grid-column:1;grid-row:1}
.jrow .rename[open] > form{grid-column:1;grid-row:1;align-self:center}
.jrow .rename form{display:flex;flex-wrap:wrap;align-items:center;gap:var(--s2);max-width:560px;min-width:0}
.jrow .rename input{background:var(--surface-2);color:var(--ink);border:1px solid var(--control-border);border-radius:var(--r-sm);padding:var(--s2) var(--s3);min-height:36px;min-width:0;flex:1;font-size:var(--fs-sm)}
/* justify-self:start — subgrid 下 grid item 預設 stretch，chip 邊框會被拉成整欄寬的空盒子。
   （後台表格的 .qs 也吃這條，故基底維持原樣，只有檔案列另外覆寫。） */
.qs{font-family:var(--f-mono);font-size:10.5px;padding:2px 7px; /* optical */ border-radius:var(--r-xs);flex:none;justify-self:start;white-space:nowrap}
/* 檔案列的 chip 反過來填滿狀態欄軌、文字置中 ⇒ 每列的 chip 盒子完全相同，chip 與點數之間
   不再有「16px 或 107px」的逐列跳動空洞（2026-08-20 實測；成因是欄軌吃最寬的標籤、
   chip 卻各自 max-content）。不寫死 px：軌寬＝該欄 max-content，換語系自動跟著走。 */
.jrow .qs{justify-self:stretch;text-align:center}
.qs.done{color:var(--ok);border:1px solid var(--accent-line)}
.qs.run{color:var(--info);border:1px solid var(--info-line)}
.qs.wait{color:var(--ink-faint);border:1px solid var(--hairline-2)}
.qs.fail{color:var(--danger);border:1px solid var(--danger-line)}
.poll-note{font-family:var(--f-mono);font-size:11px;color:var(--ink-faint);margin-bottom:var(--s4)}
/* 手機：這句講的是「不用你動手也會更新」，而它在 390 排成兩行＋間距＝51px，等於半列任務。
   狀態 chip 本來就會自己變，資訊在畫面上是可觀察的 ⇒ 手機不佔這半列（桌機照舊顯示）。 */
@media (max-width:640px){ .poll-note{display:none} }

/* empty state — invitation to act */
.empty{border:1px solid var(--hairline);border-radius:var(--r-md);background:var(--surface);padding:var(--s6);text-align:center;color:var(--ink-muted)}
.empty .hd{font-weight:600;color:var(--ink-2);margin-bottom:var(--s2)}

/* ---- job detail ----------------------------------------------------------- */
/* 詳情頁＝逐字稿編輯器（DESIGN §6.6），版面與元件全在 editor.css；這裡只留跨頁共用的
   .pill／.state-panel／.jdel／.sumry／.tprov／.tnote。 */

/* 逐字稿的 provenance：與 .sumry-prov 同一種語彙（mono、faint），住在它所描述的內容旁邊。 */
.tprov{display:flex;align-items:baseline;gap:var(--s2);margin:0 0 var(--s1);font-family:var(--f-mono);font-size:var(--fs-meta);color:var(--ink-faint)}
.tprov .lab{font-size:11px;letter-spacing:var(--tracking-wide);text-transform:uppercase}
/* 可讀版落款（DESIGN §6.2 C）：與 .sumry-note 同語彙，但放在逐字稿**之上** —— 稿子可以極長，
   透明度標示塞底部＝沒人看得到。與 .tprov 間距 --s1（該規則的 margin-bottom），與逐字稿 --s3。 */
.tnote{display:flex;align-items:baseline;gap:var(--s2);margin:0 0 var(--s3);max-width:44em;
  font-size:var(--fs-meta);line-height:1.6;color:var(--ink-muted)}
.tnote svg{width:14px;height:14px;flex:none;color:var(--accent)}
.pill{display:inline-flex;align-items:center;gap:7px; /* optical */ border:1px solid var(--control-border);border-radius:var(--r-sm);padding:var(--s2) 14px; /* optical */ min-height:36px;font-weight:500;font-size:13px;color:var(--ink);background:transparent;font-family:inherit;cursor:pointer}
.pill svg{width:15px;height:15px;flex:none}
.pill:hover{border-color:var(--accent)}

/* processing / failed panels */
.state-panel{border:1px solid var(--hairline);border-radius:var(--r-md);background:var(--surface);padding:var(--s6);text-align:center}
.state-panel .st{display:inline-flex;align-items:center;gap:var(--s2);font-family:var(--f-mono);font-size:13px}
.state-panel .st .d{width:8px;height:8px;border-radius:50%;background:currentColor}
.state-panel.run .st{color:var(--info)}
.state-panel.run .st .d{animation:pulse var(--dur-slow) var(--ease-inout) infinite alternate}
.state-panel.fail{border-color:var(--danger-line)}
.state-panel.fail .st{color:var(--danger)}
.state-panel p{margin-top:var(--s3);color:var(--ink-muted);font-size:var(--fs-sm)}
/* margin-inline:auto 是必要條件不是裝飾：這段是置中區塊，只給 max-width 會讓盒子靠左錨定，
   文字軸線相對面板最多偏 165px（2026-08-20 QA 實測；數字表全正常、只有截圖露餡）。 */
.state-panel .why{color:var(--danger);font-family:var(--f-mono);font-size:var(--fs-meta);max-width:80ch;margin-inline:auto;text-wrap:balance}
.state-panel .why:lang(zh),.state-panel .why:lang(ja),.state-panel .why:lang(ko){max-width:40em}

/* ---- AI summary (DESIGN §6.2) --------------------------------------------- */
/* 沒有摘要時整區不渲染（模板 branch）⇒ 這裡不需要、也刻意不寫空狀態樣式。
   面板用與 .tools 同一組容器語彙（surface＋hairline＋r-md），accent 只花在「這是 AI 產物」
   這一個訊號上（標籤與清單記號），不整片鍍上 mint —— accent 是稀有品（DESIGN §3.1）。 */
.sumry{margin-bottom:var(--s5);border:1px solid var(--hairline);border-radius:var(--r-md);background:var(--surface);padding:var(--s5)}
.sumry-hd{display:flex;align-items:baseline;justify-content:space-between;gap:var(--s2) var(--s4);flex-wrap:wrap}
.sumry .lab{display:inline-flex;align-items:center;gap:var(--s2);font-family:var(--f-mono);font-size:11px;font-weight:500;letter-spacing:var(--tracking-wide);text-transform:uppercase;color:var(--accent)}
.sumry .lab svg{width:14px;height:14px;flex:none}
.sumry-prov{font-family:var(--f-mono);font-size:var(--fs-meta);color:var(--ink-faint);font-variant-numeric:tabular-nums}
/* 摘要本體＝閱讀模式的排版（行長 36em、行高 1.8，同編輯器正文），它是要被讀完的一段話 */
/* overflow-wrap：摘要是模型寫的，可能夾一段沒有斷點的長字串（網址／編號），同編輯器 .say 的處理 */
.sumry-lede{margin-top:var(--s4);max-width:36em;font-size:var(--fs-body);line-height:1.8;color:var(--ink);overflow-wrap:anywhere}
.sumry-pts{margin-top:var(--s4);max-width:36em;list-style:none;display:grid;gap:var(--s2)}
.sumry-pts li{position:relative;padding-left:var(--s5);font-size:var(--fs-sm);line-height:1.7;color:var(--ink-2);overflow-wrap:anywhere}
/* 記號＝logo 的堆疊色軌縮影，不用 01/02 編號：重點之間沒有順序語意（DESIGN §7 母題） */
.sumry-pts li::before{content:"";position:absolute;left:0;top:.6em; /* optical */ width:12px;height:2px;background:var(--accent-line)}
/* 免責句與摘要之間隔一條髮線＝視覺上仍是同一塊，不做成警告框（它不是錯誤狀態） */
.sumry-note{margin-top:var(--s5);padding-top:var(--s4);border-top:1px solid var(--hairline);max-width:44em;font-size:var(--fs-meta);line-height:1.6;color:var(--ink-muted)}

/* 詳情頁的刪除入口（2026-08-20 從檔案列搬來，老闆：「刪除逐字稿應該不用再顯示到列表」）。
   自助刪除是對外承諾（privacy §9「until you delete them」）＝只換位置、不得消失。
   不可逆動作維持兩步（收合的 <details>，展開才出現確認說明與送出鈕），沿用列表動作的安靜
   mono 語彙 —— 但這裡不是列尾的一格，所以獨立成 class，不去繼承 .jrow .rename 那套 grid。
   放在頁尾、與內容以髮線分隔：它是這頁最後才想做的事，不搶下載／逐字稿的戲；
   刻意**不做成 danger 面板**（整區鍍紅＝把危險動作誤講成錯誤狀態）——
   紅只花在下面那顆送出鈕與 summary 的 hover 上（設計棒 2026-08-20 拍板，見該兩條）。 */
.jdel{margin-top:var(--s7);padding-top:var(--s5);border-top:1px solid var(--hairline)}
/* 兩個危險區相鄰（刪音檔＋刪逐字稿，ADR 0049）：一條髮線就夠，第二個不再畫線、不再拉開。 */
.jdel + .jdel{border-top:0;margin-top:var(--s3)}
.jdel > summary{display:inline-flex;align-items:center;min-height:36px;padding:var(--s1) var(--s3);
  font-family:var(--f-mono);font-size:var(--fs-meta);color:var(--ink-faint);
  border-radius:var(--r-xs);cursor:pointer;list-style:none}
.jdel > summary::-webkit-details-marker{display:none}
.jdel > summary:hover{color:var(--danger)}
.jdel[open] > summary{color:var(--ink);background:var(--surface-2)}
/* 直向堆疊、不用 flex-wrap：確認文案必須在送出鈕**上面**（先讀再按）。
   （wrap 版實測在寬螢幕會把鈕排到說明右邊 —— .hint 的 max-width 讓那一行有剩餘空間，
   flex-basis:100% 擋不住。） */
.jdel form{display:flex;flex-direction:column;align-items:start;gap:var(--s3);margin-top:var(--s3);padding-left:var(--s3)}
/* 確認文案是這個表單裡的一行說明，不是頁級段落 ⇒ 清掉 .hint 的段落級 margin-bottom */
.jdel .hint{margin:0;max-width:52ch;font-size:var(--fs-meta)}
/* 送出鈕的 danger 語彙＝共用的 `.btn-danger` 配方（宣告在下方的詞庫／設定段，一份兩處用）。 */

/* ---- 詞庫／帳號設定：控件列表單、清單、頁級說明、分節標 ---------------------------
   `.term-form`（一列「欄位＋送出鈕」）不只詞庫在用 —— 帳號設定的語言與關閉帳號也吃同一支。
   類名沿用歷史，**範圍是這兩頁的三支表單**，改它要三處一起看。 */
/* `align-items:end`：這一列是「欄位 ＋ 送出鈕」，欄位有可見標籤時整格會比鈕高（設定頁的
   「你的帳號 email」實測 62 vs 40）；預設的 stretch 會把鈕拉成 62px 高、鈕頂對到標籤而不是輸入框。
   標籤是 `.visually-hidden` 的那兩支（語言／新增詞條）兩者等高 ⇒ 這條對它們是零變化。 */
.term-form{display:flex;align-items:end;gap:var(--s3);margin-bottom:var(--s5);max-width:480px}
.term-form .field{flex:1}
.input{width:100%;background:var(--surface);border:1px solid var(--hairline-2);border-radius:var(--r-sm);padding:var(--s2) var(--s3);color:var(--ink);min-height:36px}
.input::placeholder{color:var(--ink-faint)}
.input:hover,.input:focus{border-color:var(--accent)}
/* ---- shared select control (fix: native selects rendered squat vs inputs/buttons) --
   appearance:none 砍原生外觀，min-height/padding/radius 對齊 .input／.btn-*（36px 控件列）。
   chevron＝inline SVG background（ink-muted 灰，hover/focus 由 border 帶 mint，避免雙重搶色）。
   option 樣式各瀏覽器支援有限：僅設 background/color 保深色底可讀，其餘交還原生。 */
.field-select{appearance:none;-webkit-appearance:none;font:inherit;font-size:var(--fs-sm);
  background-color:var(--surface-2);border:1px solid var(--hairline-2);border-radius:var(--r-sm);
  color:var(--ink);min-height:36px;padding:var(--s2) 36px var(--s2) var(--s3); /* 右 36px 留 chevron 位 */
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath d='M2.5 4.5 6 8l3.5-3.5' fill='none' stroke='%238b95a1' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat:no-repeat;background-position:right var(--s3) center;background-size:12px 12px}
.field-select:hover,.field-select:focus{border-color:var(--accent)}
.field-select option{background:var(--surface-2);color:var(--ink)}
.input.field-select{width:100%;background-color:var(--surface)} /* settings 表單內：吃 .input 的滿寬＋surface 底 */
.termlist{list-style:none;margin:0;padding:0;border:1px solid var(--hairline);border-radius:var(--r-md);background:var(--surface);overflow:hidden;max-width:480px}
.termlist li{display:flex;align-items:center;gap:var(--s4);padding:var(--s2) var(--s4);border-bottom:1px solid var(--hairline)}
.termlist li:last-child{border-bottom:0}
.termlist .t{flex:1;min-width:0;overflow-wrap:anywhere}
.termlist button{color:var(--ink-muted);font-size:var(--fs-meta);font-family:var(--f-mono);border:1px solid transparent;border-radius:var(--r-xs);padding:2px 7px /* optical */}
/* 🔴 hover 邊框用實色 `--danger`（vs --surface 7.4:1），**不用 `--danger-line`**：那支 α.32 疊在
   面板底上只有 1.79:1，當互動控件邊框過不了 WCAG 1.4.11 的 3:1。與 `.jdel button` 同一條規則
   （2026-08-20 已為刪除鈕修過一次；「XX-line 半透明 token 只能當裝飾線」是全站鐵則）。 */
.termlist button:hover{color:var(--danger);border-color:var(--danger)}
/* 詞庫沒有詞時的空狀態要和它取代的那份清單**同一把尺**（480px）：不同寬會讓「加入第一個詞」
   前後的容器寬度跳 416px（1440 實測 896 → 480）。`.empty` 基底維持滿寬（dashboard 的搜尋無結果
   要跟著檔案列表滿寬），故用修飾詞就地收窄。 */
.term-empty{max-width:480px}
.hint{color:var(--ink-muted);font-size:var(--fs-sm);margin-bottom:var(--s5);max-width:36em}
/* settings 分節標（mono uppercase，同 .uq-hd h2 語彙）；第二節起與前節拉開 */
.set-h{font-family:var(--f-mono);font-size:11px;font-weight:400;letter-spacing:var(--tracking-wide);text-transform:uppercase;color:var(--ink-faint);margin-bottom:var(--s3)}
.set-h + .hint{margin-bottom:var(--s4)}
.set-h:not(:first-of-type){margin-top:var(--s7)}

/* ---- 說明文字的孤行策略（`.upbar .up-meta`／`.up-hint` 那套的同一份判準，2026-08-21 補到
   設定／詞庫／登入／註冊）------------------------------------------------------------------
   行長：`.hint` 的 36em 是**刻意的閱讀寬、不是上限本身** —— DESIGN §7 條 13 的雙尺規
   （拉丁 ≤80ch／CJK 語系 ≤40em）是天花板，36em 在兩種文字下都在天花板內
   （拉丁 504px≈62ch、CJK 36 字）。**要動它請往窄改、不要往 80ch 撐**：撐到上限只會讓
   一行更難讀，本段的 max-width 不是漏設。
   孤行：這裡以前完全沒有 text-wrap（computed 全是 `wrap`），2026-08-21 實測 8 處違規 ——
   zh 390「言。」2 字、zh 360「個語言。」4 字、zh 1440「確。」2 字、
   en 390「confirm.」「characters.」「password.」孤詞、en 360「numeric.」孤詞。
   四種語言四種處方（理由與實測見上面 `.upbar` 那段，同一份判準不重抄）：
   拉丁 pretty／中文 balance／日文 pretty＋word-break:auto-phrase／韓文 keep-all＋balance。 */
.hint,.empty p,.empty .hd,.card .help li,.card .help > p,.card p.lead{text-wrap:pretty}
:is(.hint,.empty p,.empty .hd,.card .help li,.card .help > p,.card p.lead):lang(zh){text-wrap:balance}
:is(.hint,.empty p,.empty .hd,.card .help li,.card .help > p,.card p.lead):lang(ja){word-break:auto-phrase}
:is(.hint,.empty p,.empty .hd,.card .help li,.card .help > p,.card p.lead):lang(ko){word-break:keep-all;text-wrap:balance}
/* 後台頁級說明段吃同一份四語處方（不另立第二套判準；獨立一組是為了不把 `>` 帶進上面那個
   `:is()` ——`:is()` 取最重的引數當特異度，會把整條規則的特異度一起抬高）。 */
.editor.bo > :is(.form-err,.bo-note){text-wrap:pretty}
.editor.bo > :is(.form-err,.bo-note):lang(zh){text-wrap:balance}
.editor.bo > :is(.form-err,.bo-note):lang(ja){word-break:auto-phrase}
.editor.bo > :is(.form-err,.bo-note):lang(ko){word-break:keep-all;text-wrap:balance}

/* 破壞性動作的送出鈕（2026-08-20 `.jdel` 裁決的可複用版本）：§3.1 的紅是「失敗 或 危險」兩義，
   不可逆的那一下不該和「儲存」長得一模一樣。**紅只花在這一顆鈕**（面板／髮線／說明維持中性，
   整區鍍紅＝把危險動作誤講成錯誤狀態）。邊框用實色 `--danger`，不用 α.32 的 `--danger-line`。
   `.jdel button` 併進同一條規則 ⇒ 配方只有一份，不會兩處各自漂移。 */
.btn-danger,.jdel button{color:var(--danger);border-color:var(--danger)}
.btn-danger:hover,.jdel button:hover{background:var(--danger-soft);border-color:var(--danger)}

/* 危險區（設定頁的關閉帳號）：沿用 `.empty`／`.sumry` 的面板語彙 —— 不可逆動作要有看得見的圍欄，
   而詳情頁的同型動作（`.jdel`）本來就住在面板裡，設定頁卻是裸段落＝同一件事兩種長相。
   **寬度＝504px＝`.hint` 的 36em**：這是頁面上其他段落已經在用的那把尺 ⇒ 面板的**可見**右緣
   和上面兩段說明的**隱形**右緣落在同一條線上（曾試 560，與 504 差 56px＝本專案禁止的
   「10–40px 那種讀成 bug 的中間地帶」的同型：既不是 0 也不夠明顯）。表單本身仍由 `.term-form` 的
   480 收口，面板內距 s5 讓文字寬 456。 */
.dzone{max-width:504px;padding:var(--s5);border:1px solid var(--hairline);border-radius:var(--r-md);background:var(--surface)}
.dzone .hint:last-of-type{margin-bottom:var(--s4)}
.dzone .term-form{margin-bottom:0}
/* 手機把圍欄的內距讓給文字：s5(24) 兩側會讓那段法遵說明從 342 掉到 292（390 實測 5 → 6 行）。 */
@media (max-width:520px){.dzone{padding:var(--s4)}}

/* ---- auth gate (login / signup / verify) ---------------------------------- */
.gate{min-height:100vh;display:flex;flex-direction:column;align-items:center;justify-content:center;padding:var(--s6) var(--s4);background:
  radial-gradient(52% 40% at 50% 12%,var(--accent-soft),transparent 70%),var(--bg)}
.gate .brand{display:flex;align-items:center;gap:9px; /* optical */ margin-bottom:var(--s6)}
.gate .brand svg{width:30px;height:28px;color:var(--accent)}
.gate .brand .wm{font-family:var(--f-display);font-weight:600;font-size:22px;letter-spacing:-.015em}
/* 點數包（credits/buy.html）。auto-fit + minmax ⇒ 不設斷點：390 一欄、768 兩欄、1440 三欄，
   由可用寬度自己決定，不必再為每個斷點各寫一條（也就沒有「漏改一個斷點」這種故障）。
   卡片語彙沿用 `.card`／`.dzone` 的面板值（surface + hairline-2 + r-lg），不造新視覺。 */
.packs{display:grid;grid-template-columns:repeat(auto-fit,minmax(210px,1fr));gap:var(--s4);
  margin:var(--s5) 0;list-style:none;padding:0}
.pack{background:var(--surface);border:1px solid var(--hairline-2);border-radius:var(--r-lg);
  padding:var(--s5);display:flex;flex-direction:column;gap:var(--s2)}
.pack .n{font-family:var(--f-display);font-weight:700;font-size:22px;letter-spacing:-.01em}
.pack .amt{font-size:var(--fs-lead)}
.pack .rate{font-family:var(--f-mono);font-size:var(--fs-meta);color:var(--ink-muted)}
/* 按鈕靠卡片底部對齊：三張卡的價格行長度不同，不釘住底部就會高低參差。 */
.pack form{margin-top:auto;padding-top:var(--s4)}
.pack .btn-pri{width:100%}
/* 頁尾動作列（thanks 頁）：兩顆按鈕並排，窄螢幕自動換行。 */
.pg-act{display:flex;flex-wrap:wrap;gap:var(--s3);margin-top:var(--s5)}
.card{width:100%;max-width:400px;background:var(--surface);border:1px solid var(--hairline-2);border-radius:var(--r-lg);padding:var(--s6);box-shadow:var(--shadow-float)}
.card h1{font-family:var(--f-display);font-weight:700;font-size:22px;letter-spacing:-.01em;margin-bottom:var(--s5)}
.card .fld{margin-bottom:var(--s4)}
.card .fld input{width:100%;background:var(--surface-2);border:1px solid var(--hairline-2);border-radius:var(--r-sm);padding:var(--s2) var(--s3);color:var(--ink);min-height:40px}
.card .fld input:hover,.card .fld input:focus{border-color:var(--accent)}
.card .fld.has-err input{border-color:var(--danger)}
/* 欄位標籤：登入／註冊卡與 app 頁的可見標籤共用同一份宣告（設定頁的「你的帳號 email」是唯一
   可見的那一個；語言／新增詞條的標籤仍是 `.visually-hidden`，它的 absolute+clip 蓋得過 display）。 */
.card label,.editor:not(.bo) .field > label{display:block;font-family:var(--f-mono);font-size:11px;letter-spacing:var(--tracking-wide);text-transform:uppercase;color:var(--ink-muted);margin-bottom:var(--s2)}
.card .help{font-size:var(--fs-meta);color:var(--ink-muted);margin-top:var(--s2)}
/* 密碼規則是 Django 由 AUTH_PASSWORD_VALIDATORS 產生的 `<ul>`（字串不歸前端管），但它以
   瀏覽器預設樣式落地＝40px 縮排＋預設 disc，是全站唯一一處沒吃設計系統的清單。
   只改版式不改字：縮排 40px → 1.1em、marker 走 --ink-faint。
   ⚠️ **高度是持平的、不是變小**：390／en 實測 140px → 140px（26px 的寬度買不回一行，
   4 條規則仍是 40/40/40/20）。曾加過 `li + li{margin-top:2px}`，那 6px 沒買到任何東西 ⇒ 拿掉。
   要真的變短得改字串（Django 的 AUTH_PASSWORD_VALIDATORS help_text），那是後端棒的範圍。 */
.card .help ul{margin:var(--s2) 0 0;padding-left:1.1em}
.card .help li::marker{color:var(--ink-faint)}
.card .err{font-size:var(--fs-meta);color:var(--danger);margin-top:var(--s2)}
.card .fld.has-err .input{border-color:var(--danger)}
/* 錯誤訊息內的行動連結（如「重寄驗證信」）要跟純文字錯誤可區分（可達性稽核 2026-08-01） */
.card .form-err a{color:var(--danger);text-decoration:underline;text-underline-offset:2px}
.card .form-err a:hover{color:var(--ink)}
.card .btn-pri{width:100%;margin-top:var(--s2)}
/* Turnstile widget（partials/turnstile.html）：官方 data-size=flexible ＝ width 100% 但**下限 300px**
   （developers.cloudflare.com/turnstile/get-started/client-side-rendering/widget-configurations/，查證
   2026-09-06）。390 寬手機上 .gate 左右各 16 ＋ .card 左右各 32 ＋ 邊框 ⇒ 卡片內寬只有 292px。
   🔴 先前這裡寫 `overflow:hidden`「裁掉的是外框不是內容」＝ 不成立：真機截圖裁掉的是 Cloudflare
   自己的 `Privacy • Help` 連結（那是它的合規揭露，不是裝飾）。改成在窄螢幕收 .gate .card 的內距，
   讓內寬 >= 300px，widget 完整顯示；不用 data-size=compact（150×140 的直式方塊，與這張卡的
   節奏不合，且要為手機／桌機各渲一顆 widget）。
   🔴 也**不置中**：先前寫 `display:flex;justify-content:center`，實測 `.cf-turnstile` 變成
   flex item ⇒ 主軸寬度收成內容寬（就是那個 300px 下限）並置中，1440 下量到 300px 坐在 334px 的
   容器裡，比它上下的 input／按鈕**窄 34px**（QA 2026-09-06 實測）—— 而同一段註解還寫著它
   「撐滿 .fld 寬」。卡片裡其他控制項一律滿寬 ⇒ 讓它真的滿寬，flexible 才名副其實。*/
.card .turnstile{margin-bottom:var(--s4)}
/* 內寬 = 100vw - 2*16(gate) - 2*padding(card) - 2(border) >= 300 ⇒ 390 下 padding 需 <= 26px，
   取 --s5(24) ⇒ 內寬 308。斷點取 420（>420 時卡片本來就夠寬）。
   🔴 `:has(.turnstile)` 是刻意的**收窄**：這條規則改的是卡片本身（不是 widget），沒有它就會
   在 Turnstile **關閉**時也把 390／420 的 gate 卡片內距 32→24 ＝ 前端偷改視覺系統，而
   DESIGN.md 是唯一視覺權威（QA 2026-09-06）。`partials/turnstile.html` 只在兩把金鑰都設了才
   渲染 `.turnstile` ⇒ 關閉態零視覺差，開啟態才收內距。
   ⚠️ 誠實界定 ①：`:has()` 不支援的舊瀏覽器會整條略過 ⇒ 退回 32px 內距、widget 略微露出卡片
   —— 與修這個 bug 之前同一個狀態，fail-soft，不會裁掉合規連結。
   ⚠️ 誠實界定 ②：DESIGN.md 宣告的最窄斷點是 390，這條解到 390；**再窄就解不掉** —— 300px 是
   Cloudflare 自己的下限，viewport < 356 時 widget 一定寬過卡片。那時我們選「露出去」而不是
   `overflow:hidden` 裁掉它的合規連結。 */
@media (max-width:420px){ .gate .card:has(.turnstile){padding:var(--s5)} }
/* 欄位下方輔助連結（login 的 Forgot password?）：負上距收攏到密碼欄下緣 */
.card .fld-aux{margin:calc(-1 * var(--s2)) 0 var(--s4);font-size:var(--fs-sm);text-align:right}
.card .fld-aux a{color:var(--ink-muted)}
.card .fld-aux a:hover{color:var(--accent);text-decoration:underline}
.gate .alt{margin-top:var(--s5);color:var(--ink-muted);font-size:var(--fs-sm)}
.gate .alt a{color:var(--accent)}
.gate .alt a:hover{text-decoration:underline}
.card p.lead{color:var(--ink-2);line-height:var(--lh-body);margin-bottom:var(--s4)} /* 後接表單欄位（reset form）時不貼死 label */
.card p.lead b{color:var(--ink);font-weight:600}
/* 第三方登入（Google）：分隔線＋ Google 品牌鈕。鈕的配色照 Google 官方 dark theme 規範
   （#131314 面、#8E918F 框、#E3E3E3 字、18px G 標、系統字 14px 500——本站不載 Google Fonts，font-src 只有 self），不套自家 mint——
   品牌鈕的辨識度來自「長得跟別家一樣」；只有尺寸（高 40＝我們的輸入框）與圓角跟卡片對齊。 */
.card .or{display:flex;align-items:center;gap:var(--s3);margin:var(--s5) 0 var(--s4);color:var(--ink-faint);font-size:var(--fs-meta);font-family:var(--f-mono);letter-spacing:var(--tracking-wide);text-transform:uppercase}
.card .or::before,.card .or::after{content:"";flex:1;border-top:1px solid var(--hairline-2)}
.card .btn-google{display:flex;align-items:center;justify-content:center;gap:10px;width:100%;min-height:40px;padding:0 12px;background:#131314;color:#e3e3e3;border:1px solid #8e918f;border-radius:var(--r-sm);font-family:var(--f-body);font-size:14px;font-weight:500;letter-spacing:.25px;transition:background-color var(--dur-fast) var(--ease-out)}
.card .btn-google:hover{background:#2a2b2d}
.card .btn-google svg{flex:none}

/* skip link */
.skip{position:absolute;left:var(--s4);top:-100vh;background:var(--accent);color:var(--on-accent);padding:var(--s2) var(--s4);border-radius:var(--r-sm);z-index:10;font-weight:600}
.skip:focus{top:var(--s4)}

/* ---- mobile nav strip (≤820 sidebar collapses; keeps nav/signout reachable) - */
.mnav{display:none}
@media (max-width:820px){
  .mnav{display:flex;align-items:center;gap:var(--s2);padding:var(--s2) var(--s5);border-bottom:1px solid var(--hairline);overflow-x:auto}
  .mnav a{font-family:var(--f-mono);font-size:12px;color:var(--ink-2);padding:var(--s1) var(--s3);border:1px solid var(--hairline-2);border-radius:var(--r-pill);white-space:nowrap}
  .mnav a.on{color:var(--accent);border-color:var(--accent-line)}
  .mnav form{display:contents}
  .mnav button{font-family:var(--f-mono);font-size:12px;color:var(--ink-muted);padding:var(--s1) var(--s3);border:1px solid var(--hairline-2);border-radius:var(--r-pill);white-space:nowrap}
}

/* ---- RWD (mock breakpoints) ------------------------------------------------ */
@media (max-width:820px){ .app{grid-template-columns:1fr} .side{display:none} }
/* 檔案列：時間／狀態／點數／動作四欄是固定寬（實測 192/135/88/156px＋四個 s4 欄距＝635px），
   檔名欄拿的是剩下的，窄到某個寬度就會被截成「Client call — Nord…」——而檔名是使用者唯一認得的東西。
   門檻不用猜，是解出來的：檔名欄 = 視窗 - 側欄 238 - .editor 內距 64 - 邊框 2 - 635。
   訂「檔名欄不得低於 240px（約 24 個拉丁字元／12 個 CJK）」⇒ 視窗 ≥ 1177 ⇒ 取 1200 為門檻。
   實測（放寬容器後、關掉本斷點）：1200→245px、1180→225、1150→195、1100→161、1024→85、900→0。
   舊值 900 是照「一個動作」訂的，且當年就已經失守（900 時檔名欄實測 0px）；前端棒 2026-08-20 上調到
   1100 方向正確但仍不足（161px≈16 字元）。≤1200 改成檔名獨佔第一行、meta 收成第二行
   （四欄仍靠 subgrid 對齊）——攤成兩行時檔名反而拿到整行，比硬擠一行好。 */
@media (max-width:1200px){
  /* 1fr 給末欄（動作）不給點數：曾寫成 auto auto 1fr auto ⇒ 點數格被拉成 436px、
     數字孤零零貼在左邊。彈性空間放在動作欄前，meta 三欄才會靠左成束、動作靠右。 */
  .joblist{--jcols:auto auto auto minmax(0,1fr)}
  .jrow{gap:var(--s2) var(--s3)}
  .jrow > *{grid-row:2}
  .jrow .nm{grid-column:1/-1;grid-row:1;white-space:normal;overflow-wrap:anywhere}
  .jrow > .why,.jrow > .rqn{grid-row:3}
}
/* ⚠️ 這裡以前多了一個 `}`（a39e110，2026-08-21）：頂層多出的 `}` 讓解析器把緊接的整個
   `@media (max-width:640px)` 區塊當成一條無效的 qualified rule 吞掉 —— 手機上的上傳佇列換行、
   檔名兩行夾、編輯器內距全部失效，而所有測試與截圖照樣綠（verquil#354 截 390 時抓到）。 */

@media (max-width:640px){
  /* 檔名在手機是唯一的辨識依據，但長 CJK 標題會排到四行（實測單列 203.8px＝首屏的四分之一）。
     夾到兩行（390 下約 36 個 CJK 字／68 個拉丁字元）足夠認人，完整檔名點進詳情頁就看得到。 */
  .jrow .nm{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
  /* 窄螢幕：檔名獨佔一行，進度列＋百分比＋狀態排第二行（否則檔名被壓成一個字） */
  .uq-row{flex-wrap:wrap;gap:var(--s2) var(--s3)}
  .uq-row .nm{flex-basis:100%}
  .uq-row progress{flex:1;width:auto}
  .editor{padding:var(--s5)}
}
@media (max-width:520px){
  .topbar{padding:0 var(--s5);gap:var(--s3)}
  .crumb .pre{display:none} /* 手機留檔名就好（截斷規則在上面，全斷點共用） */
  .topbar .acct{gap:var(--s3)}
  /* 手機：欄軌硬撐會把 meta 擠到水平溢出（時間字串本身就 ~150px）⇒ 改回可換行的 flex，
     檔名一行、meta 一行，Rename 靠右。對齊在這個寬度不成立也不重要（一次只看得到幾列）。 */
  .joblist{display:block}
  .jrow{display:flex;flex-wrap:wrap;align-items:center;gap:var(--s2) var(--s3)}
  .jrow > *{grid-row:auto}
  .jrow .nm,.jrow > .why,.jrow > .rqn{flex-basis:100%}
  .jrow .jacts{margin-left:auto}
  .sumry{padding:var(--s4)}
  /* 🔴 `align-items` 在 column 下管的是**水平**：只寫 flex-direction 會讓上面那條 `end`
     把 select／鈕／標籤／輸入框整組推到右邊界（2026-08-21 截圖實見）。改回 stretch＝滿寬。 */
  .term-form{flex-direction:column;align-items:stretch}
}

/* ---- ≤640 控件規格：字級 16px ＋ 觸控目標 44px（DESIGN.md §7 條 10／11 的實作處） ------
   收成一段的理由：這兩條是**對整個前台的宣稱**，散在各元件的 media query 裡就必然漏掉沒人想到的
   頁面 —— #127 就是這樣漏了詞庫、帳號設定、登入表單與列尾 Rename（它們仍是 14px，iOS Safari 一
   聚焦就放大整頁）。要新增前台控件，把選擇器加進下面的 `:is()` 清單，不要在元件內另寫 16px／44px。

   ① 16px：iOS Safari 對 font-size < 16px 的輸入框在聚焦時會自動放大整頁（Apple 的既有行為，
      唯一不 hack 的解法就是把字級設到 16px；不用 maximum-scale=1 那種關掉縮放的反模式）。
   ② 44px：WCAG 2.2 SC 2.5.8 只要求 24×24，手機照 Apple HIG 44pt／Material 48dp 走。
      🔴 **清單式收口會漏頁，這是第二次**（#127 漏了詞庫／帳號設定／登入表單；2026-08-21 又發現
      任務詳情頁的 `.pill` 39px、`.export .dlopts label` 24px、`.jdel > summary` 36px —— 這頁的**核心動作**
      比 dashboard 的次要控件還小）。新增前台控件時**回來加進這份 `:is()`**，並用
      `tools/tap_targets.py` 掃全前台複驗，不要假設只漏了你正在改的那一頁。
      `.rename summary` 與 `.up-hint summary` 是純文字連結、撐高會吃掉列高 ⇒ 用 padding 撐可點區、
      **負 margin 只抵銷「多出來的那一半」**（見下面的數字），列高與 #127 完全相同。

   🔴 **scope 是這段的一部分，不是註解**：選擇器前綴 `:where(.editor:not(.bo),.card)` ＝ 前台 app 頁
   （`main.editor`）與登入／註冊卡（`.gate .card`）。少了它，`.field-select`／`.btn-*` 這種共用類名會
   把**後台**一起撐大 —— 實測 390 下 backoffice 的語言 select 14px/40.4 → 16px/44.0、七顆按鈕全部
   40.4 → 44.0（2026-08-21 code review 抓到）。後台是桌機資料面，`.bo-form` 的 14px 只擋得住
   input/textarea，擋不住 select 與按鈕 ⇒ 只能靠 scope。
   `:where()` 本身特異度 0，整條的特異度由 `:is()` 裡最重的 `.jrow .rename input` 決定＝(0,2,1)：
   剛好蓋得過 `.toast .btn-ghost{min-height:30px}`(0,2,0) 與 `.toast .x{min-height:24px}`(0,2,0)。
   **toast 也在涵蓋範圍內、不是例外**：它是常駐到使用者按掉為止的浮層（不會自動消失），
   兩顆鈕都是真的拇指目標；它是 `position:fixed` ⇒ 撐到 44 的版面成本為 0。
   `.topbar .panel` 也在 scope 內：編輯器的 Export／⋯ 面板（§6.6.12）住在 <header class="topbar">、
   **不在 main.editor 裡**，Title／Speakers 的 `.input` 靠 `main.editor` 前綴套不到（codex 2026-09-05 抓到）。 */
@media (max-width:640px){
  :where(.editor:not(.bo),.card,.topbar .panel) :is(.input,.field-select,.fld input,.jrow .rename input){font-size:16px}
  :where(.editor:not(.bo),.card,.topbar .panel) :is(.input,.field-select,.fld input,.jrow .rename input,
    .btn-pri,.btn-ghost,.toast .x,.pill,.dlopts label,.jdel > summary,.termlist button){min-height:44px}
  /* 🔴 詞庫的「移除」是**破壞性**動作，卻是全前台最小的目標：2026-08-21 實測 41–64×**26.0**
     （390／360／430 × 兩引擎全同）。它躲過 #127 與 2026-08-21 兩次清單收口，因為
     **掃描器掃不到不存在的列** —— demo 帳號的詞庫是空的，`tools/tap_targets.py` 掃到的
     詞庫頁只有空狀態。撐鈕會讓列高 42.4 → 44：把 li 的縱向內距讓給鈕（鈕自己就是那 44），
     列高淨 +1.6px。**寬也要撐**：zh 的「移除」只有 41px。 */
  :where(.editor:not(.bo)) .termlist button{min-width:44px}
  :where(.editor:not(.bo)) .termlist li{padding-block:0}
  /* toast 的關閉鈕是 icon-only（×），高度由上面那條給，寬度要另外撐（27px → 44px）。 */
  :where(.editor:not(.bo)) .toast .x{min-width:44px}
  /* padding 12 ＋ margin −10（不是 −12）：內容行高 20 ⇒ 12/−10 的實佔高＝20+24−20＝24px，
     與 #127 的 10/−8 逐 px 相同 ⇒ 列高 390/430 仍是 110.2/79.4、summary 與同列 time 的中心
     偏移仍是 0（兩引擎實測）。寫 −12 會讓實佔高變 20＝列高少 4px 的無交代位移。 */
  :where(.editor:not(.bo)) .jrow .rename summary{padding-block:12px;margin-block:-10px}
  /* .up-hint summary 原本是 10/−10（實佔 20）⇒ 12/−12 實佔仍是 20，列高不變。 */
  :where(.editor:not(.bo)) .upbar .up-hint summary{padding-block:12px;margin-block:-12px}
}

/* 🔴 **後台的 ≤640：推翻昨天（2026-08-21 上午）那條 `:not(.bo)` 排除**。
   排除的理由寫的是「後台是**桌機**資料面」—— 那個前提現在已知為假：**老闆本人用手機看後台**
   （這輪的既定事實）。前提換了，結論就要重推一次，不能沿用。實測 390 下後台有 **43 個**
   24–43px 的目標（控件一律 40.4）、輸入框一律 14px。
   ① 14px 輸入框在 iOS Safari 聚焦時會自動放大整頁 —— 後台第一個動作就是點搜尋框。
   ② 40.4 → 44：只在 ≤640 生效，**桌機的資料密度一點都不動**（後台密度可以比前台高，
      那是桌機的事；≤640 是另一個情境）。
   代價已量（見 DESIGN.md §6.3.4）：手機頁高 +數十 px，換掉整批不合格的拇指目標。
   `.bo-tbl a` 刻意不在此列 —— 資料表儲存格內的識別連結撐到 44 會讓每列長高 ~18px、
   25 列＝+450px，掃讀密度正是資料表存在的理由；它已滿足 24px 硬底線，屬**慣例例外**
   （理由登記在 `tools/tap_targets.py` 的 EXCEPTIONS，與 `.jrow .nm` 同型）。 */
@media (max-width:640px){
  .editor.bo :is(.bo-filters input,.bo-form input,.bo-form textarea,.field-select){font-size:16px}
  .editor.bo :is(.bo-filters input,.bo-form input,.bo-form textarea,.field-select,
    .btn-pri,.btn-ghost,.bo-reveal > summary){min-height:44px}
  /* 🔴 連結**預設就是 44、由資料面自己 opt out**，而不是列舉「哪些連結要撐大」——
     列舉必漏頁，這個專案已經連漏三次（#127 三頁／詳情頁三個控件／整個後台）。
     實證漏網：任務詳情頁的「重試歷史」連結**只有在該任務真的被重試過才存在**，
     dev DB 裡一筆都沒有 ⇒ 掃描器從來沒量過它。用預設規則就不必先猜到它。 */
  .editor.bo a{min-height:44px;display:inline-flex;align-items:center}
  /* 唯二 opt out：資料表儲存格與 `.bo-facts` 的識別連結。理由與代價實測見上方註解與
     `tools/tap_targets.py` 的 EXCEPTIONS（每列 +13.6px、25 列的表 +340px）。 */
  .editor.bo :is(.bo-tbl,.bo-facts) a{min-height:0;display:inline-block}
  /* 篩選列的搜尋框在 390 撐不下 260px 下限（342 的容器扣掉別的控件）⇒ 讓它整行 */
  .bo-filters input[type=search]{flex:1 1 100%;min-width:0}
}

/* ---- Backoffice (ops) — token-driven tables/forms, DESIGN §3 --------------- */
/* 🔴 後台的頁標題常常**整個就是一個 email 或檔名**（沒有空白可斷）⇒ 不給斷行機會就把整頁撐寬：
   390 實測會員頁 h1 溢出自己的欄 98–113px、`documentElement.scrollWidth` 390 → **464／479**
   （2026-08-21 量到的既有缺陷，與這輪的欄寬無關；同一條處置早已用在 `.jhd h1`）。 */
.editor.bo h1{overflow-wrap:anywhere}
.bo-filters{display:flex;gap:var(--s3);align-items:center;margin:var(--s5) 0 var(--s5);flex-wrap:wrap}
.bo-filters input[type=search],
.bo-form input,.bo-form textarea{background:var(--surface-2);border:1px solid var(--hairline-2);border-radius:var(--r-sm);padding:var(--s2) var(--s3);color:var(--ink);min-height:36px;font-size:var(--fs-sm)}
.bo-filters input[type=search]{min-width:260px}
.bo-filters input:hover,.bo-filters input:focus,
.bo-form input:hover,.bo-form input:focus,.bo-form textarea:hover,.bo-form textarea:focus{border-color:var(--accent)}
/* 窄螢幕的寬表：只讓表格自己橫向捲，整頁不得被推寬（否則版面整片可左右拉）。
   role=region+tabindex=0＝可捲容器要能用鍵盤捲、且螢幕閱讀器報得出這是哪張表。 */
.bo-scroll{overflow-x:auto;overscroll-behavior-x:contain;padding:5px;margin:-5px} /* padding/margin 互相抵銷：只為了不裁掉表內連結的 focus ring（2px 環＋3px offset） */
.bo-scroll:focus-visible{outline-offset:-1px;border-radius:var(--r-sm)}
/* 🔴 **欄寬是設計出來的，不是長出來的**（2026-08-21，取代 `width:max-content;max-width:100%`）。
   舊寫法的寬度 100% 由內容決定 —— `max-content` 的定義就是「不做任何軟斷行、撐到內容不折為止」
   （MDN）⇒ 同一張表**空的時候縮成一團、滿載時撐爆**：實測任務總表 1280 下空表 **484px**（容器 988）、
   滿載 **1306px**（橫捲 318）；會員表 577 ↔ 1029；逐字稿表 205 ↔ 736。這不是 bug，是
   **根本沒有欄寬設計**。
   改成 `table-layout:fixed` ＋ `width:100%` ＋ 每張表一份 `<colgroup>`：
   ① fixed 的欄寬來源依 CSS 2.1 §17.5.2 是「`<col>` 的 width → 首列儲存格的 width → 平分」，
      **完全不看後續列的內容** ⇒ 有幾筆資料、資料多長，都不再改變版面。
   ② fixed 要生效，table 的 `width` **必須明確指定**（MDN：width 為 auto 時 fixed 無效）——
      `width:100%` 同時解掉「空表超極小」：一筆都沒有時表格照樣是整幅寬。
   ③ 欄寬用 **`%`** 不用 `ch`：`ch` ＝「0」的推進寬（窄字，CSS Values 4），拿它估中文欄寬會
      系統性低估約一半（全形要用 `ic`＝「水」）；而且 `ch` 綁元素自身字型 —— 本表 thead 是 mono、
      td 不是，同一份 `ch` 值在兩處解出不同像素。比例式在五語都成立（GOV.UK Design System 的
      Table 也是走 `%` 的比例 override class）。
   ④ `min-width`（下方各表自報 `--tbl-min`）＝窄視窗的下限：視窗夠寬時 `100%` ≥ min-width、
      走 `%` 欄寬；視窗變窄時 min-width 生效、在 `.bo-scroll` 內橫捲（既有行為不變）。
   每欄的 % 怎麼算、依據是哪一個字串，見 DESIGN.md §6.3.8 的欄寬表（每欄都對得上一個量到的像素）。 */
.bo-tbl{table-layout:fixed;width:100%;min-width:var(--tbl-min);border-collapse:collapse;
  font-size:var(--fs-sm);margin-bottom:var(--s5)}
/* 🔴 fixed 之下，塞不進自己欄位的內容**不會撐寬欄位、會直接溢出到隔壁**（這正是 fixed 的定義）
   ⇒ 斷行保護從「調校用的 class」升級成**表格的預設**。用 `anywhere` 不用 `break-word`：
   `break-word` 不把斷點算進 min-content（本專案 2026-08-16 試過又換回來）。
   ⚠️ **`break-word` 在這裡是壞的，理由與 auto layout 那次不同、在 fixed 之下依然成立**
   （2026-08-21 實測 18 個溢出儲存格）：`.bo-tbl a` 為了 24px 命中區是 `display:inline-block`
   （見上方 SC 2.5.8 那段），**inline-block 是原子盒、寬度＝shrink-to-fit**；`anywhere` 會改變
   min-content、`break-word` **不會**（CSS Text 3 明文差異）⇒ 連結盒子照 max-content 撐開，
   `solo.member.for.single.row@verquil.invalid` 溢出自己的欄 **103px**。
   下一個人（包括我自己）還會再想試一次 break-word：先看這一段。
   後台是營運工具，檔名／信箱／provider id 被截掉就失去識別價值 ⇒ **一律換行，不截斷**
   （截斷無官方規範背書，且 `title` 當 tooltip 對鍵盤使用者不可用 —— TPGi／GitHub Primer）。
   ⚠️ 過去 `anywhere` 會在窄欄折成「一行一個字元」（實測整頁 15596px），那是因為 auto layout
   會把欄位壓到 min-content；**fixed 之下欄寬是 % 算出來的、壓不下去，那個失效模式結構上不存在** ——
   所以這裡不需要（也不該再加）`min-width:10ch` 那類逐欄下界。 */
.bo-tbl :is(td,th){overflow-wrap:anywhere}
/* 🔴 **手機空表＝空狀態取代整張表**（2026-08-21 修正）。`width:100%` 已經讓空表回到整幅
   容器寬（1280 實測任務總表 484 → **978**，老闆講的「空表超極小」就是這一條解的）。
   桌機**保留**設計的欄格 —— 篩選前後表頭位置不跳動，才看得出「剛才篩掉的是哪些欄的資料」。
   窄螢幕則相反：976px 的欄格會把唯一那句話推到 `.bo-scroll` 的可捲區右邊、畫面外
   （es「Ningún trabajo coincide.」在 390 下右緣落在 651px，容器可視只到 352 ⇒ 手機看到
   一張空白表格，**要橫捲才找得到那句話**）。
   ⚠️ **只把表格改回 `table-layout:auto` 解決不了**（前一版就是這樣寫的，錯的）：
   `thead th{white-space:nowrap}` 仍然強制整排表頭不折行 ⇒ auto layout 照樣按表頭的
   intrinsic minimum 撐寬，訊息照樣在畫面外。**要拿掉的是表頭本身**，不是欄寬演算法。
   ⇒ ≤640 的空表把 `thead` 收掉（`display:none`）：沒有資料就沒有欄要對齊，
   剩下唯一那格空狀態訊息拿到整幅容器寬、不需橫捲就完整可見。
   這一步等於**在手機空表這個情況下靠向 IBM Carbon 的「空狀態取代整張表」**，包含它的無障礙
   理由：`display:none` 同時把表頭移出無障礙樹 ⇒ 螢幕閱讀器不必先念完九欄的表格結構才聽到
   「沒有資料」。桌機與 Carbon 的分歧（保留欄頭）維持不變。理由全文＝DESIGN.md §6.3.8。
   量測指標也跟著換：判準是「訊息元素的 `getBoundingClientRect().right` ≤ `.bo-scroll` 的
   可視右緣」，**不是** `documentElement.scrollWidth` —— 後者量不到「被推到捲動容器內部右邊」
   這種溢出（前一版驗收就是這樣拿到假綠燈的）。 */
@media (max-width:640px){
  .bo-tbl:has(.bo-empty){table-layout:auto;min-width:0}
  .bo-tbl:has(.bo-empty) col{width:auto}
  /* 🔴 上面整段論證的規則本體。2026-08-21 這裡原本是一個字面的 MUTED 佔位註解，跟著 #151 上線 ——
     14 行論證（含無障礙理由）描述了一條不存在的規則，老闆在手機上看到空表照樣有表頭時問出來的。
     驗收沒抓到是因為造的測試資料每張表都有資料，空狀態從沒被渲染過。 */
  .bo-tbl:has(.bo-empty) thead{display:none}
}
/* 各表的橫捲下限：取「所有 nowrap 欄（表頭、數字、日期、chip、按鈕）在**五語最寬**的那一語
   都還放得下」的最小整數寬。算式與逐欄依據＝DESIGN.md §6.3.8。 */
.t-jobs,.t-members,.t-ledger,.t-mjobs{--tbl-min:976px} /* 1280 的內容欄實寬 978 ⇒ 桌機不橫捲 */
/* 2026-08-24：收入／毛利兩欄刪除（收入無法逐筆歸屬，見 margin.html 註解）⇒ 9 欄變 7 欄，
   橫捲下限隨之降低；es 的「ID de solicitud del proveedor」表頭單欄仍佔 242px，故仍需 min。 */
.t-margin{--tbl-min:900px}
/* 漏斗表只有三欄、兩欄是短數字 ⇒ 下限由 nowrap 表頭在 es（最長那一語）撐出來。 */
.t-funnel{--tbl-min:520px}
/* 🔴 **窄螢幕：這張表不橫捲**（fresh QA 2026-09-06）。390 寬時 `.bo-scroll` 可視只有 336px，
   而 520 的下限把三個數字全推到 411px 之後 ⇒ 手機上要橫捲才看得到任何一個數字，
   第一屏一個數字都沒有。⚠️ `a11y_scan.py` 的水平溢出檢查**看不到這件事**（內容溢出的是
   `.bo-scroll` 這個捲動容器，不是文件），所以它一路全綠 —— 這是量測工具的盲點，不是沒問題。
   其他後台表（jobs／members／margin）欄數多、每欄都有識別價值，橫捲是刻意的；
   這張只有「步驟／人數／百分比」三欄，全部塞得進 336px，沒有理由跟著捲。
   兩件事一起做才有效：拿掉下限，並讓表頭可以折行 —— `.bo-tbl thead th` 是全域 `nowrap`
   （為了五語表頭不斷行），不解除的話 fixed layout 下表頭會直接溢出到隔壁欄。
   實測（390 寬，2026-09-06）：改前 `.bo-scroll` scrollWidth 347 / clientWidth 336、
   表頭溢出自己的欄 7px 與 16px；改後 **336 / 336、零溢出**，五語最長的 es
   （表頭「Del total de registros」、列名「Obtuvo una transcripción terminada」）同樣是 336/336。 */
@media (max-width:640px){
  .bo-tbl.t-funnel{--tbl-min:0}
  /* 🔴 選擇器要**兩個 class**：`.bo-tbl thead th` 的 nowrap 與 `.t-funnel thead th` 特異度
     相同（0,1,2），而它在本檔中排在後面 ⇒ 同分時後者勝，單一 class 的覆寫寫了等於沒寫
     （第一版就是這樣，實測表頭照樣溢出隔壁欄 7px／16px）。`.bo-tbl.t-funnel` 是 (0,2,2)，
     與檔案順序無關。 */
  /* 🔴 表頭折行要折在**空白處**，不是字中間：`.bo-tbl :is(td,th)` 全域是
     `overflow-wrap:anywhere`（給檔名／email 用的），套在表頭上會把 es 的「PERSONAS」
     斷成「PER / SON / AS」、「REGISTROS」斷成「REGISTR / OS」（2026-09-06 截圖抓到）。
     表頭是**控制項文字**，不是要斷的長字串 ⇒ 這裡收回 `normal`，只在空白處折。
     🔴 同時把**字距**收回 `normal`：全域表頭是 `letter-spacing:var(--tracking-wide)` ＋
     大寫，那讓 es 的「PERSONAS」量到 90px、放不進 67px 的欄（實測溢出 23px）。
     收掉字距後「PERSONAS」≈53px、「REGISTROS」≈59px，兩欄都留有餘裕。
     只動 ≤640 的這一張表 —— 桌機與其他後台表的表頭字距完全不變。 */
  .bo-tbl.t-funnel thead th{white-space:normal;overflow-wrap:normal;letter-spacing:normal}
  /* 窄螢幕的欄寬另設一組：兩個數字欄要放得下五語最長的**單字**（實測 es「PERSONAS」80px、
     「REGISTROS」90px；桌機那組 56/17/27 在 336px 下只給到 57／91，不夠）。
     44/25/31 ＝ 實測 143／82／101px，兩個數字欄都有餘裕；步驟名那一欄可折行，吃剩下的寬度。
     ⚠️ 選擇器同樣要**兩個 class**：桌機那組 `.t-funnel col:nth-child(n)` 排在本檔更後面，
     特異度相同時後者勝 ⇒ 單一 class 的覆寫寫了等於沒寫（實測欄寬仍是 183/55/88）。 */
  .bo-tbl.t-funnel col:nth-child(1){width:44%}
  .bo-tbl.t-funnel col:nth-child(2){width:25%}
  .bo-tbl.t-funnel col:nth-child(3){width:31%}
}
/* 逐字稿是**閱讀面**不是掃讀面：欄寬給到 40em 就沒有意義了，多出來的只是空白。
   ⇒ 唯一一張同時封頂的表（`max-width`）；行長由「內容欄的 %」直接決定，
   原本的 `.bo-tbl .say{max-width:40em}` 因此成為重複的第二把尺，已刪。 */
.t-content{--tbl-min:460px;max-width:760px}
/* 任務總表 ── #id｜會員(email)｜檔案｜狀態｜時長｜日期｜操作 */
.t-jobs col:nth-child(1){width:9%}     /* #id：表頭 es「Trabajo」59px 為準 */
.t-jobs col:nth-child(2){width:19%}    /* 會員 email：吃剩餘寬度、可折行 */
.t-jobs col:nth-child(3){width:19%}    /* 檔名：同上 */
.t-jobs col:nth-child(4){width:15%}    /* 狀態 chip：es「Transcribiendo」114px */
.t-jobs col:nth-child(5){width:9.5%}   /* 時長 HH:MM:SS 48px，表頭「Duración」67px 為準 */
.t-jobs col:nth-child(6){width:15%}    /* 日期 `2026-08-21 19:12` 115px */
.t-jobs col:nth-child(7){width:13.5%}  /* 操作：es 的 Reintentar 鈕整顆 102px */
/* 會員表 ── email｜註冊日｜語言｜點數餘額｜狀態｜累計消費 */
.t-members col:nth-child(1){width:25.5%} /* email＝識別欄，剩餘寬度全給它 */
.t-members col:nth-child(2){width:17.5%} /* 表頭 es「Fecha de registro」142px */
.t-members col:nth-child(3){width:9%}    /* 語言：值「繁體中文」56px */
.t-members col:nth-child(4){width:17.5%} /* 表頭 es「Saldo de créditos」142px */
.t-members col:nth-child(5){width:11%}   /* 狀態 chip：es「Desactivado」77px */
.t-members col:nth-child(6){width:19.5%} /* 表頭 es「Créditos consumidos」159px */
/* 毛利表 ── #id｜會員｜終局｜收入｜轉錄｜AI｜成本｜毛利｜provider request id */
.t-margin col:nth-child(1){width:8%}     /* #id */
.t-margin col:nth-child(2){width:18%}    /* 會員 email：可折行 */
.t-margin col:nth-child(3){width:11%}    /* 終局，可折行 */
.t-margin col:nth-child(4){width:13%}    /* 表頭「Transcription」109px */
.t-margin col:nth-child(5){width:9%}     /* AI 成本 0.000000 60px */
.t-margin col:nth-child(6){width:8%}     /* 成本 0.0000 ＋舊費率星號 */
.t-margin col:nth-child(7){width:33%}    /* provider id：表頭 es 242px；值可折行 */
/* 會員頁帳冊 ── 日期｜類型｜金額｜原因｜任務｜操作者 */
.t-ledger col:nth-child(1){width:15%}    /* 日期時間 115px */
.t-ledger col:nth-child(2){width:15%}    /* 類型：es「Asignación manual」118px */
.t-ledger col:nth-child(3){width:10%}    /* 金額：表頭 es「Cantidad」67px 為準 */
.t-ledger col:nth-child(4){width:33%}    /* 原因＝自由文字，吃剩餘寬度 */
.t-ledger col:nth-child(5){width:9%}     /* #任務 */
.t-ledger col:nth-child(6){width:18%}    /* 操作者 email：可折行 */
/* 會員頁任務表 ── #id｜檔案｜狀態｜時長｜成本(USD)｜日期 */
.t-mjobs col:nth-child(1){width:9%}
.t-mjobs col:nth-child(2){width:38%}     /* 檔名＝這張表的識別欄，剩餘寬度全給它 */
.t-mjobs col:nth-child(3){width:15%}     /* 狀態 chip（同任務總表，狀態值集合一樣） */
.t-mjobs col:nth-child(4){width:10%}
.t-mjobs col:nth-child(5){width:13%}     /* 表頭 es「Costo (USD)」92px */
.t-mjobs col:nth-child(6){width:15%}
/* 漏斗表 ── 步驟｜人數｜佔註冊（verquil#310；「佔上一步」那一欄已刪，理由＝ops/funnel.py） */
.t-funnel col:nth-child(1){width:56%}    /* 步驟名＝識別欄，剩餘寬度全給它、可折行 */
.t-funnel col:nth-child(2){width:17%}    /* 人數：整數，表頭 es「Personas」為準 */
.t-funnel col:nth-child(3){width:27%}    /* 表頭 es「Del total de registros」；值 `100.0%` */
/* 逐字稿 ── 開始｜講者｜內容 */
.t-content col:nth-child(1){width:17%}   /* 時間碼 00:00，表頭 es「Inicio」50px */
.t-content col:nth-child(2){width:21%}   /* 講者標籤：es「Hablante 1」68px */
.t-content col:nth-child(3){width:62%}   /* 內容＝閱讀欄，760px 上限下約 33em */
.bo-tbl thead th{font-family:var(--f-mono);font-size:11px;letter-spacing:var(--tracking-wide);text-transform:uppercase;color:var(--ink-muted);text-align:left;padding:var(--s2) var(--s3);border-bottom:1px solid var(--hairline-2);white-space:nowrap} /* 表頭＝控件文字，五語（ja/ko/es 詞更長）皆不得斷行；zh-hant「任務」曾折行 */
/* 🔴 `position:relative` 不是裝飾，是**把 `.visually-hidden` 關回儲存格裡**：
   它是 `position:absolute`（tokens.css），而 absolute 元素只被「自己的**包含塊**」裁切 ——
   儲存格若沒有定位脈絡，它的包含塊就是初始包含塊，於是**整個逃出 `.bo-scroll` 的
   `overflow-x:auto`**，用未捲動的座標把 documentElement.scrollWidth 撐大。
   實測：毛利表加了兩個 `.visually-hidden` 的缺值說法後，390 下整頁 390 → **743**
   （en，zh-hant 597）＝全站唯一一處水平溢出，而畫面上完全看不出來（那個元素是隱形的）。
   **視覺隱藏 ≠ 不佔位**；橫捲容器裡放 absolute 後代，一律先給儲存格 relative。 */
.bo-tbl td,.bo-tbl tbody th{padding:var(--s3);border-bottom:1px solid var(--hairline);
  vertical-align:top;position:relative}
/* 每列第一格是 `<th scope="row">`（GOV.UK Table／Carbon 慣例）：讓「Retry」這種一頁 25 顆
   同名的鈕有可程式判定的列上下文。它是**資料**不是欄標題 ⇒ 外觀必須和 td 一模一樣，
   否則會被讀成「每列都有一個小標題」。上面那條 `.bo-tbl th` 的 mono/uppercase/muted
   只屬於 thead。 */
.bo-tbl tbody th{font-family:inherit;font-size:inherit;letter-spacing:normal;text-transform:none;
  color:var(--ink);font-weight:400;text-align:left}
.bo-tbl tbody th.num{text-align:right}
.bo-tbl .num{text-align:right}
.bo-tbl .num,.bo-tbl .tnum,.bo-tbl time{white-space:nowrap} /* 數字／時間碼／日期欄窄欄壓縮時不得斷行 */
/* 🔴 **後台唯一的「這一格點得下去」線索，原本畫在 1.40:1 上**（2026-08-21 實測）：
   底線色用 `--hairline-2`（rgba(150,172,180,.20)）疊在 --bg 上混出 #272E31，對背景只有
   **1.40:1** —— 連非文字的 3:1 都不到，五個信箱裡只有一個看得出有底線（截圖實見）。
   `-line`／`hairline` 這類半透明 token 是**裝飾線**，凡是「使用者必須看見才知道能不能點」
   的線就不能用它（同一條規則已在 `--danger-line` 當互動邊框上踩過 N 次）。
   改用實色 `--ink-muted`（#8b95a1，對 --bg **6.37:1**、對 --surface 6.08:1）：看得出是連結，
   又不會亮到跟正文（--ink 17.09:1）搶。
   後台是掃讀工具，「哪幾格可以點」是它的第一個問題。 */
.editor.bo a:not(.btn-ghost):not(.btn-pri),
.bo-tbl a{color:var(--ink);text-decoration:underline;text-decoration-color:var(--ink-muted);
  text-underline-offset:2px}
.editor.bo a:not(.btn-ghost):not(.btn-pri):hover,
.bo-tbl a:hover{text-decoration-color:var(--accent)}
/* 🔴 **SC 2.5.8（AA）：後台的連結全部只有 18px 高** —— 表格儲存格與 `.bo-facts` 裡的
   信箱／任務編號連結，量到 18.0×（`.bo-facts` 20.0），低於 24px 硬底線 81 個。
   它們**不能主張 inline 例外**：那條例外講的是「句子中的連結」，住在自己儲存格／自己
   欄位裡的識別連結不算（DESIGN.md §9 對檔案列已經這樣裁過一次）。
   修法沿用 `.jrow .nm` 已裁決的手法：`padding-block` 撐開命中區、負 margin 抵掉 ⇒
   **列高與欄寬都不變**，只有可點面積變大。 */
.editor.bo a:not(.btn-ghost):not(.btn-pri):not(.skip),
.bo-tbl a{display:inline-block;padding-block:4px;margin-block:-4px}
/* 🔴 **列級註記不准住在欄裡** —— 任務總表的失敗原因原本塞在「狀態」儲存格內，實測把
   STATUS 欄撐成 **439px**（chip 本身約 60px），Duration／Date／Actions 被推到右邊、
   每列中間空一大段；390 下更慘：表格被壓到 min-content，整頁高 **6210px（zh-hant 8210）**。
   這與檔案列「動作欄只放動作、列級註記掉到下一行」是同一條規則（DESIGN.md §6.2 已裁決）。
   修法＝註記獨立成一列 `tr.bo-why` 跨滿整表：欄寬回到由資料決定，註記拿到整行寬。
   配套：註記列自己畫收尾線，**主列的下框線要收掉**，否則一筆資料被兩條線切成兩列。
   🔴 **`:is(td,th)` 不是保險，是這條規則唯一正確的寫法**（2026-08-21 code review 抓到的回歸）：
   收線規則原本只寫 `td`，而**同一輪**每列第一格改成了 `<th scope="row">`（見上方 row header 註解）
   ⇒ 識別碼那一格照樣畫線、其餘格子不畫 ＝ 註記上方留下一截**只有第一欄寬的髮線殘段**，
   正好就是本段宣稱在防的東西。截圖一眼可見（`_shots/…/whyrow-before.png`），
   **量測抓不到**（每格單獨看都「符合自己的規則」）。
   通則：`border-collapse` 的表格裡，凡是**針對儲存格**的規則都要 `:is(td,th)` ——
   列的第一格是不是 `th`，是隨時可能改的 markup 決定，不該讓外觀規則跟著它漂移。 */
.bo-tbl tr.bo-why :is(td,th){padding:0 var(--s3) var(--s3);border-bottom:1px solid var(--hairline);
  font-size:var(--fs-meta);color:var(--ink-muted);line-height:1.5}
.bo-tbl tr:has(+ tr.bo-why) :is(td,th){border-bottom:0}
/* 註記列裡的警示文字：整句會折行，**不可**用 .qs（那是 nowrap 的 chip，長句會把表撐爆） */
.bo-warn{color:var(--danger)}
.bo-empty{color:var(--ink-muted);text-align:center;padding:var(--s6) 0}
/* 缺值：資料表既有的 `—` 語彙，弱一階讓真數字跳出來（--ink-faint 對 --bg 5.66:1）。
   它是「算不出來」不是「零」—— 兩者在毛利表上差很多，所以不能印 0.0000。 */
.bo-tbl .na{color:var(--ink-faint)}
/* 分頁器（dashboard 與 backoffice 共用）：頁碼文字走 mono／tabular，翻頁時數字不跳動 */
.pager{display:flex;gap:var(--s4);align-items:center;flex-wrap:wrap;margin:var(--s5) 0}
.pager .tnum{font-family:var(--f-mono);font-size:var(--fs-meta);color:var(--ink-faint)}
/* 🔴 `auto-fill` 而不是 `auto-fit`：`auto-fit` 會把**空軌塌掉**，於是同一頁上兩張
   `.bo-facts`（任務詳情頁的「概況」4 格 vs「Timeline」3 格）各自算出不同的欄位位置，
   同一種元件在同一頁對不齊。`auto-fill` 保留空軌 ⇒ 兩張表共用同一組欄位起點。 */
/* 🔴 min 軌寬必須跟容器上限對過帳：`.editor.bo .bo-facts` 的 max-width＝960px（見檔頭），
   min=180px 時五軌要 5×180＋4×16(gap)＝964px > 960 —— **差 4px**，於是會員詳情頁的五格
   在 1440 寬螢幕上永遠只排四欄、第五格掉到第二行、右側整片留白（2026-08-27 實測：
   computed tracks＝228×4）。min=176px ⇒ 五軌 944px ≤ 960 ✓；(960−4×16)/5＝179.2 為上限，
   取 176（16 的倍數、留 slack）。改 max-width 或 gap 時這裡要重新對帳。 */
.bo-facts{display:grid;grid-template-columns:repeat(auto-fill,minmax(176px,1fr));gap:var(--s4);margin:var(--s5) 0}
/* 🔴 **標籤一折行，值就沉不到同一條基線** —— 毛利看板實測 en@768 與 @1440 同一列的值
   高低差 **17px**（`Settled credits` 一行 vs `Provider cost — settled (USD)` 兩行）。
   而那一整頁存在的意義就是把數字擺在一起比。zh-hant 沒事（標籤短、不折行）＝**只量一種
   語言就會判它「現況就好」**。
   修法＝每格自己是 flex column、標籤 `flex:1` 吃掉高度差，值一律沉到格底。
   （同型病灶：表頭有高有低時要 `vertical-align:bottom`，見 `.bo-tbl th`。） */
.bo-facts > div{display:flex;flex-direction:column}
.bo-facts dt{flex:1;font-family:var(--f-mono);font-size:11px;letter-spacing:var(--tracking-wide);text-transform:uppercase;color:var(--ink-muted);margin-bottom:var(--s1)}
.bo-facts dd{font-size:var(--fs-body);color:var(--ink);margin-inline-start:0} /* dd 預設 40px 縮排會讓值對不齊 dt */
/* 重試歷史等「一小串記錄」：帳冊語彙（無項目符號、每列一條髮線），與 `.bo-tbl` 的列節奏一致。
   ≤640 的 44px 由上面那條「後台連結預設 44」負責，這裡不重複宣告。 */
.bo-list{list-style:none;margin:0}
.bo-list li{display:flex;align-items:center;gap:var(--s3);flex-wrap:wrap;
  padding:var(--s3) 0;border-bottom:1px solid var(--hairline);font-size:var(--fs-sm)}
.bo-panel{margin:var(--s7) 0}
.bo-panel h2{font-family:var(--f-display);font-size:18px;margin-bottom:var(--s4)}
.bo-form{max-width:480px}
.bo-form .fld{margin-bottom:var(--s4)}
.bo-form label{display:block;font-family:var(--f-mono);font-size:11px;letter-spacing:var(--tracking-wide);text-transform:uppercase;color:var(--ink-muted);margin-bottom:var(--s2)}
.bo-form input,.bo-form textarea{width:100%}
.bo-actions{display:flex;gap:var(--s3);flex-wrap:wrap;align-items:start}
/* 🔴 **後台破壞性動作的兩步揭示**（2026-08-21）：`.jdel`／設定頁「關閉帳號」已裁決的語彙，
   搬進後台 —— 差別在**受害者不是操作者自己，是客戶的帳號與錢**，所以護欄只能更厚不能更薄。
   在此之前「停用帳號」與「扣點」都是一鍵送出，而且跟旁邊那顆無害動作（寄重設信／給點）
   長得**一模一樣**、相距 12px。
   語彙照舊：**面板／髮線／說明維持中性，紅只花在送出鈕**（`.btn-danger`，一份配方三處用）——
   整區鍍紅是把「危險動作」誤講成「錯誤狀態」（§3.1 紅＝失敗 或 危險）。
   `<details>` ＝ 純 HTML，**沒有 JS 也是兩步**（後台在無 JS 下必須完整可用）。 */
/* 🔴 **收合態的揭示必須看得出「這是一個控件」**：第一版把 summary 做成無邊框的安靜文字
   （照抄 `.jdel`），但 `.jdel` 是獨自待在清單末端，這裡它**緊鄰一顆有邊框的按鈕** ——
   截圖實見它被讀成旁邊那顆鈕的說明文字，等於**把危險動作藏起來，而不是加上護欄**。
   護欄是第二步，不是「讓人找不到第一步」。（GitHub danger zone 同型：入口是一顆正常的鈕，
   後果講在確認步驟。）⇒ 給 `--control-border`（對 --bg **3.64:1**，過 1.4.11 的 3:1），
   文字維持 `--ink-2` 不鍍紅；紅只在 hover（預告去向）與送出鈕上。
   高度逐 px 對上 `.btn-ghost` 的 40.4 ＝ 8+8 內距 ＋ 2 邊框 ＋ 22.4 行高，
   否則同一條控件列上兩者的文字基線會差 2.2px。 */
.bo-reveal > summary{display:inline-flex;align-items:center;min-height:36px;
  padding:var(--s2) var(--s3);border:1px solid var(--control-border);
  border-radius:var(--r-sm);color:var(--ink-2);font-size:var(--fs-sm);cursor:pointer;list-style:none}
.bo-reveal > summary::-webkit-details-marker{display:none} /* Safari 不吃 display，要專屬選擇器 */
.bo-reveal > summary:hover{color:var(--danger);border-color:var(--danger)} /* 實色 --danger 7.91:1，不用 --danger-line */
.bo-reveal[open] > summary{color:var(--ink);background:var(--surface-2)}
.bo-reveal .bo-confirm{display:flex;flex-direction:column;align-items:start;gap:var(--s3);
  margin-top:var(--s3);padding-left:var(--s3);border-left:1px solid var(--hairline)}
.bo-reveal .bo-confirm p{margin:0;max-width:52ch;font-size:var(--fs-meta);line-height:1.6;color:var(--ink-muted)}
/* 說明文字：tokens.css 的 p{margin:0} 會讓它黏在按鈕上，逐段給節奏 */
.bo-note{margin:var(--s3) 0 var(--s5);max-width:64ch;font-size:var(--fs-sm);line-height:1.6;color:var(--ink-muted)}
/* 供應商原始回應（ADR 0036）：預設收合，展開才看得到 JSON；pre 自己捲，不撐破版面 */
.bo-raw{margin:var(--s5) 0}
.bo-raw>summary{cursor:pointer;font-weight:600}
.bo-pre{max-height:60vh;overflow:auto;padding:var(--s3);font-size:var(--fs-meta);line-height:1.5;
  white-space:pre-wrap;overflow-wrap:anywhere;border:1px solid var(--hairline);border-radius:6px}
.bo-panel>.why{margin-top:var(--s3);max-width:64ch;line-height:1.6;color:var(--danger)}
