feat(desktop): 复杂主题色与外观调整

浅/深外观、素色模式、8 色色盘、语言列表 UI 与设置表单样式优化。
This commit is contained in:
lyx committed 2026-05-21 03:29:37 +08:00
1 parent 852ab63d43
commit 562b47f3b6
3 files changed
+328 -70

No files matched your search

+95 -27
View File
@@ -24,7 +24,8 @@ const I18N = {
'ctx.pin': '置顶', 'ctx.unpin': '取消置顶', 'ctx.del': '删除',
'common.close': '关闭', 'common.more': '更多', 'common.optional': '选填',
'modal.preset': '预设功能', 'modal.addModel': '添加模型', 'modal.editModel': '编辑模型', 'modal.settings': '配置',
'set.theme': '主题色', 'set.lang': '语言', 'set.model': '模型', 'set.addModel': '添加模型',
'set.appearance': '外观', 'set.plainUi': '素色', 'set.theme': '颜色', 'set.lang': '语言', 'set.model': '模型', 'set.addModel': '添加模型',
'appearance.light': '浅色', 'appearance.dark': '深色',
'set.noModels': '暂无模型,点击下方添加',
'lang.zh': '简体中文', 'lang.en': 'English',
'model.name': '备注', 'model.namePh': '会显示在模型列表',
@@ -35,7 +36,8 @@ const I18N = {
'model.retries': '重试 (次)', 'model.connTimeout': '连接超时 (s)', 'model.readTimeout': '读取超时 (s)',
'model.save': '保存', 'common.cancel': '取消', 'common.edit': '编辑', 'common.delete': '删除',
'err.modelSave': '保存失败', 'err.modelRequired': '请填写模型、API Key 和 API 地址',
'err.modelDelete': '删除失败', 'confirm.modelDelete': '确定删除该模型配置?',
'err.modelDelete': '删除失败', 'err.modelDeleteLast': '至少保留一个模型',
'confirm.modelDelete': '确定删除该模型配置?',
'page.channels.title': '消息通道', 'page.channels.sub': '把 hub.pyw 管理的各 imbot 接入搬进来:每行一个渠道',
'page.status.title': '状态面板', 'page.status.sub': 'hub.pyw 管理的后台进程/服务,集中查看与启停',
'page.collab.title': '协作动态', 'page.collab.sub': 'subagent / Hive worker 的实时状态与产出',
@@ -83,7 +85,8 @@ const I18N = {
'ctx.pin': 'Pin', 'ctx.unpin': 'Unpin', 'ctx.del': 'Delete',
'common.close': 'Close', 'common.more': 'More', 'common.optional': 'Optional',
'modal.preset': 'Presets', 'modal.addModel': 'Add model', 'modal.editModel': 'Edit model', 'modal.settings': 'Settings',
'set.theme': 'Theme color', 'set.lang': 'Language', 'set.model': 'Model', 'set.addModel': 'Add model',
'set.appearance': 'Appearance', 'set.plainUi': 'Plain', 'set.theme': 'Color', 'set.lang': 'Language', 'set.model': 'Model', 'set.addModel': 'Add model',
'appearance.light': 'Light', 'appearance.dark': 'Dark',
'set.noModels': 'No models yet — add one below',
'lang.zh': '简体中文', 'lang.en': 'English',
'model.name': 'Note', 'model.namePh': 'Shown in the model list',
@@ -94,7 +97,8 @@ const I18N = {
'model.retries': 'Retries (×)', 'model.connTimeout': 'Connect (s)', 'model.readTimeout': 'Read (s)',
'model.save': 'Save', 'common.cancel': 'Cancel', 'common.edit': 'Edit', 'common.delete': 'Delete',
'err.modelSave': 'Save failed', 'err.modelRequired': 'Model, API Key and base URL are required',
'err.modelDelete': 'Delete failed', 'confirm.modelDelete': 'Delete this model profile?',
'err.modelDelete': 'Delete failed', 'err.modelDeleteLast': 'At least one model is required',
'confirm.modelDelete': 'Delete this model profile?',
'page.channels.title': 'Channels', 'page.channels.sub': 'imbot channels managed by hub.pyw — one row per channel',
'page.status.title': 'Status', 'page.status.sub': 'Background processes/services managed by hub.pyw',
'page.collab.title': 'Collaboration', 'page.collab.sub': 'Live state & output of subagents / Hive workers',
@@ -125,9 +129,28 @@ const I18N = {
'presetPrompt.mine': 'Collect this week\'s git commits and write a weekly report.',
},
};
let lang = (localStorage.getItem('ga_lang') === 'en') ? 'en' : 'zh';
const LANGS = ['zh', 'en'];
let lang = LANGS.includes(localStorage.getItem('ga_lang')) ? localStorage.getItem('ga_lang') : 'zh';
let theme = localStorage.getItem('ga_theme') || '1';
const STORE = { lang: 'ga_lang', theme: 'ga_theme', llmNo: 'ga_llm_no' };
const STORE = { lang: 'ga_lang', theme: 'ga_theme', appearance: 'ga_appearance', plain: 'ga_plain', llmNo: 'ga_llm_no' };
const APPEARANCE_IDS = ['light', 'dark'];
const LEGACY_STYLE = { classic: ['light', true], tinted: ['light', false], dark: ['dark', false] };
function migrateAppearance() {
let app = localStorage.getItem(STORE.appearance);
let plain = localStorage.getItem(STORE.plain) === '1';
const legacy = localStorage.getItem('ga_style');
if (!app && legacy && LEGACY_STYLE[legacy]) {
[app, plain] = LEGACY_STYLE[legacy];
localStorage.setItem(STORE.appearance, app);
if (plain) localStorage.setItem(STORE.plain, '1');
else localStorage.removeItem(STORE.plain);
localStorage.removeItem('ga_style');
}
if (!APPEARANCE_IDS.includes(app)) app = 'light';
return { appearance: app, plain: plain && app === 'light' };
}
let { appearance, plain: plainUi } = migrateAppearance();
const bridgeHost = () => `${location.protocol}//${location.hostname}:14168`;
async function bridgeFetch(path, opts = {}) {
const headers = { ...(opts.headers || {}) };
@@ -156,26 +179,67 @@ function applyI18n() {
el.setAttribute('placeholder', el.hasAttribute('data-optional-ph') ? optionalPh(phKey) : t(phKey));
});
document.querySelectorAll('[data-i18n-title]').forEach(el => { el.setAttribute('title', t(el.dataset.i18nTitle)); });
populateLangSelect();
renderLangList();
}
function populateLangSelect() {
if (!langSel) return;
langSel.innerHTML = '';
['zh', 'en'].forEach(code => {
const opt = document.createElement('option');
opt.value = code; opt.textContent = t('lang.' + code);
langSel.appendChild(opt);
function renderLangList() {
const box = document.getElementById('lang-list');
if (!box) return;
box.innerHTML = '';
LANGS.forEach(code => {
const row = document.createElement('label');
row.className = 'model-row' + (lang === code ? ' sel' : '');
row.innerHTML = `<input type="radio" name="lang-pick"${lang === code ? ' checked' : ''}><span>${escapeHtml(t('lang.' + code))}</span>`;
row.addEventListener('click', (e) => { e.preventDefault(); selectLang(code); });
box.appendChild(row);
});
langSel.value = lang;
}
function selectLang(code) {
if (!LANGS.includes(code) || lang === code) return;
lang = code;
localStorage.setItem(STORE.lang, lang);
applyI18n();
renderSessionList();
refreshStatusLabel();
updateModelChip();
renderSettingsModels();
}
function applyTheme(id) {
theme = String(id || '1');
const n = parseInt(id, 10);
theme = (n >= 1 && n <= 8) ? String(n) : '1';
document.documentElement.dataset.theme = theme;
localStorage.setItem(STORE.theme, theme);
document.querySelectorAll('#theme-swatches .swatch').forEach(el => {
el.classList.toggle('sel', el.dataset.theme === theme);
});
}
function syncPlainSwitch() {
const row = document.getElementById('plain-ui-row');
const sw = document.getElementById('plain-ui-switch');
if (!row || !sw) return;
const show = appearance === 'light';
row.hidden = !show;
sw.setAttribute('aria-checked', plainUi ? 'true' : 'false');
}
function applyAppearance(nextApp, nextPlain) {
appearance = APPEARANCE_IDS.includes(nextApp) ? nextApp : 'light';
if (appearance === 'light') {
plainUi = !!nextPlain;
if (plainUi) localStorage.setItem(STORE.plain, '1');
else localStorage.removeItem(STORE.plain);
} else {
plainUi = false;
}
localStorage.setItem(STORE.appearance, appearance);
document.documentElement.dataset.appearance = appearance;
if (plainUi) document.documentElement.dataset.plain = '1';
else delete document.documentElement.dataset.plain;
document.querySelectorAll('#appearance-seg .appear-card').forEach(el => {
const on = el.dataset.appearance === appearance;
el.classList.toggle('sel', on);
el.setAttribute('aria-checked', on ? 'true' : 'false');
});
syncPlainSwitch();
}
/* ═══════════════ 侧边栏导航 ═══════════════ */
const nav = document.getElementById('nav');
@@ -324,7 +388,6 @@ bindResize(rpResize, rpPanel, -1, 160, 400); // 右栏:cursor 左移 → 增宽
bindResize(sbResize, sbPanel, +1, 180, 360); // 左栏:cursor 右移 → 增宽
const modelChip = document.getElementById('model-chip');
const modelNameEl= modelChip ? modelChip.querySelector('.model-name') : null;
const langSel = document.getElementById('lang-select');
let msgsEl = null;
function ensureMsgs() {
@@ -735,7 +798,8 @@ async function deleteModel(id, name) {
}
renderSettingsModels();
} catch (ex) {
alert(ex.message || t('err.modelDelete'));
const msg = ex.message || '';
alert(msg.includes('last profile') ? t('err.modelDeleteLast') : (msg || t('err.modelDelete')));
}
}
function renderSettingsModels() {
@@ -767,8 +831,9 @@ function renderSettingsModels() {
function openSettings() {
openModal('settings-modal');
renderSettingsModels();
populateLangSelect();
renderLangList();
applyTheme(theme);
applyAppearance(appearance, plainUi);
}
async function loadModelProfiles() {
try {
@@ -801,14 +866,16 @@ if (themeSwatches) themeSwatches.addEventListener('click', (e) => {
const sw = e.target.closest('.swatch[data-theme]');
if (sw) applyTheme(sw.dataset.theme);
});
if (langSel) langSel.addEventListener('change', () => {
lang = (langSel.value === 'en') ? 'en' : 'zh';
localStorage.setItem(STORE.lang, lang);
applyI18n();
renderSessionList();
refreshStatusLabel();
updateModelChip();
renderSettingsModels();
const appearanceSeg = document.getElementById('appearance-seg');
if (appearanceSeg) appearanceSeg.addEventListener('click', (e) => {
const btn = e.target.closest('.appear-card[data-appearance]');
if (!btn) return;
const isLight = btn.dataset.appearance === 'light';
applyAppearance(btn.dataset.appearance, isLight && localStorage.getItem(STORE.plain) === '1');
});
const plainUiSwitch = document.getElementById('plain-ui-switch');
if (plainUiSwitch) plainUiSwitch.addEventListener('click', () => {
if (appearance === 'light') applyAppearance('light', !plainUi);
});
async function loadBridgeConfig() {
try {
@@ -1005,6 +1072,7 @@ if(tokResetBtn)tokResetBtn.addEventListener('click',()=>{if(tokSince)tokSince.va
nav.addEventListener('click',(e)=>{const item=e.target.closest('.nav-item');if(item&&item.dataset.page==='token')loadTokenPage();});
/* ═══════════════ 启动 ═══════════════ */
applyAppearance(appearance, plainUi);
applyTheme(theme);
applyI18n();
updateModelChip();
+48 -10
View File
@@ -6,6 +6,22 @@
<link rel="stylesheet" href="styles.css">
<!-- DEV 脚手架(标注模式):删本行 + 下方 annot.js 一行即可彻底移除 -->
<link rel="stylesheet" href="annot.css">
<script>
(function(){
var t=localStorage.getItem('ga_theme'); if(t) document.documentElement.dataset.theme=t;
var legacy=localStorage.getItem('ga_style');
var app=localStorage.getItem('ga_appearance');
if(!app&&legacy){
if(legacy==='dark') app='dark';
else{ app='light'; if(legacy==='classic') localStorage.setItem('ga_plain','1'); }
localStorage.setItem('ga_appearance',app);
localStorage.removeItem('ga_style');
}
app=app||'light';
document.documentElement.dataset.appearance=app;
if(app==='light'&&localStorage.getItem('ga_plain')==='1') document.documentElement.dataset.plain='1';
})();
</script>
</head>
<body>
@@ -337,19 +353,41 @@
</button>
</div>
<div class="modal-body set-body">
<div>
<div class="set-sec-t" data-i18n="set.theme"></div>
<div class="swatches" id="theme-swatches">
<span class="swatch sel" data-theme="1" role="button" tabindex="0"></span>
<span class="swatch" data-theme="2" role="button" tabindex="0"></span>
<span class="swatch" data-theme="3" role="button" tabindex="0"></span>
<span class="swatch" data-theme="4" role="button" tabindex="0"></span>
<span class="swatch" data-theme="5" role="button" tabindex="0"></span>
<div class="set-block">
<div class="set-sec-t" data-i18n="set.appearance"></div>
<div class="appear-cards" id="appearance-seg" role="radiogroup">
<button class="appear-card sel" type="button" data-appearance="light" role="radio" aria-checked="true">
<span class="appear-preview appear-preview-light" aria-hidden="true"></span>
<span class="appear-name" data-i18n="appearance.light"></span>
</button>
<button class="appear-card" type="button" data-appearance="dark" role="radio" aria-checked="false">
<span class="appear-preview appear-preview-dark" aria-hidden="true"></span>
<span class="appear-name" data-i18n="appearance.dark"></span>
</button>
</div>
<div class="set-toggle-row" id="plain-ui-row" hidden>
<span class="set-toggle-label" data-i18n="set.plainUi"></span>
<button class="set-switch" type="button" id="plain-ui-switch" role="switch" aria-checked="false">
<span class="set-switch-knob" aria-hidden="true"></span>
</button>
</div>
</div>
<div>
<div class="set-block">
<div class="set-sec-t" data-i18n="set.theme"></div>
<div class="color-grid" id="theme-swatches">
<span class="swatch sel" data-theme="1" role="button" tabindex="0" aria-label="1"></span>
<span class="swatch" data-theme="2" role="button" tabindex="0" aria-label="2"></span>
<span class="swatch" data-theme="3" role="button" tabindex="0" aria-label="3"></span>
<span class="swatch" data-theme="4" role="button" tabindex="0" aria-label="4"></span>
<span class="swatch" data-theme="5" role="button" tabindex="0" aria-label="5"></span>
<span class="swatch" data-theme="6" role="button" tabindex="0" aria-label="6"></span>
<span class="swatch" data-theme="7" role="button" tabindex="0" aria-label="7"></span>
<span class="swatch" data-theme="8" role="button" tabindex="0" aria-label="8"></span>
</div>
</div>
<div class="set-block">
<div class="set-sec-t" data-i18n="set.lang"></div>
<select class="set-select" id="lang-select"></select>
<div class="model-list lang-list" id="lang-list"></div>
</div>
<div>
<div class="set-sec-t" data-i18n="set.model"></div>
+185 -33
View File
@@ -5,12 +5,15 @@
:root{
/* —— 基础面 —— */
--bg:#f4f5f7;
--panel:#ffffff; /* 三大卡片底 */
--card:#ffffff; /* 卡片内的小块:pill/列表行/输入/菜单/按钮 */
--field-bg:#fafbfc; /* 表单输入底 */
--line:#e9eaee;
--line-soft:#f0f1f4; /* 浅分隔 / hover 底 */
--bg-base:#f4f5f7;
--bg:var(--bg-base);
--panel:#ffffff;
--card:#ffffff;
--field-bg:#fafbfc;
--line-base:#e9eaee;
--line:var(--line-base);
--line-soft-base:#f0f1f4;
--line-soft:var(--line-soft-base);
--line-strong:#d4d8e0; /* hover 描边 */
--hover-2:#eceef2; /* 次级 hover 底 */
--hover-dim:#0000000f;
@@ -35,12 +38,15 @@
--brand1:#ffb35c;
--brand2:#ff7a59;
/* —— 配置弹窗:可选主题色板(骨架占位)—— */
/* —— 配置弹窗:主题色板(mac 8 色)—— */
--swatch-1:#1f6f53;
--swatch-2:#7c5cff;
--swatch-3:#2f9e54;
--swatch-3:#007aff;
--swatch-4:#ff7a59;
--swatch-5:#1f2430;
--swatch-5:#ff2d55;
--swatch-6:#ff3b30;
--swatch-7:#ffcc00;
--swatch-8:#8e8e93;
/* —— 状态:成功 / 危险 / 关闭 —— */
--ok:#39a85a;
@@ -66,12 +72,78 @@
font-family:-apple-system,"Segoe UI","Microsoft YaHei",system-ui,sans-serif;
}
/* —— 主题色板(切主题只改 accent 组)—— */
html[data-theme="1"]{ --blue:var(--swatch-1); --blue-bg:#e7f2ed; --blue-soft:#f1f8f4; }
html[data-theme="2"]{ --blue:var(--swatch-2); --blue-bg:#f0ecff; --blue-soft:#f7f5ff; }
html[data-theme="3"]{ --blue:var(--swatch-3); --blue-bg:#e9f7ee; --blue-soft:#f1fbf4; }
html[data-theme="4"]{ --blue:var(--swatch-4); --blue-bg:#fff0eb; --blue-soft:#fff6f2; }
html[data-theme="5"]{ --blue:var(--swatch-5); --blue-bg:#eceef2; --blue-soft:#f4f5f7; }
/* —— 主题色板(素色:仅 accent 浅底)—— */
html[data-theme="1"]{ --blue:var(--swatch-1); --blue-bg:color-mix(in srgb, var(--blue) 14%, var(--panel)); --blue-soft:color-mix(in srgb, var(--blue) 7%, var(--panel)); }
html[data-theme="2"]{ --blue:var(--swatch-2); --blue-bg:color-mix(in srgb, var(--blue) 14%, var(--panel)); --blue-soft:color-mix(in srgb, var(--blue) 7%, var(--panel)); }
html[data-theme="3"]{ --blue:var(--swatch-3); --blue-bg:color-mix(in srgb, var(--blue) 14%, var(--panel)); --blue-soft:color-mix(in srgb, var(--blue) 7%, var(--panel)); }
html[data-theme="4"]{ --blue:var(--swatch-4); --blue-bg:color-mix(in srgb, var(--blue) 14%, var(--panel)); --blue-soft:color-mix(in srgb, var(--blue) 7%, var(--panel)); }
html[data-theme="5"]{ --blue:var(--swatch-5); --blue-bg:color-mix(in srgb, var(--blue) 14%, var(--panel)); --blue-soft:color-mix(in srgb, var(--blue) 7%, var(--panel)); }
html[data-theme="6"]{ --blue:var(--swatch-6); --blue-bg:color-mix(in srgb, var(--blue) 14%, var(--panel)); --blue-soft:color-mix(in srgb, var(--blue) 7%, var(--panel)); }
html[data-theme="7"]{ --blue:var(--swatch-7); --blue-bg:color-mix(in srgb, var(--blue) 14%, var(--panel)); --blue-soft:color-mix(in srgb, var(--blue) 7%, var(--panel)); --on-accent:#1f2430; }
html[data-theme="8"]{ --blue:var(--swatch-8); --blue-bg:color-mix(in srgb, var(--blue) 14%, var(--panel)); --blue-soft:color-mix(in srgb, var(--blue) 7%, var(--panel)); --on-accent:#ffffff; }
/* logo 渐变随主题色 */
html[data-theme]{
--brand1:color-mix(in srgb, var(--blue) 40%, #ffc978);
--brand2:var(--blue);
}
/* —— 外观:浅色(accent 轻 tint 页面/边框)—— */
html[data-appearance="light"]:not([data-plain="1"])[data-theme]{
--blue-bg:color-mix(in srgb, var(--blue) 20%, var(--panel));
--blue-soft:color-mix(in srgb, var(--blue) 11%, var(--panel));
--bg:color-mix(in srgb, var(--blue) 7%, var(--bg-base));
--line:color-mix(in srgb, var(--blue) 16%, var(--line-base));
--line-soft:color-mix(in srgb, var(--blue) 10%, var(--line-soft-base));
}
/* —— 外观:深色 —— */
html[data-appearance="dark"]{
--swatch-7:#ffd60a;
--swatch-8:#e6edf3;
--bg-base:#0d1117;
--bg:#0d1117;
--panel:#161b22;
--card:#161b22;
--field-bg:#0d1117;
--line-base:#30363d;
--line:#30363d;
--line-soft-base:#21262d;
--line-soft:#21262d;
--line-strong:#484f58;
--hover-2:#21262d;
--hover-dim:#ffffff0f;
--txt:#e6edf3;
--txt-strong:#ffffff;
--txt-2:#c9d1d9;
--txt-3:#afb8c1;
--muted:#7d8590;
--muted2:#656d76;
--placeholder:#484f58;
--icon:#7d8590;
--off-bg:#21262d;
--switch-off:#484f58;
--dot-idle:#484f58;
--ok:#3fb950;
--ok-strong:#46c35a;
--ok-bg:#12261e;
--ok-ring:#3fb95033;
--danger:#f85149;
--danger-dot:#f85149;
--danger-text:#ff7b72;
--danger-bg:#2d1515;
--danger-ring:#f8514938;
--overlay:#010409a6;
--shadow:0 1px 2px #01040966, 0 8px 24px #01040966;
--shadow-pop:0 8px 24px #01040999;
--shadow-modal:0 18px 50px #010409cc;
}
html[data-appearance="dark"][data-theme]{
--blue-bg:color-mix(in srgb, var(--blue) 22%, var(--panel));
--blue-soft:color-mix(in srgb, var(--blue) 12%, var(--panel));
}
html[data-appearance="dark"][data-theme="7"],
html[data-appearance="dark"][data-theme="8"]{ --on-accent:#0d1117; }
html,body{ height:100%; }
body{ background:var(--bg); color:var(--txt); font-size:13px; overflow:hidden; }
@@ -116,27 +188,103 @@ body{ background:var(--bg); color:var(--txt); font-size:13px; overflow:hidden; }
.modal-body{ flex:1; min-height:340px; overflow:auto; }
/* 配置弹窗内的骨架控件(未实现逻辑)*/
.set-body{ min-height:0; padding:18px; display:flex; flex-direction:column; gap:20px; }
.set-body{ min-height:0; padding:18px; display:flex; flex-direction:column; gap:22px; }
.set-block{ display:flex; flex-direction:column; gap:0; }
.preset-pop{ min-height:0; padding:18px; display:flex; flex-direction:column; gap:14px; }
.set-sec-t{ font-size:12px; font-weight:700; color:var(--txt-2); margin-bottom:10px; }
.swatches{ display:flex; gap:10px; flex-wrap:wrap; }
/* 外观:mac 式预览卡片 */
.appear-cards{ display:grid; grid-template-columns:1fr 1fr; gap:10px; }
.appear-card{
display:flex; flex-direction:column; align-items:center; gap:8px;
padding:10px 8px 9px; border:2px solid var(--line); border-radius:10px;
background:var(--field-bg); cursor:pointer; font-family:inherit; transition:.15s;
}
.appear-card:hover{ border-color:var(--line-strong); background:var(--line-soft); }
.appear-card.sel{
border-color:var(--blue); background:var(--blue-bg);
box-shadow:0 0 0 1px color-mix(in srgb, var(--blue) 25%, transparent);
}
.appear-preview{
width:100%; height:44px; border-radius:7px; border:1px solid var(--line);
position:relative; overflow:hidden;
}
.appear-preview-light{
background:linear-gradient(180deg, #f4f5f7 0%, #f4f5f7 42%, #ffffff 42%, #ffffff 100%);
}
.appear-preview-light::after{
content:''; position:absolute; left:10%; top:52%; width:55%; height:7px;
border-radius:4px; background:#e9eaee;
}
.appear-preview-dark{
background:linear-gradient(180deg, #0d1117 0%, #0d1117 42%, #161b22 42%, #161b22 100%);
border-color:#30363d;
}
.appear-preview-dark::after{
content:''; position:absolute; left:10%; top:52%; width:55%; height:7px;
border-radius:4px; background:#30363d;
}
.appear-name{ font-size:12px; font-weight:600; color:var(--txt-2); }
.appear-card.sel .appear-name{ color:var(--blue); }
/* 素色:mac/iOS 设置行 + Toggle Switch */
.set-toggle-row{
display:flex; align-items:center; justify-content:space-between; gap:12px;
margin-top:10px; padding:10px 12px;
border:1px solid var(--line); border-radius:10px; background:var(--field-bg);
}
.set-toggle-row[hidden]{ display:none; }
.set-toggle-label{ font-size:13px; font-weight:600; color:var(--txt-2); }
.set-switch{
position:relative; flex:0 0 auto; width:44px; height:26px;
border:none; border-radius:999px; background:var(--switch-off);
cursor:pointer; transition:background .2s; padding:0;
}
.set-switch[aria-checked="true"]{ background:var(--blue); }
.set-switch-knob{
position:absolute; top:2px; left:2px; width:22px; height:22px;
border-radius:50%; background:var(--card);
box-shadow:0 1px 3px var(--hover-dim); transition:transform .2s;
}
.set-switch[aria-checked="true"] .set-switch-knob{ transform:translateX(18px); }
.set-switch:focus-visible{ outline:2px solid var(--blue); outline-offset:2px; }
.lang-list{ margin-bottom:0; }
/* 颜色:mac 式 4×2 色盘 */
.color-grid{
display:grid; grid-template-columns:repeat(4, 1fr); gap:12px 10px;
justify-items:center; max-width:220px;
}
.swatch{
width:26px; height:26px; border-radius:50%; cursor:pointer;
border:2px solid var(--card); box-shadow:0 0 0 1px var(--line);
position:relative; width:32px; height:32px; border-radius:50%; cursor:pointer;
background:var(--swatch-color, var(--swatch-1));
border:2px solid color-mix(in srgb, var(--panel) 70%, transparent);
box-shadow:0 1px 2px var(--hover-dim);
transition:transform .12s, box-shadow .12s;
}
.swatch:hover{ transform:scale(1.08); }
.swatch:focus-visible{ outline:2px solid var(--blue); outline-offset:2px; }
.swatch.sel{
box-shadow:0 0 0 2px var(--panel), 0 0 0 4px var(--blue);
transform:scale(1.04);
}
.swatch.sel::after{
content:''; position:absolute; inset:0; border-radius:50%;
background:center/14px no-repeat url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'/%3E%3C/svg%3E");
}
html[data-theme="7"] .swatch[data-theme="7"].sel::after,
html[data-appearance="dark"] .swatch[data-theme="8"].sel::after{
background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%231f2430' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'/%3E%3C/svg%3E");
}
.swatch.sel{ box-shadow:0 0 0 2px var(--blue); }
.swatch[data-theme="1"]{ --swatch-color:var(--swatch-1); }
.swatch[data-theme="2"]{ --swatch-color:var(--swatch-2); }
.swatch[data-theme="3"]{ --swatch-color:var(--swatch-3); }
.swatch[data-theme="4"]{ --swatch-color:var(--swatch-4); }
.swatch[data-theme="5"]{ --swatch-color:var(--swatch-5); }
.set-select{
width:100%; border:1px solid var(--line); border-radius:8px; padding:8px 10px;
font-size:13px; font-family:inherit; background:var(--field-bg); color:var(--txt-2);
cursor:pointer;
}
.set-select:focus{ outline:none; border-color:var(--blue); }
.swatch[data-theme="6"]{ --swatch-color:var(--swatch-6); }
.swatch[data-theme="7"]{ --swatch-color:var(--swatch-7); }
.swatch[data-theme="8"]{ --swatch-color:var(--swatch-8); }
.model-list{ display:flex; flex-direction:column; gap:4px; margin-bottom:10px; max-height:160px; overflow:auto; }
.model-row{
display:flex; align-items:center; gap:10px; padding:8px 10px;
@@ -147,15 +295,19 @@ body{ background:var(--bg); color:var(--txt); font-size:13px; overflow:hidden; }
.model-row.sel{ background:var(--blue-bg); border-color:var(--blue); color:var(--blue); font-weight:600; }
.model-row input{ accent-color:var(--blue); flex:0 0 auto; pointer-events:none; }
.model-row-name{ flex:1; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.model-row-actions{ display:flex; gap:2px; flex:0 0 auto; opacity:0; transition:opacity .12s; }
.model-row:hover .model-row-actions, .model-row.sel .model-row-actions{ opacity:1; }
.model-act{
width:26px; height:26px; border:none; background:transparent; cursor:pointer;
color:var(--muted); border-radius:6px; display:flex; align-items:center; justify-content:center; padding:0;
.model-row-actions{
display:flex; gap:2px; flex:0 0 auto; opacity:0; transition:opacity .12s;
}
.model-act:hover{ background:var(--line-soft); color:var(--txt-2); }
.model-row:hover .model-row-actions,
.model-row.sel .model-row-actions{ opacity:1; }
.model-act{
width:28px; height:28px; border:none; background:transparent; cursor:pointer;
color:var(--muted); border-radius:6px; display:flex; align-items:center; justify-content:center;
padding:0; flex:0 0 auto;
}
.model-act:hover{ background:var(--hover-dim); color:var(--txt-2); }
.model-act-del:hover{ color:var(--danger-text); }
.model-act svg{ width:13px; height:13px; pointer-events:none; }
.model-act svg{ width:14px; height:14px; pointer-events:none; }
.set-empty{ font-size:12px; color:var(--muted); padding:6px 0; }
.set-btn{
width:100%; display:flex; align-items:center; gap:8px;