# 單檔訓練記錄頁 — 設計規範

一份可直接照抄的設計語言。整個專案是**一個 .html 檔**：雙擊開啟、離線可用、資料存 `localStorage`，沒有框架、沒有 build、沒有後端。

搭配 `SHARE-template.html` 使用（那是這份規範的可執行骨架）。

---

## 0. 核心原則

1. **一個檔案就是全部**：HTML + `<style>` + `<script>` 塞在一起。沒有 npm、沒有打包、沒有 CDN。email 寄得出去、隨身碟帶得走。
2. **深色優先**：近黑 canvas + 四階表面（surface ladder）製造層次，不靠陰影。
3. **一個強調色**：全站只有一個品牌色，而且**節制使用**。畫面上任何藍紫色都在說「這裡重要」。
4. **語意色不算裝飾**：狀態色（成功綠、警告黃、危險紅）與資料分類色（訓練區間 Z1–Z5）另外一組，可以自由用，因為它們攜帶資訊。
5. **hairline 分隔線取代卡片陰影**：1px `--line` 就夠，不要 box-shadow 堆疊。

---

## 1. 色票（複製 `:root` 即可）

```css
:root{
  /* canvas 與表面階層 —— 越上層越亮，只用這四階 */
  --bg:#010102;      /* 頁面底 */
  --card:#0f1011;    /* 一級卡片 */
  --card2:#141516;   /* 卡片內的次級區塊 / 輸入框 / hover */
  --card3:#18191a;   /* 三級（hover on hover、標籤底） */

  /* 邊框 */
  --line:#23252a;    /* 主要 hairline */
  --line2:#34343a;   /* 次要、需要更明顯時 */

  /* 文字三階 */
  --txt:#f7f8f8;     /* 主文 */
  --mut:#8a8f98;     /* 說明、標籤 */
  --mut2:#62666d;    /* 最弱：表頭、bullet */

  /* 品牌強調色（節制使用） */
  --acc:#5e6ad2;     /* 薰衣草藍 */
  --acc-h:#828fff;   /* hover */

  /* 語意色 */
  --acc2:#3fb950;    /* 成功 / 完成 */
  --warn:#d29922;    /* 警告 */
  --danger:#f85149;  /* 危險 / 刪除 */

  /* 資料分類色（依你的領域換掉，這裡是訓練強度區間） */
  --z1:#6e7681; --z2:#3fb950; --z3:#d29922;
  --z4:#f85149; --z5:#bc4bff;
}
```

### 強調色只用在這五種地方
標題的品牌字 · 關鍵數字（大字統計、倒數）· 分頁 active 狀態 · 主要按鈕 · 需要導引視線的目標值。

**不要**拿它做：一般邊框、一般 hover、大面積背景、所有連結。

### 語意色的用法：**低透明度底 + 實色左邊條 + 實色文字**
不要整塊填滿飽和色，那在深色底上會刺眼。

```css
.block-success{
  background:rgba(63,185,80,.06);
  border-left:2px solid var(--acc2);
  color:var(--acc2);
}
```

---

## 2. 字體

```css
body{
  font-family:"SF Pro Display","SF Pro Text",-apple-system,"Segoe UI","Microsoft JhengHei",sans-serif;
  font-size:15px;
  line-height:1.5;
  letter-spacing:-0.011em;      /* 負字距是整體質感的關鍵 */
  -webkit-font-smoothing:antialiased;
}
h1,h2,h3{ letter-spacing:-0.02em; font-weight:600 }
h1{ letter-spacing:-0.03em }     /* 字越大，字距收越緊 */
```

**字級階梯**：30px（統計大數）/ 19px（h1）/ 15px（內文）/ 13px（次要）/ 12px（標籤）/ 11px（徽章）/ 10px（單位）。中間不要再插值。

**負字距規則**：字級越大，`letter-spacing` 越負。這是整套設計看起來「不像 bootstrap」的最大單一原因。

---

## 3. 版面

```css
main, header .inner, nav{ max-width:1100px; margin:0 auto; padding:16px }
```

**圓角階梯**：卡片 12px · modal 14px · 區塊/輸入框 8px · 小按鈕 6px · 膠囊徽章 20px。

**間距**：只用 4 的倍數（4 / 8 / 12 / 14 / 16 / 18）。

**sticky header**：半透明 + 毛玻璃，捲動時內容從底下透出來。

```css
header{
  position:sticky; top:0; z-index:50;
  background:rgba(1,1,2,.8);
  backdrop-filter:blur(12px);
  border-bottom:1px solid var(--line);
}
```

---

## 4. 元件配方

### 分頁（tabs）
不要邊框、不要底線。未選＝灰字透明底，選中＝`--card2` 底 + 白字 + 圓角。

```css
nav button{ background:transparent;border:none;color:var(--mut);
            padding:7px 13px;border-radius:8px;transition:color .12s,background .12s }
nav button.active{ background:var(--card2);color:var(--txt) }
```

### 可摺疊卡片
`.card` 是 `--card` 底 + 1px 邊框 + `overflow:hidden`；標題列用 `--card2` 稍亮一階，hover 到 `--card3`。展開狀態靠 `.open` class，不用 JS 算高度。

```css
.body{display:none} .card.open .body{display:block}
```

**完成狀態高亮（只在收合時）**：邊框轉綠 + 標題列淡綠底 + 左側 3px 內陰影條。展開時取消，避免干擾閱讀。

```css
.card.done:not(.open){ border-color:var(--acc2) }
.card.done:not(.open) .head{
  background:rgba(63,185,80,.10);
  box-shadow:inset 3px 0 0 var(--acc2);
}
```

### 列表列：**兩列式 grid，不要三欄式 flex**
這是踩過坑才學到的。左邊放日期、右邊放操作鈕，中間放內容 → 內容欄被夾在中間，左右兩側底下全是空白。

改法：一個 4 欄 grid，第一列放 `勾選 / 日期 / 標題 / 按鈕`，第二列用 `grid-column:1/-1` 讓內文橫跨整寬。

```css
.row{ display:grid; grid-template-columns:auto 64px 1fr auto;
      column-gap:10px; padding:11px 14px; border-top:1px solid var(--line);
      align-items:start }
.row-body{ grid-column:1/-1; min-width:0; margin-top:4px }
```

同理，操作鈕若只有一個動作，**用 28px 圖示鈕（`＋` / `✎`）取代「＋新增記錄」文字鈕**，省下的橫向空間全歸內容。記得補 `title` 與 `aria-label`。

### 數據標籤（chip）
數字用主文色 + 700 字重，單位用 `<i>` 包起來降級成 10px 灰字。這樣一排 chip 掃過去只讀得到數字。

```html
<span class="chip">42.1<i>km</i></span>
```
```css
.chip{ font-size:12px;font-weight:700;background:var(--card);
       border:1px solid var(--line);border-radius:6px;padding:3px 8px;white-space:nowrap }
.chip i{ font-style:normal;font-weight:500;color:var(--mut);font-size:10px;margin-left:1px }
```

### 統計磚
```css
.stat{ background:var(--card);border:1px solid var(--line);border-radius:12px;padding:16px }
.stat .n{ font-size:30px;font-weight:800;color:var(--acc) }   /* 強調色用在這裡是對的 */
.stat .l{ font-size:12px;color:var(--mut) }
```
外層 `grid-template-columns:repeat(auto-fit,minmax(150px,1fr))` — 不用寫任何 media query 就會自己換行。

### 徽章 / 標籤
- **膠囊分類徽章**：深底 + 亮字，同色系一深一亮（例：`background:#1f3a24; color:#7ee787`）。
- **輕量標籤**：透明底 + 1px `--line` 邊框 + `--mut` 字。用在不重要的補充資訊。

### Modal
`position:fixed; inset:0` + `rgba(0,0,0,.7)` 遮罩，內容盒 `max-width:460px; max-height:90vh; overflow:auto`。用 `.open` class 切換 `display:none/flex`。

### 分段內容區塊（避免長文擠成一坨）
長段落全部塞一起沒人讀。做法：內容前綴 TAG，渲染時依 TAG 拆成有色左邊條的小區塊。

```
WIN|重大突破…    → 綠色區塊
WARN|待修正…     → 黃色區塊
RED|第一優先…    → 紅色區塊
NEXT|下一步…     → 品牌色區塊
```

每個區塊：`--card2` 底 + 2px 語意色左邊條 + `border-radius:0 8px 8px 0`（左邊不圓，讓色條連成一條線）+ 11px 大寫小標。

---

## 5. 響應式

只需要一個斷點：

```css
@media(max-width:860px){
  /* 多欄 grid 降成單欄，其他都靠 flex-wrap 和 auto-fit 自己處理 */
}
```

所有橫向排列一律加 `flex-wrap:wrap`，寬度用 `minmax()` 而非固定值，就幾乎不需要 media query。

---

## 6. 資料層（沒有後端）

```js
let DB = JSON.parse(localStorage.getItem("app_db") || "{}");
const save = () => localStorage.setItem("app_db", JSON.stringify(DB));
```

**一次性資料植入用 flag**：想預先塞入資料，但又不能每次載入都覆蓋使用者的編輯 —— 用版本 flag。

```js
if(!localStorage.getItem("app_seed_v1")){
  Object.assign(DB, SEED);
  localStorage.setItem("app_seed_v1","1");
  save();
}
```
之後要補新資料就改成 `app_seed_v2`，已載入過的瀏覽器才會套用一次。

**一定要做匯出**：資料只在瀏覽器裡，清快取就沒了。附一個 JSON / CSV 匯出鈕，`Blob` + `URL.createObjectURL` 五行搞定。

---

## 7. 三條鐵則

1. **加一個顏色之前先問：它在說什麼？** 說不出來就用 `--mut`。
2. **加一個邊框之前先問：換一階表面色行不行？** 通常行。
3. **內容永遠比 chrome 重要**：固定寬度的側欄、按鈕、日期欄，能砍多窄就砍多窄。
