minchun's picture
Claude Sonnet 5
補齊「使用手冊」→「使用說明」更名的實際內容改動
9615d53
Raw History Blame Contribute Delete
142 kB
/* ═══════════════════════════════════════════════════════════════
重譯文本分析系統 Agent — Refined Dark Academic Dashboard
Palette: Sky-500 (#0ea5e9) + Indigo-400 (#818cf8) on Slate-950
Typography: Inter (UI) + JetBrains Mono (data) + Crimson Pro (headings)
WCAG AA compliant (4.5:1+)
═══════════════════════════════════════════════════════════════ */
@import url('https://fonts.googleapis.com/css2?family=Crimson+Pro:wght@400;600;700&display=swap');
/* ── 1. TOKENS & RESET ─────────────────────────────────────────── */
/* 預設淺色;系統設定為深色模式時(或明確帶 .dark class)自動切換深色 */
/* 底色體系(2026-08-12 三修):千卉回報「純黑或純白背景面積很大,視覺壓力
也變大」。量過三層才確定抱怨的來源不只對話欄——淺色的頁面底 #f8fafc 幾乎
等於白,而真正的大面積純白是 --c-surface(表格資料列+所有 Gradio 區塊);
深色則是 #020617 近乎純黑。改成三層結構,淺深共用同一套邏輯:
底(--c-bg) 淡藍/帶藍深色,鋪滿頁面、canvas、導覽、對話欄
浮起物(--c-bubble-bg)比底亮一階,對話氣泡
資料紙張(--c-surface)淺色維持純白當表格底(資料密集區可讀性優先),
深色跟著抬亮
淺色氣泡「比欄底亮一階、浮在上面」是千卉選的方案 2,剛好與深色本來就有的
關係(氣泡比欄底亮)一致,兩個主題不再各講各的。 */
:root {
--c-bg: #f3f8fd;
--c-surface: #ffffff;
--c-surface2: #eef5fc;
--c-surface3: #ffffff;
--c-surface3-border: #e2e8f0;
--c-border: rgba(2,132,199,.16);
--c-border-hover: rgba(2,132,199,.35);
/* 2026-08-12(業主回報淺色對比不足):淺色的 --c-border 疊在白/近白底上
只有約 1.2:1,遠低於 WCAG 1.4.11 對「UI 元件邊界」要求的 3:1,區塊外框
跟水平分界線在淺色模式下等於看不見(深色當初已針對 .app-header/欄邊框/
標題底線個別補過 rgba(148,163,184,.28),淺色從沒補)。這裡改成兩級線條,
之後新增元件挑其中一級,不要再直接用 --c-border 畫可見邊界:
--c-border-strong 結構線(區塊外框、欄位分隔、水平分界)
--c-divider 細線(表格資料列線、對話氣泡邊框)
深色值直接沿用原本散在各選擇器的硬寫值,深色外觀零改變。
2026-08-12 二修(千卉看實機截圖後回饋「線條對比好高、蠻醜」):
結構線從 #7c8798(對白 3.64:1)柔化到 #a3aebd(2.25:1)。取捨要
講明白——2.25:1 沒有達到 WCAG 1.4.11 對「UI 元件邊界」的 3:1 門檻,
是刻意的視覺決定;但比改版前的 1.2:1 高近一倍,業主回報的「看不清楚
區塊」在這個亮度差已經解決。文字對比不受這條影響,仍全數通過 AA。
細線 --c-divider 淺色 #b6c0cd 對氣泡底 1.64:1、對欄底 1.76:1,
跟深色 rgba(148,163,184,.28) 疊 #0d1526 算出來的 1.67:1 幾乎相同,
這就是「深淺兩個模式的氣泡邊框看起來一樣淡」的依據(用量測對齊,
不是各自挑一個看起來順眼的值)。
2026-08-12 四修(千卉指定:邊線與水平線改成跟「重新選擇研究情境」
按鈕的邊框同一個色):結構線淺色改成 rgba(2,132,199,.30),也就是
button.secondary 用的那個值,全站線條與次要按鈕從此同一個來源。
疊在新的淺底 #f3f8fd 上約 1.46:1,比三修的 2.3:1 再淡一階——這是
第三次往柔和方向調,已經明確離 WCAG 1.4.11 的 3:1 很遠,屬業主拍板
的視覺取捨(文字對比不受影響,仍全數通過 AA)。
2026-08-12 三修(千卉:淺色系的邊框不要太深,要維持亮麗的淺藍):
#a3aebd/#b6c0cd 是中性灰藍,配上這輪的淡藍底會顯灰、顯髒。改走
sky 相:結構線 #6ba3d6、細線 #a9cbe8、輸入框 #7fb0d9。**亮度級距
不動**(結構線仍約 2.3:1、氣泡邊框仍約 1.6:1,跟前一輪柔化後的手感
相同),換的是色相不是輕重——這樣既維持千卉要的柔和度,也讓線條跟
底色同屬一個藍色家族。
--c-border 本身維持原值不動:它還餵給 --block-border-color/
--border-color-primary 這兩個 Gradio 原生元件用的變數,動了會牽連
一整批沒盤點過的元件。 */
--c-border-strong: rgba(2,132,199,.30);
--c-divider: #a9cbe8;
/* 對話氣泡邊框獨立一個 token:前一版讓它共用 --c-divider,結果深淺兩邊
對不齊——淺色的 #a9cbe8 是對著「結構線那一級的深色值(alpha .28)」
挑的,但深色的 --c-divider 其實是更淡的 .18,實測深色氣泡邊框只有
1.37:1、淺色 1.61:1,兩個模式看起來不一樣。分成獨立 token 之後兩邊
各自對齊到 1.61:1,--c-divider 也回歸單一用途(表格資料列細線)。 */
--c-bubble-border: #a9cbe8;
/* 表格表頭底色與對話氣泡底色改成獨立 token(原本借用 --c-surface2/
--c-surface):淺色時兩者疊在自己的鄰接底色上都只有 1.03–1.07:1,
業主回報「表頭跟文字黏在一起」「看不清楚區塊的顏色」就是這個。 */
--c-thead-bg: #d8e8f6;
--c-bubble-bg: #ffffff;
--c-accent: #0284c7;
--c-accent-strong: #0369a1;
--c-accent2: #4f46e5;
--c-accent2-strong: #4338ca;
--c-accent3: #9333ea;
--c-accent-rgb: 2,132,199;
--c-accent2-rgb: 79,70,229;
--c-accent3-rgb: 147,51,234;
--c-bg-rgb: 255,255,255;
--c-text: #1e293b;
--c-text-soft: #334155;
--c-text-muted: #475569;
--c-text-subtle: #64748b;
--c-success: #047857;
--c-success-rgb: 4,120,87;
--c-warn: #b45309;
--c-user-bg: #dcfce7;
--c-user-border: #86efac;
--c-user-text: #166534;
--c-code-bg: #e0f2fe;
--c-code-text: #b45309;
--c-input-bg: #ffffff;
/* 2026-08-12:原本 rgba(2,132,199,.22) 疊白只有約 1.3:1,輸入框邊界同樣
受 1.4.11 的 3:1 規範;語料 metadata 表單「欄位看不清楚」有一半是這條。 */
--c-input-border: #7fb0d9;
--c-link-hover: #075985;
--c-scrollbar: #cbd5e1;
--c-scrollbar-hover: #94a3b8;
--font-size-base: 0.875rem;
--body-background-fill: var(--c-bg) !important;
--background-fill-primary: var(--c-surface) !important;
--background-fill-secondary: #dfeaf6 !important;
--block-background-fill: var(--c-surface) !important;
--block-border-color: var(--c-border) !important;
--border-color-primary: var(--c-border) !important;
--color-accent: var(--c-accent) !important;
--color-accent-soft: rgba(2,132,199,.08) !important;
--body-text-color: var(--c-text) !important;
--block-label-text-color: var(--c-text-muted) !important;
--block-title-text-color: var(--c-text-soft) !important;
--input-background-fill: var(--c-input-bg) !important;
--input-border-color: var(--c-input-border) !important;
--button-primary-background-fill: var(--c-accent) !important;
--button-primary-text-color: #fff !important;
--shadow-drop: 0 1px 3px rgba(15,23,42,.10) !important;
}
/* 注意:系統深色模式的 @media 覆寫改放在 launch(head=...) 注入的獨立 <style>(見 _DARK_MODE_HEAD_CSS),
因為 Gradio 會把 css= 內的選擇器自動加上 .gradio-container .contain 前綴,
:root 一旦被包進 @media 只會留下加了前綴的版本,而 ":root" 不可能是任何元素的子孫,等於永遠不會命中。 */
/* 明確以 ?__theme=dark 網址參數強制深色時 */
.dark {
--c-bg: #0b1424;
--c-surface: #172136;
--c-surface2: #1b2740;
--c-surface3: #1e293b;
--c-surface3-border: #334155;
--c-border: rgba(14,165,233,.11);
--c-border-hover: rgba(14,165,233,.30);
/* 深色的結構線沿用原本散在 .app-header/.nav-col/.chat-col/標題底線
各處的硬寫值 rgba(148,163,184,.28)(已通過實機檢視),token 化之後
那些個別覆寫規則就整組刪掉了,避免同一個色值散落六處。 */
--c-border-strong: rgba(148,163,184,.28);
--c-divider: rgba(148,163,184,.18);
/* .26 是實測對齊值:疊在深色氣泡底 #172136 上等於 1.61:1,跟淺色
#a9cbe8 疊 #f5faff 的 1.61:1 相同(見 :root 的同名 token 註解)。 */
--c-bubble-border: rgba(148,163,184,.26);
--c-thead-bg: #1e2a42;
--c-bubble-bg: #172136;
--c-accent: #0ea5e9;
--c-accent-strong: #38bdf8;
--c-accent2: #818cf8;
--c-accent2-strong: #a5b4fc;
--c-accent3: #c084fc;
--c-accent-rgb: 14,165,233;
--c-accent2-rgb: 129,140,248;
--c-accent3-rgb: 192,132,252;
--c-bg-rgb: 9,14,26;
--c-text: #e2e8f0;
--c-text-soft: #cbd5e1;
--c-text-muted: #94a3b8;
--c-text-subtle: #475569;
--c-success: #34d399;
--c-success-rgb: 52,211,153;
--c-warn: #fbbf24;
--c-user-bg: rgba(34,197,94,.18);
--c-user-border: rgba(34,197,94,.42);
--c-user-text: #bbf7d0;
--c-code-bg: rgba(14,165,233,.12);
--c-code-text: #fbbf24;
--c-input-bg: #1a2540;
--c-input-border: rgba(14,165,233,.15);
--c-link-hover: #7dd3fc;
--c-scrollbar: #1e3a5f;
--c-scrollbar-hover: #334d6e;
--body-background-fill: var(--c-bg) !important;
--background-fill-primary: var(--c-surface) !important;
--background-fill-secondary: #080f1c !important;
--block-background-fill: var(--c-surface) !important;
--block-border-color: var(--c-border) !important;
--border-color-primary: var(--c-border) !important;
--color-accent: var(--c-accent) !important;
--color-accent-soft: rgba(14,165,233,.08) !important;
--body-text-color: var(--c-text) !important;
--block-label-text-color: var(--c-text-muted) !important;
--block-title-text-color: var(--c-text-soft) !important;
--input-background-fill: var(--c-input-bg) !important;
--input-border-color: var(--c-input-border) !important;
--button-primary-background-fill: var(--c-accent) !important;
--button-primary-text-color: #fff !important;
--shadow-drop: 0 1px 3px rgba(0,0,0,.5) !important;
}
/* width 一定要明確指定,否則容器會依內容伸縮(本來就沒設定過,這是造成
對話視窗/按鈕/上傳卡片寬度在不同狀態間跳動不一致的根本原因)。
原本 max-width:1680px + margin:0 auto 是舊版單欄/二欄置中版面留下的
設定;Phase 8 三欄式工作區(nav-col/chat-col/canvas-col)改成滿版
貼齊左右邊界(千卉要求 2026-07-22),改成 width:100%、拿掉
max-width 上限與左右 padding。首頁 .homepage-col 自己有獨立的
max-width:50rem + margin auto 置中,不受這裡影響,寬螢幕上還是會維持
舒適閱讀寬度、不會被拉滿版。 */
.gradio-container {
width: 100% !important;
max-width: 100% !important;
margin: 0 !important;
padding: 0 !important;
}
/* .gradio-container 內部還包了一層 Gradio 自己的 .app(class="main fillable app")
wrapper。原本以為是它的左右 padding 造成貼不到邊界,歸零後留白仍在;
千卉 2026-07-23 截圖回報四邊仍有留白,重新用 devtools 沿著
.main-layout-row 往上逐層量 computed style 才找到真因:.app 被 Gradio
設了 `max-width:1280px` 搭配 auto 置中的 margin-left(量到 77.5px =
(1435-1280)/2,抓包抓得很準),是 margin 不是 padding。真正修法是拿掉
max-width 限制、margin 歸零,padding 那條規則留著無害但其實沒解決過
問題。
後續(同日):千卉截圖標出頂部/底部還有大片空白,實測 .app 本身還有
`padding:16px 0px`(上下各16px,之前只清過左右)、.gradio-container
也還有 `padding:0 0 24px`(底部24px)——這兩個之前為了「左右貼齊」
只處理過左右方向,上下方向從沒動過,這次一併歸零。 */
.app {
padding: 0 !important;
max-width: 100% !important;
margin-left: 0 !important;
margin-right: 0 !important;
}
/* 2026-07-23:千卉回報頂部列(D)沒有真正固定置頂、底部「透過API使用/
設定」(G)也沒有固定置底。實測用 devtools 沿著 <footer> 往上量整條
祖先鏈才發現:Gradio 原生把 gradio-app/.gradio-container/.app/
.wrap 這四層全部設成 display:flex; flex-direction:column,.wrap 底下
剛好就是「主內容(.contain)+footer」兩個 flex 子項——結構本來就是
對的,問題是這條鏈的高度全部用 auto(依內容撐高),不是真正鎖定
100vh。內容只要比視窗高一點點(哪怕只多15px),整個外層文件就會
跟著捲動,把「理論上該固定」的頂部列/底部連結一起拖走——這才是
「沒有真正置頂/置底」的根本原因,不是 sticky/fixed 沒設對。
修法:把這條 Gradio 原生就是 flex column 的鏈路鎖死在 100vh,讓
.contain(真正裝我們 gr.Blocks 內容的那層)變成唯一會捲動的區域,
footer 用 flex-shrink:0 釘在最底。這樣完全不用碰 position:sticky/
fixed(8-B 已經踩過 sticky 在 Gradio wrapper 裡貼不住的坑),延續
這個檔案「結構性分離,不依賴 sticky」的既有原則,只是這次分離的
層級是整個頁面殼,不只是單一元件。 */
html, body {
height: 100% !important;
margin: 0 !important;
}
gradio-app {
display: block !important;
height: 100vh !important;
overflow: hidden !important;
}
.gradio-container {
height: 100vh !important;
overflow: hidden !important;
}
.app {
height: 100% !important;
overflow: hidden !important;
}
.wrap {
height: 100% !important;
overflow: hidden !important;
}
.contain {
flex: 1 1 auto !important;
min-height: 0 !important;
overflow-y: auto !important;
}
/* 2026-07-23:千卉確認頂部/底部貼齊後,決定整個 footer(透過API使用/
設定連結列)不要了,直接拿掉,不只是隱藏內容——footer_links 參數也
一併清空,避免 Gradio 還是生出一個空的 <footer> 佔用 flex 高度。 */
footer {
display: none !important;
}
/* 千卉截圖回報頂部列還是沒有真正貼齊頂端,量到 main-layout-row 距離
.contain 頂端還有 72px 空隙——追出來是 .nav-col/.chat-col/.canvas-col
原本寫死的 `calc(100vh - 150px)` 這個 magic number,是照舊版「頁面
周圍還有一堆 padding/margin」的年代校準出來的高度預算。這次把
.app/.gradio-container 的上下 padding 都清空、footer 也鎖定
flex-shrink:0 之後,實際可用空間比 150px 這個預留值還多,多出來的
空間沒地方去,就變成三欄跟外層容器之間看得到的空隙。
修法:不要再猜一個固定 px 預算,改成整條鏈都用 flex 100% 往下傳,
有多少空間就填多少,不會再跟實際版面脫節。.contain 底下只有一個
直屬子元素(Gradio Blocks 的根 Column),這裡讓它 flex 撐滿
.contain,裡面的 main-layout-row 再 flex 撐滿它,nav-col/chat-col/
canvas-col 三欄改成 height:100%(相對 main-layout-row 的高度),
不再各自寫死 calc(100vh - 150px)。 */
.contain > .column {
display: flex !important;
flex-direction: column !important;
height: 100% !important;
min-height: 0 !important;
}
.main-layout-row {
flex: 1 1 auto !important;
min-height: 0 !important;
}
*, *::before, *::after { box-sizing: border-box; }
/* 全域安全網(陷阱 13 第 3 點):Gradio 的 markdown 渲染統一包在
<span class="md ...">,預設是 inline 卻常常裝 block 層級的 <p>/<ul> 等,
會在內容最前面產生依 span 自己字級算出的隱形空隙。這裡統一改成 block,
避免以後在還沒踩過的地方(如新的 gr.Markdown 元件)又重複這個問題;
字級仍留給各自的選擇器決定,這裡不覆寫 font-size。 */
.md { display: block !important; }
/* ── 2. HEADER(卡片化,2026-07-23 精簡成單行)───────────────────── */
/* 2026-07-23 千卉實機回饋:原本整塊是「有外框+漸層底色+圓角」的卡片,
看起來像一個獨立區塊漂在畫面上方。改成完全扁平:拿掉外框、底色、圓角,
只留一條下邊框當工作區的水平分界;產品名與步驟列改上下兩行。 */
.app-header {
display: flex;
flex-direction: column;
align-items: flex-start;
flex-wrap: nowrap;
gap: .9rem;
text-align: left;
padding: 1.5rem 1.25rem 1.1rem;
position: relative;
margin: 0 0 1.25rem;
border: none;
/* 頂部標題與內容的分界線,用結構線 token(--c-border-strong)。
2026-08-12 之前這裡是 --c-border 加上「.dark .app-header 個別覆寫成
rgba(148,163,184,.28)」的組合(深色太淡的 #17),淺色端則一直沒補、
業主回報看不清楚。兩邊的值現在都收進 --c-border-strong,這裡不再需要
任何深色個別覆寫。
── 保留的踩坑記錄(雖然這批 .dark 規則已刪,通則仍成立)──
Gradio 會自動幫 CSS 選擇器加容器前綴,這個機制處理「.dark A, .dark B」
這種逗號並列、且每個分支都以 .dark 開頭的選擇器時有 bug:只有第一個
分支前綴正確,後面每個分支都被錯誤地多插一次 .dark(變成需要兩層
.dark 祖先才能匹配,永遠選不到東西,等於整條規則除了第一分支都是死的)。
已在 Gradio 6.20.0 實測。之後任何 .dark 開頭的規則要並列多個選擇器,
都要拆成一條一個選擇器,不要用逗號合併。 */
border-bottom: 1px solid var(--c-border-strong);
background: none;
}
/* 注意:這三個選擇器的漸層文字底色(background-image: linear-gradient(...var()...))
改在 launch(head=...) 注入的 <style> 裡設定(見 _DARK_MODE_HEAD_CSS)——
這裡只保留其餘不受影響的樣式(字型、間距等)。
踩坑(2026-07-22 千卉實機回報,首頁與 Canvas 標題皆會發生,須反白選取
才看得到):真正的根因不是重繪/合成層問題(曾誤判為 Chromium 動態內容
repaint bug,試過 transform:translateZ(0) 無效,已移除),而是瀏覽器
對 `background` 這個簡寫屬性(shorthand)解析失敗——當值是
`linear-gradient(...多個裸 var() 當色標...)` 且整條宣告掛
`!important` 時,瀏覽器會把簡寫展開成多個子屬性(background-image/
background-color/...),但 background-image 這個子屬性最終解析成空值
(用 devtools 讀 CSSStyleDeclaration 已直接證實:getPropertyValue
('background-image') 回傳空字串,即使 getPropertyPriority 顯示
'important'),導致完全沒有漸層可畫、文字又是 transparent,看起來就是
整個標題消失;反白選取會強制瀏覽器重繪,這次改用其他路徑重新算出
實際漸層才「顯現」。修法:不要用 `background` 簡寫,直接寫
`background-image: linear-gradient(...) !important;` 這個子屬性本身,
跳過簡寫展開的解析路徑——已用瀏覽器 devtools 即時修改驗證過,改長寫法
後漸層立即正常顯示。*/
.app-header .title-line {
font-family: 'Crimson Pro', 'Noto Serif TC', Georgia, serif !important;
font-size: 1.3rem !important;
font-weight: 700 !important;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
margin: 0 !important;
letter-spacing: -.01em;
line-height: 1.2 !important;
white-space: nowrap;
flex: none;
}
.app-header .title-line .en {
font-size: 1.05rem !important;
font-weight: 600 !important;
}
.badge {
display: inline-flex; align-items: center; gap: 4px;
padding: 2px 9px;
border-radius: 20px;
font-size: 0.70rem;
font-weight: 700;
letter-spacing: 0.05em;
margin: 0 3px;
vertical-align: middle;
}
.badge-llm1 {
background: rgba(var(--c-accent-rgb),.10);
color: var(--c-accent-strong);
border: 1px solid rgba(var(--c-accent-rgb),.30);
}
.badge-llm2 {
background: rgba(var(--c-accent2-rgb),.10);
color: var(--c-accent2-strong);
border: 1px solid rgba(var(--c-accent2-rgb),.30);
}
/* step indicators:2026-07-23 二度調整——header 改成上下兩行(產品名一行、
步驟列一行),所以這裡不再需要 flex:1 去跟標題搶同一行的水平空間,改成
自己佔滿整行寬度。兩行之間的間距一樣交給 .app-header 的 gap,不另設
margin-top,避免兩邊疊加。 */
.app-header .steps {
display: flex; justify-content: flex-start; flex-wrap: wrap;
gap: 4px; margin: 0; width: 100%; min-width: 0;
}
.step-pill {
display: inline-flex; align-items: center; gap: 6px;
padding: 4px 13px;
border-radius: 20px;
font-size: 0.72rem;
font-weight: 600;
background: rgba(var(--c-accent-rgb),.06);
border: 1px solid rgba(var(--c-accent-rgb),.30);
color: var(--c-text-soft);
letter-spacing: .03em;
transition: border-color .2s, background .2s;
}
.step-pill:hover {
background: rgba(var(--c-accent-rgb),.12);
border-color: rgba(var(--c-accent-rgb),.55);
}
.step-pill .step-num {
width: 17px; height: 17px; border-radius: 50%;
background: rgba(var(--c-accent-rgb),.22);
color: var(--c-accent-strong);
display: inline-flex; align-items: center; justify-content: center;
font-size: 0.62rem; font-weight: 800;
}
/* 已完成步驟:綠色實心,取代原本永遠同一個樣子的裝飾用藥丸 */
.step-pill--done {
background: rgba(var(--c-success-rgb),.14);
border-color: rgba(var(--c-success-rgb),.4);
color: var(--c-success);
}
.step-pill--done .step-num {
background: var(--c-success);
color: #fff;
font-size: 0.68rem;
}
/* 目前所在步驟:強調外框 + 持續的呼吸動畫。單一步驟內部可能安靜運算好幾
分鐘(如指標計算),沒有新的 log 出現時畫面會像凍住,加一個不間斷的
輕微動畫給使用者「系統還在動」的持續訊號;respects prefers-reduced-motion
(見全域 REDUCED MOTION 規則,會自動把動畫時長歸零) */
.step-pill--current {
background: rgba(var(--c-accent-rgb),.16);
border: 1px solid var(--c-accent);
color: var(--c-accent-strong);
font-weight: 700;
animation: stepPulse 1.6s ease-in-out infinite;
}
.step-pill--current .step-num {
background: var(--c-accent);
color: #fff;
}
@keyframes stepPulse {
0%, 100% { box-shadow: 0 0 0 0 rgba(var(--c-accent-rgb),.35); }
50% { box-shadow: 0 0 0 5px rgba(var(--c-accent-rgb),0); }
}
/* 執行失敗卡住的步驟:紅色警示樣式,且不套用 stepPulse 呼吸動畫——原本
失敗時圓點會卡在「進行中」動畫永遠閃爍,讓使用者誤以為系統還在運算 */
.step-pill--failed {
background: rgba(220,38,38,.14);
border-color: rgba(220,38,38,.4);
color: #dc2626;
font-weight: 700;
}
.step-pill--failed .step-num {
background: #dc2626;
color: #fff;
}
/* ── 3. TABS ───────────────────────────────────────────────────── */
/* 2026-07-24(E1):這裡原本有一整組 `.tabs > .tab-nav` sticky/背景/hover/
selected 樣式,但這版 Gradio 的實際 DOM 是 `.tabs > .tab-wrapper >
.tab-container`、沒有任何 `.tab-nav` 節點,那組規則從寫下來就是死規則,
已整組移除。 */
/* 2026-07-24(item 1):統計四子頁包在 .tabitem 裡,其 1.5rem 上 padding
讓標題比「語言指標分析」頁(無 tabitem,走 .canvas-result margin)多縮
24px,三頁標題上方留白不一致(實測 idx 42px vs stat 66px)。上 padding
歸零讓統計頁標題對齊 idx;水平與下方 padding 保留。 */
/* 水平 padding 移到 .canvas-result 統一處理(見該處註解),這裡只留下方
間距;上方 0 是為了跟「語言指標分析」頁標題頂端對齊(item 1)。 */
.tabitem { padding: 0 0 1.5rem !important; }
/* 2026-07-24(C1):統計比較分析的四個子頁改由左側導覽列驅動後,Canvas 內
這排分頁按鈕列(敘述統計|差異性分析|…)跟左欄四個項目是同一組選項的兩套
UI,重複。整排在視覺上隱藏,但 gr.Tabs 結構保留——巢狀分頁的切換仍靠 JS 去
點這排按鈕(見 bindResultNav 的 tabButtons.click)。
踩坑(連踩兩種隱藏法):
① display:none —— .click() 完全不觸發切換,這版 Gradio 的 Svelte on:click
對移出渲染樹的元素不反應。
② 標準 visually-hidden(position:absolute + width:1px + clip)—— 也失敗,
但原因不同:把 wrapper 縮成 1px 寬會觸發 Gradio 自己的分頁溢出邏輯,把
四個分頁全塞進 overflow dropdown,可見容器只剩一顆溢出選單鈕,JS 選擇器
(抓可見容器的 > button)就抓不到分頁按鈕了。
正解:必須保留 width:100%,Gradio 才會把四個分頁留在可見容器、不觸發溢出;
只用 height:0 + overflow:hidden + opacity:0 做視覺隱藏(不動寬度、不改
position)。按鈕高度歸零但仍在渲染樹、寬度正常,.click() 照常切換。
(實測:四顆按鈕在位、點第 4 顆→集群分析、點第 1 顆→敘述統計,正確。) */
#canvas_stat .tabs > .tab-wrapper {
height: 0 !important;
min-height: 0 !important;
overflow: hidden !important;
opacity: 0 !important;
margin: 0 !important;
padding: 0 !important;
}
/* ── 4. CHATBOT ────────────────────────────────────────────────── */
.chatbot {
border: 1px solid var(--c-border-strong) !important;
border-radius: 14px !important;
background: var(--c-surface) !important;
}
/* 主對話視窗:下方接技術執行紀錄 Accordion,兩者視覺上合併成一張卡片
(只留上方圓角,底部邊框拿掉,改由 Accordion 頂端的邊框當作分界線) */
/* 2026-07-24(仿 Claude):對話視窗貼齊欄邊——拿掉外框、圓角、白色底,
背景透明吃 chat-col 的底色,只有氣泡本身有底色。 */
.main-chat {
background: transparent !important;
border: none !important;
border-radius: 0 !important;
}
.main-chat .bubble-wrap,
.main-chat div[role="log"] {
background: transparent !important;
}
/* #2 消除重複滾軸:只讓 bubble-wrap 捲動,外層不捲 */
.main-chat [class*="wrapper"],
.main-chat > div > div:not([role="log"]) {
overflow: hidden !important;
}
.main-chat div[role="log"].bubble-wrap {
overflow-y: auto !important;
overflow-x: hidden !important;
}
/* 2026-07-23:.main-chat 外層改用 flex-grow 決定高度(見「5b. COLUMN
LAYOUT」區塊的 5:1 比例設定),中間這層 wrapper 也要跟著撐滿,
高度才會一路傳到 bubble-wrap,捲動區域才會是「剩下的全部空間」
而不是「內容多高就多高」。 */
.main-chat > div,
.main-chat [class*="wrapper"] {
flex: 1 1 auto !important;
min-height: 0 !important;
display: flex !important;
flex-direction: column !important;
}
/* 訊息從頂部開始排列,消除空白 */
div[role="log"].bubble-wrap,
div[role="log"][aria-label="chatbot conversation"],
.main-chat .bubble-wrap,
.bubble-wrap {
flex-direction: column !important;
justify-content: flex-start !important;
align-content: flex-start !important;
align-items: flex-start !important;
min-height: 0 !important;
/* 2026-07-24 千卉:對話列的左右留白對齊導覽列(16px)。原本 .5rem(8px)
讓氣泡比導覽列項目更貼邊,三欄留白不一致。 */
padding: 16px !important;
}
/* 清除 Gradio 在 bubble-wrap 內加的空白 spacer div */
.bubble-wrap > div:empty { display: none !important; }
.bubble-wrap > :first-child { margin-top: 0 !important; padding-top: 0 !important; }
/* ── 隱藏訊息計數氣泡、頁碼、複製按鈕浮層 ── */
.main-chat .count,
.main-chat .message-count,
.main-chat [class*="count"],
.main-chat .page,
.main-chat .page-count,
.main-chat .scroll-hide,
.main-chat .copy-text-button,
.main-chat .copy-btn,
.main-chat button[title*="copy" i],
.main-chat button[aria-label*="copy" i],
.main-chat [data-testid*="copy"],
.main-chat .bubble-wrap > div:last-child > span:only-child {
display: none !important;
}
/* 徹底隱藏對話視窗內所有複製/工具浮層圖示 */
.main-chat .icon-button-wrapper,
.main-chat [class*="icon-button"],
.main-chat .message-buttons,
.main-chat .message-row button,
.main-chat button[title*="複製"],
.main-chat button[aria-label*="複製"] {
display: none !important;
}
/* ── 通用訊息氣泡 ── */
/* 不同時間產生的訊息之間如果間距太小,會分不出是好幾則獨立訊息還是同一則,
拉大訊息列間距讓每則訊息之間有清楚的留白斷開 */
/* Gradio 預設給每個 message-row 左右各留 20px margin(原本應是保留給頭像用的
空間,本專案沒有用頭像),導致 AI 訊息離左邊、使用者訊息離右邊都比預期遠,
這裡歸零讓氣泡貼近對話視窗邊緣。另外 Gradio 還設了 max-width: calc(100% - 60px),
AI 訊息貼左邊不受影響,但使用者訊息靠 margin-left:auto 貼右邊,這 60px
缺口會整個落在最右側,一併覆寫掉 */
.main-chat .message-row {
margin-bottom: 1.1rem !important;
margin-left: 0 !important;
margin-right: 0 !important;
width: 100% !important;
max-width: 100% !important;
align-self: stretch !important;
}
/* 2026-07-24(千卉回報氣泡留白遠超過 16px):這一層是「看不見」的外框,
真正畫出氣泡底色與圓角的是內層 .md(見下方註解與本專案筆記)。它原本的
水平 padding 1rem 會把可見氣泡再往內推 16px——bubble-wrap 已給 16px,
疊起來可見氣泡實際落在 32px。水平內距歸零,可見氣泡才真的貼在 16px;
氣泡內的文字留白由 .md 自己的 padding 提供。 */
/* 2026-07-30(2-C 第 6 項):對話氣泡文字改 12px(千卉指定)。字級固定寫在
這裡跟下面 .message-row.bot .message/.message p/.message .md 四個地方
——不是重複設定,是 Gradio 這個氣泡結構本來就有巢狀 <p>/<span class="md">
各自帶自己的字級(見下面兩則舊註解,實測不會沿用外層容器字級),四層
都要一起改成 12px,畫面上才會真的變小;只改最外層這條,實際顯示的文字
仍會被內層 .p/.md 的 var(--font-size-base) 蓋掉,完全看不出效果。 */
/* 2026-07-31(Batch G 第 3 項):業主回報 12px 太小,改 14px。四處連動改,
理由同上——只改一處字級不一致,這裡跟下面 .message-row.bot .message/
.message p/.message .md 四處都要同步改成 14px。 */
.main-chat .message-row .message {
border-radius: 10px !important;
font-size: 14px !important;
line-height: 1.7 !important;
padding: 0 !important;
width: fit-content !important;
max-width: 90% !important;
transition: box-shadow .2s !important;
}
/* Gradio 在氣泡外層又包了一層同樣帶 .message class 的巢狀 div(非語意重複,
是它自己的 panel-full-width 包裝),上面這條規則會連內層也命中,
造成 padding 上下疊加兩次,視覺上像是文字最上面跟最下面多了一整行空白。
這裡把內層那份歸零,只留外層真正的框線與留白 */
.main-chat .message-row .message .message {
padding: 0 !important;
border-radius: 0 !important;
max-width: 100% !important;
}
/* 使用者訊息:淺灰底色跟白色頁面背景錯開,不用外框線,靠右對齊與置左的
AI 訊息做區分 */
/* Gradio 實際輸出的內層元素同時帶 user/bot 與 message 兩個 class(非巢狀父子),
用複合選擇器只命中該元素本身,避免命中內層 panel-full-width 造成雙重框線 */
.main-chat .message-row.user .message,
.main-chat .message-row.user-row .user.message {
background: transparent !important;
border: none !important;
border-radius: 0 !important;
color: var(--c-text) !important;
margin-left: auto !important;
font-weight: 500 !important;
box-shadow: none !important;
max-width: 80% !important;
text-align: right !important;
}
.main-chat .message-row.user,
.main-chat .message-row.user-row { margin-bottom: 1.1rem !important; }
/* 2026-08-22(業主:使用者泡泡外框/底色希望跟 AI 不同):真正畫出氣泡底色與
邊框的是內層 .md(見上面「AI 訊息」區塊那條共用選擇器 .message-row .message
.md),使用者與 AI 訊息目前吃到同一份 --c-bubble-bg/--c-bubble-border,
看起來一樣。這裡只針對使用者泡泡的 .md 覆寫,改用專屬的 --c-user-bg/
--c-user-border/--c-user-text(原本就有定義、只是從沒接到任何選擇器上)
——淺色是淡綠、深色是半透明綠,跟 AI 泡泡的中性藍灰做出區隔。 */
.main-chat .message-row.user .message .md,
.main-chat .message-row.user-row .user.message .md {
background: var(--c-user-bg) !important;
border: 1px solid var(--c-user-border) !important;
color: var(--c-user-text) !important;
}
/* AI 訊息(主對話視窗):淺灰底色跟白色頁面背景錯開,不用外框線 */
.main-chat .message-row.bot .message,
.main-chat .message-row.bot-row .bot.message {
background: transparent !important;
border: none !important;
border-radius: 0 !important;
color: var(--c-text) !important;
font-size: 14px !important;
line-height: 1.7 !important;
box-shadow: none !important;
text-align: left !important;
}
/* Gradio 產生的 <p> 段落本身有自己的 font-size(實測為固定 16px),
不會沿用上面設在外層容器的字級,且段落間距預設高達 16px,
視覺上偏大又有明顯空行,這裡直接命中 <p> 覆寫 */
.main-chat .message-row .message p {
font-size: 14px !important;
line-height: 1.6 !important;
margin: 0 0 .5rem !important;
}
.main-chat .message-row .message p:last-child { margin-bottom: 0 !important; }
/* 包住段落的 <span class="md prose"> 預設是 inline,卻裡面塞了 block 層級的
<p>(不合法巢狀),瀏覽器會讓這個 span 依自己的字級(16px)先產生一個看
不見的行高空隙,出現在第一段之前——這也是最上面「多一行空白」的真正來源。
改成 block 並統一字級後,這段隱形空隙就會消失 */
.main-chat .message-row .message .md {
display: block !important;
font-size: 14px !important;
line-height: 1.6 !important;
padding: 0.9rem 1.1rem !important;
/* 2026-07-24:氣泡底色改白(淺色)/較淺的 surface(深色)。原本是
--c-surface2(淺灰),改成 --c-surface:淺色=白、深色=#0d1526(比
chat-col 的 --c-bg #020617 淺一階),氣泡浮在欄底上。 */
/* 2026-08-12(業主回報淺色看不清楚區塊):白氣泡疊在 chat-col 的
--c-bg #f8fafc 上只有 1.07:1,等於沒有邊界;深色那一階(#0d1526 疊
#020617)本來就夠,所以只動淺色值——改用 --c-bubble-bg(淺色 #eef2f7、
深色維持 #0d1526)。填色差在近白區間拉不到 3:1(那要暗到不像對話框),
填色之外還要一條邊框才看得出範圍。
2026-08-12 二修(千卉:淺色氣泡也要淡邊框、深淺一致):邊框從結構線
降到細線 --c-divider。淺色 #b6c0cd 對氣泡底 1.64:1、對欄底 1.76:1,
深色 rgba(148,163,184,.28) 疊 #0d1526 是 1.67:1,兩個模式的邊框
亮度差幾乎相同,看起來才是同一種「淡邊框」。 */
background: var(--c-bubble-bg) !important;
border: 1px solid var(--c-bubble-border) !important;
border-radius: 10px !important;
}
/* ── AI 頭像(2026-08-13 四版,業主要求改成「彩虹弧線」圖示)───────
gr.Chatbot(avatar_images=(None, ai-avatar.svg)) 的前端結構是
.message-row > .avatar-container > img。Gradio 原生給 35px 圓、
1px 邊框、margin-right: var(--spacing-xxl),這裡收成 28px 並改成
紫→藍漸層圓底(跟標題字共用同一條 heading-gradient)。
(中間版本一度試過純色底+固定色碼色譜光線,業主看實機覺得不好看,
已改回這版:漸層畫在 CSS 不畫在 SVG 裡,理由同最初設計——avatar_images
是 Python 端參數,同一個檔案沒辦法隨明暗主題換色;交給 CSS 就直接吃
--c-accent-strong/--c-accent2/--c-accent3 這組已經會跟著主題切換的
token,不用再另外補 .dark/@media 深色分支,SVG 檔本身只留白色三道
同心弧(白色描邊疊在任一主題的漸層上都夠亮)。
踩坑提醒:漸層一律寫 background-image 子屬性,不要用 background
簡寫(本專案已踩過兩次,簡寫在這條 pipeline 下會解析成空值)。
flex:none 是防「圖示被 flex 壓縮」那個舊坑(收合 icon bar 曾被壓成 8px)。 */
.main-chat .avatar-container {
width: 28px !important;
height: 28px !important;
flex: none !important;
align-self: flex-start !important;
margin-right: 10px !important;
margin-left: 0 !important;
border: none !important;
border-radius: 50% !important;
background-image: linear-gradient(135deg, var(--c-accent-strong) 0%, var(--c-accent2) 55%, var(--c-accent3) 100%) !important;
}
.main-chat .avatar-container img {
width: 100% !important;
height: 100% !important;
/* 2026-08-12 二版(業主:頭像圖案有點小):內距 5px→3px,圖案在 28px
圓內的可用範圍從 18px 放大到 22px;SVG 本身的 path 也一併撐大並改成
實心(見 assets/icons/ai-avatar.svg)。兩件事一起做才有感——單改內距
只是把細線稿放大,單改 path 又會被內距框住。 */
padding: 3px !important;
object-fit: contain !important;
opacity: 1 !important;
}
/* 頭像佔掉 38px(28+10)之後,氣泡的 90% 上限要從同一條 flex line 扣掉,
否則長訊息會把對話欄撐出捲軸。 */
.main-chat .message-row.bot .message,
.main-chat .message-row.bot-row .bot.message {
max-width: calc(90% - 38px) !important;
}
.main-chat .message-row.bot .message strong,
.main-chat .message-row.bot .message b,
.main-chat .message-row.bot-row .bot.message strong,
.main-chat .message-row.bot-row .bot.message b { color: var(--c-accent-strong) !important; font-weight: 700 !important; }
.main-chat .message-row.bot .message em,
.main-chat .message-row.bot .message i,
.main-chat .message-row.bot-row .bot.message em,
.main-chat .message-row.bot-row .bot.message i { color: var(--c-accent2-strong) !important; }
.main-chat .message-row.bot .message code,
.main-chat .message-row.bot-row .bot.message code {
background: var(--c-code-bg) !important;
color: var(--c-code-text) !important;
padding: 1px 6px !important;
border-radius: 4px !important;
font-family: 'JetBrains Mono', monospace !important;
font-size: 0.82em !important;
}
.main-chat .message-row.bot .message a,
.main-chat .message-row.bot-row .bot.message a {
color: var(--c-accent-strong) !important;
text-decoration: underline !important;
text-underline-offset: 2px !important;
}
/* 2026-07-23:技術執行紀錄從 Accordion 轉正成獨立頁面(canvas_exec_col),
不再是接在對話視窗卡片下方的「抽屜」,改用跟 canvas_idx_col 等其他三個
結果頁共用的 .canvas-result class(已在 gr.Column 宣告時掛上),這裡不用
再另外寫專屬的卡片外框樣式,避免跟共用樣式重複定義出兩套外觀。 */
/* 執行視窗(簡易訊息顯示欄,非對話氣泡) */
/* 原本這裡還設了 max-height + overflow-y:auto 讓內部自己捲動,但整個頁面
本身也會捲動,兩個捲動容器疊在一起、捲軸位置又離得很近,看起來像「兩條
捲軸」造成混亂——拿掉內部捲動限制,讓內容自然撐開高度,只用頁面本身的
捲軸,維持單一捲動體驗 */
/* 2026-07-24(#16):水平 padding 歸零——canvas-result 已提供 1.25rem 左內距
讓內容跟標題(都在 96px)齊平,這裡原本再加 .75rem(12px) 會把 log 文字推到
108px、比標題與其他結果頁多縮 12px。只留上下間距。 */
.exec-log {
background: transparent !important;
border: none !important;
padding: .55rem 0 !important;
margin-top: 0 !important;
}
.exec-log p {
font-size: var(--font-size-base) !important;
line-height: 1.5 !important;
margin: 0 0 .3rem !important;
padding: 0 !important;
color: var(--c-text-muted) !important;
font-family: 'JetBrains Mono', 'Noto Sans TC', monospace !important;
white-space: pre-wrap !important;
word-break: break-word !important;
}
.exec-log p:last-child { color: var(--c-text-soft) !important; }
.exec-log code {
background: none !important;
color: var(--c-accent-strong) !important;
padding: 0 .15rem 0 0 !important;
font-size: var(--font-size-base) !important;
}
/* 跟對話視窗一樣:包住段落的 <span class="md"> 預設 inline 卻裝 block 的
<p>,會在最前面產生依自己字級算出的隱形空隙,這裡一併改成 block 並統一字級 */
.exec-log .md {
display: block !important;
font-size: var(--font-size-base) !important;
}
/* ── 5b. COLUMN LAYOUT ─────────────────────────────────────────── */
/* gap: 0 讓對話視窗跟緊接著的技術執行紀錄 Accordion 完全貼合,
視覺上合併成一張卡片(見「融合」設計討論) */
/* Phase 8-B:三欄式工作區。nav-col 窄、固定寬度不隨視窗縮放;chat-col/
canvas-col 各自獨立捲動(各自的內容不會因為對方變長而互相影響),高度
統一扣掉頁面上方 Tab 列與外距抓一個概略值,之後看實際渲染再微調 */
/* 2026-07-23:跟 chat-col/canvas-col 一樣鎖定高度+自己捲動,理由是
頁殼改成真 100vh 鎖死之後(見上方 .contain 那組規則),nav-col 如果
內容比這個高度多(手風琴展開+8個導覽項目在矮視窗上有可能發生),
沒有自己的 overflow 就會撐開 .main-layout-row,逼 .contain 整層捲動
——雖然不會再拖走頂部列/footer了(那兩個已經在 .contain 外面),但
chat-col/canvas-col 會跟著被迫一起捲動,體驗還是不對。讓三欄各自
獨立捲動,互不影響。 */
/* border-right:導覽欄與對話欄各在自己的右緣掛一條 1px 垂直分隔線
(千卉 2026-07-23 指定),三欄之間才有明確的邊界。畫在欄位自己的
邊框上、不是插入額外的分隔元件,這樣它會自動跟著欄高走完整條。
padding 左右 8px(千卉 2026-07-23):欄裡的東西都不要貼著兩側邊緣。
留白設在欄位本身而不是逐項設定,「開始新分析」按鈕、收合鈕、結果導覽
項目才會全部吃到同一份,不會有人漏掉——先前只設在 .result-nav 上時,
「開始新分析」因為是 .nav-col 的直接子項,就沒被涵蓋到。 */
.nav-col {
flex: 0 0 220px !important;
max-width: 220px !important;
display: flex !important;
flex-direction: column !important;
/* Bug 4-B 修復(2026-07-30):跟 .chat-col/.canvas-col/.main-layout-row
一樣補上 flex-wrap:nowrap。這裡沿用 Gradio .column 預設的 wrap 值,
_populate_tabs 把下載捷徑那組(nav_dl_group)掛進來後,導覽列內容
一旦超過這個固定 height,就會被摺到第二條 flex line(往右排到可視
寬度外),再被下面的 overflow-x:hidden 裁掉——節點還在 DOM,只是渲染
到看不見的地方,這正是「報告完成後導覽列項目整組消失」的根因。
超高內容改走既有的 overflow-y:auto 垂直捲動,不要再被摺行。
這條 flex-wrap:nowrap 是本檔的地雷區,2-B 這輪改版千萬不要移除
——見下一則註解。 */
flex-wrap: nowrap !important;
gap: .3rem !important;
height: 100% !important;
/* 2026-07-30(2-B 第 2 項)三段式改版:原本 overflow-y:auto 直接設在
.nav-col 本身,讓「品牌字標+操作項目+(曾經在頂端的)明暗/收合鈕」
整欄一起捲動。這輪把明暗切換/收合鈕搬到欄底常駐(置底+置右),
如果還是讓 .nav-col 整欄捲動,內容一多,底部工具列會被捲動推出可視
範圍——等於復發 Bug 4-B(「內容一多,重要項目跟著捲不見」)的同一種
症狀,只是這次主角換成底部工具列而不是下載捷徑。修法:改成「上方
品牌列(固定)+中間 .nav-items-scroll(flex:1,自己 overflow-y:auto)
+底部 .nav-bottom-toolbar(固定)」三段式結構,捲動只發生在中間段,
.nav-col 自己的 overflow 改成 hidden(三個直屬子項各自負責自己要不要
溢出,nav-col 本身不再需要當捲動容器)。flex-wrap:nowrap 依然保留在
這裡:三個直屬子項總高度若超過 100%,沒有這條會被摺到第二條 flex
line,跟 Bug 4-B 是同一種機制,不能拿掉。 */
overflow: hidden !important;
border-right: 1px solid var(--c-border-strong) !important;
/* 5-A:導覽列上下左右統一 16px 留白。所有按鈕/項目寬度撐滿這個內距框,
右上收合鈕的右緣也就落在 16px 處,不再多出 ~20px 空隙。 */
padding: 16px !important;
}
/* 中間可捲動項目區:研究情境(已隱藏的空殼)、回首頁連結、結果導覽都在
這裡面。flex:1 讓它吃掉扣除頂端品牌列與底部工具列之後的剩餘高度;
min-height:0 是關鍵——flex 子項預設 min-height:auto 會用內容高度當下限,
讓這個子項無法縮小到比內容矮,overflow-y:auto 就永遠不會真的觸發捲動,
而是把 .nav-col 撐爆(跟 Bug 4-B 的成因是同一條 flexbox 規範,只是換了
一層)。flex-wrap:nowrap 同理沿用 .nav-col 的防禦寫法,避免這裡也被
Gradio 預設的 .column flex-wrap:wrap 摺行。 */
.nav-items-scroll {
display: flex !important;
flex: 1 1 auto !important;
flex-direction: column !important;
flex-wrap: nowrap !important;
min-height: 0 !important;
gap: .3rem !important;
overflow-y: auto !important;
overflow-x: hidden !important;
}
/* 底部工具列(明暗切換+收合):flex:none 固定高度,不參與中間段的縮放,
永遠貼在 .nav-col 底部;border-top 當作千卉指定的「上方水平分隔線」。
justify-content:flex-end 靠右對齊(千卉指定「置底+置右」)。 */
#nav_bottom_toolbar, #nav_bottom_toolbar > .html-container { padding: 0 !important; margin: 0 !important; }
.nav-bottom-toolbar {
display: flex !important;
flex: none !important;
align-items: center !important;
justify-content: flex-end !important;
gap: 2px !important;
padding-top: .6rem !important;
border-top: 1px solid var(--c-border-strong) !important;
}
/* 真因排查記錄:Gradio 的 .column 預設會帶 flex-wrap:wrap。平常沒設高度時
不會有事,但這裡額外設了固定 height 讓內容用捲動取代撐高,一旦內容總高
度超過這個 height,flex-wrap:wrap 會把「排不下」的子元件換到*右邊新開一
欄*(水平並排),而不是照預期垂直捲動——這就是 canvas-col 一度出現內容
跑到欄外、往右位移一整欄寬度的真正原因(不是 sticky、也不是 overflow-x
單獨造成的,那兩個只是讓症狀被看見的地方)。修法:flex-wrap 明確關掉。 */
.chat-col {
display: flex !important;
flex-direction: column !important;
flex-wrap: nowrap !important;
/* 2026-07-24(仿 Claude 對話欄):對話視窗與輸入框直接貼齊欄邊、彼此
之間也無間隙——gap 從 1rem 改 0。欄本身給一個底色(--c-bg),讓上面
的白色氣泡與輸入框浮出來;右側分隔線保留。 */
gap: 0 !important;
height: 100% !important;
overflow: hidden !important;
min-width: 0 !important;
background: var(--c-bg) !important;
border-right: 1px solid var(--c-border-strong) !important;
}
/* 對話視窗:輸入框高度比例 5:1(千卉要求,2026-07-23)。用 flex-grow
依 chat-col 當下的可用高度分配,不寫死 px,縮放視窗時比例仍會維持;
chatbot 元件不再用 Gradio 原生 height= 參數(那是寫死 px),改讓外層
wrapper 高度完全交給 flex 決定。訊息列表本身的捲動仍是 Gradio 原生
行為(.bubble-wrap 內部自己有捲軸),外層 wrapper 用 overflow:hidden
避免出現雙捲軸。 */
.main-chat {
flex: 1 1 auto !important; /* 2026-07-24:吃掉輸入框以外的全部高度(見 #chat_upload_zone) */
min-height: 0 !important;
height: auto !important;
overflow: hidden !important;
display: flex !important;
flex-direction: column !important;
}
/* canvas-col 本身「不」捲動——它只負責把步驟列(固定)跟 canvas-scroll
(捲動)疊在一起、限制在同一個總高度內。踩坑記錄:原本想讓步驟列用
position:sticky 貼在 canvas-col 頂端,但 sticky 元素只能在「自己最近的
容器」範圍內移動,而 Gradio 自動包在 gr.HTML() 外面的 wrapper 高度只比
步驟列本身高一點點,捲動超過那一點高度後 sticky 就跟著捲走、貼不住。
改成結構性分離:步驟列跟 canvas-scroll 是兩個獨立區塊,前者天生不參與
捲動,後者自己 overflow-y:auto,兩者高度互不相干,不用依賴 sticky。 */
.canvas-col {
display: flex !important;
flex-direction: column !important;
flex-wrap: nowrap !important;
gap: 0 !important;
height: 100% !important;
overflow: hidden !important;
min-width: 0 !important;
}
.canvas-col .app-header {
flex-shrink: 0 !important;
}
/* 2-A(2026-07-30):頂部產品名+步驟列只在「AI 對話」頁顯示,其他結果頁
(語言指標分析/敘述統計/差異性分析/監督式學習/非監督式學習/
LLM輔助分析/技術執行紀錄)都不顯示——那幾頁已經佔滿 Canvas 寬度,
步驟列這時只是重複資訊。data-page 屬性是 _APP_HEAD_SCRIPT 的
applyActivePage() 寫在 .main-layout-row 上的既有機制,這裡純加一條
CSS,不需要新增任何 Python wiring。用 :not([data-page="chat"]) 反選,
不用把 5 個結果頁 key 都列一次。elem_id 鎖 app_header_html 的外層
wrapper——elem_classes 會同時蓋在外層 .block 與內層 .prose 兩層 div 上
(本檔多處踩過這坑,見 .upload-hint 那段完整記錄),id 選擇器沒有這個
問題。 */
.main-layout-row:not([data-page="chat"]) #app_header_wrap {
display: none !important;
}
/* 最後一段殘餘留白(10px):Gradio 幫所有 gr.HTML() 元件的內容包了一層
.html-container,本身有 padding:10px 12px,這是全站每個 gr.HTML()
共用的預設樣式(首頁卡片、運算中提示等都靠它),不能整個歸零,只精準
鎖定「包住 .app-header 的那一個」html-container,不影響其他 gr.HTML()
元件(例如同樣在 canvas-col 裡的 computing_html)。 */
.html-container:has(.app-header) {
padding: 0 !important;
}
.canvas-scroll {
display: flex !important;
flex-direction: column !important;
flex-wrap: nowrap !important;
flex: 1 1 auto !important;
min-height: 0 !important;
overflow-x: hidden !important;
overflow-y: auto !important;
/* 2026-07-24 千卉:Canvas 內容左右等寬。水平內距統一收在這一層(20px,
跟上方 .app-header 的左右 20px 一致),底下所有內容區塊都吃同一份——
原本只有 .canvas-result 自己加 20px,而 upload_zone/metadata_zone/
分析面板是 canvas-scroll 的直屬子項、左右各 0,同一欄裡兩種留白。
.canvas-result 的水平 padding 因此歸零,避免疊成 40px。 */
padding-left: 20px !important;
padding-right: 20px !important;
}
/* 2026-07-23:頁面切換模型。點結果類 nav 項目時 JS 會在 .main-layout-row
上加這個 class(見 _APP_HEAD_SCRIPT 的 applyActivePage()),對話欄收起、
Canvas 佔滿剩下的寬度。canvas-col 的 flex 要蓋過 Gradio 自己依 scale=7
算出的 inline flex-grow,才會真的補滿收起對話欄後空出來的寬度(同一類
問題 8-E 的 min-width 已經踩過一次,這裡沿用同一個「stylesheet
!important 蓋得過沒加 !important 的 inline style」原則)。
踩坑(新案例,跟 8-B 的 .column flex-wrap 是同一大類但這次是 .row 本身
從沒被設過):.main-layout-row(gr.Row)本身從來沒設過 flex-wrap:nowrap
——chat-col/canvas-col 各自「內部」的 flex-wrap:nowrap 只管它們自己的
子元件怎麼排,不影響 main-layout-row 怎麼排這三欄本身。(訂正 2026-07-30:
這裡原本誤寫成 nav-col 也在這份清單裡,實際上當時 nav-col 從未設過
flex-wrap:nowrap——這正是 Bug 4-B「報告完成後導覽列整組消失」被忽略的
直接原因,已於上方 .nav-col 規則補上,見該處 2026-07-30 註解。)平常
三欄用 flex-grow 精準分配寬度、剛好不超版所以wrap從沒被觸發過;這次
canvas-col 拿掉 max-width 上限、又改用 flex-basis:auto 之後,內容較寬
的表格會把 canvas-col 的 intrinsic 寬度撐到跟 nav-col 放不進同一行,
Gradio 預設的 flex-wrap:wrap 就把 canvas-col 換到 nav-col 下面另起
一行——整個版面變成上下堆疊、頁面被撐到 1200px+ 高,捲動 canvas 內容
時連 nav-col 都跟著被推出視窗外。修法:main-layout-row 本身也要明確
nowrap,不能只顧內部三欄各自的 nowrap。 */
.main-layout-row {
flex-wrap: nowrap !important;
}
.main-layout-row.chat-collapsed .chat-col {
display: none !important;
}
.main-layout-row.chat-collapsed .canvas-col {
flex: 1 1 auto !important;
max-width: none !important;
min-width: 0 !important;
}
/* Phase 8-C 踩坑:結果內容搬進 canvas-scroll 後,內容總高度遠超過
canvas-col 給的固定 height,理論上該讓 canvas-scroll 自己捲動就好;但
exec_log_accordion(技術執行紀錄)被壓縮成只剩標題列高度(22px),內容
反而擠進一個內部小捲動框。原因:CSS flexbox 規範規定「flex 子項的
automatic minimum size」只有在該子項 overflow:visible 時才會採用內容
高度當下限,一旦子項 overflow 不是 visible(Gradio 會自動幫 Accordion
這類元件的外層 wrapper 加上 inline overflow:auto,方便使用者拖曳調整
大小),下限就變成 0,於是在總內容超出容器高度、瀏覽器要收縮子項時,
這個子項就被壓到接近 0——同排的 upload_row/metadata_row/canvas_idx_col
等純 gr.Column 因為維持預設 overflow:visible 沒有這個下限問題,才沒被
壓縮。修法不是去動 Accordion 的 overflow(那是 Gradio 拖曳調整大小的
功能,不想關掉),而是直接讓這個捲動容器裡的所有子項都不參與收縮 */
.canvas-scroll > * {
flex-shrink: 0 !important;
}
/* ── 5d. HOMEPAGE(Phase 8:首頁 4 張情境卡片)──────────────────── */
/* 隱藏版情境按鈕:純作 JS 橋接目標(見 _APP_HEAD_SCRIPT),
點卡片=模擬點這裡的按鈕,Python 端 .click() 綁定完全不動。
gr.Button 的 elem_classes 直接掛在 <button> 標籤本身(不像
gr.HTML() 會多一層 wrapper),所以這裡單純 display:none 就完全
不佔版面,沒有下面 .nav-html-bridge 那個問題。 */
.sc-bridge-btn { display: none !important; }
/* 2026-07-23 踩坑:gr.HTML() 元件的實際內容跟 Gradio 自動產生的外層
wrapper(.block)是兩個不同的 DOM 節點——只把內層內容(例如
#mobile_nav_toggle/#nav_backdrop)設 display:none,wrapper 本身
還是會依自己的預設 min-height/padding 佔用版面高度,疊加上跟相鄰
元件的 flex gap,變成看得到但摸不著原因的空白區塊(千卉截圖回報
頂部/底部大片空白時發現)。第一輪試過 display:contents 沒有真的
清零(懷疑跟 elem_classes 同時疊在 .block 與 .prose 兩層有關,
detail 見 create_app() 裡兩個 gr.HTML() 呼叫旁的完整記錄),改用
最直接可靠的做法:wrapper 直接用 elem_id 鎖定、預設寫死
display:none——一般 block 元素包一個 display:none 的子元素天生
零高度,不用依賴 display:contents 這種較冷門的屬性。JS 對應改成
切換這兩個 wrapper 本身的 display(見 _APP_HEAD_SCRIPT 的
_syncMobileHamburger()),內層 #mobile_nav_toggle/#nav_backdrop
自己原本的桌面/手機斷點 CSS 完全不用動。 */
/* 兩層踩坑疊在一起:①這個 wrapper 帶 Gradio 自己的 "padded" class,
就算內層內容 display:none,wrapper 本身還是會因為 line-height留下
約20px空氣行高②就算加 height:0 讓 wrapper 自己不佔高度,JS 為了
手機斷點把它切成 display:block 時,它仍然是「有在排版流程裡」的
flex 子項,外層 column 的 gap:16px 還是會在它前後插入間距——CSS
flexbox 的 gap 只認「這個子項是不是 display:none」,不管它自己的
box 高度是不是 0。用 devtools 逐一 display:none 排查才抓到這一層,
單靠 height:0 治標不治本。
真正修法:桌面寬度(>900px)這兩個 wrapper 必須是貨真價實的
display:none(徹底退出 flex 排版,不只是零高度),用 !important
蓋掉 JS 可能設的 inline display:block;手機寬度(<=900px)才放行讓
JS 的 block/none 判斷生效。這樣桌面寬度不管 nav-col 存不存在,這兩個
wrapper 永遠不佔用任何 flex 空間。 */
#mobile_nav_toggle_wrap, #nav_backdrop_wrap {
display: none;
}
@media (min-width: 901px) {
#mobile_nav_toggle_wrap, #nav_backdrop_wrap {
display: none !important;
}
}
.homepage-col { max-width: 50rem; margin: 2.5rem auto 3rem; padding: 0 1rem; }
.homepage-hero { text-align: center; margin-bottom: 2.4rem; }
/* 2026-07-30(1-C):業主指定移除首頁第一行「重譯文本量化分析平台」
(原本的 .kicker),HTML 端已拿掉該 <span>,.kicker 本身連同兩側裝飾線
的 CSS 規則一併移除(沒有任何元素會再套用它)。 */
/* 2026-07-27:老師確認「譯彩紛呈:重譯文本分析系統」是大系列品牌名稱,
這一款子產品其實叫「II 分析引擎 SpectraEngine」——原本大系列名稱佔用
最大最搶眼的 h1+漸層,子產品名反而縮在最小的一行,跟實際定位相反。
重新分配視覺權重:大系列名稱降級成小字(series-name),子產品名稱升級
成 h1 大標題+漸層。 */
/* 2026-07-30(1-D/1-E):業主指定放大到 18px/14px(原本 .95rem/.85rem,
換算 16px 根基約 15.2px/13.6px)。直接寫死 px,不用 rem,避免驗收量測
時因根字級假設不同而跟業主指定的精確數字對不上。 */
.homepage-hero .series-name {
margin: .9rem 0 .1rem; font-size: 18px; font-weight: 600;
color: var(--c-text-soft);
}
.homepage-hero .subtitle { margin: 0 0 1.1rem; font-size: 14px; color: var(--c-text-muted); }
.homepage-hero h1 {
margin: 0 0 .6rem; font-size: 2.1rem; line-height: 1.3; font-weight: 700;
/* 同 .app-header .title-line 的漸層文字踩坑(見 _DARK_MODE_HEAD_CSS 上方註解):
background 簡寫在瀏覽器展開成子屬性時 background-image 會解析成
空值,改直接寫 background-image 這個子屬性本身即可。這裡進一步採用
.app-header .title-line 已驗證過、支援巢狀 <span class="en"> 的寫法
(-webkit-text-fill-color 會被子元素繼承,color:transparent 單獨用
在有巢狀 span 時未實測過是否可靠,兩個都寫上比較保險)。 */
background-image: linear-gradient(100deg, var(--c-accent) 10%, var(--c-accent2) 90%);
-webkit-background-clip: text; background-clip: text;
color: transparent; -webkit-text-fill-color: transparent;
}
/* 2026-07-31(Batch E 第 1 項):業主回報這行沒有跟其他行對齊置中。
量測證實:.lede 是唯一被覆寫成 text-align:left 的行,其餘行
(series-name/subtitle/h1/footnote)都吃 .homepage-hero 本身的
text-align:center。文字方塊(<p> 的 border box)四行寬度其實一致,
問題出在「視覺文字置中」——.lede 文字換行後每行長度不同,
text-align:left 讓每行從方塊左緣起排,量出來的文字視覺中心會比
方塊中心明顯偏左(1280px 寬實測:文字視覺中心 x≈602,其他行
x≈640,偏差近 40px,方向是「偏左」不是業主主觀感受的「偏右」,
可能是她螢幕寬度或縮放比例不同所致)。拿掉這條 text-align:left
覆寫,改回跟其他行一致的置中,四行的文字視覺中心才會真正對齊。 */
.homepage-hero .lede {
margin: 1.8rem 0 0; text-align: center; font-size: var(--font-size-base);
line-height: 1.8; color: var(--c-text-muted);
}
/* 1-B:首頁「開始研究分析」按鈕,進入工作區的唯一入口。這是真正的
gr.Button(不是首頁卡片那種 HTML+隱藏按鈕橋接手法——單一按鈕不需要,
見 create_app() 裡 start_analysis_btn 旁的說明),elem_classes 直接掛在
<button> 標籤本身(同 .sc-bridge-btn 那則註解),用 elem_id 選擇器蓋過
Gradio 預設樣式。藍紫色漸層沿用 h1 已在用的同一組色票(--c-accent/
--c-accent2),不是另外發明新配色。width 用 fit-content+margin auto
置中,不管外層 Gradio 自動包的 wrapper 是否撐滿欄寬都能正確置中。 */
#start_analysis_btn {
display: flex !important;
width: fit-content !important;
margin: 2.2rem auto 0 !important;
padding: .95rem 2.8rem !important;
font-size: 1.05rem !important;
font-weight: 700 !important;
border: none !important;
border-radius: 14px !important;
color: #fff !important;
background-image: linear-gradient(100deg, var(--c-accent) 10%, var(--c-accent2) 90%) !important;
box-shadow: 0 16px 40px rgba(var(--c-accent2-rgb),.35) !important;
transition: filter 150ms ease, box-shadow 150ms ease !important;
}
#start_analysis_btn:hover {
filter: brightness(1.06);
box-shadow: 0 20px 46px rgba(var(--c-accent2-rgb),.45) !important;
}
/* 3-A 追加:情境卡片搬進工作區後改垂直排列(業主指定「不要 2×2 矩陣」)。
這個 class 現在只有工作區的研究選擇區塊在用(首頁卡片已移除,見 1-A),
直接把兩欄矩陣改成單欄即可,不用另外開一個 class。 */
.scenario-grid { display: grid; grid-template-columns: 1fr; gap: 1rem; margin-top: 1.2rem; }
.scenario-card {
all: unset; box-sizing: border-box; cursor: pointer;
display: flex; gap: 1rem; align-items: flex-start; text-align: left;
padding: 1.3rem 1.4rem;
background: var(--c-surface);
border: 1px solid rgba(var(--c-accent-rgb),.40) !important;
border-radius: 16px;
box-shadow: 0 16px 50px rgba(15,23,42,.20);
transition: border-color 180ms ease, box-shadow 180ms ease, background-color 180ms ease;
}
.scenario-card:hover, .scenario-card:focus-visible {
border-color: rgba(var(--c-accent-rgb),.7) !important;
background: linear-gradient(180deg, rgba(var(--c-accent-rgb),.06) 0%, var(--c-surface) 100%);
}
.scenario-card:focus-visible { outline: 2px solid var(--c-accent); outline-offset: 2px; }
.scenario-card .sc-index {
flex: none; font-family: 'Crimson Pro', serif; font-weight: 600;
font-size: 1.8rem; line-height: 1; padding-top: .1rem;
color: rgba(var(--c-accent-rgb),.4); transition: color 180ms ease;
}
.scenario-card:hover .sc-index, .scenario-card:focus-visible .sc-index { color: var(--c-accent); }
.scenario-card .sc-body { min-width: 0; display: flex; flex-direction: column; }
.scenario-card .sc-title { margin: 0 0 .35rem; font-size: 1.02rem; font-weight: 700; color: var(--c-text); line-height: 1.4; }
.scenario-card .sc-desc { margin: 0; font-size: var(--font-size-base); color: var(--c-text-muted); line-height: 1.65; }
.homepage-secondary { display: flex; justify-content: center; margin-top: 2rem; }
.homepage-secondary .placeholder-link {
display: inline-flex; align-items: center; gap: .4rem;
font-size: var(--font-size-base); color: var(--c-accent); cursor: pointer;
}
.homepage-secondary .placeholder-link:hover { text-decoration: underline; }
.homepage-secondary .placeholder-link svg { width: 1rem; height: 1rem; opacity: 1; }
.homepage-footnote { margin-top: 2rem; text-align: center; font-size: .82rem; color: var(--c-text-subtle); }
@media (prefers-reduced-motion: reduce) { .scenario-card { transition: none; } }
/* ── 5e. NAV-COL 收合/手機漢堡抽屜(Phase 8-E)────────────────── */
/* gr.Button(icon=...) 這版 Gradio 實測是渲染成 <img src="...">,不是行內
<svg>,CSS currentColor 對它沒有作用(img 不會繼承文字顏色)。因此
assets/icons/*.svg 檔案裡直接把 stroke 寫死成淺色模式的 --c-text-muted
(#475569),深色模式改用 filter 把整張圖反相+去彩,逼近深色模式的
--c-text-muted (#94a3b8) 色調,這是唯一能讓「檔案路徑圖示」跟著主題
變色的辦法(不用另外準備一份深色版 svg 檔案)。 */
img.button-icon { opacity: .82; }
.dark img.button-icon {
filter: grayscale(1) invert(1) brightness(1.55) contrast(.85);
}
/* nav-col 收合/手機抽屜控制鈕:桌面收合鈕與手機關閉鈕互斥顯示,
預設(>900px)只顯示收合鈕;@media (max-width:900px) 那組規則會反過來
(見下方)。這兩顆是純 gr.HTML() 刻的按鈕,行內 SVG 用 _icon(),
currentColor 正常運作,不受上面 img.button-icon 的限制。 */
/* 2026-07-24:頂端一列=品牌字標(左)+ 明暗/收合鈕(右)。原本只有一個
靠右的收合鈕(.nav-collapse-row),改成 space-between 兩端對齊;字標與
下方「開始新分析」的間距(5-D)由這裡的 margin-bottom 控制,拉近一點。 */
/* Gradio 幫 gr.HTML 外層 .block/.html-container 掛了 ~12px 預設 padding,
會讓頂端列(品牌+收合鈕)比下方按鈕多縮 12px、右緣多 12px 空隙(就是
千卉量到的那 20+px)。把這個 wrapper 的 padding 歸零,頂端列才跟按鈕
一樣貼齊 nav-col 的 16px 內距框。 */
#nav_top_html, #nav_top_html > .html-container { padding: 0 !important; margin: 0 !important; }
.nav-top-row {
display: flex; align-items: center; justify-content: space-between;
margin-bottom: .35rem;
}
/* 2026-07-30(2-B 第 1/2 項):頂端列現在只剩品牌字標——明暗切換/收合鈕
搬到欄底新的 .nav-bottom-toolbar(見下方),.nav-top-actions 這個右側
容器已無內容可放,但選擇器留著不刪:nav-top-row 的 justify-content:
space-between 沒有第二個 flex item 時退化成單純靠左,不影響外觀,刪掉
反而要動兩處收合樣式的選擇器,徒增風險。 */
.nav-top-actions { display: flex; align-items: center; gap: 2px; }
/* 2026-07-31(Batch E 第 2 項):業主改主意,品牌字標從 3 行改 2 行
(「譯彩紛呈 II 分析引擎」/「SpectraEngine」),字級統一 1.1rem
(原本 3 行是 .92rem/.78rem/.66rem 遞減,2 行不再需要遞減層次)。
原本靠 flex column 讓三行各自不同字級仍能整齊靠左對齊
(align-items:flex-start)的理由,2 行同字級一樣適用,結構不用改。
漸層維持套在同一個外層元素上(不是各行各自 background-clip):
background-image 畫在 .nav-brand 整個 box 上,兩行文字一起被裁切顯示,
漸層才會像流過兩行的單一色帶,而不是每行各自重新起始一次相同的漸層
(後者會讓每行左端都是同一個顏色,視覺上像兩條各自獨立的色塊)。 */
.nav-brand {
display: flex; flex-direction: column; align-items: flex-start;
font-weight: 700; letter-spacing: .2px; line-height: 1.2;
background-image: linear-gradient(120deg, var(--c-accent-strong) 0%, var(--c-accent2) 55%, var(--c-accent3) 100%);
-webkit-background-clip: text; background-clip: text;
-webkit-text-fill-color: transparent; color: transparent;
}
.nav-brand .nav-brand-line1, .nav-brand .nav-brand-line2 { font-size: 1.1rem; }
.nav-drawer-close-row { display: none; justify-content: flex-end; margin-bottom: .5rem; }
.nav-collapse-toggle, .nav-drawer-close, .mobile-nav-toggle, .nav-icon-btn {
all: unset; box-sizing: border-box; cursor: pointer;
display: inline-flex; align-items: center; justify-content: center;
width: 32px; height: 32px; border-radius: 8px;
color: var(--c-text-muted);
transition: background-color 150ms ease-out, color 150ms ease-out;
}
.nav-collapse-toggle:hover, .nav-drawer-close:hover, .mobile-nav-toggle:hover, .nav-icon-btn:hover {
background: rgba(var(--c-accent-rgb),.08); color: var(--c-text);
}
.nav-collapse-toggle:active, .nav-icon-btn:active {
background: rgba(var(--c-accent-rgb),.16);
}
/* 明暗切換鈕:兩個圖示常駐 DOM,只用 CSS 切換顯示(預設淺色→顯示月亮,
代表「點了會變深色」)。JS 只改 data-theme-icon,不動 DOM 結構。 */
#theme_toggle .ico-moon { display: inline-flex; }
#theme_toggle .ico-sun { display: none; }
#theme_toggle[data-theme-icon="dark"] .ico-moon { display: none; }
#theme_toggle[data-theme-icon="dark"] .ico-sun { display: inline-flex; }
.nav-collapse-toggle:focus-visible, .nav-drawer-close:focus-visible,
.mobile-nav-toggle:focus-visible, .nav-icon-btn:focus-visible {
outline: 2px solid var(--c-accent); outline-offset: 2px;
}
.nav-collapse-toggle svg { transition: transform 200ms ease-out; }
.nav-col.collapsed .nav-collapse-toggle svg { transform: rotate(180deg); }
@media (prefers-reduced-motion: reduce) {
.nav-collapse-toggle svg { transition: none; }
.nav-col { transition: none !important; }
}
/* 桌面收合成窄 icon bar:文字用 font-size:0 隱藏(不是 display:none,
避免影響按鈕的 accessible name/可點擊區域),圖示尺寸另外鎖死不受
font-size 影響。統計比較分析的 4 個子按鈕是純文字、沒有各自圖示,
收合時整組 Accordion 直接隱藏(窄版 icon bar 塞不下二層選單);重置
按鈕若在收合狀態被點擊,JS 會先自動展開 nav-col 再讓確認彈窗顯示
(見 _APP_HEAD_SCRIPT setNavCollapsed 呼叫處),避免確認彈窗擠在
60px 窄欄裡。這整組「收合外觀」規則只在桌面寬度生效(min-width:901px)
——手機沒有「窄 icon bar」這個中間態,nav-col 在手機是 off-canvas
抽屜,要嘛整個滑出來、要嘛整個收在畫面外,即使 localStorage 殘留
collapsed=1 也不該套用這組窄化樣式(見下方 900px 媒體查詢改用
position:fixed 抽屜,兩者規則互斥,不需要互相 revert)。 */
.nav-col { transition: flex-basis 200ms ease-out, max-width 200ms ease-out; }
@media (min-width: 901px) {
/* gr.Column(min_width=200) 會在元件上寫死 inline style="min-width:200px",
這個 inline min-width 會蓋掉外部 stylesheet 的 max-width(flexbox
規範:min-width 與 max-width 衝突時 min-width 優先),光設
max-width:60px 收合欄寬不會有效果,撐在 200px 不動——必須額外用
!important 覆寫 min-width 本身(stylesheet 的 !important 蓋得過
沒加 !important 的 inline style,這點跟蓋不過 inline !important
不一樣)。 */
.nav-col.collapsed { flex: 0 0 60px !important; max-width: 60px !important; min-width: 60px !important; }
/* 收合成 60px icon bar:頂端列只剩品牌字標(已無其他項目可置中,
justify-content:center 留著也不影響),字標本身在 60px 裡塞不下、
隱藏起來,展開後才出現。 */
.nav-col.collapsed .nav-top-row { justify-content: center; }
.nav-col.collapsed .nav-brand { display: none !important; }
/* 2026-07-30(2-B 第 2 項):明暗切換/收合鈕搬到欄底 .nav-bottom-toolbar
後,收合時的置中邏輯也跟著搬過來——明暗切換鈕在 60px 裡放不下(跟收合
鈕擠在一起會超寬),一起隱藏,只留收合鈕自己置中,展開後才恢復兩顆
並排靠右(見 .nav-bottom-toolbar 規則本身)。
這裡一定要加 !important:.nav-bottom-toolbar 本身的 justify-content:
flex-end 也掛了 !important(本檔到處都是這個坑,CSS cascade 裡
!important 不管 specificity 高低都贏過沒加的規則),沒加會被蓋掉、
收合時仍然靠右對齊,量出來就會跟預期的「置中」對不上。 */
.nav-col.collapsed .nav-bottom-toolbar { justify-content: center !important; }
.nav-col.collapsed #theme_toggle { display: none !important; }
/* .reset-confirm 已於 2026-07-24 搬出 nav-col 改成全螢幕 modal,
不再需要在收合狀態下隱藏;.result-nav-accordion 同日隨「統計比較
分析」手風琴一起移除。 */
.nav-col.collapsed .result-nav-divider { display: none !important; }
/* 選擇器裡的 #result_nav_col 是為了贏過下方 nav-underline 樣式的
`#result_nav_col .result-nav-btn`(1 個 id + 1 個 class = 1,1,0)。
兩邊都掛 !important 時由 specificity 決勝,純 class 的
`.nav-col.collapsed .result-nav-btn`(0,3,0)會輸,收合後的置中
與 padding 就不會生效。加上 id 祖先後是 1,3,0,穩定勝出。 */
/* 2026-07-24 回歸修正:為了統一字級加了 `#reset_btn`(1,0,0)之後,純 class
的 `.nav-col.collapsed .reset-btn`(0,3,0)反而輸掉,收合時「開始新分析」
的文字不再被 font-size:0 隱藏、把 60px 窄欄撐破。這裡改用 `#reset_btn`
當選擇器(1,2,0)贏回來。呼應本檔既有註記:任何為了蓋過 Gradio 而加 id
的改動,都要回頭盤同名 class 的其他規則有沒有被連帶壓掉。 */
.nav-col.collapsed #reset_btn,
.nav-col.collapsed #nav_guide_btn,
.nav-col.collapsed #nav_about_btn,
.nav-col.collapsed #result_nav_col .result-nav-btn {
justify-content: center !important;
padding: .45rem !important; /* 2026-07-24 收窄:原本 .6rem 把圖示擠到 8px(見下方註解) */
font-size: 0 !important;
}
/* 千卉回報「收合後看不見 icon」的真因(2026-07-24 量出來才發現):不是顏色
太淡,是圖示被擠成 8px。60px 欄寬扣掉 nav 的 16px 左右內距只剩 28px,再扣
按鈕自己的 .6rem 左右內距(19.2px),留給圖示的空間只剩 ~9px,而 img 是
flex 子項、預設可被壓縮,就一路縮到 8px。
修法三件:①收合時欄內距縮到 8px(展開仍維持 16px)②按鈕內距縮到 .45rem
③圖示 flex:none 明確不可壓縮。可用寬 = 60-16-14.4 ≈ 29.6px > 22px。
另外 opacity 拉到 1(預設 .82 沒有文字陪襯時偏淡);disabled 的淡化來自
按鈕本身 opacity:.38(父層),不受影響。 */
.nav-col.collapsed { padding-left: 8px !important; padding-right: 8px !important; }
.nav-col.collapsed .button-icon {
width: 22px !important; height: 22px !important;
flex: none !important; opacity: 1 !important;
}
.nav-col.collapsed .result-nav { padding-top: .8rem !important; }
}
/* 2026-07-24:這裡原本有一整組 #nav_stat_accordion 樣式(CSS ::before 掛
chart-bar 圖示、label-wrap 的 justify-content 修正、深色模式 filter),
隨「統計比較分析」Accordion 一起移除。四個子頁現在是平列的一般導覽項目,
圖示走 gr.Button(icon=) 那條路,不需要這組特例。 */
/* 手機漢堡開關(mobile_nav_toggle)與抽屜遮罩(nav_backdrop):兩者都是
放在 workspace_row 外層、一開始就 visible=True 的獨立元件(理由見
create_app() 該處註解),CSS 預設整個不顯示,只在手機寬度且 nav-col
已掛載(情境已選定)時,由 _APP_HEAD_SCRIPT 的 _syncMobileHamburger()
切換 display,避免首頁或桌面寬度下平白多一顆浮動按鈕。 */
.mobile-nav-toggle {
display: none;
position: fixed; top: 14px; left: 14px; z-index: 45;
background: var(--c-surface) !important;
border: 1px solid var(--c-border-strong) !important;
box-shadow: 0 4px 16px rgba(15,23,42,.18);
}
/* 2026-07-30:抽屜展開時藏起浮動漢堡鈕,見 setMobileDrawer() 註解——它跟
nav-col 疊在同一個左上角,蓋住品牌字標。抽屜本身已有 nav_drawer_close
可以關閉,這顆鈕開著時沒有作用。 */
.mobile-nav-toggle--hidden { display: none !important; }
.nav-backdrop {
display: none;
position: fixed; inset: 0; z-index: 30;
background: rgba(2,6,23,.5);
opacity: 0; pointer-events: none;
transition: opacity 200ms ease-out;
}
.nav-backdrop.visible { opacity: 1; pointer-events: auto; }
@media (prefers-reduced-motion: reduce) { .nav-backdrop { transition: none; } }
@media (max-width: 900px) {
/* 手機寬度:nav-col 不再跟著 chat-col/canvas-col 上下堆疊(那條規則
見「17. RESPONSIVE」,已把 nav-col 排除),改成 off-canvas 抽屜,
預設收在畫面左側外面,漢堡鈕/背景遮罩點擊才滑入。 */
.nav-col {
position: fixed !important;
top: 0; left: 0; bottom: 0;
width: 78vw !important; max-width: 300px !important;
flex: none !important; height: 100vh !important;
overflow-y: auto !important;
background: var(--c-surface) !important;
box-shadow: 4px 0 24px rgba(15,23,42,.28) !important;
z-index: 40;
transform: translateX(-100%);
transition: transform 220ms ease-out;
padding: 1.1rem 1rem !important;
}
.nav-col.mobile-open { transform: translateX(0) !important; }
/* 桌面收合是縮寬窄 icon bar 那組規則整段包在 @media(min-width:901px)
裡(見「5e」上方),手機寬度完全不會命中,這裡不需要另外
revert——即使 localStorage 殘留 collapsed=1,.nav-col.collapsed
在手機寬度就是普通展開的抽屜外觀,不會被窄化。 */
/* 手機抽屜沒有「窄 icon bar」中間態,收合鈕無意義 → 只隱藏收合鈕本身,
品牌字標與明暗切換鈕仍留在抽屜頂端;關閉抽屜改用下方的 x(drawer-close)。 */
#nav_collapse_toggle { display: none !important; }
.nav-drawer-close-row { display: flex !important; }
.mobile-nav-toggle { display: inline-flex; }
.nav-backdrop { display: block; }
}
/* ── 6. UPLOAD AREA(醒目卡片)─────────────────────────────────── */
/* 注意:max-width + margin auto 是舊版單欄置中版面留下的樣式,現在
upload-zone 是右側 action-col 裡的元件,要跟同一欄的 scenario-buttons
同寬(撐滿欄寬),不能再置中收窄 */
/* 2026-07-23 千卉實機回饋:原本的強調色外框+大範圍陰影+漸層底色讓這張
卡片像一個浮起來的彈窗。改成扁平——外框、陰影、底色全部拿掉,只保留
圓角與內邊距維持內容的呼吸空間。彈出動畫保留,它負責的是「這塊剛剛才
出現」的狀態提示,跟視覺重量無關。 */
/* 2026-07-31(Batch E 第 4-c 項):業主附圖標註「文本上傳」標題(這裡是
::before 產生的「📂 上傳已斷詞文本」偽標題)左緣比步驟列第一顆藥丸左緣
往右縮排。實測量出來:canvas-scroll 本身已經統一給所有直屬子項 20px
左內距(跟 .app-header 的 20px 左內距是同一個基準線,兩者天生就對齊),
問題是這裡自己又疊了一層 1.5rem(24px)左右內距,把內容多推了 24px。
拿掉左右內距,改吃 canvas-scroll 那層統一的 20px,跟步驟列基準線對齊
(同時也順手把上方留白從「zone 自己的 1.4rem 頂部內距」壓掉,見下方
padding 從 "1.4rem 1.5rem" 改成 "0 0 1.4rem" 的異動——只留底部內距
維持内容與下一個區塊的呼吸空間,頂部改靠 margin-top 的 .6rem 加上標題
本身既有的 margin 提供間距即可,不用內外雙重留白)。 */
.upload-zone {
border: none !important;
border-radius: 16px !important;
background: none !important;
padding: 0 0 1.4rem !important;
margin: .6rem 0 0 !important;
width: 100% !important;
box-shadow: none !important;
animation: popupIn .25s cubic-bezier(.2,.8,.3,1) !important;
}
@keyframes popupIn {
from { opacity: 0; transform: translateY(-8px) scale(.985); }
to { opacity: 1; transform: translateY(0) scale(1); }
}
.upload-zone::before {
content: "📂 上傳已斷詞文本";
display: block;
font-size: 1.05rem;
font-weight: 700;
color: var(--c-accent-strong);
/* 2026-07-31(Batch G 第 2 項):業主回報「上傳已斷詞文本」標題上方
留白比其他階段標題少。實測(header 底緣到標題文字頂緣的距離):
研究選擇 h3 ≈ 32px、語料 Metadata 說明 h3 ≈ 68px、這裡(::before
偽元素)只有 ≈ 9.6px(等於 .upload-zone 自己的 margin-top .6rem,
偽元素本身預設沒有 margin-top,不像另外兩個標題是真的 <h3>、天生
帶 Gradio markdown prose 的標題頂部留白 ≈22.4px)。收斂目標訂在
32px(貼近「研究選擇」現有數值,改動最小),這裡補上 1.4rem
(22.4px)頂部留白,湊出跟其他標題一致的視覺留白。 */
margin-top: 1.4rem;
margin-bottom: .9rem;
letter-spacing: .02em;
}
/* 原本這裡有 .upload-zone:hover/:focus-within 加深 border-color 的規則,
2026-07-23 卡片扁平化拿掉外框後就沒有東西可加深了,一併移除。 */
/* 對話輸入框(Phase 8:不再負責上傳,改回純展示用的常駐輸入框):
刻意跟上面「醒目卡片」風格的 .upload-zone 拉開視覺重量差異,
不用強調色框線/陰影/彈出動畫,也不掛 ::before 的標題文字,
避免使用者誤以為這裡也是一個可操作的上傳入口 */
/* 2026-07-24(仿 Claude):輸入框貼齊欄邊——拿掉外框、圓角、內距、上方
margin(原本 .6rem 是它跟對話視窗的間隙,也一起拿掉)。 */
.upload-zone-plain {
border: none !important;
border-radius: 0 !important;
padding: 0 !important;
margin: 0 !important;
/* width 用 auto 不用 100%:下方 #chat_upload_zone 會給左右 margin 讓面板
內縮,若寫死 100% 會連同 margin 一起超出欄寬(實測右側溢出 15px)。 */
width: auto !important;
}
/* 輸入框本體:底色改成跟氣泡同一階(白/深色淺一階),去掉 Gradio 預設的
外框與圓角,四邊貼齊 chat-col。頂端用 1px 細線跟對話視窗分界(取代原本
靠間隙分隔)。 */
#chat_upload_zone .multimodal-textbox,
#chat_upload_zone .input-container,
#chat_upload_zone textarea {
background: var(--c-surface) !important;
border: none !important;
border-radius: 0 !important;
box-shadow: none !important;
}
/* 2026-07-24 千卉:輸入框右邊與下面要有留白。先前的 16px 只是「面板內部的
內距」,白色面板本身仍貼齊欄邊(實測面板距欄 左1 右2 下1)。改成面板整個
往內縮 16px,白底與欄邊之間真的空出來;不再需要頂端分隔線(面板浮在欄底
色上,本身就是分界)。Gradio 的 .form 外層帶淡藍底+框線也一併中性化,
否則會在面板外圍多一圈色帶。 */
#chat_upload_zone { margin: 0 16px 16px !important; border-top: none !important; }
#chat_upload_zone .form {
background: transparent !important;
border: none !important;
}
/* 2026-07-23:chat-col 改成 gap:1rem 的 flex 欄,跟 chatbot 之間的留白
已經由 gap 統一處理,這裡不用再疊加 margin-top(舊註解提到的「技術
執行紀錄」分組已在 Phase 8-B 搬去 canvas-col,跟這裡無關,是過期
說明)。改成跟 .main-chat 同一套 5:1 flex-grow 比例分配高度。 */
/* 2026-07-24(#15):輸入框改「自然高度、貼齊底部」,不再用 flex:1 佔 1/7
欄高——原本被撐成 153px 但內容只佔一小塊,底下留 61px 空縫(千卉標的
下方縫隙)。對話視窗改 flex:1 1 auto 吃掉剩餘全部高度,輸入框 flex:0 0 auto
縮到內容高度。 */
#chat_upload_zone {
flex: 0 0 auto !important;
min-height: 0 !important;
display: flex !important;
flex-direction: column !important;
}
/* Gradio MultimodalTextbox 的內層 padding 左右不對稱(full-container 12px、
input-wrapper 右 4px、textarea 0)造成左 14/右 18 不一致(#15)。統一:
full-container 對稱 12px、input-wrapper 歸零,textarea 左右緣一致。 */
/* 2026-07-24 千卉:左/下要跟右邊一樣的留白。原本 full-container 上下 10px、
左右 12px,加上內層殘留造成 textarea 距欄 左14/右15/下17 不一致。改成
四邊統一 14px,內層一律歸零,四邊留白就一致。 */
#chat_upload_zone .full-container { padding: 14px !important; }
#chat_upload_zone .input-wrapper,
#chat_upload_zone .input-row { padding: 0 !important; margin: 0 !important; }
#chat_upload_zone textarea { padding: 0 !important; }
/* textarea 只有 67px 但 input-row 是 72px(拿掉送出鈕後留下的槽位高度),
下緣多 5px 空隙、跟左右留白對不齊。讓 textarea 撐滿整列高度即可。 */
#chat_upload_zone .input-row { align-items: stretch !important; }
#chat_upload_zone textarea { height: 100% !important; }
/* 格式要求直接寫在上傳卡片裡,跟卡片本身有明顯區隔但仍在同一張卡片內,
使用者不用往上捲回對話視窗找 AI 說過的規則 */
/* 已經在 upload-zone 卡片裡了,卡片本身就有外框,這裡不再加第二層框線/底色。
注意:Gradio 把 elem_classes 同時蓋在外層 wrapper(.block.upload-hint)
跟內層 markdown 內容(.prose.upload-hint)兩個 div 上,凡是用裸的
.upload-hint 當 selector,padding/margin/border 都會在兩層各套一次、
疊加成兩倍間距或雙重邊框(已踩坑兩次:先是雙重分隔線,後是跟下面拖放框
距離過遠)。統一寫法:只在 .block.upload-hint(外層)設定間距,
.prose.upload-hint(內層)明確歸零,字級/顏色類不影響版面的規則才維持
用裸 .upload-hint(重複套用無副作用) */
.upload-hint.block {
padding: 0 0 .9rem !important;
margin-bottom: .9rem !important;
}
.upload-hint.prose {
padding: 0 !important;
margin: 0 !important;
}
.upload-hint {
font-size: var(--font-size-base) !important;
}
.upload-hint p, .upload-hint li { font-size: var(--font-size-base) !important; color: var(--c-text-soft) !important; }
.upload-hint strong { color: var(--c-accent-strong) !important; }
/* 同陷阱 13 第 3 點:<span class="md"> 預設 inline 卻裝 block 的 <p>,
會在最前面產生依自己字級算出的隱形空隙,這裡一併修正 */
.upload-hint .md {
display: block !important;
font-size: var(--font-size-base) !important;
}
.upload-actions {
margin-top: 1rem !important;
gap: .6rem !important;
}
.upload-actions button { border-radius: 10px !important; font-weight: 700 !important; }
/* 「回首頁」連結(原「開始新分析」按鈕,2026-07-30 改名+改視覺語彙)與其
確認對話框:換情境/回首頁共用同一套機制,elem_id="reset_btn" 與後端
wiring 完全不變,只是文案/圖示/樣式改了。
千卉這輪指定:樣式要跟 #result_nav_col .result-nav-btn 那組「文字連結」
一致(無邊框、無底色、置左),不要另創一套——做法不是新增一個 class,
是把 #reset_btn 併進下方 #result_nav_col .result-nav-btn 那幾條共用規則
的選擇器清單(見該處),這裡只留 margin-top(它在 nav-items-scroll 裡
排在已隱藏的 scenario-buttons 之後,用來跟上面留出一點呼吸空間,
result-nav-btn 本身沒有這個需求,所以留在這裡單獨設,不搬過去)。 */
.reset-btn { margin-top: .8rem !important; }
/* 2026-07-24:確認框從 nav-col 內嵌卡片改成置中 modal。
刻意不對 overlay 設 display——Gradio 的 visible=False 就是靠 display:none
生效,這裡若寫 display:flex !important 會把「隱藏」本身蓋掉,對話框變成
永遠開著。改用「維持 Gradio 給的 flex 排版 + 面板自己 margin:auto」達成
水平垂直置中,完全不碰 display 這個屬性。 */
.reset-confirm-overlay {
position: fixed !important;
inset: 0 !important;
z-index: 120 !important;
padding: 1.5rem !important;
background: rgba(15, 23, 42, .55) !important;
backdrop-filter: blur(2px);
}
.reset-confirm-panel {
margin: auto !important; /* flex 容器內上下左右皆置中 */
width: 100% !important;
max-width: 400px !important;
flex: none !important; /* 不被 overlay 的 flex 拉伸成滿版高 */
gap: 1.2rem !important;
padding: 1.5rem !important;
border: 1px solid var(--c-border-strong) !important;
border-radius: 14px !important;
background: var(--c-surface) !important;
box-shadow: 0 20px 50px rgba(15, 23, 42, .3) !important;
}
/* 深色模式:頁面本身已經是深色(--c-surface #0d1526),同一階深灰疊上去
幾乎看不出後面被壓暗,遮罩形同不存在。改用更黑的值才有「後面那層被關掉」
的分離感。兩種寫法都給:.dark 是 Gradio 切主題時掛的 class,媒體查詢則
對應本檔既有的 prefers-color-scheme token 機制(見 _DARK_MODE_HEAD_CSS)。 */
.dark .reset-confirm-overlay { background: rgba(0, 0, 0, .62) !important; }
@media (prefers-color-scheme: dark) {
:root:not(.light) .reset-confirm-overlay { background: rgba(0, 0, 0, .62) !important; }
}
.reset-confirm-panel .reset-confirm-text p { margin: 0 !important; }
/* 橫式:兩顆按鈕同寬並排,窄視窗也不換行 */
.reset-confirm-actions {
flex-wrap: nowrap !important;
gap: .7rem !important;
}
.reset-confirm-actions button {
border-radius: 10px !important;
font-weight: 700 !important;
}
/* 2026-08-26:「關於」面板沿用 .reset-confirm-overlay 同一套置中 modal
機制(同一個 elem_classes 掛在 overlay 上),但內容是說明文字不是
一兩句確認詞,.reset-confirm-panel 的 400px 版寬會把整段文字擠成
窄豆腐塊,所以另開 .about-panel 加寬;內容也比確認框長,視窗矮的時候
(筆電、手機橫向)要能捲動,不能被 overlay 裁掉看不到下半段。文字
對齊也從確認框的置中改左靠——長段落置中會很難讀。 */
.about-panel {
margin: auto !important;
width: 100% !important;
max-width: 560px !important;
max-height: min(80vh, 640px) !important;
overflow-y: auto !important;
flex: none !important;
gap: 1rem !important;
padding: 1.6rem 1.8rem !important;
border: 1px solid var(--c-border-strong) !important;
border-radius: 14px !important;
background: var(--c-surface) !important;
box-shadow: 0 20px 50px rgba(15, 23, 42, .3) !important;
text-align: left !important;
}
.about-panel h2, .about-panel h3 {
text-align: left !important;
}
.about-panel hr {
border: none !important;
border-top: 1px solid var(--c-border-strong) !important;
margin: .6rem 0 !important;
}
.about-panel .about-close-row {
display: flex !important;
justify-content: flex-end !important;
}
.about-panel .about-close-row button {
border-radius: 10px !important;
font-weight: 700 !important;
}
/* Phase 8-D:結果導覽(情境選定後顯示在開始新分析按鈕下方),純文字
標籤(暫不用圖示,等下一輪視覺打磨補 Heroicons SVG),維持跟
scenario-buttons 一致的「貼齊左邊、無強調色底」低視覺重量風格,
避免搶過主要操作按鈕的注意力 */
/* 左右留白已上移到 .nav-col(見該處註解),這裡不再重複設定,避免疊加成
雙倍縮排。上緣的 border-top 因此會跟著欄位的 padding 內縮 8px,跟下方
.result-nav-divider 本來就有的內縮一致。 */
.result-nav {
margin-top: 1rem !important;
padding-top: 1rem !important;
border-top: 1px solid var(--c-border-strong) !important;
gap: .3rem !important;
}
/* 2026-07-23:導覽項目改用 Bootstrap 5.3 `.nav-underline` 的視覺模型
(getbootstrap.com/docs/5.3/components/navs-tabs/)——未選取的項目是
純文字,沒有邊框也沒有底色;選取的項目只換文字顏色再加一條粗線。官方
的線畫在下緣(橫向分頁用),這裡是垂直導覽,依慣例改畫在左緣。
千卉回饋原本「看起來像很多按鈕」的真正原因不是這裡的樣式寫錯,而是
下面這個選擇器少了 id 祖先:Gradio 自己的 secondary 按鈕樣式(Svelte
編譯出 button.svelte-xxx.secondary 這種 type+class 組合)specificity
比純 class 高,即使掛 !important 也贏不了,所以 border:none 一直沒生效、
Gradio 預設的框線與底色整個透出來。子項目當初已經用 #result_nav_col
修過(見下方註解),主項目漏掉了,這次補上。
2026-07-30:「回首頁」(#reset_btn)併入這組選擇器清單,跟 nav_top_btn
(AI對話)等結果導覽項目共用一模一樣的視覺語彙。#reset_btn 不在
#result_nav_col 底下,直接用 id 自己的 specificity(1,0,0)就夠贏過
Gradio 的 button.svelte-xxx.secondary,不需要額外的祖先。 */
#result_nav_col .result-nav-btn,
#reset_btn,
#nav_guide_btn,
#nav_about_btn {
width: 100% !important;
justify-content: flex-start !important;
text-align: left !important;
border: none !important;
background: transparent !important;
box-shadow: none !important;
}
/* 2026-07-24(5-B):改成 ChatGPT 側欄那種「填色」互動模型——未選純文字、
hover 淡背景、按下更深、選取淡主色背景+主色文字。圓角讓填色區塊內縮、
不貼齊欄邊。拿掉原本 Bootstrap nav-underline 的左緣線(千卉指定改填色)。 */
#result_nav_col .result-nav-btn,
#reset_btn,
#nav_guide_btn,
#nav_about_btn {
padding: .5rem .7rem !important;
font-size: var(--font-size-base) !important;
font-weight: 500 !important;
border-radius: 8px !important;
color: var(--c-text-soft) !important;
transition: background-color .15s ease-out, color .15s ease-out !important;
}
#result_nav_col .result-nav-btn:hover:not(:disabled),
#reset_btn:hover:not(:disabled),
#nav_guide_btn:hover:not(:disabled),
#nav_about_btn:hover:not(:disabled) {
background: rgba(var(--c-accent-rgb),.08) !important;
color: var(--c-text) !important;
}
#result_nav_col .result-nav-btn:active:not(:disabled),
#reset_btn:active:not(:disabled),
#nav_guide_btn:active:not(:disabled),
#nav_about_btn:active:not(:disabled) {
background: rgba(var(--c-accent-rgb),.16) !important;
}
/* 歷史紀錄:這裡原本有一整組 .result-nav-subbtn 樣式,把統計比較分析底下
的四頁做成「小一號字+灰一階+真縮排」的子選單(縮排寫法是 2026-07-23
從 Bootstrap 官方 Sidebars 範例的 .btn-toggle-nav a 移植過來的,用
margin-left 真的把元素往右推、寬度跟著縮,而不是只用 padding-left 讓
文字位移)。同日千卉決定四頁改用跟其他導覽項目一模一樣的樣式並各自配
圖示、左緣齊平,這組樣式整個不再需要,連同 class 一起移除。
Gradio secondary 按鈕邊框那個 specificity 坑不會因此消失——它現在由
上方 `#result_nav_col .result-nav-btn` 那組共用規則一併處理。 */
/* 2026-07-24:這裡原本有 .result-nav-accordion 與其 .label-wrap 的樣式
(拿掉 Gradio .padded 的水平 12px、標題列對齊其他導覽項目),隨
「統計比較分析」Accordion 一起移除。 */
/* 2026-07-23:頁面切換模型——目前使用者所在的那一頁在導覽列高亮,
applyActivePage() JS 幫對應按鈕加/移除這個 class。視覺先求「看得出
選中誰」的基本可用,細部樣式(跟 Bootstrap 借來的分組/層級規範)留給
下一輪 nav-col 視覺重做一起處理,這裡不重複做兩次。 */
#result_nav_col .result-nav-btn.active {
background: rgba(var(--c-accent-rgb),.14) !important;
color: var(--c-accent-strong) !important;
font-weight: 700 !important;
}
/* 技術執行紀錄:低優先度診斷工具,跟上面的分析結果用分隔線+降低視覺
權重(字級與顏色都比其他導覽項目再低一階)區隔開來 */
.result-nav-divider {
height: 1px;
background: var(--c-border-strong);
margin: .5rem .3rem;
}
/* 同樣要帶 #result_nav_col,否則純 class(0,1,0)會被上方
`#result_nav_col .result-nav-btn`(1,1,0)的字級/顏色蓋掉,
技術執行紀錄就不再是降權的樣子 */
/* 2026-07-24 千卉:導覽列所有項目字級一致(以「AI對話」的 .88rem 為基準)。
技術執行紀錄原本 .82rem 比其他項目小一階,改回 .88rem;它的「低優先度」
仍靠顏色(--c-text-muted)與上方分隔線表達,不再靠縮小字級。 */
/* 2026-08-12:「使用說明」加在 nav-items-scroll 最後一項之後,發現它被推到
整個捲動區的最底部——Gradio 給 gr.Column 的預設是 flex:1 1 auto,
result_nav_col 因此把剩餘高度全部吃掉(實測長到 729px,內容其實只有
400 出頭)。業主要的是「最後一個項目下方」,不是「釘在欄位底部」,
所以讓它只佔內容高度,使用說明就會緊跟在技術執行紀錄下面。 */
#result_nav_col {
flex: 0 0 auto !important;
}
#result_nav_col .result-nav-btn--muted,
#nav_guide_btn.result-nav-btn--muted,
#nav_about_btn.result-nav-btn--muted {
font-size: var(--font-size-base) !important;
font-weight: 500 !important;
color: var(--c-text-muted) !important;
}
/* 語料 Metadata 說明表單卡片:跟 .upload-zone 同批扁平化(2026-07-23),
兩者是同一種「Canvas 裡的操作區塊」,樣式要保持一致 */
/* 2026-07-31(Batch E 第 4-c 項):同 .upload-zone 那則註解,左右內距
歸零改吃 canvas-scroll 統一的 20px(原本 1.6rem 額外內距造成標題左緣
跟步驟列對不齊),頂部內距也歸零壓掉多餘留白,只留底部內距。 */
/* 2026-07-31(Batch F):Batch E 把左內距整個歸零,副作用是標題文字直接
貼死在 canvas-scroll 的 20px 基準線上(業主實測回報「標題字貼到左邊
邊框了」)。這裡補回左內距 1.5rem(沿用 .analysis-panel 內距同一套
數值,不另創),只補左邊、不動右邊/頂部/底部,維持 Batch E 顧到的
「zone 本身的框(雖然這塊沒有可視邊框,但量測用的 bounding box)左緣
仍落在 canvas-scroll 20px 基準線上」,只是內容(標題與底下的表單/
卡片)整體再往內縮 1.5rem,跟步驟列基準線的關係從「內容緊貼」變回
「內容有呼吸空間」。 */
/* 2026-07-31(Batch G 第 2 項):業主回報「語料 Metadata 說明」標題上方
留白比其他階段標題多。實測發現根因不在 .metadata-zone 自己的
margin-top(跟 .upload-zone/.scenario-select-zone 用同一份 .6rem),
而是它上面那個兄弟節點 upload_progress_zone(3-C 加的上傳進度卡片,
elem_id="upload_progress_zone"):格式驗證通過、換頁到語料說明時,
它的內容被清空成空字串(見 on_upload_confirm 最後一個 yield),但
Gradio 幫 gr.HTML() 包的 .html-container 就算內容是空字串,仍保留
預設的 10px 12px 內距,等於平白多墊了 20px 高度的空白卡片在語料
說明標題正上方,這是量出來的留白比研究選擇/文本上傳多出一大截
的真正原因(不是 .metadata-zone 自己的留白設定有問題)。
修法:不改動 upload_progress_zone 那個共用元件的空狀態渲染(那是
跟「分析運算」進度卡共用的機制,改了會牽動另一個地方,超出這批
範圍),改用負 margin-top 直接讓 .metadata-zone 往上收,抵銷掉這段
多出來的空白卡片高度,讓最終畫面上的留白收斂到跟其他三個標題一致
的 32px(研究選擇的現有數值,改動最小)。這個負值只在「upload_
progress_zone 已清空、metadata-zone 剛顯示」這個時間點生效,兩者
在畫面上都沒有可見邊框/背景,往上收不會露出接縫或裁切。 */
.metadata-zone {
border: none !important;
border-radius: 16px !important;
background: none !important;
padding: 0 0 1.4rem 1.5rem !important;
margin: -1.65rem 0 1rem !important;
box-shadow: none !important;
animation: popupIn .25s cubic-bezier(.2,.8,.3,1) !important;
}
.metadata-zone .gr-box, .metadata-zone .form { background: transparent !important; }
/* 3-A:研究選擇區塊(工作區第 1 步),跟 .upload-zone/.metadata-zone
同一種「Canvas 裡的操作區塊」,樣式沿用同一批扁平化卡片語彙,不另創
一套視覺風格。 */
/* 2026-07-31(Batch E 第 4-c 項):同 .upload-zone,左右內距歸零、頂部
內距歸零,讓「研究選擇」標題左緣跟步驟列第一顆藥丸左緣對齊、上方
留白縮小。 */
/* 2026-07-31(Batch F):同 .metadata-zone 那則 Batch F 註解,左內距
歸零的副作用是標題貼死在步驟列基準線上,補回 1.5rem 左內距(跟
.analysis-panel 同一套數值)。zone 本身的 bounding box 左緣不受影響
(padding 加在框內,不是 margin),仍對齊 canvas-scroll 20px 基準線,
只有內容(標題與底下的情境卡片)整體往內縮。 */
.scenario-select-zone {
border: none !important;
border-radius: 16px !important;
background: none !important;
padding: 0 0 1.4rem 1.5rem !important;
margin: .6rem 0 0 !important;
width: 100% !important;
box-shadow: none !important;
animation: popupIn .25s cubic-bezier(.2,.8,.3,1) !important;
}
/* 2026-07-24 千卉:①「原文標題/作者/語言…」這些欄位標籤在深色模式吃到
Gradio 自己的樣式(實測 #475569,對比只有 2.40:1,遠低於 AA 4.5),跟旁邊
表格表頭「群組標籤」(--c-text-muted #94a3b8、6.92:1)落差很大。統一成
--c-text-muted。②兩張表格沒撐滿容器(容器內寬 1069、表格只有 1051,右側
空 18px),Gradio dataframe 的 table 寬度是依內容算的,補 width:100%。 */
#metadata_zone .container > span,
#metadata_zone label > span {
color: var(--c-text-muted) !important;
}
#metadata_zone table { width: 100% !important; }
/* 2026-08-12(業主截圖標了五個 ✗):語料說明表單的欄位被 Gradio 自動包成
白色卡片,跟下面「群組層資訊」那種「小標題+內容直接攤開」的樣子不一致。
實測拆出來是兩層各做一半:
.form(svelte-d5xbca) 底透明但有 1px 外框 → 卡片的框
.block.padded(含 fieldset)白底 + 10px 12px 內距 → 卡片的白色填充
兩層都要處理才會真的消失,只清其中一層會留下框或留下白塊。用 `>` 只打
「.form 直屬的那層」,避免波及群組層/個體層那兩個 Dataframe(它們是
metadata_zone 的直屬子項,不在 .form 裡面,外框要留著)。
內距歸零之後,欄位左緣自然跟「原文資訊」小標題與下方表格對齊,這就是
業主要的「靠左對齊」;欄位本身的邊框(--c-input-border)保留,截圖上
打叉的是外面那層白卡片,不是輸入框自己。 */
#metadata_zone .form {
border: none !important;
background: transparent !important;
box-shadow: none !important;
overflow: visible !important;
gap: 1rem !important;
/* align-items 從預設的 stretch 改 flex-start,是「群組數量/比較方式」
兩組 radio 被裁掉一半的真正解法(那是既有 bug,業主截圖裡就已經被裁,
不是這批改動造成的)。原因:.form 是 flex 列,stretch 會把每個欄位的
高度統一拉成整列的高度,而那個列高只算到 radio 的選項區(61px),
沒有把上面的欄位標籤(20px+8px 下距)算進去,多出來的 28px 就被
fieldset 自帶的 overflow:auto 裁掉。改成 flex-start 之後每個欄位各自
用自己的內容高度,裁切消失。
排除過但無效的方向(留記錄免得下次重試):改 overflow、加
flex-shrink:0、min-height:fit-content/max-content、把 fieldset 改成
flex column——前三個不影響那個被算錯的列高,最後一個會讓標籤跟選項
變成左右並排。 */
align-items: flex-start !important;
}
#metadata_zone .form > .block,
#metadata_zone .form > fieldset.block {
background: transparent !important;
border: none !important;
box-shadow: none !important;
padding: 0 !important;
/* Gradio 給 fieldset 的 overflow:auto 是裁掉標籤的那把刀(真因見上面
.form 的 align-items 註解),改 visible 當第二道保險。 */
overflow: visible !important;
}
/* 2026-08-12(業主):「群組數量/比較方式」這兩個欄位標籤要跟「原文標題/
作者」一樣是小灰字粗體。Gradio 給文字欄位標籤的是 .7rem/700/灰,但
radio 的 fieldset 標籤走另一條規則,渲染成一般大小的深色字,兩種欄位
放在同一張表單裡就不同調。這裡的 .7rem 不是新發明的字級,是對齊 Gradio
既有的欄位標籤值(實測文字欄位標籤就是 11.2px=.7rem/700/
--c-text-muted),所以不套 --font-size-base(那是內文用的,標籤比內文小
一階是這個表單本來的層級設計)。 */
#metadata_zone fieldset.block > span {
font-size: .7rem !important;
font-weight: 700 !important;
color: var(--c-text-muted) !important;
margin-bottom: 8px !important;
}
/* 「比較結構」從 gr.Group 改成 gr.Column 之後(見 app_agent.py 同批註解),
Gradio 那條藍色標題橫幅與外框就不存在了,這裡只補回跟其他小標題一致的
上下節奏——它的 Markdown 用的是同一個 `**粗體**` 寫法,顏色/字級自動與
「原文資訊」「群組層資訊」相同,不用另外設。 */
#meta_compare_group {
background: transparent !important;
border: none !important;
padding: 0 !important;
gap: 0 !important;
}
/* Phase 8-C:結果內容區塊(語言指標分析/統計比較分析/LLM輔助分析),
從原本的 gr.Tab()(自帶 .tabitem padding)改成 gr.Column,這裡補上
跟上面 upload-zone/metadata-zone 一致的垂直間距,避免緊貼在一起 */
/* 2026-07-24(左留白統一):水平內距集中在這裡(1.25rem),所有結果頁
(語言指標分析/統計四子頁/LLM輔助分析/技術執行紀錄)左右緣一致。
統計頁的 .tabitem 水平 padding 因此歸零,避免 canvas-result + tabitem
疊加成雙重縮排(先前「語言指標分析」頁沒有 tabitem、內容貼左緣,跟其他頁
對不齊,就是這個差異)。 */
/* 水平內距已上移到 .canvas-scroll 統一處理(見該處註解),這裡只留上方間距,
否則會跟 canvas-scroll 疊成兩倍縮排。 */
.canvas-result { margin-top: 1.2rem !important; padding-left: 0 !important; padding-right: 0 !important; }
/* 2026-07-24:「分析運算」內容區塊——運算中顯示三個子階段進度,運算後變成
分段摘要(標題+狀態+一句結論+查看入口)。取代舊的單行 .computing-zone
「運算中…(步驟 N/3)」提示(元件與 elem_id 沿用 computing_zone)。
這塊卡片同時也是 _build_upload_html「文本上傳」進度卡片共用的同一組
CSS 語彙(見 .analysis-panel-title 下方 2026-07-31 註解),不是各自
重複刻一份。 */
/* 2026-07-31(Batch E 第 4-c 項):這是唯一一個真正有外框/底色的「卡片」
(其他內容區塊如 upload-zone/metadata-zone 都已扁平化、沒有可視邊框),
computing_html/upload_progress_html 這兩個 gr.HTML() 元件預設會被
Gradio 包一層 .html-container(10px 12px 內距,見 .html-container:
has(.app-header) 那則註解),這裡比照同一招數精準鎖定「包住
.analysis-panel 的那一個」歸零,讓卡片本身的左緣落在 canvas-scroll
統一的 20px 基準線上,卡片左緣才能跟其他內容區塊(研究選擇/文本上傳/
語料說明/結果頁)的左緣落在同一條線上。 */
.html-container:has(.analysis-panel) {
padding: 0 !important;
}
/* 2026-07-31(Batch G 第 2 項):四個階段標題的上方留白統一收斂到 32px
(量測基準:header 步驟列底緣到標題文字頂緣的距離,貼近「研究選擇」
現有數值,改動最小)。這張卡片同時是「文本上傳」進度卡與「分析運算」
內容區塊共用的樣式,原本 margin-top 1.2rem + padding-top 1.2rem
(卡片外緣到標題文字共 ≈39px)比目標多,改窄 margin-top 到 .75rem,
卡片外緣往上收,標題(.analysis-panel-title 是 margin:0 的 h2,
不吃額外標題留白)跟著一起收斂到 32px 左右,不動 padding(左右/底部
內距與卡片內 analysis-row 的呼吸空間維持不變,只調外距)。 */
.analysis-panel {
border: 1px solid var(--c-border-strong) !important;
border-radius: 14px !important;
background: var(--c-surface2) !important;
padding: 1.2rem 1.5rem 1.4rem !important;
margin-top: .75rem !important;
}
/* 2026-07-31(Batch E 第 4-c 項,已被下方 Batch F 註解取代):卡片本身
保留左右內距(1.5rem)維持內容呼吸空間,裡面的狀態列(analysis-row)
都還需要跟卡片邊框有安全距離,不能整塊歸零(那樣會變成上面
.upload-zone 那種完全扁平卡片,跟這裡刻意保留的「有外框的卡片」設計
衝突)。當時改成只把標題本身用負 margin-left(等於卡片左內距的
1.5rem)拉出去,讓標題文字獨自貼齊卡片左側邊框,卡片邊框本身又落在
canvas-scroll 的 20px 基準線上(見上方 .html-container:has
(.analysis-panel) 那則),兩者相加後標題文字左緣正好等於步驟列第一顆
藥丸的左緣。 */
/* 2026-07-31(Batch F):業主實測回報「文本上傳」進度卡片(用的正是這個
.analysis-panel-title)標題貼死在卡片邊框上,就是上面這個負
margin-left 造成的,它把標題硬拉到跟卡片邊框零距離,等於故意抵銷掉
.analysis-panel 自己的 1.5rem 左內距。業主裁示的修法是「卡片外框對齊
步驟列、標題往內縮」,不是「標題貼齊步驟列、卡片跟著位移」,所以拿掉
這個負 margin,讓標題乖乖吃卡片本身既有的 1.5rem 左內距即可,跟卡片
內的 analysis-row 狀態列用的是同一份內距,兩者左緣自然對齊、不再各吃
各的規則。 */
.analysis-panel-title {
margin: 0 0 1rem 0 !important;
font-size: 1.05rem !important;
font-weight: 700 !important;
color: var(--c-text) !important;
}
/* 2026-08-05:分析運算進行中的連線提醒,低視覺重量(跟標題/狀態列比起來
是次要資訊),用 warn 色系底+左邊框做出「提示」語彙,不搶過主要內容。 */
.analysis-panel-hint {
margin: 0 0 1rem 0 !important;
padding: .6rem .8rem !important;
border-left: 3px solid var(--c-warn) !important;
border-radius: 4px !important;
background: rgba(180,83,9,.08) !important;
font-size: .82rem !important;
line-height: 1.5 !important;
color: var(--c-text-muted) !important;
}
.dark .analysis-panel-hint { background: rgba(251,191,36,.10) !important; }
.analysis-row {
display: flex;
flex-direction: column;
gap: .25rem;
padding: .7rem 0;
border-top: 1px solid var(--c-border-strong);
}
.analysis-row:first-of-type { border-top: none; padding-top: 0; }
.analysis-row-head { display: flex; align-items: center; gap: .55rem; }
.analysis-row-title {
font-size: var(--font-size-base) !important;
font-weight: 600 !important;
color: var(--c-text) !important;
}
.analysis-row-body {
padding-left: 1.65rem; /* 對齊圖示右側,讓結論文字跟標題文字左緣一致 */
font-size: var(--font-size-base);
color: var(--c-text-soft);
display: flex;
flex-wrap: wrap;
align-items: center;
gap: .3rem .9rem;
}
.analysis-row--pending .analysis-row-title,
.analysis-row--pending .analysis-row-body { color: var(--c-text-muted) !important; }
.analysis-entry {
color: var(--c-accent-strong) !important;
font-weight: 600;
cursor: pointer;
text-decoration: none;
}
.analysis-entry:hover { text-decoration: underline; }
/* 狀態記號:完成 ✓、失敗 !、等待中 •,尺寸統一,跟旋轉動畫的 spinner 佔位一致 */
.analysis-mark {
flex: none;
width: 20px; height: 20px;
display: inline-flex; align-items: center; justify-content: center;
border-radius: 50%;
font-size: .72rem; font-weight: 700; line-height: 1;
}
.analysis-mark--done { background: rgba(34,197,94,.15); color: #16a34a; }
.analysis-mark--failed { background: rgba(220,38,38,.15); color: #dc2626; }
.analysis-mark--pending{ background: var(--c-surface); color: var(--c-text-muted);
border: 1px solid var(--c-border); }
/* 轉圈動畫:踩坑(2026-07-24)——border 這個「簡寫」在這版 Gradio 的
pipeline 下整條不會生效(跟本檔 background 簡寫的踩坑同一類),實測
border-width 算出來是 0px、圓圈整個沒被畫出來,只有同規則裡的
border-top-color 長寫法有作用。改成全部拆成長寫法子屬性就正常。 */
.analysis-spinner {
flex: none;
width: 20px; height: 20px;
border-radius: 50%;
border-width: 2.5px !important;
border-style: solid !important;
border-color: rgba(var(--c-accent-rgb),.25) !important;
border-top-color: var(--c-accent) !important;
animation: computingSpin .8s linear infinite;
}
@keyframes computingSpin { to { transform: rotate(360deg); } }
/* 2026-07-24(B2):Gradio 對「正在串流輸出」的元件(block wrapper)自動加
.generating class,帶一圈會呼吸的藍色邊框動畫。頂部步驟列與分析運算內容
區塊都是同一個 generator 的輸出,各被套一圈,畫面上兩個藍框一起閃、很礙眼。
這個 app 不需要這個效果,整站關掉。 */
.generating {
border-color: transparent !important;
animation: none !important;
box-shadow: none !important;
}
/* 2026-07-24(A2):結果導覽項目在對應結果產出前是 disabled(見 create_app
的 interactive=False 與 _populate_tabs 的閘門)。給明確的灰階+禁用游標,
讓「還不能點」一眼看得出來,不是壞掉。 */
#result_nav_col .result-nav-btn:disabled,
#result_nav_col .result-nav-btn[disabled] {
opacity: .38 !important;
cursor: not-allowed !important;
}
.upload-area, [data-testid="droparea"] {
border: 2px dashed rgba(var(--c-accent-rgb),.28) !important;
border-radius: 12px !important;
background: rgba(var(--c-accent-rgb),.03) !important;
transition: border-color .2s ease, background .2s ease !important;
}
.upload-area:hover, [data-testid="droparea"]:hover {
border-color: rgba(var(--c-accent-rgb),.5) !important;
background: rgba(var(--c-accent-rgb),.08) !important;
}
.file-preview {
border: 1px solid var(--c-border-strong) !important;
border-radius: 10px !important;
background: var(--c-surface) !important;
}
/* 修正:此版 Gradio 的 File(list) 元件在 flex 版面下,外層 holder 會因
min-width:0 而整個縮成 0~4px 寬,導致內容(含下載連結)視覺上溢出到
容器外,要橫向捲動才找得到下載按鈕。強制給滿版寬度,並固定「下載」欄
在最前面、檔名欄自動換行,避免長檔名把下載連結擠出視野。 */
.file-preview-holder {
width: 100% !important;
flex: 1 1 auto !important;
min-width: 0 !important;
}
.file-preview {
table-layout: fixed !important;
width: 100% !important;
}
.file-preview tr > :first-child {
width: auto !important;
white-space: normal !important;
word-break: break-all !important;
}
.file-preview tr > :nth-child(2) {
width: 92px !important;
white-space: nowrap !important;
}
.file-preview tr > :last-child {
width: 34px !important;
}
/* ── 6. BUTTONS ────────────────────────────────────────────────── */
button.primary, button[data-testid="submit"] {
background: linear-gradient(135deg, #0284c7 0%, #4f46e5 100%) !important;
border: none !important;
border-radius: 9px !important;
font-weight: 700 !important;
font-size: var(--font-size-base) !important;
color: #fff !important;
box-shadow: 0 2px 12px rgba(2,132,199,.28) !important;
transition: transform .18s ease, box-shadow .18s ease, opacity .18s !important;
cursor: pointer !important;
letter-spacing: .02em;
}
button.primary:hover { transform: translateY(-2px) !important; box-shadow: 0 6px 22px rgba(2,132,199,.40) !important; }
button.primary:active { transform: translateY(0) !important; opacity: .92 !important; }
button.primary:disabled { opacity: .5 !important; cursor: not-allowed !important; transform: none !important; }
button.secondary {
background: rgba(var(--c-accent-rgb),.08) !important;
border: 1px solid rgba(var(--c-accent-rgb),.30) !important;
color: var(--c-accent-strong) !important;
border-radius: 9px !important;
font-weight: 600 !important;
font-size: var(--font-size-base) !important;
cursor: pointer !important;
transition: background .18s ease, border-color .18s ease !important;
}
button.secondary:hover {
background: rgba(var(--c-accent-rgb),.16) !important;
border-color: rgba(var(--c-accent-rgb),.45) !important;
}
/* ── 7. INPUTS ─────────────────────────────────────────────────── */
textarea, input[type="text"] {
background: var(--c-input-bg) !important;
border: 1px solid var(--c-input-border) !important;
border-radius: 9px !important;
color: var(--c-text) !important;
font-size: var(--font-size-base) !important;
line-height: 1.6 !important;
transition: border-color .18s ease, box-shadow .18s ease !important;
}
textarea:focus, input[type="text"]:focus {
border-color: var(--c-accent) !important;
box-shadow: 0 0 0 3px rgba(var(--c-accent-rgb),.16) !important;
outline: none !important;
}
/* ── 8. DATAFRAME ──────────────────────────────────────────────── */
.table-wrap {
border: 1px solid var(--c-border-strong) !important;
border-radius: 12px !important;
overflow: hidden !important;
}
/* 2026-08-12(業主回報語料 metadata 表單「文字跟表頭背景黏在一起」):
表頭底色原本借用 --c-surface2,淺色是 #f1f5f9 疊在白色資料列上只有
1.04:1,表頭等於沒有存在感;改用獨立 token --c-thead-bg(淺色 #e2e8f0,
對白 1.19:1,表頭文字 --c-text-muted 在其上仍有 6.15:1 過 AA),深色沿用
原本的 #111827 不變。表頭與資料列真正的分界靠下面那條 border-bottom,
從 rgba(accent,.16)(約 1.2:1)換成結構線 token。 */
table thead tr { background: var(--c-thead-bg) !important; }
table thead th {
background: transparent !important;
color: var(--c-text-muted) !important;
font-weight: 700 !important;
font-size: 0.75rem !important;
text-transform: uppercase !important;
letter-spacing: 0.05em !important;
border-bottom: 1px solid var(--c-border-strong) !important;
padding: 10px 12px !important;
white-space: nowrap;
}
table tbody td {
background: var(--c-surface) !important;
color: var(--c-text-soft) !important;
font-size: var(--font-size-base) !important;
/* 資料列之間的細線用 --c-divider(Tier 2):這種密集重複的線如果也拉到
3:1,整張表會變成網格紙,反而更難讀。結構性的外框與表頭分界才走 Tier 1。 */
border-color: var(--c-divider) !important;
font-family: 'JetBrains Mono', monospace !important;
padding: 7px 12px !important;
}
table tbody tr:nth-child(even) td { background: rgba(var(--c-accent-rgb),.025) !important; }
table tbody tr:hover td { background: rgba(var(--c-accent-rgb),.08) !important; }
/* ── 9. IMAGE FRAMES ───────────────────────────────────────────── */
.image-container, .image-frame {
border: 1px solid var(--c-border-strong) !important;
border-radius: 12px !important;
overflow: hidden !important;
background: var(--c-surface) !important;
}
.image-container img { display: block; width: 100%; height: auto; }
/* ── 10. RESULT SECTION CARDS ──────────────────────────────────── */
.result-card {
background: var(--c-surface);
border: 1px solid var(--c-border-strong);
border-radius: 14px;
padding: 1.25rem 1.5rem;
margin-bottom: 1.25rem;
}
.result-card-title {
font-size: 0.72rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: .08em;
color: var(--c-text-subtle);
margin: 0 0 .75rem;
padding-bottom: .5rem;
border-bottom: 1px solid var(--c-border-strong);
display: flex; align-items: center; gap: 6px;
}
.result-card-title .dot {
width: 6px; height: 6px; border-radius: 50%;
background: var(--c-accent);
display: inline-block;
}
.stat-pill {
display: inline-flex; align-items: center; gap: 5px;
background: rgba(var(--c-accent-rgb),.08);
border: 1px solid rgba(var(--c-accent-rgb),.20);
border-radius: 6px;
padding: 3px 10px;
font-size: 0.78rem;
color: var(--c-accent-strong);
font-weight: 600;
margin: 2px;
}
/* ── 11. MARKDOWN (result tabs) ────────────────────────────────── */
.prose h3, .md h3, .svelte-1ipelgc h3 {
font-family: 'Crimson Pro', Georgia, serif !important;
font-size: 1.25rem !important;
font-weight: 600 !important;
color: var(--c-text) !important;
border-bottom: 1px solid var(--c-border-strong);
padding-bottom: 0.45rem;
margin: 1.4rem 0 .8rem !important;
}
.prose h4, .md h4 {
font-size: 0.85rem !important;
font-weight: 700 !important;
color: var(--c-text-muted) !important;
text-transform: uppercase;
letter-spacing: .06em;
margin: 1rem 0 .4rem !important;
}
.prose strong, .md strong { color: var(--c-accent-strong) !important; }
.prose em, .md em { color: var(--c-accent2-strong) !important; }
.prose code, .md code {
background: var(--c-code-bg) !important;
color: var(--c-code-text) !important;
padding: 2px 6px;
border-radius: 4px;
font-family: 'JetBrains Mono', monospace !important;
font-size: 0.82em;
}
.prose ul li, .md ul li { color: var(--c-text-soft); line-height: 1.75; }
.prose ol li, .md ol li { color: var(--c-text-soft); line-height: 1.75; }
.prose p { line-height: 1.75; color: var(--c-text-soft); }
/* h1/h2/h5/h6/表格/hr/blockquote 補齊——原本只有 h3/h4 有樣式,LLM 報告等
自由格式 Markdown 若真的輸出到這幾個標題等級會吃到瀏覽器預設樣式。
刻意只 scope 在 #canvas_report(LLM 輔助分析頁),不像 h3/h4 沿用全站
.prose/.md 泛用寫法:踩過一次坑才發現 gr.HTML() 元件外層也會被 Gradio
包一層 .prose class(不是只有 gr.Markdown() 才有),首頁標題就是用
gr.HTML() 寫死的 <h1>,全站套用 h1 樣式會蓋掉首頁標題自己的漸層字設計
(color: transparent 被這裡的 color:...!important 蓋掉)。 */
#canvas_report .prose h1, #canvas_report .md h1 {
font-family: 'Crimson Pro', Georgia, serif !important;
font-size: 1.6rem !important;
font-weight: 700 !important;
color: var(--c-text) !important;
border-bottom: 1px solid var(--c-border-strong);
padding-bottom: 0.5rem;
margin: 1.6rem 0 .9rem !important;
}
#canvas_report .prose h2, #canvas_report .md h2 {
font-family: 'Crimson Pro', Georgia, serif !important;
font-size: 1.4rem !important;
font-weight: 600 !important;
color: var(--c-text) !important;
border-bottom: 1px solid var(--c-border-strong);
padding-bottom: 0.45rem;
margin: 1.5rem 0 .8rem !important;
}
#canvas_report .prose h5, #canvas_report .md h5 {
font-family: 'Crimson Pro', Georgia, serif !important;
font-size: 0.95rem !important;
font-weight: 600 !important;
color: var(--c-text-muted) !important;
margin: 1rem 0 .4rem !important;
}
#canvas_report .prose h6, #canvas_report .md h6 {
font-size: 0.78rem !important;
font-weight: 700 !important;
color: var(--c-text-subtle) !important;
text-transform: uppercase;
letter-spacing: .06em;
margin: .8rem 0 .3rem !important;
}
/* 表格——原本完全沒樣式,LLM 報告的表格吃瀏覽器預設白色框線,深色主題下很刺眼 */
#canvas_report .prose table, #canvas_report .md table {
width: 100%; border-collapse: collapse; margin: 1rem 0;
font-size: 0.82rem;
}
#canvas_report .prose th, #canvas_report .md th,
#canvas_report .prose td, #canvas_report .md td {
border: 1px solid var(--c-divider);
padding: .5rem .75rem; text-align: left;
color: var(--c-text-soft);
}
#canvas_report .prose th, #canvas_report .md th {
background: rgba(var(--c-accent-rgb),.08);
color: var(--c-text); font-weight: 700;
}
#canvas_report .prose tr:nth-child(even) td, #canvas_report .md tr:nth-child(even) td { background: var(--c-surface2); }
/* 水平線——原本完全沒樣式,吃瀏覽器預設 */
#canvas_report .prose hr, #canvas_report .md hr { border: none; border-top: 1px solid var(--c-border-strong); margin: 1.5rem 0; }
/* 引言區塊(LLM 報告常用 > 標出結論/總結) */
#canvas_report .prose blockquote, #canvas_report .md blockquote {
border-left: 3px solid var(--c-accent);
padding: .6rem 1rem; margin: 1rem 0;
background: rgba(var(--c-accent-rgb),.05);
color: var(--c-text-soft); font-style: normal;
}
#canvas_report .prose blockquote p, #canvas_report .md blockquote p { margin: 0; }
/* LLM 報告頁專用:模型常把「章節標題」寫成整行粗體而非真正的 ##/### 語法,
CSS 選不出「這行粗體是標題」還是「句中強調」,只能用「整行只有一顆粗體」
這個結構特徵做近似辨識,並非真正標題樣式的替代品 */
#canvas_report .md p:has(> strong:only-child),
#canvas_report .prose p:has(> strong:only-child) {
font-family: 'Crimson Pro', Georgia, serif;
font-size: 1.15rem; font-weight: 600;
color: var(--c-text);
margin: 1.4rem 0 .6rem;
}
/* 報告開頭「研究情境/時間」metadata 兩行,視覺上當副標題處理 */
#canvas_report .md > p:first-of-type,
#canvas_report .prose > p:first-of-type {
color: var(--c-text-muted); font-size: 0.85rem; margin-bottom: 1rem;
}
/* ── 12. LABELS ────────────────────────────────────────────────── */
/* 排除 Radio/Checkbox 選項的 <label><input>...<span>選項文字</span></label>:
這些「藥丸」本身有自己的底色(未選中時剛好跟 --c-text-subtle 深色版同一種
灰),套上這條「小字大寫、次要色」的欄位標籤樣式後文字顏色跟底色一樣,
選項文字整個看不見。用 :has(input) 排除選項本身,只保留純欄位標籤(不含
表單控制項的 label)套用這個樣式 */
.block > .wrap > label:not(:has(input)) > span,
.block > .label-wrap > span,
label:not(:has(input)) > span {
color: var(--c-text-subtle) !important;
font-weight: 700 !important;
font-size: 0.70rem !important;
text-transform: uppercase !important;
letter-spacing: 0.08em !important;
}
/* ── 13. DOWNLOAD SECTION ──────────────────────────────────────── */
.download-bar {
display: flex; align-items: center; gap: 10px;
background: linear-gradient(135deg,rgba(var(--c-accent-rgb),.06),rgba(var(--c-accent2-rgb),.06));
border: 1px solid rgba(var(--c-accent-rgb),.20);
border-radius: 12px;
padding: .8rem 1.1rem;
margin-top: 1rem;
}
.download-bar svg { flex-shrink: 0; }
/* ── 14. SCROLLBAR ─────────────────────────────────────────────── */
/* 2026-07-31(Batch E 第 3 項):業主回報「每個區塊都有 scroll bar 很
干擾視覺」,改成預設不顯示、滑鼠懸停在該區塊時才出現。
刻意不用 display:none 或改 overflow 值:那類做法會讓捲動容器的
內容寬度在有無捲軸間跳動(畫面撐一下縮一下),改成軌道/寬度全程
保持不變(width 維持 5px、track 維持 transparent),只把 thumb 顏色
在「未懸停」時設成 transparent、懸停該容器時才變回原本的
--c-scrollbar/--c-scrollbar-hover——thumb 本身仍佔用同樣的 5px
空間,只是視覺上透明,不會有任何版面位移。
`:hover::-webkit-scrollbar-thumb` 這個寫法是給「目前被滑鼠懸停的
那個元素」套用它自己的捲軸樣式,不需要額外掛在特定 class 上,
全站任何可捲動區塊懸停時都會生效(.nav-col 的項目捲動區、
.canvas-scroll、.main-chat 的訊息區、dataframe 內部捲動……)。 */
::-webkit-scrollbar { width: 5px; height: 5px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: transparent; border-radius: 3px; }
:hover::-webkit-scrollbar-thumb { background: var(--c-scrollbar); }
:hover::-webkit-scrollbar-thumb:hover { background: var(--c-scrollbar-hover); }
/* 2026-08-12(業主回報「表格欄位錯版」)──────────────────────────────
症狀:表格右側出現一條白色空帶,看起來像最後一欄被切掉。
逐項量過全部 16 張表(語料說明 2 張+五個結果頁),**表頭與資料列的欄寬
完全一致、沒有任何一欄真的錯位**,所以不是欄位對不齊,是捲軸佔掉的位置:
Gradio 給 dataframe 的內部容器 .virtual-table-viewport 設了
`max-height:500px; overflow-y:auto`,列數一多就出現垂直捲軸,佔掉 10px
版面寬度,而 .table-wrap 的底色是白的 → 那 10px 就是白帶。實測不捲動時
表格右緣只差 2px(框線),一捲動就跳到 12px,與業主截圖吻合。
踩坑(值得記):一開始想沿用本檔第 14 節那套「平常透明、懸停才出現」的
::-webkit-scrollbar 規則,寫了帶 class 的版本卻完全沒生效。真因不是
specificity——Gradio 在這個元件上設了**標準屬性** scrollbar-width:thin
與 scrollbar-color,而 Chrome 只要看到標準屬性,就會整組忽略 legacy 的
::-webkit-scrollbar 偽元素。所以要改這個元件的捲軸,只能用標準屬性。
**試過但不能用的修法(重要,別再走一次)**:把 max-height 拿掉讓表格
整條攤開、交給 canvas 捲(比照 .exec-log 那個「不要兩層捲動」的既有決定)。
實測會壞掉——Gradio 這個表格是**虛擬捲動**,只渲染視窗內的列,靠自己的
捲動位置決定要畫哪幾列。拿掉 max-height 之後它不再自己捲,虛擬化拿不到
捲動事件:敘述統計那張表變成「保留了 2832px 高度、卻只畫出 1 列」的空表。
所以這個元件的內部捲動必須留著。
最終修法:全部 dataframe 一律 scrollbar-width:none,捲軸不再佔版面寬度,
白帶完全消失(實測 10px→0、表格右緣只剩 2px 框線)。滾輪/觸控/鍵盤
照常可捲,虛擬化不受影響(已實測 scrollTop 有效、列照樣重新渲染)。
範圍是逐表量完才定的——一開始想「只有語料表單藏捲軸、結果頁保留橫向
提示」,量完發現方向剛好相反:會出現白帶的是敘述統計/差異性分析/
監督式學習那三張長表(垂直捲動、不需橫捲),而唯一需要橫向捲軸的語言
指標分析(76 欄)從來不出現垂直捲軸、也就沒有白帶。
代價講明:語言指標分析那條橫向捲軸也跟著隱形,使用者要靠「右緣被切一半
的欄位」+Shift+滾輪察覺還有欄位。覺得提示不夠時,把下面 #canvas_idx
那兩行註解解開即可單獨還原它的捲軸。
!important 是必要的:Gradio 在同一個元素上設 scrollbar-width:thin,
不加就吃不到(語料表單那條是靠 #metadata_zone 的 id 才贏過它)。 */
.virtual-table-viewport {
scrollbar-width: none !important;
}
/* 2026-08-14(業主回報「語言指標分析表格被截斷」):語言指標分析欄數多
(目前實際顯示 62 項指標,欄數隨 HIDDEN_INDICATOR_CODES 增減),橫向
捲軸被上面那條全站規則隱形後,使用者只能看到「右緣被切一半的欄位」,
沒有提示還能左右拖曳看完整表格。單獨還原這裡的橫向捲軸(沿用第 14 節
「預設透明、懸停才出現」那套視覺邏輯,只是這裡是 scrollbar-color 的
標準屬性版本,不是 ::-webkit-scrollbar 偽元素)。
!important 是必要的:上面那條全站規則本身也是 !important,CSS 的
important 優先於一般宣告、跟選擇器精確度無關,這裡不加就贏不了它
(實測套用後 scrollbar-width 電腦算出來還是 none,就是漏了這個)。
2026-08-15:業主回報差異性分析表格(欄數也不少:指標+每組 N/M/SD+
方法/統計量/p值/顯著/效果量名稱/效果量/效果大小)右側欄位一樣
被切掉,同一顆坑,範圍擴大成 #canvas_stat(統計比較分析——敘述統計/
差異性分析/監督式/非監督式共用這個容器)。敘述統計那張表欄數少、
本來就不會超出容器寬度,橫向捲軸不會出現,套用這條規則對它沒有副作用。 */
#canvas_idx .virtual-table-viewport,
#canvas_stat .virtual-table-viewport { scrollbar-width: thin !important; scrollbar-color: transparent transparent !important; }
#canvas_idx .virtual-table-viewport:hover,
#canvas_stat .virtual-table-viewport:hover { scrollbar-color: var(--c-scrollbar) transparent !important; }
/* ── 15. FOCUS & ACCESSIBILITY ─────────────────────────────────── */
:focus-visible {
outline: 2px solid var(--c-accent) !important;
outline-offset: 2px !important;
border-radius: 4px;
}
a { color: var(--c-accent-strong); transition: color .15s; }
a:hover { color: var(--c-link-hover); }
/* ── 16. REDUCED MOTION ────────────────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: .01ms !important;
transition-duration: .01ms !important;
}
}
/* ── 17. RESPONSIVE ────────────────────────────────────────────── */
/* 三欄式工作區(nav-col/chat-col/canvas-col)僅限桌面寬度;手機或窄視窗
(≤900px)chat-col/canvas-col 改回上下堆疊,不再各自獨立捲動(避免
小螢幕出現多層巢狀捲軸),改用頁面本身捲動。nav-col 這裡刻意不列
進來:Phase 8-E 把它改成手機 off-canvas 抽屜(position:fixed,見
「5e. NAV-COL 收合/手機漢堡抽屜」區塊),跟著上下堆疊會跟抽屜的
fixed 定位互相打架。 */
@media (max-width: 900px) {
.main-layout-row { flex-direction: column !important; }
.main-layout-row .chat-col,
.main-layout-row .canvas-col {
width: 100% !important;
max-width: 100% !important;
flex: 1 1 auto !important;
height: auto !important;
overflow-y: visible !important;
}
.canvas-scroll { overflow-y: visible !important; min-height: 0 !important; }
}
@media (max-width: 768px) {
.app-header .title-line { font-size: 1.1rem !important; }
.tabitem { padding: 1rem .75rem !important; }
.app-header .steps { gap: 3px; }
.step-pill { font-size: 0.62rem; padding: 2px 7px; }
}