/* The components shared by more than one view.
 *
 * Frozen after Batch 0 (ARCHITECTURE §4.3): anything that belongs to a single
 * view goes in views/<name>.css instead, so two parallel tasks never edit the
 * same stylesheet. If a rule here is only used by one section, it is in the
 * wrong file.
 *
 * **Token 不在這裡了 — #259。** 顏色、陰影、圓角、字型堆疊搬進 web/themes.css,
 * 一個主題一塊,契約清單寫在那一支的檔頭。這裡剩下的是形狀:哪一個東西是圓角
 * 卡、哪一顆鈕是藥丸 —— 而「圓角多圓」由主題答。加一個主題不必碰這一支;改這一支
 * 的一條規則也不會動到任何一個主題。 */

* { box-sizing: border-box; }
/* 底色要畫在 html,不只 body — #352(D-F90)。安卓 15 起系統列透明、Chrome 135 起
   裝成 app 的網頁延伸到狀態列與導覽列底下,那兩塊露出來的是**畫布(html)**的底色:
   只設 body 的話,畫面是深色、上下兩條卻是白的(2026-09-05 使用者截圖)。 */
/* **瀏覽器自己那個下拉重新整理讓開** — D-H33 ②(#585)。Android Chrome 在
   `scrollTop === 0` 往下拉時會接管整個手勢:它先畫自己那顆轉圈、再發一發
   `touchcancel`,而 `nav.js` 的 `pullEnd()` 因此**永遠不會跑**(2026-09-20 在
   Chrome 152 上實測:指示器亮得起來、放開之後一發 `/api/state` 都沒有)。
   `contain` 只擋「捲動鏈往上傳」,這一頁自己的捲動一格都沒變。
   Safari < 16 不認得這個屬性,而那沒有代價:它忽略,行為與今天逐字相同。 */
html { background: var(--bg); }
/* **底紋是 `background` 的第一層,不是一個 `::before`** — #392。負 z-index 的偽元
   素在繪製順序上排在**這一格自己的底色前面**(root 背景 → 負 z 子層 → 區塊層級後
   代的背景),所以 `body::before` 那一版會被 `background: var(--bg)` 整片蓋掉 ——
   實測看不到任何底紋。兩層寫在同一句話裡沒有這個問題:顏色一定是最後一層,而
   `--bg-deco` 在不畫底紋的主題上是 `none`(一個合法的 <image>)。 */
body {
  margin: 0; background: var(--bg-deco), var(--bg); color: var(--ink);
  /* 15px,而 root 由 web/prefs.css 釘在 16 — #360。速記法裡的字級與那 190 條
     `font-size` 是同一件事:少換這一個,整份文件的**基準**就不跟著字級偏好走,
     而它正是最多元素繼承到的那一個。 */
  font: .9375rem/1.5 var(--font-body);
  -webkit-font-smoothing: antialiased;
}
/* 底下留白 = 導覽列 + FAB 那一疊 — #201。原本是 96px(只讓過 nav);FAB 停在
   nav 上緣再高 56px,捲到底時最後一行字會落在它底下。 */
.wrap { max-width: 520px; margin: 0 auto; padding: 0 16px 152px; }
/* 標題列讓開瀏海 — #350(D-F88)。`apple-mobile-web-app-status-bar-style` 從這一
   票起是 `black-translucent`(index.html 那一段寫著為什麼),而那個值的代價就是
   **內容從 y = 0 開始畫** —— 少了這一道,裝成 app 的 iPhone 上左上角的頁名與右上
   角的鈴鐺會躺在時間與電量底下。

   加在 padding-top 上而不是 margin:`env()` 在讀不到的環境(桌面瀏覽器、Android、
   `viewport-fit=cover` 沒寫的頁面)是 0,所以 `calc(18px + 0px)` 逐像素等於原本
   那一行 —— 這一道對其他每一台裝置都不存在。**它與 nav 的
   `env(safe-area-inset-bottom)` 是同一件事的兩端**(那一行從 #201 起就在)。 */
header {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  padding: calc(18px + env(safe-area-inset-top)) 0 14px;
}
.brand { font-family: var(--font-heading); font-weight: 650; letter-spacing: -.01em; }
/* 那一格裝的是一個控制,不是一句字 — D-F105(#393,nav.js 的 titleControl())。
   `.brand` 的字體與字級留著沒有意義也沒有害處(底下那組 seg 自己說了它要多大),
   這一條只負責讓那一格不再是一段文字流:少了它,`<span>` 裡的那個 `<div>` 靠行高
   撐高,而 seg 的 44 是它自己的 `min-height` 給的。 */
.brand.hd-ctl { display: flex; align-items: center; min-width: 0; }
.hd-left { display: flex; align-items: center; gap: 6px; min-width: 0; }
.hdr-back {
  flex: none; display: inline-flex; align-items: center; justify-content: center;
  width: 44px; height: 44px; min-width: 44px; min-height: 44px;
  cursor: pointer; padding: 0;
  color: var(--ink); background: none; border: 0;
  border-radius: var(--radius-pill);
}
.hd-icon {
  flex: none; display: inline-flex; align-items: center; justify-content: center;
  width: 44px; height: 44px; min-width: 44px; min-height: 44px;
  cursor: pointer; padding: 0;
  color: var(--ink); background: var(--chip);
  border: 1px solid var(--line); border-radius: var(--radius-pill);
}
/* 右上角那一組:換帳戶的下拉 + 通知鈴鐺 — #201。**它們得是同一個子節點**,
   否則 header 的 space-between 會把三個直接子節點拆成左中右,下拉飄到正中間。

   `flex: 1` + 靠右對齊,兩個都是量出來的(390 寬實測):少了靠右對齊,下拉的
   `max-width: 60%` 剩下的那 12.4px 會留在**右邊**,鈴鐺因此離螢幕邊 28.4px 而
   不是與內容切齊的 16px;少了 `flex: 1`,那個 60% 是「這一組」的 60%(93.6px)
   而不是 header 剩下那一段的(161px),長一點的帳戶名會提早被截掉。 */
.hd-right {
  flex: 1; display: flex; align-items: center; justify-content: flex-end;
  gap: 10px; min-width: 0;
}
/* ------------------------------------------------- 頂帶 `#band` — #551(UI 方向 ③)

   使用者原話 2026-09-13:「頂帶是帳本色,多本帳同時在條上,每一本報自己那個口徑
   …點擊頂帶會長出現在那個多本帳同時在條上的東西,下面 UI 用半透明灰遮掉…就是把
   我目前設計的右上角選單換掉」;2026-09-16:「橫向不好用 改成直的」(#580)。

   它取代的是 D-F94 / D-F105 的那一格(「帳本:」那句字 `.pick-lab` + 一顆
   `.pick` 藥丸 `.hd-pool`)。**`.pick` 元件本身留著** —— 紀錄頁的月份下拉與建
   帳本表單還在用它,退役的只有頁首這一個呼叫端。

   **用 class 不用 id**:`display` 寫在 id 選擇器上就再也收不起來了(`.hide` 是
   單一 class,而這個 repo 的特異度地雷踩過六次)。 */
.hd-band { flex: 1; display: flex; justify-content: flex-end; min-width: 0; }
/* 收合態 —— **右上角那顆半膠囊** — #606(使用者原話 2026-09-20:「改成右上角 從
   右邊往左邊長的長方 左側圓弧」)。#551 把顏色畫在 `<header>` 上、寬度 `flex: 1`
   吃滿 `.hd-right` —— 合起來就是使用者說的「綠色在上面整塊」。兩件事都收回這顆
   鈕:底色讀它 `style` 上那兩格 var(#552 產的,今天就有),寬度由內容撐。

   **右緣貼齊視窗**:`margin-right: -16px` 吃掉 `.wrap` 那一格內距(字面值不是
   var);置中之後(≥552)貼的是 `.wrap` 外緣,那一格歸 #467。右邊那條框跟著貼緣
   不畫,左邊兩角才給 `border-radius`。44 的下限同全站每一顆觸控目標(D-F100);
   右內距讓開橫放時的安全區(直立與桌面都是 0,等於 12px)。 */
.band-btn {
  flex: 0 1 auto; display: flex; align-items: center; gap: 8px; min-width: 0;
  min-height: 44px; cursor: pointer;
  padding: 4px calc(12px + env(safe-area-inset-right, 0px)) 4px 16px;
  margin-right: -16px;
  font: inherit; text-align: left; color: var(--ink);
  background: var(--ledger-tone-soft, transparent);
  border: 1px solid var(--ledger-tone, transparent); border-right: 0;
  border-radius: var(--radius-pill) 0 0 var(--radius-pill);
}
.band-btn:focus-visible { outline: 2px solid var(--ring); outline-offset: 2px; }
.band-btn[disabled] { cursor: default; }
/* 名字長就截斷,不換行也不把旁邊的東西擠出去 —— 判準逐字同退役的 `.pick-now`:
   頁首唯一要答的問題是「現在在看哪一本」,而那句話的開頭比結尾重要。
   `TheLayoutHoldsOnEverySize` 的 `LONG_POOL_NAME` 那一格量的就是這三個宣告。

   #606 之後 `flex: 1` 換了理由:`flex-basis: 0` = 不要求寬度,所以擠的時候先沒有
   話語權的是名字,不是籤。 */
.band-name {
  flex: 1; min-width: 0; font-weight: 600;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* 種類籤:**帳本色的字站在 `-soft` 的底上** — D-F105(種類色由底色表達、
   「(多人分帳)」那幾個字不印在名字後面,種類獨立一顆)。

   ⚠️ **字色是 `--ledger-tone` 而不是 `--ink`,而那是量過的**:頁首整條已經是
   `-soft`,一顆同色底的籤在上面看不出邊界,所以說話的是字色。那一對
   (`--ledger-<種類>` on `--ledger-<種類>-soft`)在 `test_web.EveryThemeMeetsContrast`
   裡叫「…籤」,12 格都有;拿 `--ink` 當籤的字色反而是沒有人量過的那一種
   (`--ink` 量的是**名字**站在 `-soft` 上那一格)。 */
/* ⚠️ **它縮得動,而那是 #606 量出來的一格。** #551 寫的是 `flex: none`,那時膠囊
   的內距是各 10、headroom 1.8px;半膠囊的左內距 16 加一條左框把它變成 5px 的溢出,
   而 Chrome 的對策是把整頁縮小來裝(實測 320 寬 × `large` 的記一筆頁:
   `TheLayoutHoldsOnEverySize` 的「視口自己變寬了 320 → 326」)。`⌄` 仍然
   `flex: none` —— 這一列上唯一不准消失的記號(D-H6)。 */
.band-kind {
  flex: 0 1 auto; min-width: 0; white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis;
  font-size: 0.71875rem; line-height: 1.6;
  color: var(--ledger-tone, var(--dim)); border-radius: var(--radius-s);
  padding: 2px 7px;
}
/* 右緣那顆 `⌄` — D-H6:`⌄` = 往下展開(頂帶底下長出那條軸)。形狀與 `.pick-caret`
   逐字相同:同一個記號在這個 app 裡已經有一種長相。 */
.band-caret { flex: none; font-size: 0.625rem; font-style: normal; color: var(--dim); }
/* 展開態的遮罩與那條軸。**z-index 排在 nav(22)與 FAB(21)之上**,理由是
   D-F108 的同一句:遮罩開著的時候底下那一頁不是使用者在操作的東西,而一顆按得到
   的 nav 鈕會讓那句話變成假的(`TheLayoutHoldsOnEverySize` 用
   `elementFromPoint` 在五顆的中心點驗它,不是量尺寸)。

   兩者都 `position: fixed`:它們長在 `<header>` 裡面(委派要登記在同一個容器
   上),而 fixed 把它們從那一列的版面裡拿出來 —— 頂帶因此不佔那條軸的高度。 */
.band-scrim { position: fixed; inset: 0; z-index: 24; background: var(--scrim); }
/* 展開態 —— **一張浮在頁面上的大卡片** — #606(使用者原話 2026-09-20:「現在的
   下拉選單是生硬的底框 改成大卡片的型態」)。#580 那一版是一條頂到視窗兩邊、圓角
   0、只有 `border-bottom` 的帶子,「生硬的底框」講的就是那四格;這一票只換那四格
   加一道陰影,**卡片裡面(`.band-card`)一個字都沒動**。

   **左右各留 16px、上限 440 靠右**:16 同膠囊那個 `-16px`,都是 `.wrap` 的內距;
   靠右靠 `margin-left: auto`(三格同時有值是 over-constrained,LTR 會忽略 `right`
   而靠左,留一格 `auto` 的邊界才解得到靠右)—— 卡片從那顆膠囊長出來。

   `top` 由 nav.js 在展開的那一刻量進來(頂帶的高度隨字級與安全區變),同 `--navh`
   的形狀。`max-height: 70vh` 與 `overflow-x: hidden` 是 #580 的兩格。 */
.band-strip {
  position: fixed; left: 16px; right: 16px; max-width: 440px; margin-left: auto;
  z-index: 25; transform-origin: top right;
  display: flex; flex-direction: column; gap: 8px; padding: 10px;
  max-height: 70vh; overflow-y: auto; overflow-x: hidden;
  background: var(--card); border: 1px solid var(--line);
  border-radius: var(--radius-l); box-shadow: var(--shadow);
  animation: band-grow .18s cubic-bezier(.2, .8, .3, 1);
}
/* 從膠囊那一角長出來 — #606。**只有 opacity 與位移,沒有 `scale`**,而那是斷言的
   前提不是品味:#580 的「一張卡吃滿那一層的寬」在 click 之後立刻量 rect,縮放中
   的元素會讓那一量落在動畫中間某一幀。曲線同 `pick-up`。 */
@keyframes band-grow {
  from { opacity: 0; transform: translateY(-8px); }
  to { opacity: 1; transform: none; }
}
/* 關掉動畫的人直接拿最後那一幀 —— 同這支檔案另外四塊 `prefers-reduced-motion`。 */
@media (prefers-reduced-motion: reduce) {
  .band-strip { animation: none; }
}
/* ⚠️ **開合走 `hidden` 屬性,而這兩條是它的代價。** 瀏覽器內建的 `[hidden] {
   display: none }` 是 UA 樣式表的一條,特異度比作者樣式表裡**任何**一條都低 ——
   上面那條 `.band-strip { display: flex }` 一寫下去,`hidden` 就再也收不起來了
   (症狀:遮罩與條從第一幀起就攤在畫面上,而「開了沒」的斷言全部是綠的)。
   `[hidden]` 那一格 (0,2,0) 壓得過 (0,1,0),形狀同上面 `.pick` 用 `data-open`
   的理由 —— 這個 repo 的特異度地雷踩過六次。 */
.band-scrim[hidden], .band-strip[hidden] { display: none; }
/* 一張 mini 卡:名字 + 種類 + 這一本自己那個口徑的一個數字 — D-F108 ②(色塊裡
   要寫帳本名)。淡底 + `--ink` 的名字 + 帳本色的框,與退役的那顆藥丸是同一種
   語言(`.pick-cur.lt-*`),而那一組配對已經在對比表裡。

   ⚠️ **#580 起它是一列**:`width: 100%` 取代 #551 的 `min-width`/`max-width`(那
   兩格是橫軸上「一張卡多寬」的答案),帳本色多一條左緣的色條。**色條走
   `box-shadow: inset` 而不是 `border-left`**:下面 `aria-current` 那一條把邊框整圈
   加粗、特異度壓得過這裡,`border-left-width` 會被它連左緣一起蓋掉。4px 的色條站
   在 10px 的左內距裡,沒有色條的那一張(「＋」)因此不必自己調內距。 */
.band-card {
  position: relative; flex: none;
  display: flex; flex-direction: column; align-items: flex-start; gap: 2px;
  width: 100%; min-height: 44px; padding: 8px 10px;
  font: inherit; text-align: left; cursor: pointer; color: var(--ink);
  background: var(--ledger-tone-soft, var(--chip));
  border: 1px solid var(--ledger-tone, var(--line));
  border-radius: var(--radius-l);
  box-shadow: inset 4px 0 0 var(--ledger-tone, transparent);
}
.band-card:focus-visible { outline: 2px solid var(--ring); outline-offset: 2px; }
/* 現在在看的那一本 —— 框粗一階。**不換顏色**:條上每一張的顏色講的是「這是哪一
   本帳」,而「現在在哪一本」是另一個問題,借顏色說它會讓兩件事撞在一起。 */
.band-card[aria-current="true"] { border-width: 2px; }
.bc-name {
  max-width: 100%; font-weight: 600;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.bc-kind { font-size: 0.71875rem; color: var(--ledger-tone, var(--dim)); white-space: nowrap; }
.bc-measure { font-size: 0.8125rem; white-space: nowrap; }
/* 最下面那一張「＋ 新增帳本」不是一本帳,所以不吃帳本色 — 它是一個動作。 */
.band-new {
  align-items: center; justify-content: center; text-align: center;
  background: var(--chip); border-color: var(--line); border-style: dashed;
}
/* 每一張右上角那顆未讀圈 — D-F97。顏色與 nav 上那一顆是同一格 token,兩處講的是
   同一個數字(`aria-hidden`:那個數字已經在整張卡的 `aria-label` 裡)。 */
.band-badge {
  position: absolute; top: -6px; right: -6px;
  min-width: 17px; padding: 0 5px; border-radius: var(--radius-pill);
  background: var(--warn); color: var(--on-warn);
  font-size: 0.6875rem; line-height: 17px; text-align: center;
}
/* 原生 `<select>` 的那條規則在 #233 拿掉了 —— #229 盤點表上剩下的兩顆(關帳分配
   政策 `#npClose`、更新餘額那張卡的 `#bkWho`)那一票一起換成了底下的 `.pick`,
   全站因此一顆 `<select>` 都不剩,規則沒有東西可以套。**留著才是危險的**:它的
   `max-width: 60%` 正是 390 上把「各自拿回入金,消耗按分配權重分攤」截成半句話
   的那一條,而一條沒有人套得到的規則會讓下一個人以為原生下拉還是這個 app 的
   選項之一。 */

/* ------------------------------------------- 自訂下拉選單 `.pick` — #229

   元件本身、它的三個呼叫端與那筆「放棄了什麼」的帳,寫在 web/picker.js 的檔頭。
   規則住在這裡而不是某一支 views/*.css,判準是這個檔案自己開頭那一句:只有一個
   section 用得到的規則放在這裡就是放錯地方 —— 而這一組有三個(右上角的切換器在
   header 上、建帳戶表單在 #v-me、月份在 #v-log)。

   **收合面的高度跟著呼叫端走**,不在這裡寫死:右上角那顆要和鈴鐺切齊(40px,
   style.css 底下 `.bell` 那一條當年量的),月份那顆站在一排 26px 的籤中間。

   ⚠️ **「真正要按的目標是紙上的選項,收合面不必 44」那句話在 D-F100(#373)被推
   翻了。** 收合面自己就是一顆要按的鈕 —— 不按它,紙根本不會出現。#370 在九種尺
   寸上量到它 37–42px 高(`#poolSwitch`),而使用者裁的是「全站觸控目標一律
   ≥44×44」,沒有「它只是入口」這個例外。padding 一格都沒動:44 由 `min-height`
   給,字仍然置中(`<button>` 自己會置中它的內容),所以短名字的那一顆看起來只是
   高了幾個像素,不是變成一顆大方塊。 */
.pick { min-width: 0; }
.pick-cur {
  display: inline-flex; align-items: center; justify-content: space-between;
  gap: 6px; max-width: 100%; min-height: 44px;
  font: inherit; color: var(--ink); background: var(--chip); cursor: pointer;
  border: 1px solid var(--line); border-radius: var(--radius-pill); padding: 6px 12px;
}
/* 名字長就截斷,不換行也不把旁邊的東西擠出去 —— 收合面唯一要答的問題是「現在
   選的是哪一個」,而那句話的開頭比結尾重要。 */
.pick-now { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pick-caret { flex: none; font-size: 0.625rem; font-style: normal; color: var(--dim); }
.pick[data-open] .pick-cur { color: var(--accent); border-color: var(--accent); }
/* 收合面自己的焦點框。瀏覽器本來就會畫一個(它是一顆真的 `<button>`),換成 `--ring`
   只是為了讓它和紙上那一圈是同一種東西 —— 390 實測(sandbox,2026-08-23)選完之後
   焦點會回到這顆鈕上,而那一圈預設是藍的,在一張綠色的卡上讀起來像另一個系統的
   東西。 */
.pick-cur:focus-visible { outline: 2px solid var(--ring); outline-offset: 2px; }

/* **開合走 `data-open`,不走 `.hide`** —— `.hide` 是單一 class,而 view 的 css 後
   掛進 <head>,任何一條 `.pick-sheet { display: … }` 都會把它蓋掉(.bk-dot /
   .bal-sheet 踩過的坑)。這裡兩邊都是自己寫的:預設 `display: none`,`.pick[data-open]`
   那條 (0,2,0) 壓得過它。 */
.pick-scrim, .pick-sheet { display: none; }
.pick[data-open] .pick-scrim { display: block; }
/* 紙的版面抄 `.asheet`(#202)與 `.bal-sheet`(#216):離 nav 8px、左右各 8px、
   卡片底、圓角、往上 16px 的彈出。z-index 蓋過整個底部 chrome(nav 22、FAB 21),
   仍然在登入閘(auth.css 的 30)之下。

   **動畫掛在 `[data-open]` 上而不是靜態規則上**:紙不是每次開都重新造出來的節點
   (整段 HTML 由呼叫端一次畫好),`display: none → block` 那一刻動畫才會重跑 ——
   形狀同 home.css 的 `#flow .fl-why:not(.hide)`。.2s 落在 150–300ms 裡。 */
.pick[data-open] .pick-sheet {
  display: block; animation: pick-up .2s cubic-bezier(.2, .8, .3, 1);
}
.pick-scrim { position: fixed; inset: 0; z-index: 24; background: var(--scrim); }
.pick-sheet {
  position: fixed; left: 8px; right: 8px; z-index: 25;
  bottom: calc(var(--navh, 68px) + 8px);
  background: var(--card); border: 1px solid var(--line);
  border-radius: var(--radius-l); padding: 12px 16px 4px; box-shadow: var(--shadow);
}
@keyframes pick-up { from { opacity: 0; transform: translateY(16px); } }
@media (prefers-reduced-motion: reduce) { .pick[data-open] .pick-sheet { animation: none; } }
/* 紙彈在畫面底部,離被按的那顆鈕很遠 —— 它要自己說得出「這是哪一格」(同 .atitle
   與 .bal-sheet-t 的理由)。 */
.pick-head { padding-bottom: 10px; border-bottom: 1px solid var(--line); }
/* 長清單自己捲,紙不會長到蓋住整個畫面。52vh:390×844 上是 439px,剛好放得下
   八列 44px 的選項,而紙的頂端仍然離狀態列很遠。 */
.pick-list { max-height: 52vh; overflow-y: auto; }
.pick-grp + .pick-grp { border-top: 1px solid var(--line); }
.pick-ghd { padding: 12px 0 2px; font-size: 0.75rem; letter-spacing: .02em; color: var(--dim); }
/* 一列一個選項。44px 是點擊目標的下限,而說明那一行(建帳戶表單上每一種帳本的
   一句話)長在名字底下,不是取代它 —— 同 `.awhy` 的排法。 */
.pick-opt {
  display: flex; flex-direction: column; justify-content: center; gap: 2px;
  min-height: 44px; padding: 10px 0; cursor: pointer;
  border-bottom: 1px solid var(--line);
}
.pick-grp:last-child .pick-opt:last-child { border-bottom: 0; }
.pick-opt b { font-weight: 600; }
.pick-opt[aria-selected="true"] { color: var(--accent); }
/* 選中那一列右邊那顆勾 — #392。它以前是 `content: " ✓"`(一個 Dingbats 字元,
   字體說了算),而 `content` 畫不出 SVG:換成 icon 集之後那顆勾由 picker.js 一律
   印出來,**這裡決定哪一列看得到它** —— 一條規則,而不是兩種標記。 */
.pick-opt > .ic { margin-left: 6px; color: var(--accent); }
.pick-opt:not([aria-selected="true"]) > .ic { visibility: hidden; }
/* 鍵盤走到哪裡要看得見。**只在 :focus-visible 上畫**:用手指點開的人不需要一個
   跟著他的手指跑的方框,而他要的那一格已經有 ✓ 與 accent 色在說話。 */
.pick-opt:focus-visible { outline: 2px solid var(--ring); outline-offset: -3px; border-radius: var(--radius-s); }
.pick-note { font-size: 0.78125rem; color: var(--dim); line-height: 1.5; }

/* ---- 從按鈕往下長的那一種 `.pick[data-drop]` — D-F98(#356)-----------------

   使用者原話 2026-09-06:「快速切換選單從上往下長」。上面那張紙彈在畫面底部,
   而它是為了**拇指**(#229 的取捨:390 上浮在觸發鈕底下的清單有一半落在拇指伸
   不到的地方)。這一顆的答案反過來,理由也反過來:切換帳本要看的是「我現在在
   哪一本、旁邊還有哪幾本」,而那個問題的上下文就是那顆寫著現在這一本帳的鈕 ——
   一張從螢幕底部升起來的紙把答案搬到離問題最遠的地方。

   **只有這一顆換,另外兩顆(建帳本的型態、紀錄頁的月份)一個字都沒動**:它們是
   表單裡的欄位,站在畫面中段,底部彈出對它們仍然是對的。所以形狀是一個選項而
   不是一次改寫。

   四件事:
     position     基準是 `.pick` 自己(收合面就在它裡面),所以紙貼著鈕的下緣長。
     right: 0     靠鈕的**右緣**對齊 —— 那顆鈕站在標題列最右邊,靠左長會掉出螢幕。
     min-width    「寬度對齊按鈕」的下限是那顆鈕本身(`100%`),但一列要裝下型態、
                  帳本名與未讀圈,所以再給一個 15rem 的地板;上限收在 86vw,
                  390 寬的機器上兩邊仍然有邊。

                  ⚠️ **那個 86vw 要除以縮放倍率 — D-F99(#370)。** 字級偏好走的
                  是 `html { zoom }`(web/prefs.css),而**視口單位不跟著縮**:
                  `86vw` 算出來是 86% × 視口寬的 CSS px,再被 zoom 乘一次。實測
                  (Chrome 152、360×740、大字級、帳本名 22 個中文字):紙量到
                  356px 寬(= 98.9vw),而它靠右對齊在 341.6,**左緣落在
                  -14.4** —— 掉出螢幕外面。`var(--zoom, 1)` 由 prefs.css 那兩條
                  規則給,沒有人縮放時是 1,整條算式逐字退回原本的 86vw。

                  ⚠️ **而 86vw 在寬螢幕上是錯的尺 —— 這是 #370 收尾時量到的第二
                  個病。** 這張紙靠的是**那顆鈕的右緣**(`right: 0`),而 552 之
                  後 `.wrap` 就置中了:鈕的右緣停在內容欄的右邊,離視窗右邊還有一
                  段。用視窗寬當上限,紙就從內容欄的左邊長出去 —— 實測(帳本名
                  49 字元的中英混合名):768×1024 上紙寬 660、鈕右緣 628,**左緣
                  -32**;844×390 是 -8,大字級是 -23。
                  真正的上限是**內容欄的內寬**:`.wrap` 是 `max-width: 520px` 加
                  左右各 16px 的 border-box 內距,所以是 488。兩個上限取小的那一
                  個:窄螢幕上 86vw 贏(欄比視窗窄不了),寬螢幕上 488 贏,而紙的
                  左緣因此永遠不會跑到內容欄的左邊外面。
     box-shadow   浮起來的東西要有影子(同 `.card` 用的那一格 token)。 */
.pick[data-drop] { position: relative; }
.pick[data-drop] .pick-sheet {
  position: absolute; top: calc(100% + 6px); bottom: auto; left: auto; right: 0;
  min-width: max(100%, 15rem);
  max-width: min(calc(86vw / var(--zoom, 1)), 488px);
  padding: 4px 14px;
}
/* 往下長的那一張不必自己說「這是哪一格」— 它就貼在那顆鈕底下,而鈕的左邊已經
   寫著「帳本:」。上面那張紙需要標題是因為它離被按的地方很遠。 */
.pick[data-drop] .pick-head { display: none; }
/* 幕留著(點外面關掉、擋住底下那一列的誤觸都靠它),但**不要那層黑**:蓋住整個
   畫面的暗色是「一張紙升起來了」的語彙,而這一張只是從一顆鈕底下長出來的。 */
.pick[data-drop] .pick-scrim { background: transparent; }
.pick[data-drop] .pick-sheet { animation: pick-down .16s cubic-bezier(.2, .8, .3, 1); }
@keyframes pick-down { from { opacity: 0; transform: translateY(-8px); } }
@media (prefers-reduced-motion: reduce) { .pick[data-drop] .pick-sheet { animation: none; } }

/* 一列排成一橫排:**左邊帳本型態、右邊帳本名** — D-F98。有 `tag` 的那一列才切成
   橫的(`.pick-row`),沒有的照舊是上下兩行(名字 + 說明)。 */
.pick-opt.pick-row { flex-direction: row; align-items: center; gap: 10px; }
/* 型態是一個標籤不是一句話:固定的起點讓一整排帳本名對齊成一欄,掃起來比逐列
   找名字的開頭快。`--chip` 底 + `--dim` 字與 `.ghost` 同一種語言(次要資訊)。 */
.pick-tag {
  flex: none; min-width: 62px; text-align: center;
  font-size: 0.71875rem; line-height: 1.6; color: var(--dim);
  background: var(--chip); border-radius: var(--radius-s); padding: 2px 7px;
}
/* 帳本種類 = 一塊顏色,不是四個字 — D-F105(#393)。使用者原話 2026-09-08:
   「(多人分帳)這幾個字藏掉 微咪家用的底色改用多人分帳的顏色」。

   ⚠️ **色塊裡把型態那幾個字寫回去了 — D-F108 ②(#404)。** 使用者實機看完那一版:
   「快速切換選單展開後每列色塊裡要寫帳本名(只有頁首那顆藥丸不寫)」。D-F105 藏
   掉那四個字解的是**收合面**的寬度(320 的頁首同時裝著返回鈕、模式 seg 與這顆
   藥丸),而展開的清單有一整張紙 —— 一塊沒有字的顏色要人先學會四種顏色各代表
   什麼。所以現在**兩個版位是同一種樣子**:淡底 + `--ink` 的字 + 種類色。

   淡底那一半是**量出來的**:`--ink` 站在四個 `-soft` 上的對比在五個主題裡是
   10.9–16.3,而實色底沒有一顆字色站得上去 —— #392 的 token 契約裡沒有
   `--on-ledger-*`,而 `--on-accent`(多半是白)在深色主題的 `--ledger-split`
   (#60A5FA)上只有 2.2。兩組配對都進了 `test_web.EveryThemeMeetsContrast`,而
   這一格接上去的正是已經量過的那一組(不是第五種配對)。

   高度回到 `.pick-tag` 自己的行高(#393 那一行 `height: 18px` 是「裡面沒有東西」
   才需要的);寬度的地板**跟著最長的那個型態走**(「集資資金池」五個字 —— 6.5em
   在這一格的 0.71875rem 上是 74.75px,含左右各 7px 的內距),因為一整排帳本名要
   對齊成一欄,而那正是這一格存在的理由:62px 那個舊地板只裝得下四個字,五個字的
   那一列會自己長長,整欄名字跟著錯開。**用 em 不用 px**:字級偏好放大時這一格與
   它裡面的字要一起長(web/prefs.css 的 zoom 之外還有一條 `font-size` 的路)。
   **型態那幾個字不准換行**:320 寬那張紙只有 275px,折行的標籤會把那一列撐成兩倍
   高,而該讓的是名字那一欄 —— 它自己會截尾(下面 `.pick-opt.pick-row b`)。

   型態那幾個字現在**眼睛與耳朵各有一份**:色塊自己印出來,每一列的 `aria-label`
   照樣唸得出來 —— nav.js 的 poolRows() 那一格寫了為什麼後者不能省。 */
/* 帳本色從「這是哪一種帳本」變成「這一本帳本自己的那一格」 — #552(UI 方向 ②)。
   使用者原話 2026-09-13:「帳本要可以被編輯顏色」。

   **畫的那一半只讀 `--ledger-tone` / `--ledger-tone-soft`,不認得種類。** 顏色
   有十二格而種類只有四個答案:讓樣式表認得十二個 class 等於把 `fmt.js` 那張表
   抄進 CSS 裡,而抄過去的那一份沒有人會記得跟著改。

   **那兩格有兩個作者,而後者永遠贏**:
     · `lt-<種類>` 這幾條 —— 沒挑過顏色時的底,值就是這一種帳本的第一格;
     · 元素上的 `style=""`(`fmt.ledgerToneVars(pool)` 產的那一段)—— 行內宣告
       壓得過 class,所以挑過色的帳本畫的是它挑的那一格。
   兩層都在的理由是**前端不必知道誰負責算預設**:picker.js 一律把 vars 掛上去,
   而掛不上去的呼叫端(還沒接的、或資料髒的)照樣有一個看得見的顏色。

   **`var()` 都帶退路**,而那不是保險:`--ledger-tone` 不是 themes.css 的 token
   (它是執行期才寫得出來的一格,同 `--navh` / `--zoom`),而沒有退路的 `var()`
   指到一個當下沒有值的名字時,**整條宣告在 computed-value time 就是 invalid**
   —— 瀏覽器會安靜地退回繼承來的值(test_web 的
   EveryVarAStylesheetCallsIsOneTheTokenLayerDefines 寫了那兩個案例)。退路寫的
   逐字是這一格沒有顏色時本來的樣子(`.pick-cur` 的 `--chip` / `--line`)。 */
.pick-cur.lt-split, .pick-tag.lt-split {
  --ledger-tone: var(--ledger-split); --ledger-tone-soft: var(--ledger-split-soft);
}
.pick-cur.lt-pool, .pick-tag.lt-pool {
  --ledger-tone: var(--ledger-pool); --ledger-tone-soft: var(--ledger-pool-soft);
}
.pick-cur.lt-capital, .pick-tag.lt-capital {
  --ledger-tone: var(--ledger-capital); --ledger-tone-soft: var(--ledger-capital-soft);
}
.pick-cur.lt-personal, .pick-tag.lt-personal {
  --ledger-tone: var(--ledger-personal); --ledger-tone-soft: var(--ledger-personal-soft);
}
.pick-tag.lt-split, .pick-tag.lt-pool,
.pick-tag.lt-capital, .pick-tag.lt-personal {
  min-width: 6.5em; color: var(--ink); white-space: nowrap;
  background: var(--ledger-tone-soft, var(--chip));
}
.pick-cur.lt-split, .pick-cur.lt-pool,
.pick-cur.lt-capital, .pick-cur.lt-personal {
  background: var(--ledger-tone-soft, var(--chip));
  border-color: var(--ledger-tone, var(--line));
}
/* 名字吃掉剩下的寬度,長就截斷 —— 同 `.pick-now` 的判準(那句話的開頭比結尾
   重要),而選中的那一列還有 accent 色在說話。 */
.pick-opt.pick-row b { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* 每一列右上角那顆未讀圈 — D-F97。**貼上緣不置中**(`align-self`):它是掛在這一
   列上的標記,不是這一列的第三欄。顏色與 nav 上那一顆是同一格 token,兩處講的
   是同一個數字。 */
.pick-badge {
  flex: none; align-self: flex-start;
  min-width: 17px; padding: 0 5px; border-radius: var(--radius-pill);
  background: var(--warn); color: var(--on-warn);
  font-size: 0.65625rem; line-height: 1.0625rem; font-weight: 650; text-align: center;
  font-variant-numeric: tabular-nums;
}

.card {
  background: var(--card); border: 1px solid var(--line);
  border-radius: var(--radius-l); padding: 18px; margin-bottom: 14px; box-shadow: var(--shadow);
}
.label { color: var(--dim); font-size: 0.78125rem; letter-spacing: .02em; }
.big { font-family: var(--font-heading); font-size: 2.375rem; font-weight: 640; letter-spacing: -.025em; margin: 4px 0 2px; }
.big small { font-size: 1rem; font-weight: 450; color: var(--dim); margin-right: 4px; }
.row { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; }
.muted { color: var(--dim); font-size: 0.8125rem; }
.num { font-variant-numeric: tabular-nums; }
.warn { color: var(--warn); }
.good { color: var(--accent); }
h3 { font-family: var(--font-heading); font-size: 0.875rem; margin: 0 0 4px; font-weight: 640; }

/* 一列一個人:首頁的狀態列與帳戶頁的成員列共用 */
.who { display: flex; align-items: center; gap: 12px; padding: 13px 0; border-bottom: 1px solid var(--line); }
.who:last-child { border-bottom: 0; }
/* ⛔ `.who .sw`(10×34 的色塊)在 #554 退役 —— 三個呼叫端(首頁那三列、結清頁)
   換成 `.av` 那一顆頭像,同一個人在記一筆、首頁與結清上從此是同一張臉。 */
.who .id { flex: 1; min-width: 0; }
.who .id b { font-weight: 600; }
.who .id span { display: block; font-size: 0.75rem; color: var(--dim); }
.who .val { text-align: right; white-space: nowrap; }

/* 列右邊那兩個記號 — D-H6(#367)。**同一段 path,轉幾度而已**:`.cv` 指右(「›」
   = 按下去跳到另一頁),`.cv-fold` 轉 90° 指下(「⌄」= 往下展開),而它所在的按鈕
   `aria-expanded="true"` 時再轉成指上(「⌃」)。畫它的那兩支在 `web/icons/chevron.js`。

   ⚠️ **轉向只寫在這裡。** `.fl-more` 那一處在 #367 之
   前有一條「展開時 `.caret` 轉 90°」的規則,而那一層現在轉的是裡面這顆 svg ——
   兩層都轉的下場是 180°(實測:箭頭指回左邊)。那幾條因此在同一票裡拿掉,不是留著
   沒人用。

   `width`/`height` 吃 `em`,所以每一個呼叫點仍然靠外面那顆 `.caret` 的 `font-size`
   決定它多大(`.mg-entry .caret` 是 13px、`.fl-more .caret` 是 12px…);換成 px 的
   話字級偏好(`data-fsize`)放大整個畫面時,只有這幾顆箭頭不會跟著長。

   `stroke` 走 `currentColor`:顏色仍然由外面那顆 `.caret` 說了算(`--dim`,或危險
   區那一列的 `--warn`),這裡不再有第二個作者。

   動畫 150ms —— 收合那一組的既有值(D-H6 允許省略,而轉向是它唯一動的東西)。 */
.caret .cv {
  display: block; width: 1em; height: 1em;
  fill: none; stroke: currentColor; stroke-width: 2;
  stroke-linecap: round; stroke-linejoin: round;
}
.caret .cv-fold { transform: rotate(90deg); transition: transform .15s; }
[aria-expanded="true"] .caret .cv-fold { transform: rotate(-90deg); }

/* 一張臉 —— 姓名首字 + 成員色 — #554(D-H29 §1「成員色塊進按鈕」)。畫它的是
   `fmt.avatarHTML()`,而**全站只有這一份樣式**:記一筆那兩頁的「誰先付 / 誰分攤」、
   首頁「誰要給誰多少錢」那幾列、結清頁的淨部位共用同一顆。**add.css / batch.css
   不准各寫一份** —— 兩頁逐格相同(D-F108 第 1 點)在 CSS 上就是這個意思。

   **26px 是寫死的 px,不是 em**:字級偏好(`data-fsize`)放大整個畫面時,一排臉
   跟著長會把那一排鈕擠到換行(量出來的:44 的鈕高只剩 2px 的餘裕)。裡面那個字
   用 rem,所以它仍然跟著字級走。

   `flex: none` 是必要的:它的三個位置都在 flex 列裡(`.who` / `.avb` / `.share`),
   而一顆被壓扁的臉是橢圓的。

   底與字的三種組合住在 `fmt.avatarHTML()`(有座位的寫 inline 底色,另外兩種在
   底下那兩條)—— 判準整段寫在那一支的檔頭。 */
.av {
  display: inline-flex; align-items: center; justify-content: center;
  flex: none; width: 26px; height: 26px; border-radius: 50%;
  font-size: 0.8125rem; font-weight: 600; line-height: 1;
  vertical-align: middle; color: var(--on-member);
}
/* 「帳本」那一顆 — #554 裁示 1。**實心帳本色上沒有一顆字色站得住**(PAIRS 檔頭),
   所以這一對是「帳本籤」那一級:淡底 + 帳本色的字。

   ⚠️ **`var()` 帶退路,理由逐字同底下 `.hd-seg` 那一組**:`--ledger-tone` 不是
   themes.css 的 token(它是執行期才寫得出來的一格),#551 把頂帶接上去之前沒有人
   定義它 —— 而沒有退路的 `var()` 在 computed-value time 就是 invalid,底色會安靜地
   退回透明。退路寫的是那之前本來的樣子。 */
.av.av-ledger {
  background: var(--ledger-tone-soft, var(--ledger-pool-soft));
  color: var(--ledger-tone, var(--ledger-pool));
}
/* 沒有座位的那一位 — #554 裁示 1:`--on-member` 只套在 `--member-N` 上(Astra
   P1-5),所以這一格自己帶字色。`(--dim, --bg)` 已經在 PAIRS 上。 */
.av.av-none { background: var(--dim); color: var(--bg); }
/* 一顆鈕裡有一張臉 — `entry-fields.payerRowHTML()` 的 `.avb`。**縫是 8px**(#554
   裁示 4),而鈕的 44 下限由底下 `.seg button` 那一組給:26 + 上下內距 16 = 42,
   撐它的仍然是 `min-height`。 */
.seg button.avb { display: inline-flex; align-items: center; gap: 8px; }
/* 分攤那一列的臉在 `<label>` 裡(按那張臉就是勾那一列),而那一格不是 flex ——
   縫因此是一格 margin,值同上面那 8px。**它寫在這裡而不是 add.css**:`.share` 的
   版面住在 add.css,但頭像的樣式全站只有一份(見 `.av` 檔頭那一句)。 */
.share label .av { margin-right: 8px; }

label.f { display: block; margin: 14px 0 6px; font-size: 0.78125rem; color: var(--dim); }
/* ⚠️ **`min-height` 的選擇器把核取方塊與單選鈕排除掉了,而這不是潔癖是實測。**
   `min-height` 壓得過 `height`(兩個不同的屬性,不是同一條的兩個值),所以一條
   光禿禿的 `input { min-height: 44px }` 會把 add.css 的 `.share input[type=checkbox]`
   那顆 20px 的方塊拉成 20×44 的長條 —— 那不是「觸控目標變大」,那是畫壞了。

   那兩種自己的 44 不在這裡:它們是「一顆小方塊 + 旁邊一整列可按的字」,而那一整
   列(`.share` 的 label 那一格)本來就高過 44 —— 手指按的是那一列,不是方塊。 */
input {
  font: inherit; color: var(--ink); background: var(--bg); width: 100%;
  border: 1px solid var(--line); border-radius: var(--radius-s); padding: 10px 12px;
}
input:not([type=checkbox]):not([type=radio]) { min-height: 44px; }
.seg { display: flex; flex-wrap: wrap; gap: 8px; }
.seg button {
  font: inherit; color: var(--ink); background: var(--chip); cursor: pointer;
  border: 1px solid transparent; border-radius: var(--radius-pill); padding: 8px 14px;
  min-height: 44px; min-width: 44px;
}
.seg button[aria-pressed="true"] { background: var(--card); border-color: var(--accent); color: var(--accent); font-weight: 600; }
/* 「誰先付」那一排選中的那一顆 = **當前帳本色**,不是 accent — #554(D-H29 §1 的
   擴充:「選中鈕邊框 = 帳本色」)。**只有 `.avb` 那一排**:種類那一排(支出 / 收入 /
   退款 / 轉帳)留在 accent 上,而兩排 seg 在記帳頁上下疊在同一個畫面裡 —— 理由逐字
   同底下 `.hd-seg` 那一組(讓「這是誰」與「這是哪一種」一眼分得開)。
   `font-weight` 由上面那一條給,這裡只換三格顏色;退路同 `.av-ledger`。 */
.seg button.avb[aria-pressed="true"] {
  background: var(--ledger-tone-soft, var(--ledger-pool-soft));
  border-color: var(--ledger-tone, var(--ledger-pool));
  color: var(--ledger-tone, var(--ledger-pool));
}
/* 標題列上那一組模式 seg — D-F105(#393)。使用者原話 2026-09-08:「A′-2:seg 直接
   當標題「← 記單筆|記多筆」,帳本切換留右側」。**規則排在 `.seg` 那一組後面而不是
   跟 header 住在一起**:它覆寫的正是上面那三行,而兩者的特異度一模一樣(class +
   屬性 + 型別),先後就是全部的勝負。

   `.seg` 那一組是**表單裡**的:8px 的縫、會換行、每格 14px 內距 —— 三樣在 320 寬
   的頁首上都會爆。量出來的預算(design-themes/FAB-UX.md §3 的像素表,內容區 288):
   返回 44 + 6 + seg(2×51 + 3 = 105)+ header gap 10 + 「帳本:」36 + 6 = 207,
   剩 81px 給藥丸(左右內距 24 + caret 22 + 名字 35,長名字截尾 —— D-F105 第 5 點)。

   **`flex: none` + `nowrap`**:這一列上唯一准縮的是藥丸(`.pick-now` 自己有省略
   號),seg 一縮就掉到 44 以下(D-F100)。 */
.hd-seg { flex: none; flex-wrap: nowrap; gap: 3px; }
.hd-seg button { min-width: 51px; padding: 0 6px; font-size: 0.8125rem; font-weight: 500; }
/* 選中格 = 現在站在哪一頁,而它**用帳本種類那一組色,不是 accent** — D-F105 第 1
   點。頂卡上那一排(支出/收入/退款/轉帳)的選中色是 accent 系,而兩排 seg 在記帳
   頁上下疊在同一個畫面裡;借 `--ledger-pool` 那一格是為了讓「這是模式,不是種類」
   一眼分得開 —— D-H17 第 2 點的「與相機鈕不同色」那句語意,從退役的 `.fab.swap`
   搬到這裡。對比由 `EveryThemeMeetsContrast` 的「共同帳戶籤」那一格守著。 */
/* ⚠️ **這一組從 #552 起讀 `--ledger-tone`,退路仍然是 `--ledger-pool`。**
   上面那一段講的「借 `--ledger-pool` 是為了說『這是模式,不是種類』」沒有失效
   —— 換的是它借哪一格:頂帶接上去之後(#551 在外框上掛 `ledgerToneVars(pool)`)
   這一組會跟著**當前帳本**的顏色走,而在那之前 `--ledger-tone` 沒有人定義,
   退路逐字就是今天的樣子。所以這一票不改任何一個畫素,只是把插頭接好。 */
.hd-seg button[aria-pressed="true"] {
  background: var(--ledger-tone-soft, var(--ledger-pool-soft));
  border-color: var(--ledger-tone, var(--ledger-pool));
  color: var(--ledger-tone, var(--ledger-pool)); font-weight: 600;
}

.primary {
  font: inherit; font-weight: 600; width: 100%; cursor: pointer; margin-top: 18px;
  background: linear-gradient(135deg, var(--accent-grad-a), var(--accent-grad-b));
  color: var(--on-accent); border: 0; border-radius: var(--radius-m); padding: 13px;
}
.primary:disabled { opacity: .4; cursor: not-allowed; }
/* ⚠️ **44 的下限從 add.css 搬上來了 — D-F100(#373)。** 那一條原本寫在
   `.capout / .caplib / .capback` 上,而它的檔頭當年寫著「44px 的下限寫在這裡而不
   是 `.ghost`:那一條全站共用」—— 意思是「不要為了三顆鈕動全站」。D-F100 把那句
   話的前提換掉了:全站就是要 44,所以下限的位置也跟著換成全站這一層。

   padding 沒動 —— 6px 的上下內距仍然決定這顆藥丸「看起來」多高;44 只在它撐不到
   的時候補進來(13px 的字撐出來大約 30px)。寬度同理:`#acCopyId` 的「複製」兩個
   字本來就 48 寬,`min-width` 對它一格都沒有影響,擋的是只有一個字的那一種。 */
.ghost {
  font: inherit; font-size: 0.8125rem; cursor: pointer; background: transparent; color: var(--dim);
  border: 1px solid var(--line); border-radius: var(--radius-pill); padding: 6px 12px;
  min-height: 44px; min-width: 44px;
}
.ghost.danger { color: var(--danger); }

/* 子畫面的返回鈕 — #136。它是 shell chrome(nav.js 畫的),不屬於任何一個
   view,所以住在這裡而不是某一支 views/*.css:紀錄頁的編輯與共同帳戶頁的
   建立新帳戶(#135)看到的是同一顆。 */

/* ⚠️ **`.bell` 那一組在 D-F97(#358)整段刪掉了。** 使用者原話 2026-09-06:
   「右上角鈴鐺拿掉;通知變成『我的帳號』裡的一個入口」。#201 把通知搬上頭是因為
   底部那一排只有五格而它「要一直看得見」;而「一直看得見」這件事現在由**徽章**
   自己做 —— 它長在「我的帳號」那顆分頁鈕上(底下 `.nbadge`),入口則收進那一頁的
   第一層。一顆按鈕的位子換掉一個紅點,右上角因此只剩「這一頁的操作」。

   徽章的樣式與定位基準跟著搬到這裡 —— **它從 views/notify.css 搬過來,理由與當年
   放在那裡的理由是同一條**:那時候徽章長在通知自己那一頁的入口上(#166 的「更多」
   那一列),拿掉那一頁它就該一起消失;現在它長在 shell 的導覽列上,和 `.fab` /
   `nav` 是同一族 —— 通知那一頁怎麼畫、要不要留,都不改變導覽列上有一顆徽章這件事。 */

/* 導覽列上那顆未讀徽章 — D-F97(#358)。「我的帳號」那一格的右上角,數字由
   nav.js 的 setUnread() 寫(0 就整顆 `hidden`)。

   **定位基準寫在 `nav button` 上,不是在徽章自己身上**:徽章是 absolute,少了
   基準它會跑到最近的一個定位祖先去 —— 那是整個 `nav`,五格的右上角。

   位置不是那顆鈕的角落,是 **icon 的角落**:`right: calc(50% - 22px)` 從那一格
   的中線往右量,而 icon(`nav em`,17px)也是置中的 —— 兩者因此對得上,不管
   那一格有多寬(五格平分,字級放大時一起變)。 */
nav button { position: relative; }
.nbadge {
  position: absolute; top: 3px; right: calc(50% - 22px);
  min-width: 17px; padding: 0 5px; border-radius: var(--radius-pill);
  background: var(--warn); color: var(--on-warn);
  font-size: 0.65625rem; line-height: 1.0625rem; font-weight: 650; text-align: center;
  font-variant-numeric: tabular-nums;
}

/* 記一筆的懸浮鈕 — #201(使用者原話:「右下角、控制 bar 之上的懸浮圓形 + 號」)。

   **位置建在量出來的 nav 高度上**,不是寫死的 px:`--navh` 由 nav.js 的 sizeNav()
   寫在 :root 上(nav 的高度是字級 + `env(safe-area-inset-bottom)` 算出來的),
   16px 那個縫是版面,留在這裡。退路值只在 sizeNav() 跑之前的那一瞬間用得到。

   **z-index 在 nav 之下**:兩者不重疊,而萬一重疊了該被蓋住的是浮的這顆 ——
   導覽列是骨架。它與 auth.css 的 #gate(30)、api.js 的 toast(40)在同一條線上。

   底色是 accent 實心配白字,和 `.primary` 同一種語言:它是這個 app 的主要動作。
   **這不會跟「現在在哪一頁」打架** —— 那個狀態在 nav 上是 accent 色的字,而這顆
   鈕不在 nav 上(站在記一筆那一頁時它拿到 `aria-current`,但沒有一條規則讀它:
   人已經站在那一頁上,浮著的入口不必再喊一次)。 */
.fab {
  position: fixed; right: 16px; bottom: calc(var(--navh, 68px) + 16px); z-index: 21;
  width: 56px; height: 56px; border-radius: var(--radius-pill); border: 0; cursor: pointer;
  font: inherit; font-size: 1.625rem; line-height: 1;
  background: linear-gradient(135deg, var(--accent-grad-a), var(--accent-grad-b));
  color: var(--on-accent);
  box-shadow: var(--shadow-fab);
  transition: transform .18s cubic-bezier(.2, .8, .3, 1), opacity .18s linear;
}
/* 寬螢幕上跟著內容欄收 — #289(QA F19)。`.wrap` 在 520 之後就置中不再變寬,而
   這顆鈕貼的是**視窗**右下角:768 寬實測 `.wrap` 收在 124–644,`#fab` 卻停在
   696–752,飄在內容欄右邊那片空白背景上,和它要作用的那張卡沒有任何視覺關係。

   算式就是版面自己那一條:視窗比內容欄寬多少,兩邊各分一半,再加回原本那 16px
   的縫 —— 所以 390 寬(欄比視窗窄不了)一個像素都不動,768 寬時它落在 628,與
   內容欄右緣的 16px 縫對齊。**門檻 552 = 520 + 兩邊各 16**(`.wrap` 是
   border-box,padding 算在那 520 裡),也就是內容欄開始置中的那一刻。

   **用 `50%` 不用 `50vw`**:`position: fixed` 的百分比算在初始包含區塊上,那是
   **不含捲軸**的視窗;`100vw` 含捲軸,桌機上會把這顆鈕往右推半條捲軸的寬度。

   左邊那一顆(`html[data-fab="left"]`)的同一條在 web/prefs.css —— 那條規則的
   特異度 (0,2,1) 壓得過這裡的 (0,1,0),得寫在它自己那一支旁邊才蓋得掉。 */
@media (min-width: 552px) { .fab { right: calc(50% - 244px); } }

/* 下滑收起來 — nav.js 的 onScroll()。**不是 `.hide`**:那是 `display: none`,
   而這裡要的是一段動畫;`pointer-events: none` 讓收起來的那顆按不到,不然使用者
   會點到一顆看不見的鈕。特異度 (0,2,0) 壓得過上面那條 (0,1,0)。

   `.kbd` 是**軟鍵盤彈出來的時候** — D-F99(#370),切它的是 nav.js 的
   fabKeyboard()(那一段寫著為什麼是「收起來」而不是「改跟著 visualViewport 的
   底」)。長相與 `.away` 逐字相同,而**是兩個 class 不是一個**:`.away` 是捲動方
   向的狀態,onScroll() 隨時會把它改掉 —— 共用一個的話,鍵盤還開著、手指往回滑一
   下,那顆鈕就自己冒到輸入法上面。 */
.fab.away, .fab.kbd {
  transform: translateY(96px) scale(.9); opacity: 0; pointer-events: none;
}
@media (prefers-reduced-motion: reduce) { .fab { transition: none; } }

/* z-index 是 #168 加的(那時排的是「更多」那張選單);#201 之後這一疊是
   FAB(21)→ nav(22)。兩個數字都在 auth.css 的 #gate(30)之下 —— 登入閘蓋著
   時整個 app 不該有東西冒出來,而 api.js 的 toast(40)在最上面。 */
nav {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 22; background: var(--nav);
  border-top: 1px solid var(--line); display: flex; padding-bottom: env(safe-area-inset-bottom);
}
nav button {
  flex: 1; font: inherit; font-size: 0.71875rem; cursor: pointer; padding: 10px 0 12px;
  background: none; border: 0; color: var(--dim);
}
/* 記一筆那顆底色的規則在 #201 退役了 —— 它整顆搬出 nav 變成 FAB(上面的
   `.fab`),而「不同顏色的底」在那裡是 accent 實心,比兌淡的那一版更像主要動作。
   #166 當時寫的取捨(底色不能逼白字上場,否則說不出「我在這一頁」)只在**那顆鈕
   站在 nav 上**時成立:nav 上「現在選著」是 accent 色的字,而 FAB 不在那一排裡。 */
nav button[aria-current="page"] { color: var(--accent); font-weight: 640; }
/* nav 那一格的 icon — #392 從五個幾何字元(▤▦⌂≡◈)換成 icon 集裡的五顆。
   `<em>` 那一層留著:它是「圖在上、字在下」那個版面,而 icon 換掉的是它裝的
   東西不是它自己。 */
nav em { display: block; font-style: normal; margin-bottom: 2px; line-height: 0; }
nav em .ic { vertical-align: 0; }

/* 「更多」那張向上彈的選單(`.scrim` / `.sheet` / `.mrow`,#168)在 #201 整組
   刪掉了 —— 它裝的三個入口各自回到導覽列與鈴鐺上,沒有東西再需要一張浮起來的
   紙。`--navh` 那個接縫留著,現在是 FAB 在用(見上面 `.fab`)。 */

.hide { display: none; }

/* 換分頁的方向感 — #544。使用者 2026-09-12 原話:「首頁的左右划沒有對應的動畫」。
   **那不是壞掉,是從來沒有**:實測(2026-09-12,Chrome 152,390×844 沙盒)從首頁
   滑到紀錄,放開之後 25 幀裡 `#v-home` / `#v-log` 的 `transform` 全是 `none`、
   `opacity` 全是 `1`、`animation-name` 全是 `none` —— `go()` 走 `paint()` 切
   `.hide`,而 `.hide` 是 `display: none`,中間沒有任何一幀可以動。

   **方向由 `SWIPE_PANES` 的索引差決定,不是由手勢**(nav.js 的 paneShift()):
   點分頁鈕與滑過去因此是同一個動畫,而 `報表 · 首頁 · 紀錄` 那條線上的左右關係
   是固定的 —— 「新的那一頁從哪一邊進來」是它站在哪一格的性質,不是使用者剛才
   用哪一根手指的性質。

   **舊頁那張殘影(`.pane-out`)是必要的,不是裝飾**:新頁從 `opacity: 0` 起跳,
   而舊頁的 `.hide` 是 `display: none` —— 少了它,換頁的頭幾幀是一片空白。它
   `position: fixed` 在自己被收起來之前那一刻的位置(座標由 nav.js 量好寫進
   inline style),所以 `.wrap` 的定位脈絡一個字都不必動:在 `.wrap` 上種
   `position: relative` 會改掉每一個 view 裡 `position: absolute` 元素的
   offsetParent。fixed 的另一半好處是它不撐大文件的捲動範圍 —— 往右挪的那 24px
   不會變成一條橫向捲軸(D-F99 那組不變量裡的第二條)。

   `display: block` 壓的是同一個元素上的 `.hide`:兩條都是單一 class(0,1,0),
   後來者贏,而這一條就寫在它下面。z-index 1 落在 FAB(21)與 nav(22)之下:
   殘影蓋得過新頁的內容,但不蓋底部那一排鈕。

   .2s 與 `cubic-bezier(.2, .8, .3, 1)` 同這一檔既有的每一段進場(`.pick-sheet`
   的 `pick-up`),落在 180–220ms 裡;**只動 `transform` 與 `opacity`**。

   ⚠️ **`prefers-reduced-motion` 那一半的正主在 nav.js**(paneShift() 開頭那一
   句 matchMedia):這裡的 `animation: none` 是第二道,不是唯一一道 —— 清掉這幾
   個 class 的是 `animationend`,而一個 `animation: none` 的元素**永遠不會發那個
   事件**,殘影會就這樣釘在畫面上。所以那一種偏好底下 class 一個都不加。 */
.pane-in-left { animation: pane-in-left .2s cubic-bezier(.2, .8, .3, 1); }
.pane-in-right { animation: pane-in-right .2s cubic-bezier(.2, .8, .3, 1); }
.pane-out {
  display: block; position: fixed; z-index: 1; pointer-events: none;
  /* **左右抄的是 `.wrap` 的那兩個數字,不是量出來的 px** — 2026-09-13 修的那一發
     (#544 的 land 全套紅)。殘影原本連 `left` 與 `width` 都由 nav.js 量好寫成
     inline px,而 `getBoundingClientRect()` 回的是**縮放之後**的數字、inline 的
     長度又是在**縮放之前**的座標系裡被讀的 —— 大字級(`zoom: 1.15`,prefs.css)
     底下同一個值被乘了兩次:390 寬的機器上殘影裡的卡量到 406(= 353 × 1.15),
     `TheLayoutHoldsOnEverySize` 的「有元素寬過視口」倒在那裡。

     這一行是 `.wrap` 那一條的複寫(`520 - 16 × 2 = 488`,同 `.pick[data-drop]
     .pick-sheet` 的那個 488),而**由瀏覽器在縮放之後的座標系裡自己算** —— 沒有
     數字經過 JS,也就沒有第二次相乘。**貼的是內容欄的兩邊(各 16px),不是視口
     的兩邊**:殘影的矩形因此與那一頁在版面上時逐字相同,而往左右挪的那 16px 正
     好用掉 `.wrap` 留的那一格 —— 實測(2026-09-13,Chrome 152,390×844 大字級)
     `left: 0` 那一版的殘影是整個視口寬,動畫中間它的左緣掉到 **-18**(而它的內容
     仍然在欄裡);改貼內容欄之後,整段動畫裡它的矩形沒有出過視口。

     **不寫視口寬那個單位**:它不跟著 `zoom` 縮(D-F99 那一課,`.pick[data-drop]
     .pick-sheet` 要自己除回去),而桌機上它還含著捲軸的寬度(`.fab` 那一條的理由,
     #370)。 */
  left: 16px; right: 16px; margin: 0 auto; max-width: 488px;
}
.pane-out-left { animation: pane-out-left .2s cubic-bezier(.2, .8, .3, 1) both; }
.pane-out-right { animation: pane-out-right .2s cubic-bezier(.2, .8, .3, 1) both; }
/* **那 16px 不是隨手挑的,它是版面剩下的那一格** — `.wrap` 左右各 16px 的
   padding,所以每一個 section 的右緣離視口右緣正好 16px。往右挪 24px 的那一版
   實測(2026-09-12,Chrome 152,390×844)會把**版面視口自己撐寬**:量到
   `innerWidth` 與 `scrollWidth` 在動畫那五幀從 390 變成 398(= 374 + 24),而那
   正是 D-F99 那組不變量的第一、二條在擋的東西。挪 16px 的時候右緣恰好貼齊視口,
   一像素都沒有溢出去(同一支探針量到全程 390)。

   殘影那一半不受這條限制(`position: fixed` 不撐大文件的捲動範圍,同一輪實測:
   `pane-out-right` 那幾幀 `innerWidth` 仍然是 390),但兩邊用同一個數字 —— 兩頁
   在同一段時間裡往相反方向走同樣的距離,那是一個交叉的動作,不是兩個動作。 */
@keyframes pane-in-left { from { opacity: 0; transform: translateX(16px); } }
@keyframes pane-in-right { from { opacity: 0; transform: translateX(-16px); } }
@keyframes pane-out-left { to { opacity: 0; transform: translateX(-16px); } }
@keyframes pane-out-right { to { opacity: 0; transform: translateX(16px); } }
@media (prefers-reduced-motion: reduce) {
  .pane-in-left, .pane-in-right, .pane-out-left, .pane-out-right { animation: none; }
}

/* --------------------------------------------------------------- icon — #392

   一套 stroke SVG(web/icons/set.js),而尺寸只有三格:24 / 20 / 16。**寫成 class
   而不是每一處各給一個 width**:同一顆 icon 在 nav 上與在籤裡是兩個大小,而
   `<use>` 引的是同一份 path —— 尺寸是使用的一部分,不是 icon 的一部分。

   `stroke: currentColor` 寫在 sprite 的 `<symbol>` 上,所以顏色由 icon 站的地方
   決定:nav 上是 `--dim`、選中那一格是 `--accent`、危險那一列是 `--danger`。
   **這正是 emoji 做不到、而 #221 / #225 兩次判決講的那件事。**

   `flex: none` 是量出來的:icon 多半站在 flex 容器裡(一列的右邊、chip 裡),而
   沒有它的話那個方框會被壓扁。 */
.ic { display: inline-block; flex: none; vertical-align: -.14em; }
.ic-lg { width: 24px; height: 24px; }
.ic-md { width: 20px; height: 20px; }
.ic-sm { width: 16px; height: 16px; }
/* sprite 本體。`display: none` 的 `<svg>` 仍然引得到(`<use>` 走的是文件裡的 id,
   不是畫面上的節點)—— 這是 sprite 這個做法的全部機制。 */
#icons { display: none; }

/* ------------------------------------------ 離線標示那一條 — D-H33 ②(#586)

   頁首底下一條通欄細帶(`nav.js` 畫,全站一份;DESIGN-OFFLINE §3.4)。

   **通欄靠負邊界吃掉 `.wrap` 那一格內距** —— 字面值 16 不是 var,同 `.band-btn`
   的 `margin-right: -16px`(那一格歸 #467)。內距再補回來,字因此與內容切齊。

   **`--warn` 站在 `--warn-soft` 上**:那一對已經在對比表裡(`EveryThemeMeetsContrast`
   的「警告籤」那一格,12 個主題都量過)。選它而不是 `--danger`:離線不是一則錯誤,
   它是一件使用者要知道的事 —— #392 把這兩格拆開的理由逐字就是這一句。

   `display` 一格都不寫,所以 UA 樣式表的 `[hidden] { display: none }` 收得起來
   —— 頂帶那一對 `.band-scrim[hidden]` 規則是反例,而它自己那一段寫著為什麼。 */
.offbar {
  margin: 0 -16px; padding: 6px 16px;
  font-size: 0.8125rem; line-height: 1.5; text-align: center;
  color: var(--warn); background: var(--warn-soft);
}

/* ------------------------------------------ 下拉重新整理的指示器 — D-H33 ②

   一顆轉著的小圈,**沒有字**:它出現的那一刻使用者的手指還在螢幕上,而這一刻要
   說的事只有「放開就會重新抓」—— 一句話在那個姿勢底下讀不完,而那顆圈是每一支
   app 的同一句話。沒有字也就沒有第二份文案要翻(web/i18n-en.js)。

   **是 `body` 的一個偽元素,不是一個節點**:它的開合只有一個作者(nav.js 那一格
   class),而一個只在手勢中間存在的節點會多出「誰負責把它拿掉」這個問題 —— 而那
   正是 #383 那一族殘留的形狀。`body::before` 沒有人用(底紋是 `background` 的第
   一層,見本檔開頭那一段),所以這一格是空的。

   `top` 讓開瀏海,同 `header` 那一行的理由;`z-index` 在遮罩(24)之下 —— 頂帶
   開著的時候下拉根本不成立(nav.js 的 `bandOpen()` 擋在前面)。 */
body.pulling::before {
  content: ""; position: fixed; z-index: 23;
  top: calc(10px + env(safe-area-inset-top)); left: 50%; margin-left: -11px;
  width: 22px; height: 22px; border-radius: 50%;
  border: 2px solid var(--line); border-top-color: var(--accent);
  animation: pullSpin .7s linear infinite;
}
@keyframes pullSpin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
  body.pulling::before { animation: none; }
}
