From 751ee74f19b98e8cd32cb567d614f4dc0d48a608 Mon Sep 17 00:00:00 2001 From: richblack Date: Tue, 4 Aug 2026 23:35:49 +0800 Subject: [PATCH] =?UTF-8?q?t193=20=E4=B8=89=E4=BF=AE=EF=BC=9A=E6=AF=8F?= =?UTF-8?q?=E5=BA=AB=E4=B8=80=E9=A0=81=EF=BC=8B=E7=8B=80=E6=85=8B=E6=99=82?= =?UTF-8?q?=E9=96=93=E8=BB=B8=EF=BC=8B=E9=A0=90=E8=A8=AD=E6=B7=BA=E8=89=B2?= =?UTF-8?q?=EF=BC=88leo=20=E5=85=AD=E9=BB=9E=E5=9B=9E=E9=A5=8B=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## ① logo 尺寸不對導致白邊 真兇:我寫死 height:22px **沒給 width:auto** ⇒ 容器一壓就變形、露出底板邊緣。 修:照 portal 側欄同一條 clamp(22px,5vw,28px) + width:auto + max-width:100%, 並依 CIS「clear space = one chevron height」在四邊留白。 實測:logo 底板與品牌區落差 **0**(原本是明顯方塊)。 ## ② 深色跟網頁不同/「它有淺色佈景?」 是——**portal 預設淺色**,深色由使用者切換並存 localStorage。 我卻硬跟系統走 ⇒ 兩邊當然不同。 修:改成與 portal 同一套(預設淺色+切換鈕+localStorage), 按鈕規格也逐字對齊 portal 的 .btn/.btn3(padding/font-size/radius/border)。 實測淺色:品牌區(253,252,251)=Paper、側欄(242,241,237)=Canvas,與 CIS 一致。 ## ③ 「開啟知識庫網頁」開到錯的庫 真兇:它在首頁、寫死取 accounts[0] ⇒ 有兩個庫必然開錯。 修:移到**各庫頁的上方**,開的就是那個庫。 ## ④⑤ 30 個資料夾放不下/「加入資料夾」會加到哪個帳號? 修:**每個知識庫一個獨立分頁**(側邊欄動態列出,附資料夾數)。 「加入資料夾」在庫頁裡,**作用對象就是那個庫**,不會加錯。 ## ⑥ 首頁要顯示 status(看守/發現變化/萃取/上傳) 修:首頁改成**狀態時間軸**四步,進行中那步的圓點會呼吸。 誠實邊界:collector 目前回報「整輪」而非逐檔階段, 所以同步中時後三步一起標進行中,**不假裝有更細的進度**。 兩支機械閘全過(check-cis.sh 14 項/check-render.sh 深淺色皆驗)。 style.css 仍是 0 個 hex(顏色全走 var)。 --- cmd/arcrun-app/app.go | 45 +++++++ cmd/arcrun-app/frontend/index.html | 9 +- cmd/arcrun-app/frontend/src/main.js | 174 ++++++++++++++------------ cmd/arcrun-app/frontend/src/style.css | 96 +++++++++++--- 4 files changed, 224 insertions(+), 100 deletions(-) diff --git a/cmd/arcrun-app/app.go b/cmd/arcrun-app/app.go index 8406d6a..f25c657 100644 --- a/cmd/arcrun-app/app.go +++ b/cmd/arcrun-app/app.go @@ -138,6 +138,50 @@ type UIState struct { Engine string `json:"engine"` // "workers-ai" | "gemma" GeminiKey string `json:"geminiKey"` // 只回遮罩,不回真值 ExtractedOK int `json:"extractedOK"` // 首頁「已整理幾份」 + Steps []Step `json:"steps"` // 首頁狀態時間軸(leo #6) +} + +// Step 是首頁狀態時間軸的一格。 +// 🔴 leo 08-04:「首頁要顯示的應該是 status,如果**看守、發現變化、萃取、上傳… +// 不同 status 在哪裡顯示**?」 +// ⇒ 把一輪同步拆成四步,讓使用者看得到「現在走到哪」,而不是只有一句「看守中」。 +type Step struct { + Title string `json:"title"` + Meta string `json:"meta,omitempty"` + State string `json:"state"` // "done"(已完成)|"now"(進行中)|""(還沒輪到) +} + +// buildSteps 依 status.json 與訊號檔推出四步的狀態。 +// 誠實邊界:collector 目前回報的是「整輪」而非逐檔階段,所以同步中時 +// 「發現變化→萃取→上傳」一起標進行中;不假裝有更細的進度。 +func buildSteps(s syncStatus, syncing bool) []Step { + watch := Step{Title: "看守資料夾", Meta: "有變動就自動開始", State: "done"} + if syncing { + return []Step{ + watch, + {Title: "發現變化", State: "done"}, + {Title: "用 AI 整理成知識卡", Meta: "進行中", State: "now"}, + {Title: "上傳到你的知識庫", State: ""}, + } + } + done := "" + if s.ExtractedOK > 0 || s.LastSync != "" { + done = "done" + } + meta := "" + if s.ExtractedOK > 0 { + meta = fmt.Sprintf("上一輪 %d 份", s.ExtractedOK) + } + up := Step{Title: "上傳到你的知識庫", Meta: meta, State: done} + if s.ExtractFailed > 0 { + up.Meta = fmt.Sprintf("%s · ⚠ %d 份失敗", meta, s.ExtractFailed) + } + return []Step{ + watch, + {Title: "發現變化", Meta: "等待中", State: done}, + {Title: "用 AI 整理成知識卡", State: done}, + up, + } } // GetState 是前端每秒拉一次的單一入口。 @@ -164,6 +208,7 @@ func (a *App) GetState() UIState { sync := loadSyncStatus() st.ExtractedOK = sync.ExtractedOK st.Syncing, st.StatusBig, st.StatusSub = describeStatus(sync) + st.Steps = buildSteps(sync, st.Syncing) return st } diff --git a/cmd/arcrun-app/frontend/index.html b/cmd/arcrun-app/frontend/index.html index 4661a74..5176651 100644 --- a/cmd/arcrun-app/frontend/index.html +++ b/cmd/arcrun-app/frontend/index.html @@ -19,12 +19,8 @@ Arcrun - + +
@@ -38,6 +34,7 @@
啟動中…
+
diff --git a/cmd/arcrun-app/frontend/src/main.js b/cmd/arcrun-app/frontend/src/main.js index a17af3e..219cd2a 100644 --- a/cmd/arcrun-app/frontend/src/main.js +++ b/cmd/arcrun-app/frontend/src/main.js @@ -1,8 +1,11 @@ // Arcrun 桌面 App — 前端(t193) // -// 版面照 leo 08-04 指定(附 Google Drive 截圖): -// 「可以完全模仿 gdrive,**左方側邊欄,在右方替換頁面**」 -// ⇒ 設定不再開 popup,全部是右側的頁;只有「確認刪除」才用覆蓋層。 +// 版面(leo 08-04 指定,附 Google Drive 截圖):左側邊欄、右側換頁。 +// 🔴 08-04 二輪回饋(本次處理): +// ③「開啟知識庫網頁」不該在首頁(會開到錯的庫)⇒ 移到**各庫頁的上方** +// ④ 一個庫 30 個資料夾放不下 ⇒ **每個知識庫一個獨立分頁** +// ⑤「加入資料夾」會加到哪個帳號?⇒ 在庫頁裡加,**作用對象就是那個庫**,不會加錯 +// ⑥ 首頁要顯示 status:看守/發現變化/萃取/上傳 ⇒ **狀態時間軸** import './arcrun-cis.css'; // 共用底層(色票/字體/紋理)——唯一真相源在 arcrun-cis/css/ import './style.css'; // 本 App 的版面 @@ -11,73 +14,96 @@ const $ = (id) => document.getElementById(id); const esc = (s) => String(s).replace(/[&<>"]/g, (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"' }[c])); -// 跟隨系統深淺色(CIS 兩套色票都在 arcrun-cis.css 定義好了) -const mq = window.matchMedia('(prefers-color-scheme: dark)'); -const applyTheme = () => - document.documentElement.setAttribute('data-theme', mq.matches ? 'dark' : 'light'); -mq.addEventListener('change', applyTheme); -applyTheme(); +// 主題:**與 portal 同一套規則**——預設淺色,使用者切換後存 localStorage +// (leo 問「它有淺色佈景?」⇒ 是,portal 預設就是淺色) +const THEME_KEY = 'arcrun_app_theme'; +function applyTheme(t) { + document.documentElement.setAttribute('data-theme', t); + try { localStorage.setItem(THEME_KEY, t); } catch (e) {} + const b = $('themeBtn'); if (b) b.textContent = t === 'dark' ? '☀' : '☾'; +} +applyTheme((() => { try { return localStorage.getItem(THEME_KEY) === 'dark' ? 'dark' : 'light'; } catch (e) { return 'light'; } })()); +$('themeBtn').onclick = () => + applyTheme(document.documentElement.getAttribute('data-theme') === 'dark' ? 'light' : 'dark'); let state = null; -let page = 'home'; -let updateInfo = null; // { current, latest, available, notes, staged } +let page = 'home'; // 'home' | 'ai' | 'update' | 'lib:' +let updateInfo = null; // ── 覆蓋層(只給必須打斷的確認)── -function openSheet(html, wire) { - $('sheet').innerHTML = html; $('overlay').classList.add('on'); if (wire) wire(); -} +function openSheet(html, wire) { $('sheet').innerHTML = html; $('overlay').classList.add('on'); if (wire) wire(); } function closeSheet() { $('overlay').classList.remove('on'); } $('overlay').addEventListener('click', (e) => { if (e.target.id === 'overlay') closeSheet(); }); document.addEventListener('keydown', (e) => { if (e.key === 'Escape') closeSheet(); }); -// ── 側邊欄換頁 ── -document.querySelectorAll('#side .nav').forEach((el) => { - el.onclick = () => { - page = el.dataset.page; - document.querySelectorAll('#side .nav').forEach((n) => n.classList.toggle('on', n === el)); - renderPage(); - }; -}); +// ── 側邊欄:每個知識庫一項(leo:「每個帳號有獨立的一個頁面」)── +function renderNav() { + const accs = (state && state.accounts) || []; + $('nav').innerHTML = ` + + ${accs.length ? `
知識庫
` : ''} + ${accs.map((a, i) => ` + `).join('')} +
設定
+ + `; + $('nav').querySelectorAll('.nav').forEach((el) => { + el.classList.toggle('on', el.dataset.p === page); + el.onclick = () => { page = el.dataset.p; renderNav(); renderPage(); }; + }); +} -// ── 各頁 ── +// ── 首頁:狀態時間軸(leo:「看守、發現變化、萃取、上傳… 不同 status 在哪裡顯示?」)── function pageHome(s) { - const folders = (s.accounts || []).reduce((n, a) => n + (a.folders || []).length, 0); if (!s.accounts || !s.accounts.length) return onboarding(); + const st = s.steps || []; return `
-

目前狀況

-
-
${s.accounts.length}
個知識庫
-
${folders}
個資料夾在看守
-
${s.extractedOK || 0}
份已整理
+

現在的狀態

+
+ ${st.map((x) => ` +
+ + ${esc(x.title)} + ${esc(x.meta || '')} +
`).join('')}
-

快速動作

-
- - - +

總計

+
+
${s.accounts.length}
個知識庫
+
${s.accounts.reduce((n,a)=>n+(a.folders||[]).length,0)}
個資料夾在看守
+
${s.extractedOK || 0}
份已整理
`; } -function pageFolders(s) { - if (!s.accounts || !s.accounts.length) return onboarding(); - return (s.accounts).map((a) => ` -
${esc(a.name)}${esc(a.host)}
+// ── 各庫頁:動作全部作用在這個庫(不會加錯帳號)── +function pageLib(s, idx) { + const a = s.accounts[idx]; + if (!a) return `
找不到這個知識庫
`; + const portal = 'https://' + a.host.replace('arcrun-cypher-executor.', 'arcrun-rag-ui.') + '/portal/'; + return ` +
+
+
${esc(a.name)}
+
${esc(a.host)}
+
+ + +
${(a.folders || []).map((f) => `
${esc(f.path)} 自動同步中
`).join('') - || `
還沒有資料夾
`} - `).join('') + ` -
- - -
`; + || `
這個知識庫還沒有資料夾
+
按右上的「加入資料夾」,選一個要自動整理的資料夾。
`}`; } function pageAI(s) { @@ -100,11 +126,9 @@ function pageAI(s) {
`; } -// leo:「檢查更新連到 docs 的檢查更新,不正確,**是直接在這裡進行檢查更新**, -// 應該是跳到一個頁面顯示**現在版本與最新版本**,然後**下載可以進行安裝**」 function pageUpdate(s) { const u = updateInfo; - const latest = u ? (u.latest || '查詢中…') : '查詢中…'; + const latest = u ? (u.latest || '查詢中…') : '按「檢查更新」查詢'; let action = ``; let note = ''; if (u && u.staged) { @@ -149,32 +173,30 @@ function onboarding() {
`; } -// ── 渲染 ── function renderPage() { if (!state) return; - const el = $('page'); - el.innerHTML = page === 'folders' ? pageFolders(state) - : page === 'ai' ? pageAI(state) - : page === 'update' ? pageUpdate(state) - : pageHome(state); + let html; + if (page.startsWith('lib:')) html = pageLib(state, Number(page.slice(4))); + else if (page === 'ai') html = pageAI(state); + else if (page === 'update') html = pageUpdate(state); + else html = pageHome(state); + // 每個庫頁底下都給「新增知識庫帳號」入口 + if (page === 'home' && state.accounts && state.accounts.length) { + html += `
`; + } + $('page').innerHTML = html; wire(); } function wire() { const on = (id, fn) => { const e = $(id); if (e) e.onclick = fn; }; - on('qAdd', addFolder); on('fAdd', addFolder); - on('qDocs', () => go.OpenURL('https://rag.arcrun.dev/docs/')); - on('uDocs', () => go.OpenURL('https://rag.arcrun.dev/docs/use/update/')); - on('qPortal', () => { - const a = state.accounts && state.accounts[0]; - if (a) go.OpenURL('https://' + a.host.replace('arcrun-cypher-executor.', 'arcrun-rag-ui.') + '/portal/'); - }); - on('fAcct', showConnect); on('obConnect', showConnect); + on('uDocs', () => go.OpenURL('https://rag.arcrun.dev/docs/')); + on('hAcct', showConnect); on('obConnect', showConnect); on('obInstall', () => go.OpenURL('https://install.arcrun.dev/')); on('aiSave', saveAI); - on('uCheck', checkUpdate); - on('uDownload', downloadUpdate); - on('uApply', applyUpdate); + on('uCheck', checkUpdate); on('uDownload', downloadUpdate); on('uApply', applyUpdate); + document.querySelectorAll('[data-portal]').forEach((b) => { b.onclick = () => go.OpenURL(b.dataset.portal); }); + document.querySelectorAll('[data-addto]').forEach((b) => { b.onclick = () => addFolder(Number(b.dataset.addto)); }); document.querySelectorAll('[data-rm]').forEach((b) => { b.onclick = () => confirmRemove(Number(b.dataset.acc), b.dataset.rm); }); @@ -182,28 +204,27 @@ function wire() { function render(s) { const first = !state; + const navChanged = state && JSON.stringify((state.accounts||[]).map(a=>[a.name,(a.folders||[]).length])) + !== JSON.stringify((s.accounts||[]).map(a=>[a.name,(a.folders||[]).length])); state = s; - $('ver').textContent = s.version ? s.version : ''; + $('ver').textContent = s.version || ''; $('statusBig').textContent = s.statusBig; $('statusBig').classList.toggle('syncing', !!s.syncing); $('statusSub').textContent = s.statusSub; - // 只有狀態列每秒更新;頁面內容不重繪,避免打斷輸入 - if (first) renderPage(); + if (first || navChanged) { renderNav(); renderPage(); } + else if (page === 'home') renderPage(); // 首頁的狀態時間軸要跟著跳 } -async function tick() { - try { render(await go.GetState()); } catch (e) { /* 後端還沒起來 */ } -} +async function tick() { try { render(await go.GetState()); } catch (e) {} } // ── 動作 ── $('btnSync').onclick = async () => { await go.SyncNow(); tick(); }; -async function addFolder() { - if (!state || !state.accounts.length) { showConnect(); return; } +async function addFolder(accIdx) { const p = await go.PickFolder(); if (!p) return; - await go.AddFolder(0, p); - state = await go.GetState(); renderPage(); + await go.AddFolder(accIdx, p); + state = await go.GetState(); renderNav(); renderPage(); } function confirmRemove(accIdx, path) { @@ -215,7 +236,7 @@ function confirmRemove(accIdx, path) { $('c1').onclick = closeSheet; $('c2').onclick = async () => { await go.RemoveFolder(accIdx, path); closeSheet(); - state = await go.GetState(); renderPage(); + state = await go.GetState(); renderNav(); renderPage(); }; }); } @@ -236,7 +257,7 @@ function showConnect() { $('c2').onclick = async () => { try { await go.Connect($('u').value.trim(), $('e').value.trim(), $('p').value); - closeSheet(); state = await go.GetState(); renderPage(); + closeSheet(); state = await go.GetState(); renderNav(); renderPage(); } catch (ex) { $('err').textContent = String(ex); $('err').style.display = 'block'; } }; }); @@ -250,7 +271,6 @@ async function saveAI() { } catch (ex) { $('aiErr').textContent = String(ex); $('aiErr').style.display = 'block'; } } -// 更新:**在 App 裡完成**(leo 指定),不是丟去 docs async function checkUpdate() { updateInfo = { latest: '查詢中…' }; renderPage(); try { updateInfo = await go.CheckUpdate(); } catch (ex) { updateInfo = { err: String(ex) }; } diff --git a/cmd/arcrun-app/frontend/src/style.css b/cmd/arcrun-app/frontend/src/style.css index 8a5f5b4..2e83c3b 100644 --- a/cmd/arcrun-app/frontend/src/style.css +++ b/cmd/arcrun-app/frontend/src/style.css @@ -21,14 +21,21 @@ html, body { overflow: hidden; height: 100%; } padding: 22px 0 14px; border-right: 1px solid rgba(var(--ink-rgb), .1); } -/* 🔴 lockup 底色(實測發現):`arcrun-lockup-h-paper-on-ink.png` **自帶不透明 Ink 底板** - (左上像素 RGBA=23,24,26,255),它的設計前提是「貼在純 Ink 表面上」。 - 我們的背景有紙張紋理 ⇒ 直接放會看到一個突兀的方塊(leo 截圖裡那個白/黑框)。 - ⇒ 深色模式下把品牌區做成純 Ink 表面,與該圖的底板無縫接合。 - 淺色模式用 `-ink.png`(左上像素 alpha=0,透明)⇒ 不需要處理。 */ -#side .brand { padding: 14px 22px 18px; } -#side .brand img { height: 22px; display: block; } -:root[data-theme="dark"] #side .brand { background: var(--ink); } +/* 🔴 lockup(CIS 硬要求,leo 兩次點名): + ① 尺寸照 portal 側欄同一條:clamp(22px,5vw,28px) + **width:auto** + (只寫 height 不寫 width,容器一壓就變形/露出底板邊緣=leo 看到的白邊) + ② `-paper-on-ink.png` **自帶不透明 Ink 底板**(實測左上像素 RGBA=23,24,26,255), + 設計前提是貼在**純 Ink 表面** ⇒ 深色模式把品牌區做成純 Ink,並留 clear space + (CIS:clear space = one chevron height on all four sides)。 + ③ 淺色的 `-ink.png` 是透明的,不需要底板。 */ +#side .brand { padding: 16px 22px 18px; } +#side .brand img { + height: clamp(22px, 5vw, 28px); width: auto; max-width: 100%; display: block; +} +:root[data-theme="dark"] #side .brand { + background: var(--ink); + padding: 16px 22px 18px; /* 上下左右都留白=clear space,底板不會貼邊 */ +} #side .brand .wm-paper { display: none; } :root[data-theme="dark"] #side .brand .wm-ink { display: none; } :root[data-theme="dark"] #side .brand .wm-paper { display: block; } @@ -67,19 +74,32 @@ header { main { flex: 1; overflow-y: auto; padding: 20px 28px 28px; } -/* ── 元件 ── */ +/* ── 元件:規格**逐字對齊 portal**(leo:「網頁的按鈕比較精緻,跟現有的差異較大」)── + portal: .btn { padding:13px; font-size:16px; font-weight:600; border-radius:10px; border:none; background:var(--amber) } + .btn3 { padding:11px 16px; font-size:15px; border-radius:10px; border:1px solid rgba(ink,.25); background:none } + 這裡只把 padding 依桌面視窗略收(網頁是手機優先、桌面不需要那麼大的點擊區)。 */ button { - border: 1px solid rgba(var(--ink-rgb), .16); - background: transparent; color: var(--ink); - padding: 8px 15px; border-radius: 8px; font-size: 14px; cursor: pointer; - transition: background .12s, border-color .12s; + padding: 10px 16px; font-size: 14.5px; border-radius: 10px; + border: 1px solid rgba(var(--ink-rgb), .25); + background: none; color: rgba(var(--ink-rgb), .7); + cursor: pointer; transition: background .12s, color .12s, border-color .12s; +} +button:hover { background: var(--well); color: var(--ink); } +button:disabled { opacity: .45; cursor: default; } +button.primary { + border: none; background: var(--amber); color: var(--knob); font-weight: 600; +} +button.primary:hover { filter: brightness(1.06); background: var(--amber); } +button.ghost { + border-color: transparent; background: none; + color: rgba(var(--ink-rgb), .45); padding: 7px 10px; font-size: 13px; } -button:hover { background: var(--well); } -button.primary { background: var(--amber); border-color: var(--amber); color: var(--knob); font-weight: 600; } -button.primary:hover { filter: brightness(1.06); } -button.ghost { border-color: transparent; color: rgba(var(--ink-rgb), .45); padding: 6px 9px; font-size: 13px; } button.ghost:hover { color: var(--err); background: transparent; } +/* 主題切換(portal 有,daemon 也要有——leo:「它有淺色佈景?」 + ⇒ 是,portal **預設淺色**,深色由使用者切換並存 localStorage。 */ +#themeBtn { padding: 7px 11px; font-size: 13px; } + .card { background: var(--well); border-radius: 12px; padding: 18px 20px; margin-bottom: 14px; @@ -138,3 +158,45 @@ input[type=text], input[type=password] { .sheet h2 { margin: 0 0 6px; font-size: 16.5px; } .sheet p { margin: 0 0 4px; font-size: 14px; color: rgba(var(--ink-rgb), .55); line-height: 1.7; } .sheet .acts { justify-content: flex-end; } + +/* ── 每個知識庫一個獨立分頁(leo 08-04)── + 「同步資料夾那個頁面如果有 2 個庫,每個庫有 30 個同步資料夾,**放得下嗎**? + 應該**一個庫有一個頁簽**或是其他方法可以切換。」 + 「『加入資料夾』按下後**會加在哪個帳號**?**有必要每個帳號有獨立的一個頁面**。」 + ⇒ 側邊欄直接列出每個知識庫;點進去就是那個庫的頁,動作全部作用在它身上,不會加錯。 */ +#side .sec { + padding: 16px 26px 6px; font-size: 11.5px; letter-spacing: .08em; + color: rgba(var(--ink-rgb), .35); +} +#side .nav .cnt { + margin-left: auto; font-size: 11.5px; opacity: .7; + font-family: ui-monospace, Menlo, monospace; +} +#side .nav .nm { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } + +/* 每個庫的頁首:庫名+網址+「開啟知識庫網頁」(leo:不該放首頁,要在各庫上方) */ +.libhead { + display: flex; align-items: center; gap: 14px; + padding-bottom: 14px; margin-bottom: 14px; + border-bottom: 1px solid rgba(var(--ink-rgb), .1); +} +.libhead .g { flex: 1; min-width: 0; } +.libhead .nm { font-size: 16px; font-weight: 600; } +.libhead .host { + font-size: 12px; color: rgba(var(--ink-rgb), .4); + font-family: ui-monospace, Menlo, monospace; + overflow: hidden; text-overflow: ellipsis; white-space: nowrap; +} + +/* 狀態時間軸(leo:「如果看守、發現變化、萃取、上傳… 不同 status 在哪裡顯示?」) */ +.steps { display: flex; flex-direction: column; gap: 2px; margin-top: 12px; } +.step { display: flex; align-items: center; gap: 11px; padding: 7px 0; font-size: 14px; } +.step .dot { + width: 9px; height: 9px; border-radius: 50%; flex: none; + background: rgba(var(--ink-rgb), .18); +} +.step.done .dot { background: var(--ok); } +.step.now .dot { background: var(--amber); animation: pulse 1.1s ease-in-out infinite; } +.step.now { font-weight: 600; } +.step .t { flex: 1; } +.step .m { font-size: 12.5px; color: rgba(var(--ink-rgb), .45); }