t193 二修:側邊欄版面+共用 CSS+App 內更新+兩支視覺機械閘

leo 08-04 看過 v0.17.0 後的四點,逐條回應:

## ① 「不會這裡又寫了一個新的 CSS?」——是,我又寫了一份
真兇:portal 的樣式內嵌在 HTML 裡,沒有獨立檔 ⇒ 四個站各抄一份、本來就在漂移。
解:抽出 InkStoneCo/arcrun-cis/css/arcrun-cis.css 當唯一真相源。
    daemon 的 style.css 現在**一個 hex 都沒有**,顏色全走 var(--...)。

## ② 「完全模仿 gdrive,左方側邊欄,在右方替換頁面」
底部 nav bar 拿掉(leo:「幾乎沒看過這種 nav bar 在下方的」)。
改成左側邊欄四頁:首頁/同步資料夾/AI 設定/版本與更新,右側換頁。
設定不再開 popup;只有「確認移除資料夾」才用覆蓋層。

## ③ 「既然是 Web,你做的時候無法檢視?」——可以,我之前沒做
新增 check-render.sh:headless Chrome 真的渲染一次並**量像素**。
它抓到一個肉眼容易誤判的真 bug:
  arcrun-lockup-h-paper-on-ink.png **自帶不透明 Ink 底板**(實測左上像素 RGBA=23,24,26,255),
  設計前提是貼在純 Ink 表面;我們背景有紙張紋理 ⇒ 出現突兀方塊。
  修:深色模式把品牌區做成純 Ink。實測落差 4(原本會是一個明顯方塊)。
過程中也發現自己對著**舊的 dist**除錯,浪費一輪——這支閘同時防這個。

## ④ 「檢查更新連到 docs,不正確,是直接在這裡進行」
新增「版本與更新」頁:顯示**現在版本 vs 最新版本**,
三段式全在 App 內完成——檢查/下載並安裝/重新啟動套用。
selfupdate 邏輯逐字沿用 arcrun-tray(含 t186 走 raw、t184 蓋正在跑的 .app),不重寫。

## 交貨閘(leo:「你做為檢查有嗎?沒有怎麼交貨?」)
check-cis.sh 擴充:版面檔不准出現任何 hex + 共用底層色票逐項比對。
兩支閘實跑全過。
This commit is contained in:
2026-08-04 23:09:54 +08:00
parent bd0cead00e
commit 3d13e5a54e
9 changed files with 893 additions and 226 deletions
+88 -98
View File
@@ -1,72 +1,62 @@
/* Arcrun 桌面 App — 樣式(t193
/* Arcrun 桌面 App — **版面**樣式(t193
*
* 🔴 leo 2026-08-04:「我的要求是**符合 CIS**,在風格上**跟 portal 一樣**」
* 「CIS 已經提供規範,你做的**連 Logo 都沒放上去**,這跟美不美有關係嗎?
* 要求放進 CIS 是**硬要求**,你做為檢查有嗎?沒有怎麼交貨?」
* 🔴 色票/字體/紙張紋理**不在這裡**——那些在 `arcrun-cis.css`(唯一真相源
* InkStoneCo/arcrun-cis/css/arcrun-cis.cssportallandinginstall 共用同一份)。
* leo 08-04:「不會這裡又寫了一個新的 CSS?前面剛剛才說過**不要創造新的**?」
* ⇒ 本檔**只准出現版面與元件**,一律用 var(--...)**不准寫任何 hex 色碼**。
*
* ⇒ 本檔的色票/字體/背景紋理**逐字取自 portal**
* matrix/arcrun:console-ui/public/portal/index.html 的 :root 區塊)
* **不自創任何顏色**。要改就去改 portal 那份、再同步過來。
*
* 這也是換掉 fyne 的理由:fyne 全自繪、CSS 套不進去,
* 換成 WailsWebView)後 daemon 與 portal 才可能是**同一套視覺**。
* 版面(leo 08-04 指定,附 Google Drive 截圖):
* 「可以完全模仿 gdrive,**左方側邊欄,在右方替換頁面**
* 你現在做成在底下產生 nav bar,幾乎沒看過這種 nav bar 在下方的,會令人十分不習慣」
*/
:root {
--paper-a: #FDFCFB; --paper-b: #F2F1ED;
--ink: #17181A; --ink-rgb: 23,24,26;
--amber: #B04A2F; --amber-rgb: 176,74,47;
--ok: #1d7a48; --ok-rgb: 29,122,72;
--err: #b03a26; --err-rgb: 176,58,38;
--well: rgba(23,24,26,.06); --well2: rgba(23,24,26,.08);
}
:root[data-theme="dark"] {
--paper-a: #1b1c1e; --paper-b: #1e1f21;
--ink: #FDFCFB; --ink-rgb: 253,252,251;
--amber: #D9784F; --amber-rgb: 217,120,79;
--ok: #7fe0a8; --ok-rgb: 63,190,120;
--err: #e58575; --err-rgb: 217,95,76;
--well: rgba(0,0,0,.25); --well2: rgba(0,0,0,.3);
}
html, body { overflow: hidden; height: 100%; }
/* portal 的紙張紋理背景——三站一致的識別(不是裝飾,是 CIS 的一部分) */
html, body {
margin: 0; height: 100%;
background: repeating-linear-gradient(0deg,
var(--paper-a) 0px, var(--paper-a) 3px,
var(--paper-b) 3px, var(--paper-b) 4px);
color: var(--ink);
font-family: -apple-system, "IBM Plex Sans", "PingFang TC", "Noto Sans TC",
"Microsoft JhengHei", system-ui, sans-serif;
font-size: 16px;
-webkit-font-smoothing: antialiased;
overflow: hidden;
}
input, textarea, button, select { font-family: inherit; }
::placeholder { color: rgba(var(--ink-rgb), .35); }
input:focus, select:focus { outline: 2px solid rgba(var(--amber-rgb), .5); outline-offset: 1px; }
a { color: var(--amber); }
.muted { color: rgba(var(--ink-rgb), .55); }
.dim { color: rgba(var(--ink-rgb), .4); }
/* ── 骨架:左欄固定、右側換頁 ── */
#app { display: flex; height: 100vh; }
/* ── 版面:上(品牌+狀態)/中(可捲清單)/下(動作列)── */
#app { display: flex; flex-direction: column; height: 100vh; }
#side {
width: 232px; flex: none;
display: flex; flex-direction: column;
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); }
#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; }
/* 側邊欄項目:選中用 Relation(CIS 唯一強調色,≤5% 畫面 ⇒ 一次只有一項) */
#side nav { display: flex; flex-direction: column; gap: 2px; padding: 0 12px; }
#side .nav {
display: flex; align-items: center; gap: 11px;
padding: 10px 14px; border-radius: 999px;
font-size: 14.5px; color: rgba(var(--ink-rgb), .62);
cursor: pointer; user-select: none;
}
#side .nav:hover { background: var(--well); color: var(--ink); }
#side .nav.on { background: var(--amber); color: var(--knob); font-weight: 600; }
#side .nav .ic { width: 18px; text-align: center; font-size: 15px; opacity: .9; }
#side .side-foot { margin-top: auto; padding: 12px 22px 0; }
#side .ver { font-size: 12px; color: rgba(var(--ink-rgb), .35); font-family: ui-monospace, Menlo, monospace; }
#content { flex: 1; min-width: 0; display: flex; flex-direction: column; }
header {
padding: 18px 24px 14px;
padding: 22px 28px 16px;
display: flex; align-items: flex-start; gap: 18px;
border-bottom: 1px solid rgba(var(--ink-rgb), .1);
display: flex; align-items: flex-start; gap: 20px;
}
/* CIS lockup:淺色用 ink 版、深色用 paper 版(與 portal 同一組 PNG、同一套切換規則)。
⚠️ 不可用自產的 SVG 描邊版(字腔會缺,08-01 已作廢,見 arcrun-cis/README.md)。 */
.brand img { height: 26px; display: block; }
.brand .wm-paper { display: none; }
:root[data-theme="dark"] .brand .wm-ink { display: none; }
:root[data-theme="dark"] .brand .wm-paper { display: block; }
.status { flex: 1; min-width: 0; }
.status .big { font-size: 17px; font-weight: 600; letter-spacing: .02em; }
.status .sub { margin-top: 4px; font-size: 13.5px; color: rgba(var(--ink-rgb), .5); }
.status .big { font-size: 18px; font-weight: 600; letter-spacing: .02em; }
.status .sub { margin-top: 5px; font-size: 13.5px; color: rgba(var(--ink-rgb), .5); }
/* 同步中會呼吸——「看得出來在動」(issue #17) */
.status .big.syncing::after {
content: ""; display: inline-block; width: 7px; height: 7px; margin-left: 9px;
@@ -75,54 +65,63 @@ header {
}
@keyframes pulse { 0%,100% { opacity: .25 } 50% { opacity: 1 } }
/* 按鈕:Relation 是**唯一重點色**,CIS 規定 ≤ 一個畫面的 5% ⇒ 只有主要動作用它 */
main { flex: 1; overflow-y: auto; padding: 20px 28px 28px; }
/* ── 元件 ── */
button {
border: 1px solid rgba(var(--ink-rgb), .16);
background: transparent; color: var(--ink);
padding: 8px 14px; border-radius: 8px; font-size: 14px; cursor: pointer;
padding: 8px 15px; border-radius: 8px; font-size: 14px; cursor: pointer;
transition: background .12s, border-color .12s;
}
button:hover { background: var(--well); }
button.primary {
background: var(--amber); border-color: var(--amber);
color: #FDFCFB; font-weight: 600;
}
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), .5); padding: 6px 8px; }
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; }
/* ── 中:資料夾清單(leo:「幾十個,根本塞不下」⇒ 這一區才捲動)── */
main { flex: 1; overflow-y: auto; padding: 6px 0; }
.acct {
padding: 16px 24px 6px;
display: flex; align-items: baseline; gap: 10px;
.card {
background: var(--well); border-radius: 12px;
padding: 18px 20px; margin-bottom: 14px;
}
.acct .name { font-size: 15px; font-weight: 600; }
.acct .host { font-size: 12.5px; color: rgba(var(--ink-rgb), .4); font-family: ui-monospace, Menlo, monospace; }
.card h3 { margin: 0 0 4px; font-size: 15px; }
.card .d { font-size: 13.5px; color: rgba(var(--ink-rgb), .55); line-height: 1.8; }
.card .row { display: flex; align-items: center; gap: 12px; }
.card .row .g { flex: 1; min-width: 0; }
.acct { padding: 4px 2px 8px; display: flex; align-items: baseline; gap: 10px; }
.acct .name { font-size: 14.5px; font-weight: 600; }
.acct .host { font-size: 12px; color: rgba(var(--ink-rgb), .4); font-family: ui-monospace, Menlo, monospace; }
.folder {
margin: 0 16px 6px; padding: 11px 14px;
padding: 11px 15px; margin-bottom: 7px;
background: var(--well); border-radius: 10px;
display: flex; align-items: center; gap: 12px;
}
.folder .path { flex: 1; min-width: 0; font-size: 14px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.folder .tag { font-size: 12px; color: var(--ok); white-space: nowrap; }
/* 空狀態=onboarding(不是空白面板) */
.empty { padding: 56px 32px; text-align: center; }
.big-num { font-size: 26px; font-weight: 600; letter-spacing: .01em; }
.kv { display: flex; gap: 26px; margin-top: 4px; }
.kv .k { font-size: 12.5px; color: rgba(var(--ink-rgb), .45); }
.empty { padding: 52px 24px; text-align: center; }
.empty .t { font-size: 17px; font-weight: 600; margin-bottom: 10px; }
.empty .d { font-size: 14px; color: rgba(var(--ink-rgb), .55); line-height: 1.9; margin-bottom: 22px; }
.empty .d { font-size: 14px; color: rgba(var(--ink-rgb), .55); line-height: 1.9; margin-bottom: 20px; }
footer {
padding: 12px 24px; border-top: 1px solid rgba(var(--ink-rgb), .1);
display: flex; align-items: center; gap: 8px;
label.radio { display: flex; align-items: flex-start; gap: 10px; margin: 12px 0; font-size: 14px; cursor: pointer; }
label.radio input { margin-top: 3px; }
input[type=text], input[type=password] {
width: 100%; box-sizing: border-box; padding: 9px 12px; font-size: 14px;
border: 1px solid rgba(var(--ink-rgb), .18); border-radius: 8px;
background: var(--paper-b); color: var(--ink);
}
footer .spacer { flex: 1; }
footer .ver { font-size: 12.5px; color: rgba(var(--ink-rgb), .35); font-family: ui-monospace, Menlo, monospace; }
.field { margin: 14px 0; }
.field .lb { font-size: 13px; margin-bottom: 6px; color: rgba(var(--ink-rgb), .6); }
.err { color: var(--err); font-size: 13px; margin-top: 10px; }
.acts { display: flex; gap: 8px; margin-top: 18px; }
/* 對話框:**內嵌覆蓋層**,不是另開一個小 popup 視窗
(leo:「每個功能都開一個小小的 popup 視窗,非常缺乏整體感」) */
/* 覆蓋層:只給「確認刪除」這類必須打斷的動作,設定頁一律走右側換頁 */
.overlay {
position: fixed; inset: 0; background: rgba(0,0,0,.42);
display: flex; align-items: center; justify-content: center;
@@ -130,21 +129,12 @@ footer .ver { font-size: 12.5px; color: rgba(var(--ink-rgb), .35); font-family:
}
.overlay.on { opacity: 1; pointer-events: auto; }
.sheet {
width: min(520px, calc(100vw - 64px));
width: min(460px, calc(100vw - 64px));
background: var(--paper-a); color: var(--ink);
border: 1px solid rgba(var(--ink-rgb), .12);
border-radius: 14px; padding: 24px;
border-radius: 14px; padding: 22px 24px;
box-shadow: 0 18px 50px rgba(0,0,0,.28);
}
.sheet h2 { margin: 0 0 6px; font-size: 17px; }
.sheet p { margin: 0 0 16px; font-size: 14px; color: rgba(var(--ink-rgb), .55); line-height: 1.7; }
.sheet label { display: block; font-size: 13px; margin: 12px 0 5px; color: rgba(var(--ink-rgb), .6); }
.sheet input[type=text], .sheet input[type=password] {
width: 100%; box-sizing: border-box; padding: 9px 11px; font-size: 14px;
border: 1px solid rgba(var(--ink-rgb), .18); border-radius: 8px;
background: var(--paper-b); color: var(--ink);
}
.sheet .radio { display: flex; align-items: flex-start; gap: 9px; margin: 10px 0; font-size: 14px; cursor: pointer; }
.sheet .radio input { margin-top: 3px; }
.sheet .acts { display: flex; justify-content: flex-end; gap: 8px; margin-top: 22px; }
.sheet .err { color: var(--err); font-size: 13px; margin-top: 10px; }
.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; }