Files
HomeAgent/internal/plugins/webui/dashboard.css
JianFeeeee ef08ebb7d3 chore(mascot): 立绘 v2 —— 真 alpha 素材 + 头部裁切头像 + 素材判据
老大给的 4 张新立绘里有 3 张是「棋盘格假透明」JPEG(像素是灰白格子,
不是 alpha)。直接进仓库会在深色面板上露出格子,所以先抠成真 alpha 再落盘。

## 为什么主图与头像要分两个文件

现役 mascot.webp 唯一的消费点是**聊天消息的 30px 圆形头像**
(dashboard.js 4 处 aiAvatar + GUI app.js 2 处),概览页并没有放立绘。
主图是坐姿全身,按现有 CSS(object-fit:cover + scale(1.5))裁成圆后
只剩裙子、看不到脸 —— 只换图不做头像版,等于把头像换成一张看不懂的图。

- assets/branding/mascot-xiaozhai.webp        1024² 主立绘(官方展示)
- assets/branding/mascot-xiaozhai-avatar.webp  512² 头部方裁(运行期源)
- internal/plugins/webui/mascot.webp           256² 头像版(原 133KB→24KB)
- cmd/gui/renderer/mascot.webp                 128² 头像版(原 133KB→9KB)

参考稿另存三张(不参与运行):sheet(设定总图)/ portrait(半身)/
turnaround(三视图)。

## 抠图怎么做的(可复现,不是手搓)

背景是 20.45px 周期的棋盘格(237/254 两色)。判据取「中性灰 且 灰度≥228」
的像素做候选,再从图像边界做连通域传播 —— 这样白裙子/白围裙因为不中性
(带蓝调)不会被误判,避免了当年 site/README 记录的 flood-fill 渗色问题。
边界再内缩 1px 抹白边,0.7px 高斯羽化去锯齿。抠完透明占比 55.8%,
深/浅两种背景上目检均无残留格子与白边。

## CSS:scale(1.5) → 1.25

原来的 1.5 是为「全身图裁圆」补的放大。换成头部方裁后,1.5 会切掉两侧
发绳与呆毛 —— 按真实 30px 渲染放大对照看过 1.0/1.15/1.25/1.4 四档。

## 新增判据(含反向验证)

internal/plugins/webui/mascot_asset_test.go 直接读 WebP 容器头,
断言 VP8X 的 Alpha 标志位与 ALPH chunk **同时存在**,外加正方形/≥128px/
≤128KB。理由:这类退化在肉眼 review 里抓不住(缩到 30px 就是一团浅色),
而用户每天都在看;且 site/README.md 记着当年正因为「白底图没法抠」
干脆不放立绘,这个坑值得一个自动化判据。

反向验证(已实跑,非声称):把素材换成 HEAD 旧图,判据报红并准确指出
「Alpha 标志位未置位 / 缺 ALPH chunk / 体积超限」;换回新图转绿。
另有一条独立用例构造「带 VP8X 但不置 Alpha 位」的最小容器,
确认解析器不会对退化素材误判为绿。

## 顺带修的两处

1. assets/docs/{zh,en}/PLUGIN_DEV.md 的 mascot 引用路径是坏的:
   `../../assets/branding/...` 从 assets/docs/zh/ 出发解析成
   `assets/assets/branding/...`,文件不存在(旧版一直是坏的)。
   已改为 `../../branding/...` 并逐条验证路径可解析。
   同时指向新的头像版(那处是 20px 圆形内联小图标)。
2. gzip.go / gzip_test.go 注释里的「mascot.webp(133KB)」改为不带死数字
   的说法 —— 体积会随素材变,注释写死数字只会变成误导。

## 验证

go build ./...            → 0
go vet ./internal/plugins/webui/ ./cmd/gui/ → 干净
gofmt -l                  → 空
go test ./internal/plugins/webui/ -count=1 → ok(15.1s,含新增 2 个用例)

## 没做的事

- 没碰 main、没开 PR、没部署、没重启服务。
- **概览页仍未放立绘**。site/README 里「素材不可用」这条已不成立(素材有
  alpha 了),但版面是否该放图属于设计取舍,不在本次范围 —— 文档已相应改写,
  不再用「素材不行」当理由。
- 抠图对发丝边缘是「够用」不是「完美」:0.7px 羽化在 1024 下看不出来,
  但若将来要印物料级大图,需要重出带原生 alpha 的源图。
2026-10-18 07:38:10 +08:00

3101 lines
90 KiB
CSS
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

:root {
/* ===== NapCat Design DNA tokens ===== */
--sakura-100: #ffe4e9;
--sakura-200: #ffcdd9;
--sakura-300: #ff9eb5;
--sakura-400: #ff7fac;
--sakura-500: #f33b7c;
--sakura-600: #c92462;
--sakura-700: #991b4b;
--frost-100: #d7e8ee;
--frost-200: #a9d6e3;
--frost-300: #88c0d0;
--frost-400: #5ea8bf;
--frost-500: #3f88a3;
--success: #17a964;
--warning: #d99a2b;
--error: #db3694;
--info: #3f6ef5;
--glass-bg: rgba(13, 18, 34, 0.6);
--glass-bg-strong: rgba(13, 18, 34, 0.82);
--glass-blur: 14px;
--glass-border: rgba(255, 255, 255, 0.08);
--glass-hover: rgba(255, 255, 255, 0.05);
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.25);
--shadow-md: 0 4px 16px rgba(0, 0, 0, 0.28);
--shadow-lg: 0 12px 36px rgba(0, 0, 0, 0.38);
--shadow-glow:
0 0 0 1px rgba(var(--accent-rgb), 0.4),
0 4px 20px rgba(var(--accent-rgb), 0.18);
--radius-sm: 6px;
--radius-md: 10px;
--radius-lg: 14px;
--radius-pill: 999px;
--font-sans:
"Segoe UI", "Segoe UI Variable Text", -apple-system,
BlinkMacSystemFont, Roboto, "PingFang SC", "Hiragino Sans GB",
"Microsoft YaHei UI", "Microsoft YaHei", sans-serif;
--font-mono:
"JetBrains Mono", ui-monospace, "SFMono-Regular", "SF Mono", "Menlo",
"Consolas", monospace;
--ease-out: cubic-bezier(0.22, 1, 0.36, 1);
--dur-micro: 150ms;
--dur-normal: 300ms;
/* ===== Compat tokens (rethemed) ===== */
--bg-primary: #0b1020;
--bg-secondary: rgba(17, 24, 44, 0.72);
--bg-card: rgba(17, 24, 44, 0.6);
--bg-input: rgba(13, 18, 34, 0.75);
--bg-hover: rgba(255, 255, 255, 0.06);
--text-primary: #eef1f8;
--text-secondary: #a7b0c4;
--text-muted: #77809a;
--border-color: rgba(255, 255, 255, 0.09);
/* accent 的 rgb 分量:所有 hardcoded rgba(255,127,172,x) 都改用它,
这样换配色(尤其 monochrome)时连阴影/描边一起换,不会残留粉色。 */
--accent-rgb: 255, 127, 172;
--accent-strong-rgb: 243, 59, 124;
--accent: #ff7fac;
--accent-bg: rgba(var(--accent-rgb), 0.14);
--toast-bg: rgba(23, 169, 100, 0.16);
--toast-color: #6ee7a8;
--toast-error-bg: rgba(219, 54, 148, 0.18);
--toast-error-color: #ff9ec6;
--pre-color: #cdd3f5;
--pre-bg: rgba(10, 14, 28, 0.85);
--chat-bg: rgba(10, 14, 28, 0.7);
--msg-user-bg: rgba(var(--accent-rgb), 0.16);
--msg-user-color: #ffb9d0;
--msg-assistant-bg: rgba(136, 192, 208, 0.14);
--msg-assistant-color: #a9d6e3;
--msg-system-bg: rgba(63, 110, 245, 0.16);
--msg-system-color: #a3b8ff;
--msg-bubble-bg: #161b2e;
--msg-bubble-color: #e9edf6;
--msg-bubble-border: rgba(255, 255, 255, 0.1);
--kv-border: rgba(255, 255, 255, 0.07);
--btn-ghost-border: rgba(255, 255, 255, 0.14);
--btn-ghost-hover-bg: rgba(255, 255, 255, 0.07);
--save-btn-border: #d99a2b;
--loading-border: rgba(255, 255, 255, 0.12);
--loading-top: #ff7fac;
--grad-a: rgba(var(--accent-rgb), 0.14);
--grad-b: rgba(136, 192, 208, 0.12);
--grad-c: rgba(var(--accent-strong-rgb), 0.1);
--c-sakura: #ff7fac;
--c-cyan: #2dd4bf;
--c-violet: #a78bfa;
--c-emerald: #34d399;
--c-amber: #fbbf24;
--c-blue: #60a5fa;
}
[data-theme="light"] {
--glass-bg: rgba(255, 255, 255, 0.66);
--glass-bg-strong: rgba(255, 255, 255, 0.88);
--glass-border: rgba(var(--accent-rgb), 0.22);
--glass-hover: rgba(var(--accent-rgb), 0.07);
--shadow-sm: 0 1px 3px rgba(153, 27, 75, 0.08);
--shadow-md: 0 6px 20px rgba(153, 27, 75, 0.1);
--shadow-lg: 0 16px 40px rgba(153, 27, 75, 0.14);
--shadow-glow:
0 0 0 1px rgba(var(--accent-strong-rgb), 0.3), 0 6px 22px rgba(var(--accent-strong-rgb), 0.14);
--bg-primary: #ffffff;
--bg-secondary: rgba(255, 255, 255, 0.78);
--bg-card: rgba(255, 255, 255, 0.72);
--bg-input: rgba(255, 224, 233, 0.55);
--bg-hover: rgba(var(--accent-rgb), 0.08);
--text-primary: #3b2030;
--text-secondary: #7a5c6b;
--text-muted: #a48a96;
--border-color: rgba(201, 36, 98, 0.14);
--accent: #c92462;
--accent-bg: #ffe4e9;
--toast-bg: rgba(23, 169, 100, 0.14);
--toast-color: #128a52;
--toast-error-bg: rgba(219, 54, 148, 0.12);
--toast-error-color: #c2185b;
--pre-color: #5c4060;
--pre-bg: #fff0f5;
--chat-bg: #fff0f5;
--msg-user-bg: #ffe4e9;
--msg-user-color: #c2185b;
--msg-assistant-bg: #e3f2f6;
--msg-assistant-color: #2f7188;
--msg-system-bg: #e6ecfe;
--msg-system-color: #3f6ef5;
--msg-bubble-bg: #ffffff;
--msg-bubble-color: #262a33;
--msg-bubble-border: rgba(201, 36, 98, 0.14);
--kv-border: rgba(201, 36, 98, 0.1);
--btn-ghost-border: rgba(201, 36, 98, 0.22);
--btn-ghost-hover-bg: #ffe4e9;
--save-btn-border: #d99a2b;
--loading-border: rgba(201, 36, 98, 0.18);
--loading-top: #c92462;
--grad-a: rgba(var(--accent-rgb), 0.16);
--grad-b: rgba(136, 192, 208, 0.12);
--grad-c: rgba(var(--accent-strong-rgb), 0.12);
}
[data-color="sakura"][data-theme="light"] {
--accent-rgb: 201, 36, 98;
--accent-strong-rgb: 153, 27, 75;
--bg-primary: #ffffff;
--bg-input: rgba(255, 224, 233, 0.55);
--bg-hover: rgba(var(--accent-rgb), 0.08);
--pre-bg: #fff0f5;
--chat-bg: #fff0f5;
--msg-bubble-border: rgba(201, 36, 98, 0.14);
--btn-ghost-hover-bg: #ffe4e9;
--grad-a: rgba(var(--accent-rgb), 0.14);
--grad-c: rgba(var(--accent-strong-rgb), 0.09);
}
[data-color="cyan"] {
--accent-rgb: 45, 212, 191;
--accent-strong-rgb: 13, 148, 136;
--accent: #2dd4bf;
--accent-bg: rgba(45, 212, 191, 0.14);
--msg-user-bg: rgba(45, 212, 191, 0.16);
--msg-user-color: #99f6e4;
--loading-top: #2dd4bf;
--grad-a: rgba(45, 212, 191, 0.14);
--grad-c: rgba(45, 212, 191, 0.1);
}
[data-color="cyan"][data-theme="light"] {
--accent-rgb: 15, 118, 110;
--accent-strong-rgb: 15, 118, 110;
--accent: #0f766e;
--accent-bg: #ccfbf1;
--msg-user-bg: #ccfbf1;
--msg-user-color: #0f766e;
--loading-top: #0f766e;
--bg-primary: #f0fdfa;
--bg-input: rgba(204, 251, 241, 0.6);
--bg-hover: rgba(13, 148, 136, 0.08);
--pre-bg: #e7f9f3;
--chat-bg: #e7f9f3;
--msg-bubble-border: rgba(13, 148, 136, 0.14);
--btn-ghost-hover-bg: #ccfbf1;
--grad-a: rgba(13, 148, 136, 0.14);
--grad-c: rgba(13, 148, 136, 0.09);
}
[data-color="violet"] {
--accent-rgb: 167, 139, 250;
--accent-strong-rgb: 124, 58, 237;
--accent: #a78bfa;
--accent-bg: rgba(167, 139, 250, 0.14);
--msg-user-bg: rgba(167, 139, 250, 0.16);
--msg-user-color: #ddd6fe;
--loading-top: #a78bfa;
--grad-a: rgba(167, 139, 250, 0.14);
--grad-c: rgba(167, 139, 250, 0.1);
}
[data-color="violet"][data-theme="light"] {
--accent-rgb: 124, 58, 237;
--accent-strong-rgb: 109, 40, 217;
--accent: #7c3aed;
--accent-bg: #ede9fe;
--msg-user-bg: #ede9fe;
--msg-user-color: #7c3aed;
--loading-top: #7c3aed;
--bg-primary: #f8f6ff;
--bg-input: rgba(237, 233, 254, 0.6);
--bg-hover: rgba(124, 58, 237, 0.08);
--pre-bg: #f3f0ff;
--chat-bg: #f3f0ff;
--msg-bubble-border: rgba(124, 58, 237, 0.14);
--btn-ghost-hover-bg: #ede9fe;
--grad-a: rgba(124, 58, 237, 0.13);
--grad-c: rgba(124, 58, 237, 0.08);
}
[data-color="emerald"] {
--accent-rgb: 52, 211, 153;
--accent-strong-rgb: 5, 150, 105;
--accent: #34d399;
--accent-bg: rgba(52, 211, 153, 0.14);
--msg-user-bg: rgba(52, 211, 153, 0.16);
--msg-user-color: #a7f3d0;
--loading-top: #34d399;
--grad-a: rgba(52, 211, 153, 0.14);
--grad-c: rgba(52, 211, 153, 0.1);
}
[data-color="emerald"][data-theme="light"] {
--accent-rgb: 5, 150, 105;
--accent-strong-rgb: 4, 120, 87;
--accent: #059669;
--accent-bg: #d1fae5;
--msg-user-bg: #d1fae5;
--msg-user-color: #059669;
--loading-top: #059669;
--bg-primary: #f0fdf6;
--bg-input: rgba(209, 250, 229, 0.6);
--bg-hover: rgba(5, 150, 105, 0.08);
--pre-bg: #e9f9f0;
--chat-bg: #e9f9f0;
--msg-bubble-border: rgba(5, 150, 105, 0.14);
--btn-ghost-hover-bg: #d1fae5;
--grad-a: rgba(5, 150, 105, 0.14);
--grad-c: rgba(5, 150, 105, 0.09);
}
[data-color="amber"] {
--accent-rgb: 251, 191, 36;
--accent-strong-rgb: 217, 119, 6;
--accent: #fbbf24;
--accent-bg: rgba(251, 191, 36, 0.14);
--msg-user-bg: rgba(251, 191, 36, 0.16);
--msg-user-color: #fde68a;
--loading-top: #fbbf24;
--grad-a: rgba(251, 191, 36, 0.14);
--grad-c: rgba(251, 191, 36, 0.1);
}
[data-color="amber"][data-theme="light"] {
--accent-rgb: 217, 119, 6;
--accent-strong-rgb: 180, 83, 9;
--accent: #d97706;
--accent-bg: #fef3c7;
--msg-user-bg: #fef3c7;
--msg-user-color: #b45309;
--loading-top: #d97706;
--bg-primary: #fffdf4;
--bg-input: rgba(254, 243, 199, 0.6);
--bg-hover: rgba(217, 119, 6, 0.08);
--pre-bg: #fdf6e1;
--chat-bg: #fdf6e1;
--msg-bubble-border: rgba(217, 119, 6, 0.14);
--btn-ghost-hover-bg: #fef3c7;
--grad-a: rgba(217, 119, 6, 0.13);
--grad-c: rgba(217, 119, 6, 0.08);
}
[data-color="blue"] {
--accent-rgb: 96, 165, 250;
--accent-strong-rgb: 37, 99, 235;
--accent: #60a5fa;
--accent-bg: rgba(96, 165, 250, 0.14);
--msg-user-bg: rgba(96, 165, 250, 0.16);
--msg-user-color: #bfdbfe;
--loading-top: #60a5fa;
--grad-a: rgba(96, 165, 250, 0.14);
--grad-c: rgba(96, 165, 250, 0.1);
}
[data-color="blue"][data-theme="light"] {
--accent-rgb: 37, 99, 235;
--accent-strong-rgb: 29, 78, 216;
--accent: #2563eb;
--accent-bg: #dbeafe;
--msg-user-bg: #dbeafe;
--msg-user-color: #2563eb;
--loading-top: #2563eb;
--bg-primary: #f7faff;
--bg-input: rgba(219, 234, 254, 0.6);
--bg-hover: rgba(37, 99, 235, 0.08);
--pre-bg: #eef4ff;
--chat-bg: #eef4ff;
--msg-bubble-border: rgba(37, 99, 235, 0.14);
--btn-ghost-hover-bg: #dbeafe;
--grad-a: rgba(37, 99, 235, 0.13);
--grad-c: rgba(37, 99, 235, 0.08);
}
[data-color="mono"] {
--accent-rgb: 205, 210, 220;
--accent-strong-rgb: 150, 156, 168;
--accent: #cdd2dc;
--accent-bg: rgba(205, 210, 220, 0.12);
--sakura-300: #b9bfcc;
--sakura-400: #cdd2dc;
--sakura-500: #e6e9ef;
--frost-300: #a8aeba;
--frost-400: #8b919d;
--msg-user-bg: rgba(205, 210, 220, 0.12);
--msg-user-color: #e6e9ef;
--msg-assistant-bg: rgba(205, 210, 220, 0.09);
--msg-assistant-color: #c9ced9;
--loading-top: #cdd2dc;
--grad-a: rgba(205, 210, 220, 0.12);
--grad-b: rgba(205, 210, 220, 0.09);
--grad-c: rgba(205, 210, 220, 0.07);
--c-sakura: #cdd2dc;
--shadow-glow:
0 0 0 1px rgba(205, 210, 220, 0.2), 0 4px 20px rgba(0, 0, 0, 0.3);
}
[data-color="mono"][data-theme="light"] {
--accent-rgb: 60, 64, 72;
--accent-strong-rgb: 30, 32, 38;
--accent: #2f333b;
--accent-bg: #ececf0;
--sakura-300: #6b7280;
--sakura-400: #3f434c;
--sakura-500: #23262c;
--frost-300: #7c828e;
--frost-400: #5f6570;
--text-primary: #1f2126;
--text-secondary: #5b5f66;
--text-muted: #8b8f97;
--border-color: rgba(0, 0, 0, 0.1);
--kv-border: rgba(0, 0, 0, 0.08);
--btn-ghost-border: rgba(0, 0, 0, 0.16);
--glass-border: rgba(0, 0, 0, 0.1);
--glass-hover: rgba(0, 0, 0, 0.04);
--shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.06);
--shadow-md: 0 6px 20px rgba(0, 0, 0, 0.08);
--shadow-lg: 0 16px 40px rgba(0, 0, 0, 0.1);
--shadow-glow:
0 0 0 1px rgba(0, 0, 0, 0.1), 0 4px 20px rgba(0, 0, 0, 0.08);
--msg-user-bg: #ececf0;
--msg-user-color: #23262c;
--msg-assistant-bg: #f0f0f2;
--msg-assistant-color: #3f434c;
--loading-top: #2f333b;
--bg-primary: #ffffff;
--bg-input: rgba(0, 0, 0, 0.045);
--bg-hover: rgba(0, 0, 0, 0.05);
--pre-bg: #f5f5f7;
--chat-bg: #f5f5f7;
--msg-bubble-border: rgba(0, 0, 0, 0.1);
--btn-ghost-hover-bg: #ececf0;
--grad-a: rgba(0, 0, 0, 0.05);
--grad-b: rgba(0, 0, 0, 0.04);
--grad-c: rgba(0, 0, 0, 0.03);
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: var(--font-sans);
}
body {
background:
radial-gradient(
900px 700px at 85% -10%,
var(--grad-a),
transparent 60%
),
radial-gradient(
800px 600px at -10% 20%,
var(--grad-b),
transparent 60%
),
radial-gradient(
700px 500px at 50% 110%,
var(--grad-c),
transparent 60%
),
var(--bg-primary);
background-attachment: fixed;
color: var(--text-primary);
min-height: 100vh;
overflow-x: hidden;
-webkit-font-smoothing: antialiased;
font-size: 14px;
text-rendering: optimizeLegibility;
user-select: none;
-webkit-user-select: none;
transition:
background 0.3s,
color 0.2s;
}
.selectable,
.msg .text,
.msg .tc-args,
.msg .tc-result,
.msg .reasoning-body,
pre,
input,
textarea,
.term-buf {
user-select: text;
-webkit-user-select: text;
}
nav {
background: var(--bg-secondary);
backdrop-filter: blur(var(--glass-blur)) saturate(1.4);
-webkit-backdrop-filter: blur(var(--glass-blur)) saturate(1.4);
width: 16rem;
height: 100vh;
position: fixed;
top: 0;
left: 0;
z-index: 100;
display: flex;
flex-direction: column;
border-right: 1px solid var(--glass-border);
padding: 16px 12px;
overflow-y: auto;
overflow-x: hidden;
transition:
transform var(--dur-normal) var(--ease-out),
background 0.2s,
border 0.2s;
}
nav h1 {
font-size: 16.5px;
font-weight: 700;
/* 品牌字用 sakura→frost 渐变:比纯色更像「产品」,也不跟导航选中态抢颜色 */
background: linear-gradient(100deg, var(--sakura-300), var(--frost-300));
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
margin: 2px 10px 18px;
white-space: nowrap;
letter-spacing: -0.015em;
display: flex;
align-items: center;
gap: 8px;
}
.nav-links {
display: flex;
flex-direction: column;
gap: 2px;
flex: 1;
}
nav a {
display: flex;
align-items: center;
gap: 10px;
padding: 10px 12px;
color: var(--text-secondary);
text-decoration: none;
font-size: 13.5px;
font-weight: 500;
cursor: pointer;
border-radius: var(--radius-md);
/* 选中条用 inset 阴影而不是 border-left:border 会把整行文字右推 3px,
hover 时图标跟着跳一下 —— 这种「一像素级不稳」正是廉价感的来源。 */
box-shadow: inset 2px 0 0 transparent;
transition:
background var(--dur-micro) var(--ease-out),
color var(--dur-micro) var(--ease-out),
box-shadow var(--dur-micro) var(--ease-out);
}
.nav-ico {
width: 17px;
height: 17px;
flex-shrink: 0;
fill: none;
stroke: currentColor;
stroke-width: 1.7;
stroke-linecap: round;
stroke-linejoin: round;
opacity: 0.8;
}
nav a:hover {
color: var(--text-primary);
background: var(--glass-hover);
}
nav a.active {
color: var(--accent);
background: var(--accent-bg);
box-shadow: inset 2px 0 0 var(--accent);
font-weight: 600;
}
nav a.active .nav-ico {
opacity: 1;
}
.nav-footer {
border-top: 1px solid var(--glass-border);
padding: 12px 4px 4px;
display: flex;
justify-content: center;
}
.main-wrap {
margin-left: 16rem;
min-height: 100vh;
display: flex;
flex-direction: column;
transition: margin-left var(--dur-normal) var(--ease-out);
}
body.sidebar-collapsed nav {
transform: translateX(-100%);
}
body.sidebar-collapsed .main-wrap {
margin-left: 0;
}
.topbar {
position: sticky;
top: 0;
z-index: 90;
height: 56px;
display: flex;
align-items: center;
gap: 12px;
padding: 0 24px;
background: var(--bg-secondary);
backdrop-filter: blur(var(--glass-blur)) saturate(1.4);
-webkit-backdrop-filter: blur(var(--glass-blur)) saturate(1.4);
border-bottom: 1px solid var(--glass-border);
}
.sidebar-toggle {
background: transparent;
border: 1px solid var(--glass-border);
color: var(--text-secondary);
cursor: pointer;
padding: 4px 10px;
border-radius: var(--radius-sm);
font-size: 13px;
line-height: 1.2;
transition:
all var(--dur-micro) var(--ease-out),
transform var(--dur-micro) var(--ease-out);
}
.sidebar-toggle:hover {
color: var(--accent);
border-color: rgba(var(--accent-rgb), 0.5);
transform: scale(1.05);
}
.breadcrumb {
display: flex;
align-items: center;
gap: 8px;
font-size: 12.5px;
color: var(--text-muted);
animation: breadcrumbIn var(--dur-normal) var(--ease-out);
}
/* 当前页在顶栏里当标题用:字号/字重/颜色都要跟「面包屑碎片」拉开,
否则整条顶栏就是一行灰字,页面没有入口感。 */
.breadcrumb .crumb-current {
font-size: 15px;
font-weight: 650;
color: var(--text-primary);
letter-spacing: -0.01em;
}
.breadcrumb .crumb-home,
.breadcrumb .crumb-sep {
font-size: 12.5px;
opacity: 0.7;
}
@keyframes breadcrumbIn {
from {
opacity: 0;
transform: translateX(-6px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
.crumb-home {
color: var(--accent);
font-weight: 600;
}
.crumb-sep {
color: var(--text-muted);
opacity: 0.6;
}
.crumb-current {
color: var(--text-primary);
font-weight: 500;
}
.theme-btn {
background: var(--glass-bg-strong);
border: 1px solid var(--glass-border);
backdrop-filter: blur(8px);
color: var(--text-secondary);
cursor: pointer;
padding: 4px 10px;
border-radius: var(--radius-pill);
font-size: 14px;
line-height: 1;
margin-right: 8px;
transition:
all var(--dur-micro) var(--ease-out),
transform var(--dur-micro) var(--ease-out);
}
.theme-btn:hover {
color: var(--accent);
border-color: rgba(var(--accent-rgb), 0.5);
box-shadow: var(--shadow-glow);
transform: scale(1.06);
}
#bg-layer {
position: fixed;
inset: 0;
z-index: -1;
pointer-events: none;
background-image: var(--bg-img, none);
background-size: cover;
background-position: center;
background-repeat: no-repeat;
transform: scale(1.04);
filter: blur(calc(var(--bg-blur, 0) * 1px));
transition: filter 0.25s var(--ease-out);
}
.palette-pop {
position: fixed;
bottom: 66px;
left: 16px;
width: 216px;
max-width: calc(100vw - 24px);
max-height: calc(100vh - 130px);
overflow-y: auto;
padding: 12px;
background: var(--glass-bg-strong);
backdrop-filter: blur(12px);
border: 1px solid var(--glass-border);
border-radius: var(--radius-md);
box-shadow: var(--shadow-lg);
display: none;
z-index: 300;
}
.palette-pop.on {
display: block;
animation: viewIn 0.16s var(--ease-out);
}
.palette-pop h4 {
font-size: 11px;
font-weight: 600;
color: var(--text-secondary);
margin: 2px 0 6px;
}
.palette-pop button.cdot {
width: 22px;
height: 22px;
border-radius: 50%;
border: 2px solid rgba(255, 255, 255, 0.18);
cursor: pointer;
margin: 3px;
padding: 0;
vertical-align: middle;
transition:
transform 0.15s,
box-shadow 0.15s;
}
.palette-pop button.cdot:hover {
transform: scale(1.25);
}
.palette-pop button.cdot.on {
border-color: var(--text-primary);
box-shadow: var(--shadow-glow);
transform: scale(1.15);
}
.palette-pop input[type="text"] {
background: var(--bg-input);
border: 1px solid var(--border-color);
border-radius: var(--radius-sm);
padding: 5px 8px;
color: var(--text-primary);
font-size: 11px;
width: 100%;
margin-bottom: 6px;
outline: none;
}
.palette-pop .pp-row {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 5px;
margin-top: 4px;
}
.palette-pop .pp-row .btn {
padding: 3px 9px;
font-size: 11px;
white-space: nowrap;
}
.palette-pop .pp-val {
display: inline-flex;
align-items: center;
gap: 4px;
font-size: 10px;
color: var(--text-muted);
min-width: 0;
text-align: right;
margin-left: auto;
}
.palette-pop input[type="range"] {
width: 72px;
margin: 0;
accent-color: var(--accent);
vertical-align: middle;
}
.container {
/* 1240px 是「卡片网格 + 图表」都能读的最宽值;再宽就变成一片空旷的留白。
居中而不是靠左铺满 —— 超宽屏上摊满整个屏幕是「后台模板」的典型观感。 */
padding: 22px 24px 56px;
max-width: 1240px;
margin: 0 auto;
width: 100%;
}
.card {
position: relative;
background: var(--bg-card);
backdrop-filter: blur(var(--glass-blur)) saturate(1.3);
-webkit-backdrop-filter: blur(var(--glass-blur)) saturate(1.3);
border: 1px solid var(--glass-border);
border-radius: var(--radius-lg);
padding: 22px;
margin-bottom: 18px;
box-shadow: var(--shadow-sm);
transition:
background 0.2s,
border 0.2s,
box-shadow var(--dur-normal) var(--ease-out);
}
/* hover 只提亮阴影与描边,**不再 translateY**:
整页十几张卡一起「上下浮」,视线扫过时像在抖,是廉价感最明显的一处。 */
.card:hover {
box-shadow: var(--shadow-md);
border-color: rgba(var(--accent-rgb), 0.22);
}
.card.tilt {
transform-style: preserve-3d;
transition:
box-shadow var(--dur-normal) var(--ease-out),
transform 0.15s ease-out;
}
.card.tilt .tilt-glow {
position: absolute;
inset: 0;
border-radius: inherit;
pointer-events: none;
opacity: 0;
transition: opacity 0.3s ease-out;
background: radial-gradient(
240px circle at var(--mx, 50%) var(--my, 50%),
rgba(var(--accent-rgb), 0.12),
transparent 70%
);
mix-blend-mode: normal;
}
.card.tilt:hover .tilt-glow {
opacity: 1;
}
@media (hover: none), (prefers-reduced-motion: reduce) {
.card.tilt {
transform: none !important;
}
.card.tilt .tilt-glow {
display: none;
}
}
.card h2 {
/* 标题左侧加一道 accent 短竖:卡片之间就有了同一套「章节起点」信号,
不必靠加粗/变大去抢,长页面翻起来也不迷路。 */
display: flex;
align-items: center;
gap: 9px;
font-size: 15.5px;
font-weight: 650;
margin-bottom: 14px;
color: var(--text-primary);
letter-spacing: -0.012em;
}
.card h2::before {
content: "";
width: 3px;
height: 14px;
border-radius: 2px;
background: linear-gradient(
180deg,
var(--sakura-300),
var(--frost-300)
);
flex-shrink: 0;
}
.card h3 {
font-size: 13px;
font-weight: 600;
color: var(--text-secondary);
margin: 16px 0 8px;
}
.grid-2 {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16px;
}
.grid-3 {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 16px;
}
.grid-4 {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 16px;
}
.stat-value {
font-size: 26px;
font-weight: 700;
color: var(--accent);
letter-spacing: -0.02em;
background: linear-gradient(
120deg,
var(--sakura-400),
var(--frost-300)
);
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
}
.stat-label {
font-size: 11px;
color: var(--text-muted);
margin-top: 2px;
letter-spacing: 0.02em;
}
.stat-card {
padding: 16px 20px;
}
.status-dot {
display: inline-block;
width: 8px;
height: 8px;
border-radius: 50%;
margin-right: 6px;
box-shadow: 0 0 6px currentColor;
}
.dot-green {
background: var(--success);
color: var(--success);
}
.dot-yellow {
background: var(--warning);
color: var(--warning);
}
.dot-red {
background: var(--error);
color: var(--error);
}
.dot-gray {
background: var(--text-muted);
color: var(--text-muted);
}
table {
width: 100%;
border-collapse: collapse;
font-size: 13px;
}
th {
text-align: left;
padding: 8px 10px;
color: var(--text-muted);
font-weight: 500;
border-bottom: 1px solid var(--border-color);
font-size: 11px;
text-transform: uppercase;
letter-spacing: 0.5px;
}
td {
padding: 8px 10px;
border-bottom: 1px solid var(--kv-border);
}
tr:hover td {
background: var(--bg-hover);
}
.badge {
display: inline-block;
padding: 2px 10px;
border-radius: var(--radius-pill);
font-size: 11px;
font-weight: 600;
letter-spacing: 0.02em;
}
.badge-green {
background: rgba(23, 169, 100, 0.16);
color: var(--success);
}
.badge-red {
background: rgba(219, 54, 148, 0.16);
color: var(--error);
}
.badge-yellow {
background: rgba(217, 154, 43, 0.16);
color: var(--warning);
}
.badge-blue {
background: var(--accent-bg);
color: var(--accent);
}
.badge-gray {
background: var(--glass-hover);
color: var(--text-secondary);
}
.btn {
/* 药丸形换成与卡片同一套圆角:满屏药丸按钮是「玩具感」的主要来源之一 */
padding: 7px 14px;
border-radius: var(--radius-md);
border: none;
font-size: 12.5px;
cursor: pointer;
font-weight: 600;
letter-spacing: 0.005em;
line-height: 1.35;
transition:
background var(--dur-micro) var(--ease-out),
color var(--dur-micro) var(--ease-out),
border-color var(--dur-micro) var(--ease-out),
box-shadow var(--dur-micro) var(--ease-out),
transform var(--dur-micro) var(--ease-out);
}
.btn:active {
transform: scale(0.96);
}
.btn-primary {
background: linear-gradient(
120deg,
var(--sakura-400),
var(--sakura-500)
);
color: #fff;
box-shadow: 0 2px 12px rgba(var(--accent-rgb), 0.35);
}
.btn-primary:hover {
filter: brightness(1.08);
box-shadow: 0 4px 18px rgba(var(--accent-rgb), 0.45);
}
.btn-danger {
background: var(--error);
color: #fff;
}
.btn-danger:hover {
filter: brightness(1.1);
}
.btn-sm {
padding: 4px 10px;
font-size: 11.5px;
}
.btn-ghost {
background: transparent;
border: 1px solid var(--btn-ghost-border);
color: var(--text-secondary);
}
.btn-ghost:hover {
background: var(--btn-ghost-hover-bg);
color: var(--text-primary);
border-color: rgba(var(--accent-rgb), 0.4);
}
.tab-content {
display: none;
}
.tab-content.active {
display: block;
animation: tabIn var(--dur-normal) var(--ease-out);
}
@keyframes tabIn {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
input,
textarea,
select {
background: var(--bg-input);
border: 1px solid var(--border-color);
border-radius: var(--radius-md);
padding: 9px 12px;
color: var(--text-primary);
font-size: 13px;
width: 100%;
margin-bottom: 10px;
outline: none;
transition:
border 0.15s,
background 0.2s,
color 0.2s,
box-shadow 0.15s;
}
input:focus,
textarea:focus,
select:focus {
border-color: var(--accent);
box-shadow: var(--shadow-glow);
background: var(--bg-input);
}
textarea {
resize: vertical;
min-height: 80px;
font-family: var(--font-mono);
font-size: 12px;
}
label {
display: block;
font-size: 11px;
color: var(--text-secondary);
margin-bottom: 3px;
font-weight: 500;
}
pre {
background: var(--pre-bg);
border-radius: var(--radius-sm);
padding: 12px;
font-size: 12px;
overflow-x: auto;
color: var(--pre-color);
font-family: var(--font-mono);
max-height: 400px;
overflow-y: auto;
}
code {
font-family: var(--font-mono);
font-size: 12px;
color: var(--pre-color);
}
.settings-layout {
display: flex;
flex-direction: column;
gap: 16px;
}
.settings-tabs {
display: flex;
flex-wrap: wrap;
gap: 4px;
background: var(--bg-card);
backdrop-filter: blur(var(--glass-blur));
-webkit-backdrop-filter: blur(var(--glass-blur));
border: 1px solid var(--glass-border);
border-radius: var(--radius-lg);
padding: 6px;
box-shadow: var(--shadow-sm);
}
.settings-tabs span {
display: inline-block;
padding: 7px 16px;
color: var(--text-secondary);
font-size: 13px;
cursor: pointer;
text-decoration: none;
border-radius: var(--radius-pill);
border: 1px solid transparent;
transition: all 0.15s;
}
.settings-tabs span:hover {
background: var(--bg-hover);
color: var(--text-primary);
}
.settings-tabs span.active {
background: var(--accent-bg);
color: var(--accent);
border-color: rgba(var(--accent-rgb), 0.35);
}
.settings-content {
flex: 1;
min-width: 0;
}
.settings-content > .card {
margin-bottom: 16px;
}
.settings-key {
font-family: var(--font-mono);
font-size: 11px;
color: var(--text-muted);
margin-bottom: 2px;
}
.reasoning {
border-left: 2px solid var(--frost-400);
padding-left: 12px;
margin: 8px 0;
font-size: 12px;
color: var(--text-muted);
}
.reasoning-title {
cursor: pointer;
font-size: 11px;
color: var(--text-muted);
font-weight: 600;
user-select: none;
margin-bottom: 4px;
}
.reasoning-body {
color: var(--text-muted);
line-height: 1.5;
}
.reasoning-body p {
margin: 4px 0;
}
.msg-content .text h1,
.msg-content .text h2,
.msg-content .text h3 {
font-size: 1em;
margin: 8px 0 4px;
color: var(--text-primary);
}
.msg-content .text p {
margin: 4px 0;
line-height: 1.5;
}
.msg-content .text ul,
.msg-content .text ol {
padding-left: 20px;
margin: 4px 0;
}
.msg-content .text li {
margin: 2px 0;
}
.msg-content .text code {
background: var(--pre-bg);
padding: 1px 4px;
border-radius: 3px;
font-size: 11px;
}
.msg-content .text pre {
background: var(--pre-bg);
border-radius: 6px;
padding: 10px;
margin: 8px 0;
overflow-x: auto;
font-size: 11px;
max-height: 300px;
}
.msg-content .text pre code {
background: none;
padding: 0;
}
.msg-content .text blockquote {
border-left: 3px solid var(--border-color);
padding-left: 10px;
margin: 8px 0;
color: var(--text-secondary);
}
.msg-content .text table {
border-collapse: collapse;
margin: 8px 0;
font-size: 12px;
width: 100%;
}
.msg-content .text th,
.msg-content .text td {
border: 1px solid var(--border-color);
padding: 4px 8px;
text-align: left;
}
.msg-content .text img {
max-width: 100%;
border-radius: 6px;
}
.toast {
position: fixed;
bottom: 20px;
right: 20px;
background: var(--toast-bg);
backdrop-filter: blur(14px);
-webkit-backdrop-filter: blur(14px);
color: var(--toast-color);
padding: 10px 22px;
border-radius: var(--radius-md);
font-size: 13px;
display: none;
z-index: 100;
border: 1px solid var(--glass-border);
box-shadow: var(--shadow-md);
animation: toastIn 0.3s var(--ease-out);
}
@keyframes toastIn {
from {
opacity: 0;
transform: translateY(12px) scale(0.96);
}
to {
opacity: 1;
transform: translateY(0) scale(1);
}
}
.toast.error {
background: var(--toast-error-bg);
color: var(--toast-error-color);
}
.toast.success {
background: var(--toast-bg);
color: var(--toast-color);
border-color: rgba(23, 169, 100, 0.35);
}
.toast.warn {
background: rgba(217, 154, 43, 0.16);
color: var(--warning);
border-color: rgba(217, 154, 43, 0.35);
}
.confirm-overlay {
position: fixed;
inset: 0;
display: none;
align-items: center;
justify-content: center;
background: rgba(8, 10, 20, 0.55);
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
z-index: 200;
animation: fadeIn 0.2s ease;
}
.confirm-box {
background: var(--glass-bg-strong);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
border: 1px solid var(--glass-border);
border-radius: var(--radius-lg);
padding: 24px;
max-width: 380px;
width: 90%;
box-shadow: var(--shadow-lg);
animation: tabIn 0.25s var(--ease-out);
}
.confirm-box h3 {
font-size: 15px;
font-weight: 600;
margin-bottom: 10px;
color: var(--text-primary);
}
.confirm-box p {
font-size: 13px;
color: var(--text-secondary);
line-height: 1.55;
margin-bottom: 18px;
word-break: break-all;
}
.confirm-actions {
display: flex;
justify-content: flex-end;
gap: 8px;
}
/* 首启人格向导(复用 confirm-* 弹窗)*/
.persona-ta {
width: 100%;
display: none;
margin-bottom: 12px;
font: 12px/1.5 var(--font-mono, monospace);
padding: 8px;
border-radius: var(--radius-md, 8px);
border: 1px solid var(--glass-border);
background: var(--glass-bg);
color: var(--text-primary);
box-sizing: border-box;
}
.persona-warn {
display: none;
font-size: 12px;
line-height: 1.5;
color: var(--text-secondary);
margin-bottom: 10px;
}
.persona-actions {
display: flex;
justify-content: flex-end;
gap: 8px;
flex-wrap: wrap;
}
.empty-state {
text-align: center;
padding: 48px 24px;
color: var(--text-muted);
background: var(--glass-bg);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
border: 1px dashed var(--glass-border);
border-radius: var(--radius-lg);
margin: 8px 0;
}
.empty-state p {
font-size: 14px;
margin-bottom: 8px;
color: var(--text-secondary);
}
.empty-state .icon {
font-size: 40px;
margin-bottom: 12px;
opacity: 0.6;
filter: drop-shadow(0 4px 10px rgba(var(--accent-rgb), 0.3));
}
.chat-layout {
display: flex;
flex-direction: column;
gap: 16px;
height: calc(100vh - 112px);
min-height: 60vh;
}
.chat-tabs {
/* 分段控件(segmented control)而不是一排药丸 chip:
四个页签是同一组互斥选项,「一个容器 + 一个高亮块」比「四个各自带边框的胶囊」
更准确地表达了这层关系,也少四道描边噪声。 */
display: inline-flex;
gap: 3px;
padding: 3px;
flex-wrap: wrap;
align-self: flex-start;
border-radius: var(--radius-md);
background: var(--bg-input);
border: 1px solid var(--border-color);
}
.chat-tabs span {
padding: 6px 14px;
font-size: 12.5px;
font-weight: 500;
cursor: pointer;
color: var(--text-muted);
border-radius: 7px;
border: none;
transition:
background var(--dur-micro) var(--ease-out),
color var(--dur-micro) var(--ease-out);
}
.chat-tabs span:hover {
color: var(--text-primary);
background: var(--bg-hover);
}
.chat-tabs span.active {
color: var(--accent);
background: var(--accent-bg);
font-weight: 600;
box-shadow: inset 0 0 0 1px rgba(var(--accent-rgb), 0.28);
}
.chat-panel {
display: none;
flex: 1;
min-height: 0;
flex-direction: column;
}
.chat-panel.active {
display: flex;
}
.chat-main {
flex: 1;
min-width: 0;
min-height: 0;
display: flex;
flex-direction: column;
}
.chat-main .card {
flex: 1;
display: flex;
flex-direction: column;
margin-bottom: 0;
min-height: 0;
background: transparent;
border: none;
box-shadow: none;
backdrop-filter: none;
padding: 0;
}
.chat-main .card h2 {
flex-shrink: 0;
}
.chat-messages {
flex: 1;
overflow-y: auto;
padding: 20px 20px 28px;
margin-bottom: 0;
display: flex;
flex-direction: column;
/* 消息间距从 6px 拉到 14px:原来每条贴在一起,一屏十几条像一堵墙,
分不清哪句是哪句。 */
gap: 14px;
min-height: 0;
}
.msg {
display: flex;
gap: 10px;
margin-bottom: 0;
align-items: flex-start;
max-width: 100%;
}
.msg-user {
flex-direction: row-reverse;
align-self: flex-end;
}
.msg-assistant {
align-self: flex-start;
}
.msg-system {
align-self: center;
max-width: 90%;
}
.msg-avatar {
width: 30px;
height: 30px;
border-radius: 50%;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
flex-shrink: 0;
/* 细描边把头像从同色背景里“拓”出来,否则暗色下就是一团 */
box-shadow: 0 0 0 1px var(--glass-border);
}
.msg-avatar img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
/* mascot.webp 是**头部方裁**(脸居中),cover 已能把脸填满圆形。
原先 scale(1.5) 是给「全身立绘裁圆」补的——现在图本身换了,
再放大 1.5 会切掉两侧发绳与呆毛,所以降到 1.25 只做轻微推近。 */
.msg-assistant .msg-avatar img {
transform: scale(1.25);
}
.msg-avatar svg {
display: block;
width: 16px;
height: 16px;
}
.msg-user .msg-avatar {
background: var(--msg-user-bg);
color: var(--msg-user-color);
}
.msg-assistant .msg-avatar {
background: transparent;
}
.msg-system .msg-avatar {
background: var(--msg-system-bg);
color: var(--msg-system-color);
}
.msg-content {
min-width: 0;
flex: 1 1 auto;
/* 行长上限:整屏宽的一行文字没人读得下去(~78ch 是舒适区),
也让「同一侧连续多条」自然堆成一列而不是横铺开。 */
max-width: min(78ch, 86%);
}
.msg-content .msg-bubble + .msg-bubble {
margin-top: 6px;
}
.msg-channel {
align-self: flex-start;
}
.msg-channel .msg-avatar.chan-avatar {
color: #ffffff;
font-weight: 700;
font-size: 13px;
text-transform: uppercase;
}
.msg-channel .msg-chan-name {
font-size: 10px;
color: var(--text-muted);
opacity: 0.8;
margin-bottom: 2px;
padding-left: 4px;
letter-spacing: 0.5px;
}
.msg-channel .msg-bubble {
background: var(--msg-bubble-bg);
color: var(--msg-bubble-color);
border-bottom-left-radius: 4px;
border-color: var(--msg-bubble-border);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.28);
}
.msg-bubble {
padding: 10px 14px;
border-radius: 12px;
font-size: 14.5px;
line-height: 1.65;
word-break: break-word;
position: relative;
border: 1px solid transparent;
transition: box-shadow var(--dur-normal) var(--ease-out);
}
.msg-user .msg-bubble {
/* 用户与助手必须**看得出不一样**:原来两侧同底色、只差一个圆角,
整段对话读下来分不清谁说的。用户侧用 accent 渲染,助手侧保持中性面。 */
background: var(--msg-user-bg);
color: var(--text-primary);
border-bottom-right-radius: 5px;
border-color: rgba(var(--accent-rgb), 0.26);
}
.msg-assistant .msg-bubble {
background: var(--msg-bubble-bg);
color: var(--msg-bubble-color);
border-bottom-left-radius: 5px;
border-color: var(--msg-bubble-border);
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
}
.msg-system .msg-bubble {
background: var(--msg-system-bg);
color: var(--msg-system-color);
text-align: center;
font-size: 12px;
border-color: var(--msg-bubble-border);
}
.msg-bubble .text {
white-space: pre-wrap;
}
.msg-bubble .text p {
margin: 4px 0;
}
.msg-bubble .text h1,
.msg-bubble .text h2,
.msg-bubble .text h3 {
font-size: 1em;
margin: 8px 0 4px;
}
.msg-bubble .text ul,
.msg-bubble .text ol {
padding-left: 20px;
margin: 4px 0;
}
.msg-bubble .text pre {
background: var(--pre-bg);
color: var(--pre-color);
border-radius: 6px;
padding: 8px;
margin: 4px 0;
overflow-x: auto;
font-size: 11.5px;
line-height: 1.5;
border: 1px solid var(--kv-border);
max-height: 200px;
}
.msg-bubble .text code {
background: var(--pre-bg);
color: var(--pre-color);
padding: 1px 4px;
border-radius: 3px;
font-size: 11.5px;
}
.msg-bubble .text pre code {
background: none;
padding: 0;
}
.msg-bubble .text blockquote {
border-left: 3px solid var(--border-color);
padding-left: 8px;
margin: 4px 0;
opacity: 0.8;
}
.msg-bubble .text table {
border-collapse: collapse;
margin: 4px 0;
font-size: 12px;
width: 100%;
}
.msg-bubble .text th,
.msg-bubble .text td {
border: 1px solid var(--border-color);
padding: 3px 6px;
text-align: left;
}
.msg-bubble .text img {
max-width: 100%;
border-radius: 6px;
}
/* ===== 思考卡片(PiDeck 风格)===== */
.reasoning-card {
margin: 6px 0;
border: 1px dashed var(--kv-border);
border-radius: var(--radius-sm);
background: var(--bg-panel, var(--bg-hover));
overflow: hidden;
}
.reasoning-card .reasoning-head {
display: flex;
align-items: center;
gap: 7px;
padding: 6px 10px;
font-size: 11px;
cursor: pointer;
user-select: none;
color: var(--text-secondary);
}
.reasoning-card .reasoning-head:hover {
background: var(--bg-hover);
}
.reasoning-card .rc-ico {
color: var(--accent);
flex-shrink: 0;
}
.reasoning-card .rc-title {
font-weight: 600;
color: var(--text-primary);
}
.reasoning-card .rc-chev {
margin-left: auto;
display: inline-flex;
align-items: center;
color: var(--text-muted);
transition: transform 0.2s var(--ease-out);
}
.reasoning-card.open .rc-chev {
transform: rotate(180deg);
}
.reasoning-card .reasoning-body {
padding: 4px 10px 8px;
}
.reasoning-card .reasoning-content {
line-height: 1.5;
color: var(--text-tertiary);
font-size: 11.5px;
}
.reasoning-card .reasoning-content p {
margin: 4px 0;
}
.reasoning-card .reasoning-sweep {
height: 2px;
border-radius: 2px;
margin-top: 6px;
background: linear-gradient(
90deg,
transparent,
var(--accent),
transparent
);
background-size: 200% 100%;
animation: reasoningSweep 1.2s var(--ease-out) infinite;
opacity: 0.6;
}
@keyframes reasoningSweep {
from {
background-position: 200% 0;
}
to {
background-position: -200% 0;
}
}
/* ===== 工具卡片(PiDeck 风格,扁平不套气泡)===== */
.tool-card {
background: var(--bg-panel, var(--bg-hover));
color: var(--text-secondary);
border-radius: var(--radius-sm);
padding: 7px 10px;
margin: 6px 0;
font-size: 11px;
border: 1px solid var(--kv-border);
border-left: 3px solid var(--accent);
cursor: pointer;
transition:
border-color 0.2s var(--ease-out),
background 0.2s var(--ease-out);
}
.tool-card.tc-running {
border-left-color: var(--accent);
}
.tool-card.tc-done {
border-left-color: rgba(23, 169, 100, 0.8);
}
.tool-card.tc-error {
border-left-color: var(--error, #db3694);
}
.tool-card .tc-line {
display: flex;
align-items: center;
gap: 6px;
font-size: 11px;
user-select: none;
}
.tool-card .tc-ico {
display: inline-flex;
flex-shrink: 0;
color: var(--accent);
}
.tool-card.tc-error .tc-ico {
color: var(--error, #db3694);
}
.tool-card.tc-done .tc-ico {
color: rgba(23, 169, 100, 0.9);
}
.tool-card .tc-name {
font-weight: 600;
color: var(--text-primary);
}
.tool-card .tc-plugin {
font-size: 9.5px;
color: var(--text-muted);
background: var(--pre-bg);
border-radius: 4px;
padding: 0 4px;
line-height: 1.6;
flex-shrink: 0;
}
.tool-card .tc-state {
margin-left: auto;
font-size: 9.5px;
font-weight: 500;
padding: 1px 6px;
border-radius: 999px;
flex-shrink: 0;
display: inline-flex;
align-items: center;
gap: 4px;
}
.tool-card .tc-run {
background: rgba(63, 110, 245, 0.18);
color: #a3b8ff;
}
.tool-card .tc-done {
background: rgba(23, 169, 100, 0.18);
color: #6ee7a8;
}
.tool-card .tc-deny {
background: rgba(219, 54, 148, 0.18);
color: #ff9ec6;
}
.tool-card .tc-spinner {
width: 11px;
height: 11px;
border-radius: 50%;
border: 2px solid rgba(63, 110, 245, 0.25);
border-top-color: #3f6ef5;
animation: spin 0.7s linear infinite;
display: inline-block;
}
.tool-card .tc-caret {
display: inline-flex;
align-items: center;
color: var(--text-muted);
transition: transform 0.15s var(--ease-out);
}
.tool-card.open .tc-caret {
transform: rotate(180deg);
}
.tool-card .tc-detail {
margin-top: 6px;
}
.tool-card .tc-detail-label {
font-size: 9.5px;
font-weight: 600;
color: var(--text-muted);
text-transform: uppercase;
letter-spacing: 0.03em;
margin-bottom: 2px;
}
.tool-card .tc-args,
.tool-card .tc-result {
font-family: var(--font-mono);
font-size: 11px;
opacity: 0.85;
white-space: pre-wrap;
word-break: break-all;
margin-top: 5px;
background: var(--pre-bg);
color: var(--pre-color);
border-radius: 4px;
padding: 5px 7px;
}
.tool-card .tc-result {
opacity: 0.72;
max-height: 80px;
overflow-y: auto;
}
[data-theme="light"] .tool-card .tc-run {
color: #3f6ef5;
}
[data-theme="light"] .tool-card .tc-done {
color: #128a52;
}
[data-theme="light"] .tool-card .tc-deny {
color: #c2185b;
}
.tool-card.tool-drip-in {
animation: toolDripIn 0.32s var(--ease-out) both;
}
@keyframes toolDripIn {
0% {
opacity: 0;
transform: translateY(-18px) scale(0.96);
}
70% {
opacity: 1;
transform: translateY(2px) scale(1.005);
}
100% {
opacity: 1;
transform: translateY(0) scale(1);
}
}
.msg-bubble .thinking-tools {
display: flex;
gap: 6px;
align-items: center;
flex-wrap: wrap;
}
.msg-bubble .thinking-tool {
display: inline-flex;
align-items: center;
gap: 5px;
font-size: 11px;
color: var(--text-secondary);
background: var(--pre-bg);
border-radius: 999px;
padding: 3px 10px;
}
.msg-bubble .thinking-tool.pill-in {
animation: pillIn 0.25s var(--ease-out) both;
}
@keyframes pillIn {
from {
opacity: 0;
transform: translateX(8px);
}
to {
opacity: 1;
transform: none;
}
}
.msg-bubble .live-spinner {
width: 15px;
height: 15px;
border-radius: 50%;
border: 2px solid var(--msg-user-bg);
border-top-color: var(--accent);
animation: spin 0.7s linear infinite;
flex-shrink: 0;
display: inline-block;
vertical-align: middle;
}
.msg-bubble .live-spinner + .thinking-tools,
.msg-bubble .live-spinner + .text {
margin-left: 12px;
}
.msg-bubble.grow-in {
animation: bubbleGrow 0.5s var(--ease-out) both;
}
.msg-bubble.grow-in .text {
animation: textFadeIn 0.35s ease 0.125s both;
}
@keyframes bubbleGrow {
from {
max-height: 28px;
opacity: 0.4;
}
to {
max-height: 3000px;
opacity: 1;
}
}
@keyframes textFadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
.tool-call.tool-drip-in {
animation: toolDripIn 0.3s var(--ease-out) both;
}
@keyframes toolDripIn {
from {
opacity: 0;
transform: translateY(-14px) scale(0.98);
}
to {
opacity: 1;
transform: none;
}
}
/* 实时终端画面:模拟 agent 操控的终端屏幕 */
.term-screen {
background: #0f1115;
border: 1px solid #2a2e35;
border-radius: 8px;
margin: 6px 0 4px 0;
overflow: hidden;
font-size: 11px;
line-height: 1.45;
}
.term-screen .term-head {
display: flex;
align-items: center;
gap: 6px;
padding: 4px 8px;
background: #1a1d23;
color: #9aa0a8;
font-size: 10px;
border-bottom: 1px solid #2a2e35;
font-family: var(--font-mono);
}
.term-screen .term-head .term-dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: #3fb950;
flex-shrink: 0;
}
.term-screen .term-head .term-dot.stopped {
background: #d29922;
}
.term-screen pre.term-buf {
margin: 0;
padding: 6px 8px;
background: transparent;
color: #d8dee9;
font-family: var(--font-mono);
font-size: 11px;
white-space: pre-wrap;
word-break: break-all;
max-height: 260px;
overflow-y: auto;
scrollbar-width: thin;
}
.term-screen pre.term-buf::-webkit-scrollbar {
width: 6px;
}
.term-screen pre.term-buf::-webkit-scrollbar-thumb {
background: #33373d;
border-radius: 3px;
}
.chat-input-row {
display: flex;
gap: 8px;
flex-shrink: 0;
padding-top: 14px;
margin-top: 4px;
/* 输入区与消息流之间加一道分隔:否则最后一条消息与输入框糊在一起,
看不出“消息到头了”。 */
border-top: 1px solid var(--border-color);
}
.chat-input-row input {
flex: 1;
margin-bottom: 0;
}
.chat-input-row button {
flex-shrink: 0;
margin-bottom: 0;
}
.loading {
display: inline-block;
width: 16px;
height: 16px;
border: 2px solid var(--loading-border);
border-radius: 50%;
border-top-color: var(--loading-top);
animation: spin 0.6s linear infinite;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
.monaco-like {
font-family: var(--font-mono);
font-size: 12px;
background: var(--bg-input);
border: 1px solid var(--border-color);
border-radius: 6px;
}
.kv-row {
display: grid;
grid-template-columns: minmax(110px, 180px) 1fr;
gap: 12px;
align-items: baseline;
padding: 8px;
/* 负外边距让 hover 高亮能满宽,而不把内容挤窄 */
margin: 0 -8px;
border-bottom: 1px solid var(--kv-border);
font-size: 13px;
border-radius: var(--radius-sm);
transition: background var(--dur-micro) var(--ease-out);
}
.kv-row:hover {
background: var(--glass-hover);
}
.kv-row:last-child {
border-bottom: none;
}
.kv-row .key {
color: var(--text-muted);
/* 键列不再硬编码 180px 宽度:窄屏/长键名时能自己收,避免把值挤成竖排 */
width: auto;
flex-shrink: 0;
font-size: 12.5px;
}
.kv-row .val {
color: var(--text-primary);
word-break: break-all;
/* 数字等宽:端口/计数/内存这类值上下对齐才好扫 */
font-variant-numeric: tabular-nums;
min-width: 0;
}
.tool-badge {
display: inline-block;
padding: 1px 6px;
border-radius: 3px;
font-size: 10px;
background: var(--accent-bg);
color: var(--accent);
margin: 1px;
}
.check-pass {
color: var(--success);
}
.check-fail {
color: var(--error);
}
.check-skip {
color: var(--text-secondary);
}
.memory-graph {
width: 100%;
height: 300px;
background: var(--bg-input);
border-radius: var(--radius-md);
border: 1px solid var(--border-color);
position: relative;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
color: var(--text-muted);
font-size: 13px;
}
.health-panel {
display: grid;
gap: 8px;
}
.health-item {
display: flex;
align-items: center;
gap: 10px;
padding: 8px 12px;
background: var(--bg-input);
border-radius: var(--radius-sm);
font-size: 13px;
border: 1px solid transparent;
transition:
border var(--dur-micro),
background var(--dur-micro);
}
.health-item:hover {
border-color: rgba(var(--accent-rgb), 0.25);
}
.health-item .check-name {
flex: 1;
}
.health-item .check-status {
font-size: 11px;
font-weight: 500;
}
.fade-in {
animation: fadeIn 0.2s ease;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(4px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
#sm-container-chat {
height: 260px;
background: var(--bg-input);
border-radius: var(--radius-sm);
border: 1px solid var(--border-color);
overflow: hidden;
position: relative;
}
#sm-container-chat canvas {
display: block;
}
/* 星图容器:独立页签 / 总览页 / 聊天面板三处共用同一套样式。
position:relative 是必需的 —— hover 角标(#sm-label)绝对定位在容器上。*/
#sm-container-page,
#sm-container-home {
background: var(--bg-input);
border-radius: var(--radius-sm);
border: 1px solid var(--border-color);
overflow: hidden;
position: relative;
width: 100%;
}
#sm-container-page canvas,
#sm-container-home canvas {
display: block;
}
/* 节点名覆盖层:盖在 WebGL canvas 上,不吃鼠标事件(否则星图不能拖拽)。*/
#sm-labels {
pointer-events: none;
z-index: 4;
}
/* 选中详情面板:点击节点后的信息反馈。
★ 补它的原因:`starmapSelected` 此前只被赋值、无人读取,
点击节点在界面上没有任何信息反馈——“可读性差”的核心成因之一。*/
#sm-detail {
position: absolute;
right: 10px;
top: 10px;
display: none;
width: 232px;
max-height: 62%;
overflow-y: auto;
padding: 10px 12px;
font-size: 12px;
line-height: 1.5;
color: var(--text-primary);
background: rgba(10, 10, 20, 0.88);
border: 1px solid var(--border-color);
border-radius: var(--radius-sm);
z-index: 6;
/* 面板本身要能滚动,但不该把滚轮事件漏给下面的星图。*/
pointer-events: auto;
}
#sm-detail .sm-detail-title {
font-weight: 600;
margin-bottom: 2px;
word-break: break-all;
}
#sm-detail .sm-detail-meta,
#sm-detail .sm-detail-id {
color: var(--text-muted);
font-size: 11px;
margin-bottom: 4px;
word-break: break-all;
}
#sm-detail .sm-detail-rel {
margin-top: 6px;
padding-top: 6px;
border-top: 1px solid var(--border-color);
color: var(--text-secondary);
font-size: 11px;
}
#sm-detail .sm-detail-relrow {
margin-bottom: 2px;
word-break: break-all;
}
#sm-detail .sm-detail-arrow {
color: var(--accent, #7fb5ad);
margin-right: 3px;
}
#sm-detail .sm-detail-nb {
color: var(--text-primary);
}
/* hover 角标:取代原先每节点一张 256x64 CanvasTexture
(1151 张贴图 ≈ 72MB 显存)。DOM 文字更清晰且零 GPU 开销。*/
#sm-label {
position: absolute;
left: 10px;
bottom: 10px;
display: none;
max-width: 60%;
padding: 4px 10px;
font-size: 12px;
line-height: 1.4;
color: var(--text-primary);
background: rgba(10, 10, 20, 0.82);
border: 1px solid var(--border-color);
border-radius: var(--radius-sm);
pointer-events: none;
z-index: 5;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.toggle-row {
margin-top: 8px;
display: flex;
align-items: center;
gap: 12px;
}
.toggle-row .label-text {
color: var(--text-muted);
font-size: 12px;
}
.toggle-switch {
position: relative;
width: 36px;
height: 20px;
cursor: pointer;
flex-shrink: 0;
}
.toggle-track {
position: absolute;
inset: 0;
background: var(--bg-input);
border-radius: 10px;
transition: all 0.3s;
border: 1px solid var(--border-color);
}
.toggle-track.on {
background: var(--accent-bg);
border-color: var(--accent);
box-shadow: var(--shadow-glow);
}
.toggle-knob {
position: absolute;
width: 16px;
height: 16px;
left: 2px;
top: 2px;
background: var(--text-muted);
border-radius: 50%;
transition: all 0.3s;
}
.toggle-knob.on {
left: 18px;
background: var(--accent);
}
.toggle-btn {
display: flex;
align-items: center;
gap: 4px;
padding: 2px 8px;
border-radius: var(--radius-sm);
border: 1px solid var(--border-color);
background: transparent;
color: var(--text-muted);
font-size: 12px;
font-family: inherit;
cursor: pointer;
transition: all 0.2s;
}
.toggle-btn:hover {
background: var(--accent-bg);
color: var(--text-primary);
}
.toggle-btn.on {
background: var(--accent-bg);
color: var(--accent);
border-color: var(--accent);
}
.label-text {
color: var(--text-muted);
font-size: 12px;
}
.loading-spinner {
width: 32px;
height: 32px;
border: 3px solid var(--loading-border);
border-top: 3px solid var(--accent);
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
.sidebar-subnav {
display: flex;
gap: 0;
border-bottom: 1px solid var(--border-color);
margin-bottom: 10px;
}
.sidebar-subnav span {
padding: 6px 12px;
font-size: 12px;
cursor: pointer;
color: var(--text-muted);
border-bottom: 2px solid transparent;
transition: all 0.15s;
}
.sidebar-subnav span:hover {
color: var(--text-primary);
}
.sidebar-subnav span.active {
color: var(--accent);
border-bottom-color: var(--accent);
}
@media (max-width: 900px) {
.chat-layout {
height: auto;
}
.msg {
max-width: 100%;
}
}
@media (max-width: 768px) {
nav {
width: 14rem;
box-shadow: var(--shadow-lg);
}
.main-wrap {
margin-left: 0;
}
nav h1 {
font-size: 15px;
}
.topbar {
padding: 0 12px;
}
.breadcrumb .crumb-home {
display: none;
}
.container {
padding: 12px;
}
.card {
padding: 12px;
}
.grid-2,
.grid-3,
.grid-4 {
grid-template-columns: 1fr;
}
.stat-value {
font-size: 20px;
}
.settings-layout {
flex-direction: column;
}
.settings-tabs {
max-height: 200px;
overflow-y: auto;
}
.kv-row {
flex-direction: column;
gap: 2px;
}
.kv-row .key {
width: auto;
}
.msg-avatar {
width: 24px;
height: 24px;
font-size: 11px;
}
.health-item {
flex-wrap: wrap;
gap: 4px;
}
.health-item .check-name {
flex: auto;
width: 100%;
}
}
/* ===== 8.7 Accessibility ===== */
:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
border-radius: var(--radius-sm);
}
::selection {
background: rgba(var(--accent-rgb), 0.35);
}
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}
/* ===== Themed scrollbar ===== */
::-webkit-scrollbar {
width: 10px;
height: 10px;
}
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-thumb {
background: rgba(var(--accent-rgb), 0.25);
border-radius: var(--radius-pill);
border: 2px solid transparent;
background-clip: content-box;
}
::-webkit-scrollbar-thumb:hover {
background: rgba(var(--accent-rgb), 0.45);
background-clip: content-box;
border: 2px solid transparent;
}
* {
scrollbar-width: thin;
scrollbar-color: rgba(var(--accent-rgb), 0.35) transparent;
}
/* ===== 运行态面板(图形化总览)=====
目标是"一眼看懂内核现在在干什么":排队/中断/中断栈/驻留子四个数字,
四级中断队列的条形,中断栈的层叠图,以及通道拓扑。全部用纯 CSS + 内联 SVG,
不引入任何依赖(前端刻意没有构建链)。 */
.rt-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(132px, 1fr));
gap: 10px;
margin: 4px 0 14px;
}
.rt-tile {
position: relative;
padding: 12px 14px 10px;
border-radius: var(--radius-md);
background: var(--bg-input);
border: 1px solid var(--border-color);
overflow: hidden;
}
.rt-tile .rt-num {
font-size: 28px;
font-weight: 700;
line-height: 1.1;
letter-spacing: -0.5px;
}
.rt-tile .rt-label {
font-size: 11px;
color: var(--text-muted);
margin-top: 2px;
}
.rt-tile .rt-sub {
font-size: 10px;
color: var(--text-muted);
opacity: 0.85;
}
.rt-tile.rt-active {
border-color: var(--sakura-400);
box-shadow: var(--shadow-glow);
}
.rt-tile.rt-warn .rt-num {
color: #ffb86b;
}
/* 底部细条:把"占了多少"用长度表达,比纯数字更快读懂 */
.rt-bar {
height: 4px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.08);
margin-top: 8px;
overflow: hidden;
}
.rt-bar > i {
display: block;
height: 100%;
background: linear-gradient(90deg, var(--sakura-400), var(--frost-300));
transition: width 0.35s var(--ease-out);
}
/* ===== 队列:五个等大表框 =====
此前是五行「名字 | 进度条 | 元数据」的扁条:四级中断全为 0 时,
四行几乎全是空白,既占高度又难看。改成五个等大的框(与 KPI、阶段管道
同一套视觉语言),深度用大字报出来,下面是可见的格槽。 */
.rt-queues {
display: grid;
/* 100px 而不是 150px:两块都要保证「5 个一行」。
否则容器 540px(小窗口侧栏展开时就是这个宽度)会排成「4 个 + 1 个」——
第二行四个空位,看着就是一块坑。宁可框窄一点,也不留孤行。 */
grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
gap: 10px;
margin-bottom: 8px;
}
.rt-qcell {
display: flex;
flex-direction: column;
gap: 7px;
padding: 11px 12px 10px;
border-radius: var(--radius-md);
background: var(--bg-input);
border: 1px solid var(--border-color);
min-width: 0;
transition:
border-color 0.25s var(--ease-out),
box-shadow 0.25s var(--ease-out);
}
.rt-qhead {
display: flex;
align-items: baseline;
gap: 7px;
min-width: 0;
}
.rt-qhead > b {
font-size: 16px;
font-weight: 800;
line-height: 1;
letter-spacing: -0.02em;
}
.rt-qhead > span {
font-size: 10.5px;
color: var(--text-muted);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.rt-qnum {
font-size: 26px;
font-weight: 800;
line-height: 1;
letter-spacing: -0.02em;
font-variant-numeric: tabular-nums;
}
.rt-qmeta {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 4px 8px;
font-size: 10.5px;
color: var(--text-muted);
font-variant-numeric: tabular-nums;
}
/* 级别配色:框头与格槽点亮共用同一套色 */
.rt-qcell.rt-lv-4 .rt-qhead > b { color: #ff5c7a; }
.rt-qcell.rt-lv-3 .rt-qhead > b { color: #ffa657; }
.rt-qcell.rt-lv-2 .rt-qhead > b { color: var(--sakura-400); }
.rt-qcell.rt-lv-1 .rt-qhead > b { color: var(--frost-300); }
.rt-qcell.rt-lv-q .rt-qhead > b { color: #a3be8c; }
/* 有积压时整个框描边点亮:一眼看出哪条队列在堵 */
.rt-qcell.rt-lv-4.rt-active { border-color: rgba(255, 92, 122, 0.55); box-shadow: 0 0 0 1px rgba(255, 92, 122, 0.18); }
.rt-qcell.rt-lv-3.rt-active { border-color: rgba(255, 166, 87, 0.55); box-shadow: 0 0 0 1px rgba(255, 166, 87, 0.18); }
.rt-qcell.rt-lv-2.rt-active { border-color: rgba(var(--accent-rgb), 0.55); box-shadow: 0 0 0 1px rgba(var(--accent-rgb), 0.18); }
.rt-qcell.rt-lv-1.rt-active { border-color: rgba(136, 192, 208, 0.55); box-shadow: 0 0 0 1px rgba(136, 192, 208, 0.18); }
.rt-qcell.rt-lv-q.rt-active { border-color: rgba(163, 190, 140, 0.55); box-shadow: 0 0 0 1px rgba(163, 190, 140, 0.18); }
/* 排队队列无级别:用虚线框与四级中断区分开 */
.rt-qcell.rt-qcell-queued {
border-style: dashed;
}
/* 队列格槽:固定可见的「车位」,占用多少一眼可数。
此前是宽度百分比进度条 —— 队列为 0 时宽度为 0,整行只剩文字,
看上去就是「这块空着」。 */
.rt-slots {
display: flex;
align-items: stretch;
gap: 3px;
height: 16px;
min-width: 0;
}
.rt-slots > i {
flex: 1 1 0;
min-width: 3px;
border-radius: 3px;
background: rgba(255, 255, 255, 0.06);
border: 1px solid rgba(255, 255, 255, 0.05);
transition:
background 0.25s var(--ease-out),
box-shadow 0.25s var(--ease-out);
}
.rt-slots.rt-lv-4 > i.on { background: #ff5c7a; box-shadow: 0 0 6px rgba(255, 92, 122, 0.45); }
.rt-slots.rt-lv-3 > i.on { background: #ffa657; box-shadow: 0 0 6px rgba(255, 166, 87, 0.4); }
.rt-slots.rt-lv-2 > i.on { background: var(--sakura-400); box-shadow: 0 0 6px rgba(var(--accent-rgb), 0.4); }
.rt-slots.rt-lv-1 > i.on { background: var(--frost-300); box-shadow: 0 0 6px rgba(136, 192, 208, 0.4); }
.rt-slots.rt-lv-q > i.on { background: #a3be8c; box-shadow: 0 0 6px rgba(163, 190, 140, 0.4); }
.rt-slots-more {
flex: 0 0 auto;
align-self: center;
margin-left: 4px;
font-size: 10px;
font-weight: 600;
color: var(--text-muted);
font-variant-numeric: tabular-nums;
}
/* 拓扑图:整张图为 SVG,文字降到最少,其余信息用图形编码
(归属=容器/配色、容量=细条、输出能力=圆点、路由=连线)。 */
.rt-svg-wrap {
overflow-x: auto;
padding: 4px 0 8px;
}
.rt-svg {
min-width: 560px;
font-family: inherit;
display: block;
}
.rt-svg text {
fill: var(--text-primary);
}
/* 拓扑图的文字全部走类(不用 font-size/fill 属性):
1) 字号统一可读(原来 9–11px 且低对比度,实机很难看清);
2) 颜色交给主题变量,切主题/配色时不用重算 SVG。
长名截断由 JS 的 rtClip 负责(SVG 没有 text-overflow)。 */
.rt-topo-svg .tp-name { font-size: 12px; fill: var(--text-primary); }
.rt-topo-svg .tp-sub { font-size: 10.5px; fill: var(--text-muted); font-variant-numeric: tabular-nums; }
.rt-topo-svg .tp-val { font-size: 12.5px; font-weight: 700; fill: var(--text-primary); }
.rt-topo-svg .tp-lab { font-size: 11px; font-weight: 600; }
.rt-topo-svg .tp-warn { font-size: 10.5px; fill: #ffb86b; }
.rt-topo-svg .tp-hint { font-size: 10.5px; fill: var(--text-muted); }
.rt-topo-svg .tp-edge { fill: none; }
.rt-topo-svg .tp-edge-in { stroke-opacity: 0.45; stroke-width: 1.4; }
.rt-topo-svg .tp-edge-out { stroke-opacity: 0.35; stroke-width: 1.2; }
/* 排队队列无级别:虚线框已在 .rt-qcell.rt-qcell-queued 处理 */
/* 中断栈:栈顶在上,做出"层叠现场"的观感 */
.rt-stack {
display: flex;
flex-direction: column-reverse;
gap: 4px;
margin: 2px 0 6px;
}
.rt-frame {
display: flex;
align-items: center;
gap: 8px;
padding: 5px 9px;
border-radius: 8px;
font-size: 11px;
background: rgba(255, 255, 255, 0.05);
border-left: 3px solid var(--sakura-400);
}
.rt-frame .rt-frame-top {
margin-left: auto;
font-size: 10px;
color: var(--text-muted);
}
.rt-empty {
font-size: 11px;
color: var(--text-muted);
padding: 4px 0;
}
/* 通道拓扑:输入 → 内核 → 输出 */
.rt-topo {
display: grid;
grid-template-columns: 1fr auto 1fr;
gap: 10px;
align-items: center;
margin-top: 6px;
}
.rt-topo-col {
display: flex;
flex-direction: column;
gap: 6px;
}
.rt-topo-col.rt-right {
text-align: right;
}
.rt-chan {
padding: 6px 9px;
border-radius: 8px;
background: rgba(255, 255, 255, 0.05);
border: 1px solid var(--border-color);
font-size: 11px;
cursor: default;
}
.rt-chan .rt-chan-name {
font-weight: 600;
}
.rt-chan .rt-chan-sub {
font-size: 10px;
color: var(--text-muted);
}
.rt-chan .rt-caps {
display: inline-flex;
gap: 3px;
margin-left: 6px;
vertical-align: middle;
}
.rt-cap {
font-size: 9px;
padding: 1px 5px;
border-radius: 999px;
background: rgba(136, 192, 208, 0.16);
color: var(--frost-300);
}
/* —— 通道分配(按归属):容器 + 容量滑块 ——
为什么用容器而不是一排文字:归属是“分组”语义,只有把同一 owner 的
inputch 画进同一个盒子,才能一眼看出“这条输入被划给了哪个驻留子”。 */
.rt-owner {
border: 1px solid var(--border-color);
border-left: 3px solid var(--frost-300);
border-radius: 8px;
padding: 7px 9px;
margin-bottom: 7px;
background: rgba(255, 255, 255, 0.03);
}
.rt-owner.rt-owner-child {
border-left-color: #b48ead;
background: rgba(180, 142, 173, 0.07);
}
.rt-owner-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
margin-bottom: 4px;
}
.rt-owner-name {
font-size: 11px;
font-weight: 600;
}
.rt-owner-meta {
font-size: 10px;
color: var(--text-muted);
}
.rt-assign {
display: flex;
align-items: center;
gap: 8px;
padding: 4px 0;
border-top: 1px dashed rgba(255, 255, 255, 0.06);
font-size: 11px;
}
.rt-assign .rt-chan-name {
font-weight: 600;
min-width: 84px;
}
.rt-assign .rt-chan-sub {
font-size: 10px;
color: var(--text-muted);
margin-left: auto;
}
.rt-badge-warn {
font-size: 9px;
padding: 1px 6px;
border-radius: 999px;
background: rgba(191, 97, 106, 0.22);
color: #d08770;
}
/* 容量滑块:轨道 + 填充 + 把手 + 读数。 */
.rt-slider {
display: inline-flex;
align-items: center;
gap: 5px;
flex: 0 0 auto;
}
.rt-slider-label {
font-size: 9px;
color: var(--text-muted);
}
.rt-slider-track {
position: relative;
display: inline-block;
width: 64px;
height: 6px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.10);
overflow: visible;
}
.rt-slider-track > i {
position: absolute;
left: 0;
top: 0;
bottom: 0;
border-radius: 999px;
background: var(--frost-300);
}
.rt-slider-track > b {
position: absolute;
top: 50%;
width: 9px;
height: 9px;
margin-left: -4.5px;
margin-top: -4.5px;
border-radius: 50%;
background: #fff;
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.25);
}
.rt-slider-val {
font-size: 10px;
min-width: 34px;
color: var(--text-muted);
}
/* 行内迷你条(登记 / 抢占并排) */
.rt-mini {
display: inline-flex;
align-items: center;
gap: 3px;
margin-left: 6px;
font-size: 10px;
}
.rt-mini-track {
display: inline-block;
width: 30px;
height: 4px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.10);
overflow: hidden;
vertical-align: middle;
}
.rt-mini-track > i {
display: block;
height: 100%;
border-radius: 999px;
background: var(--frost-300);
}
.rt-core {
width: 74px;
height: 74px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 11px;
font-weight: 600;
text-align: center;
line-height: 1.2;
background: radial-gradient(circle at 50% 40%, rgba(var(--accent-strong-rgb), 0.35), rgba(24, 24, 38, 0.9));
border: 1px solid var(--glass-border);
box-shadow: var(--shadow-glow);
}
.rt-section-title {
font-size: 12px;
font-weight: 600;
color: var(--text-secondary);
letter-spacing: 0.3px;
margin: 14px 0 8px;
text-transform: uppercase;
}
/* ===== 阶段管道:五个等大表框 =====
与中断队列、KPI 同一套视觉语言(等大框 + 大号加粗标题)。
此前是「小圆点 + 一条连接线 + 9px 小字」,在总览里几乎读不出
「现在走到哪一步」,尺寸也远小于旁边的 KPI 框。
现在事件直接落在它所属的框里,不需要靠颜色或图例去猜。 */
.rt-pipe-row {
display: grid;
/* 与 .rt-queues 同一套列宽规则:两块的框宽、换行点保持一致 */
grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
gap: 10px;
margin: 2px 0 8px;
transition: opacity 0.3s var(--ease-out);
}
.rt-pipe-idle {
opacity: 0.55;
}
.rt-pipe-cell {
display: flex;
flex-direction: column;
gap: 8px;
padding: 11px 12px 10px;
border-radius: var(--radius-md);
background: var(--bg-input);
border: 1px solid var(--border-color);
min-width: 0;
transition:
border-color 0.25s var(--ease-out),
box-shadow 0.25s var(--ease-out),
background 0.25s var(--ease-out);
}
.rt-pipe-cell.active {
border-color: var(--accent);
background: rgba(var(--accent-rgb), 0.08);
box-shadow: 0 0 0 1px rgba(var(--accent-rgb), 0.22);
}
.rt-pipe-head {
display: flex;
align-items: center;
gap: 6px;
min-width: 0;
}
.rt-pipe-head > b {
font-size: 13.5px;
font-weight: 700;
letter-spacing: 0.01em;
color: var(--text-secondary);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.rt-pipe-head .rt-ico {
width: 15px;
height: 15px;
color: var(--text-secondary);
}
.rt-pipe-cell.active .rt-pipe-head > b,
.rt-pipe-cell.active .rt-pipe-head .rt-ico {
color: var(--accent);
}
.rt-pipe-events {
display: flex;
flex-wrap: wrap;
gap: 4px;
min-height: 23px;
}
/* 「工具」格的滚动视口:固定一行高,只露最新一条。
工具调用一轮内可能几十次,全部追加会把这一格撑成长条,反而看不出
「现在在调什么」;这里让旧条向上滚出视口,留下最新那条。 */
.rt-pipe-scroll {
flex-wrap: nowrap;
height: 23px;
min-height: 23px;
overflow: hidden;
align-items: center;
}
/* 名称可缩、计数不可缩:窄框里先截断工具名,累计次数始终看得见。 */
.rt-pipe-scroll .rt-chip {
flex: 0 1 auto;
min-width: 0;
max-width: none;
}
/* rtFlashLatestTool 每来一条新调用就重加一次,重置动画时间轴。 */
.rt-chip-enter {
animation: rt-chip-scroll-in 0.3s var(--ease-out);
}
@keyframes rt-chip-scroll-in {
from {
transform: translateY(115%);
opacity: 0;
}
to {
transform: translateY(0);
opacity: 1;
}
}
.rt-scroll-count {
flex: 0 0 auto;
margin-left: auto;
font-style: normal;
font-size: 10.5px;
opacity: 0.6;
font-variant-numeric: tabular-nums;
}
/* 拓扑光点:沿 animateMotion 给的路径跑;不吃鼠标事件,跑完由 JS 摘掉。 */
.rt-spark {
pointer-events: none;
filter: drop-shadow(0 0 4px rgba(255, 255, 255, 0.8));
}
/* ===== 设置页「外观」区 ===== */
.ap-dots {
display: inline-flex;
gap: 8px;
flex-wrap: wrap;
align-items: center;
}
.ap-dot {
width: 22px;
height: 22px;
border-radius: 50%;
border: 2px solid var(--border-color);
cursor: pointer;
padding: 0;
transition: transform 0.12s var(--ease-out);
}
.ap-dot:hover {
transform: scale(1.12);
}
.ap-dot.on {
border-color: var(--text-primary);
box-shadow: 0 0 0 2px var(--accent-bg);
}
/* ===== 总览 KPI(图标 + 数字,静态骨架)=====
节点只在首帧建一次,刷新只改值与类名 —— 这是总览页不再闪的前提。 */
.ov-kpis {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(118px, 1fr));
gap: 10px;
}
.ov-kpi {
display: flex;
flex-direction: column;
gap: 5px;
padding: 12px 14px 11px;
border-radius: var(--radius-md);
background: var(--bg-input);
border: 1px solid var(--border-color);
min-width: 0;
}
.ov-ico svg {
width: 18px;
height: 18px;
fill: none;
stroke: var(--accent);
stroke-width: 1.7;
stroke-linecap: round;
stroke-linejoin: round;
display: block;
}
.ov-val {
display: flex;
align-items: center;
gap: 6px;
font-size: 18px;
font-weight: 700;
line-height: 1.15;
min-width: 0;
}
.ov-val b {
font-weight: 700;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* 副行:内核名 + commit。总览只给版本号会分不清是哪个内核、哪次构建。 */
.ov-sub {
font-size: 10px;
color: var(--text-muted);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
min-height: 12px;
font-variant-numeric: tabular-nums;
}
.ov-lab {
font-size: 10px;
letter-spacing: 0.05em;
text-transform: uppercase;
color: var(--text-muted);
}
.ov-dot {
flex: 0 0 auto;
width: 8px;
height: 8px;
border-radius: 50%;
background: var(--text-muted);
}
.ov-dot.ok {
background: var(--success);
box-shadow: 0 0 8px rgba(23, 169, 100, 0.55);
}
.ov-dot.warn {
background: var(--warning);
}
.ov-dot.bad {
background: var(--error);
}
/* 开源许可卡:协议标识 + 协议全文 + 源码仓库。
此前只是 KPI 卡底部一行小链接(.ov-foot),在长页面里基本看不见。 */
.ov-legal h2 {
margin-bottom: 6px;
}
.ov-legal-note {
margin: 10px 0 0;
font-size: 11px;
line-height: 1.55;
color: var(--text-muted);
}
/* 行内小图标:一律 SVG,不使用 emoji/符号字符。
事件 chip 用它区分普通工具与 output_* 输出通道。 */
.rt-ico {
width: 12px;
height: 12px;
flex: 0 0 auto;
fill: none;
stroke: currentColor;
stroke-width: 2;
stroke-linecap: round;
stroke-linejoin: round;
vertical-align: -1.5px;
}
.rt-ico-sm {
width: 11px;
height: 11px;
}
.rt-loop {
display: inline-flex;
color: var(--accent);
font-weight: 700;
}
.rt-chip {
display: inline-flex;
align-items: center;
gap: 3px;
/* min-width:0 + overflow:hidden:框窄时 chip 跟着缩,而不是把框撞破 */
min-width: 0;
max-width: 170px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 11px;
padding: 2px 8px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.05);
border: 1px solid var(--border-color);
color: var(--text-secondary);
}
.rt-chip-t {
/* font-weight 继承,不因为换成 <b> 而变粗;省略号靠它生效 */
font-weight: inherit;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.rt-chip-tool {
border-color: rgba(136, 192, 208, 0.42);
color: var(--frost-300);
}
.rt-chip-output {
border-color: var(--accent);
color: var(--accent);
}
.rt-chip-stage {
opacity: 0.7;
}
.rt-chip-none {
opacity: 0.35;
border-style: dashed;
}
.rt-chip-n {
font-style: normal;
opacity: 0.75;
font-variant-numeric: tabular-nums;
}
/* ===== 服务入口列表(反代插件服务的「点一下直接打开」) ===== */
/* 与 run-* 系列同样的格槽风格:左状态点、中名称、右动作,避免行内文字挤在一起 */
.svc-list{display:flex;flex-direction:column;gap:6px}
.svc-row{display:flex;align-items:center;gap:10px;padding:9px 12px;border-radius:10px;background:var(--bg-input,rgba(13,13,22,.5));border:1px solid var(--border-color,rgba(255,255,255,.09));font-size:13px;transition:border-color .15s,background .15s}
.svc-row:hover{border-color:var(--sakura-400,#ff7fac)}
.svc-dot{width:8px;height:8px;border-radius:50%;flex-shrink:0}
.svc-name{font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:180px}
.svc-sub{color:var(--text-muted,#6e7284);font-size:12px;white-space:nowrap}
.svc-tag{font-size:10px;padding:2px 6px;border-radius:6px;background:var(--frost-300,#88c0d0);color:#0d0d16;font-weight:700;letter-spacing:.04em}
.svc-tag-warn{background:var(--danger,#d1383d);color:#fff}
.svc-path{color:var(--text-muted,#6e7284);font-family:ui-monospace,Menlo,Consolas,monospace;font-size:11px;margin-left:auto;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:240px}
.svc-err{color:var(--danger,#d1383d);font-size:11px;margin-left:auto;max-width:260px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}