Spaces:
Sleeping
Sleeping
Download assets/app.css from minchun/II_SpectraEngine: direct link, hf CLI and curl.
- Browser
- Download file 142 kB
-
https://huggingface.co/spaces/minchun/II_SpectraEngine/resolve/main/assets/app.css
- Command line
-
hf download hf://spaces/minchun/II_SpectraEngine/assets/app.css
-
curl -L -o app.css https://huggingface.co/spaces/minchun/II_SpectraEngine/resolve/main/assets/app.css
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) ; | |
| --background-fill-primary: var(--c-surface) ; | |
| --background-fill-secondary: #dfeaf6 ; | |
| --block-background-fill: var(--c-surface) ; | |
| --block-border-color: var(--c-border) ; | |
| --border-color-primary: var(--c-border) ; | |
| --color-accent: var(--c-accent) ; | |
| --color-accent-soft: rgba(2,132,199,.08) ; | |
| --body-text-color: var(--c-text) ; | |
| --block-label-text-color: var(--c-text-muted) ; | |
| --block-title-text-color: var(--c-text-soft) ; | |
| --input-background-fill: var(--c-input-bg) ; | |
| --input-border-color: var(--c-input-border) ; | |
| --button-primary-background-fill: var(--c-accent) ; | |
| --button-primary-text-color: #fff ; | |
| --shadow-drop: 0 1px 3px rgba(15,23,42,.10) ; | |
| } | |
| /* 注意:系統深色模式的 @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) ; | |
| --background-fill-primary: var(--c-surface) ; | |
| --background-fill-secondary: #080f1c ; | |
| --block-background-fill: var(--c-surface) ; | |
| --block-border-color: var(--c-border) ; | |
| --border-color-primary: var(--c-border) ; | |
| --color-accent: var(--c-accent) ; | |
| --color-accent-soft: rgba(14,165,233,.08) ; | |
| --body-text-color: var(--c-text) ; | |
| --block-label-text-color: var(--c-text-muted) ; | |
| --block-title-text-color: var(--c-text-soft) ; | |
| --input-background-fill: var(--c-input-bg) ; | |
| --input-border-color: var(--c-input-border) ; | |
| --button-primary-background-fill: var(--c-accent) ; | |
| --button-primary-text-color: #fff ; | |
| --shadow-drop: 0 1px 3px rgba(0,0,0,.5) ; | |
| } | |
| /* 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% ; | |
| max-width: 100% ; | |
| margin: 0 ; | |
| padding: 0 ; | |
| } | |
| /* .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 ; | |
| max-width: 100% ; | |
| margin-left: 0 ; | |
| margin-right: 0 ; | |
| } | |
| /* 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% ; | |
| margin: 0 ; | |
| } | |
| gradio-app { | |
| display: block ; | |
| height: 100vh ; | |
| overflow: hidden ; | |
| } | |
| .gradio-container { | |
| height: 100vh ; | |
| overflow: hidden ; | |
| } | |
| .app { | |
| height: 100% ; | |
| overflow: hidden ; | |
| } | |
| .wrap { | |
| height: 100% ; | |
| overflow: hidden ; | |
| } | |
| .contain { | |
| flex: 1 1 auto ; | |
| min-height: 0 ; | |
| overflow-y: auto ; | |
| } | |
| /* 2026-07-23:千卉確認頂部/底部貼齊後,決定整個 footer(透過API使用/ | |
| 設定連結列)不要了,直接拿掉,不只是隱藏內容——footer_links 參數也 | |
| 一併清空,避免 Gradio 還是生出一個空的 <footer> 佔用 flex 高度。 */ | |
| footer { | |
| display: none ; | |
| } | |
| /* 千卉截圖回報頂部列還是沒有真正貼齊頂端,量到 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 ; | |
| flex-direction: column ; | |
| height: 100% ; | |
| min-height: 0 ; | |
| } | |
| .main-layout-row { | |
| flex: 1 1 auto ; | |
| min-height: 0 ; | |
| } | |
| *, *::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 ; } | |
| /* ── 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 ; | |
| font-size: 1.3rem ; | |
| font-weight: 700 ; | |
| -webkit-background-clip: text; | |
| -webkit-text-fill-color: transparent; | |
| background-clip: text; | |
| margin: 0 ; | |
| letter-spacing: -.01em; | |
| line-height: 1.2 ; | |
| white-space: nowrap; | |
| flex: none; | |
| } | |
| .app-header .title-line .en { | |
| font-size: 1.05rem ; | |
| font-weight: 600 ; | |
| } | |
| .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 ; } | |
| /* 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 ; | |
| min-height: 0 ; | |
| overflow: hidden ; | |
| opacity: 0 ; | |
| margin: 0 ; | |
| padding: 0 ; | |
| } | |
| /* ── 4. CHATBOT ────────────────────────────────────────────────── */ | |
| .chatbot { | |
| border: 1px solid var(--c-border-strong) ; | |
| border-radius: 14px ; | |
| background: var(--c-surface) ; | |
| } | |
| /* 主對話視窗:下方接技術執行紀錄 Accordion,兩者視覺上合併成一張卡片 | |
| (只留上方圓角,底部邊框拿掉,改由 Accordion 頂端的邊框當作分界線) */ | |
| /* 2026-07-24(仿 Claude):對話視窗貼齊欄邊——拿掉外框、圓角、白色底, | |
| 背景透明吃 chat-col 的底色,只有氣泡本身有底色。 */ | |
| .main-chat { | |
| background: transparent ; | |
| border: none ; | |
| border-radius: 0 ; | |
| } | |
| .main-chat .bubble-wrap, | |
| .main-chat div[role="log"] { | |
| background: transparent ; | |
| } | |
| /* #2 消除重複滾軸:只讓 bubble-wrap 捲動,外層不捲 */ | |
| .main-chat [class*="wrapper"], | |
| .main-chat > div > div:not([role="log"]) { | |
| overflow: hidden ; | |
| } | |
| .main-chat div[role="log"].bubble-wrap { | |
| overflow-y: auto ; | |
| overflow-x: hidden ; | |
| } | |
| /* 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 ; | |
| min-height: 0 ; | |
| display: flex ; | |
| flex-direction: column ; | |
| } | |
| /* 訊息從頂部開始排列,消除空白 */ | |
| div[role="log"].bubble-wrap, | |
| div[role="log"][aria-label="chatbot conversation"], | |
| .main-chat .bubble-wrap, | |
| .bubble-wrap { | |
| flex-direction: column ; | |
| justify-content: flex-start ; | |
| align-content: flex-start ; | |
| align-items: flex-start ; | |
| min-height: 0 ; | |
| /* 2026-07-24 千卉:對話列的左右留白對齊導覽列(16px)。原本 .5rem(8px) | |
| 讓氣泡比導覽列項目更貼邊,三欄留白不一致。 */ | |
| padding: 16px ; | |
| } | |
| /* 清除 Gradio 在 bubble-wrap 內加的空白 spacer div */ | |
| .bubble-wrap > div:empty { display: none ; } | |
| .bubble-wrap > :first-child { margin-top: 0 ; padding-top: 0 ; } | |
| /* ── 隱藏訊息計數氣泡、頁碼、複製按鈕浮層 ── */ | |
| .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 ; | |
| } | |
| /* 徹底隱藏對話視窗內所有複製/工具浮層圖示 */ | |
| .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 ; | |
| } | |
| /* ── 通用訊息氣泡 ── */ | |
| /* 不同時間產生的訊息之間如果間距太小,會分不出是好幾則獨立訊息還是同一則, | |
| 拉大訊息列間距讓每則訊息之間有清楚的留白斷開 */ | |
| /* 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 ; | |
| margin-left: 0 ; | |
| margin-right: 0 ; | |
| width: 100% ; | |
| max-width: 100% ; | |
| align-self: stretch ; | |
| } | |
| /* 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 ; | |
| font-size: 14px ; | |
| line-height: 1.7 ; | |
| padding: 0 ; | |
| width: fit-content ; | |
| max-width: 90% ; | |
| transition: box-shadow .2s ; | |
| } | |
| /* Gradio 在氣泡外層又包了一層同樣帶 .message class 的巢狀 div(非語意重複, | |
| 是它自己的 panel-full-width 包裝),上面這條規則會連內層也命中, | |
| 造成 padding 上下疊加兩次,視覺上像是文字最上面跟最下面多了一整行空白。 | |
| 這裡把內層那份歸零,只留外層真正的框線與留白 */ | |
| .main-chat .message-row .message .message { | |
| padding: 0 ; | |
| border-radius: 0 ; | |
| max-width: 100% ; | |
| } | |
| /* 使用者訊息:淺灰底色跟白色頁面背景錯開,不用外框線,靠右對齊與置左的 | |
| 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 ; | |
| border: none ; | |
| border-radius: 0 ; | |
| color: var(--c-text) ; | |
| margin-left: auto ; | |
| font-weight: 500 ; | |
| box-shadow: none ; | |
| max-width: 80% ; | |
| text-align: right ; | |
| } | |
| .main-chat .message-row.user, | |
| .main-chat .message-row.user-row { margin-bottom: 1.1rem ; } | |
| /* 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) ; | |
| border: 1px solid var(--c-user-border) ; | |
| color: var(--c-user-text) ; | |
| } | |
| /* AI 訊息(主對話視窗):淺灰底色跟白色頁面背景錯開,不用外框線 */ | |
| .main-chat .message-row.bot .message, | |
| .main-chat .message-row.bot-row .bot.message { | |
| background: transparent ; | |
| border: none ; | |
| border-radius: 0 ; | |
| color: var(--c-text) ; | |
| font-size: 14px ; | |
| line-height: 1.7 ; | |
| box-shadow: none ; | |
| text-align: left ; | |
| } | |
| /* Gradio 產生的 <p> 段落本身有自己的 font-size(實測為固定 16px), | |
| 不會沿用上面設在外層容器的字級,且段落間距預設高達 16px, | |
| 視覺上偏大又有明顯空行,這裡直接命中 <p> 覆寫 */ | |
| .main-chat .message-row .message p { | |
| font-size: 14px ; | |
| line-height: 1.6 ; | |
| margin: 0 0 .5rem ; | |
| } | |
| .main-chat .message-row .message p:last-child { margin-bottom: 0 ; } | |
| /* 包住段落的 <span class="md prose"> 預設是 inline,卻裡面塞了 block 層級的 | |
| <p>(不合法巢狀),瀏覽器會讓這個 span 依自己的字級(16px)先產生一個看 | |
| 不見的行高空隙,出現在第一段之前——這也是最上面「多一行空白」的真正來源。 | |
| 改成 block 並統一字級後,這段隱形空隙就會消失 */ | |
| .main-chat .message-row .message .md { | |
| display: block ; | |
| font-size: 14px ; | |
| line-height: 1.6 ; | |
| padding: 0.9rem 1.1rem ; | |
| /* 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) ; | |
| border: 1px solid var(--c-bubble-border) ; | |
| border-radius: 10px ; | |
| } | |
| /* ── 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 ; | |
| height: 28px ; | |
| flex: none ; | |
| align-self: flex-start ; | |
| margin-right: 10px ; | |
| margin-left: 0 ; | |
| border: none ; | |
| border-radius: 50% ; | |
| background-image: linear-gradient(135deg, var(--c-accent-strong) 0%, var(--c-accent2) 55%, var(--c-accent3) 100%) ; | |
| } | |
| .main-chat .avatar-container img { | |
| width: 100% ; | |
| height: 100% ; | |
| /* 2026-08-12 二版(業主:頭像圖案有點小):內距 5px→3px,圖案在 28px | |
| 圓內的可用範圍從 18px 放大到 22px;SVG 本身的 path 也一併撐大並改成 | |
| 實心(見 assets/icons/ai-avatar.svg)。兩件事一起做才有感——單改內距 | |
| 只是把細線稿放大,單改 path 又會被內距框住。 */ | |
| padding: 3px ; | |
| object-fit: contain ; | |
| opacity: 1 ; | |
| } | |
| /* 頭像佔掉 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) ; | |
| } | |
| .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) ; font-weight: 700 ; } | |
| .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) ; } | |
| .main-chat .message-row.bot .message code, | |
| .main-chat .message-row.bot-row .bot.message code { | |
| background: var(--c-code-bg) ; | |
| color: var(--c-code-text) ; | |
| padding: 1px 6px ; | |
| border-radius: 4px ; | |
| font-family: 'JetBrains Mono', monospace ; | |
| font-size: 0.82em ; | |
| } | |
| .main-chat .message-row.bot .message a, | |
| .main-chat .message-row.bot-row .bot.message a { | |
| color: var(--c-accent-strong) ; | |
| text-decoration: underline ; | |
| text-underline-offset: 2px ; | |
| } | |
| /* 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 ; | |
| border: none ; | |
| padding: .55rem 0 ; | |
| margin-top: 0 ; | |
| } | |
| .exec-log p { | |
| font-size: var(--font-size-base) ; | |
| line-height: 1.5 ; | |
| margin: 0 0 .3rem ; | |
| padding: 0 ; | |
| color: var(--c-text-muted) ; | |
| font-family: 'JetBrains Mono', 'Noto Sans TC', monospace ; | |
| white-space: pre-wrap ; | |
| word-break: break-word ; | |
| } | |
| .exec-log p:last-child { color: var(--c-text-soft) ; } | |
| .exec-log code { | |
| background: none ; | |
| color: var(--c-accent-strong) ; | |
| padding: 0 .15rem 0 0 ; | |
| font-size: var(--font-size-base) ; | |
| } | |
| /* 跟對話視窗一樣:包住段落的 <span class="md"> 預設 inline 卻裝 block 的 | |
| <p>,會在最前面產生依自己字級算出的隱形空隙,這裡一併改成 block 並統一字級 */ | |
| .exec-log .md { | |
| display: block ; | |
| font-size: var(--font-size-base) ; | |
| } | |
| /* ── 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 ; | |
| max-width: 220px ; | |
| display: flex ; | |
| flex-direction: column ; | |
| /* 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 ; | |
| gap: .3rem ; | |
| height: 100% ; | |
| /* 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 ; | |
| border-right: 1px solid var(--c-border-strong) ; | |
| /* 5-A:導覽列上下左右統一 16px 留白。所有按鈕/項目寬度撐滿這個內距框, | |
| 右上收合鈕的右緣也就落在 16px 處,不再多出 ~20px 空隙。 */ | |
| padding: 16px ; | |
| } | |
| /* 中間可捲動項目區:研究情境(已隱藏的空殼)、回首頁連結、結果導覽都在 | |
| 這裡面。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 ; | |
| flex: 1 1 auto ; | |
| flex-direction: column ; | |
| flex-wrap: nowrap ; | |
| min-height: 0 ; | |
| gap: .3rem ; | |
| overflow-y: auto ; | |
| overflow-x: hidden ; | |
| } | |
| /* 底部工具列(明暗切換+收合):flex:none 固定高度,不參與中間段的縮放, | |
| 永遠貼在 .nav-col 底部;border-top 當作千卉指定的「上方水平分隔線」。 | |
| justify-content:flex-end 靠右對齊(千卉指定「置底+置右」)。 */ | |
| #nav_bottom_toolbar, #nav_bottom_toolbar > .html-container { padding: 0 ; margin: 0 ; } | |
| .nav-bottom-toolbar { | |
| display: flex ; | |
| flex: none ; | |
| align-items: center ; | |
| justify-content: flex-end ; | |
| gap: 2px ; | |
| padding-top: .6rem ; | |
| border-top: 1px solid var(--c-border-strong) ; | |
| } | |
| /* 真因排查記錄:Gradio 的 .column 預設會帶 flex-wrap:wrap。平常沒設高度時 | |
| 不會有事,但這裡額外設了固定 height 讓內容用捲動取代撐高,一旦內容總高 | |
| 度超過這個 height,flex-wrap:wrap 會把「排不下」的子元件換到*右邊新開一 | |
| 欄*(水平並排),而不是照預期垂直捲動——這就是 canvas-col 一度出現內容 | |
| 跑到欄外、往右位移一整欄寬度的真正原因(不是 sticky、也不是 overflow-x | |
| 單獨造成的,那兩個只是讓症狀被看見的地方)。修法:flex-wrap 明確關掉。 */ | |
| .chat-col { | |
| display: flex ; | |
| flex-direction: column ; | |
| flex-wrap: nowrap ; | |
| /* 2026-07-24(仿 Claude 對話欄):對話視窗與輸入框直接貼齊欄邊、彼此 | |
| 之間也無間隙——gap 從 1rem 改 0。欄本身給一個底色(--c-bg),讓上面 | |
| 的白色氣泡與輸入框浮出來;右側分隔線保留。 */ | |
| gap: 0 ; | |
| height: 100% ; | |
| overflow: hidden ; | |
| min-width: 0 ; | |
| background: var(--c-bg) ; | |
| border-right: 1px solid var(--c-border-strong) ; | |
| } | |
| /* 對話視窗:輸入框高度比例 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 ; /* 2026-07-24:吃掉輸入框以外的全部高度(見 #chat_upload_zone) */ | |
| min-height: 0 ; | |
| height: auto ; | |
| overflow: hidden ; | |
| display: flex ; | |
| flex-direction: column ; | |
| } | |
| /* canvas-col 本身「不」捲動——它只負責把步驟列(固定)跟 canvas-scroll | |
| (捲動)疊在一起、限制在同一個總高度內。踩坑記錄:原本想讓步驟列用 | |
| position:sticky 貼在 canvas-col 頂端,但 sticky 元素只能在「自己最近的 | |
| 容器」範圍內移動,而 Gradio 自動包在 gr.HTML() 外面的 wrapper 高度只比 | |
| 步驟列本身高一點點,捲動超過那一點高度後 sticky 就跟著捲走、貼不住。 | |
| 改成結構性分離:步驟列跟 canvas-scroll 是兩個獨立區塊,前者天生不參與 | |
| 捲動,後者自己 overflow-y:auto,兩者高度互不相干,不用依賴 sticky。 */ | |
| .canvas-col { | |
| display: flex ; | |
| flex-direction: column ; | |
| flex-wrap: nowrap ; | |
| gap: 0 ; | |
| height: 100% ; | |
| overflow: hidden ; | |
| min-width: 0 ; | |
| } | |
| .canvas-col .app-header { | |
| flex-shrink: 0 ; | |
| } | |
| /* 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 ; | |
| } | |
| /* 最後一段殘餘留白(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 ; | |
| } | |
| .canvas-scroll { | |
| display: flex ; | |
| flex-direction: column ; | |
| flex-wrap: nowrap ; | |
| flex: 1 1 auto ; | |
| min-height: 0 ; | |
| overflow-x: hidden ; | |
| overflow-y: auto ; | |
| /* 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 ; | |
| padding-right: 20px ; | |
| } | |
| /* 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 ; | |
| } | |
| .main-layout-row.chat-collapsed .chat-col { | |
| display: none ; | |
| } | |
| .main-layout-row.chat-collapsed .canvas-col { | |
| flex: 1 1 auto ; | |
| max-width: none ; | |
| min-width: 0 ; | |
| } | |
| /* 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 ; | |
| } | |
| /* ── 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 ; } | |
| /* 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 ; | |
| } | |
| } | |
| .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 ; | |
| width: fit-content ; | |
| margin: 2.2rem auto 0 ; | |
| padding: .95rem 2.8rem ; | |
| font-size: 1.05rem ; | |
| font-weight: 700 ; | |
| border: none ; | |
| border-radius: 14px ; | |
| color: #fff ; | |
| background-image: linear-gradient(100deg, var(--c-accent) 10%, var(--c-accent2) 90%) ; | |
| box-shadow: 0 16px 40px rgba(var(--c-accent2-rgb),.35) ; | |
| transition: filter 150ms ease, box-shadow 150ms ease ; | |
| } | |
| #start_analysis_btn:hover { | |
| filter: brightness(1.06); | |
| box-shadow: 0 20px 46px rgba(var(--c-accent2-rgb),.45) ; | |
| } | |
| /* 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) ; | |
| 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) ; | |
| 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 ; margin: 0 ; } | |
| .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 ; } | |
| } | |
| /* 桌面收合成窄 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 ; max-width: 60px ; min-width: 60px ; } | |
| /* 收合成 60px icon bar:頂端列只剩品牌字標(已無其他項目可置中, | |
| justify-content:center 留著也不影響),字標本身在 60px 裡塞不下、 | |
| 隱藏起來,展開後才出現。 */ | |
| .nav-col.collapsed .nav-top-row { justify-content: center; } | |
| .nav-col.collapsed .nav-brand { display: none ; } | |
| /* 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 ; } | |
| .nav-col.collapsed #theme_toggle { display: none ; } | |
| /* .reset-confirm 已於 2026-07-24 搬出 nav-col 改成全螢幕 modal, | |
| 不再需要在收合狀態下隱藏;.result-nav-accordion 同日隨「統計比較 | |
| 分析」手風琴一起移除。 */ | |
| .nav-col.collapsed .result-nav-divider { display: none ; } | |
| /* 選擇器裡的 #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 ; | |
| padding: .45rem ; /* 2026-07-24 收窄:原本 .6rem 把圖示擠到 8px(見下方註解) */ | |
| font-size: 0 ; | |
| } | |
| /* 千卉回報「收合後看不見 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 ; padding-right: 8px ; } | |
| .nav-col.collapsed .button-icon { | |
| width: 22px ; height: 22px ; | |
| flex: none ; opacity: 1 ; | |
| } | |
| .nav-col.collapsed .result-nav { padding-top: .8rem ; } | |
| } | |
| /* 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) ; | |
| border: 1px solid var(--c-border-strong) ; | |
| 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 ; } | |
| .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 ; | |
| top: 0; left: 0; bottom: 0; | |
| width: 78vw ; max-width: 300px ; | |
| flex: none ; height: 100vh ; | |
| overflow-y: auto ; | |
| background: var(--c-surface) ; | |
| box-shadow: 4px 0 24px rgba(15,23,42,.28) ; | |
| z-index: 40; | |
| transform: translateX(-100%); | |
| transition: transform 220ms ease-out; | |
| padding: 1.1rem 1rem ; | |
| } | |
| .nav-col.mobile-open { transform: translateX(0) ; } | |
| /* 桌面收合是縮寬窄 icon bar 那組規則整段包在 @media(min-width:901px) | |
| 裡(見「5e」上方),手機寬度完全不會命中,這裡不需要另外 | |
| revert——即使 localStorage 殘留 collapsed=1,.nav-col.collapsed | |
| 在手機寬度就是普通展開的抽屜外觀,不會被窄化。 */ | |
| /* 手機抽屜沒有「窄 icon bar」中間態,收合鈕無意義 → 只隱藏收合鈕本身, | |
| 品牌字標與明暗切換鈕仍留在抽屜頂端;關閉抽屜改用下方的 x(drawer-close)。 */ | |
| #nav_collapse_toggle { display: none ; } | |
| .nav-drawer-close-row { display: flex ; } | |
| .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 ; | |
| border-radius: 16px ; | |
| background: none ; | |
| padding: 0 0 1.4rem ; | |
| margin: .6rem 0 0 ; | |
| width: 100% ; | |
| box-shadow: none ; | |
| animation: popupIn .25s cubic-bezier(.2,.8,.3,1) ; | |
| } | |
| @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 ; | |
| border-radius: 0 ; | |
| padding: 0 ; | |
| margin: 0 ; | |
| /* width 用 auto 不用 100%:下方 #chat_upload_zone 會給左右 margin 讓面板 | |
| 內縮,若寫死 100% 會連同 margin 一起超出欄寬(實測右側溢出 15px)。 */ | |
| width: auto ; | |
| } | |
| /* 輸入框本體:底色改成跟氣泡同一階(白/深色淺一階),去掉 Gradio 預設的 | |
| 外框與圓角,四邊貼齊 chat-col。頂端用 1px 細線跟對話視窗分界(取代原本 | |
| 靠間隙分隔)。 */ | |
| #chat_upload_zone .multimodal-textbox, | |
| #chat_upload_zone .input-container, | |
| #chat_upload_zone textarea { | |
| background: var(--c-surface) ; | |
| border: none ; | |
| border-radius: 0 ; | |
| box-shadow: none ; | |
| } | |
| /* 2026-07-24 千卉:輸入框右邊與下面要有留白。先前的 16px 只是「面板內部的 | |
| 內距」,白色面板本身仍貼齊欄邊(實測面板距欄 左1 右2 下1)。改成面板整個 | |
| 往內縮 16px,白底與欄邊之間真的空出來;不再需要頂端分隔線(面板浮在欄底 | |
| 色上,本身就是分界)。Gradio 的 .form 外層帶淡藍底+框線也一併中性化, | |
| 否則會在面板外圍多一圈色帶。 */ | |
| #chat_upload_zone { margin: 0 16px 16px ; border-top: none ; } | |
| #chat_upload_zone .form { | |
| background: transparent ; | |
| border: none ; | |
| } | |
| /* 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 ; | |
| min-height: 0 ; | |
| display: flex ; | |
| flex-direction: column ; | |
| } | |
| /* 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 ; } | |
| #chat_upload_zone .input-wrapper, | |
| #chat_upload_zone .input-row { padding: 0 ; margin: 0 ; } | |
| #chat_upload_zone textarea { padding: 0 ; } | |
| /* textarea 只有 67px 但 input-row 是 72px(拿掉送出鈕後留下的槽位高度), | |
| 下緣多 5px 空隙、跟左右留白對不齊。讓 textarea 撐滿整列高度即可。 */ | |
| #chat_upload_zone .input-row { align-items: stretch ; } | |
| #chat_upload_zone textarea { height: 100% ; } | |
| /* 格式要求直接寫在上傳卡片裡,跟卡片本身有明顯區隔但仍在同一張卡片內, | |
| 使用者不用往上捲回對話視窗找 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 ; | |
| margin-bottom: .9rem ; | |
| } | |
| .upload-hint.prose { | |
| padding: 0 ; | |
| margin: 0 ; | |
| } | |
| .upload-hint { | |
| font-size: var(--font-size-base) ; | |
| } | |
| .upload-hint p, .upload-hint li { font-size: var(--font-size-base) ; color: var(--c-text-soft) ; } | |
| .upload-hint strong { color: var(--c-accent-strong) ; } | |
| /* 同陷阱 13 第 3 點:<span class="md"> 預設 inline 卻裝 block 的 <p>, | |
| 會在最前面產生依自己字級算出的隱形空隙,這裡一併修正 */ | |
| .upload-hint .md { | |
| display: block ; | |
| font-size: var(--font-size-base) ; | |
| } | |
| .upload-actions { | |
| margin-top: 1rem ; | |
| gap: .6rem ; | |
| } | |
| .upload-actions button { border-radius: 10px ; font-weight: 700 ; } | |
| /* 「回首頁」連結(原「開始新分析」按鈕,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 ; } | |
| /* 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 ; | |
| inset: 0 ; | |
| z-index: 120 ; | |
| padding: 1.5rem ; | |
| background: rgba(15, 23, 42, .55) ; | |
| backdrop-filter: blur(2px); | |
| } | |
| .reset-confirm-panel { | |
| margin: auto ; /* flex 容器內上下左右皆置中 */ | |
| width: 100% ; | |
| max-width: 400px ; | |
| flex: none ; /* 不被 overlay 的 flex 拉伸成滿版高 */ | |
| gap: 1.2rem ; | |
| padding: 1.5rem ; | |
| border: 1px solid var(--c-border-strong) ; | |
| border-radius: 14px ; | |
| background: var(--c-surface) ; | |
| box-shadow: 0 20px 50px rgba(15, 23, 42, .3) ; | |
| } | |
| /* 深色模式:頁面本身已經是深色(--c-surface #0d1526),同一階深灰疊上去 | |
| 幾乎看不出後面被壓暗,遮罩形同不存在。改用更黑的值才有「後面那層被關掉」 | |
| 的分離感。兩種寫法都給:.dark 是 Gradio 切主題時掛的 class,媒體查詢則 | |
| 對應本檔既有的 prefers-color-scheme token 機制(見 _DARK_MODE_HEAD_CSS)。 */ | |
| .dark .reset-confirm-overlay { background: rgba(0, 0, 0, .62) ; } | |
| @media (prefers-color-scheme: dark) { | |
| :root:not(.light) .reset-confirm-overlay { background: rgba(0, 0, 0, .62) ; } | |
| } | |
| .reset-confirm-panel .reset-confirm-text p { margin: 0 ; } | |
| /* 橫式:兩顆按鈕同寬並排,窄視窗也不換行 */ | |
| .reset-confirm-actions { | |
| flex-wrap: nowrap ; | |
| gap: .7rem ; | |
| } | |
| .reset-confirm-actions button { | |
| border-radius: 10px ; | |
| font-weight: 700 ; | |
| } | |
| /* 2026-08-26:「關於」面板沿用 .reset-confirm-overlay 同一套置中 modal | |
| 機制(同一個 elem_classes 掛在 overlay 上),但內容是說明文字不是 | |
| 一兩句確認詞,.reset-confirm-panel 的 400px 版寬會把整段文字擠成 | |
| 窄豆腐塊,所以另開 .about-panel 加寬;內容也比確認框長,視窗矮的時候 | |
| (筆電、手機橫向)要能捲動,不能被 overlay 裁掉看不到下半段。文字 | |
| 對齊也從確認框的置中改左靠——長段落置中會很難讀。 */ | |
| .about-panel { | |
| margin: auto ; | |
| width: 100% ; | |
| max-width: 560px ; | |
| max-height: min(80vh, 640px) ; | |
| overflow-y: auto ; | |
| flex: none ; | |
| gap: 1rem ; | |
| padding: 1.6rem 1.8rem ; | |
| border: 1px solid var(--c-border-strong) ; | |
| border-radius: 14px ; | |
| background: var(--c-surface) ; | |
| box-shadow: 0 20px 50px rgba(15, 23, 42, .3) ; | |
| text-align: left ; | |
| } | |
| .about-panel h2, .about-panel h3 { | |
| text-align: left ; | |
| } | |
| .about-panel hr { | |
| border: none ; | |
| border-top: 1px solid var(--c-border-strong) ; | |
| margin: .6rem 0 ; | |
| } | |
| .about-panel .about-close-row { | |
| display: flex ; | |
| justify-content: flex-end ; | |
| } | |
| .about-panel .about-close-row button { | |
| border-radius: 10px ; | |
| font-weight: 700 ; | |
| } | |
| /* Phase 8-D:結果導覽(情境選定後顯示在開始新分析按鈕下方),純文字 | |
| 標籤(暫不用圖示,等下一輪視覺打磨補 Heroicons SVG),維持跟 | |
| scenario-buttons 一致的「貼齊左邊、無強調色底」低視覺重量風格, | |
| 避免搶過主要操作按鈕的注意力 */ | |
| /* 左右留白已上移到 .nav-col(見該處註解),這裡不再重複設定,避免疊加成 | |
| 雙倍縮排。上緣的 border-top 因此會跟著欄位的 padding 內縮 8px,跟下方 | |
| .result-nav-divider 本來就有的內縮一致。 */ | |
| .result-nav { | |
| margin-top: 1rem ; | |
| padding-top: 1rem ; | |
| border-top: 1px solid var(--c-border-strong) ; | |
| gap: .3rem ; | |
| } | |
| /* 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% ; | |
| justify-content: flex-start ; | |
| text-align: left ; | |
| border: none ; | |
| background: transparent ; | |
| box-shadow: none ; | |
| } | |
| /* 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 ; | |
| font-size: var(--font-size-base) ; | |
| font-weight: 500 ; | |
| border-radius: 8px ; | |
| color: var(--c-text-soft) ; | |
| transition: background-color .15s ease-out, color .15s ease-out ; | |
| } | |
| #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) ; | |
| color: var(--c-text) ; | |
| } | |
| #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) ; | |
| } | |
| /* 歷史紀錄:這裡原本有一整組 .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) ; | |
| color: var(--c-accent-strong) ; | |
| font-weight: 700 ; | |
| } | |
| /* 技術執行紀錄:低優先度診斷工具,跟上面的分析結果用分隔線+降低視覺 | |
| 權重(字級與顏色都比其他導覽項目再低一階)區隔開來 */ | |
| .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 ; | |
| } | |
| #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) ; | |
| font-weight: 500 ; | |
| color: var(--c-text-muted) ; | |
| } | |
| /* 語料 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 ; | |
| border-radius: 16px ; | |
| background: none ; | |
| padding: 0 0 1.4rem 1.5rem ; | |
| margin: -1.65rem 0 1rem ; | |
| box-shadow: none ; | |
| animation: popupIn .25s cubic-bezier(.2,.8,.3,1) ; | |
| } | |
| .metadata-zone .gr-box, .metadata-zone .form { background: transparent ; } | |
| /* 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 ; | |
| border-radius: 16px ; | |
| background: none ; | |
| padding: 0 0 1.4rem 1.5rem ; | |
| margin: .6rem 0 0 ; | |
| width: 100% ; | |
| box-shadow: none ; | |
| animation: popupIn .25s cubic-bezier(.2,.8,.3,1) ; | |
| } | |
| /* 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) ; | |
| } | |
| #metadata_zone table { width: 100% ; } | |
| /* 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 ; | |
| background: transparent ; | |
| box-shadow: none ; | |
| overflow: visible ; | |
| gap: 1rem ; | |
| /* 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 ; | |
| } | |
| #metadata_zone .form > .block, | |
| #metadata_zone .form > fieldset.block { | |
| background: transparent ; | |
| border: none ; | |
| box-shadow: none ; | |
| padding: 0 ; | |
| /* Gradio 給 fieldset 的 overflow:auto 是裁掉標籤的那把刀(真因見上面 | |
| .form 的 align-items 註解),改 visible 當第二道保險。 */ | |
| overflow: visible ; | |
| } | |
| /* 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 ; | |
| font-weight: 700 ; | |
| color: var(--c-text-muted) ; | |
| margin-bottom: 8px ; | |
| } | |
| /* 「比較結構」從 gr.Group 改成 gr.Column 之後(見 app_agent.py 同批註解), | |
| Gradio 那條藍色標題橫幅與外框就不存在了,這裡只補回跟其他小標題一致的 | |
| 上下節奏——它的 Markdown 用的是同一個 `**粗體**` 寫法,顏色/字級自動與 | |
| 「原文資訊」「群組層資訊」相同,不用另外設。 */ | |
| #meta_compare_group { | |
| background: transparent ; | |
| border: none ; | |
| padding: 0 ; | |
| gap: 0 ; | |
| } | |
| /* 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 ; padding-left: 0 ; padding-right: 0 ; } | |
| /* 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 ; | |
| } | |
| /* 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) ; | |
| border-radius: 14px ; | |
| background: var(--c-surface2) ; | |
| padding: 1.2rem 1.5rem 1.4rem ; | |
| margin-top: .75rem ; | |
| } | |
| /* 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 ; | |
| font-size: 1.05rem ; | |
| font-weight: 700 ; | |
| color: var(--c-text) ; | |
| } | |
| /* 2026-08-05:分析運算進行中的連線提醒,低視覺重量(跟標題/狀態列比起來 | |
| 是次要資訊),用 warn 色系底+左邊框做出「提示」語彙,不搶過主要內容。 */ | |
| .analysis-panel-hint { | |
| margin: 0 0 1rem 0 ; | |
| padding: .6rem .8rem ; | |
| border-left: 3px solid var(--c-warn) ; | |
| border-radius: 4px ; | |
| background: rgba(180,83,9,.08) ; | |
| font-size: .82rem ; | |
| line-height: 1.5 ; | |
| color: var(--c-text-muted) ; | |
| } | |
| .dark .analysis-panel-hint { background: rgba(251,191,36,.10) ; } | |
| .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) ; | |
| font-weight: 600 ; | |
| color: var(--c-text) ; | |
| } | |
| .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) ; } | |
| .analysis-entry { | |
| color: var(--c-accent-strong) ; | |
| 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 ; | |
| border-style: solid ; | |
| border-color: rgba(var(--c-accent-rgb),.25) ; | |
| border-top-color: var(--c-accent) ; | |
| 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 ; | |
| animation: none ; | |
| box-shadow: none ; | |
| } | |
| /* 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 ; | |
| cursor: not-allowed ; | |
| } | |
| .upload-area, [data-testid="droparea"] { | |
| border: 2px dashed rgba(var(--c-accent-rgb),.28) ; | |
| border-radius: 12px ; | |
| background: rgba(var(--c-accent-rgb),.03) ; | |
| transition: border-color .2s ease, background .2s ease ; | |
| } | |
| .upload-area:hover, [data-testid="droparea"]:hover { | |
| border-color: rgba(var(--c-accent-rgb),.5) ; | |
| background: rgba(var(--c-accent-rgb),.08) ; | |
| } | |
| .file-preview { | |
| border: 1px solid var(--c-border-strong) ; | |
| border-radius: 10px ; | |
| background: var(--c-surface) ; | |
| } | |
| /* 修正:此版 Gradio 的 File(list) 元件在 flex 版面下,外層 holder 會因 | |
| min-width:0 而整個縮成 0~4px 寬,導致內容(含下載連結)視覺上溢出到 | |
| 容器外,要橫向捲動才找得到下載按鈕。強制給滿版寬度,並固定「下載」欄 | |
| 在最前面、檔名欄自動換行,避免長檔名把下載連結擠出視野。 */ | |
| .file-preview-holder { | |
| width: 100% ; | |
| flex: 1 1 auto ; | |
| min-width: 0 ; | |
| } | |
| .file-preview { | |
| table-layout: fixed ; | |
| width: 100% ; | |
| } | |
| .file-preview tr > :first-child { | |
| width: auto ; | |
| white-space: normal ; | |
| word-break: break-all ; | |
| } | |
| .file-preview tr > :nth-child(2) { | |
| width: 92px ; | |
| white-space: nowrap ; | |
| } | |
| .file-preview tr > :last-child { | |
| width: 34px ; | |
| } | |
| /* ── 6. BUTTONS ────────────────────────────────────────────────── */ | |
| button.primary, button[data-testid="submit"] { | |
| background: linear-gradient(135deg, #0284c7 0%, #4f46e5 100%) ; | |
| border: none ; | |
| border-radius: 9px ; | |
| font-weight: 700 ; | |
| font-size: var(--font-size-base) ; | |
| color: #fff ; | |
| box-shadow: 0 2px 12px rgba(2,132,199,.28) ; | |
| transition: transform .18s ease, box-shadow .18s ease, opacity .18s ; | |
| cursor: pointer ; | |
| letter-spacing: .02em; | |
| } | |
| button.primary:hover { transform: translateY(-2px) ; box-shadow: 0 6px 22px rgba(2,132,199,.40) ; } | |
| button.primary:active { transform: translateY(0) ; opacity: .92 ; } | |
| button.primary:disabled { opacity: .5 ; cursor: not-allowed ; transform: none ; } | |
| button.secondary { | |
| background: rgba(var(--c-accent-rgb),.08) ; | |
| border: 1px solid rgba(var(--c-accent-rgb),.30) ; | |
| color: var(--c-accent-strong) ; | |
| border-radius: 9px ; | |
| font-weight: 600 ; | |
| font-size: var(--font-size-base) ; | |
| cursor: pointer ; | |
| transition: background .18s ease, border-color .18s ease ; | |
| } | |
| button.secondary:hover { | |
| background: rgba(var(--c-accent-rgb),.16) ; | |
| border-color: rgba(var(--c-accent-rgb),.45) ; | |
| } | |
| /* ── 7. INPUTS ─────────────────────────────────────────────────── */ | |
| textarea, input[type="text"] { | |
| background: var(--c-input-bg) ; | |
| border: 1px solid var(--c-input-border) ; | |
| border-radius: 9px ; | |
| color: var(--c-text) ; | |
| font-size: var(--font-size-base) ; | |
| line-height: 1.6 ; | |
| transition: border-color .18s ease, box-shadow .18s ease ; | |
| } | |
| textarea:focus, input[type="text"]:focus { | |
| border-color: var(--c-accent) ; | |
| box-shadow: 0 0 0 3px rgba(var(--c-accent-rgb),.16) ; | |
| outline: none ; | |
| } | |
| /* ── 8. DATAFRAME ──────────────────────────────────────────────── */ | |
| .table-wrap { | |
| border: 1px solid var(--c-border-strong) ; | |
| border-radius: 12px ; | |
| overflow: hidden ; | |
| } | |
| /* 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) ; } | |
| table thead th { | |
| background: transparent ; | |
| color: var(--c-text-muted) ; | |
| font-weight: 700 ; | |
| font-size: 0.75rem ; | |
| text-transform: uppercase ; | |
| letter-spacing: 0.05em ; | |
| border-bottom: 1px solid var(--c-border-strong) ; | |
| padding: 10px 12px ; | |
| white-space: nowrap; | |
| } | |
| table tbody td { | |
| background: var(--c-surface) ; | |
| color: var(--c-text-soft) ; | |
| font-size: var(--font-size-base) ; | |
| /* 資料列之間的細線用 --c-divider(Tier 2):這種密集重複的線如果也拉到 | |
| 3:1,整張表會變成網格紙,反而更難讀。結構性的外框與表頭分界才走 Tier 1。 */ | |
| border-color: var(--c-divider) ; | |
| font-family: 'JetBrains Mono', monospace ; | |
| padding: 7px 12px ; | |
| } | |
| table tbody tr:nth-child(even) td { background: rgba(var(--c-accent-rgb),.025) ; } | |
| table tbody tr:hover td { background: rgba(var(--c-accent-rgb),.08) ; } | |
| /* ── 9. IMAGE FRAMES ───────────────────────────────────────────── */ | |
| .image-container, .image-frame { | |
| border: 1px solid var(--c-border-strong) ; | |
| border-radius: 12px ; | |
| overflow: hidden ; | |
| background: var(--c-surface) ; | |
| } | |
| .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 ; | |
| font-size: 1.25rem ; | |
| font-weight: 600 ; | |
| color: var(--c-text) ; | |
| border-bottom: 1px solid var(--c-border-strong); | |
| padding-bottom: 0.45rem; | |
| margin: 1.4rem 0 .8rem ; | |
| } | |
| .prose h4, .md h4 { | |
| font-size: 0.85rem ; | |
| font-weight: 700 ; | |
| color: var(--c-text-muted) ; | |
| text-transform: uppercase; | |
| letter-spacing: .06em; | |
| margin: 1rem 0 .4rem ; | |
| } | |
| .prose strong, .md strong { color: var(--c-accent-strong) ; } | |
| .prose em, .md em { color: var(--c-accent2-strong) ; } | |
| .prose code, .md code { | |
| background: var(--c-code-bg) ; | |
| color: var(--c-code-text) ; | |
| padding: 2px 6px; | |
| border-radius: 4px; | |
| font-family: 'JetBrains Mono', monospace ; | |
| 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 ; | |
| font-size: 1.6rem ; | |
| font-weight: 700 ; | |
| color: var(--c-text) ; | |
| border-bottom: 1px solid var(--c-border-strong); | |
| padding-bottom: 0.5rem; | |
| margin: 1.6rem 0 .9rem ; | |
| } | |
| #canvas_report .prose h2, #canvas_report .md h2 { | |
| font-family: 'Crimson Pro', Georgia, serif ; | |
| font-size: 1.4rem ; | |
| font-weight: 600 ; | |
| color: var(--c-text) ; | |
| border-bottom: 1px solid var(--c-border-strong); | |
| padding-bottom: 0.45rem; | |
| margin: 1.5rem 0 .8rem ; | |
| } | |
| #canvas_report .prose h5, #canvas_report .md h5 { | |
| font-family: 'Crimson Pro', Georgia, serif ; | |
| font-size: 0.95rem ; | |
| font-weight: 600 ; | |
| color: var(--c-text-muted) ; | |
| margin: 1rem 0 .4rem ; | |
| } | |
| #canvas_report .prose h6, #canvas_report .md h6 { | |
| font-size: 0.78rem ; | |
| font-weight: 700 ; | |
| color: var(--c-text-subtle) ; | |
| text-transform: uppercase; | |
| letter-spacing: .06em; | |
| margin: .8rem 0 .3rem ; | |
| } | |
| /* 表格——原本完全沒樣式,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) ; | |
| font-weight: 700 ; | |
| font-size: 0.70rem ; | |
| text-transform: uppercase ; | |
| letter-spacing: 0.08em ; | |
| } | |
| /* ── 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 ; | |
| } | |
| /* 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 ; scrollbar-color: transparent transparent ; } | |
| #canvas_idx .virtual-table-viewport:hover, | |
| #canvas_stat .virtual-table-viewport:hover { scrollbar-color: var(--c-scrollbar) transparent ; } | |
| /* ── 15. FOCUS & ACCESSIBILITY ─────────────────────────────────── */ | |
| :focus-visible { | |
| outline: 2px solid var(--c-accent) ; | |
| outline-offset: 2px ; | |
| 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 ; | |
| transition-duration: .01ms ; | |
| } | |
| } | |
| /* ── 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 ; } | |
| .main-layout-row .chat-col, | |
| .main-layout-row .canvas-col { | |
| width: 100% ; | |
| max-width: 100% ; | |
| flex: 1 1 auto ; | |
| height: auto ; | |
| overflow-y: visible ; | |
| } | |
| .canvas-scroll { overflow-y: visible ; min-height: 0 ; } | |
| } | |
| @media (max-width: 768px) { | |
| .app-header .title-line { font-size: 1.1rem ; } | |
| .tabitem { padding: 1rem .75rem ; } | |
| .app-header .steps { gap: 3px; } | |
| .step-pill { font-size: 0.62rem; padding: 2px 7px; } | |
| } | |