File size: 19,687 Bytes
5ece6e2
659e17c
5ece6e2
 
f5990cb
5ece6e2
 
 
 
b1b0036
5ece6e2
3dc69f8
5ece6e2
 
 
 
b1b0036
3dc69f8
 
b1b0036
3dc69f8
5ece6e2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3dc69f8
 
 
5ece6e2
 
 
 
f5990cb
 
3dc69f8
 
f5990cb
5ece6e2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3dc69f8
 
 
 
 
 
 
 
b1b0036
 
3dc69f8
 
 
 
 
 
 
f5990cb
 
 
 
 
 
 
 
b1b0036
3dc69f8
 
 
f5990cb
 
 
3dc69f8
059e85b
b1b0036
 
 
f5990cb
3dc69f8
f5990cb
 
 
b1b0036
3dc69f8
b1b0036
059e85b
3dc69f8
059e85b
 
f5990cb
5ece6e2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f5990cb
b1b0036
659e17c
 
9615d53
5ece6e2
 
 
 
 
 
 
 
659e17c
 
f5990cb
 
659e17c
 
 
f5990cb
659e17c
f5990cb
5ece6e2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
---
version: 1.3
project: SpectraEngine(譯彩紛呈 II 分析引擎)
brand: 致理科技大學應日系 鄧老師委託案
last_updated: 2026-08-12
source: assets/app.css(E4 抽出前為 app_agent.py 內嵌 _CUSTOM_CSS 常數)

colors:
  light:
    bg: "#f3f8fd"
    surface: "#ffffff"
    surface2: "#eef5fc"
    surface3: "#ffffff"
    surface3-border: "#e2e8f0"
    border: "rgba(2,132,199,.16)"
    border-hover: "rgba(2,132,199,.35)"
    border-strong: "rgba(2,132,199,.30)"
    divider: "#a9cbe8"
    thead-bg: "#d8e8f6"
    bubble-bg: "#ffffff"
    input-border: "#7fb0d9"
    accent: "#0284c7"
    accent-strong: "#0369a1"
    accent2: "#4f46e5"
    accent2-strong: "#4338ca"
    accent3: "#9333ea"
    text: "#1e293b"
    text-soft: "#334155"
    text-muted: "#475569"
    text-subtle: "#64748b"
    success: "#047857"
    warn: "#b45309"
    user-bubble-bg: "#dcfce7"
    user-bubble-border: "#86efac"
    user-bubble-text: "#166534"
  dark:
    bg: "#0b1424"
    surface: "#172136"
    surface2: "#1b2740"
    surface3: "#1e293b"
    surface3-border: "#334155"
    border: "rgba(14,165,233,.11)"
    border-hover: "rgba(14,165,233,.30)"
    border-strong: "rgba(148,163,184,.28)"
    divider: "rgba(148,163,184,.18)"
    thead-bg: "#1e2a42"
    bubble-bg: "#172136"
    input-border: "rgba(14,165,233,.15)"
    accent: "#0ea5e9"
    accent-strong: "#38bdf8"
    accent2: "#818cf8"
    accent2-strong: "#a5b4fc"
    accent3: "#c084fc"
    text: "#e2e8f0"
    text-soft: "#cbd5e1"
    text-muted: "#94a3b8"
    text-subtle: "#475569"
    success: "#34d399"
    warn: "#fbbf24"
    user-bubble-bg: "rgba(34,197,94,.18)"
    user-bubble-border: "rgba(34,197,94,.42)"
    user-bubble-text: "#bbf7d0"
  primary-cta-gradient: "linear-gradient(135deg, #0284c7 0%, #4f46e5 100%)"
  accent-gradient: "linear-gradient(100deg, var(--c-accent) 10%, var(--c-accent2) 90%)"
  heading-gradient: "linear-gradient(120deg, var(--c-accent-strong) 0%, var(--c-accent2) 55%, var(--c-accent3) 100%)"

typography:
  fontFamily:
    ui: "Inter, Noto Sans TC, system-ui, sans-serif"
    mono: "JetBrains Mono, monospace"
    heading-latin: "Crimson Pro"
    heading-zh: "Noto Sans TC"
  unit: rem
  remBase: "16px"
  base:
    fontSize: "0.875rem"
    cssVar: "--font-size-base"
    note: "全站內文字級唯一來源,任何內文字級改動一律引用這個變數,不手寫 rem 數值"
  scale:
    display-lg: "2.1rem"
    display-md: "1.8rem"
    display-sm: "1.6rem"
    title-lg: "1.4rem"
    title-md: "1.25rem"
    title-sm: "1.15rem"
    body-lg: "1.05rem"
    body-base: "var(--font-size-base)"
    caption: "0.78rem"
    badge: "0.70rem"
    step-pill: "0.62rem"

rounded:
  sm: "4px"
  md: "9px"
  lg: "10px"
  xl: "12px"
  card: "14px"
  card-lg: "16px"
  pill: "20px"

shadow:
  card: "0 4px 16px rgba(15,23,42,.18)"
  card-hover: "0 16px 40px rgba(79,70,229,.35)"
  card-hover-strong: "0 20px 46px rgba(79,70,229,.45)"
  overlay: "0 16px 50px rgba(15,23,42,.20)"
  drawer: "4px 0 24px rgba(15,23,42,.28)"

layout:
  columns: "nav-col : chat-col : canvas-col = 2 : 3 : 7(gr.Column scale)"
  nav-col-width: "220px(收合 60px,@media min-width:901px 才套用收合樣式)"
  chat-col-min-width: "280px"
  canvas-col-min-width: "380px"
  homepage-max-width: "50rem"
  base-padding: "16px(nav-col/輸入框基準,20px canvas 內容區)"
---

# SpectraEngine 設計系統

> 本文件包含兩層:YAML front matter 為機器可讀的 design tokens(供 AI agent 或 Stitch 等工具直接使用);Markdown 本文記錄「為什麼」與人的判斷準則。實作以 `assets/app.css` 為準——這份 CSS 原本內嵌在 `app_agent.py`,2026-08-05(E4)抽成獨立檔案,改用 `app.launch(css_paths=...)` 載入。

---

## 專案定位

`SpectraEngine`(譯彩紛呈 II 分析引擎)是一套針對重譯文本語言特徵的量化分析平台,技術上是 Python + Gradio 單頁研究工具,服務對象是進行重譯研究的學術使用者(大學教師、研究生)。介面分兩種截然不同的使用情境,視覺語言刻意分兩派:

- **對話區**(左側導覽+對話串+輸入框):參考 **AI-Native UI** 風格——訊息泡泡、打字中三點跳動動畫、串流文字顯示,對應報告撰寫等待期間的載入體感需求。介面語彙上參考 Claude.ai/ChatGPT 網頁版的排版與操作體驗。
- **結果分頁**(表格/圖表區):參考 **Data-Dense Dashboard** 風格——多圖表/資料表並存、緊湊排版、最大化資訊密度,對應 75 項指標表格與 4 種統計圖表的呈現需求。

兩種風格共用同一套色彩與間距 token,差異只在密度與動效,不是兩套獨立設計系統。

---

## 配色

雙主題(淺色/深色)系統,經 WCAG AA 對比度驗算(叫 ui-ux-pro-max skill 對執行中實機量測,非套用通則):淺色模式主要文字對比度約 7.24–14.63:1、深色模式約 7.87–14.78:1,9 個關鍵文字元素全數通過 AA。

**配色決策原則:本次改版不重新設計配色**——業主原本版本已建立完整雙主題 token(sky + indigo + slate 系),驗算品質良好,只在使用場景上做微調,不更換色相。

### 一般強調色 vs 加強強調色

淺色模式的一般強調色 `#0284c7`(accent)用於一般大小的內文文字時,對比度 4.09:1 略低於 WCAG AA 的 4.5:1 門檻,但用於按鈕/圖示/大字等允許 3:1 門檻的地方完全合格。系統另外準備了 `accent-strong`(`#0369a1`,5.93:1)供小字/連結專用。**兩者不能混用**:一般強調色用在按鈕/圖示/大字,加強強調色用在小字/連結文字。

### 底色體系:三層(2026-08-12 新增)

業主回報「純黑或純白背景面積很大,整個視覺壓力也變大」。量過之後確認抱怨的來源不只對話欄:淺色的頁面底 `#f8fafc` 幾乎等於白,而真正的大面積純白是 `--c-surface`(表格資料列與所有 Gradio 區塊);深色則是 `#020617` 近乎純黑。

改成三層結構,淺色與深色共用同一套邏輯:

| 層 | Token | 淺色 | 深色 | 鋪在哪 |
| :--- | :--- | :--- | :--- | :--- |
| 底 | `--c-bg` | `#f3f8fd` | `#0b1424` | 頁面、canvas、導覽列、對話欄 |
| 浮起物 | `--c-bubble-bg` | `#ffffff` | `#172136` | 對話氣泡,比底亮一階 |
| 資料紙張 | `--c-surface` | `#ffffff` | `#172136` | 表格資料列、卡片、輸入框、Gradio 區塊 |

兩個判斷準則:

- **淺色的資料紙張維持純白**,不跟著染藍。75 項指標表格是資料密集區,白底的可讀性優先於「消滅所有純白」;染藍的是它下面那層底,白色表格因此變成浮在淡藍上的紙張,層次比原本白疊白更清楚。
- **氣泡比底亮一階、浮在上面**,深淺一致。深色本來就是這個關係(原 `#0d1526` 浮在 `#020617` 上),這輪讓淺色跟上,兩個主題不再各講各的。近白區間的填色差本來就只有一階,邊界靠細線邊框補(見下)。

### 邊界與分隔線:兩級線條(2026-08-12 新增)

WCAG 1.4.11 對「UI 元件與圖形物件的邊界」要求 3:1,這條在淺色模式一直沒被滿足:`--c-border`(`rgba(2,132,199,.16)`)疊在白/近白底上只有約 1.2:1,區塊外框、水平分界線、表格框線在淺色模式等於看不見(深色模式當初已針對 `.app-header`、欄邊框、標題底線個別補成 `rgba(148,163,184,.28)`,淺色端從沒補)。業主 2026-08-12 回報「淺色版看不清楚區塊的顏色」即為此。

線條因此分兩級,新增元件時二選一,不要再直接用 `--c-border` 畫可見邊界:

| Token | 用途 | 淺色 | 深色 |
| :--- | :--- | :--- | :--- |
| `--c-border-strong` | 結構線:區塊外框、欄位分隔、頂部列底線、標題底線、`hr`、表格外框與表頭分界 | `rgba(2,132,199,.30)`(= `button.secondary` 的邊框色,對底約 1.46:1) | `rgba(148,163,184,.28)` |
| `--c-divider` | 細線:表格資料列之間這種密集重複的線 | `#a9cbe8` | `rgba(148,163,184,.18)` |
| `--c-bubble-border` | 對話氣泡邊框(獨立一級,兩個主題實測對齊 1.61:1) | `#a9cbe8` | `rgba(148,163,184,.26)` |
| `--c-input-border` | 輸入框邊界 | `#7fb0d9`(約 2.0:1) | `rgba(14,165,233,.15)` |

密集重複的線刻意不拉到 3:1:整張資料表如果每一條線都是結構線強度,會變成網格紙、反而更難讀,1.4.11 的門檻也只針對「識別元件所必需」的邊界。

**淺色線條一律走 sky 相,不用中性灰藍。** 中間版本曾用 `#a3aebd`/`#b6c0cd`(同樣的亮度級距、但偏灰),配上淡藍底會顯灰顯髒,業主明確要求「維持亮麗的淺藍」。換色相時**亮度級距不動**,手感才不會又跑掉。

**結構線最終落在 1.46:1,是連續三輪往柔和方向調的結果,已明確離開 1.4.11 的 3:1 門檻。** 沿革:`#7c8798`(3.64:1,符合規範)→ 業主嫌太搶眼改 `#a3aebd`(2.25:1)→ 嫌偏灰改 sky 相 `#6ba3d6`(2.3:1)→ 業主指定「跟『重新選擇研究情境』按鈕的邊框同一個色」,定案 `rgba(2,132,199,.30)`。

這是業主拍板的視覺取捨,不是疏忽:**線條顏色從此與 `button.secondary` 共用同一個值**,全站次要邊界只有一個來源,改一處全站跟著走。文字對比完全不受影響,仍全數通過 AA。日後若無障礙稽核要求嚴格達標,把這個 token 換成同色相但更深的 sky 值即可,19 處線條會一起跟上。

`--c-border` 本身維持原值,因為它同時餵給 `--block-border-color` 與 `--border-color-primary` 兩個 Gradio 原生元件變數,改動會牽連一整批沒盤點過的元件。

### 對話氣泡(2026-08-12 更新)

淺色氣泡原本是白底無框,疊在對話欄底 `#f8fafc` 上只有 1.07:1。定案改成 **填色+淡邊框**:底色 `--c-bubble-bg`(淺色 `#ffffff`、深色 `#172136`),外加 1px `--c-bubble-border`。氣泡比欄底亮一階、浮在上面(見上方「底色體系」)。

邊框比結構線明顯,因為填色差只有一階(白氣泡疊 `#f3f8fd` 只有 1.07:1),邊界全靠它。**「深淺一致」是用量測對齊的,不是兩邊各挑一個看起來順眼的值**:淺色 `#a9cbe8` 疊白 1.69:1、深色 `rgba(148,163,184,.26)` 疊 `#172136` 1.61:1。

這裡踩過一次坑值得記下來:中間版本讓氣泡邊框共用 `--c-divider`,淺色值卻是對著「深色結構線那一級(alpha `.28`)」挑的,而深色 `--c-divider` 其實是更淡的 `.18`——結果深色氣泡邊框只有 1.37:1、淺色 1.61:1,兩個主題看起來不一樣。**跨主題要對齊的視覺元素,就給它自己的 token,不要借用剛好數值接近的鄰居。**

可見邊界畫在內層 `.md`,不是外層 `.message`(量測時要認這層)。使用者訊息與 AI 訊息共用同一層,目前兩側同款氣泡、只靠左右對齊區分,是 2026-08-12 業主確認過的現況。

### 漸層文字與漸層背景的已知踩坑

`background: linear-gradient(...)` 簡寫在這版瀏覽器會把 `background-image` 這個子屬性解析成空值(實測用 devtools 讀 `getPropertyValue('background-image')` 回傳空字串),視覺上等於文字消失。**規則:漸層一律寫 `background-image: linear-gradient(...)` 這個子屬性本身,不用 `background` 簡寫**。全站三處漸層文字/按鈕(`.app-header .title-line`、`.homepage-hero h1`、CTA 按鈕)都遵守這條。

判斷漸層文字的 WCAG 對比度時,不能用 `getComputedStyle().color`(漸層文字用 `-webkit-text-fill-color:transparent`,會讀到透明),要用截圖取樣實際渲染像素計算。

---

## 字體

- **英文/數字**:Crimson Pro(學術感、研究工具調性),標題與強調數字用
- **中文本文**:Noto Sans TC(維持業主原設定;Stitch 等工具的字體庫多為西文導向、不含中文字體,中西混搭是人工判斷後的決定,非工具庫建議)
- **UI 介面文字**:Inter(Gradio 主題 `font=` 設定,中文 fallback 到 Noto Sans TC)
- **等寬/資料**:JetBrains Mono(指標數值、程式碼片段)

### 字級規範

全站基礎內文字級統一 `0.875rem`(14px),**唯一來源是 CSS 變數 `--font-size-base`,任何內文字級改動都要引用這個變數,不允許手寫 rem 數值**(2026-07-24 曾經手寫 `.88rem` 被業主當場糾正,需求文件第十一節的既有規範)。標題、步驟列 pill、小徽章這類刻意做出層級差異的地方例外,可以有自己的固定字級(見 YAML `typography.scale`)。

---

## 造型與間距

- **圓角**:卡片類 14–16px、按鈕/輸入框 9–10px、徽章/pill 20px(全圓)
- **陰影**:僅用在懸浮/強調狀態(卡片 hover、彈窗、抽屜),靜態卡片不加陰影——2026-07-23 業主實機回饋「原本的強調色外框+大範圍陰影+漸層底色讓卡片像浮起來的彈窗」,改成扁平化:外框、陰影、底色拿掉,只留圓角與內距維持呼吸空間;彈出動畫保留(負責「這塊剛出現」的狀態提示,跟視覺重量無關)
- **版面比例**:三欄式工作區 `nav-col : chat-col : canvas-col = 2 : 3 : 7`,寬螢幕滿版貼齊左右邊界(不做置中留白),首頁例外維持 `max-width: 50rem` 置中,避免寬螢幕上被拉滿版
- **結果頁內容順序**:標題 → 圖表/表格 → 下載按鈕,六個結果頁一致(2026-08-12 業主要求把「語言指標分析」的下載從頁面頂部改回最下方,統一順序)
- **表單欄位不包卡片**(2026-08-12):語料說明表單的欄位一律「小標題+內容直接攤開」,不要 Gradio 自動包的白色 `.form` 卡片。Gradio 這層卡片是兩件事疊起來的——`.form` 出框線、內層 `.block`/`fieldset` 出白底與內距——兩層都要清掉才會消失。內距清掉之後欄位左緣自然對齊小標題與下方表格。另外 `.form` 的 `align-items` 要改 `flex-start`:預設的 `stretch` 會把每個欄位的高度鎖成整列高度,而那個列高不含欄位標籤,radio 這種「標籤在上、選項在下」的元件會被裁掉一截
- **欄位標籤只有一種樣式**(2026-08-12):`.7rem`/`700`/`--c-text-muted`。這是 Gradio 給文字欄位標籤的既有值,不是另訂的字級,所以不套 `--font-size-base`(標籤比內文小一階是這張表單本來的層級設計)。radio 的 `fieldset` 標籤走 Gradio 另一條規則、預設是一般大小的深色字,要另外對齊,否則同一張表單裡兩種欄位的標籤長得不一樣
- **表格不自己出捲軸**(2026-08-12):dataframe 的內部容器一律 `scrollbar-width: none`。Gradio 預設 `max-height: 500px` + `overflow-y: auto`,列多時捲軸會佔掉 10px 版面寬度,表格因此比外框窄一截,右側露出一條空白帶,看起來像最後一欄被切掉(業主回報過)。滾輪/觸控/鍵盤照常可捲。**不要改用移除 `max-height` 的做法**:這個表格是虛擬捲動,靠自身捲動位置決定渲染哪些列,移除後會變成「保留整表高度卻只渲染 1 列」的空表。也**不要用 `::-webkit-scrollbar`**:Gradio 在該元件設了標準屬性 `scrollbar-width`/`scrollbar-color`,Chrome 見到標準屬性就會忽略 legacy 偽元素
- **導覽列末項**:「使用說明」固定在左側導覽列最後一項(技術執行紀錄下方,用分隔線隔開),與分析進度無關、全程可見;它是下載動作不是頁面,不參與導覽列的 active 狀態
- **基準留白**:導覽列與輸入框四邊留白 16px 為基準,其餘區塊(對話氣泡、Canvas 內容區)對齊同一基準線,避免同一頁面出現兩種留白邏輯

---

## 圖示

Heroicons outline 風格 SVG,24×24、`stroke-width="1.75"`、`fill="none"`,手繪 path(非套件逐點對照)。業主明確否決沿用專案既有的 emoji 慣例。

**新增導覽圖示時要量「實際上色範圍」,不能只看 viewBox。** 所有圖示的 viewBox 都是 24×24、容器都是 22px,但畫出來的大小取決於 path 撐到哪裡。現有導覽圖示的上色範圍多落在 18×16 附近(`folder`/`chat-bubble`/`table-cells` 皆是),2026-08-12 新增的 `book-open` 初版只有 16×15,肉眼看得出比別人小一圈、卻不容易指出原因,是截圖逐一量非背景像素才確認。新增圖示畫完後量一次,對齊 18×16 這個量體。

### AI 頭像(2026-08-12 新增)

AI 訊息左側的頭像是 28px 圓形,紫→藍漸層底+白色四角星(sparkle),漸層與標題字共用同一條 heading-gradient。

實作分工是刻意的:**漸層畫在 CSS 的 `.avatar-container`,SVG 檔(`assets/icons/ai-avatar.svg`)只放白色 glyph**。原因是 `gr.Chatbot(avatar_images=...)` 是 Python 端參數,同一個檔案沒辦法隨明暗主題換色;把漸層交給 CSS 才吃得到 `--c-accent-strong / --c-accent2 / --c-accent3` 這組會跟著主題切換的 token。白色圖形疊在任一主題的漸層上都夠亮。頭像寬高固定並設 `flex: none`,避免重蹈「圖示被 flex 壓縮」的舊坑。

glyph 規格與其他導覽圖示不同,另立一套:**兩顆星都是實心 `fill`、不描邊**,path 撐到接近滿版(大星 x3.9–17.1/y2.2–17.2、小星 x15.3–21.5/y13.6–20.7),`img` 內距 3px,圖案在 28px 圓內的可用範圍 22px。實心與放大要一起做才有效——只縮內距是把細線稿放大,只放大 path 又會被內距框住。

深色模式處理:`gr.Button(icon=...)` 在這版 Gradio 渲染成 `<img>` 標籤而非行內 `<svg>`,CSS `currentColor` 對它無效,因此淺色版固定 stroke 顏色,深色模式改用 `filter: grayscale(1) invert(1) brightness(1.55) contrast(.85)` 反相逼近深色版文字色,不另外準備第二份深色 SVG 檔案。需要 `currentColor` 正常運作(例如收合鈕、明暗切換鈕這類不需要 Python `.click()` 的裝飾性圖示)則改用 `gr.HTML()` 內嵌行內 SVG。

---

## 動效

- 對話訊息串流顯示、打字中三點跳動動畫(AI-Native UI 語彙)
- 運算中狀態用轉圈動畫,避免使用 Gradio 預設的 `.generating` 呼吸藍框(視覺上與本站的呼吸動畫語彙衝突,需求文件已列為既有修正項)
- 手機抽屜/確認彈窗用 `transform` 位移或 overlay 淡入,不做過度炫技的轉場

---

## 已知技術限制(設計時務必納入考量)

- **沒有測試套件**,任何視覺改動都要手動走一次流程驗證,純美感/主觀判斷的改動先截圖給業主看過再定案
- Gradio 6 的 CSS pipeline 會自動幫 `css=`/`css_paths=` 內的所有選擇器加上 `.gradio-container .contain` 前綴,`:root` 選擇器包進 `@media` 區塊後永遠無法命中;系統深色模式的 token 覆寫因此改放在 `app.launch(head=...)` 注入的獨立 `<style>`(見 `_DARK_MODE_HEAD_CSS`),繞過這個轉換管線
- 部分 Gradio 原生元件(`gr.Column(visible=False)` 等)在特定時序下會有可見性狀態的非直覺行為,改版面配置前建議先查 `app_agent.py` 裡累積的相關踩坑註解,避免重踩

---

*本文件為本次 UI/UX 改版團隊(吳千卉)依既有實作彙整,非全新設計提案。配色與整體風格方向延續業主原有版本,未來調整請同步更新本文件與 `assets/app.css`,保持兩者一致。*