# LILA_v2 元件基準 — 組裝索引

給下一個 agent 讀的。目的是讓你**直接組裝，不要重新設計**。

如果你正打算自己寫一套 CSS、自己發明一組 class 名、或自己決定 nav 有幾格，先讀完這份。
這裡已經有的東西不要重做；這裡標成 OPEN 的東西不要代你決定。

先讀 `../LILA_v2/AGENTS.md`（工作區邊界）與 `LILA_v2/DECISIONS.md（未部署，僅在本機）`（已定事項與為什麼）。本檔只講元件怎麼用。

## 三十秒起手

1. 依序載入 `tokens.css` → `utilities.css` → 你需要的 `shell.css` / `lists.css` / `read.css`。順序不要換。
2. 複製 `../LILA_v2/prototype/phone.html` 的 shell 骨架，把 `.shell-slot` 裡的內容換成你要的。
3. 缺哪個元件先查下面的索引表，再決定要不要新做。

看得到的入口：

| 檔案 | 是什麼 |
|---|---|
| `index.html` | **canonical design system catalog**，唯一的 design system 入口。人類閱讀順序：總覽（含完整手機）→ Shell／清單／判讀的元件與狀態 → Patterns → Foundations → Decision Status。五層分類保留為 `data-layer` metadata 與 agent 索引（見 D15） |
| `../LILA_v2/prototype/phone.html` | **composition demo／playground**，不是 design system。職責只有證明元件能組成完整手機體驗 |
| `preview-shell.html` | shell 與 nav 的所有狀態平鋪 |
| `preview-lists.html` | 清單元件族的所有狀態平鋪 |
| `preview-read.html` | 滑動判讀的所有狀態，可實際滑 |

## Token 是唯一視覺來源

`tokens.css` 是唯一可以出現顏色、間距、字級、圓角、轉場數值的地方。

- 元件 CSS 一律 `var(--token)`，**不得寫死 hex / px**。三個 worker 的檔案已經全數通過這項機械掃描。
- 缺變數不要自己加。在 review 檔提建議，由 PO 決定要不要進 token 契約。
- `utilities.css` 放跨 domain 共用的 utility（目前只有 `.visually-hidden`）。它不是 token 檔，不要往裡面放色值或元件；也不要把跨 domain utility 藏在某個 domain 的 CSS 裡——那會害只載入另一份 CSS 的人解析不到。
- 想改整體調性就只改這一個檔，其他檔不動。這是這套基準最主要的槓桿。

灰階為主。**唯一允許的彩色是漲跌語意**：`--color-up` / `--color-down` / `--color-flat`。
資訊位一律 `--color-skeleton` 骨架佔位，不要填真資訊、不要編數字。

值的來源標在 `tokens.css` 註解裡：`[H1]` 是從 `a-guided-brief` 實際 CSS 提取，`[PO]` 是 PO 決定或推導。

## 接縫契約（收斂後的單一版本）

三個 domain 各自有假設，以下是 PO 收斂後的版本，衝突時以本節為準。

1. **內容只掛在 `.shell-slot[data-shell-slot]`。** 不要插到 `.lila-shell`、`.shell-content`、`.shell-stack` 的直屬層，那會破壞 chrome 與轉場。
2. **必要結構不可省略**：
   `.shell-root[data-shell-root] > .shell-stack > .shell-stack-page[data-shell-page] > .shell-page-scroll[data-shell-scroll] > .shell-slot[data-shell-slot]`
   就算你的內容自己捲，也不要刪掉 `.shell-page-scroll` 或 `.shell-slot`——shell 的高度鏈、轉場與 JS 查找都依賴這兩層。
3. **可用高度由 shell 算好**：總高扣掉 status bar、header、nav、safe bottom。內容端**不要再扣一次**，也不要自己加 `--navbar-height` 或 `--safe-bottom`，否則在沒有 nav 的 push 層會重複留白。
4. **縱向 scroll 只能有一個 owner。** 兩種模式，二選一：
   - 預設：`.shell-page-scroll` 負責捲。內容端最外層不得設 `height: 100vh` 或整頁 `overflow-y: auto`。清單走這個模式。
   - fill：在該 `.shell-stack-page` 加 `data-shell-scroll-mode="fill"`。shell 不捲、給出確定全高，由 slot 內的元件自己捲。**滑動判讀必須走這個模式**，否則 `scroll-snap` 吸附會被外層吃掉、`height: 100%` 會塌。
   局部的水平捲動（filter chip、carousel）自己負責，shell 不攔手勢。
5. **`.shell-slot` 不自帶水平 padding**，讓 K 線與 swipe 能貼齊邊緣。需要內距時由你的 wrapper 用 spacing token。清單預設用 `.list-stack__content` 的 `--space-4`；如果 shell 那側已經給了 padding，改用 `.list-stack__content--flush` 避免雙層。
6. **header 與 back 歸 shell。** 不要在內容裡放第二套全頁 header 或 back 按鈕。次層 header title 預設是骨架，因為頁名還是 OPEN。
7. **事件往上冒泡，不要反向讀別人的 DOM。**
   - shell 發 `lila:shell-change`，detail 含 `action` / `activeTab` / `activePage` / `depth` / `stacks`。
   - reader 發 `read:change`、`read:dismiss`、`read:statechange`。`read:dismiss` 由組裝端接到 shell 的 `back()`。
   - 不要去讀 reader 的 progress DOM 來推狀態。

## 元件索引

完整的狀態、變體、DOM 與 token 依賴表在各 worker 的 review 檔裡，這裡只給導覽。

### Shell（`.shell-*`，共 25 個元件／元件群）

契約全文：`contracts/shell_nav.md` 第 5 節。

| class | 一行用途 |
|---|---|
| `.lila-shell` | 固定外框與四列版位 |
| `.shell-statusbar` | status chrome 與 safe top |
| `.shell-header` / `-back` / `-title` | root 與次層共用 header；title 支援骨架態 |
| `.shell-depth` / `.shell-depth-dot` | 目前 stack 深度指示，JS 動態生成 |
| `.shell-content` | 所有 root 的固定 viewport 與 clipping 邊界 |
| `.shell-root` | 一條 root tab 的獨立 stack 容器 |
| `.shell-stack` / `.shell-stack-page` | page 疊層與 push / back 轉場層 |
| `.shell-page-scroll` | 預設模式下的唯一縱向 scroll owner |
| `.shell-slot` | **內容 worker 的唯一掛載點** |
| `.shell-nav` / `.shell-tab` / `-icon` / `-label` / `-badge` | 四格 root 導覽；badge 是無數字灰點 |
| `.shell-skeleton-*`（line / block / card / icon / group） | 骨架 primitives |
| `.shell-placeholder-page` / `-action` | preview 專用，正式內容可整個替換 |

JS：`window.LILAShell.mount(el)`；instance 提供 `switchTab(name)`、`push(page)`、`back()`、`setBadge(tab, bool)`、`getState()`、`destroy()`。載入 `shell.js` 會自動 mount 所有 `[data-shell]`。

Nav 四格固定為 `today / watchlist / market / profile`，顯示「今日 / 自選 / 市場 / 我的」。**個股不佔格**，一律 push 進入。

### 清單層（`.stock-row` 等，共 33 個元件）

契約全文：`contracts/feed_list.md` 第 3、6 節。純 CSS，無 JS。

最核心的是 `.stock-row`，四條 stack 共用同一套，差異只靠 modifier 與插槽：

    .stock-row[--up|--down|--flat|--card|--compact|--draggable|--loading]
      > .stock-row__leading   （拖曳 / 選取控制，可省略）
      > .stock-row__primary   （進入個股的主點擊區：identity + quote + sparkline）
      > .stock-row__action    （加入自選 / 刪除 / chevron）

`.stock-row` 本身不是 `<button>`，避免巢狀 button——右側動作、拖曳、刪除也都是按鈕。push handler 接在 `.stock-row__primary`。

| 分類 | class |
|---|---|
| 容器 | `.list-stack` / `__content` / `--flush`、`.list-section`、`.stock-list`、`.feed-list` |
| 分組與篩選 | `.list-section-header`、`.filter-strip`、`.filter-chip`、`.segmented-control` |
| 今日專屬 | `.feed-card`、`.reason-slot` |
| 自選專屬 | `.list-edit-toolbar`、`.batch-bar`、`.drag-handle`、`.selection-control`、`.delete-control` |
| 我的 | `.profile-shell` / `__placeholder`、`.destination-list` / `.destination-row` |
| 通用 | `.row-action`、`.text-action`、`.empty-state`、`.skeleton-line`、`.skeleton-text-group`、`.skeleton-block--sparkline` |

骨架寬度規則已統一（長 72% / 中 52% / 短 34% / 數字 26%），不要每個元件重新發明。

### 滑動判讀（`.read-*`，共 10 個元件）

契約全文：`contracts/read_swipe.md` 第 6 節。

這是從 0730 還原的核心互動。**滑動單位是一個判讀 lens／步驟，不是一檔股票**；方向是縱向 reels，靠 `scroll-snap-type: y proximity` 在接近段首時吸附，頂部 progress 可直接跳段。

| class | 一行用途 |
|---|---|
| `.read-swipe` | reader 根節點與狀態邊界（ready / loading / empty；first / middle / last） |
| `.read-swipe__progress` / `__segment` | 段落進度與跳段 hit target |
| `.read-swipe__viewport` | **唯一縱向 scroller 與手勢面** |
| `.read-card` / `__content` | 一個可獨立替換的判讀步驟 |
| `.read-card__eyebrow` / `__title` / `__line` / `__metric*` / `__block` | 卡內骨架 primitives |
| `.read-kline` | 唯一具體圖形，inline SVG 假資料 K 線 |
| `.read-swipe__state[data-read-loading\|data-read-empty]` | blocking 狀態層 |

JS：`new ReadSwipe(root)` 或 `ReadSwipe.get(root)`；提供 `goTo(i)`、`setState("ready"|"loading"|"empty")`。

橫向左滑返回的門檻已 token 化（`--gesture-swipe-threshold`，原本 0730 寫死 60px）。鍵盤替代路徑（方向鍵、PageUp/Down、Home/End）是本輪新增的可及性補充，0730 原本沒有。

## 不要做這些

- 不要寫死顏色、間距、字級。
- 不要為某一條 stack 另做一套股票列。用 modifier。
- 不要在內容裡放第二套 header / back / 全頁 scroller。
- 不要把 `preview-*.html` 當 runtime 元件用（`.shell-preview-*`、`.shell-placeholder-*` 是展示用的）。
- 不要填真資訊、不要編價格漲跌成交量、不要寫判讀文案。這不是保守，是 `LILA_v2/DECISIONS.md（未部署，僅在本機）` D2 的變因分離：資訊一填進去，就分不清「流程不好」還是「資訊沒做好」。
- 不要改 `tokens.css`、`LILA_v2/DECISIONS.md（未部署，僅在本機）`、`../LILA_v2/AGENTS.md`、本檔。這四個檔的寫入者是 PO。

## 組裝已發現的落差（第一份使用回饋）

`phone.html` 是這套基準的第一個真實消費者，組裝時發現了六條落差。完整表格在
`contracts/shell_nav.md` 的「組裝時發現的落差」一節。摘要與處置：

| 落差 | 狀態 |
|---|---|
| `read.css` 有未 scope 的 `html` / `body` / `button` 規則，載進真實 app 會蓋到 page 層 | 已派 `read_swipe` 修 |
| `.visually-hidden` 只住在 `read.css`，只載入 `lists.css` 的人解析不到 | 已建 `utilities.css`，並派 `read_swipe` 改為 import |
| 清單元件是純 CSS，`.filter-chip`、`.segmented-control`、編輯／批次態沒有 controller，在 demo 裡不能真的切 | **待使用者決定**是否補 opt-in controller（見 O8） |
| `lists.css` 的 mobile media query 在手機寬度隱藏 `.stock-row__sparkline`，所以 `phone.html` 上看不到迷你走勢 | **待使用者決定**（見 O6） |
| 個股判讀層 push 後 bottom nav 仍可見；0730 原本是全螢幕 overlay | **待使用者決定**（見 O7） |
| `phone.html` 與三份 domain CSS 都載入 `tokens.css`（重複但不改變 cascade） | 非阻斷，未處理 |

沒有發現 class 同名衝突，沒有重複 page padding，reader 的 `isolation: isolate` 讓 progress
的 z-index 留在自己的 stacking context、沒有跟 shell header / nav 打架。

## OPEN — 不得代你決定

| 代號 | 未定的事 |
|---|---|
| O1 | 要讓新手看懂籌碼 K 線的**哪一段** |
| O2 | 「今日」Feed 的選股邏輯、排序、分組、推薦理由內容 |
| O3 | 各條 stack 內部的實際資訊內容與頁名 |
| O4 | 「我的」底下的設定細項 |
| O5 | 視覺調性：H1 的藍灰系 vs 目前的中性灰階（見 `tokens.css` 開頭註解） |
| O6 | 手機寬度要不要顯示 `.stock-row__sparkline`（目前 media query 隱藏） |
| O7 | 個股判讀層要不要隱藏 bottom nav（目前保留可見；0730 原本是全螢幕 overlay） |
| O8 | 清單要不要補互動 controller（目前純 CSS，chip 與編輯態按了不會有反應） |
| O9 | **K 線出現在哪些版位。** 目前只在判讀卡內（`.read-kline`），清單迷你走勢是骨架。這個配置是 PO 開規格時順手定的，從未拍板——不要因為它已經實作出來就當成已定案 |

元件庫已經替這些留好位子。你要探索的是這些，不是重新做元件。

## 已知未驗證

- **瀏覽器驗證只涵蓋 Chromium。** catalog 重做那一輪，worker 找到 Playwright 內建 Chromium，以 `file://` 實際載入 `index.html`：4 個 iframe 都完成載入、sticky nav current-section 同步、`<details>` 預設收起、複製功能有回饋、Shell 切 tab 與 Read 切 loading 都成功、console／page error 為 0。
  **但 Safari、Firefox、窄螢幕 viewport 都沒測**，`phone.html` 的手勢慣性與 proximity snap 手感也還沒有人實際用手滑過。
  （更早的三份元件交付當時回報環境沒有可用瀏覽器，只做了語法檢查、token 引用掃描、DOM 契約靜態核對與 Quick Look 靜態渲染。那些交付本身沒有被重新以瀏覽器驗證過。）
- `a-guided-brief` 的 `../shared/base.css` 不在本輪 worker 的可讀邊界內（PO 開規格時把範圍畫窄了），所以 H1 的全域字體、shell chrome、共用陰影可能有未覆蓋的值。
- 驗收動線建議：開 `phone.html` → 今日 push 到個股 → 判讀能上下滑、能點 progress 跳段 → 切到市場再 push → 切回今日應該仍在個股層 → back 兩次回 root。
