feat(webui): 默认配色改黑白 + 设置页新增「外观」区

jianf:默认配色太花,且配色要能在设置页调。

- 新增 mono(黑白灰)配色并设为默认:未选过配色的 localStorage 一律
  data-color=mono。黑白下连拓扑归属配色也走灰阶,不至于只剩一张彩图。
- accent 的所有硬编码 rgba(255,127,172,x) 收敛成语义变量 --accent-rgb,
  各配色块(sakura/cyan/violet/emerald/amber/blue)各自声明自己的 rgb,
  于是换配色时阴影/描边/阴影辉光一起换,不再残留粉色。
- 设置页新增「外观」区(侧栏最前):主题(浅/深)+ 7 个配色圆点 +
  背景图 URL/模糊。原先只有侧栏底部一个调色盘图标,找不到。
- 切配色时强制重画运行态(置空 _rtSig),否则拓扑会停在旧色。
This commit is contained in:
JianFeeeee
2026-09-14 15:44:17 +08:00
parent 27a7a3b439
commit f722498dba
2 changed files with 229 additions and 38 deletions

View File

@ -25,8 +25,8 @@
--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(255, 127, 172, 0.4),
0 4px 20px rgba(255, 127, 172, 0.18);
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;
@ -51,8 +51,12 @@
--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(255, 127, 172, 0.14);
--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);
@ -60,7 +64,7 @@
--pre-color: #cdd3f5;
--pre-bg: rgba(10, 14, 28, 0.85);
--chat-bg: rgba(10, 14, 28, 0.7);
--msg-user-bg: rgba(255, 127, 172, 0.16);
--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;
@ -75,9 +79,9 @@
--save-btn-border: #d99a2b;
--loading-border: rgba(255, 255, 255, 0.12);
--loading-top: #ff7fac;
--grad-a: rgba(255, 127, 172, 0.14);
--grad-a: rgba(var(--accent-rgb), 0.14);
--grad-b: rgba(136, 192, 208, 0.12);
--grad-c: rgba(243, 59, 124, 0.1);
--grad-c: rgba(var(--accent-strong-rgb), 0.1);
--c-sakura: #ff7fac;
--c-cyan: #2dd4bf;
--c-violet: #a78bfa;
@ -88,18 +92,18 @@
[data-theme="light"] {
--glass-bg: rgba(255, 255, 255, 0.66);
--glass-bg-strong: rgba(255, 255, 255, 0.88);
--glass-border: rgba(255, 127, 172, 0.22);
--glass-hover: rgba(255, 127, 172, 0.07);
--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(243, 59, 124, 0.3), 0 6px 22px rgba(243, 59, 124, 0.14);
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(255, 127, 172, 0.08);
--bg-hover: rgba(var(--accent-rgb), 0.08);
--text-primary: #3b2030;
--text-secondary: #7a5c6b;
--text-muted: #a48a96;
@ -128,22 +132,26 @@
--save-btn-border: #d99a2b;
--loading-border: rgba(201, 36, 98, 0.18);
--loading-top: #c92462;
--grad-a: rgba(255, 127, 172, 0.16);
--grad-a: rgba(var(--accent-rgb), 0.16);
--grad-b: rgba(136, 192, 208, 0.12);
--grad-c: rgba(243, 59, 124, 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(255, 127, 172, 0.08);
--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(255, 127, 172, 0.14);
--grad-c: rgba(243, 59, 124, 0.09);
--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);
@ -153,6 +161,8 @@
--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;
@ -169,6 +179,8 @@
--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);
@ -178,6 +190,8 @@
--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;
@ -194,6 +208,8 @@
--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);
@ -203,6 +219,8 @@
--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;
@ -219,6 +237,8 @@
--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);
@ -228,6 +248,8 @@
--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;
@ -244,6 +266,8 @@
--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);
@ -253,6 +277,8 @@
--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;
@ -268,6 +294,67 @@
--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;
@ -450,7 +537,7 @@
}
.sidebar-toggle:hover {
color: var(--accent);
border-color: rgba(255, 127, 172, 0.5);
border-color: rgba(var(--accent-rgb), 0.5);
transform: scale(1.05);
}
.breadcrumb {
@ -513,7 +600,7 @@
}
.theme-btn:hover {
color: var(--accent);
border-color: rgba(255, 127, 172, 0.5);
border-color: rgba(var(--accent-rgb), 0.5);
box-shadow: var(--shadow-glow);
transform: scale(1.06);
}
@ -644,7 +731,7 @@
整页十几张卡一起「上下浮」,视线扫过时像在抖,是廉价感最明显的一处。 */
.card:hover {
box-shadow: var(--shadow-md);
border-color: rgba(255, 127, 172, 0.22);
border-color: rgba(var(--accent-rgb), 0.22);
}
.card.tilt {
transform-style: preserve-3d;
@ -661,7 +748,7 @@
transition: opacity 0.3s ease-out;
background: radial-gradient(
240px circle at var(--mx, 50%) var(--my, 50%),
rgba(255, 127, 172, 0.12),
rgba(var(--accent-rgb), 0.12),
transparent 70%
);
mix-blend-mode: normal;
@ -846,11 +933,11 @@
var(--sakura-500)
);
color: #fff;
box-shadow: 0 2px 12px rgba(255, 127, 172, 0.35);
box-shadow: 0 2px 12px rgba(var(--accent-rgb), 0.35);
}
.btn-primary:hover {
filter: brightness(1.08);
box-shadow: 0 4px 18px rgba(255, 127, 172, 0.45);
box-shadow: 0 4px 18px rgba(var(--accent-rgb), 0.45);
}
.btn-danger {
background: var(--error);
@ -871,7 +958,7 @@
.btn-ghost:hover {
background: var(--btn-ghost-hover-bg);
color: var(--text-primary);
border-color: rgba(255, 127, 172, 0.4);
border-color: rgba(var(--accent-rgb), 0.4);
}
.tab-content {
display: none;
@ -979,7 +1066,7 @@
.settings-tabs span.active {
background: var(--accent-bg);
color: var(--accent);
border-color: rgba(255, 127, 172, 0.35);
border-color: rgba(var(--accent-rgb), 0.35);
}
.settings-content {
flex: 1;
@ -1206,7 +1293,7 @@
font-size: 40px;
margin-bottom: 12px;
opacity: 0.6;
filter: drop-shadow(0 4px 10px rgba(255, 127, 172, 0.3));
filter: drop-shadow(0 4px 10px rgba(var(--accent-rgb), 0.3));
}
.chat-layout {
display: flex;
@ -1248,7 +1335,7 @@
color: var(--accent);
background: var(--accent-bg);
font-weight: 600;
box-shadow: inset 0 0 0 1px rgba(255, 127, 172, 0.28);
box-shadow: inset 0 0 0 1px rgba(var(--accent-rgb), 0.28);
}
.chat-panel {
display: none;
@ -1399,7 +1486,7 @@
background: var(--msg-user-bg);
color: var(--text-primary);
border-bottom-right-radius: 5px;
border-color: rgba(255, 127, 172, 0.26);
border-color: rgba(var(--accent-rgb), 0.26);
}
.msg-assistant .msg-bubble {
background: var(--msg-bubble-bg);
@ -1954,7 +2041,7 @@
background var(--dur-micro);
}
.health-item:hover {
border-color: rgba(255, 127, 172, 0.25);
border-color: rgba(var(--accent-rgb), 0.25);
}
.health-item .check-name {
flex: 1;
@ -2168,7 +2255,7 @@
border-radius: var(--radius-sm);
}
::selection {
background: rgba(255, 127, 172, 0.35);
background: rgba(var(--accent-rgb), 0.35);
}
@media (prefers-reduced-motion: reduce) {
*,
@ -2188,19 +2275,19 @@
background: transparent;
}
::-webkit-scrollbar-thumb {
background: rgba(255, 127, 172, 0.25);
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(255, 127, 172, 0.45);
background: rgba(var(--accent-rgb), 0.45);
background-clip: content-box;
border: 2px solid transparent;
}
* {
scrollbar-width: thin;
scrollbar-color: rgba(255, 127, 172, 0.35) transparent;
scrollbar-color: rgba(var(--accent-rgb), 0.35) transparent;
}
/* ===== 运行态面板(图形化总览)=====
@ -2531,7 +2618,7 @@
font-weight: 600;
text-align: center;
line-height: 1.2;
background: radial-gradient(circle at 50% 40%, rgba(243, 59, 124, 0.35), rgba(24, 24, 38, 0.9));
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);
}
@ -2602,7 +2689,7 @@
.rt-pipe-node.active > i {
background: var(--sakura-400);
border-color: var(--sakura-400);
box-shadow: 0 0 0 4px rgba(255, 127, 172, 0.18);
box-shadow: 0 0 0 4px rgba(var(--accent-rgb), 0.18);
}
.rt-pipe-node.active > b {
color: var(--text-primary);
@ -2616,7 +2703,7 @@
margin-left: -5.5px;
border-radius: 50%;
background: #fff;
box-shadow: 0 0 10px rgba(255, 127, 172, 0.85);
box-shadow: 0 0 10px rgba(var(--accent-rgb), 0.85);
transition: left 0.45s var(--ease-out);
z-index: 2;
}
@ -2629,3 +2716,27 @@
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);
}

View File

@ -96,6 +96,8 @@
}
var PALETTES = {
// mono 放第一位:它现在是默认配色(黑白单调),也应当是选择器里最显眼那个。
mono: "#c2c7d0",
sakura: "#ff7fac",
cyan: "#2dd4bf",
violet: "#a78bfa",
@ -107,6 +109,14 @@
function setColor(name) {
document.documentElement.setAttribute("data-color", name);
localStorage.setItem("ha-color", name);
// 配色变了要强制重画运行态:拓扑的归属配色跟着 data-color 走,
// 而数据签名没变,不置空 _rtSig 的话图会一直停在旧色。
_rtSig = null;
if (document.getElementById("rt-sec-topo")) {
try {
renderRuntime();
} catch (e) {}
}
var pop = document.getElementById("palette-pop");
if (!pop) return;
var btns = pop.querySelectorAll("button.cdot");
@ -145,7 +155,7 @@
if (!pop) return;
var on = pop.classList.contains("on");
if (!pop.querySelector("button.cdot")) {
var cur = localStorage.getItem("ha-color") || "sakura";
var cur = localStorage.getItem("ha-color") || "mono";
var img = localStorage.getItem("ha-bg-img") || "";
var blur = localStorage.getItem("ha-bg-blur") || "0";
var dots = "";
@ -199,8 +209,10 @@
(function () {
var saved = localStorage.getItem("ha-theme");
setTheme(saved || "light");
var savedColor = localStorage.getItem("ha-color");
if (savedColor) setColor(savedColor);
// 默认配色 = 黑白:必须**主动** setColor 一次。只设 data-theme 时
// base 主题仍是樱粉(:root 的 --accent 就是粉色),不写 data-color 就
// 会默认花。用户选过别的颜色时以 localStorage 为准。
setColor(localStorage.getItem("ha-color") || "mono");
var sb = localStorage.getItem("ha-sidebar");
if (sb === "1" || (!sb && window.innerWidth <= 768)) {
document.body.classList.add("sidebar-collapsed");
@ -646,6 +658,13 @@
// 归属配色:根用青色,驻留子轮转其余颜色(同一张图里能一眼分清谁是谁)。
var RT_OWNER_COLORS = ["#88c0d0", "#b48ead", "#a3be8c", "#ebcb8b", "#d08770", "#8fbcbb"];
// 黑白配色下,拓扑归属也用灰阶 —— 否则整页只剩这张图还是一片彩。
var RT_OWNER_COLORS_MONO = ["#e6e9ef", "#c2c7d0", "#9aa0ad", "#7b818d", "#5f6570", "#454a55"];
function rtOwnerColors() {
return document.documentElement.getAttribute("data-color") === "mono"
? RT_OWNER_COLORS_MONO
: RT_OWNER_COLORS;
}
// rtOwnerGroups 把 inputch 按**归属**分组owner == 根 agent id 与 "" 归一为根)。
function rtOwnerGroups(inputs, residents, rootID) {
@ -675,6 +694,7 @@
return a < b ? -1 : 1;
});
var ci = 0;
var OW = rtOwnerColors();
return order.map(function (o) {
var res = null;
residents.forEach(function (r) {
@ -685,7 +705,7 @@
owner: o,
child: o !== "",
label: o === "" ? __("根 agent / 内核默认", "root agent / kernel default") : __("驻留子 ", "resident ") + o,
color: o === "" ? RT_OWNER_COLORS[0] : RT_OWNER_COLORS[1 + (ci++ % (RT_OWNER_COLORS.length - 1))],
color: o === "" ? OW[0] : OW[1 + (ci++ % (OW.length - 1))],
list: list,
count: list.length,
res: res,
@ -4172,6 +4192,15 @@
var el = document.querySelector(".settings-tabs");
if (!el) return;
el.innerHTML = "";
// 「外观」不是插件设置,但它是 webui 自己的配置,放在设置页最前面。
var ap = document.createElement("span");
ap.textContent = __("外观", "Appearance");
if (state.selectedSection === "appearance") ap.className = "active";
ap.onclick = function () {
state.selectedSection = "appearance";
renderOneSettings();
};
el.appendChild(ap);
state.settingsPlugins.forEach(function (p) {
var a = document.createElement("span");
a.textContent = pluginDisplayName(p);
@ -4184,7 +4213,58 @@
});
}
// 外观设置区:主题(浅/深)+ 配色 + 背景图。与侧栏那个 palette-pop 同一套
// setColor/applyBgImg/applyBgBlur只是给它一个正式的、可发现的落点
// (原来只有侧栏底部一个调色盘图标,找不到)。
function appearanceHtml() {
var theme = document.documentElement.getAttribute("data-theme") || "light";
var color = localStorage.getItem("ha-color") || "mono";
var dots = "";
Object.keys(PALETTES).forEach(function (k) {
dots +=
'<button class="ap-dot' + (k === color ? " on" : "") + '" data-c="' + k +
'" title="' + k + '" style="background:' + PALETTES[k] + '" onclick="pickColor(\'' + k + '\')"></button>';
});
var img = localStorage.getItem("ha-bg-img") || "";
var blur = localStorage.getItem("ha-bg-blur") || "0";
return (
'<div class="card"><h2>' + __("外观", "Appearance") + "</h2>" +
'<div class="kv-row"><span class="key">' + __("主题", "Theme") + '</span><span class="val">' +
'<button class="btn btn-sm' + (theme === "light" ? "" : " btn-ghost") + '" onclick="pickTheme(\'light\')">' + __("浅色", "Light") + "</button> " +
'<button class="btn btn-sm' + (theme === "dark" ? "" : " btn-ghost") + '" onclick="pickTheme(\'dark\')">' + __("深色", "Dark") + "</button>" +
"</span></div>" +
'<div class="kv-row"><span class="key">' + __("配色", "Color") + '</span><span class="val"><span class="ap-dots">' + dots + "</span></span></div>" +
"<label>" + __("背景图片 URL", "Background image URL") + "</label>" +
'<input type="text" id="ap-bg-img" value="' + escHtml(img) + '" placeholder="https://...jpg / png">' +
'<div style="display:flex;gap:8px;margin-top:8px">' +
'<button class="btn btn-sm" onclick="applyBgImg(document.getElementById(\'ap-bg-img\').value)">' + __("应用", "Apply") + "</button>" +
'<button class="btn btn-ghost btn-sm" onclick="applyBgImg(\'\');document.getElementById(\'ap-bg-img\').value=\'\'">' + __("清除", "Clear") + "</button>" +
"</div>" +
'<label style="margin-top:12px">' + __("背景模糊", "Background blur") + '<span id="ap-blur-val">' + blur + "px</span></label>" +
'<input type="range" id="ap-blur-range" min="0" max="30" value="' + blur + '" oninput="applyBgBlur(this.value);var v=document.getElementById(\'ap-blur-val\');if(v)v.textContent=this.value+\'px\'">' +
'<p style="color:var(--text-muted);font-size:12px;margin-top:10px">' +
__("配色只影响本站点界面的强调色,不改动 Agent 的人格或数据。", "Colors only affect this dashboard's accent; agent behavior and data are unchanged.") +
"</p></div>"
);
}
function pickTheme(name) {
saveTheme(name);
renderOneSettings();
}
function pickColor(name) {
setColor(name);
renderOneSettings();
}
function renderOneSettings() {
if (state.selectedSection === "appearance") {
document.getElementById("tab-settings").innerHTML =
'<div class="settings-layout"><div class="settings-tabs"></div><div class="settings-content">' +
appearanceHtml() +
"</div></div>";
renderSettingsSidebar();
return;
}
var prefix = state.selectedSection + ".";
var allKeys = Object.keys(state.settings || {});
var filtered = allKeys.filter(function (k) {