diff --git a/jzl_workflows/data/anima_01-1.webp b/jzl_workflows/data/anima_01-1.webp index 3d20c80..2eb2566 100644 Binary files a/jzl_workflows/data/anima_01-1.webp and b/jzl_workflows/data/anima_01-1.webp differ diff --git a/jzl_workflows/data/anima_02-1.webp b/jzl_workflows/data/anima_02-1.webp index 3d20c80..2eb2566 100644 Binary files a/jzl_workflows/data/anima_02-1.webp and b/jzl_workflows/data/anima_02-1.webp differ diff --git a/jzl_workflows/data/anima_03-1.webp b/jzl_workflows/data/anima_03-1.webp index 3d20c80..2eb2566 100644 Binary files a/jzl_workflows/data/anima_03-1.webp and b/jzl_workflows/data/anima_03-1.webp differ diff --git a/jzl_workflows/data/flux2_01-1.webp b/jzl_workflows/data/flux2_01-1.webp index 3d20c80..2eb2566 100644 Binary files a/jzl_workflows/data/flux2_01-1.webp and b/jzl_workflows/data/flux2_01-1.webp differ diff --git a/jzl_workflows/data/flux2_02-1.webp b/jzl_workflows/data/flux2_02-1.webp index 3d20c80..2eb2566 100644 Binary files a/jzl_workflows/data/flux2_02-1.webp and b/jzl_workflows/data/flux2_02-1.webp differ diff --git a/jzl_workflows/data/flux2_03-1.webp b/jzl_workflows/data/flux2_03-1.webp index 3d20c80..2eb2566 100644 Binary files a/jzl_workflows/data/flux2_03-1.webp and b/jzl_workflows/data/flux2_03-1.webp differ diff --git a/jzl_workflows/data/flux2_04-1.webp b/jzl_workflows/data/flux2_04-1.webp index 3d20c80..2eb2566 100644 Binary files a/jzl_workflows/data/flux2_04-1.webp and b/jzl_workflows/data/flux2_04-1.webp differ diff --git a/jzl_workflows/data/flux2_05-1.webp b/jzl_workflows/data/flux2_05-1.webp index 3d20c80..2eb2566 100644 Binary files a/jzl_workflows/data/flux2_05-1.webp and b/jzl_workflows/data/flux2_05-1.webp differ diff --git a/jzl_workflows/data/flux2_06-1.webp b/jzl_workflows/data/flux2_06-1.webp index 3d20c80..2eb2566 100644 Binary files a/jzl_workflows/data/flux2_06-1.webp and b/jzl_workflows/data/flux2_06-1.webp differ diff --git a/jzl_workflows/data/flux2_07-1.webp b/jzl_workflows/data/flux2_07-1.webp index 3d20c80..2eb2566 100644 Binary files a/jzl_workflows/data/flux2_07-1.webp and b/jzl_workflows/data/flux2_07-1.webp differ diff --git a/jzl_workflows/data/flux3_01-1.webp b/jzl_workflows/data/flux3_01-1.webp index 3d20c80..2eb2566 100644 Binary files a/jzl_workflows/data/flux3_01-1.webp and b/jzl_workflows/data/flux3_01-1.webp differ diff --git a/jzl_workflows/data/flux3_02-1.webp b/jzl_workflows/data/flux3_02-1.webp index 3d20c80..2eb2566 100644 Binary files a/jzl_workflows/data/flux3_02-1.webp and b/jzl_workflows/data/flux3_02-1.webp differ diff --git a/jzl_workflows/data/flux3_03-1.webp b/jzl_workflows/data/flux3_03-1.webp index 3d20c80..2eb2566 100644 Binary files a/jzl_workflows/data/flux3_03-1.webp and b/jzl_workflows/data/flux3_03-1.webp differ diff --git a/jzl_workflows/data/krea2_01-1.webp b/jzl_workflows/data/krea2_01-1.webp index 3d20c80..2eb2566 100644 Binary files a/jzl_workflows/data/krea2_01-1.webp and b/jzl_workflows/data/krea2_01-1.webp differ diff --git a/jzl_workflows/data/krea2_02-1.webp b/jzl_workflows/data/krea2_02-1.webp index 3d20c80..2eb2566 100644 Binary files a/jzl_workflows/data/krea2_02-1.webp and b/jzl_workflows/data/krea2_02-1.webp differ diff --git a/jzl_workflows/data/krea2_03-1.webp b/jzl_workflows/data/krea2_03-1.webp index 3d20c80..2eb2566 100644 Binary files a/jzl_workflows/data/krea2_03-1.webp and b/jzl_workflows/data/krea2_03-1.webp differ diff --git a/jzl_workflows/data/krea2_04-1.webp b/jzl_workflows/data/krea2_04-1.webp index 3d20c80..2eb2566 100644 Binary files a/jzl_workflows/data/krea2_04-1.webp and b/jzl_workflows/data/krea2_04-1.webp differ diff --git a/jzl_workflows/data/krea2_07-1.webp b/jzl_workflows/data/krea2_07-1.webp index 3d20c80..2eb2566 100644 Binary files a/jzl_workflows/data/krea2_07-1.webp and b/jzl_workflows/data/krea2_07-1.webp differ diff --git a/jzl_workflows/data/krea2_08-1.webp b/jzl_workflows/data/krea2_08-1.webp index 3d20c80..2eb2566 100644 Binary files a/jzl_workflows/data/krea2_08-1.webp and b/jzl_workflows/data/krea2_08-1.webp differ diff --git a/jzl_workflows/data/krea2_1139-1.webp b/jzl_workflows/data/krea2_1139-1.webp index 3d20c80..2eb2566 100644 Binary files a/jzl_workflows/data/krea2_1139-1.webp and b/jzl_workflows/data/krea2_1139-1.webp differ diff --git a/jzl_workflows/data/krea2_1140-1.webp b/jzl_workflows/data/krea2_1140-1.webp index 3d20c80..2eb2566 100644 Binary files a/jzl_workflows/data/krea2_1140-1.webp and b/jzl_workflows/data/krea2_1140-1.webp differ diff --git a/jzl_workflows/data/qwen21_01-1.webp b/jzl_workflows/data/qwen21_01-1.webp index 3d20c80..2eb2566 100644 Binary files a/jzl_workflows/data/qwen21_01-1.webp and b/jzl_workflows/data/qwen21_01-1.webp differ diff --git a/jzl_workflows/data/qwen21_02-1.webp b/jzl_workflows/data/qwen21_02-1.webp index 3d20c80..2eb2566 100644 Binary files a/jzl_workflows/data/qwen21_02-1.webp and b/jzl_workflows/data/qwen21_02-1.webp differ diff --git a/jzl_workflows/data/qwen21_03-1.webp b/jzl_workflows/data/qwen21_03-1.webp index 3d20c80..2eb2566 100644 Binary files a/jzl_workflows/data/qwen21_03-1.webp and b/jzl_workflows/data/qwen21_03-1.webp differ diff --git a/jzl_workflows/data/qwen21_04-1.webp b/jzl_workflows/data/qwen21_04-1.webp index 3d20c80..2eb2566 100644 Binary files a/jzl_workflows/data/qwen21_04-1.webp and b/jzl_workflows/data/qwen21_04-1.webp differ diff --git a/jzl_workflows/data/qwen21_05-1.webp b/jzl_workflows/data/qwen21_05-1.webp index 3d20c80..2eb2566 100644 Binary files a/jzl_workflows/data/qwen21_05-1.webp and b/jzl_workflows/data/qwen21_05-1.webp differ diff --git a/jzl_workflows/data/qwen21_06-1.webp b/jzl_workflows/data/qwen21_06-1.webp index 3d20c80..2eb2566 100644 Binary files a/jzl_workflows/data/qwen21_06-1.webp and b/jzl_workflows/data/qwen21_06-1.webp differ diff --git a/jzl_workflows/data/qwen2511_01-1.webp b/jzl_workflows/data/qwen2511_01-1.webp index 3d20c80..2eb2566 100644 Binary files a/jzl_workflows/data/qwen2511_01-1.webp and b/jzl_workflows/data/qwen2511_01-1.webp differ diff --git a/jzl_workflows/data/qwen2511_02-1.webp b/jzl_workflows/data/qwen2511_02-1.webp index 3d20c80..2eb2566 100644 Binary files a/jzl_workflows/data/qwen2511_02-1.webp and b/jzl_workflows/data/qwen2511_02-1.webp differ diff --git a/jzl_workflows/data/qwen2511_03-1.webp b/jzl_workflows/data/qwen2511_03-1.webp index 3d20c80..2eb2566 100644 Binary files a/jzl_workflows/data/qwen2511_03-1.webp and b/jzl_workflows/data/qwen2511_03-1.webp differ diff --git a/jzl_workflows/data/qwen2511_04-1.webp b/jzl_workflows/data/qwen2511_04-1.webp index 3d20c80..2eb2566 100644 Binary files a/jzl_workflows/data/qwen2511_04-1.webp and b/jzl_workflows/data/qwen2511_04-1.webp differ diff --git a/jzl_workflows/data/qwen2511_05-1.webp b/jzl_workflows/data/qwen2511_05-1.webp index 3d20c80..2eb2566 100644 Binary files a/jzl_workflows/data/qwen2511_05-1.webp and b/jzl_workflows/data/qwen2511_05-1.webp differ diff --git a/jzl_workflows/data/qwen2511_06-1.webp b/jzl_workflows/data/qwen2511_06-1.webp index 3d20c80..2eb2566 100644 Binary files a/jzl_workflows/data/qwen2511_06-1.webp and b/jzl_workflows/data/qwen2511_06-1.webp differ diff --git a/jzl_workflows/data/qwen2511_07-1.webp b/jzl_workflows/data/qwen2511_07-1.webp index 3d20c80..2eb2566 100644 Binary files a/jzl_workflows/data/qwen2511_07-1.webp and b/jzl_workflows/data/qwen2511_07-1.webp differ diff --git a/jzl_workflows/data/qwen2511_08-1.webp b/jzl_workflows/data/qwen2511_08-1.webp index 3d20c80..2eb2566 100644 Binary files a/jzl_workflows/data/qwen2511_08-1.webp and b/jzl_workflows/data/qwen2511_08-1.webp differ diff --git a/jzl_workflows/data/qwen2511_09-1.webp b/jzl_workflows/data/qwen2511_09-1.webp index 3d20c80..2eb2566 100644 Binary files a/jzl_workflows/data/qwen2511_09-1.webp and b/jzl_workflows/data/qwen2511_09-1.webp differ diff --git a/jzl_workflows/data/qwen2511_10-1.webp b/jzl_workflows/data/qwen2511_10-1.webp index 3d20c80..2eb2566 100644 Binary files a/jzl_workflows/data/qwen2511_10-1.webp and b/jzl_workflows/data/qwen2511_10-1.webp differ diff --git a/jzl_workflows/data/qwen2512_01-1.webp b/jzl_workflows/data/qwen2512_01-1.webp index 3d20c80..2eb2566 100644 Binary files a/jzl_workflows/data/qwen2512_01-1.webp and b/jzl_workflows/data/qwen2512_01-1.webp differ diff --git a/jzl_workflows/data/qwen2512_02-1.webp b/jzl_workflows/data/qwen2512_02-1.webp index 3d20c80..2eb2566 100644 Binary files a/jzl_workflows/data/qwen2512_02-1.webp and b/jzl_workflows/data/qwen2512_02-1.webp differ diff --git a/jzl_workflows/data/qwen2512_03-1.webp b/jzl_workflows/data/qwen2512_03-1.webp index 3d20c80..2eb2566 100644 Binary files a/jzl_workflows/data/qwen2512_03-1.webp and b/jzl_workflows/data/qwen2512_03-1.webp differ diff --git a/jzl_workflows/data/qwen2512_04-1.webp b/jzl_workflows/data/qwen2512_04-1.webp index 3d20c80..2eb2566 100644 Binary files a/jzl_workflows/data/qwen2512_04-1.webp and b/jzl_workflows/data/qwen2512_04-1.webp differ diff --git a/jzl_workflows/data/qwen2512_05-1.webp b/jzl_workflows/data/qwen2512_05-1.webp index 3d20c80..2eb2566 100644 Binary files a/jzl_workflows/data/qwen2512_05-1.webp and b/jzl_workflows/data/qwen2512_05-1.webp differ diff --git a/jzl_workflows/data/qwen2512_06-1.webp b/jzl_workflows/data/qwen2512_06-1.webp index 3d20c80..2eb2566 100644 Binary files a/jzl_workflows/data/qwen2512_06-1.webp and b/jzl_workflows/data/qwen2512_06-1.webp differ diff --git a/jzl_workflows/data/zimage_01-1.webp b/jzl_workflows/data/zimage_01-1.webp index 3d20c80..2eb2566 100644 Binary files a/jzl_workflows/data/zimage_01-1.webp and b/jzl_workflows/data/zimage_01-1.webp differ diff --git a/jzl_workflows/data/zimage_02-1.webp b/jzl_workflows/data/zimage_02-1.webp index 3d20c80..2eb2566 100644 Binary files a/jzl_workflows/data/zimage_02-1.webp and b/jzl_workflows/data/zimage_02-1.webp differ diff --git a/jzl_workflows/data/zimage_03-1.webp b/jzl_workflows/data/zimage_03-1.webp index 3d20c80..2eb2566 100644 Binary files a/jzl_workflows/data/zimage_03-1.webp and b/jzl_workflows/data/zimage_03-1.webp differ diff --git a/jzl_workflows/data/zimage_04-1.webp b/jzl_workflows/data/zimage_04-1.webp index 3d20c80..2eb2566 100644 Binary files a/jzl_workflows/data/zimage_04-1.webp and b/jzl_workflows/data/zimage_04-1.webp differ diff --git a/jzl_workflows/data/zimage_05-1.webp b/jzl_workflows/data/zimage_05-1.webp index 3d20c80..2eb2566 100644 Binary files a/jzl_workflows/data/zimage_05-1.webp and b/jzl_workflows/data/zimage_05-1.webp differ diff --git a/jzl_workflows/data/zimage_06-1.webp b/jzl_workflows/data/zimage_06-1.webp index 3d20c80..2eb2566 100644 Binary files a/jzl_workflows/data/zimage_06-1.webp and b/jzl_workflows/data/zimage_06-1.webp differ diff --git a/jzl_workflows/data/zimage_10-1.webp b/jzl_workflows/data/zimage_10-1.webp index 3d20c80..2eb2566 100644 Binary files a/jzl_workflows/data/zimage_10-1.webp and b/jzl_workflows/data/zimage_10-1.webp differ diff --git a/web/xb_audio_slicer.js b/web/xb_audio_slicer.js index afe7f91..43baf52 100644 --- a/web/xb_audio_slicer.js +++ b/web/xb_audio_slicer.js @@ -32,7 +32,7 @@ app.registerExtension({ const el = wDur.inputEl || wDur.element; if (el) { el.readOnly = true; - el.style.backgroundColor = "#1a1a1a"; + el.style.backgroundColor = "var(--xb-bg)"; el.style.color = "#00E676"; el.style.textAlign = "center"; el.style.fontWeight = "bold"; diff --git a/web/xb_audio_slicer_v1.js b/web/xb_audio_slicer_v1.js index 65edf6d..13b9b65 100644 --- a/web/xb_audio_slicer_v1.js +++ b/web/xb_audio_slicer_v1.js @@ -31,7 +31,7 @@ app.registerExtension({ const el = wDur.inputEl || wDur.element; if (el) { el.readOnly = true; - el.style.cssText = "background-color:#1a1a1a;color:#00E676;text-align:center;font-weight:bold;font-size:15px;min-width:120px;white-space:nowrap;overflow:hidden;"; + el.style.cssText = "background-color:var(--xb-bg);color:#00E676;text-align:center;font-weight:bold;font-size:15px;min-width:120px;white-space:nowrap;overflow:hidden;"; } }, 100); } @@ -56,14 +56,14 @@ app.registerExtension({ } const ctr = document.createElement("div"); - Object.assign(ctr.style, { display:"flex", flexDirection:"column", gap:"6px", width:"100%", padding:"6px", boxSizing:"border-box", background:"#161616", borderRadius:"6px", color:"#ccc", fontFamily:"sans-serif", marginTop:"4px" }); + Object.assign(ctr.style, { display:"flex", flexDirection:"column", gap:"6px", width:"100%", padding:"6px", boxSizing:"border-box", background:"#161616", borderRadius:"6px", color:"var(--xb-text)", fontFamily:"sans-serif", marginTop:"4px" }); const audioEl = document.createElement("audio"); audioEl.controls = true; audioEl.style.cssText = "width:100%;height:34px;outline:none;"; ctr.appendChild(audioEl); const canvas = document.createElement("canvas"); - canvas.style.cssText = "width:100%;height:40px;border-radius:4px;background:#111;cursor:pointer;display:block;"; + canvas.style.cssText = "width:100%;height:40px;border-radius:4px;background:var(--xb-inset);cursor:pointer;display:block;"; const dpr = window.devicePixelRatio || 1; canvas.width = 1200 * dpr; canvas.height = 80 * dpr; ctr.appendChild(canvas); diff --git a/web/xb_audio_slicer_v2.js b/web/xb_audio_slicer_v2.js index 4e756a7..d10d26c 100644 --- a/web/xb_audio_slicer_v2.js +++ b/web/xb_audio_slicer_v2.js @@ -30,7 +30,7 @@ app.registerExtension({ // Nodes 2.0:显示型字段走 options.read_only(Vue 部件没有 element 可改) if (wTot && wTot.options) wTot.options.read_only = true; - if (wTot) setTimeout(() => { const el = wTot.inputEl || wTot.element; if (el) { el.readOnly = true; el.style.cssText = "background-color:#1a1a1a;color:#00E676;text-align:center;font-weight:bold;font-size:14px;min-width:120px;white-space:nowrap;overflow:hidden;"; } }, 100); + if (wTot) setTimeout(() => { const el = wTot.inputEl || wTot.element; if (el) { el.readOnly = true; el.style.cssText = "background-color:var(--xb-bg);color:#00E676;text-align:center;font-weight:bold;font-size:14px;min-width:120px;white-space:nowrap;overflow:hidden;"; } }, 100); const audioEl1 = document.createElement("audio"); audioEl1.controls = true; audioEl1.style.cssText = "width:100%;height:30px;outline:none;"; const audioEl2 = document.createElement("audio"); audioEl2.controls = true; audioEl2.style.cssText = "width:100%;height:30px;outline:none;"; @@ -39,7 +39,7 @@ app.registerExtension({ let drags = { 1: null, 2: null }; // 存储双轨的拖动状态 const ctr = document.createElement("div"); - Object.assign(ctr.style, { display:"flex", flexDirection:"column", gap:"4px", width:"100%", padding:"6px", boxSizing:"border-box", background:"#161616", borderRadius:"6px", color:"#ccc", fontFamily:"sans-serif", marginTop:"4px" }); + Object.assign(ctr.style, { display:"flex", flexDirection:"column", gap:"4px", width:"100%", padding:"6px", boxSizing:"border-box", background:"#161616", borderRadius:"6px", color:"var(--xb-text)", fontFamily:"sans-serif", marginTop:"4px" }); const mkUpload = (w, label) => { const fi = document.createElement("input"); fi.type = "file"; fi.accept = "audio/*,video/*"; fi.style.display = "none"; @@ -59,12 +59,12 @@ app.registerExtension({ const dpr = window.devicePixelRatio || 1; const label1 = document.createElement("div"); label1.textContent = "🎵 音频1"; label1.style.cssText = "font-size:11px;color:#4FC3F7;font-weight:bold;"; ctr.appendChild(label1); ctr.appendChild(audioEl1); - const canvas1 = document.createElement("canvas"); canvas1.style.cssText = "width:100%;height:36px;border-radius:3px;background:#111;cursor:pointer;display:block;"; + const canvas1 = document.createElement("canvas"); canvas1.style.cssText = "width:100%;height:36px;border-radius:3px;background:var(--xb-inset);cursor:pointer;display:block;"; canvas1.width = 1200 * dpr; canvas1.height = 72 * dpr; ctr.appendChild(canvas1); const label2 = document.createElement("div"); label2.textContent = "🎵 音频2"; label2.style.cssText = "font-size:11px;color:#F44336;font-weight:bold;"; ctr.appendChild(label2); ctr.appendChild(audioEl2); - const canvas2 = document.createElement("canvas"); canvas2.style.cssText = "width:100%;height:36px;border-radius:3px;background:#111;cursor:pointer;display:block;"; + const canvas2 = document.createElement("canvas"); canvas2.style.cssText = "width:100%;height:36px;border-radius:3px;background:var(--xb-inset);cursor:pointer;display:block;"; canvas2.width = 1200 * dpr; canvas2.height = 72 * dpr; ctr.appendChild(canvas2); const domWidget = node.addDOMWidget("xb3_ui", "custom", ctr); diff --git a/web/xb_audio_slicer_v3.js b/web/xb_audio_slicer_v3.js index 7762b31..4208158 100644 --- a/web/xb_audio_slicer_v3.js +++ b/web/xb_audio_slicer_v3.js @@ -31,7 +31,7 @@ app.registerExtension({ [w.mutes1_data, w.mutes2_data].forEach(wd => { hideWidget(node, wd); }); if (w.total_display) { if (w.total_display.options) w.total_display.options.read_only = true; // Nodes 2.0 - setTimeout(() => { const el = w.total_display.inputEl || w.total_display.element; if (el) { el.readOnly = true; el.style.cssText = "background-color:#1a1a1a;color:#00E676;text-align:center;font-weight:bold;font-size:14px;border:none;"; } }, 200); + setTimeout(() => { const el = w.total_display.inputEl || w.total_display.element; if (el) { el.readOnly = true; el.style.cssText = "background-color:var(--xb-bg);color:#00E676;text-align:center;font-weight:bold;font-size:14px;border:none;"; } }, 200); } // 轨道状态机 diff --git a/web/xb_compat.js b/web/xb_compat.js index ba6385a..857d3d6 100644 --- a/web/xb_compat.js +++ b/web/xb_compat.js @@ -270,7 +270,7 @@ export function ensureBanner(node, name, text, minH = 30) { el.dataset.xbBanner = "1"; el.textContent = text; el.style.cssText = - "padding:6px 8px;border-radius:6px;background:#2b3138;color:#cfd6dd;" + + "padding:6px 8px;border-radius:6px;background:var(--xb-bg-raise);color:var(--xb-text);" + "font-size:12px;line-height:1.4;text-align:center;user-select:none;"; w = node.addDOMWidget(name, name, el, { serialize: false, hideOnZoom: false, getMinHeight: () => minH }); sizeDomWidget(node, w, minH); diff --git a/web/xb_digital_human_dual.js b/web/xb_digital_human_dual.js index 3071541..bc937f5 100644 --- a/web/xb_digital_human_dual.js +++ b/web/xb_digital_human_dual.js @@ -73,7 +73,7 @@ app.registerExtension({ [w.mutes1_data, w.mutes2_data].forEach(wd => { hideWidget(node, wd); }); if (w.total_display) { if (w.total_display.options) w.total_display.options.read_only = true; // Nodes 2.0 - setTimeout(() => { const el = w.total_display.inputEl || w.total_display.element; if (el) { el.readOnly = true; el.style.cssText = "background-color:#1a1a1a;color:#00E676;text-align:center;font-weight:bold;font-size:14px;border:none;"; } }, 200); + setTimeout(() => { const el = w.total_display.inputEl || w.total_display.element; if (el) { el.readOnly = true; el.style.cssText = "background-color:var(--xb-bg);color:#00E676;text-align:center;font-weight:bold;font-size:14px;border:none;"; } }, 200); } // ── 轨道状态机(来自 xb_audio_slicer_v3.js)── diff --git a/web/xb_digital_human_single.js b/web/xb_digital_human_single.js index 48412d6..35ee03b 100644 --- a/web/xb_digital_human_single.js +++ b/web/xb_digital_human_single.js @@ -73,7 +73,7 @@ app.registerExtension({ const el = wDur.inputEl || wDur.element; if (el) { el.readOnly = true; - el.style.cssText = "background-color:#1a1a1a;color:#00E676;text-align:center;font-weight:bold;font-size:15px;min-width:120px;white-space:nowrap;overflow:hidden;"; + el.style.cssText = "background-color:var(--xb-bg);color:#00E676;text-align:center;font-weight:bold;font-size:15px;min-width:120px;white-space:nowrap;overflow:hidden;"; } }, 100); } @@ -100,14 +100,14 @@ app.registerExtension({ // ── 波形可视化 UI(来自 xb_audio_slicer_v1.js)── const ctr = document.createElement("div"); - Object.assign(ctr.style, { display: "flex", flexDirection: "column", gap: "6px", width: "100%", padding: "6px", boxSizing: "border-box", background: "#161616", borderRadius: "6px", color: "#ccc", fontFamily: "sans-serif", marginTop: "4px" }); + Object.assign(ctr.style, { display: "flex", flexDirection: "column", gap: "6px", width: "100%", padding: "6px", boxSizing: "border-box", background: "#161616", borderRadius: "6px", color: "var(--xb-text)", fontFamily: "sans-serif", marginTop: "4px" }); const audioEl = document.createElement("audio"); audioEl.controls = true; audioEl.style.cssText = "width:100%;height:34px;outline:none;"; ctr.appendChild(audioEl); const canvas = document.createElement("canvas"); - canvas.style.cssText = "width:100%;height:40px;border-radius:4px;background:#111;cursor:pointer;display:block;"; + canvas.style.cssText = "width:100%;height:40px;border-radius:4px;background:var(--xb-inset);cursor:pointer;display:block;"; const dpr = window.devicePixelRatio || 1; canvas.width = 1200 * dpr; canvas.height = 80 * dpr; ctr.appendChild(canvas); diff --git a/web/xb_fast_toggle.js b/web/xb_fast_toggle.js index f897e29..9ce0ba3 100644 --- a/web/xb_fast_toggle.js +++ b/web/xb_fast_toggle.js @@ -130,12 +130,12 @@ function mkBtn(text, tip, onClick, css) { b.textContent = text; if (tip) b.title = tip; b.style.cssText = "font-size:11px;line-height:1.2;height:22px;padding:0 8px;border-radius:5px;" - + "border:1px solid #555;background:#2b2b2b;color:#ddd;cursor:pointer;white-space:nowrap;font-family:inherit;" + + "border:1px solid var(--xb-border);background:var(--xb-bg-raise);color:var(--xb-text);cursor:pointer;white-space:nowrap;font-family:inherit;" + (css || ""); b.addEventListener("pointerdown", (e) => e.stopPropagation()); b.addEventListener("click", (e) => { e.preventDefault(); e.stopPropagation(); onClick?.(e); }); - b.addEventListener("mouseenter", () => { b.style.background = "#3a3a3a"; }); - b.addEventListener("mouseleave", () => { b.style.background = "#2b2b2b"; }); + b.addEventListener("mouseenter", () => { b.style.background = "var(--xb-border)"; }); + b.addEventListener("mouseleave", () => { b.style.background = "var(--xb-bg-raise)"; }); return b; } @@ -250,14 +250,14 @@ function openPicker(node, kind) { + "display:flex;overflow:auto;"; const panel = document.createElement("div"); panel.style.cssText = "width:660px;max-width:94vw;max-height:86vh;margin:auto;flex:0 0 auto;display:flex;flex-direction:column;" - + "background:#1c1c1e;border:1px solid #333;border-radius:8px;color:#ddd;font:13px/1.55 system-ui,sans-serif;" + + "background:var(--xb-bg);border:1px solid var(--xb-border);border-radius:8px;color:var(--xb-text);font:13px/1.55 system-ui,sans-serif;" + "box-shadow:0 20px 40px rgba(0,0,0,.6);overflow:hidden;"; const head = document.createElement("div"); - head.style.cssText = "padding:12px 16px;background:#232326;border-bottom:1px solid #3a3a3a;display:flex;align-items:center;gap:10px;"; + head.style.cssText = "padding:12px 16px;background:var(--xb-bg-soft);border-bottom:1px solid var(--xb-border);display:flex;align-items:center;gap:10px;"; const hTitle = document.createElement("div"); hTitle.textContent = isGroups ? "⚙️ 选择要控制的分组" : "⚙️ 选择要控制的节点"; - hTitle.style.cssText = "flex:1 1 auto;font-size:15px;font-weight:700;color:#eee;"; + hTitle.style.cssText = "flex:1 1 auto;font-size:15px;font-weight:700;color:var(--xb-text);"; const hClose = mkBtn("✕", "关闭", () => close()); head.append(hTitle, hClose); @@ -266,10 +266,10 @@ function openPicker(node, kind) { const search = document.createElement("input"); search.type = "text"; search.placeholder = isGroups ? "搜索分组…" : "搜索节点名 / 类型…"; - search.style.cssText = "flex:1 1 220px;min-width:160px;height:26px;background:#1e1e1e;color:#ddd;" - + "border:1px solid #444;border-radius:5px;padding:0 8px;font-size:12px;font-family:inherit;"; + search.style.cssText = "flex:1 1 220px;min-width:160px;height:26px;background:var(--xb-bg);color:var(--xb-text);" + + "border:1px solid var(--xb-border);border-radius:5px;padding:0 8px;font-size:12px;font-family:inherit;"; const countEl = document.createElement("span"); - countEl.style.cssText = "color:#8a8a8a;font-size:12px;font-variant-numeric:tabular-nums;"; + countEl.style.cssText = "color:var(--xb-text-dim);font-size:12px;font-variant-numeric:tabular-nums;"; const hintEl = document.createElement("span"); hintEl.textContent = "⠿ 拖拽可调整顺序"; hintEl.style.cssText = "color:#6f7883;font-size:11px;"; @@ -279,7 +279,7 @@ function openPicker(node, kind) { list.style.cssText = "flex:1 1 auto;min-height:0;overflow-y:auto;padding:8px 12px;display:flex;flex-direction:column;gap:3px;"; const foot = document.createElement("div"); - foot.style.cssText = "padding:10px 16px;border-top:1px solid #3a3a3a;display:flex;justify-content:flex-end;gap:8px;flex-wrap:wrap;background:#18181a;"; + foot.style.cssText = "padding:10px 16px;border-top:1px solid var(--xb-border);display:flex;justify-content:flex-end;gap:8px;flex-wrap:wrap;background:var(--xb-bg-soft);"; const close = () => { try { overlay.remove(); } catch (_) { /* ignore */ } }; @@ -319,7 +319,7 @@ function openPicker(node, kind) { ghost.setAttribute("data-xb-ghost", "1"); ghost.style.cssText += `;position:fixed;left:${srcRect.left}px;top:${srcRect.top}px;width:${srcRect.width}px;` + "box-sizing:border-box;z-index:10360;pointer-events:none;margin:0;" - + "background:#33343a;border:1px solid #f59e0b;border-radius:6px;" + + "background:var(--xb-bg-raise);border:1px solid #f59e0b;border-radius:6px;" + "box-shadow:0 14px 30px rgba(0,0,0,.65),0 0 0 1px rgba(245,158,11,.25);" + "transform:scale(1.02);opacity:.98;"; document.body.append(ghost); @@ -348,7 +348,7 @@ function openPicker(node, kind) { panel.style.height = ""; src.style.opacity = "1"; src.style.borderStyle = "solid"; - src.style.borderColor = "#333"; + src.style.borderColor = "var(--xb-border)"; }; const onMove = (e) => { if (!dragKey) return; @@ -411,7 +411,7 @@ function openPicker(node, kind) { shown++; const row = document.createElement("label"); row.style.cssText = "display:flex;align-items:center;gap:8px;padding:5px 8px;border-radius:5px;cursor:pointer;" - + "background:#242424;border:1px solid #333;"; + + "background:var(--xb-bg-raise);border:1px solid var(--xb-border);"; row.dataset.xbRow = it.key; const grip = document.createElement("span"); grip.textContent = "⠿"; @@ -444,7 +444,7 @@ function openPicker(node, kind) { if (!shown) { const empty = document.createElement("div"); empty.textContent = isGroups ? "当前画布上没有分组" : "没有匹配的节点"; - empty.style.cssText = "padding:16px;color:#8a8a8a;text-align:center;"; + empty.style.cssText = "padding:16px;color:var(--xb-text-dim);text-align:center;"; list.append(empty); } syncCount(); @@ -481,7 +481,7 @@ function openPicker(node, kind) { }; const cancel = mkBtn("取消", "放弃修改", close); - const ok = mkBtn("确定", "保存选择", confirm, "border-color:#3f86d0;background:#22456b;color:#eaf4ff;font-weight:600;"); + const ok = mkBtn("确定", "保存选择", confirm, "border-color:#3f86d0;background:#22456b;color:var(--xb-on-accent);font-weight:600;"); foot.append( mkBtn("全选", "选中当前筛选结果里的全部条目", () => selectAll(true)), mkBtn("清空", "取消全部选择", () => selectAll(false)), @@ -502,7 +502,7 @@ function buildPanel(node, kind) { const isGroups = kind === "groups"; const container = document.createElement("div"); container.style.cssText = "width:100%;height:100%;box-sizing:border-box;display:flex;flex-direction:column;" - + `gap:4px;padding:${PANEL_PAD / 2}px;overflow:visible;background:#2b2b2b;border-radius:6px;`; + + `gap:4px;padding:${PANEL_PAD / 2}px;overflow:visible;background:var(--xb-bg-raise);border-radius:6px;`; // 节点表面只留「开关列表」——选择 / 禁用方式 / 切换模式 全部收进右键菜单 const list = document.createElement("div"); @@ -615,7 +615,7 @@ function toggleRow(node, index) { function buildSwitch(enabled, disabled) { const box = document.createElement("span"); box.style.cssText = "flex:0 0 auto;position:relative;width:34px;height:18px;border-radius:9px;" - + `background:${disabled ? "#3a3a3a" : enabled ? "#3f86d0" : "#4a4a4a"};` + + `background:${disabled ? "var(--xb-border)" : enabled ? "#3f86d0" : "var(--xb-border)"};` + "transition:background .15s;"; const knob = document.createElement("span"); knob.style.cssText = "position:absolute;top:2px;width:14px;height:14px;border-radius:7px;background:#fff;" @@ -676,7 +676,7 @@ function refreshRows(node) { ? "还没有选择分组 —— " : "还没有选择节点 —— ") + "右击本节点 →「" + (node.type === TYPE_GROUPS ? "选择分组" : "选择节点") + "…」勾选。"; - tip.style.cssText = "padding:10px;color:#8a8a8a;font-size:11px;line-height:1.6;"; + tip.style.cssText = "padding:10px;color:var(--xb-text-dim);font-size:11px;line-height:1.6;"; list.append(tip); applyPanelHeight(node, true); return; @@ -689,7 +689,7 @@ function refreshRows(node) { const el = document.createElement("div"); el.style.cssText = "display:flex;align-items:center;gap:8px;padding:3px 6px;border-radius:5px;" + `background:${enabled ? "#22456b" : "#1f1f1f"};` - + `border:1px solid ${enabled ? "#3f86d0" : "#3a3a3a"};` + + `border:1px solid ${enabled ? "#3f86d0" : "var(--xb-border)"};` + "cursor:pointer;"; const name = document.createElement("span"); @@ -707,7 +707,7 @@ function refreshRows(node) { if (!ok) { // 永远保持一个开启:拒绝全关 → 闪一下提示 el.style.borderColor = "#e05555"; - setTimeout(() => { try { el.style.borderColor = enabled ? "#3f86d0" : "#3a3a3a"; } catch (_) { /* ignore */ } }, 450); + setTimeout(() => { try { el.style.borderColor = enabled ? "#3f86d0" : "var(--xb-border)"; } catch (_) { /* ignore */ } }, 450); } }); el.addEventListener("mouseenter", () => { el.style.filter = "brightness(1.18)"; }); diff --git a/web/xb_gallery_picker.js b/web/xb_gallery_picker.js index 4e0b647..e4a1dbc 100644 --- a/web/xb_gallery_picker.js +++ b/web/xb_gallery_picker.js @@ -10,7 +10,7 @@ * · 普通图片文件夹(无清单):按文件名多宫格 + 搜索 + 排序 + 加载更多; * · 单选(一个端口一张图)→ 回填节点内部字段 pose_ref / outfit_ref。 * - * 视觉沿用本包弹窗风格(#1c1c1e / 880px / 圆角 8px)。 + * 视觉沿用本包弹窗风格(var(--xb-bg) / 880px / 圆角 8px)。 */ import { api } from "../../scripts/api.js"; @@ -164,25 +164,25 @@ function shell(title) { const mask = el("div", "position:fixed;inset:0;z-index:10000;background:rgba(0,0,0,.78);" + "display:flex;overflow:auto;box-sizing:border-box;"); const card = el("div", "margin:auto;flex:0 0 auto;display:flex;flex-direction:column;" - + "background:#1c1c1e;border:1px solid #333;border-radius:8px;color:#ddd;" + + "background:var(--xb-bg);border:1px solid var(--xb-border);border-radius:8px;color:var(--xb-text);" + "font-family:system-ui,sans-serif;line-height:1.65;box-shadow:0 20px 40px rgba(0,0,0,.6);overflow:hidden;"); - const head = el("div", "padding:14px 18px 12px;background:#232326;border-bottom:1px solid #3a3a3a;" + const head = el("div", "padding:14px 18px 12px;background:var(--xb-bg-soft);border-bottom:1px solid var(--xb-border);" + "display:flex;align-items:center;gap:12px;flex-wrap:wrap;"); - head.append(el("span", "flex:1 1 320px;min-width:200px;font-size:1.2em;font-weight:700;color:#eee;", title)); + head.append(el("span", "flex:1 1 320px;min-width:200px;font-size:1.2em;font-weight:700;color:var(--xb-text);", title)); // 顶部常驻条(放「禁用」勾选框等,切换视图时不会被清空) - const top = el("div", "padding:8px 18px;background:#202024;border-bottom:1px solid #333;" + const top = el("div", "padding:8px 18px;background:var(--xb-bg);border-bottom:1px solid var(--xb-border);" + "display:flex;align-items:center;gap:12px;flex-wrap:wrap;"); top.hidden = true; const body = el("div", "padding:14px 18px 16px;display:flex;flex-direction:column;gap:10px;overflow:auto;min-height:0;"); - const foot = el("div", "padding:10px 18px;border-top:1px solid #3a3a3a;background:#18181a;" + const foot = el("div", "padding:10px 18px;border-top:1px solid var(--xb-border);background:var(--xb-bg-soft);" + "display:flex;align-items:center;gap:10px;flex-wrap:wrap;"); // 顶部工具条:字号 A−/A+ | 界面 −/%/+ | ✕ const tools = el("span", "display:flex;align-items:center;gap:8px;flex-wrap:wrap;font-weight:normal;"); - const fontVal = el("span", "min-width:26px;text-align:center;color:#e8a87c;font-variant-numeric:tabular-nums;", String(fontGet())); - const zoomVal = el("span", "min-width:46px;text-align:center;color:#ffd98a;font-variant-numeric:tabular-nums;", + const fontVal = el("span", "min-width:26px;text-align:center;color:var(--xb-orange);font-variant-numeric:tabular-nums;", String(fontGet())); + const zoomVal = el("span", "min-width:46px;text-align:center;color:var(--xb-warn);font-variant-numeric:tabular-nums;", Math.round(zoomGet() * 100) + "%"); const refreshVals = () => { fontVal.textContent = String(fontGet()); @@ -193,30 +193,30 @@ function shell(title) { applyScale(card); try { card.dispatchEvent(new Event("xb-scale")); } catch (_) {} }; - const fontMinus = mkBtn("A−", "border:1px solid #5b9bd5;background:#2a4a6a;color:#cfe3f7;height:1.9em;padding:0 .55em;", () => { + const fontMinus = mkBtn("A−", "border:1px solid #5b9bd5;background:var(--xb-tint-info);color:var(--xb-info);height:1.9em;padding:0 .55em;", () => { fontSet(fontGet() - 1); scaleNow(); refreshVals(); }); - const fontPlus = mkBtn("A+", "border:1px solid #5b9bd5;background:#2a4a6a;color:#cfe3f7;height:1.9em;padding:0 .55em;", () => { + const fontPlus = mkBtn("A+", "border:1px solid #5b9bd5;background:var(--xb-tint-info);color:var(--xb-info);height:1.9em;padding:0 .55em;", () => { fontSet(fontGet() + 1); scaleNow(); refreshVals(); }); - const zoomMinus = mkBtn("−", "border:1px solid #8a6d3b;background:#5a4a2a;color:#ffd98a;height:1.9em;padding:0 .6em;", () => { + const zoomMinus = mkBtn("−", "border:1px solid #8a6d3b;background:var(--xb-tint-warn);color:var(--xb-warn);height:1.9em;padding:0 .6em;", () => { zoomSet(zoomGet() - ZOOM_STEP); scaleNow(); refreshVals(); }); - const zoomPlus = mkBtn("+", "border:1px solid #8a6d3b;background:#5a4a2a;color:#ffd98a;height:1.9em;padding:0 .6em;", () => { + const zoomPlus = mkBtn("+", "border:1px solid #8a6d3b;background:var(--xb-tint-warn);color:var(--xb-warn);height:1.9em;padding:0 .6em;", () => { zoomSet(zoomGet() + ZOOM_STEP); scaleNow(); refreshVals(); }); tools.append( - el("span", "color:#8a8a8a;font-size:.9em;", "字号"), fontMinus, fontVal, fontPlus, + el("span", "color:var(--xb-text-dim);font-size:.9em;", "字号"), fontMinus, fontVal, fontPlus, el("span", "color:#5a5a5a;", "|"), - el("span", "color:#8a8a8a;font-size:.9em;", "界面"), zoomMinus, zoomVal, zoomPlus, + el("span", "color:var(--xb-text-dim);font-size:.9em;", "界面"), zoomMinus, zoomVal, zoomPlus, ); head.append(tools); @@ -235,7 +235,7 @@ function shell(title) { scaleNow(); refreshVals(); }; - tools.append(mkBtn("✕", "border:1px solid #555;background:#2b2b2b;color:#ddd;height:1.9em;padding:0 .7em;", close)); + tools.append(mkBtn("✕", "border:1px solid var(--xb-border);background:var(--xb-bg-raise);color:var(--xb-text);height:1.9em;padding:0 .7em;", close)); card.append(head, top, body, foot); mask.append(card); @@ -290,23 +290,23 @@ export async function openRefPicker({ kind, current = "", onPick } = {}) { disableBox.type = "checkbox"; disableBox.checked = disabled; disableBox.id = `xb-ref-disable-${kind}`; - const disableText = el("span", "color:#ddd;cursor:pointer;user-select:none;", `🚫 禁用此参考图(不做任何输出)`); + const disableText = el("span", "color:var(--xb-text);cursor:pointer;user-select:none;", `🚫 禁用此参考图(不做任何输出)`); const disableWrap = el("label", "display:flex;align-items:center;gap:8px;cursor:pointer;padding:2px 0;"); disableWrap.append(disableBox, disableText); const HINT_OFF = "勾选后本参考图不做任何输出(等同 mute):接在它下游的节点会被直接跳过,不执行也不报错。"; const HINT_ON = `已禁用:${kindName}图不做任何输出,下游节点会被跳过。取消勾选即恢复。`; - const disableHint = el("span", "color:#8a8a8a;font-size:.88em;", HINT_OFF); + const disableHint = el("span", "color:var(--xb-text-dim);font-size:.88em;", HINT_OFF); ui.top.append(disableWrap, disableHint); ui.top.hidden = false; disableBox.addEventListener("change", () => { disabled = disableBox.checked; - disableText.style.color = disabled ? "#ffb74d" : "#ddd"; - disableHint.style.color = disabled ? "#d9a441" : "#8a8a8a"; + disableText.style.color = disabled ? "#ffb74d" : "var(--xb-text)"; + disableHint.style.color = disabled ? "#d9a441" : "var(--xb-text-dim)"; disableHint.textContent = disabled ? HINT_ON : HINT_OFF; emit(null, false); // 立即写回节点(不关窗,方便接着选图) }); disableHint.textContent = disabled ? HINT_ON : HINT_OFF; - disableText.style.color = disabled ? "#ffb74d" : "#ddd"; + disableText.style.color = disabled ? "#ffb74d" : "var(--xb-text)"; // ── ① 绑定界面(未绑定 / 未确认时强制先绑定) ────────────────── async function showBinding(settings, message = "") { @@ -314,22 +314,22 @@ export async function openRefPicker({ kind, current = "", onPick } = {}) { ui.foot.replaceChildren(); const cfg = (settings && settings[kind]) || {}; - ui.body.append(el("div", "color:#ffb74d;line-height:1.75;white-space:pre-wrap;", + ui.body.append(el("div", "color:var(--xb-warn);line-height:1.75;white-space:pre-wrap;", `⚠️ 首次使用需手动绑定${kind === "pose" ? "姿态" : "穿搭"}参考图库目录。\n` + "· 整套图库:选择包含 data / img / thumbs 的目录" + "(目录里有 data/*.json 素材清单即可自动识别)\n" + "· 普通文件夹:任意装图片的目录即可(按文件名多宫格显示)\n" + "· 本节点不内置、不分发任何图库素材;请自行确认素材的合法来源与使用授权。")); - const pathInput = el("input", "flex:1 1 320px;min-width:220px;height:2em;background:#1e1e1e;color:#ddd;" - + "border:1px solid #444;border-radius:4px;padding:0 8px;font-size:1em;box-sizing:border-box;"); + const pathInput = el("input", "flex:1 1 320px;min-width:220px;height:2em;background:var(--xb-bg);color:var(--xb-text);" + + "border:1px solid var(--xb-border);border-radius:4px;padding:0 8px;font-size:1em;box-sizing:border-box;"); pathInput.type = "text"; pathInput.value = cfg.directory || ""; pathInput.placeholder = "填写 ComfyUI 所在电脑上的绝对路径"; const status = el("div", "color:#e07070;min-height:1.4em;white-space:pre-wrap;", message); - const browse = mkBtn("📂 浏览本机目录", "border:1px solid #555;background:#2b2b2b;color:#ddd;height:2.2em;padding:0 12px;", async () => { + const browse = mkBtn("📂 浏览本机目录", "border:1px solid var(--xb-border);background:var(--xb-bg-raise);color:var(--xb-text);height:2.2em;padding:0 12px;", async () => { try { const resp = await api.fetchApi("/xb_toolbox/choose_folder", { method: "POST" }); const data = await resp.json(); @@ -357,7 +357,7 @@ export async function openRefPicker({ kind, current = "", onPick } = {}) { agreeRow.append(agree, el("span", "", "我已确认合法获得该图库的使用权,并自行承担素材使用与合规责任(本插件只读取本机文件,不上传、不分发)。")); - const save = mkBtn("✅ 检查并绑定", "background:#2d5a88;color:#fff;border:none;height:2.2em;padding:0 18px;font-weight:600;", async () => { + const save = mkBtn("✅ 检查并绑定", "background:var(--xb-btn-primary);color:#fff;border:none;height:2.2em;padding:0 18px;font-weight:600;", async () => { save.disabled = true; status.style.color = "#bbb"; status.textContent = "正在检查目录…"; @@ -381,12 +381,12 @@ export async function openRefPicker({ kind, current = "", onPick } = {}) { }); ui.body.append(row, agreeRow, status); - ui.foot.append(el("span", "flex:1 1 auto;color:#8a8a8a;", "绑定信息只保存在本机 ComfyUI 用户目录,不写进工作流。"), save); + ui.foot.append(el("span", "flex:1 1 auto;color:var(--xb-text-dim);", "绑定信息只保存在本机 ComfyUI 用户目录,不写进工作流。"), save); } // ── ② 图库界面 ──────────────────────────────────────────────── async function showGallery(settings, message = "") { - ui.body.replaceChildren(el("div", "color:#bbb;", "正在读取图库…")); + ui.body.replaceChildren(el("div", "color:var(--xb-text-dim);", "正在读取图库…")); ui.foot.replaceChildren(); let cat; @@ -406,19 +406,19 @@ export async function openRefPicker({ kind, current = "", onPick } = {}) { // ── 筛选栏 ── const bar = el("div", "display:flex;gap:8px;align-items:center;flex-wrap:wrap;"); - const search = el("input", "flex:1 1 240px;min-width:160px;height:2em;background:#1e1e1e;color:#ddd;" - + "border:1px solid #444;border-radius:4px;padding:0 8px;font-size:1em;box-sizing:border-box;"); + const search = el("input", "flex:1 1 240px;min-width:160px;height:2em;background:var(--xb-bg);color:var(--xb-text);" + + "border:1px solid var(--xb-border);border-radius:4px;padding:0 8px;font-size:1em;box-sizing:border-box;"); search.type = "search"; search.placeholder = kind === "pose" ? "搜索姿势、编号、体系、分类、角度…" : "搜索款式、颜色、关键词、编号…"; bar.append(search); const makeSelect = (caption, values, allLabel, onChange) => { - const select = el("select", "height:2em;background:#1e1e1e;color:#ddd;border:1px solid #444;" + const select = el("select", "height:2em;background:var(--xb-bg);color:var(--xb-text);border:1px solid var(--xb-border);" + "border-radius:4px;padding:0 6px;font-size:1em;"); select.add(new Option(allLabel, "")); for (const value of values) select.add(new Option(value, value)); select.addEventListener("change", () => onChange(select.value)); - const wrap = el("label", "display:flex;gap:6px;align-items:center;color:#aaa;white-space:nowrap;"); + const wrap = el("label", "display:flex;gap:6px;align-items:center;color:var(--xb-text-dim);white-space:nowrap;"); wrap.append(el("span", "", caption), select); return { wrap, select }; }; @@ -451,7 +451,7 @@ export async function openRefPicker({ kind, current = "", onPick } = {}) { if ((facets.tag || []).length) bar.append(tagSel.wrap); } else { bar.append(catSel.wrap); - const sortSel = el("select", "height:2em;background:#1e1e1e;color:#ddd;border:1px solid #444;" + const sortSel = el("select", "height:2em;background:var(--xb-bg);color:var(--xb-text);border:1px solid var(--xb-border);" + "border-radius:4px;padding:0 6px;font-size:1em;"); sortSel.add(new Option("文件名 ↑", "name")); sortSel.add(new Option("文件名 ↓", "name-desc")); @@ -459,7 +459,7 @@ export async function openRefPicker({ kind, current = "", onPick } = {}) { bar.append(sortSel); } - bar.append(mkBtn("重置筛选", "border:1px solid #555;background:transparent;color:#ccc;height:2em;padding:0 10px;", () => { + bar.append(mkBtn("重置筛选", "border:1px solid var(--xb-border);background:transparent;color:var(--xb-text);height:2em;padding:0 10px;", () => { Object.assign(state, { query: "", system: "", category: "", angle: "", tag: "", collection: "all" }); search.value = ""; sysSel.select.value = ""; @@ -474,7 +474,7 @@ export async function openRefPicker({ kind, current = "", onPick } = {}) { const tabButtons = []; if (isLibrary) { for (const [key, text] of [["all", "全部"], ["favorites", "♡ 收藏"], ["recent", "最近使用"]]) { - const btn = mkBtn(text, "border:1px solid #555;background:#242424;color:#ccc;height:2em;padding:0 10px;", () => { + const btn = mkBtn(text, "border:1px solid var(--xb-border);background:var(--xb-bg-raise);color:var(--xb-text);height:2em;padding:0 10px;", () => { state.collection = key; render(); }); @@ -483,7 +483,7 @@ export async function openRefPicker({ kind, current = "", onPick } = {}) { tabs.append(btn); } if (canGroup) { - tabs.append(mkBtn("切换分组 / 逐张", "border:1px solid #555;background:transparent;color:#bbb;height:2em;padding:0 10px;", () => { + tabs.append(mkBtn("切换分组 / 逐张", "border:1px solid var(--xb-border);background:transparent;color:var(--xb-text-dim);height:2em;padding:0 10px;", () => { state.grouped = !state.grouped; render(); })); @@ -492,11 +492,11 @@ export async function openRefPicker({ kind, current = "", onPick } = {}) { // 不能分组的库(没有姿势编号)不显示「切换分组」按钮,state.grouped 一并置 false if (!canGroup) state.grouped = false; - const summary = el("div", "color:#9fc3e8;font-size:.95em;"); + const summary = el("div", "color:var(--xb-info);font-size:.95em;"); const notice = el("div", "color:#e07070;min-height:1.2em;"); notice.textContent = message || ""; const grid = el("div", "display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:10px;align-content:start;"); - const more = mkBtn("加载更多", "border:1px dashed #555;background:transparent;color:#bbb;height:2.2em;grid-column:1/-1;", () => { + const more = mkBtn("加载更多", "border:1px dashed var(--xb-border);background:transparent;color:var(--xb-text-dim);height:2.2em;grid-column:1/-1;", () => { state.shown += PAGE; render(false); }); @@ -514,10 +514,10 @@ export async function openRefPicker({ kind, current = "", onPick } = {}) { // ── 卡片 ── function makeCard(item, groupItems) { - const card = el("div", "position:relative;border:2px solid " + (selected?.id === item.id ? "#5b9bd5" : "#333") - + ";border-radius:6px;overflow:hidden;background:#222;display:flex;flex-direction:column;"); + const card = el("div", "position:relative;border:2px solid " + (selected?.id === item.id ? "#5b9bd5" : "var(--xb-border)") + + ";border-radius:6px;overflow:hidden;background:var(--xb-inset);display:flex;flex-direction:column;"); // 缩略图用 contain:姿势/服装都要看全,绝不裁切;背景压深色让留白不刺眼 - const img = el("img", "width:100%;height:9.6em;object-fit:contain;display:block;background:#101012;cursor:zoom-in;"); + const img = el("img", "width:100%;height:9.6em;object-fit:contain;display:block;background:var(--xb-bg);cursor:zoom-in;"); img.loading = "lazy"; img.decoding = "async"; img.alt = item.name; @@ -537,9 +537,9 @@ export async function openRefPicker({ kind, current = "", onPick } = {}) { } const meta = el("div", "padding:6px 8px;font-size:.86em;line-height:1.5;overflow:hidden;"); - meta.append(el("div", "color:#8fd3ff;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;", item.name)); + meta.append(el("div", "color:var(--xb-info);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;", item.name)); const sub = [item.system, item.category, item.angle].filter(Boolean).join(" · "); - if (sub) meta.append(el("div", "color:#8a8a8a;font-size:.92em;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;", sub)); + if (sub) meta.append(el("div", "color:var(--xb-text-dim);font-size:.92em;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;", sub)); const actions = el("div", "display:flex;gap:5px;padding:0 8px 7px;"); const pick = mkBtn(selected?.id === item.id ? "✓ 已选" : "选择", @@ -549,7 +549,7 @@ export async function openRefPicker({ kind, current = "", onPick } = {}) { render(false); }); const fav = mkBtn(favorites().includes(item.id) ? "♥" : "♡", - "flex:0 0 auto;border:1px solid #555;background:transparent;color:#e89;height:1.9em;font-size:.8em;padding:0 8px;", () => { + "flex:0 0 auto;border:1px solid var(--xb-border);background:transparent;color:#e89;height:1.9em;font-size:.8em;padding:0 8px;", () => { const list = favorites(); writeLocal(`favorites.${kind}`, list.includes(item.id) ? list.filter((id) => id !== item.id) @@ -567,9 +567,9 @@ export async function openRefPicker({ kind, current = "", onPick } = {}) { // 视角条只用单字(正/斜/侧/俯/仰)+ 悬停全名:卡片变窄或字号放大时也绝不换行挤压 const angleBtn = mkBtn(angle.slice(0, 1), "flex:1 1 0;min-width:0;font-size:.78em;line-height:1.5;height:1.9em;padding:0;" + "white-space:nowrap;overflow:hidden;" - + "border:1px solid " + (isCurrent ? "#5b9bd5" : "#444") + ";" - + "background:" + (target ? (isCurrent ? "#2d5a88" : "#2a2a2a") : "transparent") + ";" - + "color:" + (target ? "#ddd" : "#555") + ";", () => { + + "border:1px solid " + (isCurrent ? "#5b9bd5" : "var(--xb-border)") + ";" + + "background:" + (target ? (isCurrent ? "#2d5a88" : "var(--xb-bg-raise)") : "transparent") + ";" + + "color:" + (target ? "var(--xb-text)" : "var(--xb-border)") + ";", () => { if (target) preview(target, groupItems); }); angleBtn.title = target ? `${angle} · 点击打开大图` : `${angle} 暂无素材`; @@ -583,7 +583,7 @@ export async function openRefPicker({ kind, current = "", onPick } = {}) { // ── 大图预览 ── function preview(item, groupItems) { const view = shell(`预览 · ${item.name}`); - const stage = el("div", "display:flex;justify-content:center;align-items:center;background:#111;" + const stage = el("div", "display:flex;justify-content:center;align-items:center;background:var(--xb-inset);" + "border-radius:6px;overflow:hidden;padding:4px;box-sizing:border-box;"); // ⚠️ 只给 max-width / max-height + object-fit:contain:浏览器会按原图比例缩放, // 永远显示完整画面、不会被拉伸变形;两处上限都用**布局像素**(视口高度 ÷ 界面缩放), @@ -614,7 +614,7 @@ export async function openRefPicker({ kind, current = "", onPick } = {}) { [value.system, value.category, value.angle].filter(Boolean).join(" · "), `ID:${value.id}`, ].filter(Boolean).join("\n"); - const info = el("div", "color:#bbb;white-space:pre-wrap;font-size:.95em;line-height:1.6;"); + const info = el("div", "color:var(--xb-text-dim);white-space:pre-wrap;font-size:.95em;line-height:1.6;"); info.textContent = previewLines(item); let currentItem = item; @@ -624,8 +624,8 @@ export async function openRefPicker({ kind, current = "", onPick } = {}) { if (!isLibrary || !Array.isArray(groupItems) || groupItems.length < 2) return; for (const angle of ANGLE_ORDER) { const target = groupItems.find((value) => value.angle === angle); - const btn = mkBtn(angle, "border:1px solid " + (target && target.id === currentItem.id ? "#5b9bd5" : "#444") - + ";background:" + (target ? "#2a2a2a" : "transparent") + ";color:" + (target ? "#ddd" : "#555") + const btn = mkBtn(angle, "border:1px solid " + (target && target.id === currentItem.id ? "#5b9bd5" : "var(--xb-border)") + + ";background:" + (target ? "var(--xb-bg-raise)" : "transparent") + ";color:" + (target ? "var(--xb-text)" : "var(--xb-border)") + ";height:1.9em;padding:0 10px;white-space:nowrap;", () => { if (!target) return; currentItem = target; @@ -640,13 +640,13 @@ export async function openRefPicker({ kind, current = "", onPick } = {}) { paintAngles(); view.body.append(stage, info, angles); - const use = mkBtn("使用此图", "background:#2d5a88;color:#fff;border:none;height:2.2em;padding:0 18px;font-weight:600;", () => { + const use = mkBtn("使用此图", "background:var(--xb-btn-primary);color:#fff;border:none;height:2.2em;padding:0 18px;font-weight:600;", () => { selected = currentItem; currentPick = currentItem; view.close(); render(false); }); - view.foot.append(el("span", "flex:1 1 auto;color:#8a8a8a;", "Esc 关闭"), use); + view.foot.append(el("span", "flex:1 1 auto;color:var(--xb-text-dim);", "Esc 关闭"), use); } // ── 渲染 ── @@ -686,9 +686,9 @@ export async function openRefPicker({ kind, current = "", onPick } = {}) { for (const btn of tabButtons) { const active = btn.dataset.collection === state.collection; - btn.style.background = active ? "#2d5a88" : "#242424"; - btn.style.color = active ? "#fff" : "#ccc"; - btn.style.borderColor = active ? "#5b9bd5" : "#555"; + btn.style.background = active ? "#2d5a88" : "var(--xb-bg-raise)"; + btn.style.color = active ? "#fff" : "var(--xb-text)"; + btn.style.borderColor = active ? "#5b9bd5" : "var(--xb-border)"; } paintFooter(); } @@ -697,24 +697,24 @@ export async function openRefPicker({ kind, current = "", onPick } = {}) { function paintFooter() { ui.foot.replaceChildren(); if (selected) { - const thumb = el("img", "width:46px;height:46px;object-fit:contain;border-radius:4px;border:1px solid #444;background:#101012;flex:0 0 auto;"); + const thumb = el("img", "width:46px;height:46px;object-fit:contain;border-radius:4px;border:1px solid var(--xb-border);background:var(--xb-bg);flex:0 0 auto;"); thumb.src = imageUrl(kind, selected, true); const label = (selected.group ? `[${selected.group}] ` : "") + selected.name; - const text = el("span", "flex:1 1 auto;color:#ddd;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;", label); + const text = el("span", "flex:1 1 auto;color:var(--xb-text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;", label); text.title = label; ui.foot.append(thumb, text); } else { - ui.foot.append(el("span", "flex:1 1 auto;color:#8a8a8a;", "点卡片上的「选择」或大图里的「使用此图」")); + ui.foot.append(el("span", "flex:1 1 auto;color:var(--xb-text-dim);", "点卡片上的「选择」或大图里的「使用此图」")); } const use = mkBtn(disabled ? "✅ 保存(当前已禁用)" : (selected ? "✅ 使用此图" : "请先选一张"), - "background:#2d5a88;color:#fff;border:none;height:2.2em;padding:0 18px;font-weight:600;", () => { + "background:var(--xb-btn-primary);color:#fff;border:none;height:2.2em;padding:0 18px;font-weight:600;", () => { if (!selected) return; const list = recent(); writeLocal(`recent.${kind}`, [selected.id, ...list.filter((id) => id !== selected.id)].slice(0, 80)); emit(selected, true); }); use.disabled = !selected; - const rebind = mkBtn("🔧 更换图库目录", "border:1px solid #555;background:transparent;color:#bbb;height:2.2em;padding:0 10px;", () => { + const rebind = mkBtn("🔧 更换图库目录", "border:1px solid var(--xb-border);background:transparent;color:var(--xb-text-dim);height:2.2em;padding:0 10px;", () => { req("/settings") .then((data) => showBinding(data.settings)) .catch((err) => { notice.textContent = err?.message || String(err); }); diff --git a/web/xb_image_prompt_preset.js b/web/xb_image_prompt_preset.js index dbd3b7b..c1baabd 100644 --- a/web/xb_image_prompt_preset.js +++ b/web/xb_image_prompt_preset.js @@ -1453,15 +1453,15 @@ function el(tag, css, text) { return e; } -const BOX_CSS = "background:#1d1d1d;color:#ccc;border:1px solid #444;border-radius:4px;padding:5px 8px;font-size:12px;box-sizing:border-box;font-family:inherit;"; +const BOX_CSS = "background:var(--xb-bg);color:var(--xb-text);border:1px solid var(--xb-border);border-radius:4px;padding:5px 8px;font-size:12px;box-sizing:border-box;font-family:inherit;"; function makeSectionTitle(text) { - return el("div", "font-size:13px;font-weight:600;color:#9fc3e8;margin:14px 0 8px;padding-bottom:5px;border-bottom:1px solid #333;", text); + return el("div", "font-size:13px;font-weight:600;color:var(--xb-info);margin:14px 0 8px;padding-bottom:5px;border-bottom:1px solid var(--xb-border);", text); } function field(labelText, control) { const g = el("div", "display:flex;align-items:center;gap:12px;margin-bottom:10px;"); - const lab = el("label", "flex:0 0 160px;font-size:13px;color:#999;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;", labelText); + const lab = el("label", "flex:0 0 160px;font-size:13px;color:var(--xb-text-dim);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;", labelText); control.style.flex = "1 1 0"; control.style.minWidth = "0"; g.append(lab, control); @@ -1488,7 +1488,7 @@ function radioRow(options, value, onChange) { for (const o of options) { const val = (typeof o === "object" && o !== null && o.value !== undefined) ? String(o.value) : String(o); const label = (typeof o === "object" && o !== null && o.label) ? String(o.label) : String(o); - const lab = el("label", "display:flex;align-items:center;gap:6px;font-size:13px;color:#999;cursor:pointer;user-select:none;"); + const lab = el("label", "display:flex;align-items:center;gap:6px;font-size:13px;color:var(--xb-text-dim);cursor:pointer;user-select:none;"); const c = el("input", "width:18px;height:18px;accent-color:#f59e0b;cursor:pointer;"); c.type = "checkbox"; c.checked = String(value) === val; @@ -1515,7 +1515,7 @@ function textareaControl(value, onChange, css) { } function checkboxControl(value, labelText, onChange, tip) { - const w = el("label", "display:flex;align-items:center;gap:8px;cursor:pointer;font-size:12px;color:#ddd;user-select:none;"); + const w = el("label", "display:flex;align-items:center;gap:8px;cursor:pointer;font-size:12px;color:var(--xb-text);user-select:none;"); const c = el("input", "width:16px;height:16px;accent-color:#f59e0b;cursor:pointer;"); c.type = "checkbox"; c.checked = !!value; @@ -1577,10 +1577,10 @@ function zoomSync(root) { } function buildZoomRow(root) { const row = el("span", "display:inline-flex;align-items:center;gap:4px;user-select:none;"); - const minus = smallBtn("A−", "border:1px solid #5b9bd5;background:#2a4a6a;color:#cfe3f7;width:22px;height:22px;padding:0;line-height:1;", "缩小编辑字号(Ctrl/⌘+滚轮 也可)"); - const val = el("span", "min-width:26px;text-align:center;font-size:12px;color:#e8a87c;font-variant-numeric:tabular-nums;", String(zoomDisplay())); + const minus = smallBtn("A−", "border:1px solid #5b9bd5;background:var(--xb-tint-info);color:var(--xb-info);width:22px;height:22px;padding:0;line-height:1;", "缩小编辑字号(Ctrl/⌘+滚轮 也可)"); + const val = el("span", "min-width:26px;text-align:center;font-size:12px;color:var(--xb-orange);font-variant-numeric:tabular-nums;", String(zoomDisplay())); val.classList.add("ipp-zoom-val"); - const plus = smallBtn("A+", "border:1px solid #5b9bd5;background:#2a4a6a;color:#cfe3f7;width:22px;height:22px;padding:0;line-height:1;", "放大编辑字号(Ctrl/⌘+滚轮 也可)"); + const plus = smallBtn("A+", "border:1px solid #5b9bd5;background:var(--xb-tint-info);color:var(--xb-info);width:22px;height:22px;padding:0;line-height:1;", "放大编辑字号(Ctrl/⌘+滚轮 也可)"); const step = (d) => { zoomSet((zoomGet() == null ? 12 : zoomGet()) + d); zoomApplyContainer(root); @@ -1624,10 +1624,10 @@ window.addEventListener("resize", () => { function buildUIRow(target) { const row = el("span", "display:inline-flex;align-items:center;gap:4px;user-select:none;"); row.append(el("span", "font-size:11px;color:#888;white-space:nowrap;", "界面")); - const minus = smallBtn("−", "border:1px solid #8a6d3b;background:#5a4a2a;color:#ffd98a;width:22px;height:22px;padding:0;line-height:1;font-size:13px;", "缩小整个界面(Ctrl/⌘+滚轮 也可)"); - const val = el("span", "min-width:42px;text-align:center;font-size:12px;color:#ffd98a;font-variant-numeric:tabular-nums;", Math.round(uiGet() * 100) + "%"); + const minus = smallBtn("−", "border:1px solid #8a6d3b;background:var(--xb-tint-warn);color:var(--xb-warn);width:22px;height:22px;padding:0;line-height:1;font-size:13px;", "缩小整个界面(Ctrl/⌘+滚轮 也可)"); + const val = el("span", "min-width:42px;text-align:center;font-size:12px;color:var(--xb-warn);font-variant-numeric:tabular-nums;", Math.round(uiGet() * 100) + "%"); val.classList.add("ipp-ui-val"); - const plus = smallBtn("+", "border:1px solid #8a6d3b;background:#5a4a2a;color:#ffd98a;width:22px;height:22px;padding:0;line-height:1;font-size:13px;", "放大整个界面(Ctrl/⌘+滚轮 也可)"); + const plus = smallBtn("+", "border:1px solid #8a6d3b;background:var(--xb-tint-warn);color:var(--xb-warn);width:22px;height:22px;padding:0;line-height:1;font-size:13px;", "放大整个界面(Ctrl/⌘+滚轮 也可)"); const step = (d) => { uiSet(uiGet() + d * UI_STEP); uiApply(target); @@ -1889,12 +1889,12 @@ function openPanel(ctx, panelId) { const title = `${cat.label} 元素`; const overlay = el("div", "position:fixed;inset:0;background:rgba(0,0,0,.78);z-index:9999;display:flex;align-items:center;justify-content:center;overflow:auto;"); - const dialog = el("section", "background:#1c1c1e;border:1px solid #333;border-radius:8px;width:880px;max-height:90vh;display:flex;flex-direction:column;box-shadow:0 20px 40px rgba(0,0,0,.6);flex:0 0 auto;color:#ddd;font-size:13px;"); + const dialog = el("section", "background:var(--xb-bg);border:1px solid var(--xb-border);border-radius:8px;width:880px;max-height:90vh;display:flex;flex-direction:column;box-shadow:0 20px 40px rgba(0,0,0,.6);flex:0 0 auto;color:var(--xb-text);font-size:13px;"); - const header = el("div", "padding:16px 20px 12px;border-bottom:1px solid #444;"); - const titleEl = el("div", "font-size:18px;font-weight:700;color:#eee;", title); + const header = el("div", "padding:16px 20px 12px;border-bottom:1px solid var(--xb-border);"); + const titleEl = el("div", "font-size:18px;font-weight:700;color:var(--xb-text);", title); header.append(titleEl); - header.append(el("div", "font-size:12px;color:#999;margin-top:5px;", "点选项行即加入提示词(变蓝),再点一下移除(变灰);点【添加详细描述】会在新窗口里给该词条写补充描述(悬停行可看)。配置随工作流保存,改完重新执行节点即生效。")); + header.append(el("div", "font-size:12px;color:var(--xb-text-dim);margin-top:5px;", "点选项行即加入提示词(变蓝),再点一下移除(变灰);点【添加详细描述】会在新窗口里给该词条写补充描述(悬停行可看)。配置随工作流保存,改完重新执行节点即生效。")); dialog.append(header); // ⚠️ 顶部不留内边距:滚动容器的 padding 区不参与裁剪,滚上去的行会从这条缝里 @@ -1903,12 +1903,12 @@ function openPanel(ctx, panelId) { const panelBox = el("div", "padding:0 20px 14px;overflow-y:auto;flex:1;min-height:280px;"); // ── 吸顶预览框(不受滚动条约束,始终在画面内) ── - const sticky = el("div", "position:sticky;top:0;z-index:6;background:#1c1c1e;padding:16px 0 10px;margin:0 0 6px;border-bottom:1px solid #333;"); + const sticky = el("div", "position:sticky;top:0;z-index:6;background:var(--xb-bg);padding:16px 0 10px;margin:0 0 6px;border-bottom:1px solid var(--xb-border);"); const head = el("div", "display:flex;align-items:center;gap:8px;margin-bottom:6px;flex-wrap:wrap;"); - head.append(el("span", "font-size:12px;font-weight:600;color:#9fc3e8;", "📝 提示词预览(与节点提示词框实时同步,可直接编辑)")); + head.append(el("span", "font-size:12px;font-weight:600;color:var(--xb-info);", "📝 提示词预览(与节点提示词框实时同步,可直接编辑)")); const headCount = el("span", "font-size:11px;color:#888;", ""); head.append(headCount); - const presetChip = el("div", "font-size:11px;color:#f0d79a;background:#2a2416;border:1px dashed #8a6d3b;border-radius:4px;padding:4px 8px;margin-bottom:6px;white-space:pre-wrap;line-height:1.5;display:none;"); + const presetChip = el("div", "font-size:11px;color:var(--xb-warn);background:var(--xb-tint-warn);border:1px dashed #8a6d3b;border-radius:4px;padding:4px 8px;margin-bottom:6px;white-space:pre-wrap;line-height:1.5;display:none;"); const preview = el("textarea", BOX_CSS + "width:100%;min-height:74px;max-height:210px;resize:vertical;line-height:1.65;font-size:12px;"); preview.spellcheck = false; preview.placeholder = "点左侧 + 添加元素,或直接在这里手写提示词…"; @@ -1946,10 +1946,10 @@ function openPanel(ctx, panelId) { // ── 底部 ── const error = el("div", "color:#e55;font-size:12px;margin:0 20px;min-height:16px;"); - const footer = el("div", "padding:12px 20px;border-top:1px solid #444;display:flex;justify-content:flex-end;gap:10px;background:#18181a;border-bottom-left-radius:8px;border-bottom-right-radius:8px;"); - const cancelBtn = el("button", "background:transparent;border:1px solid #555;color:#fff;border-radius:4px;padding:8px 20px;font-size:14px;cursor:pointer;font-family:inherit;", "取消"); - const saveBtn = el("button", "background:#2d5a88;color:#fff;border:none;border-radius:4px;padding:8px 20px;font-size:14px;font-weight:600;cursor:pointer;font-family:inherit;", "💾 保存"); - const autoRow = el("label", "display:flex;align-items:center;gap:6px;font-size:13px;color:#999;cursor:pointer;user-select:none;"); + const footer = el("div", "padding:12px 20px;border-top:1px solid var(--xb-border);display:flex;justify-content:flex-end;gap:10px;background:var(--xb-bg-soft);border-bottom-left-radius:8px;border-bottom-right-radius:8px;"); + const cancelBtn = el("button", "background:transparent;border:1px solid var(--xb-border);color:#fff;border-radius:4px;padding:8px 20px;font-size:14px;cursor:pointer;font-family:inherit;", "取消"); + const saveBtn = el("button", "background:var(--xb-btn-primary);color:#fff;border:none;border-radius:4px;padding:8px 20px;font-size:14px;font-weight:600;cursor:pointer;font-family:inherit;", "💾 保存"); + const autoRow = el("label", "display:flex;align-items:center;gap:6px;font-size:13px;color:var(--xb-text-dim);cursor:pointer;user-select:none;"); const autoCb = el("input", "width:18px;height:18px;accent-color:#f59e0b;cursor:pointer;"); autoCb.type = "checkbox"; autoCb.checked = settings.auto_save !== false; @@ -2034,13 +2034,13 @@ const CAT_UI = {}; // 会话级:cat -> { collapsed, onlySel, keyword }(不 * ============================================================ */ function openConfirm(title, message, onOk, okText) { const overlay = el("div", "position:fixed;inset:0;background:rgba(0,0,0,.72);z-index:10200;display:flex;align-items:center;justify-content:center;"); - const box = el("section", "background:#1c1c1e;border:1px solid #333;border-radius:8px;width:460px;max-width:92vw;box-shadow:0 20px 40px rgba(0,0,0,.6);padding:18px;box-sizing:border-box;color:#ddd;font-size:13px;"); + const box = el("section", "background:var(--xb-bg);border:1px solid var(--xb-border);border-radius:8px;width:460px;max-width:92vw;box-shadow:0 20px 40px rgba(0,0,0,.6);padding:18px;box-sizing:border-box;color:var(--xb-text);font-size:13px;"); box.dataset.ippModal = "confirm"; - box.append(el("div", "font-size:15px;font-weight:700;color:#eee;margin-bottom:8px;", title || "确认")); - box.append(el("div", "font-size:13px;color:#bbb;line-height:1.8;white-space:pre-wrap;margin-bottom:14px;", message || "")); + box.append(el("div", "font-size:15px;font-weight:700;color:var(--xb-text);margin-bottom:8px;", title || "确认")); + box.append(el("div", "font-size:13px;color:var(--xb-text-dim);line-height:1.8;white-space:pre-wrap;margin-bottom:14px;", message || "")); const row = el("div", "display:flex;gap:10px;justify-content:flex-end;"); - const cancelBtn = el("button", "background:transparent;border:1px solid #555;color:#fff;border-radius:4px;padding:7px 18px;font-size:13px;cursor:pointer;font-family:inherit;", "取消"); - const okBtn = el("button", "background:#8a3a2a;border:1px solid #c2604a;color:#ffe7df;border-radius:4px;padding:7px 18px;font-size:13px;font-weight:600;cursor:pointer;font-family:inherit;", okText || "确定"); + const cancelBtn = el("button", "background:transparent;border:1px solid var(--xb-border);color:#fff;border-radius:4px;padding:7px 18px;font-size:13px;cursor:pointer;font-family:inherit;", "取消"); + const okBtn = el("button", "background:var(--xb-tint-error);border:1px solid #c2604a;color:#ffe7df;border-radius:4px;padding:7px 18px;font-size:13px;font-weight:600;cursor:pointer;font-family:inherit;", okText || "确定"); const close = () => { try { overlay.remove(); } catch (_) {} }; cancelBtn.addEventListener("click", (e) => { e.stopPropagation(); close(); }); okBtn.addEventListener("click", (e) => { e.stopPropagation(); close(); try { onOk && onOk(); } catch (_) {} }); @@ -2058,29 +2058,29 @@ function openConfirm(title, message, onOk, okText) { * ============================================================ */ function openRelPicker(node, nameText, lang, current, onPick) { const overlay = el("div", "position:fixed;inset:0;background:rgba(0,0,0,.78);z-index:10200;display:flex;align-items:center;justify-content:center;"); - const box = el("section", "background:#1c1c1e;border:1px solid #333;border-radius:8px;width:780px;max-width:94vw;max-height:92vh;overflow:auto;box-shadow:0 20px 40px rgba(0,0,0,.6);padding:18px;box-sizing:border-box;color:#ddd;font-size:13px;"); + const box = el("section", "background:var(--xb-bg);border:1px solid var(--xb-border);border-radius:8px;width:780px;max-width:94vw;max-height:92vh;overflow:auto;box-shadow:0 20px 40px rgba(0,0,0,.6);padding:18px;box-sizing:border-box;color:var(--xb-text);font-size:13px;"); box.dataset.ippModal = "rel"; - box.append(el("div", "font-size:15px;font-weight:700;color:#eee;margin-bottom:4px;", "🎒 道具与主体的关系")); - box.append(el("div", "font-size:12px;color:#8a97a5;margin-bottom:12px;line-height:1.7;", + box.append(el("div", "font-size:15px;font-weight:700;color:var(--xb-text);margin-bottom:4px;", "🎒 道具与主体的关系")); + box.append(el("div", "font-size:12px;color:var(--xb-text-dim);margin-bottom:12px;line-height:1.7;", `道具:${nameText}\n选一个关系词,拼进提示词时它会加在道具前面(例:手拿折扇 / a folding fan, held in the hand)。`)); const grid = el("div", "display:grid;grid-template-columns:repeat(6,1fr);gap:6px;margin-bottom:14px;"); const close = () => { try { overlay.remove(); } catch (_) {} }; for (const [id, zh, en] of RELATIONS) { const on = id === current; - const b = el("button", `display:flex;flex-direction:column;align-items:center;justify-content:center;gap:2px;min-height:46px;border-radius:6px;cursor:pointer;font-family:inherit;padding:4px 2px;box-sizing:border-box;border:1px solid ${on ? "#7fb3e0" : "#4a4a4a"};background:${on ? "#2d5a88" : "#262626"};color:${on ? "#eaf4ff" : "#ddd"};`); + const b = el("button", `display:flex;flex-direction:column;align-items:center;justify-content:center;gap:2px;min-height:46px;border-radius:6px;cursor:pointer;font-family:inherit;padding:4px 2px;box-sizing:border-box;border:1px solid ${on ? "#7fb3e0" : "var(--xb-border)"};background:${on ? "#2d5a88" : "var(--xb-bg-raise)"};color:${on ? "#eaf4ff" : "var(--xb-text)"};`); b.dataset.rel = id; b.title = `${zh} / ${en}`; b.append(el("span", "font-size:13px;line-height:1.2;", zh)); b.append(el("span", "font-size:9px;line-height:1.2;color:#9fb6cc;text-align:center;word-break:break-word;", en)); - b.addEventListener("mouseenter", () => { if (!on) b.style.background = "#333"; }); - b.addEventListener("mouseleave", () => { if (!on) b.style.background = "#262626"; }); + b.addEventListener("mouseenter", () => { if (!on) b.style.background = "var(--xb-border)"; }); + b.addEventListener("mouseleave", () => { if (!on) b.style.background = "var(--xb-bg-raise)"; }); b.addEventListener("click", (e) => { e.stopPropagation(); close(); try { onPick && onPick(id); } catch (_) {} }); grid.append(b); } box.append(grid); const row = el("div", "display:flex;gap:10px;justify-content:flex-end;"); - const noneBtn = el("button", "background:#2a4a6a;border:1px solid #5b9bd5;color:#cfe3f7;border-radius:4px;padding:7px 16px;font-size:13px;cursor:pointer;font-family:inherit;", "不加关系(只加道具词)"); - const cancelBtn = el("button", "background:transparent;border:1px solid #555;color:#fff;border-radius:4px;padding:7px 18px;font-size:13px;cursor:pointer;font-family:inherit;", "取消"); + const noneBtn = el("button", "background:var(--xb-tint-info);border:1px solid #5b9bd5;color:var(--xb-info);border-radius:4px;padding:7px 16px;font-size:13px;cursor:pointer;font-family:inherit;", "不加关系(只加道具词)"); + const cancelBtn = el("button", "background:transparent;border:1px solid var(--xb-border);color:#fff;border-radius:4px;padding:7px 18px;font-size:13px;cursor:pointer;font-family:inherit;", "取消"); noneBtn.addEventListener("click", (e) => { e.stopPropagation(); close(); try { onPick && onPick(""); } catch (_) {} }); cancelBtn.addEventListener("click", (e) => { e.stopPropagation(); close(); }); overlay.addEventListener("mousedown", (e) => { if (e.target === overlay) close(); }); @@ -2098,10 +2098,10 @@ function openRelPicker(node, nameText, lang, current, onPick) { * ============================================================ */ function openDescEditor(node, entry, nameText, value, onSave) { const overlay = el("div", "position:fixed;inset:0;background:rgba(0,0,0,.72);z-index:10100;display:flex;align-items:center;justify-content:center;"); - const box = el("section", "background:#1c1c1e;border:1px solid #333;border-radius:8px;width:600px;max-width:92vw;box-shadow:0 20px 40px rgba(0,0,0,.6);padding:18px;box-sizing:border-box;color:#ddd;font-size:13px;"); + const box = el("section", "background:var(--xb-bg);border:1px solid var(--xb-border);border-radius:8px;width:600px;max-width:92vw;box-shadow:0 20px 40px rgba(0,0,0,.6);padding:18px;box-sizing:border-box;color:var(--xb-text);font-size:13px;"); box.dataset.ippModal = "desc"; // 稳定锤点(便于定位/自动化测试) - box.append(el("div", "font-size:15px;font-weight:700;color:#eee;margin-bottom:4px;", "📝 添加详细描述")); - box.append(el("div", "font-size:12px;color:#8a97a5;margin-bottom:10px;line-height:1.7;white-space:pre-wrap;", + box.append(el("div", "font-size:15px;font-weight:700;color:var(--xb-text);margin-bottom:4px;", "📝 添加详细描述")); + box.append(el("div", "font-size:12px;color:var(--xb-text-dim);margin-bottom:10px;line-height:1.7;white-space:pre-wrap;", `词条:${nameText}\n` + "写在这里的内容会以「词条(描述)」的形式拼进提示词(例:一位女性(银灰色短发))," + "所以只写要补的细节即可 —— 不用重复词条本身,也别写整句话。")); @@ -2113,9 +2113,9 @@ function openDescEditor(node, entry, nameText, value, onSave) { const tip = el("div", "font-size:11px;color:#777;margin-top:6px;", "Esc = 取消 · Ctrl/⌘ + Enter = 保存"); box.append(tip); const row = el("div", "display:flex;justify-content:flex-end;gap:10px;margin-top:14px;"); - const cancelBtn = el("button", "background:transparent;border:1px solid #555;color:#fff;border-radius:4px;padding:7px 18px;font-size:13px;cursor:pointer;font-family:inherit;", "取消"); - const clrBtn = el("button", "background:transparent;border:1px solid #844;color:#ffb0b0;border-radius:4px;padding:7px 14px;font-size:13px;cursor:pointer;font-family:inherit;margin-right:auto;", "🗑 清除描述"); - const okBtn = el("button", "background:#2d5a88;color:#fff;border:none;border-radius:4px;padding:7px 20px;font-size:13px;font-weight:600;cursor:pointer;font-family:inherit;", "✅ 保存描述"); + const cancelBtn = el("button", "background:transparent;border:1px solid var(--xb-border);color:#fff;border-radius:4px;padding:7px 18px;font-size:13px;cursor:pointer;font-family:inherit;", "取消"); + const clrBtn = el("button", "background:transparent;border:1px solid #844;color:var(--xb-error);border-radius:4px;padding:7px 14px;font-size:13px;cursor:pointer;font-family:inherit;margin-right:auto;", "🗑 清除描述"); + const okBtn = el("button", "background:var(--xb-btn-primary);color:#fff;border:none;border-radius:4px;padding:7px 20px;font-size:13px;font-weight:600;cursor:pointer;font-family:inherit;", "✅ 保存描述"); row.append(clrBtn, cancelBtn, okBtn); box.append(row); overlay.append(box); @@ -2139,11 +2139,11 @@ function openDescEditor(node, entry, nameText, value, onSave) { /** 自建槽位的「中/英名称」编辑窗(同样是一个小弹窗) */ function openSlotNameEditor(zhValue, enValue, onSave) { const overlay = el("div", "position:fixed;inset:0;background:rgba(0,0,0,.72);z-index:10100;display:flex;align-items:center;justify-content:center;"); - const box = el("section", "background:#1c1c1e;border:1px solid #333;border-radius:8px;width:520px;max-width:92vw;box-shadow:0 20px 40px rgba(0,0,0,.6);padding:18px;box-sizing:border-box;color:#ddd;font-size:13px;"); + const box = el("section", "background:var(--xb-bg);border:1px solid var(--xb-border);border-radius:8px;width:520px;max-width:92vw;box-shadow:0 20px 40px rgba(0,0,0,.6);padding:18px;box-sizing:border-box;color:var(--xb-text);font-size:13px;"); box.dataset.ippModal = "slotname"; // 稳定锤点 - box.append(el("div", "font-size:15px;font-weight:700;color:#eee;margin-bottom:10px;", "✎ 自建槽位名称(中 / 英)")); + box.append(el("div", "font-size:15px;font-weight:700;color:var(--xb-text);margin-bottom:10px;", "✎ 自建槽位名称(中 / 英)")); const mk = (label, val, ph) => { - box.append(el("div", "font-size:11px;color:#8a97a5;margin:6px 0 3px;", label)); + box.append(el("div", "font-size:11px;color:var(--xb-text-dim);margin:6px 0 3px;", label)); const i = el("input", BOX_CSS + "width:100%;font-size:13px;padding:6px 8px;"); i.type = "text"; i.value = val || ""; @@ -2154,8 +2154,8 @@ function openSlotNameEditor(zhValue, enValue, onSave) { const zh = mk("中文词条(中文 [ZH] 输出时用)", zhValue, "例:银灰色短发"); const en = mk("English word(英文 [EN] 输出时用)", enValue, "e.g. silver-gray short hair"); const row = el("div", "display:flex;justify-content:flex-end;gap:10px;margin-top:14px;"); - const cancelBtn = el("button", "background:transparent;border:1px solid #555;color:#fff;border-radius:4px;padding:7px 18px;font-size:13px;cursor:pointer;font-family:inherit;", "取消"); - const okBtn = el("button", "background:#2d5a88;color:#fff;border:none;border-radius:4px;padding:7px 20px;font-size:13px;font-weight:600;cursor:pointer;font-family:inherit;", "✅ 保存名称"); + const cancelBtn = el("button", "background:transparent;border:1px solid var(--xb-border);color:#fff;border-radius:4px;padding:7px 18px;font-size:13px;cursor:pointer;font-family:inherit;", "取消"); + const okBtn = el("button", "background:var(--xb-btn-primary);color:#fff;border:none;border-radius:4px;padding:7px 20px;font-size:13px;font-weight:600;cursor:pointer;font-family:inherit;", "✅ 保存名称"); row.append(cancelBtn, okBtn); box.append(row); overlay.append(box); @@ -2190,12 +2190,12 @@ function renderCategoryPanel(box, api, catId) { catRow.dataset.ippCatRow = "1"; for (const c of CATEGORIES) { const on = c.id === catId; - const b = el("button", `box-sizing:border-box;height:30px;display:flex;align-items:center;justify-content:center;border-radius:6px;border:2px solid ${on ? "#7fb3e0" : "#5b9bd5"};background:${on ? "#2d5a88" : "#3a3a3a"};color:${on ? "#eaf4ff" : "#eee"};font-size:13px;cursor:pointer;font-family:inherit;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;`, c.label); + const b = el("button", `box-sizing:border-box;height:30px;display:flex;align-items:center;justify-content:center;border-radius:6px;border:2px solid ${on ? "#7fb3e0" : "#5b9bd5"};background:${on ? "#2d5a88" : "var(--xb-border)"};color:${on ? "#eaf4ff" : "var(--xb-text)"};font-size:13px;cursor:pointer;font-family:inherit;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;`, c.label); b.dataset.cat = c.id; if (on) b.dataset.catActive = "1"; b.title = `切到「${c.label}」面板 —— ${c.tip}`; - b.addEventListener("mouseenter", () => { if (!on) b.style.background = "#4a4a4a"; }); - b.addEventListener("mouseleave", () => { if (!on) b.style.background = "#3a3a3a"; }); + b.addEventListener("mouseenter", () => { if (!on) b.style.background = "var(--xb-border)"; }); + b.addEventListener("mouseleave", () => { if (!on) b.style.background = "var(--xb-border)"; }); b.addEventListener("click", (e) => { e.stopPropagation(); if (!on && api.switchCategory) api.switchCategory(c.id); @@ -2205,7 +2205,7 @@ function renderCategoryPanel(box, api, catId) { box.append(catRow); const bar = el("div", "display:flex;gap:8px;flex-wrap:wrap;margin-bottom:6px;"); bar.append( - smallBtn("🧹 清空提示词", "border:1px solid #a44;background:#7a2a2a;color:#ffd0d0;padding:6px 12px;", "清空正文(同时清掉所有元素的「已选」状态)", () => { + smallBtn("🧹 清空提示词", "border:1px solid #a44;background:var(--xb-tint-error);color:var(--xb-error);padding:6px 12px;", "清空正文(同时清掉所有元素的「已选」状态)", () => { openConfirm("🧹 清空提示词", "确定清空提示词正文?\n(所有元素的「已选」状态也会一起清掉)", () => { s.elements.selected = {}; s.elements.rel = {}; @@ -2215,17 +2215,17 @@ function renderCategoryPanel(box, api, catId) { notify("已清空提示词", "success"); }); }), - smallBtn("🔧 按选择重建", "border:1px solid #5b9bd5;background:#2a4a6a;color:#cfe3f7;padding:6px 12px;", "按「已选元素 + 固定分类顺序(风格→视角→主体→姿态→装扮→道具→光影→背景)+ 当前语言」重排正文(手工改乱了就用它)", () => { + smallBtn("🔧 按选择重建", "border:1px solid #5b9bd5;background:var(--xb-tint-info);color:var(--xb-info);padding:6px 12px;", "按「已选元素 + 固定分类顺序(风格→视角→主体→姿态→装扮→道具→光影→背景)+ 当前语言」重排正文(手工改乱了就用它)", () => { const t = rebuildBody(s, index, lang()); bodySet(t); fire(); notify(t ? "已按已选元素重建正文" : "当前没有已选元素(重建后正文为空)", t ? "success" : "warning"); }), - smallBtn("📋 复制成句", "border:1px solid #555;background:#2a2a2a;color:#ccc;padding:6px 12px;", "复制最终成句(选中有预设的档位时含预设句)", () => { + smallBtn("📋 复制成句", "border:1px solid var(--xb-border);background:var(--xb-bg-raise);color:var(--xb-text);padding:6px 12px;", "复制最终成句(选中有预设的档位时含预设句)", () => { copyText(finalPrompt(surf.mode, surf.presetText, surf.lang, bodyGet(), surf.io)); }), - smallBtn("⬆️ 导出词表", "border:1px solid #6b9b5b;background:#3a5a2a;color:#d7f0c8;padding:6px 12px;", "把「自建槽位 + 已选 + 描述」导出成 JSON 备份", () => exportJson(s)), - smallBtn("⬇️ 导入词表", "border:1px solid #6b9b5b;background:#3a5a2a;color:#d7f0c8;padding:6px 12px;", "从 JSON 恢复「自建槽位 + 已选 + 描述」(合并,不覆盖内置词表)", () => importJson((obj) => { + smallBtn("⬆️ 导出词表", "border:1px solid #6b9b5b;background:var(--xb-tint-ok);color:var(--xb-ok);padding:6px 12px;", "把「自建槽位 + 已选 + 描述」导出成 JSON 备份", () => exportJson(s)), + smallBtn("⬇️ 导入词表", "border:1px solid #6b9b5b;background:var(--xb-tint-ok);color:var(--xb-ok);padding:6px 12px;", "从 JSON 恢复「自建槽位 + 已选 + 描述」(合并,不覆盖内置词表)", () => importJson((obj) => { if (!obj || typeof obj !== "object") return; for (const k of ["custom", "noted", "selected"]) { if (obj[k] && typeof obj[k] === "object") s.elements[k] = Object.assign({}, s.elements[k], obj[k]); @@ -2257,7 +2257,7 @@ function renderCategoryPanel(box, api, catId) { // 436 条词条只在第一次渲染时建 DOM;标题栏仍可点一下收起(纯会话状态) if (!CAT_UI[cat.id]) CAT_UI[cat.id] = { collapsed: false, onlySel: false, keyword: "", sub: "" }; const ui = CAT_UI[cat.id]; - const wrap = el("div", "border:1px solid #333;border-radius:6px;margin-bottom:8px;background:#191919;overflow:hidden;"); + const wrap = el("div", "border:1px solid var(--xb-border);border-radius:6px;margin-bottom:8px;background:var(--xb-inset);overflow:hidden;"); const selOf = () => (s.elements.selected[cat.id] || []); const selArr = () => (s.elements.selected[cat.id] || (s.elements.selected[cat.id] = [])); const rebuild = () => { wrap.innerHTML = ""; build(); }; @@ -2332,14 +2332,14 @@ function renderCategoryPanel(box, api, catId) { const cur = subOf(cat, ui.sub).id; for (const c of chipRefs) { const on = c.dataset.sub === cur; - c.style.background = on ? "#2d5a88" : "#2a2a2a"; - c.style.borderColor = on ? "#7fb3e0" : "#555"; + c.style.background = on ? "#2d5a88" : "var(--xb-bg-raise)"; + c.style.borderColor = on ? "#7fb3e0" : "var(--xb-border)"; c.style.color = on ? "#eaf4ff" : "#bbb"; c.style.fontWeight = on ? "600" : "400"; } }; for (const sub of (cat.subs || [])) { - const b = el("button", "border:1px solid #555;border-radius:14px;padding:4px 14px;font-size:12px;cursor:pointer;font-family:inherit;white-space:nowrap;", `${sub.label}(${(sub.keys || []).length})`); + const b = el("button", "border:1px solid var(--xb-border);border-radius:14px;padding:4px 14px;font-size:12px;cursor:pointer;font-family:inherit;white-space:nowrap;", `${sub.label}(${(sub.keys || []).length})`); b.dataset.sub = sub.id; b.title = `切到「${sub.label}」子类(${(sub.keys || []).length} 条)`; b.addEventListener("mousedown", (e) => e.stopPropagation()); @@ -2389,7 +2389,7 @@ function renderCategoryPanel(box, api, catId) { fire(); notify(n ? `已加入 ${n} 条` : "这些词条都已在提示词里", n ? "success" : "warning"); }; - tb.append(smallBtn("+ 全部加入", "border:1px solid #5b9bd5;background:#2a4a6a;color:#cfe3f7;", "把本分类当前可见的词条全部追加进提示词", () => { + tb.append(smallBtn("+ 全部加入", "border:1px solid #5b9bd5;background:var(--xb-tint-info);color:var(--xb-info);", "把本分类当前可见的词条全部追加进提示词", () => { const list = visibleEntries(); if (!list.length) { notify("没有可加入的词条", "warning"); return; } if (list.length > 60) { @@ -2398,7 +2398,7 @@ function renderCategoryPanel(box, api, catId) { } addAllNow(list); })); - tb.append(smallBtn("🗑 全部移除", "border:1px solid #a44;background:#5a2a2a;color:#ffd0d0;", "把本分类已选词条全部从提示词移除", () => { + tb.append(smallBtn("🗑 全部移除", "border:1px solid #a44;background:var(--xb-tint-error);color:var(--xb-error);", "把本分类已选词条全部从提示词移除", () => { const keys = selOf().slice(); if (!keys.length) { notify("本分类还没有已选词条", "warning"); return; } removeKeys(keys); @@ -2428,7 +2428,7 @@ function renderCategoryPanel(box, api, catId) { const selected = selOf().includes(entry.key); const descKey = `${cat.id}|${entry.key}`; const desc = s.elements.noted[descKey] || ""; - const row = el("div", `display:flex;align-items:center;gap:8px;padding:5px 8px;border-radius:5px;margin-bottom:3px;box-sizing:border-box;cursor:pointer;user-select:none;background:${selected ? "#22456b" : "#242424"};border:1px solid ${selected ? "#3f86d0" : "#3a3a3a"};`); + const row = el("div", `display:flex;align-items:center;gap:8px;padding:5px 8px;border-radius:5px;margin-bottom:3px;box-sizing:border-box;cursor:pointer;user-select:none;background:${selected ? "#22456b" : "var(--xb-bg-raise)"};border:1px solid ${selected ? "#3f86d0" : "var(--xb-border)"};`); // 悬停整行 → 显示完整词条、当前状态与详细描述(原生 tooltip) row.title = `${entry.zh || "(空)"} / ${entry.en || "(空)"}` + (entry.custom ? "\n(自建槽位)" : "") @@ -2447,14 +2447,14 @@ function renderCategoryPanel(box, api, catId) { renderRows(); fire(); }); - if (entry.custom) row.append(el("span", "font-size:10px;color:#f0d79a;border:1px solid #8a6d3b;border-radius:3px;padding:0 3px;flex:0 0 auto;", "自建")); + if (entry.custom) row.append(el("span", "font-size:10px;color:var(--xb-warn);border:1px solid #8a6d3b;border-radius:3px;padding:0 3px;flex:0 0 auto;", "自建")); const rel = relPair(s, cat.id, entry.key); if (rel) row.append(el("span", "font-size:10px;color:#9fe0c0;border:1px solid #3f7a5f;border-radius:3px;padding:0 3px;flex:0 0 auto;", rel.zh)); const nm = el("span", `flex:1 1 auto;min-width:0;font-size:12px;line-height:1.4;color:${selected ? "#eaf4ff" : "#c8c8c8"};overflow:hidden;text-overflow:ellipsis;white-space:nowrap;`, `${selected ? "✔ " : ""}${entry.zh || "(空)"} / ${entry.en || "(空)"}`); row.append(nm); if (cat.id === "props") { - row.append(smallBtn(rel ? `🔗 ${rel.zh}` : "🔗 关系", "border:1px solid #5b9bd5;background:#2a4a6a;color:#cfe3f7;padding:4px 10px;flex:0 0 auto;", "选择/更换这个道具与主体的关系词(先移旧片段再加新片段)", () => { + row.append(smallBtn(rel ? `🔗 ${rel.zh}` : "🔗 关系", "border:1px solid #5b9bd5;background:var(--xb-tint-info);color:var(--xb-info);padding:4px 10px;flex:0 0 auto;", "选择/更换这个道具与主体的关系词(先移旧片段再加新片段)", () => { openRelPicker(api.node, `${entry.zh || entry.key} / ${entry.en || ""}`, lang(), (relPair(s, cat.id, entry.key) || {}).id || "", (relId) => { @@ -2466,7 +2466,7 @@ function renderCategoryPanel(box, api, catId) { })); } const descBtn = smallBtn(desc ? "📝 详细描述" : "添加详细描述", - `border:1px solid ${desc ? "#8a6d3b" : "#555"};background:${desc ? "#3a2f16" : "#2a2a2a"};color:${desc ? "#ffd98a" : "#9fc3e8"};padding:4px 10px;flex:0 0 auto;`, + `border:1px solid ${desc ? "#8a6d3b" : "var(--xb-border)"};background:${desc ? "#3a2f16" : "var(--xb-bg-raise)"};color:${desc ? "#ffd98a" : "#9fc3e8"};padding:4px 10px;flex:0 0 auto;`, desc ? "编辑该词条的详细描述(新窗口)" : "为该词条添加详细描述(新窗口)", () => openDescEditor(api.node, entry, `${entry.zh || entry.en || entry.key}`, desc, (text) => { const v = String(text || "").trim(); @@ -2505,7 +2505,7 @@ function renderCategoryPanel(box, api, catId) { const selected = selOf().includes(it.key); const descKey = `${cat.id}|${it.key}`; const desc = s.elements.noted[descKey] || ""; - const row = el("div", `display:flex;align-items:center;gap:8px;padding:5px 8px;border-radius:5px;margin-bottom:3px;box-sizing:border-box;cursor:pointer;user-select:none;background:${selected ? "#22456b" : "#242424"};border:1px solid ${selected ? "#3f86d0" : "#3a3a3a"};`); + const row = el("div", `display:flex;align-items:center;gap:8px;padding:5px 8px;border-radius:5px;margin-bottom:3px;box-sizing:border-box;cursor:pointer;user-select:none;background:${selected ? "#22456b" : "var(--xb-bg-raise)"};border:1px solid ${selected ? "#3f86d0" : "var(--xb-border)"};`); row.title = `${it.zh || "(未填中文)"} / ${it.en || "(未填英文)"}\n(自建槽位)` + `\n${selected ? "已加入提示词 —— 再点一下移除" : "点一下加入提示词"}` + "\n\n详细描述:" + (desc || "(还没有,点【添加详细描述】写一条)"); @@ -2515,10 +2515,10 @@ function renderCategoryPanel(box, api, catId) { rebuild(); fire(); }); - row.append(el("span", "font-size:10px;color:#f0d79a;border:1px solid #8a6d3b;border-radius:3px;padding:0 3px;flex:0 0 auto;", "自建")); + row.append(el("span", "font-size:10px;color:var(--xb-warn);border:1px solid #8a6d3b;border-radius:3px;padding:0 3px;flex:0 0 auto;", "自建")); row.append(el("span", `flex:1 1 auto;min-width:0;font-size:12px;color:${selected ? "#eaf4ff" : "#c8c8c8"};overflow:hidden;text-overflow:ellipsis;white-space:nowrap;`, `${selected ? "✔ " : ""}${it.zh || "(未填中文)"} / ${it.en || "(未填英文)"}`)); - row.append(smallBtn("✎ 编辑名称", "border:1px solid #555;background:#2a2a2a;color:#9fc3e8;padding:4px 10px;flex:0 0 auto;", "新窗口编辑该槽位的中/英词条", () => { + row.append(smallBtn("✎ 编辑名称", "border:1px solid var(--xb-border);background:var(--xb-bg-raise);color:var(--xb-info);padding:4px 10px;flex:0 0 auto;", "新窗口编辑该槽位的中/英词条", () => { openSlotNameEditor(it.zh, it.en, (z, e2) => { it.zh = z; it.en = e2; if (entry) { entry.zh = z; entry.en = e2; } @@ -2528,7 +2528,7 @@ function renderCategoryPanel(box, api, catId) { }); })); row.append(smallBtn(desc ? "📝 详细描述" : "添加详细描述", - `border:1px solid ${desc ? "#8a6d3b" : "#555"};background:${desc ? "#3a2f16" : "#2a2a2a"};color:${desc ? "#ffd98a" : "#9fc3e8"};padding:4px 10px;flex:0 0 auto;`, + `border:1px solid ${desc ? "#8a6d3b" : "var(--xb-border)"};background:${desc ? "#3a2f16" : "var(--xb-bg-raise)"};color:${desc ? "#ffd98a" : "#9fc3e8"};padding:4px 10px;flex:0 0 auto;`, desc ? "编辑该自建词条的详细描述(新窗口)" : "为该自建词条添加详细描述(新窗口)", () => openDescEditor(api.node, entry || { key: it.key, zh: it.zh, en: it.en }, `${it.zh || it.en || it.key}`, desc, (text) => { const v = String(text || "").trim(); @@ -2543,7 +2543,7 @@ function renderCategoryPanel(box, api, catId) { api.refreshPreview(null); fire(); }))); - row.append(smallBtn("🗑", "border:1px solid #a44;background:#3a2020;color:#ffb0b0;width:24px;height:24px;padding:0;line-height:1;flex:0 0 auto;", "删除这个自建槽位", () => { + row.append(smallBtn("🗑", "border:1px solid #a44;background:var(--xb-tint-error);color:var(--xb-error);width:24px;height:24px;padding:0;line-height:1;flex:0 0 auto;", "删除这个自建槽位", () => { openConfirm("删除自建槽位", `删除自建槽位「${it.zh || it.en || it.key}」?`, () => { removeKeys([it.key]); delete s.elements.noted[`${cat.id}|${it.key}`]; @@ -2561,7 +2561,7 @@ function renderCategoryPanel(box, api, catId) { renderCustom(); const cusBar = el("div", "display:flex;gap:8px;flex-wrap:wrap;margin-top:4px;"); - cusBar.append(smallBtn("+ 新增槽位", "border:1px dashed #5b9bd5;background:#2a3a4a;color:#9fc3e8;padding:6px 12px;", "新增一个自定义词条槽位(中/英各填一个)", () => { + cusBar.append(smallBtn("+ 新增槽位", "border:1px dashed #5b9bd5;background:var(--xb-tint-info);color:var(--xb-info);padding:6px 12px;", "新增一个自定义词条槽位(中/英各填一个)", () => { const arr = s.elements.custom[cat.id] || (s.elements.custom[cat.id] = []); arr.push({ key: `c${Date.now().toString(36)}${Math.floor(Math.random() * 46656).toString(36)}`, zh: "", en: "" }); index = buildIndex(s.elements); @@ -2569,7 +2569,7 @@ function renderCategoryPanel(box, api, catId) { rebuild(); fire(); })); - cusBar.append(smallBtn("🗑 删除槽位", "border:1px solid #a44;background:#5a2a2a;color:#ffd0d0;padding:6px 12px;", "删除本分类最后一个自建槽位(要删指定的,用该行右侧的 🗑)", () => { + cusBar.append(smallBtn("🗑 删除槽位", "border:1px solid #a44;background:var(--xb-tint-error);color:var(--xb-error);padding:6px 12px;", "删除本分类最后一个自建槽位(要删指定的,用该行右侧的 🗑)", () => { const arr = s.elements.custom[cat.id] || []; if (!arr.length) { notify("本分类还没有自建槽位", "warning"); return; } const last = arr[arr.length - 1]; @@ -2592,10 +2592,10 @@ function renderCategoryPanel(box, api, catId) { /** 分类外壳:标题栏总是建,内容懒建(展开才建) */ function build() { wrap.innerHTML = ""; - const headRow = el("div", "display:flex;align-items:center;gap:8px;padding:8px 10px;background:#20242a;cursor:pointer;flex-wrap:wrap;"); - const arrow = el("span", "font-size:11px;color:#9fc3e8;width:12px;flex:0 0 12px;", ui.collapsed ? "▶" : "▼"); - const cnt = el("span", "font-size:11px;color:#8a97a5;", countText()); - headRow.append(arrow, el("span", "font-size:13px;font-weight:600;color:#cfe3f7;", cat.label), cnt, + const headRow = el("div", "display:flex;align-items:center;gap:8px;padding:8px 10px;background:var(--xb-bg-soft);cursor:pointer;flex-wrap:wrap;"); + const arrow = el("span", "font-size:11px;color:var(--xb-info);width:12px;flex:0 0 12px;", ui.collapsed ? "▶" : "▼"); + const cnt = el("span", "font-size:11px;color:var(--xb-text-dim);", countText()); + headRow.append(arrow, el("span", "font-size:13px;font-weight:600;color:var(--xb-info);", cat.label), cnt, el("span", "font-size:11px;color:#6f7c8a;flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;", cat.tip || "")); const body = el("div", "padding:8px 10px;"); body.style.display = ui.collapsed ? "none" : "block"; @@ -2810,11 +2810,11 @@ function setupNode(node) { const btnRow = el("div", "display:grid;grid-template-columns:repeat(4,1fr);grid-auto-rows:30px;gap:6px;flex:0 0 auto;"); for (const b of PANEL_BUTTONS) { const cat = catOf(b.panel); - const btn = el("button", "width:100%;height:30px;box-sizing:border-box;display:flex;align-items:center;justify-content:center;border-radius:6px;border:2px solid #5b9bd5;background:#3a3a3a;color:#eee;font-size:13px;cursor:pointer;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-family:inherit;", b.label); + const btn = el("button", "width:100%;height:30px;box-sizing:border-box;display:flex;align-items:center;justify-content:center;border-radius:6px;border:2px solid #5b9bd5;background:var(--xb-border);color:var(--xb-text);font-size:13px;cursor:pointer;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-family:inherit;", b.label); btn.title = `打开「${cat.label}」元素面板 —— ${cat.tip}\\n(里面:点选项行加入/移除提示词,【添加详细描述】写补充描述)`; btn.addEventListener("mousedown", (e) => e.stopPropagation()); - btn.addEventListener("mouseenter", () => { btn.style.background = "#4a4a4a"; }); - btn.addEventListener("mouseleave", () => { btn.style.background = "#3a3a3a"; }); + btn.addEventListener("mouseenter", () => { btn.style.background = "var(--xb-border)"; }); + btn.addEventListener("mouseleave", () => { btn.style.background = "var(--xb-border)"; }); btn.addEventListener("click", (e) => { e.stopPropagation(); const settings = loadSettings(node); @@ -2871,17 +2871,17 @@ function setupNode(node) { btn.title = ref && ref.path ? `已选:${ref.name || ref.path}\n${ref.path}\n点击可重新选择` : `尚未选择${kind === "pose" ? "姿态" : "穿搭"}参考图,点击打开图库${kind === "pose" ? "(输出端口:姿态图)" : "(输出端口:穿搭图)"}`; - btn.style.color = ref && ref.path ? "#d8ffd8" : "#eee"; + btn.style.color = ref && ref.path ? "#d8ffd8" : "var(--xb-text)"; }; if (isGalleryNode(node)) { const refRow = el("div", "display:grid;grid-template-columns:repeat(2,1fr);grid-auto-rows:30px;gap:6px;flex:0 0 auto;"); for (const kind of ["pose", "costume"]) { const btn = el("button", "width:100%;height:30px;box-sizing:border-box;display:flex;align-items:center;justify-content:center;" - + "border-radius:6px;border:2px solid #c58a3a;background:#3a3a3a;color:#eee;font-size:13px;cursor:pointer;" + + "border-radius:6px;border:2px solid #c58a3a;background:var(--xb-border);color:var(--xb-text);font-size:13px;cursor:pointer;" + "white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-family:inherit;", REF_LABELS[kind] + "选择"); btn.addEventListener("mousedown", (e) => e.stopPropagation()); - btn.addEventListener("mouseenter", () => { btn.style.background = "#4a4a4a"; }); - btn.addEventListener("mouseleave", () => { btn.style.background = "#3a3a3a"; }); + btn.addEventListener("mouseenter", () => { btn.style.background = "var(--xb-border)"; }); + btn.addEventListener("mouseleave", () => { btn.style.background = "var(--xb-border)"; }); btn.addEventListener("click", (e) => { e.stopPropagation(); const widgetRef = findWidget(node, REF_FIELDS[kind]); @@ -2915,7 +2915,7 @@ function setupNode(node) { // ③ 提示词框(flex:1 1 auto 吃掉剩余高度;原来的「生成详情」状态行已按要求删除) // 右上角三个小按钮:添加预设 / 编辑预设 / 复制文本(都作用于这一个提示词文本框) - const promptLab = el("div", "font-size:12px;color:#bbb;flex:1 1 auto;min-width:0;", + const promptLab = el("div", "font-size:12px;color:var(--xb-text-dim);flex:1 1 auto;min-width:0;", "📝 节点提示词(与面板预览框双向同步)"); const promptBtns = el("div", "display:flex;gap:6px;align-items:center;flex:0 0 auto;"); promptBtns.append( @@ -2972,11 +2972,11 @@ function setupNode(node) { b.textContent = label; b.title = tip; b.style.cssText = "font-size:0.92em;line-height:1.35;height:1.9em;padding:0 0.75em;border-radius:4px;" - + "border:1px solid #555;background:#2b2b2b;color:#ddd;cursor:pointer;white-space:nowrap;"; + + "border:1px solid var(--xb-border);background:var(--xb-bg-raise);color:var(--xb-text);cursor:pointer;white-space:nowrap;"; b.addEventListener("pointerdown", (e) => e.stopPropagation()); b.addEventListener("click", (e) => { e.preventDefault(); e.stopPropagation(); onClick?.(); }); - b.addEventListener("mouseenter", () => { b.style.background = "#3a3a3a"; }); - b.addEventListener("mouseleave", () => { b.style.background = "#2b2b2b"; }); + b.addEventListener("mouseenter", () => { b.style.background = "var(--xb-border)"; }); + b.addEventListener("mouseleave", () => { b.style.background = "var(--xb-bg-raise)"; }); return b; } @@ -3040,22 +3040,22 @@ function setupNode(node) { card.style.cssText = "width:" + (opts.width || MODAL_DEFAULT_WIDTH) + "px;max-width:96vw;max-height:90vh;" + "margin:auto;flex:0 0 auto;" + "display:flex;flex-direction:column;" - + "background:#1c1c1e;border:1px solid #333;border-radius:8px;color:#ddd;" + + "background:var(--xb-bg);border:1px solid var(--xb-border);border-radius:8px;color:var(--xb-text);" + "font:15px/1.65 system-ui,sans-serif;box-shadow:0 20px 40px rgba(0,0,0,.6);overflow:hidden;"; const head = document.createElement("div"); - head.style.cssText = "padding:14px 18px 12px;background:#232326;border-bottom:1px solid #3a3a3a;" + head.style.cssText = "padding:14px 18px 12px;background:var(--xb-bg-soft);border-bottom:1px solid var(--xb-border);" + "display:flex;align-items:center;gap:12px;flex-wrap:wrap;"; const titleEl = document.createElement("span"); titleEl.textContent = title; - titleEl.style.cssText = "flex:1 1 300px;min-width:220px;font-size:1.2em;font-weight:700;color:#eee;"; + titleEl.style.cssText = "flex:1 1 300px;min-width:220px;font-size:1.2em;font-weight:700;color:var(--xb-text);"; head.append(titleEl); const tools = document.createElement("span"); tools.style.cssText = "display:flex;align-items:center;gap:8px;flex-wrap:wrap;font-weight:normal;"; // 字号 const fontVal = document.createElement("span"); - fontVal.style.cssText = "min-width:26px;text-align:center;color:#e8a87c;font-variant-numeric:tabular-nums;"; + fontVal.style.cssText = "min-width:26px;text-align:center;color:var(--xb-orange);font-variant-numeric:tabular-nums;"; fontVal.textContent = String(modalFontGet()); const fontMinus = mkTinyBtn("A−", "缩小弹窗字号(Ctrl/⌘+滚轮 也可)", () => { modalFontSet(modalFontGet() - 1); fontVal.textContent = String(modalFontGet()); applyModalScale(card); @@ -3065,7 +3065,7 @@ function setupNode(node) { }); // 界面缩放 const zoomVal = document.createElement("span"); - zoomVal.style.cssText = "min-width:44px;text-align:center;color:#ffd98a;font-variant-numeric:tabular-nums;"; + zoomVal.style.cssText = "min-width:44px;text-align:center;color:var(--xb-warn);font-variant-numeric:tabular-nums;"; zoomVal.textContent = Math.round(modalZoomGet() * 100) + "%"; const zoomMinus = mkTinyBtn("−", "缩小整个弹窗界面", () => { modalZoomSet(modalZoomGet() - 0.1); zoomVal.textContent = Math.round(modalZoomGet() * 100) + "%"; applyModalScale(card); @@ -3075,9 +3075,9 @@ function setupNode(node) { }); const closeBtn = mkTinyBtn("✕", "关闭", () => close()); tools.append( - el("span", "color:#8a8a8a;", "字号"), fontMinus, fontVal, fontPlus, + el("span", "color:var(--xb-text-dim);", "字号"), fontMinus, fontVal, fontPlus, el("span", "color:#5a5a5a;", "|"), - el("span", "color:#8a8a8a;", "界面"), zoomMinus, zoomVal, zoomPlus, + el("span", "color:var(--xb-text-dim);", "界面"), zoomMinus, zoomVal, zoomPlus, closeBtn, ); head.append(tools); @@ -3154,7 +3154,7 @@ function setupNode(node) { const { bodyEl, close } = openModal("📥 添加预设(点一项 → 加进下面的提示词文本框)"); if (!modes.length) { const tip = document.createElement("div"); - tip.style.cssText = "color:#ffb74d;"; + tip.style.cssText = "color:var(--xb-warn);"; tip.textContent = `没有找到预设文件:xb_skill/${PRESET_NODE_NAME}/${data?.lang || "ZH"} 目录里没有 .txt。` + "可以先在「编辑预设」里把当前提示词存成一个新预设。"; bodyEl.append(tip); @@ -3171,23 +3171,23 @@ function setupNode(node) { const row = document.createElement("button"); row.type = "button"; row.style.cssText = "text-align:left;padding:7px 10px;border-radius:5px;border:1px solid #3d3d3d;" - + "background:#1f1f1f;color:#ddd;cursor:pointer;font-size:1em;"; + + "background:var(--xb-inset);color:var(--xb-text);cursor:pointer;font-size:1em;"; const nameSpan = document.createElement("div"); nameSpan.textContent = (backupOnly.includes(label) ? "📦 " : "") + label; - nameSpan.style.cssText = "font-weight:bold;color:#8fd3ff;font-size:1.05em;"; + nameSpan.style.cssText = "font-weight:bold;color:var(--xb-info);font-size:1.05em;"; nameSpan.title = backupOnly.includes(label) ? "来自备份目录(output/生图提示词预设)" : "来自预设目录(xb_skill)"; const prev = document.createElement("div"); prev.textContent = txt.length > 60 ? txt.slice(0, 60) + "…" : (txt || "(空文件)"); prev.style.cssText = "color:#9a9a9a;font-size:0.9em;margin-top:3px;line-height:1.5;"; row.append(nameSpan, prev); - row.addEventListener("mouseenter", () => { row.style.background = "#2c3a44"; row.style.borderColor = "#4a7a99"; }); + row.addEventListener("mouseenter", () => { row.style.background = "var(--xb-bg-raise)"; row.style.borderColor = "#4a7a99"; }); row.addEventListener("mouseleave", () => { row.style.background = "#1f1f1f"; row.style.borderColor = "#3d3d3d"; }); row.addEventListener("click", () => { applyPresetLabel(label, txt); close(); }); list.append(row); } bodyEl.append(list); const bdir = document.createElement("div"); - bdir.style.cssText = "color:#8a8a8a;font-size:0.88em;margin-top:6px;word-break:break-all;flex:0 0 auto;"; + bdir.style.cssText = "color:var(--xb-text-dim);font-size:0.88em;margin-top:6px;word-break:break-all;flex:0 0 auto;"; bdir.textContent = `📦 备份目录(也会被读取):${data?.backup_dir || "output/生图提示词预设"}`; const foot = document.createElement("div"); foot.style.cssText = "display:flex;justify-content:flex-end;gap:6px;margin-top:4px;flex:0 0 auto;flex-wrap:wrap;"; @@ -3206,18 +3206,18 @@ function setupNode(node) { const rowSel = document.createElement("div"); rowSel.style.cssText = "display:flex;gap:6px;align-items:center;flex:1 1 300px;min-width:240px;flex-wrap:nowrap;"; const sel = document.createElement("select"); - sel.style.cssText = "flex:1 1 260px;min-width:200px;height:2em;background:#1e1e1e;color:#ddd;" - + "border:1px solid #444;border-radius:4px;padding:0 6px;font-size:1em;"; - rowSel.append(el("span", "color:#aaa;white-space:nowrap;", "已有预设(选中即载入)"), sel); + sel.style.cssText = "flex:1 1 260px;min-width:200px;height:2em;background:var(--xb-bg);color:var(--xb-text);" + + "border:1px solid var(--xb-border);border-radius:4px;padding:0 6px;font-size:1em;"; + rowSel.append(el("span", "color:var(--xb-text-dim);white-space:nowrap;", "已有预设(选中即载入)"), sel); const rowName = document.createElement("div"); rowName.style.cssText = "display:flex;gap:6px;align-items:center;flex:1 1 260px;min-width:220px;flex-wrap:nowrap;"; - rowName.append(el("span", "color:#aaa;white-space:nowrap;", "预设名称")); + rowName.append(el("span", "color:var(--xb-text-dim);white-space:nowrap;", "预设名称")); const nameInput = document.createElement("input"); nameInput.type = "text"; nameInput.placeholder = "例如:电影感特写(同名 = 覆盖,新名 = 新建)"; - nameInput.style.cssText = "flex:1;min-width:140px;height:2em;background:#1e1e1e;color:#ddd;" - + "border:1px solid #444;border-radius:4px;padding:0 8px;font-size:1em;"; + nameInput.style.cssText = "flex:1;min-width:140px;height:2em;background:var(--xb-bg);color:var(--xb-text);" + + "border:1px solid var(--xb-border);border-radius:4px;padding:0 8px;font-size:1em;"; nameInput.value = lastPresetLabel || ""; rowName.append(nameInput); @@ -3253,8 +3253,8 @@ function setupNode(node) { // ── 第 2 行:大文本编辑框 ── const boxHead = document.createElement("div"); boxHead.style.cssText = "display:flex;align-items:center;justify-content:space-between;gap:8px;margin-top:2px;"; - const boxLabel = el("span", "color:#9fc3e8;font-weight:bold;", "预设内容(这里是最终会加到提示词最顶端/被保存的正文)"); - const charCount = el("span", "color:#8a8a8a;font-variant-numeric:tabular-nums;", "0 字"); + const boxLabel = el("span", "color:var(--xb-info);font-weight:bold;", "预设内容(这里是最终会加到提示词最顶端/被保存的正文)"); + const charCount = el("span", "color:var(--xb-text-dim);font-variant-numeric:tabular-nums;", "0 字"); const boxHeadRight = document.createElement("div"); boxHeadRight.style.cssText = "display:flex;gap:8px;align-items:center;flex:0 0 auto;"; boxHeadRight.append(charCount, fromPromptBtn); @@ -3265,7 +3265,7 @@ function setupNode(node) { box.dataset.autofit = "1"; box.placeholder = "在这里编写预设内容…(在左上方选择已有预设会自动载入到这里,或点「取当前提示词」)"; box.style.cssText = "width:100%;flex:1 1 auto;min-height:300px;resize:vertical;box-sizing:border-box;" - + "background:#1d1d1d;color:#ddd;border:1px solid #444;border-radius:5px;padding:10px 12px;" + + "background:var(--xb-bg);color:var(--xb-text);border:1px solid var(--xb-border);border-radius:5px;padding:10px 12px;" + "line-height:1.7;white-space:pre-wrap;overflow-wrap:anywhere;word-break:break-word;" + "font-family:inherit;font-size:" + modalFontGet() + "px;"; const fitBox = () => { @@ -3280,11 +3280,11 @@ function setupNode(node) { // ── 说明 / 状态 ── const hint = document.createElement("div"); - hint.style.cssText = "color:#8a8a8a;font-size:0.88em;word-break:break-all;"; + hint.style.cssText = "color:var(--xb-text-dim);font-size:0.88em;word-break:break-all;"; hint.textContent = `保存位置:xb_skill/${PRESET_NODE_NAME}/${data?.lang || "ZH"}/  备份:${data?.backup_dir || "output/生图提示词预设"}`; const status = document.createElement("div"); - status.style.cssText = "min-height:18px;color:#aaa;"; + status.style.cssText = "min-height:18px;color:var(--xb-text-dim);"; // ── 底部按钮 ── const foot = document.createElement("div"); diff --git a/web/xb_image_prompt_preset_pro.js b/web/xb_image_prompt_preset_pro.js index 5119098..84b75c7 100644 --- a/web/xb_image_prompt_preset_pro.js +++ b/web/xb_image_prompt_preset_pro.js @@ -1303,15 +1303,15 @@ function el(tag, css, text) { return e; } -const BOX_CSS = "background:#1d1d1d;color:#ccc;border:1px solid #444;border-radius:4px;padding:5px 8px;font-size:12px;box-sizing:border-box;font-family:inherit;"; +const BOX_CSS = "background:var(--xb-bg);color:var(--xb-text);border:1px solid var(--xb-border);border-radius:4px;padding:5px 8px;font-size:12px;box-sizing:border-box;font-family:inherit;"; function makeSectionTitle(text) { - return el("div", "font-size:13px;font-weight:600;color:#9fc3e8;margin:14px 0 8px;padding-bottom:5px;border-bottom:1px solid #333;", text); + return el("div", "font-size:13px;font-weight:600;color:var(--xb-info);margin:14px 0 8px;padding-bottom:5px;border-bottom:1px solid var(--xb-border);", text); } function field(labelText, control) { const g = el("div", "display:flex;align-items:center;gap:12px;margin-bottom:10px;"); - const lab = el("label", "flex:0 0 160px;font-size:13px;color:#999;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;", labelText); + const lab = el("label", "flex:0 0 160px;font-size:13px;color:var(--xb-text-dim);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;", labelText); control.style.flex = "1 1 0"; control.style.minWidth = "0"; g.append(lab, control); @@ -1338,7 +1338,7 @@ function radioRow(options, value, onChange) { for (const o of options) { const val = (typeof o === "object" && o !== null && o.value !== undefined) ? String(o.value) : String(o); const label = (typeof o === "object" && o !== null && o.label) ? String(o.label) : String(o); - const lab = el("label", "display:flex;align-items:center;gap:6px;font-size:13px;color:#999;cursor:pointer;user-select:none;"); + const lab = el("label", "display:flex;align-items:center;gap:6px;font-size:13px;color:var(--xb-text-dim);cursor:pointer;user-select:none;"); const c = el("input", "width:18px;height:18px;accent-color:#f59e0b;cursor:pointer;"); c.type = "checkbox"; c.checked = String(value) === val; @@ -1365,7 +1365,7 @@ function textareaControl(value, onChange, css) { } function checkboxControl(value, labelText, onChange, tip) { - const w = el("label", "display:flex;align-items:center;gap:8px;cursor:pointer;font-size:12px;color:#ddd;user-select:none;"); + const w = el("label", "display:flex;align-items:center;gap:8px;cursor:pointer;font-size:12px;color:var(--xb-text);user-select:none;"); const c = el("input", "width:16px;height:16px;accent-color:#f59e0b;cursor:pointer;"); c.type = "checkbox"; c.checked = !!value; @@ -1427,10 +1427,10 @@ function zoomSync(root) { } function buildZoomRow(root) { const row = el("span", "display:inline-flex;align-items:center;gap:4px;user-select:none;"); - const minus = smallBtn("A−", "border:1px solid #5b9bd5;background:#2a4a6a;color:#cfe3f7;width:22px;height:22px;padding:0;line-height:1;", "缩小编辑字号(Ctrl/⌘+滚轮 也可)"); - const val = el("span", "min-width:26px;text-align:center;font-size:12px;color:#e8a87c;font-variant-numeric:tabular-nums;", String(zoomDisplay())); + const minus = smallBtn("A−", "border:1px solid #5b9bd5;background:var(--xb-tint-info);color:var(--xb-info);width:22px;height:22px;padding:0;line-height:1;", "缩小编辑字号(Ctrl/⌘+滚轮 也可)"); + const val = el("span", "min-width:26px;text-align:center;font-size:12px;color:var(--xb-orange);font-variant-numeric:tabular-nums;", String(zoomDisplay())); val.classList.add("ipp-zoom-val"); - const plus = smallBtn("A+", "border:1px solid #5b9bd5;background:#2a4a6a;color:#cfe3f7;width:22px;height:22px;padding:0;line-height:1;", "放大编辑字号(Ctrl/⌘+滚轮 也可)"); + const plus = smallBtn("A+", "border:1px solid #5b9bd5;background:var(--xb-tint-info);color:var(--xb-info);width:22px;height:22px;padding:0;line-height:1;", "放大编辑字号(Ctrl/⌘+滚轮 也可)"); const step = (d) => { zoomSet((zoomGet() == null ? 12 : zoomGet()) + d); zoomApplyContainer(root); @@ -1474,10 +1474,10 @@ window.addEventListener("resize", () => { function buildUIRow(target) { const row = el("span", "display:inline-flex;align-items:center;gap:4px;user-select:none;"); row.append(el("span", "font-size:11px;color:#888;white-space:nowrap;", "界面")); - const minus = smallBtn("−", "border:1px solid #8a6d3b;background:#5a4a2a;color:#ffd98a;width:22px;height:22px;padding:0;line-height:1;font-size:13px;", "缩小整个界面(Ctrl/⌘+滚轮 也可)"); - const val = el("span", "min-width:42px;text-align:center;font-size:12px;color:#ffd98a;font-variant-numeric:tabular-nums;", Math.round(uiGet() * 100) + "%"); + const minus = smallBtn("−", "border:1px solid #8a6d3b;background:var(--xb-tint-warn);color:var(--xb-warn);width:22px;height:22px;padding:0;line-height:1;font-size:13px;", "缩小整个界面(Ctrl/⌘+滚轮 也可)"); + const val = el("span", "min-width:42px;text-align:center;font-size:12px;color:var(--xb-warn);font-variant-numeric:tabular-nums;", Math.round(uiGet() * 100) + "%"); val.classList.add("ipp-ui-val"); - const plus = smallBtn("+", "border:1px solid #8a6d3b;background:#5a4a2a;color:#ffd98a;width:22px;height:22px;padding:0;line-height:1;font-size:13px;", "放大整个界面(Ctrl/⌘+滚轮 也可)"); + const plus = smallBtn("+", "border:1px solid #8a6d3b;background:var(--xb-tint-warn);color:var(--xb-warn);width:22px;height:22px;padding:0;line-height:1;font-size:13px;", "放大整个界面(Ctrl/⌘+滚轮 也可)"); const step = (d) => { uiSet(uiGet() + d * UI_STEP); uiApply(target); @@ -1741,12 +1741,12 @@ function openPanel(ctx, panelId) { const title = `${cat.label} 元素`; const overlay = el("div", "position:fixed;inset:0;background:rgba(0,0,0,.78);z-index:9999;display:flex;align-items:center;justify-content:center;overflow:auto;"); - const dialog = el("section", "background:#1c1c1e;border:1px solid #333;border-radius:8px;width:880px;max-height:90vh;display:flex;flex-direction:column;box-shadow:0 20px 40px rgba(0,0,0,.6);flex:0 0 auto;color:#ddd;font-size:13px;"); + const dialog = el("section", "background:var(--xb-bg);border:1px solid var(--xb-border);border-radius:8px;width:880px;max-height:90vh;display:flex;flex-direction:column;box-shadow:0 20px 40px rgba(0,0,0,.6);flex:0 0 auto;color:var(--xb-text);font-size:13px;"); - const header = el("div", "padding:16px 20px 12px;border-bottom:1px solid #444;"); - const titleEl = el("div", "font-size:18px;font-weight:700;color:#eee;", title); + const header = el("div", "padding:16px 20px 12px;border-bottom:1px solid var(--xb-border);"); + const titleEl = el("div", "font-size:18px;font-weight:700;color:var(--xb-text);", title); header.append(titleEl); - header.append(el("div", "font-size:12px;color:#999;margin-top:5px;", "点选项行即加入提示词(变蓝),再点一下移除(变灰);点【添加详细描述】会在新窗口里给该词条写补充描述(悬停行可看)。配置随工作流保存,改完重新执行节点即生效。")); + header.append(el("div", "font-size:12px;color:var(--xb-text-dim);margin-top:5px;", "点选项行即加入提示词(变蓝),再点一下移除(变灰);点【添加详细描述】会在新窗口里给该词条写补充描述(悬停行可看)。配置随工作流保存,改完重新执行节点即生效。")); dialog.append(header); // ⚠️ 顶部不留内边距:滚动容器的 padding 区不参与裁剪,滚上去的行会从这条缝里 @@ -1754,12 +1754,12 @@ function openPanel(ctx, panelId) { const panelBox = el("div", "padding:0 20px 14px;overflow-y:auto;flex:1;min-height:280px;"); // ── 吸顶预览框(不受滚动条约束,始终在画面内) ── - const sticky = el("div", "position:sticky;top:0;z-index:6;background:#1c1c1e;padding:16px 0 10px;margin:0 0 6px;border-bottom:1px solid #333;"); + const sticky = el("div", "position:sticky;top:0;z-index:6;background:var(--xb-bg);padding:16px 0 10px;margin:0 0 6px;border-bottom:1px solid var(--xb-border);"); const head = el("div", "display:flex;align-items:center;gap:8px;margin-bottom:6px;flex-wrap:wrap;"); - head.append(el("span", "font-size:12px;font-weight:600;color:#9fc3e8;", "📝 提示词预览(与节点提示词框实时同步,可直接编辑)")); + head.append(el("span", "font-size:12px;font-weight:600;color:var(--xb-info);", "📝 提示词预览(与节点提示词框实时同步,可直接编辑)")); const headCount = el("span", "font-size:11px;color:#888;", ""); head.append(headCount); - const presetChip = el("div", "font-size:11px;color:#f0d79a;background:#2a2416;border:1px dashed #8a6d3b;border-radius:4px;padding:4px 8px;margin-bottom:6px;white-space:pre-wrap;line-height:1.5;display:none;"); + const presetChip = el("div", "font-size:11px;color:var(--xb-warn);background:var(--xb-tint-warn);border:1px dashed #8a6d3b;border-radius:4px;padding:4px 8px;margin-bottom:6px;white-space:pre-wrap;line-height:1.5;display:none;"); const preview = el("textarea", BOX_CSS + "width:100%;min-height:74px;max-height:210px;resize:vertical;line-height:1.65;font-size:12px;"); preview.spellcheck = false; preview.placeholder = "点左侧 + 添加元素,或直接在这里手写提示词…"; @@ -1800,10 +1800,10 @@ function openPanel(ctx, panelId) { // ── 底部 ── const error = el("div", "color:#e55;font-size:12px;margin:0 20px;min-height:16px;"); - const footer = el("div", "padding:12px 20px;border-top:1px solid #444;display:flex;justify-content:flex-end;gap:10px;background:#18181a;border-bottom-left-radius:8px;border-bottom-right-radius:8px;"); - const cancelBtn = el("button", "background:transparent;border:1px solid #555;color:#fff;border-radius:4px;padding:8px 20px;font-size:14px;cursor:pointer;font-family:inherit;", "取消"); - const saveBtn = el("button", "background:#2d5a88;color:#fff;border:none;border-radius:4px;padding:8px 20px;font-size:14px;font-weight:600;cursor:pointer;font-family:inherit;", "💾 保存"); - const autoRow = el("label", "display:flex;align-items:center;gap:6px;font-size:13px;color:#999;cursor:pointer;user-select:none;"); + const footer = el("div", "padding:12px 20px;border-top:1px solid var(--xb-border);display:flex;justify-content:flex-end;gap:10px;background:var(--xb-bg-soft);border-bottom-left-radius:8px;border-bottom-right-radius:8px;"); + const cancelBtn = el("button", "background:transparent;border:1px solid var(--xb-border);color:#fff;border-radius:4px;padding:8px 20px;font-size:14px;cursor:pointer;font-family:inherit;", "取消"); + const saveBtn = el("button", "background:var(--xb-btn-primary);color:#fff;border:none;border-radius:4px;padding:8px 20px;font-size:14px;font-weight:600;cursor:pointer;font-family:inherit;", "💾 保存"); + const autoRow = el("label", "display:flex;align-items:center;gap:6px;font-size:13px;color:var(--xb-text-dim);cursor:pointer;user-select:none;"); const autoCb = el("input", "width:18px;height:18px;accent-color:#f59e0b;cursor:pointer;"); autoCb.type = "checkbox"; autoCb.checked = settings.auto_save !== false; @@ -1888,13 +1888,13 @@ const CAT_UI = {}; // 会话级:cat -> { collapsed, onlySel, keyword }(不 * ============================================================ */ function openConfirm(title, message, onOk, okText) { const overlay = el("div", "position:fixed;inset:0;background:rgba(0,0,0,.72);z-index:10200;display:flex;align-items:center;justify-content:center;"); - const box = el("section", "background:#1c1c1e;border:1px solid #333;border-radius:8px;width:460px;max-width:92vw;box-shadow:0 20px 40px rgba(0,0,0,.6);padding:18px;box-sizing:border-box;color:#ddd;font-size:13px;"); + const box = el("section", "background:var(--xb-bg);border:1px solid var(--xb-border);border-radius:8px;width:460px;max-width:92vw;box-shadow:0 20px 40px rgba(0,0,0,.6);padding:18px;box-sizing:border-box;color:var(--xb-text);font-size:13px;"); box.dataset.ippModal = "confirm"; - box.append(el("div", "font-size:15px;font-weight:700;color:#eee;margin-bottom:8px;", title || "确认")); - box.append(el("div", "font-size:13px;color:#bbb;line-height:1.8;white-space:pre-wrap;margin-bottom:14px;", message || "")); + box.append(el("div", "font-size:15px;font-weight:700;color:var(--xb-text);margin-bottom:8px;", title || "确认")); + box.append(el("div", "font-size:13px;color:var(--xb-text-dim);line-height:1.8;white-space:pre-wrap;margin-bottom:14px;", message || "")); const row = el("div", "display:flex;gap:10px;justify-content:flex-end;"); - const cancelBtn = el("button", "background:transparent;border:1px solid #555;color:#fff;border-radius:4px;padding:7px 18px;font-size:13px;cursor:pointer;font-family:inherit;", "取消"); - const okBtn = el("button", "background:#8a3a2a;border:1px solid #c2604a;color:#ffe7df;border-radius:4px;padding:7px 18px;font-size:13px;font-weight:600;cursor:pointer;font-family:inherit;", okText || "确定"); + const cancelBtn = el("button", "background:transparent;border:1px solid var(--xb-border);color:#fff;border-radius:4px;padding:7px 18px;font-size:13px;cursor:pointer;font-family:inherit;", "取消"); + const okBtn = el("button", "background:var(--xb-tint-error);border:1px solid #c2604a;color:#ffe7df;border-radius:4px;padding:7px 18px;font-size:13px;font-weight:600;cursor:pointer;font-family:inherit;", okText || "确定"); const close = () => { try { overlay.remove(); } catch (_) {} }; cancelBtn.addEventListener("click", (e) => { e.stopPropagation(); close(); }); okBtn.addEventListener("click", (e) => { e.stopPropagation(); close(); try { onOk && onOk(); } catch (_) {} }); @@ -1912,29 +1912,29 @@ function openConfirm(title, message, onOk, okText) { * ============================================================ */ function openRelPicker(node, nameText, lang, current, onPick) { const overlay = el("div", "position:fixed;inset:0;background:rgba(0,0,0,.78);z-index:10200;display:flex;align-items:center;justify-content:center;"); - const box = el("section", "background:#1c1c1e;border:1px solid #333;border-radius:8px;width:780px;max-width:94vw;max-height:92vh;overflow:auto;box-shadow:0 20px 40px rgba(0,0,0,.6);padding:18px;box-sizing:border-box;color:#ddd;font-size:13px;"); + const box = el("section", "background:var(--xb-bg);border:1px solid var(--xb-border);border-radius:8px;width:780px;max-width:94vw;max-height:92vh;overflow:auto;box-shadow:0 20px 40px rgba(0,0,0,.6);padding:18px;box-sizing:border-box;color:var(--xb-text);font-size:13px;"); box.dataset.ippModal = "rel"; - box.append(el("div", "font-size:15px;font-weight:700;color:#eee;margin-bottom:4px;", "🎒 道具与主体的关系")); - box.append(el("div", "font-size:12px;color:#8a97a5;margin-bottom:12px;line-height:1.7;", + box.append(el("div", "font-size:15px;font-weight:700;color:var(--xb-text);margin-bottom:4px;", "🎒 道具与主体的关系")); + box.append(el("div", "font-size:12px;color:var(--xb-text-dim);margin-bottom:12px;line-height:1.7;", `道具:${nameText}\n选一个关系词,拼进提示词时它会加在道具前面(例:手拿折扇 / a folding fan, held in the hand)。`)); const grid = el("div", "display:grid;grid-template-columns:repeat(6,1fr);gap:6px;margin-bottom:14px;"); const close = () => { try { overlay.remove(); } catch (_) {} }; for (const [id, zh, en] of RELATIONS) { const on = id === current; - const b = el("button", `display:flex;flex-direction:column;align-items:center;justify-content:center;gap:2px;min-height:46px;border-radius:6px;cursor:pointer;font-family:inherit;padding:4px 2px;box-sizing:border-box;border:1px solid ${on ? "#7fb3e0" : "#4a4a4a"};background:${on ? "#2d5a88" : "#262626"};color:${on ? "#eaf4ff" : "#ddd"};`); + const b = el("button", `display:flex;flex-direction:column;align-items:center;justify-content:center;gap:2px;min-height:46px;border-radius:6px;cursor:pointer;font-family:inherit;padding:4px 2px;box-sizing:border-box;border:1px solid ${on ? "#7fb3e0" : "var(--xb-border)"};background:${on ? "#2d5a88" : "var(--xb-bg-raise)"};color:${on ? "#eaf4ff" : "var(--xb-text)"};`); b.dataset.rel = id; b.title = `${zh} / ${en}`; b.append(el("span", "font-size:13px;line-height:1.2;", zh)); b.append(el("span", "font-size:9px;line-height:1.2;color:#9fb6cc;text-align:center;word-break:break-word;", en)); - b.addEventListener("mouseenter", () => { if (!on) b.style.background = "#333"; }); - b.addEventListener("mouseleave", () => { if (!on) b.style.background = "#262626"; }); + b.addEventListener("mouseenter", () => { if (!on) b.style.background = "var(--xb-border)"; }); + b.addEventListener("mouseleave", () => { if (!on) b.style.background = "var(--xb-bg-raise)"; }); b.addEventListener("click", (e) => { e.stopPropagation(); close(); try { onPick && onPick(id); } catch (_) {} }); grid.append(b); } box.append(grid); const row = el("div", "display:flex;gap:10px;justify-content:flex-end;"); - const noneBtn = el("button", "background:#2a4a6a;border:1px solid #5b9bd5;color:#cfe3f7;border-radius:4px;padding:7px 16px;font-size:13px;cursor:pointer;font-family:inherit;", "不加关系(只加道具词)"); - const cancelBtn = el("button", "background:transparent;border:1px solid #555;color:#fff;border-radius:4px;padding:7px 18px;font-size:13px;cursor:pointer;font-family:inherit;", "取消"); + const noneBtn = el("button", "background:var(--xb-tint-info);border:1px solid #5b9bd5;color:var(--xb-info);border-radius:4px;padding:7px 16px;font-size:13px;cursor:pointer;font-family:inherit;", "不加关系(只加道具词)"); + const cancelBtn = el("button", "background:transparent;border:1px solid var(--xb-border);color:#fff;border-radius:4px;padding:7px 18px;font-size:13px;cursor:pointer;font-family:inherit;", "取消"); noneBtn.addEventListener("click", (e) => { e.stopPropagation(); close(); try { onPick && onPick(""); } catch (_) {} }); cancelBtn.addEventListener("click", (e) => { e.stopPropagation(); close(); }); overlay.addEventListener("mousedown", (e) => { if (e.target === overlay) close(); }); @@ -1952,10 +1952,10 @@ function openRelPicker(node, nameText, lang, current, onPick) { * ============================================================ */ function openDescEditor(node, entry, nameText, value, onSave) { const overlay = el("div", "position:fixed;inset:0;background:rgba(0,0,0,.72);z-index:10100;display:flex;align-items:center;justify-content:center;"); - const box = el("section", "background:#1c1c1e;border:1px solid #333;border-radius:8px;width:600px;max-width:92vw;box-shadow:0 20px 40px rgba(0,0,0,.6);padding:18px;box-sizing:border-box;color:#ddd;font-size:13px;"); + const box = el("section", "background:var(--xb-bg);border:1px solid var(--xb-border);border-radius:8px;width:600px;max-width:92vw;box-shadow:0 20px 40px rgba(0,0,0,.6);padding:18px;box-sizing:border-box;color:var(--xb-text);font-size:13px;"); box.dataset.ippModal = "desc"; // 稳定锤点(便于定位/自动化测试) - box.append(el("div", "font-size:15px;font-weight:700;color:#eee;margin-bottom:4px;", "📝 添加详细描述")); - box.append(el("div", "font-size:12px;color:#8a97a5;margin-bottom:10px;line-height:1.7;white-space:pre-wrap;", + box.append(el("div", "font-size:15px;font-weight:700;color:var(--xb-text);margin-bottom:4px;", "📝 添加详细描述")); + box.append(el("div", "font-size:12px;color:var(--xb-text-dim);margin-bottom:10px;line-height:1.7;white-space:pre-wrap;", `词条:${nameText}\n` + "写在这里的内容会以「词条(描述)」的形式拼进提示词(例:一位女性(银灰色短发))," + "所以只写要补的细节即可 —— 不用重复词条本身,也别写整句话。")); @@ -1967,9 +1967,9 @@ function openDescEditor(node, entry, nameText, value, onSave) { const tip = el("div", "font-size:11px;color:#777;margin-top:6px;", "Esc = 取消 · Ctrl/⌘ + Enter = 保存"); box.append(tip); const row = el("div", "display:flex;justify-content:flex-end;gap:10px;margin-top:14px;"); - const cancelBtn = el("button", "background:transparent;border:1px solid #555;color:#fff;border-radius:4px;padding:7px 18px;font-size:13px;cursor:pointer;font-family:inherit;", "取消"); - const clrBtn = el("button", "background:transparent;border:1px solid #844;color:#ffb0b0;border-radius:4px;padding:7px 14px;font-size:13px;cursor:pointer;font-family:inherit;margin-right:auto;", "🗑 清除描述"); - const okBtn = el("button", "background:#2d5a88;color:#fff;border:none;border-radius:4px;padding:7px 20px;font-size:13px;font-weight:600;cursor:pointer;font-family:inherit;", "✅ 保存描述"); + const cancelBtn = el("button", "background:transparent;border:1px solid var(--xb-border);color:#fff;border-radius:4px;padding:7px 18px;font-size:13px;cursor:pointer;font-family:inherit;", "取消"); + const clrBtn = el("button", "background:transparent;border:1px solid #844;color:var(--xb-error);border-radius:4px;padding:7px 14px;font-size:13px;cursor:pointer;font-family:inherit;margin-right:auto;", "🗑 清除描述"); + const okBtn = el("button", "background:var(--xb-btn-primary);color:#fff;border:none;border-radius:4px;padding:7px 20px;font-size:13px;font-weight:600;cursor:pointer;font-family:inherit;", "✅ 保存描述"); row.append(clrBtn, cancelBtn, okBtn); box.append(row); overlay.append(box); @@ -1993,11 +1993,11 @@ function openDescEditor(node, entry, nameText, value, onSave) { /** 自建槽位的「中/英名称」编辑窗(同样是一个小弹窗) */ function openSlotNameEditor(zhValue, enValue, onSave) { const overlay = el("div", "position:fixed;inset:0;background:rgba(0,0,0,.72);z-index:10100;display:flex;align-items:center;justify-content:center;"); - const box = el("section", "background:#1c1c1e;border:1px solid #333;border-radius:8px;width:520px;max-width:92vw;box-shadow:0 20px 40px rgba(0,0,0,.6);padding:18px;box-sizing:border-box;color:#ddd;font-size:13px;"); + const box = el("section", "background:var(--xb-bg);border:1px solid var(--xb-border);border-radius:8px;width:520px;max-width:92vw;box-shadow:0 20px 40px rgba(0,0,0,.6);padding:18px;box-sizing:border-box;color:var(--xb-text);font-size:13px;"); box.dataset.ippModal = "slotname"; // 稳定锤点 - box.append(el("div", "font-size:15px;font-weight:700;color:#eee;margin-bottom:10px;", "✎ 自建槽位名称(中 / 英)")); + box.append(el("div", "font-size:15px;font-weight:700;color:var(--xb-text);margin-bottom:10px;", "✎ 自建槽位名称(中 / 英)")); const mk = (label, val, ph) => { - box.append(el("div", "font-size:11px;color:#8a97a5;margin:6px 0 3px;", label)); + box.append(el("div", "font-size:11px;color:var(--xb-text-dim);margin:6px 0 3px;", label)); const i = el("input", BOX_CSS + "width:100%;font-size:13px;padding:6px 8px;"); i.type = "text"; i.value = val || ""; @@ -2008,8 +2008,8 @@ function openSlotNameEditor(zhValue, enValue, onSave) { const zh = mk("中文词条(中文 [ZH] 输出时用)", zhValue, "例:银灰色短发"); const en = mk("English word(英文 [EN] 输出时用)", enValue, "e.g. silver-gray short hair"); const row = el("div", "display:flex;justify-content:flex-end;gap:10px;margin-top:14px;"); - const cancelBtn = el("button", "background:transparent;border:1px solid #555;color:#fff;border-radius:4px;padding:7px 18px;font-size:13px;cursor:pointer;font-family:inherit;", "取消"); - const okBtn = el("button", "background:#2d5a88;color:#fff;border:none;border-radius:4px;padding:7px 20px;font-size:13px;font-weight:600;cursor:pointer;font-family:inherit;", "✅ 保存名称"); + const cancelBtn = el("button", "background:transparent;border:1px solid var(--xb-border);color:#fff;border-radius:4px;padding:7px 18px;font-size:13px;cursor:pointer;font-family:inherit;", "取消"); + const okBtn = el("button", "background:var(--xb-btn-primary);color:#fff;border:none;border-radius:4px;padding:7px 20px;font-size:13px;font-weight:600;cursor:pointer;font-family:inherit;", "✅ 保存名称"); row.append(cancelBtn, okBtn); box.append(row); overlay.append(box); @@ -2044,12 +2044,12 @@ function renderCategoryPanel(box, api, catId) { catRow.dataset.ippCatRow = "1"; for (const c of CATEGORIES) { const on = c.id === catId; - const b = el("button", `box-sizing:border-box;height:30px;display:flex;align-items:center;justify-content:center;border-radius:6px;border:2px solid ${on ? "#7fb3e0" : "#5b9bd5"};background:${on ? "#2d5a88" : "#3a3a3a"};color:${on ? "#eaf4ff" : "#eee"};font-size:13px;cursor:pointer;font-family:inherit;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;`, c.label); + const b = el("button", `box-sizing:border-box;height:30px;display:flex;align-items:center;justify-content:center;border-radius:6px;border:2px solid ${on ? "#7fb3e0" : "#5b9bd5"};background:${on ? "#2d5a88" : "var(--xb-border)"};color:${on ? "#eaf4ff" : "var(--xb-text)"};font-size:13px;cursor:pointer;font-family:inherit;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;`, c.label); b.dataset.cat = c.id; if (on) b.dataset.catActive = "1"; b.title = `切到「${c.label}」面板 —— ${c.tip}`; - b.addEventListener("mouseenter", () => { if (!on) b.style.background = "#4a4a4a"; }); - b.addEventListener("mouseleave", () => { if (!on) b.style.background = "#3a3a3a"; }); + b.addEventListener("mouseenter", () => { if (!on) b.style.background = "var(--xb-border)"; }); + b.addEventListener("mouseleave", () => { if (!on) b.style.background = "var(--xb-border)"; }); b.addEventListener("click", (e) => { e.stopPropagation(); if (!on && api.switchCategory) api.switchCategory(c.id); @@ -2059,7 +2059,7 @@ function renderCategoryPanel(box, api, catId) { box.append(catRow); const bar = el("div", "display:flex;gap:8px;flex-wrap:wrap;margin-bottom:6px;"); bar.append( - smallBtn("🧹 清空提示词", "border:1px solid #a44;background:#7a2a2a;color:#ffd0d0;padding:6px 12px;", "清空正文(同时清掉所有元素的「已选」状态)", () => { + smallBtn("🧹 清空提示词", "border:1px solid #a44;background:var(--xb-tint-error);color:var(--xb-error);padding:6px 12px;", "清空正文(同时清掉所有元素的「已选」状态)", () => { openConfirm("🧹 清空提示词", "确定清空提示词正文?\n(所有元素的「已选」状态也会一起清掉)", () => { s.elements.selected = {}; s.elements.rel = {}; @@ -2069,17 +2069,17 @@ function renderCategoryPanel(box, api, catId) { notify("已清空提示词", "success"); }); }), - smallBtn("🔧 按选择重建", "border:1px solid #5b9bd5;background:#2a4a6a;color:#cfe3f7;padding:6px 12px;", "按「已选元素 + 固定分类顺序(风格→视角→主体→姿态→装扮→道具→光影→背景)+ 当前语言」重排正文(手工改乱了就用它)", () => { + smallBtn("🔧 按选择重建", "border:1px solid #5b9bd5;background:var(--xb-tint-info);color:var(--xb-info);padding:6px 12px;", "按「已选元素 + 固定分类顺序(风格→视角→主体→姿态→装扮→道具→光影→背景)+ 当前语言」重排正文(手工改乱了就用它)", () => { const t = rebuildBody(s, index, lang()); bodySet(t); fire(); notify(t ? "已按已选元素重建正文" : "当前没有已选元素(重建后正文为空)", t ? "success" : "warning"); }), - smallBtn("📋 复制成句", "border:1px solid #555;background:#2a2a2a;color:#ccc;padding:6px 12px;", "复制最终成句(三视图模式下含预设句)", () => { + smallBtn("📋 复制成句", "border:1px solid var(--xb-border);background:var(--xb-bg-raise);color:var(--xb-text);padding:6px 12px;", "复制最终成句(三视图模式下含预设句)", () => { copyText(finalPrompt(surf.mode, surf.presetText, surf.lang, bodyGet(), surf.io)); }), - smallBtn("⬆️ 导出词表", "border:1px solid #6b9b5b;background:#3a5a2a;color:#d7f0c8;padding:6px 12px;", "把「自建槽位 + 已选 + 描述」导出成 JSON 备份", () => exportJson(s)), - smallBtn("⬇️ 导入词表", "border:1px solid #6b9b5b;background:#3a5a2a;color:#d7f0c8;padding:6px 12px;", "从 JSON 恢复「自建槽位 + 已选 + 描述」(合并,不覆盖内置词表)", () => importJson((obj) => { + smallBtn("⬆️ 导出词表", "border:1px solid #6b9b5b;background:var(--xb-tint-ok);color:var(--xb-ok);padding:6px 12px;", "把「自建槽位 + 已选 + 描述」导出成 JSON 备份", () => exportJson(s)), + smallBtn("⬇️ 导入词表", "border:1px solid #6b9b5b;background:var(--xb-tint-ok);color:var(--xb-ok);padding:6px 12px;", "从 JSON 恢复「自建槽位 + 已选 + 描述」(合并,不覆盖内置词表)", () => importJson((obj) => { if (!obj || typeof obj !== "object") return; for (const k of ["custom", "noted", "selected"]) { if (obj[k] && typeof obj[k] === "object") s.elements[k] = Object.assign({}, s.elements[k], obj[k]); @@ -2111,7 +2111,7 @@ function renderCategoryPanel(box, api, catId) { // 436 条词条只在第一次渲染时建 DOM;标题栏仍可点一下收起(纯会话状态) if (!CAT_UI[cat.id]) CAT_UI[cat.id] = { collapsed: false, onlySel: false, keyword: "", sub: "" }; const ui = CAT_UI[cat.id]; - const wrap = el("div", "border:1px solid #333;border-radius:6px;margin-bottom:8px;background:#191919;overflow:hidden;"); + const wrap = el("div", "border:1px solid var(--xb-border);border-radius:6px;margin-bottom:8px;background:var(--xb-inset);overflow:hidden;"); const selOf = () => (s.elements.selected[cat.id] || []); const selArr = () => (s.elements.selected[cat.id] || (s.elements.selected[cat.id] = [])); const rebuild = () => { wrap.innerHTML = ""; build(); }; @@ -2186,14 +2186,14 @@ function renderCategoryPanel(box, api, catId) { const cur = subOf(cat, ui.sub).id; for (const c of chipRefs) { const on = c.dataset.sub === cur; - c.style.background = on ? "#2d5a88" : "#2a2a2a"; - c.style.borderColor = on ? "#7fb3e0" : "#555"; + c.style.background = on ? "#2d5a88" : "var(--xb-bg-raise)"; + c.style.borderColor = on ? "#7fb3e0" : "var(--xb-border)"; c.style.color = on ? "#eaf4ff" : "#bbb"; c.style.fontWeight = on ? "600" : "400"; } }; for (const sub of (cat.subs || [])) { - const b = el("button", "border:1px solid #555;border-radius:14px;padding:4px 14px;font-size:12px;cursor:pointer;font-family:inherit;white-space:nowrap;", `${sub.label}(${(sub.keys || []).length})`); + const b = el("button", "border:1px solid var(--xb-border);border-radius:14px;padding:4px 14px;font-size:12px;cursor:pointer;font-family:inherit;white-space:nowrap;", `${sub.label}(${(sub.keys || []).length})`); b.dataset.sub = sub.id; b.title = `切到「${sub.label}」子类(${(sub.keys || []).length} 条)`; b.addEventListener("mousedown", (e) => e.stopPropagation()); @@ -2243,7 +2243,7 @@ function renderCategoryPanel(box, api, catId) { fire(); notify(n ? `已加入 ${n} 条` : "这些词条都已在提示词里", n ? "success" : "warning"); }; - tb.append(smallBtn("+ 全部加入", "border:1px solid #5b9bd5;background:#2a4a6a;color:#cfe3f7;", "把本分类当前可见的词条全部追加进提示词", () => { + tb.append(smallBtn("+ 全部加入", "border:1px solid #5b9bd5;background:var(--xb-tint-info);color:var(--xb-info);", "把本分类当前可见的词条全部追加进提示词", () => { const list = visibleEntries(); if (!list.length) { notify("没有可加入的词条", "warning"); return; } if (list.length > 60) { @@ -2252,7 +2252,7 @@ function renderCategoryPanel(box, api, catId) { } addAllNow(list); })); - tb.append(smallBtn("🗑 全部移除", "border:1px solid #a44;background:#5a2a2a;color:#ffd0d0;", "把本分类已选词条全部从提示词移除", () => { + tb.append(smallBtn("🗑 全部移除", "border:1px solid #a44;background:var(--xb-tint-error);color:var(--xb-error);", "把本分类已选词条全部从提示词移除", () => { const keys = selOf().slice(); if (!keys.length) { notify("本分类还没有已选词条", "warning"); return; } removeKeys(keys); @@ -2282,7 +2282,7 @@ function renderCategoryPanel(box, api, catId) { const selected = selOf().includes(entry.key); const descKey = `${cat.id}|${entry.key}`; const desc = s.elements.noted[descKey] || ""; - const row = el("div", `display:flex;align-items:center;gap:8px;padding:5px 8px;border-radius:5px;margin-bottom:3px;box-sizing:border-box;cursor:pointer;user-select:none;background:${selected ? "#22456b" : "#242424"};border:1px solid ${selected ? "#3f86d0" : "#3a3a3a"};`); + const row = el("div", `display:flex;align-items:center;gap:8px;padding:5px 8px;border-radius:5px;margin-bottom:3px;box-sizing:border-box;cursor:pointer;user-select:none;background:${selected ? "#22456b" : "var(--xb-bg-raise)"};border:1px solid ${selected ? "#3f86d0" : "var(--xb-border)"};`); // 悬停整行 → 显示完整词条、当前状态与详细描述(原生 tooltip) row.title = `${entry.zh || "(空)"} / ${entry.en || "(空)"}` + (entry.custom ? "\n(自建槽位)" : "") @@ -2301,14 +2301,14 @@ function renderCategoryPanel(box, api, catId) { renderRows(); fire(); }); - if (entry.custom) row.append(el("span", "font-size:10px;color:#f0d79a;border:1px solid #8a6d3b;border-radius:3px;padding:0 3px;flex:0 0 auto;", "自建")); + if (entry.custom) row.append(el("span", "font-size:10px;color:var(--xb-warn);border:1px solid #8a6d3b;border-radius:3px;padding:0 3px;flex:0 0 auto;", "自建")); const rel = relPair(s, cat.id, entry.key); if (rel) row.append(el("span", "font-size:10px;color:#9fe0c0;border:1px solid #3f7a5f;border-radius:3px;padding:0 3px;flex:0 0 auto;", rel.zh)); const nm = el("span", `flex:1 1 auto;min-width:0;font-size:12px;line-height:1.4;color:${selected ? "#eaf4ff" : "#c8c8c8"};overflow:hidden;text-overflow:ellipsis;white-space:nowrap;`, `${selected ? "✔ " : ""}${entry.zh || "(空)"} / ${entry.en || "(空)"}`); row.append(nm); if (cat.id === "props") { - row.append(smallBtn(rel ? `🔗 ${rel.zh}` : "🔗 关系", "border:1px solid #5b9bd5;background:#2a4a6a;color:#cfe3f7;padding:4px 10px;flex:0 0 auto;", "选择/更换这个道具与主体的关系词(先移旧片段再加新片段)", () => { + row.append(smallBtn(rel ? `🔗 ${rel.zh}` : "🔗 关系", "border:1px solid #5b9bd5;background:var(--xb-tint-info);color:var(--xb-info);padding:4px 10px;flex:0 0 auto;", "选择/更换这个道具与主体的关系词(先移旧片段再加新片段)", () => { openRelPicker(api.node, `${entry.zh || entry.key} / ${entry.en || ""}`, lang(), (relPair(s, cat.id, entry.key) || {}).id || "", (relId) => { @@ -2320,7 +2320,7 @@ function renderCategoryPanel(box, api, catId) { })); } const descBtn = smallBtn(desc ? "📝 详细描述" : "添加详细描述", - `border:1px solid ${desc ? "#8a6d3b" : "#555"};background:${desc ? "#3a2f16" : "#2a2a2a"};color:${desc ? "#ffd98a" : "#9fc3e8"};padding:4px 10px;flex:0 0 auto;`, + `border:1px solid ${desc ? "#8a6d3b" : "var(--xb-border)"};background:${desc ? "#3a2f16" : "var(--xb-bg-raise)"};color:${desc ? "#ffd98a" : "#9fc3e8"};padding:4px 10px;flex:0 0 auto;`, desc ? "编辑该词条的详细描述(新窗口)" : "为该词条添加详细描述(新窗口)", () => openDescEditor(api.node, entry, `${entry.zh || entry.en || entry.key}`, desc, (text) => { const v = String(text || "").trim(); @@ -2359,7 +2359,7 @@ function renderCategoryPanel(box, api, catId) { const selected = selOf().includes(it.key); const descKey = `${cat.id}|${it.key}`; const desc = s.elements.noted[descKey] || ""; - const row = el("div", `display:flex;align-items:center;gap:8px;padding:5px 8px;border-radius:5px;margin-bottom:3px;box-sizing:border-box;cursor:pointer;user-select:none;background:${selected ? "#22456b" : "#242424"};border:1px solid ${selected ? "#3f86d0" : "#3a3a3a"};`); + const row = el("div", `display:flex;align-items:center;gap:8px;padding:5px 8px;border-radius:5px;margin-bottom:3px;box-sizing:border-box;cursor:pointer;user-select:none;background:${selected ? "#22456b" : "var(--xb-bg-raise)"};border:1px solid ${selected ? "#3f86d0" : "var(--xb-border)"};`); row.title = `${it.zh || "(未填中文)"} / ${it.en || "(未填英文)"}\n(自建槽位)` + `\n${selected ? "已加入提示词 —— 再点一下移除" : "点一下加入提示词"}` + "\n\n详细描述:" + (desc || "(还没有,点【添加详细描述】写一条)"); @@ -2369,10 +2369,10 @@ function renderCategoryPanel(box, api, catId) { rebuild(); fire(); }); - row.append(el("span", "font-size:10px;color:#f0d79a;border:1px solid #8a6d3b;border-radius:3px;padding:0 3px;flex:0 0 auto;", "自建")); + row.append(el("span", "font-size:10px;color:var(--xb-warn);border:1px solid #8a6d3b;border-radius:3px;padding:0 3px;flex:0 0 auto;", "自建")); row.append(el("span", `flex:1 1 auto;min-width:0;font-size:12px;color:${selected ? "#eaf4ff" : "#c8c8c8"};overflow:hidden;text-overflow:ellipsis;white-space:nowrap;`, `${selected ? "✔ " : ""}${it.zh || "(未填中文)"} / ${it.en || "(未填英文)"}`)); - row.append(smallBtn("✎ 编辑名称", "border:1px solid #555;background:#2a2a2a;color:#9fc3e8;padding:4px 10px;flex:0 0 auto;", "新窗口编辑该槽位的中/英词条", () => { + row.append(smallBtn("✎ 编辑名称", "border:1px solid var(--xb-border);background:var(--xb-bg-raise);color:var(--xb-info);padding:4px 10px;flex:0 0 auto;", "新窗口编辑该槽位的中/英词条", () => { openSlotNameEditor(it.zh, it.en, (z, e2) => { it.zh = z; it.en = e2; if (entry) { entry.zh = z; entry.en = e2; } @@ -2382,7 +2382,7 @@ function renderCategoryPanel(box, api, catId) { }); })); row.append(smallBtn(desc ? "📝 详细描述" : "添加详细描述", - `border:1px solid ${desc ? "#8a6d3b" : "#555"};background:${desc ? "#3a2f16" : "#2a2a2a"};color:${desc ? "#ffd98a" : "#9fc3e8"};padding:4px 10px;flex:0 0 auto;`, + `border:1px solid ${desc ? "#8a6d3b" : "var(--xb-border)"};background:${desc ? "#3a2f16" : "var(--xb-bg-raise)"};color:${desc ? "#ffd98a" : "#9fc3e8"};padding:4px 10px;flex:0 0 auto;`, desc ? "编辑该自建词条的详细描述(新窗口)" : "为该自建词条添加详细描述(新窗口)", () => openDescEditor(api.node, entry || { key: it.key, zh: it.zh, en: it.en }, `${it.zh || it.en || it.key}`, desc, (text) => { const v = String(text || "").trim(); @@ -2397,7 +2397,7 @@ function renderCategoryPanel(box, api, catId) { api.refreshPreview(null); fire(); }))); - row.append(smallBtn("🗑", "border:1px solid #a44;background:#3a2020;color:#ffb0b0;width:24px;height:24px;padding:0;line-height:1;flex:0 0 auto;", "删除这个自建槽位", () => { + row.append(smallBtn("🗑", "border:1px solid #a44;background:var(--xb-tint-error);color:var(--xb-error);width:24px;height:24px;padding:0;line-height:1;flex:0 0 auto;", "删除这个自建槽位", () => { openConfirm("删除自建槽位", `删除自建槽位「${it.zh || it.en || it.key}」?`, () => { removeKeys([it.key]); delete s.elements.noted[`${cat.id}|${it.key}`]; @@ -2415,7 +2415,7 @@ function renderCategoryPanel(box, api, catId) { renderCustom(); const cusBar = el("div", "display:flex;gap:8px;flex-wrap:wrap;margin-top:4px;"); - cusBar.append(smallBtn("+ 新增槽位", "border:1px dashed #5b9bd5;background:#2a3a4a;color:#9fc3e8;padding:6px 12px;", "新增一个自定义词条槽位(中/英各填一个)", () => { + cusBar.append(smallBtn("+ 新增槽位", "border:1px dashed #5b9bd5;background:var(--xb-tint-info);color:var(--xb-info);padding:6px 12px;", "新增一个自定义词条槽位(中/英各填一个)", () => { const arr = s.elements.custom[cat.id] || (s.elements.custom[cat.id] = []); arr.push({ key: `c${Date.now().toString(36)}${Math.floor(Math.random() * 46656).toString(36)}`, zh: "", en: "" }); index = buildIndex(s.elements); @@ -2423,7 +2423,7 @@ function renderCategoryPanel(box, api, catId) { rebuild(); fire(); })); - cusBar.append(smallBtn("🗑 删除槽位", "border:1px solid #a44;background:#5a2a2a;color:#ffd0d0;padding:6px 12px;", "删除本分类最后一个自建槽位(要删指定的,用该行右侧的 🗑)", () => { + cusBar.append(smallBtn("🗑 删除槽位", "border:1px solid #a44;background:var(--xb-tint-error);color:var(--xb-error);padding:6px 12px;", "删除本分类最后一个自建槽位(要删指定的,用该行右侧的 🗑)", () => { const arr = s.elements.custom[cat.id] || []; if (!arr.length) { notify("本分类还没有自建槽位", "warning"); return; } const last = arr[arr.length - 1]; @@ -2446,10 +2446,10 @@ function renderCategoryPanel(box, api, catId) { /** 分类外壳:标题栏总是建,内容懒建(展开才建) */ function build() { wrap.innerHTML = ""; - const headRow = el("div", "display:flex;align-items:center;gap:8px;padding:8px 10px;background:#20242a;cursor:pointer;flex-wrap:wrap;"); - const arrow = el("span", "font-size:11px;color:#9fc3e8;width:12px;flex:0 0 12px;", ui.collapsed ? "▶" : "▼"); - const cnt = el("span", "font-size:11px;color:#8a97a5;", countText()); - headRow.append(arrow, el("span", "font-size:13px;font-weight:600;color:#cfe3f7;", cat.label), cnt, + const headRow = el("div", "display:flex;align-items:center;gap:8px;padding:8px 10px;background:var(--xb-bg-soft);cursor:pointer;flex-wrap:wrap;"); + const arrow = el("span", "font-size:11px;color:var(--xb-info);width:12px;flex:0 0 12px;", ui.collapsed ? "▶" : "▼"); + const cnt = el("span", "font-size:11px;color:var(--xb-text-dim);", countText()); + headRow.append(arrow, el("span", "font-size:13px;font-weight:600;color:var(--xb-info);", cat.label), cnt, el("span", "font-size:11px;color:#6f7c8a;flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;", cat.tip || "")); const body = el("div", "padding:8px 10px;"); body.style.display = ui.collapsed ? "none" : "block"; @@ -2673,11 +2673,11 @@ function setupNode(node) { const btnRow = el("div", "display:grid;grid-template-columns:repeat(4,1fr);grid-auto-rows:30px;gap:6px;flex:0 0 auto;"); for (const b of PANEL_BUTTONS) { const cat = catOf(b.panel); - const btn = el("button", "width:100%;height:30px;box-sizing:border-box;display:flex;align-items:center;justify-content:center;border-radius:6px;border:2px solid #5b9bd5;background:#3a3a3a;color:#eee;font-size:13px;cursor:pointer;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-family:inherit;", b.label); + const btn = el("button", "width:100%;height:30px;box-sizing:border-box;display:flex;align-items:center;justify-content:center;border-radius:6px;border:2px solid #5b9bd5;background:var(--xb-border);color:var(--xb-text);font-size:13px;cursor:pointer;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-family:inherit;", b.label); btn.title = `打开「${cat.label}」元素面板 —— ${cat.tip}\\n(里面:点选项行加入/移除提示词,【添加详细描述】写补充描述)`; btn.addEventListener("mousedown", (e) => e.stopPropagation()); - btn.addEventListener("mouseenter", () => { btn.style.background = "#4a4a4a"; }); - btn.addEventListener("mouseleave", () => { btn.style.background = "#3a3a3a"; }); + btn.addEventListener("mouseenter", () => { btn.style.background = "var(--xb-border)"; }); + btn.addEventListener("mouseleave", () => { btn.style.background = "var(--xb-border)"; }); btn.addEventListener("click", (e) => { e.stopPropagation(); const settings = loadSettings(node); @@ -2776,9 +2776,9 @@ function setupNode(node) { box.style.margin = "0 8px"; box.style.boxSizing = "border-box"; box.style.borderRadius = "4px"; - box.style.border = "1px solid #444"; - box.style.background = "#1d1d1d"; - box.style.color = "#ccc"; + box.style.border = "1px solid var(--xb-border)"; + box.style.background = "var(--xb-bg)"; + box.style.color = "var(--xb-text)"; box.style.padding = "5px 8px"; box.style.fontSize = "12px"; box.style.lineHeight = "1.6"; @@ -3463,7 +3463,7 @@ function xbrTextControl(value, placeholder, onChange) { /** 导演台同款「种子 + 生成后控制(随机/固定/增加/减少)」一行 */ function xbrSeedRow(run, onChange) { const g = el("div", "display:flex;align-items:center;gap:10px;margin-bottom:10px;"); - g.append(el("label", "flex:0 0 160px;font-size:13px;color:#999;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;", "种子")); + g.append(el("label", "flex:0 0 160px;font-size:13px;color:var(--xb-text-dim);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;", "种子")); const i = xbrNumberControl(run.seed, { min: 0, max: Number.MAX_SAFE_INTEGER, step: 1 }, (v) => { run.seed = Math.round(v); onChange?.(); }); i.style.flex = "1 1 0"; const ctrl = smallBtn("", "flex:1 1 0;min-width:0;background:#b45309;border:1px solid #d97706;color:#fff;text-align:center;", "生成后控制:点击在 随机 / 固定 / 增加 / 减少 间切换", null); @@ -3486,24 +3486,24 @@ function xbrSeedRow(run, onChange) { let xbrModal = null; function xbrDialog({ label, title, subtitle, width = 820, autoSaveRef = null, onCommit = null, onImmediate = null, onChange = null, onClose = null }) { const overlay = el("div", "position:fixed;inset:0;background:rgba(0,0,0,.78);z-index:10300;display:flex;align-items:center;justify-content:center;overflow:auto;"); - const dialog = el("section", `background:#1c1c1e;border:1px solid #333;border-radius:8px;width:${width}px;max-width:94vw;` + - "max-height:90vh;display:flex;flex-direction:column;box-shadow:0 20px 40px rgba(0,0,0,.6);flex:0 0 auto;color:#ddd;font-size:13px;"); + const dialog = el("section", `background:var(--xb-bg);border:1px solid var(--xb-border);border-radius:8px;width:${width}px;max-width:94vw;` + + "max-height:90vh;display:flex;flex-direction:column;box-shadow:0 20px 40px rgba(0,0,0,.6);flex:0 0 auto;color:var(--xb-text);font-size:13px;"); dialog.dataset.xbrProModal = label; dialog.setAttribute("role", "dialog"); dialog.setAttribute("aria-modal", "true"); dialog.setAttribute("aria-label", label); - const header = el("div", "padding:18px 20px;border-bottom:1px solid #444;"); - header.append(el("div", "font-size:17px;font-weight:700;color:#eee;", title)); - if (subtitle) header.append(el("div", "font-size:12px;color:#999;margin-top:5px;line-height:1.6;", subtitle)); + const header = el("div", "padding:18px 20px;border-bottom:1px solid var(--xb-border);"); + header.append(el("div", "font-size:17px;font-weight:700;color:var(--xb-text);", title)); + if (subtitle) header.append(el("div", "font-size:12px;color:var(--xb-text-dim);margin-top:5px;line-height:1.6;", subtitle)); const body = el("div", "padding:16px 20px;overflow-y:auto;flex:1;min-height:200px;"); const error = el("div", "color:#e55;font-size:12px;margin:0 20px;min-height:14px;"); - const footer = el("div", "padding:12px 20px;border-top:1px solid #444;display:flex;justify-content:flex-end;gap:10px;align-items:center;background:#18181a;border-bottom-left-radius:8px;border-bottom-right-radius:8px;"); + const footer = el("div", "padding:12px 20px;border-top:1px solid var(--xb-border);display:flex;justify-content:flex-end;gap:10px;align-items:center;background:var(--xb-bg-soft);border-bottom-left-radius:8px;border-bottom-right-radius:8px;"); const left = el("div", "display:flex;align-items:center;gap:16px;margin-right:auto;flex-wrap:wrap;"); let autoCb = null; if (autoSaveRef) { - const autoRow = el("label", "display:flex;align-items:center;gap:6px;font-size:13px;color:#999;cursor:pointer;user-select:none;"); + const autoRow = el("label", "display:flex;align-items:center;gap:6px;font-size:13px;color:var(--xb-text-dim);cursor:pointer;user-select:none;"); autoCb = el("input", "width:18px;height:18px;accent-color:#f59e0b;cursor:pointer;"); autoCb.type = "checkbox"; autoCb.checked = autoSaveRef.value !== false; @@ -3518,8 +3518,8 @@ function xbrDialog({ label, title, subtitle, width = 820, autoSaveRef = null, on uiRow.title = "调整整个弹窗界面大小(不缩放网页/画布;Ctrl/⌘+滚轮 也可)"; left.append(zoomRow, uiRow); - const cancelBtn = el("button", "background:transparent;border:1px solid #555;color:#fff;border-radius:4px;padding:8px 20px;font-size:14px;cursor:pointer;font-family:inherit;", "取消"); - const saveBtn = onCommit ? el("button", "background:#2d5a88;color:#fff;border:none;border-radius:4px;padding:8px 20px;font-size:14px;font-weight:600;cursor:pointer;font-family:inherit;", "💾 保存") : null; + const cancelBtn = el("button", "background:transparent;border:1px solid var(--xb-border);color:#fff;border-radius:4px;padding:8px 20px;font-size:14px;cursor:pointer;font-family:inherit;", "取消"); + const saveBtn = onCommit ? el("button", "background:var(--xb-btn-primary);color:#fff;border:none;border-radius:4px;padding:8px 20px;font-size:14px;font-weight:600;cursor:pointer;font-family:inherit;", "💾 保存") : null; footer.append(left, cancelBtn); if (saveBtn) footer.append(saveBtn); dialog.append(header, body, error, footer); @@ -3766,15 +3766,15 @@ function xbrCopyPromptText(node) { function xbrPresetDialog(title) { const overlay = el("div", "position:fixed;inset:0;background:rgba(0,0,0,.78);z-index:10300;display:flex;overflow:auto;"); const dialog = el("section", "width:880px;max-width:96vw;max-height:90vh;margin:auto;flex:0 0 auto;" - + "display:flex;flex-direction:column;background:#1c1c1e;border:1px solid #333;border-radius:8px;color:#ddd;" + + "display:flex;flex-direction:column;background:var(--xb-bg);border:1px solid var(--xb-border);border-radius:8px;color:var(--xb-text);" + "font:15px/1.65 system-ui,sans-serif;box-shadow:0 20px 40px rgba(0,0,0,.6);overflow:hidden;"); - const header = el("div", "padding:14px 18px 12px;background:#232326;border-bottom:1px solid #3a3a3a;" + const header = el("div", "padding:14px 18px 12px;background:var(--xb-bg-soft);border-bottom:1px solid var(--xb-border);" + "display:flex;align-items:center;gap:12px;flex-wrap:wrap;"); - header.append(el("span", "flex:1 1 300px;min-width:220px;font-size:1.2em;font-weight:700;color:#eee;", title)); + header.append(el("span", "flex:1 1 300px;min-width:220px;font-size:1.2em;font-weight:700;color:var(--xb-text);", title)); const tools = el("span", "display:flex;align-items:center;gap:8px;flex-wrap:wrap;font-weight:normal;"); - tools.append(el("span", "color:#8a8a8a;", "字号"), buildZoomRow(dialog), + tools.append(el("span", "color:var(--xb-text-dim);", "字号"), buildZoomRow(dialog), el("span", "color:#5a5a5a;", "|"), buildUIRow(dialog)); - const closeBtn = smallBtn("✕", "border:1px solid #555;background:#2b2b2b;color:#ddd;padding:3px 10px;", "关闭"); + const closeBtn = smallBtn("✕", "border:1px solid var(--xb-border);background:var(--xb-bg-raise);color:var(--xb-text);padding:3px 10px;", "关闭"); tools.append(closeBtn); header.append(tools); const body = el("div", "padding:14px 18px 16px;display:flex;flex-direction:column;gap:10px;overflow:hidden;flex:1 1 auto;min-height:0;"); @@ -3802,7 +3802,7 @@ async function xbrShowPresetPicker(node, onPick) { const backupOnly = Array.isArray(data?.backup_only) ? data.backup_only : []; const { body, close } = xbrPresetDialog("📥 添加预设(点一项 → 加进节点提示词)"); if (!labels.length) { - body.append(el("div", "color:#ffb74d;", + body.append(el("div", "color:var(--xb-warn);", `没有找到预设文件:xb_skill/${XBR_PRESET_LIB_NODE}/${data?.lang || "ZH"} 目录里没有 .txt。` + "可以先在「✏️ 编辑预设」里把当前提示词存成一个新预设。")); } @@ -3810,14 +3810,14 @@ async function xbrShowPresetPicker(node, onPick) { for (const label of labels) { const txt = xbrLibText(label, lang); const row = el("button", "text-align:left;padding:7px 10px;border-radius:5px;border:1px solid #3d3d3d;" - + "background:#1f1f1f;color:#ddd;cursor:pointer;font-size:1em;font-family:inherit;"); + + "background:var(--xb-inset);color:var(--xb-text);cursor:pointer;font-size:1em;font-family:inherit;"); row.type = "button"; - const name = el("div", "font-weight:bold;color:#8fd3ff;font-size:1.05em;", (backupOnly.includes(label) ? "📦 " : "") + label); + const name = el("div", "font-weight:bold;color:var(--xb-info);font-size:1.05em;", (backupOnly.includes(label) ? "📦 " : "") + label); name.title = backupOnly.includes(label) ? "来自备份目录(output/生图提示词预设)" : "来自预设目录(xb_skill)"; const prev = el("div", "color:#9a9a9a;font-size:0.9em;margin-top:3px;line-height:1.5;", txt.length > 60 ? txt.slice(0, 60) + "…" : (txt || "(空文件)")); row.append(name, prev); - row.addEventListener("mouseenter", () => { row.style.background = "#2c3a44"; row.style.borderColor = "#4a7a99"; }); + row.addEventListener("mouseenter", () => { row.style.background = "var(--xb-bg-raise)"; row.style.borderColor = "#4a7a99"; }); row.addEventListener("mouseleave", () => { row.style.background = "#1f1f1f"; row.style.borderColor = "#3d3d3d"; }); row.addEventListener("click", () => { if (typeof onPick === "function") onPick(label, txt); @@ -3827,13 +3827,13 @@ async function xbrShowPresetPicker(node, onPick) { list.append(row); } body.append(list); - body.append(el("div", "color:#8a8a8a;font-size:0.88em;word-break:break-all;flex:0 0 auto;", + body.append(el("div", "color:var(--xb-text-dim);font-size:0.88em;word-break:break-all;flex:0 0 auto;", `📦 备份目录(也会被读取):${data?.backup_dir || "output/生图提示词预设"}`)); const foot = el("div", "display:flex;justify-content:flex-end;gap:6px;flex:0 0 auto;flex-wrap:wrap;"); foot.append( - smallBtn("刷新列表", "border:1px solid #555;background:#2b2b2b;color:#ddd;padding:5px 12px;font-size:12px;", "重新扫描预设目录与备份目录", + smallBtn("刷新列表", "border:1px solid var(--xb-border);background:var(--xb-bg-raise);color:var(--xb-text);padding:5px 12px;font-size:12px;", "重新扫描预设目录与备份目录", async () => { close(); await xbrFetchPresetLib(true); xbrShowPresetPicker(node); }), - smallBtn("关闭", "border:1px solid #555;background:#2b2b2b;color:#ddd;padding:5px 12px;font-size:12px;", "关闭窗口", close), + smallBtn("关闭", "border:1px solid var(--xb-border);background:var(--xb-bg-raise);color:var(--xb-text);padding:5px 12px;font-size:12px;", "关闭窗口", close), ); body.append(foot); } @@ -3869,13 +3869,13 @@ async function xbrShowPresetEditor(node, opts = {}) { const { body, close } = xbrPresetDialog(cfg.title); const rowSel = el("div", "display:flex;gap:6px;align-items:center;flex:1 1 300px;min-width:240px;flex-wrap:nowrap;"); - const sel = el("select", "flex:1 1 260px;min-width:200px;height:2em;background:#1e1e1e;color:#ddd;" - + "border:1px solid #444;border-radius:4px;padding:0 6px;font-size:1em;font-family:inherit;"); - rowSel.append(el("span", "color:#aaa;white-space:nowrap;", "已有预设(选中即载入)"), sel); + const sel = el("select", "flex:1 1 260px;min-width:200px;height:2em;background:var(--xb-bg);color:var(--xb-text);" + + "border:1px solid var(--xb-border);border-radius:4px;padding:0 6px;font-size:1em;font-family:inherit;"); + rowSel.append(el("span", "color:var(--xb-text-dim);white-space:nowrap;", "已有预设(选中即载入)"), sel); const rowName = el("div", "display:flex;gap:6px;align-items:center;flex:1 1 260px;min-width:220px;flex-wrap:nowrap;"); - rowName.append(el("span", "color:#aaa;white-space:nowrap;", "预设名称")); - const nameInput = el("input", "flex:1;min-width:140px;height:2em;background:#1e1e1e;color:#ddd;" - + "border:1px solid #444;border-radius:4px;padding:0 8px;font-size:1em;font-family:inherit;"); + rowName.append(el("span", "color:var(--xb-text-dim);white-space:nowrap;", "预设名称")); + const nameInput = el("input", "flex:1;min-width:140px;height:2em;background:var(--xb-bg);color:var(--xb-text);" + + "border:1px solid var(--xb-border);border-radius:4px;padding:0 8px;font-size:1em;font-family:inherit;"); nameInput.type = "text"; nameInput.placeholder = "例如:电影感特写(同名 = 覆盖,新名 = 新建)"; rowName.append(nameInput); @@ -3884,10 +3884,10 @@ async function xbrShowPresetEditor(node, opts = {}) { const boxHead = el("div", "display:flex;align-items:center;justify-content:space-between;gap:8px;margin-top:2px;flex:0 0 auto;"); const boxHeadRight = el("div", "display:flex;gap:8px;align-items:center;flex:0 0 auto;"); - const charCount = el("span", "color:#8a8a8a;font-variant-numeric:tabular-nums;", "0 字"); + const charCount = el("span", "color:var(--xb-text-dim);font-variant-numeric:tabular-nums;", "0 字"); boxHeadRight.append(charCount); if (cfg.fromPrompt) { - boxHeadRight.append(smallBtn("取当前提示词", "border:1px solid #555;background:#2b2b2b;color:#ddd;padding:4px 10px;font-size:12px;", + boxHeadRight.append(smallBtn("取当前提示词", "border:1px solid var(--xb-border);background:var(--xb-bg-raise);color:var(--xb-text);padding:4px 10px;font-size:12px;", "把节点提示词框的内容复制到编辑框", () => { box.value = xbrPromptText(node); status.style.color = "#aaa"; @@ -3895,10 +3895,10 @@ async function xbrShowPresetEditor(node, opts = {}) { fitBox(); })); } - boxHead.append(el("span", "color:#9fc3e8;font-weight:bold;", cfg.contentLabel), boxHeadRight); + boxHead.append(el("span", "color:var(--xb-info);font-weight:bold;", cfg.contentLabel), boxHeadRight); const box = el("textarea", "width:100%;flex:1 1 auto;min-height:300px;resize:vertical;box-sizing:border-box;" - + "background:#1d1d1d;color:#ddd;border:1px solid #444;border-radius:5px;padding:10px 12px;" + + "background:var(--xb-bg);color:var(--xb-text);border:1px solid var(--xb-border);border-radius:5px;padding:10px 12px;" + "line-height:1.7;white-space:pre-wrap;overflow-wrap:anywhere;word-break:break-word;font-family:inherit;font-size:15px;"); box.spellcheck = false; box.placeholder = cfg.placeholder; @@ -3910,12 +3910,12 @@ async function xbrShowPresetEditor(node, opts = {}) { box.addEventListener("input", fitBox); fitBox(); - const hint = el("div", "color:#8a8a8a;font-size:0.88em;word-break:break-all;flex:0 0 auto;", + const hint = el("div", "color:var(--xb-text-dim);font-size:0.88em;word-break:break-all;flex:0 0 auto;", `保存位置:xb_skill/${cfg.nodeName}/${data?.lang || "ZH"}/  备份:${data?.backup_dir || "output/生图提示词预设"}`); - const status = el("div", "min-height:18px;color:#aaa;flex:0 0 auto;"); + const status = el("div", "min-height:18px;color:var(--xb-text-dim);flex:0 0 auto;"); const foot = el("div", "display:flex;justify-content:flex-end;gap:6px;flex:0 0 auto;flex-wrap:wrap;"); - const btnCss = "border:1px solid #555;background:#2b2b2b;color:#ddd;padding:5px 14px;font-size:12px;"; + const btnCss = "border:1px solid var(--xb-border);background:var(--xb-bg-raise);color:var(--xb-text);padding:5px 14px;font-size:12px;"; const fillSelect = (d) => { const cur = sel.value; @@ -3950,7 +3950,7 @@ async function xbrShowPresetEditor(node, opts = {}) { try { nameInput.focus(); } catch (_) {} }); - const saveBtn = smallBtn("保存", btnCss + "border-color:#3f86d0;background:#22456b;color:#eaf4ff;", + const saveBtn = smallBtn("保存", btnCss + "border-color:#3f86d0;background:#22456b;color:var(--xb-on-accent);", "把编辑框内容保存成预设文件(同名覆盖 / 新名新建)", async () => { const label = String(nameInput.value || "").trim(); if (!label) { status.style.color = "#ff8a80"; status.textContent = "请先填写预设名称"; nameInput.focus(); return; } @@ -4096,14 +4096,14 @@ async function xbrShowSkillEditor(node, presetName, onChanged) { const { body, close } = xbrPresetDialog("✏️ 编辑 skill(技能文档整段作为 LLM 的系统提示词)"); const rowSel = el("div", "display:flex;gap:6px;align-items:center;flex:1 1 300px;min-width:240px;flex-wrap:nowrap;"); - const sel = el("select", "flex:1 1 260px;min-width:200px;height:2em;background:#1e1e1e;color:#ddd;" - + "border:1px solid #444;border-radius:4px;padding:0 6px;font-size:1em;font-family:inherit;"); - rowSel.append(el("span", "color:#aaa;white-space:nowrap;", "已有 skill(选中即载入)"), sel); + const sel = el("select", "flex:1 1 260px;min-width:200px;height:2em;background:var(--xb-bg);color:var(--xb-text);" + + "border:1px solid var(--xb-border);border-radius:4px;padding:0 6px;font-size:1em;font-family:inherit;"); + rowSel.append(el("span", "color:var(--xb-text-dim);white-space:nowrap;", "已有 skill(选中即载入)"), sel); const rowName = el("div", "display:flex;gap:6px;align-items:center;flex:1 1 260px;min-width:220px;flex-wrap:nowrap;"); - rowName.append(el("span", "color:#aaa;white-space:nowrap;", "文件名")); - const nameInput = el("input", "flex:1;min-width:140px;height:2em;background:#1e1e1e;color:#ddd;" - + "border:1px solid #444;border-radius:4px;padding:0 8px;font-size:1em;font-family:inherit;"); + rowName.append(el("span", "color:var(--xb-text-dim);white-space:nowrap;", "文件名")); + const nameInput = el("input", "flex:1;min-width:140px;height:2em;background:var(--xb-bg);color:var(--xb-text);" + + "border:1px solid var(--xb-border);border-radius:4px;padding:0 8px;font-size:1em;font-family:inherit;"); nameInput.type = "text"; nameInput.placeholder = "例如:my_skill.txt(同名 = 覆盖,新名 = 新建)"; rowName.append(nameInput); @@ -4111,11 +4111,11 @@ async function xbrShowSkillEditor(node, presetName, onChanged) { firstRow.append(rowSel, rowName); const boxHead = el("div", "display:flex;align-items:center;justify-content:space-between;gap:8px;margin-top:2px;flex:0 0 auto;"); - const charCount = el("span", "color:#8a8a8a;font-variant-numeric:tabular-nums;", "0 字"); - boxHead.append(el("span", "color:#9fc3e8;font-weight:bold;", "skill 全文(整段作为系统提示词)"), charCount); + const charCount = el("span", "color:var(--xb-text-dim);font-variant-numeric:tabular-nums;", "0 字"); + boxHead.append(el("span", "color:var(--xb-info);font-weight:bold;", "skill 全文(整段作为系统提示词)"), charCount); const box = el("textarea", "width:100%;flex:1 1 auto;min-height:300px;resize:vertical;box-sizing:border-box;" - + "background:#1d1d1d;color:#ddd;border:1px solid #444;border-radius:5px;padding:10px 12px;" + + "background:var(--xb-bg);color:var(--xb-text);border:1px solid var(--xb-border);border-radius:5px;padding:10px 12px;" + "line-height:1.7;white-space:pre-wrap;overflow-wrap:anywhere;word-break:break-word;font-family:inherit;font-size:15px;"); box.spellcheck = false; box.placeholder = "在这里编写技能文档(角色设定 + 输出契约)…"; @@ -4127,11 +4127,11 @@ async function xbrShowSkillEditor(node, presetName, onChanged) { box.addEventListener("input", fitBox); fitBox(); - const hint = el("div", "color:#8a8a8a;font-size:0.88em;word-break:break-all;flex:0 0 auto;", + const hint = el("div", "color:var(--xb-text-dim);font-size:0.88em;word-break:break-all;flex:0 0 auto;", "保存位置:xb_skill/skills/ (同一批文件也被「🖼️ Qwen2.1提示词预设」等节点共用)"); - const status = el("div", "min-height:18px;color:#aaa;flex:0 0 auto;"); + const status = el("div", "min-height:18px;color:var(--xb-text-dim);flex:0 0 auto;"); const foot = el("div", "display:flex;justify-content:flex-end;gap:6px;flex:0 0 auto;flex-wrap:wrap;"); - const btnCss = "border:1px solid #555;background:#2b2b2b;color:#ddd;padding:5px 14px;font-size:12px;"; + const btnCss = "border:1px solid var(--xb-border);background:var(--xb-bg-raise);color:var(--xb-text);padding:5px 14px;font-size:12px;"; const fillSelect = () => { const cur = sel.value; @@ -4166,7 +4166,7 @@ async function xbrShowSkillEditor(node, presetName, onChanged) { try { nameInput.focus(); } catch (_) {} }); - const saveBtn = smallBtn("保存", btnCss + "border-color:#3f86d0;background:#22456b;color:#eaf4ff;", + const saveBtn = smallBtn("保存", btnCss + "border-color:#3f86d0;background:#22456b;color:var(--xb-on-accent);", "保存技能文件(同名覆盖 / 新名新建)", async () => { const name = String(nameInput.value || "").trim(); if (!name) { status.style.color = "#ff8a80"; status.textContent = "请先填写文件名"; nameInput.focus(); return; } @@ -4207,7 +4207,7 @@ async function xbrShowSkillEditor(node, presetName, onChanged) { /* ── 弹窗内容:✨ 提示词增强预设 ─────────────────────────── */ function xbrRenderPreset(body, node, draft, ctx) { - const btnCss = "border:1px solid #555;background:#2b2b2b;color:#ddd;padding:4px 12px;font-size:12px;"; + const btnCss = "border:1px solid var(--xb-border);background:var(--xb-bg-raise);color:var(--xb-text);padding:4px 12px;font-size:12px;"; // ── 设定词:文本框 + 预设词工具(添加 / 编辑 / 复制)────────────────────── body.append(makeSectionTitle("设定词")); @@ -4315,7 +4315,7 @@ function xbrRenderPreset(body, node, draft, ctx) { if (presetOpts.length) { body.append(field("增强预设", selectControl(presetOpts, draft.preset, (v) => { draft.preset = v; }))); } else { - body.append(el("div", "font-size:11px;color:#ffb74d;", "没有可用的增强预设(检查 LLM 预设模块是否正常加载)")); + body.append(el("div", "font-size:11px;color:var(--xb-warn);", "没有可用的增强预设(检查 LLM 预设模块是否正常加载)")); } body.append(el("div", "font-size:11px;color:#888;line-height:1.6;margin:2px 0 8px;", `该预设内容即「🧩 提示词设定」输出的正文,同时作为 LLM 的系统提示词;只列与输出语言 ${draft.lang} 相符的预设。`)); @@ -4350,7 +4350,7 @@ function xbrRenderPreset(body, node, draft, ctx) { if (taskOpts.length) { body.append(field("反推预设", selectControl(taskOpts, draft.task_preset, (v) => { draft.task_preset = v; }))); } else { - body.append(el("div", "font-size:11px;color:#ffb74d;", "没有可用的反推预设")); + body.append(el("div", "font-size:11px;color:var(--xb-warn);", "没有可用的反推预设")); } body.append(el("div", "font-size:11px;color:#888;line-height:1.6;margin:2px 0 8px;", "带 * 的预设里 * 是必填占位符,由节点上的提示词框或外接「📝 提示词」填入。")); @@ -4465,7 +4465,7 @@ function xbrRenderHelp(body) { " · 节点表面参数很少?故意的:选项都收在弹窗里,表面只留最常动的几个", " · 改完参数要重新执行节点才生效", ]; - body.append(el("div", "font-size:12px;color:#bbb;line-height:1.9;white-space:pre-wrap;font-family:ui-monospace,Consolas,monospace;", lines.join("\n"))); + body.append(el("div", "font-size:12px;color:var(--xb-text-dim);line-height:1.9;white-space:pre-wrap;font-family:ui-monospace,Consolas,monospace;", lines.join("\n"))); } const XBR_PANEL_RENDER = { llm: xbrRenderLlm, preset: xbrRenderPreset, help: xbrRenderHelp }; @@ -4571,12 +4571,12 @@ async function xbrOpenModal(node, panelId) { function xbrBuildButtonRow(node) { const grid = el("div", "display:grid;grid-template-columns:repeat(" + Math.max(1, XBR_PANEL_BUTTONS.length) + ",1fr);grid-auto-rows:30px;gap:6px;flex:0 0 auto;"); for (const b of XBR_PANEL_BUTTONS) { - const btn = el("button", "width:100%;height:30px;box-sizing:border-box;display:flex;align-items:center;justify-content:center;border-radius:6px;border:2px solid #5b9bd5;background:#3a3a3a;color:#eee;font-size:13px;cursor:pointer;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-family:inherit;", b.label); + const btn = el("button", "width:100%;height:30px;box-sizing:border-box;display:flex;align-items:center;justify-content:center;border-radius:6px;border:2px solid #5b9bd5;background:var(--xb-border);color:var(--xb-text);font-size:13px;cursor:pointer;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-family:inherit;", b.label); btn.title = `打开「${b.title}」弹窗`; btn.dataset.xbrProBtn = b.id; btn.addEventListener("mousedown", (e) => e.stopPropagation()); - btn.addEventListener("mouseenter", () => { btn.style.background = "#4a4a4a"; }); - btn.addEventListener("mouseleave", () => { btn.style.background = "#3a3a3a"; }); + btn.addEventListener("mouseenter", () => { btn.style.background = "var(--xb-border)"; }); + btn.addEventListener("mouseleave", () => { btn.style.background = "var(--xb-border)"; }); btn.addEventListener("click", (e) => { e.stopPropagation(); xbrOpenModal(node, b.id); }); grid.append(btn); } @@ -4622,13 +4622,13 @@ function xbrInfoLines(node, promptText) { /** 提示词框标题行(左标签 + 右「复制文本」)+ 参数设定显示;并挂上端口锁定/执行回写/摘要刷新 */ function xbrBuildPromptHeader(node, promptBox) { const head = el("div", "display:flex;align-items:center;gap:8px;flex:0 0 auto;"); - const lab = el("div", "font-size:12px;color:#bbb;", "📝 节点提示词(与面板预览框双向同步)"); + const lab = el("div", "font-size:12px;color:var(--xb-text-dim);", "📝 节点提示词(与面板预览框双向同步)"); // 与「🖼️ 生图提示词预设」的提示词框右上角按钮完全同一套尺寸 / 配色 - const copy = smallBtn("复制文本", "margin-left:auto;font-size:0.92em;line-height:1.35;height:1.9em;padding:0 0.75em;border-radius:4px;border:1px solid #555;background:#2b2b2b;color:#ddd;white-space:nowrap;", + const copy = smallBtn("复制文本", "margin-left:auto;font-size:0.92em;line-height:1.35;height:1.9em;padding:0 0.75em;border-radius:4px;border:1px solid var(--xb-border);background:var(--xb-bg-raise);color:var(--xb-text);white-space:nowrap;", "复制提示词框内容", () => { copyText(promptBox.value || ""); }); head.append(lab, copy); - const infoBox = el("div", "flex:0 0 auto;height:150px;overflow:auto;background:#1c1c1e;border:1px solid #333;border-radius:6px;padding:6px 9px;font-size:11px;line-height:1.65;color:#b9c6d2;white-space:pre-wrap;box-sizing:border-box;font-family:ui-monospace,Consolas,monospace;"); + const infoBox = el("div", "flex:0 0 auto;height:150px;overflow:auto;background:var(--xb-bg);border:1px solid var(--xb-border);border-radius:6px;padding:6px 9px;font-size:11px;line-height:1.65;color:#b9c6d2;white-space:pre-wrap;box-sizing:border-box;font-family:ui-monospace,Consolas,monospace;"); infoBox.dataset.captureWheel = "true"; infoBox.title = "当前配置摘要(只读)"; @@ -4642,8 +4642,8 @@ function xbrBuildPromptHeader(node, promptBox) { const inp = (node.inputs || []).find((i) => i.name === "text"); const linked = !!inp && inp.link != null; promptBox.readOnly = linked; - promptBox.style.background = linked ? "#141416" : "#1d1d1d"; - promptBox.style.color = linked ? "#8a8a8a" : "#ccc"; + promptBox.style.background = linked ? "#141416" : "var(--xb-bg)"; + promptBox.style.color = linked ? "var(--xb-text-dim)" : "var(--xb-text)"; promptBox.title = linked ? "已由「📝 提示词」端口控制,断开连线后恢复编辑" : ""; lab.textContent = linked ? "📝 提示词已锁定(由「📝 提示词」端口控制)" : "📝 节点提示词(与面板预览框双向同步)"; lab.style.color = linked ? "#d9a441" : "#bbb"; diff --git a/web/xb_list_dispatcher.js b/web/xb_list_dispatcher.js index bf52319..bf289b5 100644 --- a/web/xb_list_dispatcher.js +++ b/web/xb_list_dispatcher.js @@ -51,10 +51,10 @@ function setupDynamicOutputs(node) { const dw = res.widget; if (dw.inputEl) { dw.inputEl.readOnly = true; - dw.inputEl.style.backgroundColor = "#2a2a2a"; + dw.inputEl.style.backgroundColor = "var(--xb-bg-raise)"; dw.inputEl.style.color = "#bbbbbb"; dw.inputEl.style.fontSize = "12px"; - dw.inputEl.style.border = "1px solid #444"; + dw.inputEl.style.border = "1px solid var(--xb-border)"; } // Nodes 2.0:显示型字段走 options.read_only(Vue 部件没有 element 可改) try { if (dw.options) dw.options.read_only = true; } catch (_) {} diff --git a/web/xb_llama_prompt_reverse.js b/web/xb_llama_prompt_reverse.js index 598652f..7dec9c7 100644 --- a/web/xb_llama_prompt_reverse.js +++ b/web/xb_llama_prompt_reverse.js @@ -7,7 +7,7 @@ * ② 手写提示词编辑框(右上角「📋 复制」按钮;「📝 提示词」端口接线后自动锁定) * ③ 参数设定显示(只读摘要 4 行:LLM后端/语言/模型 | 增强预设 | 反推预设 | 温度等采样参数) * - * 弹窗(导演台同款):居中 overlay + `#1c1c1e` 面板 + header(标题/副标题)+ 内容 + 底部「取消 / 💾 保存」 + * 弹窗(导演台同款):居中 overlay + `var(--xb-bg)` 面板 + header(标题/副标题)+ 内容 + 底部「取消 / 💾 保存」 * · 🤖 大语言模型配置 = 复刻导演台「大语言模型配置」面板(去掉「开关」「故事扩写设置」两组) * · ✨ 提示词增强预设 = 增强预设 / 反推预设 / 追加设定 * · 📖 使用说明 @@ -89,11 +89,11 @@ function ensureStyle() { const st = el("style", ""); st.id = "xbr-style"; st.textContent = [ - ".xbr-input{box-sizing:border-box;background:var(--comfy-input-bg,#2a2a2a);color:var(--fg-color,#ddd);" + - "border:1px solid var(--border-color,#444);border-radius:4px;padding:6px 10px;font-size:13px;outline:none;font-family:inherit;}", - ".xbr-input:hover{background:var(--comfy-input-bg-hover,#3a3a3a);}", + ".xbr-input{box-sizing:border-box;background:var(--comfy-input-bg,var(--xb-bg-raise));color:var(--fg-color,var(--xb-text));" + + "border:1px solid var(--border-color,var(--xb-border));border-radius:4px;padding:6px 10px;font-size:13px;outline:none;font-family:inherit;}", + ".xbr-input:hover{background:var(--comfy-input-bg-hover,var(--xb-border));}", ".xbr-input:focus{border-color:#5b9bd5;}", - ".xbr-input option{background:var(--comfy-menu-bg,#2a2a2a);}", + ".xbr-input option{background:var(--comfy-menu-bg,var(--xb-bg-raise));}", ".xbr-modal input[type='checkbox']{width:18px;height:18px;accent-color:#f59e0b;cursor:pointer;}", ".xbr-modal textarea{line-height:1.5;}", ].join("\n"); @@ -174,7 +174,7 @@ function numberControl(value, opts, onChange) { return i; } function checkboxControl(value, labelText, onChange) { - const w = el("label", "display:flex;align-items:center;gap:10px;cursor:pointer;font-size:13px;color:var(--fg-color,#ddd);margin-bottom:8px;"); + const w = el("label", "display:flex;align-items:center;gap:10px;cursor:pointer;font-size:13px;color:var(--fg-color,var(--xb-text));margin-bottom:8px;"); const c = el("input", "width:18px;height:18px;accent-color:#f59e0b;cursor:pointer;"); c.type = "checkbox"; c.checked = !!value; @@ -216,8 +216,8 @@ function textareaControl(value, placeholder, rows, onChange) { return t; } function smallBtn(text, css, title) { - const b = el("button", "border:1px solid var(--border-color,#555);background:var(--comfy-button-bg,#333);" + - "color:var(--fg-color,#eee);border-radius:4px;padding:5px 12px;font-size:12px;cursor:pointer;white-space:nowrap;" + (css || ""), text); + const b = el("button", "border:1px solid var(--border-color,var(--xb-border));background:var(--comfy-button-bg,var(--xb-border));" + + "color:var(--fg-color,var(--xb-text));border-radius:4px;padding:5px 12px;font-size:12px;cursor:pointer;white-space:nowrap;" + (css || ""), text); b.type = "button"; if (title) b.title = title; return b; @@ -301,12 +301,12 @@ function buildUIRow(target) { const row = el("span", "display:inline-flex;align-items:center;gap:4px;user-select:none;"); row.append(el("span", "font-size:11px;color:#888;white-space:nowrap;", "界面")); const mk = (t, d) => { - const b = smallBtn(t, "width:22px;height:22px;padding:0;line-height:1;font-size:13px;border:1px solid #8a6d3b;background:#5a4a2a;color:#ffd98a;", d > 0 ? "放大整个界面(不缩放网页/画布)" : "缩小整个界面(不缩放网页/画布)"); + const b = smallBtn(t, "width:22px;height:22px;padding:0;line-height:1;font-size:13px;border:1px solid #8a6d3b;background:var(--xb-tint-warn);color:var(--xb-warn);", d > 0 ? "放大整个界面(不缩放网页/画布)" : "缩小整个界面(不缩放网页/画布)"); b.addEventListener("mousedown", (e) => e.stopPropagation()); b.addEventListener("click", (e) => { e.stopPropagation(); uiZoomStep(d); uiZoomApply(target); uiZoomSyncLabels(target); }); return b; }; - const val = el("span", "min-width:40px;text-align:center;font-size:12px;color:#ffd98a;font-variant-numeric:tabular-nums;", Math.round(uiZoomGet() * 100) + "%"); + const val = el("span", "min-width:40px;text-align:center;font-size:12px;color:var(--xb-warn);font-variant-numeric:tabular-nums;", Math.round(uiZoomGet() * 100) + "%"); val.classList.add("xbr-ui-zoom-val"); row.append(mk("−", -1), val, mk("+", 1)); return row; @@ -479,15 +479,15 @@ function buildDialog({ label, title, subtitle, width = 820, footerExtraFactory = ensureStyle(); const overlay = el("div", "position:fixed;inset:0;background:rgba(0,0,0,0.78);z-index:9999;display:flex;" + "align-items:center;justify-content:center;overflow:auto;"); - const dialog = el("section", `background:#1c1c1e;border:1px solid #333;border-radius:8px;width:${width}px;max-width:94vw;` + + const dialog = el("section", `background:var(--xb-bg);border:1px solid var(--xb-border);border-radius:8px;width:${width}px;max-width:94vw;` + "max-height:90vh;display:flex;flex-direction:column;box-shadow:0 20px 40px rgba(0,0,0,0.6);flex:0 0 auto;"); dialog.className = "xbr-modal"; dialog.setAttribute("role", "dialog"); dialog.setAttribute("aria-modal", "true"); dialog.setAttribute("aria-label", label); - const header = el("div", "padding:18px 20px;border-bottom:1px solid var(--border-color,#444);"); - header.append(el("div", "font-size:17px;font-weight:700;color:var(--fg-color,#eee);", title)); + const header = el("div", "padding:18px 20px;border-bottom:1px solid var(--border-color,var(--xb-border));"); + header.append(el("div", "font-size:17px;font-weight:700;color:var(--fg-color,var(--xb-text));", title)); if (subtitle) header.append(el("div", "font-size:12px;color:var(--descrip-text,#999);margin-top:5px;line-height:1.6;", subtitle)); dialog.append(header); @@ -495,8 +495,8 @@ function buildDialog({ label, title, subtitle, width = 820, footerExtraFactory = dialog.append(body); const error = el("div", "color:#e55;font-size:12px;margin:0 20px;min-height:14px;"); - const footer = el("div", "padding:14px 20px;border-top:1px solid var(--border-color,#444);display:flex;" + - "justify-content:flex-end;gap:10px;align-items:center;background:#18181a;border-bottom-left-radius:8px;border-bottom-right-radius:8px;"); + const footer = el("div", "padding:14px 20px;border-top:1px solid var(--border-color,var(--xb-border));display:flex;" + + "justify-content:flex-end;gap:10px;align-items:center;background:var(--xb-bg-soft);border-bottom-left-radius:8px;border-bottom-right-radius:8px;"); if (footerExtraFactory) { // 导演台同款:底部左侧放「自动保存 + 界面缩放」,与右侧「取消/保存」同行 const left = el("div", "display:flex;align-items:center;gap:16px;margin-right:auto;flex-wrap:wrap;"); @@ -507,7 +507,7 @@ function buildDialog({ label, title, subtitle, width = 820, footerExtraFactory = footer.append(cancelBtn); let saveBtn = null; if (onSave) { - saveBtn = smallBtn(saveLabel, "padding:8px 20px;font-size:14px;font-weight:600;background:#2d5a88;border:none;color:#fff;"); + saveBtn = smallBtn(saveLabel, "padding:8px 20px;font-size:14px;font-weight:600;background:var(--xb-btn-primary);border:none;color:#fff;"); footer.append(saveBtn); } dialog.append(error, footer); @@ -713,7 +713,7 @@ function renderHelpPanel(body) { " · 界面缩放作用于整个弹窗(不缩放网页/画布),Ctrl/⌘ + 滚轮 也可调,记忆在浏览器本地", " · 「取消」丢弃未保存改动;配置随工作流保存;在线 API 的 Key 存在 ComfyUI user 目录,不进工作流", ]; - body.append(el("div", "font-size:12px;color:#bbb;line-height:1.9;white-space:pre-wrap;font-family:ui-monospace,Consolas,monospace;", lines.join("\n"))); + body.append(el("div", "font-size:12px;color:var(--xb-text-dim);line-height:1.9;white-space:pre-wrap;font-family:ui-monospace,Consolas,monospace;", lines.join("\n"))); } const PANEL_RENDER = { llm: renderLlmPanel, preset: renderPresetPanel, help: renderHelpPanel }; @@ -884,7 +884,7 @@ function setupNode(node) { const b = el("button", [ "width:100%", `height:${BTN_H}px`, "box-sizing:border-box", "display:flex", "align-items:center", "justify-content:center", - "border-radius:6px", "border:2px solid #5b9bd5", "background:#3a3a3a", "color:#eee", + "border-radius:6px", "border:2px solid #5b9bd5", "background:var(--xb-border)", "color:var(--xb-text)", "font-size:14px", "cursor:pointer", "white-space:nowrap", "overflow:hidden", "text-overflow:ellipsis", "padding:0 6px", "font-family:inherit", ].join(";")); @@ -893,8 +893,8 @@ function setupNode(node) { b.title = `打开「${p.title}」弹窗`; b.dataset.xbrPanelBtn = p.id; b.addEventListener("mousedown", (e) => e.stopPropagation()); - b.addEventListener("mouseenter", () => { b.style.background = "#4a4a4a"; }); - b.addEventListener("mouseleave", () => { b.style.background = "#3a3a3a"; }); + b.addEventListener("mouseenter", () => { b.style.background = "var(--xb-border)"; }); + b.addEventListener("mouseleave", () => { b.style.background = "var(--xb-border)"; }); b.addEventListener("click", () => openPanelModal(node, p.id)); btnGrid.append(b); } @@ -919,7 +919,7 @@ function setupNode(node) { promptWrap.append(promptHead); const promptBox = el("textarea", "flex:1 1 auto;min-height:60px;width:100%;box-sizing:border-box;resize:none;line-height:1.5;" + - "font-family:inherit;background:#1c1c1e;color:var(--fg-color,#ddd);border:1px solid var(--border-color,#444);" + + "font-family:inherit;background:var(--xb-bg);color:var(--fg-color,var(--xb-text));border:1px solid var(--border-color,var(--xb-border));" + "border-radius:6px;padding:8px 10px;font-size:13px;outline:none;"); promptBox.value = String(readWidgetValue(wCustom) ?? ""); promptBox.placeholder = "在这里写提示词 / 或带 * 预设的占位符内容(如 BBox 检测的目标名称)"; @@ -931,7 +931,7 @@ function setupNode(node) { promptTimer = setTimeout(() => { setWidgetValue(wCustom, promptBox.value); node.setDirtyCanvas?.(true, true); }, 250); }); promptBox.addEventListener("focus", () => { if (!promptBox.readOnly) promptBox.style.borderColor = "#5b9bd5"; }); - promptBox.addEventListener("blur", () => { promptBox.style.borderColor = "var(--border-color,#444)"; }); + promptBox.addEventListener("blur", () => { promptBox.style.borderColor = "var(--border-color,var(--xb-border))"; }); promptWrap.append(promptBox); container.append(promptWrap); @@ -941,9 +941,9 @@ function setupNode(node) { try { const linked = !!textInput() && textInput().link != null; promptBox.readOnly = linked; - promptBox.style.background = linked ? "#141416" : "#1c1c1e"; - promptBox.style.color = linked ? "#8a8a8a" : "var(--fg-color,#ddd)"; - promptBox.style.borderColor = linked ? "#5a5a5a" : "var(--border-color,#444)"; + promptBox.style.background = linked ? "#141416" : "var(--xb-bg)"; + promptBox.style.color = linked ? "var(--xb-text-dim)" : "var(--fg-color,var(--xb-text))"; + promptBox.style.borderColor = linked ? "#5a5a5a" : "var(--border-color,var(--xb-border))"; promptBox.title = linked ? "已由「📝 提示词」端口控制,断开连线后恢复编辑" : ""; lockHint.textContent = linked ? "🔒 手写提示词已锁定(由「📝 提示词」端口控制)" @@ -954,7 +954,7 @@ function setupNode(node) { node.__xbrUpdateLock = updateLock; // ⑦ 参数设定显示(只读摘要) - const infoBox = el("div", `flex:0 0 auto;height:${MIN_INFO_H}px;overflow:auto;background:#1c1c1e;border:1px solid #333;` + + const infoBox = el("div", `flex:0 0 auto;height:${MIN_INFO_H}px;overflow:auto;background:var(--xb-bg);border:1px solid var(--xb-border);` + "border-radius:6px;padding:6px 9px;font-size:11px;line-height:1.65;color:#b9c6d2;white-space:pre-wrap;box-sizing:border-box;font-family:ui-monospace,Consolas,monospace;"); infoBox.dataset.captureWheel = "true"; container.append(infoBox); diff --git a/web/xb_qwen_prompt_preset.js b/web/xb_qwen_prompt_preset.js index c3a298b..d1ee224 100644 --- a/web/xb_qwen_prompt_preset.js +++ b/web/xb_qwen_prompt_preset.js @@ -1400,15 +1400,15 @@ function el(tag, css, text) { return e; } -const BOX_CSS = "background:#1d1d1d;color:#ccc;border:1px solid #444;border-radius:4px;padding:5px 8px;font-size:12px;box-sizing:border-box;font-family:inherit;"; +const BOX_CSS = "background:var(--xb-bg);color:var(--xb-text);border:1px solid var(--xb-border);border-radius:4px;padding:5px 8px;font-size:12px;box-sizing:border-box;font-family:inherit;"; function makeSectionTitle(text) { - return el("div", "font-size:13px;font-weight:600;color:#9fc3e8;margin:14px 0 8px;padding-bottom:5px;border-bottom:1px solid #333;", text); + return el("div", "font-size:13px;font-weight:600;color:var(--xb-info);margin:14px 0 8px;padding-bottom:5px;border-bottom:1px solid var(--xb-border);", text); } function field(labelText, control) { const g = el("div", "display:flex;align-items:center;gap:12px;margin-bottom:10px;"); - const lab = el("label", "flex:0 0 160px;font-size:13px;color:#999;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;", labelText); + const lab = el("label", "flex:0 0 160px;font-size:13px;color:var(--xb-text-dim);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;", labelText); control.style.flex = "1 1 0"; control.style.minWidth = "0"; g.append(lab, control); @@ -1435,7 +1435,7 @@ function radioRow(options, value, onChange) { for (const o of options) { const val = (typeof o === "object" && o !== null && o.value !== undefined) ? String(o.value) : String(o); const label = (typeof o === "object" && o !== null && o.label) ? String(o.label) : String(o); - const lab = el("label", "display:flex;align-items:center;gap:6px;font-size:13px;color:#999;cursor:pointer;user-select:none;"); + const lab = el("label", "display:flex;align-items:center;gap:6px;font-size:13px;color:var(--xb-text-dim);cursor:pointer;user-select:none;"); const c = el("input", "width:18px;height:18px;accent-color:#f59e0b;cursor:pointer;"); c.type = "checkbox"; c.checked = String(value) === val; @@ -1462,7 +1462,7 @@ function textareaControl(value, onChange, css) { } function checkboxControl(value, labelText, onChange, tip) { - const w = el("label", "display:flex;align-items:center;gap:8px;cursor:pointer;font-size:12px;color:#ddd;user-select:none;"); + const w = el("label", "display:flex;align-items:center;gap:8px;cursor:pointer;font-size:12px;color:var(--xb-text);user-select:none;"); const c = el("input", "width:16px;height:16px;accent-color:#f59e0b;cursor:pointer;"); c.type = "checkbox"; c.checked = !!value; @@ -1524,10 +1524,10 @@ function zoomSync(root) { } function buildZoomRow(root) { const row = el("span", "display:inline-flex;align-items:center;gap:4px;user-select:none;"); - const minus = smallBtn("A−", "border:1px solid #5b9bd5;background:#2a4a6a;color:#cfe3f7;width:22px;height:22px;padding:0;line-height:1;", "缩小编辑字号(Ctrl/⌘+滚轮 也可)"); - const val = el("span", "min-width:26px;text-align:center;font-size:12px;color:#e8a87c;font-variant-numeric:tabular-nums;", String(zoomDisplay())); + const minus = smallBtn("A−", "border:1px solid #5b9bd5;background:var(--xb-tint-info);color:var(--xb-info);width:22px;height:22px;padding:0;line-height:1;", "缩小编辑字号(Ctrl/⌘+滚轮 也可)"); + const val = el("span", "min-width:26px;text-align:center;font-size:12px;color:var(--xb-orange);font-variant-numeric:tabular-nums;", String(zoomDisplay())); val.classList.add("ipp-zoom-val"); - const plus = smallBtn("A+", "border:1px solid #5b9bd5;background:#2a4a6a;color:#cfe3f7;width:22px;height:22px;padding:0;line-height:1;", "放大编辑字号(Ctrl/⌘+滚轮 也可)"); + const plus = smallBtn("A+", "border:1px solid #5b9bd5;background:var(--xb-tint-info);color:var(--xb-info);width:22px;height:22px;padding:0;line-height:1;", "放大编辑字号(Ctrl/⌘+滚轮 也可)"); const step = (d) => { zoomSet((zoomGet() == null ? 12 : zoomGet()) + d); zoomApplyContainer(root); @@ -1571,10 +1571,10 @@ window.addEventListener("resize", () => { function buildUIRow(target) { const row = el("span", "display:inline-flex;align-items:center;gap:4px;user-select:none;"); row.append(el("span", "font-size:11px;color:#888;white-space:nowrap;", "界面")); - const minus = smallBtn("−", "border:1px solid #8a6d3b;background:#5a4a2a;color:#ffd98a;width:22px;height:22px;padding:0;line-height:1;font-size:13px;", "缩小整个界面(Ctrl/⌘+滚轮 也可)"); - const val = el("span", "min-width:42px;text-align:center;font-size:12px;color:#ffd98a;font-variant-numeric:tabular-nums;", Math.round(uiGet() * 100) + "%"); + const minus = smallBtn("−", "border:1px solid #8a6d3b;background:var(--xb-tint-warn);color:var(--xb-warn);width:22px;height:22px;padding:0;line-height:1;font-size:13px;", "缩小整个界面(Ctrl/⌘+滚轮 也可)"); + const val = el("span", "min-width:42px;text-align:center;font-size:12px;color:var(--xb-warn);font-variant-numeric:tabular-nums;", Math.round(uiGet() * 100) + "%"); val.classList.add("ipp-ui-val"); - const plus = smallBtn("+", "border:1px solid #8a6d3b;background:#5a4a2a;color:#ffd98a;width:22px;height:22px;padding:0;line-height:1;font-size:13px;", "放大整个界面(Ctrl/⌘+滚轮 也可)"); + const plus = smallBtn("+", "border:1px solid #8a6d3b;background:var(--xb-tint-warn);color:var(--xb-warn);width:22px;height:22px;padding:0;line-height:1;font-size:13px;", "放大整个界面(Ctrl/⌘+滚轮 也可)"); const step = (d) => { uiSet(uiGet() + d * UI_STEP); uiApply(target); @@ -1838,12 +1838,12 @@ function openPanel(ctx, panelId) { const title = `${cat.label} 元素`; const overlay = el("div", "position:fixed;inset:0;background:rgba(0,0,0,.78);z-index:9999;display:flex;align-items:center;justify-content:center;overflow:auto;"); - const dialog = el("section", "background:#1c1c1e;border:1px solid #333;border-radius:8px;width:880px;max-height:90vh;display:flex;flex-direction:column;box-shadow:0 20px 40px rgba(0,0,0,.6);flex:0 0 auto;color:#ddd;font-size:13px;"); + const dialog = el("section", "background:var(--xb-bg);border:1px solid var(--xb-border);border-radius:8px;width:880px;max-height:90vh;display:flex;flex-direction:column;box-shadow:0 20px 40px rgba(0,0,0,.6);flex:0 0 auto;color:var(--xb-text);font-size:13px;"); - const header = el("div", "padding:16px 20px 12px;border-bottom:1px solid #444;"); - const titleEl = el("div", "font-size:18px;font-weight:700;color:#eee;", title); + const header = el("div", "padding:16px 20px 12px;border-bottom:1px solid var(--xb-border);"); + const titleEl = el("div", "font-size:18px;font-weight:700;color:var(--xb-text);", title); header.append(titleEl); - header.append(el("div", "font-size:12px;color:#999;margin-top:5px;", "点选项行即加入提示词(变蓝),再点一下移除(变灰);点【添加详细描述】会在新窗口里给该词条写补充描述(悬停行可看)。配置随工作流保存,改完重新执行节点即生效。")); + header.append(el("div", "font-size:12px;color:var(--xb-text-dim);margin-top:5px;", "点选项行即加入提示词(变蓝),再点一下移除(变灰);点【添加详细描述】会在新窗口里给该词条写补充描述(悬停行可看)。配置随工作流保存,改完重新执行节点即生效。")); dialog.append(header); // ⚠️ 顶部不留内边距:滚动容器的 padding 区不参与裁剪,滚上去的行会从这条缝里 @@ -1851,12 +1851,12 @@ function openPanel(ctx, panelId) { const panelBox = el("div", "padding:0 20px 14px;overflow-y:auto;flex:1;min-height:280px;"); // ── 吸顶预览框(不受滚动条约束,始终在画面内) ── - const sticky = el("div", "position:sticky;top:0;z-index:6;background:#1c1c1e;padding:16px 0 10px;margin:0 0 6px;border-bottom:1px solid #333;"); + const sticky = el("div", "position:sticky;top:0;z-index:6;background:var(--xb-bg);padding:16px 0 10px;margin:0 0 6px;border-bottom:1px solid var(--xb-border);"); const head = el("div", "display:flex;align-items:center;gap:8px;margin-bottom:6px;flex-wrap:wrap;"); - head.append(el("span", "font-size:12px;font-weight:600;color:#9fc3e8;", "📝 提示词预览(与节点提示词框实时同步,可直接编辑)")); + head.append(el("span", "font-size:12px;font-weight:600;color:var(--xb-info);", "📝 提示词预览(与节点提示词框实时同步,可直接编辑)")); const headCount = el("span", "font-size:11px;color:#888;", ""); head.append(headCount); - const presetChip = el("div", "font-size:11px;color:#f0d79a;background:#2a2416;border:1px dashed #8a6d3b;border-radius:4px;padding:4px 8px;margin-bottom:6px;white-space:pre-wrap;line-height:1.5;display:none;"); + const presetChip = el("div", "font-size:11px;color:var(--xb-warn);background:var(--xb-tint-warn);border:1px dashed #8a6d3b;border-radius:4px;padding:4px 8px;margin-bottom:6px;white-space:pre-wrap;line-height:1.5;display:none;"); const preview = el("textarea", BOX_CSS + "width:100%;min-height:74px;max-height:210px;resize:vertical;line-height:1.65;font-size:12px;"); preview.spellcheck = false; preview.placeholder = "点左侧 + 添加元素,或直接在这里手写提示词…"; @@ -1897,10 +1897,10 @@ function openPanel(ctx, panelId) { // ── 底部 ── const error = el("div", "color:#e55;font-size:12px;margin:0 20px;min-height:16px;"); - const footer = el("div", "padding:12px 20px;border-top:1px solid #444;display:flex;justify-content:flex-end;gap:10px;background:#18181a;border-bottom-left-radius:8px;border-bottom-right-radius:8px;"); - const cancelBtn = el("button", "background:transparent;border:1px solid #555;color:#fff;border-radius:4px;padding:8px 20px;font-size:14px;cursor:pointer;font-family:inherit;", "取消"); - const saveBtn = el("button", "background:#2d5a88;color:#fff;border:none;border-radius:4px;padding:8px 20px;font-size:14px;font-weight:600;cursor:pointer;font-family:inherit;", "💾 保存"); - const autoRow = el("label", "display:flex;align-items:center;gap:6px;font-size:13px;color:#999;cursor:pointer;user-select:none;"); + const footer = el("div", "padding:12px 20px;border-top:1px solid var(--xb-border);display:flex;justify-content:flex-end;gap:10px;background:var(--xb-bg-soft);border-bottom-left-radius:8px;border-bottom-right-radius:8px;"); + const cancelBtn = el("button", "background:transparent;border:1px solid var(--xb-border);color:#fff;border-radius:4px;padding:8px 20px;font-size:14px;cursor:pointer;font-family:inherit;", "取消"); + const saveBtn = el("button", "background:var(--xb-btn-primary);color:#fff;border:none;border-radius:4px;padding:8px 20px;font-size:14px;font-weight:600;cursor:pointer;font-family:inherit;", "💾 保存"); + const autoRow = el("label", "display:flex;align-items:center;gap:6px;font-size:13px;color:var(--xb-text-dim);cursor:pointer;user-select:none;"); const autoCb = el("input", "width:18px;height:18px;accent-color:#f59e0b;cursor:pointer;"); autoCb.type = "checkbox"; autoCb.checked = settings.auto_save !== false; @@ -1985,13 +1985,13 @@ const CAT_UI = {}; // 会话级:cat -> { collapsed, onlySel, keyword }(不 * ============================================================ */ function openConfirm(title, message, onOk, okText) { const overlay = el("div", "position:fixed;inset:0;background:rgba(0,0,0,.72);z-index:10200;display:flex;align-items:center;justify-content:center;"); - const box = el("section", "background:#1c1c1e;border:1px solid #333;border-radius:8px;width:460px;max-width:92vw;box-shadow:0 20px 40px rgba(0,0,0,.6);padding:18px;box-sizing:border-box;color:#ddd;font-size:13px;"); + const box = el("section", "background:var(--xb-bg);border:1px solid var(--xb-border);border-radius:8px;width:460px;max-width:92vw;box-shadow:0 20px 40px rgba(0,0,0,.6);padding:18px;box-sizing:border-box;color:var(--xb-text);font-size:13px;"); box.dataset.ippModal = "confirm"; - box.append(el("div", "font-size:15px;font-weight:700;color:#eee;margin-bottom:8px;", title || "确认")); - box.append(el("div", "font-size:13px;color:#bbb;line-height:1.8;white-space:pre-wrap;margin-bottom:14px;", message || "")); + box.append(el("div", "font-size:15px;font-weight:700;color:var(--xb-text);margin-bottom:8px;", title || "确认")); + box.append(el("div", "font-size:13px;color:var(--xb-text-dim);line-height:1.8;white-space:pre-wrap;margin-bottom:14px;", message || "")); const row = el("div", "display:flex;gap:10px;justify-content:flex-end;"); - const cancelBtn = el("button", "background:transparent;border:1px solid #555;color:#fff;border-radius:4px;padding:7px 18px;font-size:13px;cursor:pointer;font-family:inherit;", "取消"); - const okBtn = el("button", "background:#8a3a2a;border:1px solid #c2604a;color:#ffe7df;border-radius:4px;padding:7px 18px;font-size:13px;font-weight:600;cursor:pointer;font-family:inherit;", okText || "确定"); + const cancelBtn = el("button", "background:transparent;border:1px solid var(--xb-border);color:#fff;border-radius:4px;padding:7px 18px;font-size:13px;cursor:pointer;font-family:inherit;", "取消"); + const okBtn = el("button", "background:var(--xb-tint-error);border:1px solid #c2604a;color:#ffe7df;border-radius:4px;padding:7px 18px;font-size:13px;font-weight:600;cursor:pointer;font-family:inherit;", okText || "确定"); const close = () => { try { overlay.remove(); } catch (_) {} }; cancelBtn.addEventListener("click", (e) => { e.stopPropagation(); close(); }); okBtn.addEventListener("click", (e) => { e.stopPropagation(); close(); try { onOk && onOk(); } catch (_) {} }); @@ -2009,29 +2009,29 @@ function openConfirm(title, message, onOk, okText) { * ============================================================ */ function openRelPicker(node, nameText, lang, current, onPick) { const overlay = el("div", "position:fixed;inset:0;background:rgba(0,0,0,.78);z-index:10200;display:flex;align-items:center;justify-content:center;"); - const box = el("section", "background:#1c1c1e;border:1px solid #333;border-radius:8px;width:780px;max-width:94vw;max-height:92vh;overflow:auto;box-shadow:0 20px 40px rgba(0,0,0,.6);padding:18px;box-sizing:border-box;color:#ddd;font-size:13px;"); + const box = el("section", "background:var(--xb-bg);border:1px solid var(--xb-border);border-radius:8px;width:780px;max-width:94vw;max-height:92vh;overflow:auto;box-shadow:0 20px 40px rgba(0,0,0,.6);padding:18px;box-sizing:border-box;color:var(--xb-text);font-size:13px;"); box.dataset.ippModal = "rel"; - box.append(el("div", "font-size:15px;font-weight:700;color:#eee;margin-bottom:4px;", "🎒 道具与主体的关系")); - box.append(el("div", "font-size:12px;color:#8a97a5;margin-bottom:12px;line-height:1.7;", + box.append(el("div", "font-size:15px;font-weight:700;color:var(--xb-text);margin-bottom:4px;", "🎒 道具与主体的关系")); + box.append(el("div", "font-size:12px;color:var(--xb-text-dim);margin-bottom:12px;line-height:1.7;", `道具:${nameText}\n选一个关系词,拼进提示词时它会加在道具前面(例:手拿折扇 / a folding fan, held in the hand)。`)); const grid = el("div", "display:grid;grid-template-columns:repeat(6,1fr);gap:6px;margin-bottom:14px;"); const close = () => { try { overlay.remove(); } catch (_) {} }; for (const [id, zh, en] of RELATIONS) { const on = id === current; - const b = el("button", `display:flex;flex-direction:column;align-items:center;justify-content:center;gap:2px;min-height:46px;border-radius:6px;cursor:pointer;font-family:inherit;padding:4px 2px;box-sizing:border-box;border:1px solid ${on ? "#7fb3e0" : "#4a4a4a"};background:${on ? "#2d5a88" : "#262626"};color:${on ? "#eaf4ff" : "#ddd"};`); + const b = el("button", `display:flex;flex-direction:column;align-items:center;justify-content:center;gap:2px;min-height:46px;border-radius:6px;cursor:pointer;font-family:inherit;padding:4px 2px;box-sizing:border-box;border:1px solid ${on ? "#7fb3e0" : "var(--xb-border)"};background:${on ? "#2d5a88" : "var(--xb-bg-raise)"};color:${on ? "#eaf4ff" : "var(--xb-text)"};`); b.dataset.rel = id; b.title = `${zh} / ${en}`; b.append(el("span", "font-size:13px;line-height:1.2;", zh)); b.append(el("span", "font-size:9px;line-height:1.2;color:#9fb6cc;text-align:center;word-break:break-word;", en)); - b.addEventListener("mouseenter", () => { if (!on) b.style.background = "#333"; }); - b.addEventListener("mouseleave", () => { if (!on) b.style.background = "#262626"; }); + b.addEventListener("mouseenter", () => { if (!on) b.style.background = "var(--xb-border)"; }); + b.addEventListener("mouseleave", () => { if (!on) b.style.background = "var(--xb-bg-raise)"; }); b.addEventListener("click", (e) => { e.stopPropagation(); close(); try { onPick && onPick(id); } catch (_) {} }); grid.append(b); } box.append(grid); const row = el("div", "display:flex;gap:10px;justify-content:flex-end;"); - const noneBtn = el("button", "background:#2a4a6a;border:1px solid #5b9bd5;color:#cfe3f7;border-radius:4px;padding:7px 16px;font-size:13px;cursor:pointer;font-family:inherit;", "不加关系(只加道具词)"); - const cancelBtn = el("button", "background:transparent;border:1px solid #555;color:#fff;border-radius:4px;padding:7px 18px;font-size:13px;cursor:pointer;font-family:inherit;", "取消"); + const noneBtn = el("button", "background:var(--xb-tint-info);border:1px solid #5b9bd5;color:var(--xb-info);border-radius:4px;padding:7px 16px;font-size:13px;cursor:pointer;font-family:inherit;", "不加关系(只加道具词)"); + const cancelBtn = el("button", "background:transparent;border:1px solid var(--xb-border);color:#fff;border-radius:4px;padding:7px 18px;font-size:13px;cursor:pointer;font-family:inherit;", "取消"); noneBtn.addEventListener("click", (e) => { e.stopPropagation(); close(); try { onPick && onPick(""); } catch (_) {} }); cancelBtn.addEventListener("click", (e) => { e.stopPropagation(); close(); }); overlay.addEventListener("mousedown", (e) => { if (e.target === overlay) close(); }); @@ -2049,10 +2049,10 @@ function openRelPicker(node, nameText, lang, current, onPick) { * ============================================================ */ function openDescEditor(node, entry, nameText, value, onSave) { const overlay = el("div", "position:fixed;inset:0;background:rgba(0,0,0,.72);z-index:10100;display:flex;align-items:center;justify-content:center;"); - const box = el("section", "background:#1c1c1e;border:1px solid #333;border-radius:8px;width:600px;max-width:92vw;box-shadow:0 20px 40px rgba(0,0,0,.6);padding:18px;box-sizing:border-box;color:#ddd;font-size:13px;"); + const box = el("section", "background:var(--xb-bg);border:1px solid var(--xb-border);border-radius:8px;width:600px;max-width:92vw;box-shadow:0 20px 40px rgba(0,0,0,.6);padding:18px;box-sizing:border-box;color:var(--xb-text);font-size:13px;"); box.dataset.ippModal = "desc"; // 稳定锤点(便于定位/自动化测试) - box.append(el("div", "font-size:15px;font-weight:700;color:#eee;margin-bottom:4px;", "📝 添加详细描述")); - box.append(el("div", "font-size:12px;color:#8a97a5;margin-bottom:10px;line-height:1.7;white-space:pre-wrap;", + box.append(el("div", "font-size:15px;font-weight:700;color:var(--xb-text);margin-bottom:4px;", "📝 添加详细描述")); + box.append(el("div", "font-size:12px;color:var(--xb-text-dim);margin-bottom:10px;line-height:1.7;white-space:pre-wrap;", `词条:${nameText}\n` + "写在这里的内容会以「词条(描述)」的形式拼进提示词(例:一位女性(银灰色短发))," + "所以只写要补的细节即可 —— 不用重复词条本身,也别写整句话。")); @@ -2064,9 +2064,9 @@ function openDescEditor(node, entry, nameText, value, onSave) { const tip = el("div", "font-size:11px;color:#777;margin-top:6px;", "Esc = 取消 · Ctrl/⌘ + Enter = 保存"); box.append(tip); const row = el("div", "display:flex;justify-content:flex-end;gap:10px;margin-top:14px;"); - const cancelBtn = el("button", "background:transparent;border:1px solid #555;color:#fff;border-radius:4px;padding:7px 18px;font-size:13px;cursor:pointer;font-family:inherit;", "取消"); - const clrBtn = el("button", "background:transparent;border:1px solid #844;color:#ffb0b0;border-radius:4px;padding:7px 14px;font-size:13px;cursor:pointer;font-family:inherit;margin-right:auto;", "🗑 清除描述"); - const okBtn = el("button", "background:#2d5a88;color:#fff;border:none;border-radius:4px;padding:7px 20px;font-size:13px;font-weight:600;cursor:pointer;font-family:inherit;", "✅ 保存描述"); + const cancelBtn = el("button", "background:transparent;border:1px solid var(--xb-border);color:#fff;border-radius:4px;padding:7px 18px;font-size:13px;cursor:pointer;font-family:inherit;", "取消"); + const clrBtn = el("button", "background:transparent;border:1px solid #844;color:var(--xb-error);border-radius:4px;padding:7px 14px;font-size:13px;cursor:pointer;font-family:inherit;margin-right:auto;", "🗑 清除描述"); + const okBtn = el("button", "background:var(--xb-btn-primary);color:#fff;border:none;border-radius:4px;padding:7px 20px;font-size:13px;font-weight:600;cursor:pointer;font-family:inherit;", "✅ 保存描述"); row.append(clrBtn, cancelBtn, okBtn); box.append(row); overlay.append(box); @@ -2090,11 +2090,11 @@ function openDescEditor(node, entry, nameText, value, onSave) { /** 自建槽位的「中/英名称」编辑窗(同样是一个小弹窗) */ function openSlotNameEditor(zhValue, enValue, onSave) { const overlay = el("div", "position:fixed;inset:0;background:rgba(0,0,0,.72);z-index:10100;display:flex;align-items:center;justify-content:center;"); - const box = el("section", "background:#1c1c1e;border:1px solid #333;border-radius:8px;width:520px;max-width:92vw;box-shadow:0 20px 40px rgba(0,0,0,.6);padding:18px;box-sizing:border-box;color:#ddd;font-size:13px;"); + const box = el("section", "background:var(--xb-bg);border:1px solid var(--xb-border);border-radius:8px;width:520px;max-width:92vw;box-shadow:0 20px 40px rgba(0,0,0,.6);padding:18px;box-sizing:border-box;color:var(--xb-text);font-size:13px;"); box.dataset.ippModal = "slotname"; // 稳定锤点 - box.append(el("div", "font-size:15px;font-weight:700;color:#eee;margin-bottom:10px;", "✎ 自建槽位名称(中 / 英)")); + box.append(el("div", "font-size:15px;font-weight:700;color:var(--xb-text);margin-bottom:10px;", "✎ 自建槽位名称(中 / 英)")); const mk = (label, val, ph) => { - box.append(el("div", "font-size:11px;color:#8a97a5;margin:6px 0 3px;", label)); + box.append(el("div", "font-size:11px;color:var(--xb-text-dim);margin:6px 0 3px;", label)); const i = el("input", BOX_CSS + "width:100%;font-size:13px;padding:6px 8px;"); i.type = "text"; i.value = val || ""; @@ -2105,8 +2105,8 @@ function openSlotNameEditor(zhValue, enValue, onSave) { const zh = mk("中文词条(中文 [ZH] 输出时用)", zhValue, "例:银灰色短发"); const en = mk("English word(英文 [EN] 输出时用)", enValue, "e.g. silver-gray short hair"); const row = el("div", "display:flex;justify-content:flex-end;gap:10px;margin-top:14px;"); - const cancelBtn = el("button", "background:transparent;border:1px solid #555;color:#fff;border-radius:4px;padding:7px 18px;font-size:13px;cursor:pointer;font-family:inherit;", "取消"); - const okBtn = el("button", "background:#2d5a88;color:#fff;border:none;border-radius:4px;padding:7px 20px;font-size:13px;font-weight:600;cursor:pointer;font-family:inherit;", "✅ 保存名称"); + const cancelBtn = el("button", "background:transparent;border:1px solid var(--xb-border);color:#fff;border-radius:4px;padding:7px 18px;font-size:13px;cursor:pointer;font-family:inherit;", "取消"); + const okBtn = el("button", "background:var(--xb-btn-primary);color:#fff;border:none;border-radius:4px;padding:7px 20px;font-size:13px;font-weight:600;cursor:pointer;font-family:inherit;", "✅ 保存名称"); row.append(cancelBtn, okBtn); box.append(row); overlay.append(box); @@ -2141,12 +2141,12 @@ function renderCategoryPanel(box, api, catId) { catRow.dataset.ippCatRow = "1"; for (const c of CATEGORIES) { const on = c.id === catId; - const b = el("button", `box-sizing:border-box;height:30px;display:flex;align-items:center;justify-content:center;border-radius:6px;border:2px solid ${on ? "#7fb3e0" : "#5b9bd5"};background:${on ? "#2d5a88" : "#3a3a3a"};color:${on ? "#eaf4ff" : "#eee"};font-size:13px;cursor:pointer;font-family:inherit;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;`, c.label); + const b = el("button", `box-sizing:border-box;height:30px;display:flex;align-items:center;justify-content:center;border-radius:6px;border:2px solid ${on ? "#7fb3e0" : "#5b9bd5"};background:${on ? "#2d5a88" : "var(--xb-border)"};color:${on ? "#eaf4ff" : "var(--xb-text)"};font-size:13px;cursor:pointer;font-family:inherit;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;`, c.label); b.dataset.cat = c.id; if (on) b.dataset.catActive = "1"; b.title = `切到「${c.label}」面板 —— ${c.tip}`; - b.addEventListener("mouseenter", () => { if (!on) b.style.background = "#4a4a4a"; }); - b.addEventListener("mouseleave", () => { if (!on) b.style.background = "#3a3a3a"; }); + b.addEventListener("mouseenter", () => { if (!on) b.style.background = "var(--xb-border)"; }); + b.addEventListener("mouseleave", () => { if (!on) b.style.background = "var(--xb-border)"; }); b.addEventListener("click", (e) => { e.stopPropagation(); if (!on && api.switchCategory) api.switchCategory(c.id); @@ -2156,7 +2156,7 @@ function renderCategoryPanel(box, api, catId) { box.append(catRow); const bar = el("div", "display:flex;gap:8px;flex-wrap:wrap;margin-bottom:6px;"); bar.append( - smallBtn("🧹 清空提示词", "border:1px solid #a44;background:#7a2a2a;color:#ffd0d0;padding:6px 12px;", "清空正文(同时清掉所有元素的「已选」状态)", () => { + smallBtn("🧹 清空提示词", "border:1px solid #a44;background:var(--xb-tint-error);color:var(--xb-error);padding:6px 12px;", "清空正文(同时清掉所有元素的「已选」状态)", () => { openConfirm("🧹 清空提示词", "确定清空提示词正文?\n(所有元素的「已选」状态也会一起清掉)", () => { s.elements.selected = {}; s.elements.rel = {}; @@ -2166,17 +2166,17 @@ function renderCategoryPanel(box, api, catId) { notify("已清空提示词", "success"); }); }), - smallBtn("🔧 按选择重建", "border:1px solid #5b9bd5;background:#2a4a6a;color:#cfe3f7;padding:6px 12px;", "按「已选元素 + 固定分类顺序(风格→视角→主体→姿态→装扮→道具→光影→背景)+ 当前语言」重排正文(手工改乱了就用它)", () => { + smallBtn("🔧 按选择重建", "border:1px solid #5b9bd5;background:var(--xb-tint-info);color:var(--xb-info);padding:6px 12px;", "按「已选元素 + 固定分类顺序(风格→视角→主体→姿态→装扮→道具→光影→背景)+ 当前语言」重排正文(手工改乱了就用它)", () => { const t = rebuildBody(s, index, lang()); bodySet(t); fire(); notify(t ? "已按已选元素重建正文" : "当前没有已选元素(重建后正文为空)", t ? "success" : "warning"); }), - smallBtn("📋 复制成句", "border:1px solid #555;background:#2a2a2a;color:#ccc;padding:6px 12px;", "复制最终成句(三视图模式下含预设句)", () => { + smallBtn("📋 复制成句", "border:1px solid var(--xb-border);background:var(--xb-bg-raise);color:var(--xb-text);padding:6px 12px;", "复制最终成句(三视图模式下含预设句)", () => { copyText(finalPrompt(surf.mode, surf.presetText, surf.lang, bodyGet(), surf.io)); }), - smallBtn("⬆️ 导出词表", "border:1px solid #6b9b5b;background:#3a5a2a;color:#d7f0c8;padding:6px 12px;", "把「自建槽位 + 已选 + 描述」导出成 JSON 备份", () => exportJson(s)), - smallBtn("⬇️ 导入词表", "border:1px solid #6b9b5b;background:#3a5a2a;color:#d7f0c8;padding:6px 12px;", "从 JSON 恢复「自建槽位 + 已选 + 描述」(合并,不覆盖内置词表)", () => importJson((obj) => { + smallBtn("⬆️ 导出词表", "border:1px solid #6b9b5b;background:var(--xb-tint-ok);color:var(--xb-ok);padding:6px 12px;", "把「自建槽位 + 已选 + 描述」导出成 JSON 备份", () => exportJson(s)), + smallBtn("⬇️ 导入词表", "border:1px solid #6b9b5b;background:var(--xb-tint-ok);color:var(--xb-ok);padding:6px 12px;", "从 JSON 恢复「自建槽位 + 已选 + 描述」(合并,不覆盖内置词表)", () => importJson((obj) => { if (!obj || typeof obj !== "object") return; for (const k of ["custom", "noted", "selected"]) { if (obj[k] && typeof obj[k] === "object") s.elements[k] = Object.assign({}, s.elements[k], obj[k]); @@ -2208,7 +2208,7 @@ function renderCategoryPanel(box, api, catId) { // 436 条词条只在第一次渲染时建 DOM;标题栏仍可点一下收起(纯会话状态) if (!CAT_UI[cat.id]) CAT_UI[cat.id] = { collapsed: false, onlySel: false, keyword: "", sub: "" }; const ui = CAT_UI[cat.id]; - const wrap = el("div", "border:1px solid #333;border-radius:6px;margin-bottom:8px;background:#191919;overflow:hidden;"); + const wrap = el("div", "border:1px solid var(--xb-border);border-radius:6px;margin-bottom:8px;background:var(--xb-inset);overflow:hidden;"); const selOf = () => (s.elements.selected[cat.id] || []); const selArr = () => (s.elements.selected[cat.id] || (s.elements.selected[cat.id] = [])); const rebuild = () => { wrap.innerHTML = ""; build(); }; @@ -2283,14 +2283,14 @@ function renderCategoryPanel(box, api, catId) { const cur = subOf(cat, ui.sub).id; for (const c of chipRefs) { const on = c.dataset.sub === cur; - c.style.background = on ? "#2d5a88" : "#2a2a2a"; - c.style.borderColor = on ? "#7fb3e0" : "#555"; + c.style.background = on ? "#2d5a88" : "var(--xb-bg-raise)"; + c.style.borderColor = on ? "#7fb3e0" : "var(--xb-border)"; c.style.color = on ? "#eaf4ff" : "#bbb"; c.style.fontWeight = on ? "600" : "400"; } }; for (const sub of (cat.subs || [])) { - const b = el("button", "border:1px solid #555;border-radius:14px;padding:4px 14px;font-size:12px;cursor:pointer;font-family:inherit;white-space:nowrap;", `${sub.label}(${(sub.keys || []).length})`); + const b = el("button", "border:1px solid var(--xb-border);border-radius:14px;padding:4px 14px;font-size:12px;cursor:pointer;font-family:inherit;white-space:nowrap;", `${sub.label}(${(sub.keys || []).length})`); b.dataset.sub = sub.id; b.title = `切到「${sub.label}」子类(${(sub.keys || []).length} 条)`; b.addEventListener("mousedown", (e) => e.stopPropagation()); @@ -2340,7 +2340,7 @@ function renderCategoryPanel(box, api, catId) { fire(); notify(n ? `已加入 ${n} 条` : "这些词条都已在提示词里", n ? "success" : "warning"); }; - tb.append(smallBtn("+ 全部加入", "border:1px solid #5b9bd5;background:#2a4a6a;color:#cfe3f7;", "把本分类当前可见的词条全部追加进提示词", () => { + tb.append(smallBtn("+ 全部加入", "border:1px solid #5b9bd5;background:var(--xb-tint-info);color:var(--xb-info);", "把本分类当前可见的词条全部追加进提示词", () => { const list = visibleEntries(); if (!list.length) { notify("没有可加入的词条", "warning"); return; } if (list.length > 60) { @@ -2349,7 +2349,7 @@ function renderCategoryPanel(box, api, catId) { } addAllNow(list); })); - tb.append(smallBtn("🗑 全部移除", "border:1px solid #a44;background:#5a2a2a;color:#ffd0d0;", "把本分类已选词条全部从提示词移除", () => { + tb.append(smallBtn("🗑 全部移除", "border:1px solid #a44;background:var(--xb-tint-error);color:var(--xb-error);", "把本分类已选词条全部从提示词移除", () => { const keys = selOf().slice(); if (!keys.length) { notify("本分类还没有已选词条", "warning"); return; } removeKeys(keys); @@ -2379,7 +2379,7 @@ function renderCategoryPanel(box, api, catId) { const selected = selOf().includes(entry.key); const descKey = `${cat.id}|${entry.key}`; const desc = s.elements.noted[descKey] || ""; - const row = el("div", `display:flex;align-items:center;gap:8px;padding:5px 8px;border-radius:5px;margin-bottom:3px;box-sizing:border-box;cursor:pointer;user-select:none;background:${selected ? "#22456b" : "#242424"};border:1px solid ${selected ? "#3f86d0" : "#3a3a3a"};`); + const row = el("div", `display:flex;align-items:center;gap:8px;padding:5px 8px;border-radius:5px;margin-bottom:3px;box-sizing:border-box;cursor:pointer;user-select:none;background:${selected ? "#22456b" : "var(--xb-bg-raise)"};border:1px solid ${selected ? "#3f86d0" : "var(--xb-border)"};`); // 悬停整行 → 显示完整词条、当前状态与详细描述(原生 tooltip) row.title = `${entry.zh || "(空)"} / ${entry.en || "(空)"}` + (entry.custom ? "\n(自建槽位)" : "") @@ -2398,14 +2398,14 @@ function renderCategoryPanel(box, api, catId) { renderRows(); fire(); }); - if (entry.custom) row.append(el("span", "font-size:10px;color:#f0d79a;border:1px solid #8a6d3b;border-radius:3px;padding:0 3px;flex:0 0 auto;", "自建")); + if (entry.custom) row.append(el("span", "font-size:10px;color:var(--xb-warn);border:1px solid #8a6d3b;border-radius:3px;padding:0 3px;flex:0 0 auto;", "自建")); const rel = relPair(s, cat.id, entry.key); if (rel) row.append(el("span", "font-size:10px;color:#9fe0c0;border:1px solid #3f7a5f;border-radius:3px;padding:0 3px;flex:0 0 auto;", rel.zh)); const nm = el("span", `flex:1 1 auto;min-width:0;font-size:12px;line-height:1.4;color:${selected ? "#eaf4ff" : "#c8c8c8"};overflow:hidden;text-overflow:ellipsis;white-space:nowrap;`, `${selected ? "✔ " : ""}${entry.zh || "(空)"} / ${entry.en || "(空)"}`); row.append(nm); if (cat.id === "props") { - row.append(smallBtn(rel ? `🔗 ${rel.zh}` : "🔗 关系", "border:1px solid #5b9bd5;background:#2a4a6a;color:#cfe3f7;padding:4px 10px;flex:0 0 auto;", "选择/更换这个道具与主体的关系词(先移旧片段再加新片段)", () => { + row.append(smallBtn(rel ? `🔗 ${rel.zh}` : "🔗 关系", "border:1px solid #5b9bd5;background:var(--xb-tint-info);color:var(--xb-info);padding:4px 10px;flex:0 0 auto;", "选择/更换这个道具与主体的关系词(先移旧片段再加新片段)", () => { openRelPicker(api.node, `${entry.zh || entry.key} / ${entry.en || ""}`, lang(), (relPair(s, cat.id, entry.key) || {}).id || "", (relId) => { @@ -2417,7 +2417,7 @@ function renderCategoryPanel(box, api, catId) { })); } const descBtn = smallBtn(desc ? "📝 详细描述" : "添加详细描述", - `border:1px solid ${desc ? "#8a6d3b" : "#555"};background:${desc ? "#3a2f16" : "#2a2a2a"};color:${desc ? "#ffd98a" : "#9fc3e8"};padding:4px 10px;flex:0 0 auto;`, + `border:1px solid ${desc ? "#8a6d3b" : "var(--xb-border)"};background:${desc ? "#3a2f16" : "var(--xb-bg-raise)"};color:${desc ? "#ffd98a" : "#9fc3e8"};padding:4px 10px;flex:0 0 auto;`, desc ? "编辑该词条的详细描述(新窗口)" : "为该词条添加详细描述(新窗口)", () => openDescEditor(api.node, entry, `${entry.zh || entry.en || entry.key}`, desc, (text) => { const v = String(text || "").trim(); @@ -2456,7 +2456,7 @@ function renderCategoryPanel(box, api, catId) { const selected = selOf().includes(it.key); const descKey = `${cat.id}|${it.key}`; const desc = s.elements.noted[descKey] || ""; - const row = el("div", `display:flex;align-items:center;gap:8px;padding:5px 8px;border-radius:5px;margin-bottom:3px;box-sizing:border-box;cursor:pointer;user-select:none;background:${selected ? "#22456b" : "#242424"};border:1px solid ${selected ? "#3f86d0" : "#3a3a3a"};`); + const row = el("div", `display:flex;align-items:center;gap:8px;padding:5px 8px;border-radius:5px;margin-bottom:3px;box-sizing:border-box;cursor:pointer;user-select:none;background:${selected ? "#22456b" : "var(--xb-bg-raise)"};border:1px solid ${selected ? "#3f86d0" : "var(--xb-border)"};`); row.title = `${it.zh || "(未填中文)"} / ${it.en || "(未填英文)"}\n(自建槽位)` + `\n${selected ? "已加入提示词 —— 再点一下移除" : "点一下加入提示词"}` + "\n\n详细描述:" + (desc || "(还没有,点【添加详细描述】写一条)"); @@ -2466,10 +2466,10 @@ function renderCategoryPanel(box, api, catId) { rebuild(); fire(); }); - row.append(el("span", "font-size:10px;color:#f0d79a;border:1px solid #8a6d3b;border-radius:3px;padding:0 3px;flex:0 0 auto;", "自建")); + row.append(el("span", "font-size:10px;color:var(--xb-warn);border:1px solid #8a6d3b;border-radius:3px;padding:0 3px;flex:0 0 auto;", "自建")); row.append(el("span", `flex:1 1 auto;min-width:0;font-size:12px;color:${selected ? "#eaf4ff" : "#c8c8c8"};overflow:hidden;text-overflow:ellipsis;white-space:nowrap;`, `${selected ? "✔ " : ""}${it.zh || "(未填中文)"} / ${it.en || "(未填英文)"}`)); - row.append(smallBtn("✎ 编辑名称", "border:1px solid #555;background:#2a2a2a;color:#9fc3e8;padding:4px 10px;flex:0 0 auto;", "新窗口编辑该槽位的中/英词条", () => { + row.append(smallBtn("✎ 编辑名称", "border:1px solid var(--xb-border);background:var(--xb-bg-raise);color:var(--xb-info);padding:4px 10px;flex:0 0 auto;", "新窗口编辑该槽位的中/英词条", () => { openSlotNameEditor(it.zh, it.en, (z, e2) => { it.zh = z; it.en = e2; if (entry) { entry.zh = z; entry.en = e2; } @@ -2479,7 +2479,7 @@ function renderCategoryPanel(box, api, catId) { }); })); row.append(smallBtn(desc ? "📝 详细描述" : "添加详细描述", - `border:1px solid ${desc ? "#8a6d3b" : "#555"};background:${desc ? "#3a2f16" : "#2a2a2a"};color:${desc ? "#ffd98a" : "#9fc3e8"};padding:4px 10px;flex:0 0 auto;`, + `border:1px solid ${desc ? "#8a6d3b" : "var(--xb-border)"};background:${desc ? "#3a2f16" : "var(--xb-bg-raise)"};color:${desc ? "#ffd98a" : "#9fc3e8"};padding:4px 10px;flex:0 0 auto;`, desc ? "编辑该自建词条的详细描述(新窗口)" : "为该自建词条添加详细描述(新窗口)", () => openDescEditor(api.node, entry || { key: it.key, zh: it.zh, en: it.en }, `${it.zh || it.en || it.key}`, desc, (text) => { const v = String(text || "").trim(); @@ -2494,7 +2494,7 @@ function renderCategoryPanel(box, api, catId) { api.refreshPreview(null); fire(); }))); - row.append(smallBtn("🗑", "border:1px solid #a44;background:#3a2020;color:#ffb0b0;width:24px;height:24px;padding:0;line-height:1;flex:0 0 auto;", "删除这个自建槽位", () => { + row.append(smallBtn("🗑", "border:1px solid #a44;background:var(--xb-tint-error);color:var(--xb-error);width:24px;height:24px;padding:0;line-height:1;flex:0 0 auto;", "删除这个自建槽位", () => { openConfirm("删除自建槽位", `删除自建槽位「${it.zh || it.en || it.key}」?`, () => { removeKeys([it.key]); delete s.elements.noted[`${cat.id}|${it.key}`]; @@ -2512,7 +2512,7 @@ function renderCategoryPanel(box, api, catId) { renderCustom(); const cusBar = el("div", "display:flex;gap:8px;flex-wrap:wrap;margin-top:4px;"); - cusBar.append(smallBtn("+ 新增槽位", "border:1px dashed #5b9bd5;background:#2a3a4a;color:#9fc3e8;padding:6px 12px;", "新增一个自定义词条槽位(中/英各填一个)", () => { + cusBar.append(smallBtn("+ 新增槽位", "border:1px dashed #5b9bd5;background:var(--xb-tint-info);color:var(--xb-info);padding:6px 12px;", "新增一个自定义词条槽位(中/英各填一个)", () => { const arr = s.elements.custom[cat.id] || (s.elements.custom[cat.id] = []); arr.push({ key: `c${Date.now().toString(36)}${Math.floor(Math.random() * 46656).toString(36)}`, zh: "", en: "" }); index = buildIndex(s.elements); @@ -2520,7 +2520,7 @@ function renderCategoryPanel(box, api, catId) { rebuild(); fire(); })); - cusBar.append(smallBtn("🗑 删除槽位", "border:1px solid #a44;background:#5a2a2a;color:#ffd0d0;padding:6px 12px;", "删除本分类最后一个自建槽位(要删指定的,用该行右侧的 🗑)", () => { + cusBar.append(smallBtn("🗑 删除槽位", "border:1px solid #a44;background:var(--xb-tint-error);color:var(--xb-error);padding:6px 12px;", "删除本分类最后一个自建槽位(要删指定的,用该行右侧的 🗑)", () => { const arr = s.elements.custom[cat.id] || []; if (!arr.length) { notify("本分类还没有自建槽位", "warning"); return; } const last = arr[arr.length - 1]; @@ -2543,10 +2543,10 @@ function renderCategoryPanel(box, api, catId) { /** 分类外壳:标题栏总是建,内容懒建(展开才建) */ function build() { wrap.innerHTML = ""; - const headRow = el("div", "display:flex;align-items:center;gap:8px;padding:8px 10px;background:#20242a;cursor:pointer;flex-wrap:wrap;"); - const arrow = el("span", "font-size:11px;color:#9fc3e8;width:12px;flex:0 0 12px;", ui.collapsed ? "▶" : "▼"); - const cnt = el("span", "font-size:11px;color:#8a97a5;", countText()); - headRow.append(arrow, el("span", "font-size:13px;font-weight:600;color:#cfe3f7;", cat.label), cnt, + const headRow = el("div", "display:flex;align-items:center;gap:8px;padding:8px 10px;background:var(--xb-bg-soft);cursor:pointer;flex-wrap:wrap;"); + const arrow = el("span", "font-size:11px;color:var(--xb-info);width:12px;flex:0 0 12px;", ui.collapsed ? "▶" : "▼"); + const cnt = el("span", "font-size:11px;color:var(--xb-text-dim);", countText()); + headRow.append(arrow, el("span", "font-size:13px;font-weight:600;color:var(--xb-info);", cat.label), cnt, el("span", "font-size:11px;color:#6f7c8a;flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;", cat.tip || "")); const body = el("div", "padding:8px 10px;"); body.style.display = ui.collapsed ? "none" : "block"; @@ -2811,11 +2811,11 @@ function setupNode(node) { const btnRow = el("div", "display:grid;grid-template-columns:repeat(4,1fr);grid-auto-rows:30px;gap:6px;flex:0 0 auto;"); for (const b of PANEL_BUTTONS) { const cat = catOf(b.panel); - const btn = el("button", "width:100%;height:30px;box-sizing:border-box;display:flex;align-items:center;justify-content:center;border-radius:6px;border:2px solid #5b9bd5;background:#3a3a3a;color:#eee;font-size:13px;cursor:pointer;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-family:inherit;", b.label); + const btn = el("button", "width:100%;height:30px;box-sizing:border-box;display:flex;align-items:center;justify-content:center;border-radius:6px;border:2px solid #5b9bd5;background:var(--xb-border);color:var(--xb-text);font-size:13px;cursor:pointer;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-family:inherit;", b.label); btn.title = `打开「${cat.label}」元素面板 —— ${cat.tip}\\n(里面:点选项行加入/移除提示词,【添加详细描述】写补充描述)`; btn.addEventListener("mousedown", (e) => e.stopPropagation()); - btn.addEventListener("mouseenter", () => { btn.style.background = "#4a4a4a"; }); - btn.addEventListener("mouseleave", () => { btn.style.background = "#3a3a3a"; }); + btn.addEventListener("mouseenter", () => { btn.style.background = "var(--xb-border)"; }); + btn.addEventListener("mouseleave", () => { btn.style.background = "var(--xb-border)"; }); btn.addEventListener("click", (e) => { e.stopPropagation(); const settings = loadSettings(node); @@ -2914,9 +2914,9 @@ function setupNode(node) { box.style.margin = "0 8px"; box.style.boxSizing = "border-box"; box.style.borderRadius = "4px"; - box.style.border = "1px solid #444"; - box.style.background = "#1d1d1d"; - box.style.color = "#ccc"; + box.style.border = "1px solid var(--xb-border)"; + box.style.background = "var(--xb-bg)"; + box.style.color = "var(--xb-text)"; box.style.padding = "5px 8px"; box.style.fontSize = "12px"; box.style.lineHeight = "1.6"; @@ -3453,7 +3453,7 @@ function xbrTextControl(value, placeholder, onChange) { /** 导演台同款「种子 + 生成后控制(随机/固定/增加/减少)」一行 */ function xbrSeedRow(run, onChange) { const g = el("div", "display:flex;align-items:center;gap:10px;margin-bottom:10px;"); - g.append(el("label", "flex:0 0 160px;font-size:13px;color:#999;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;", "种子")); + g.append(el("label", "flex:0 0 160px;font-size:13px;color:var(--xb-text-dim);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;", "种子")); const i = xbrNumberControl(run.seed, { min: 0, max: Number.MAX_SAFE_INTEGER, step: 1 }, (v) => { run.seed = Math.round(v); onChange?.(); }); i.style.flex = "1 1 0"; const ctrl = smallBtn("", "flex:1 1 0;min-width:0;background:#b45309;border:1px solid #d97706;color:#fff;text-align:center;", "生成后控制:点击在 随机 / 固定 / 增加 / 减少 间切换", null); @@ -3476,24 +3476,24 @@ function xbrSeedRow(run, onChange) { let xbrModal = null; function xbrDialog({ label, title, subtitle, width = 820, autoSaveRef = null, onCommit = null, onImmediate = null, onChange = null, onClose = null }) { const overlay = el("div", "position:fixed;inset:0;background:rgba(0,0,0,.78);z-index:10300;display:flex;align-items:center;justify-content:center;overflow:auto;"); - const dialog = el("section", `background:#1c1c1e;border:1px solid #333;border-radius:8px;width:${width}px;max-width:94vw;` + - "max-height:90vh;display:flex;flex-direction:column;box-shadow:0 20px 40px rgba(0,0,0,.6);flex:0 0 auto;color:#ddd;font-size:13px;"); + const dialog = el("section", `background:var(--xb-bg);border:1px solid var(--xb-border);border-radius:8px;width:${width}px;max-width:94vw;` + + "max-height:90vh;display:flex;flex-direction:column;box-shadow:0 20px 40px rgba(0,0,0,.6);flex:0 0 auto;color:var(--xb-text);font-size:13px;"); dialog.dataset.xbrProModal = label; dialog.setAttribute("role", "dialog"); dialog.setAttribute("aria-modal", "true"); dialog.setAttribute("aria-label", label); - const header = el("div", "padding:18px 20px;border-bottom:1px solid #444;"); - header.append(el("div", "font-size:17px;font-weight:700;color:#eee;", title)); - if (subtitle) header.append(el("div", "font-size:12px;color:#999;margin-top:5px;line-height:1.6;", subtitle)); + const header = el("div", "padding:18px 20px;border-bottom:1px solid var(--xb-border);"); + header.append(el("div", "font-size:17px;font-weight:700;color:var(--xb-text);", title)); + if (subtitle) header.append(el("div", "font-size:12px;color:var(--xb-text-dim);margin-top:5px;line-height:1.6;", subtitle)); const body = el("div", "padding:16px 20px;overflow-y:auto;flex:1;min-height:200px;"); const error = el("div", "color:#e55;font-size:12px;margin:0 20px;min-height:14px;"); - const footer = el("div", "padding:12px 20px;border-top:1px solid #444;display:flex;justify-content:flex-end;gap:10px;align-items:center;background:#18181a;border-bottom-left-radius:8px;border-bottom-right-radius:8px;"); + const footer = el("div", "padding:12px 20px;border-top:1px solid var(--xb-border);display:flex;justify-content:flex-end;gap:10px;align-items:center;background:var(--xb-bg-soft);border-bottom-left-radius:8px;border-bottom-right-radius:8px;"); const left = el("div", "display:flex;align-items:center;gap:16px;margin-right:auto;flex-wrap:wrap;"); let autoCb = null; if (autoSaveRef) { - const autoRow = el("label", "display:flex;align-items:center;gap:6px;font-size:13px;color:#999;cursor:pointer;user-select:none;"); + const autoRow = el("label", "display:flex;align-items:center;gap:6px;font-size:13px;color:var(--xb-text-dim);cursor:pointer;user-select:none;"); autoCb = el("input", "width:18px;height:18px;accent-color:#f59e0b;cursor:pointer;"); autoCb.type = "checkbox"; autoCb.checked = autoSaveRef.value !== false; @@ -3508,8 +3508,8 @@ function xbrDialog({ label, title, subtitle, width = 820, autoSaveRef = null, on uiRow.title = "调整整个弹窗界面大小(不缩放网页/画布;Ctrl/⌘+滚轮 也可)"; left.append(zoomRow, uiRow); - const cancelBtn = el("button", "background:transparent;border:1px solid #555;color:#fff;border-radius:4px;padding:8px 20px;font-size:14px;cursor:pointer;font-family:inherit;", "取消"); - const saveBtn = onCommit ? el("button", "background:#2d5a88;color:#fff;border:none;border-radius:4px;padding:8px 20px;font-size:14px;font-weight:600;cursor:pointer;font-family:inherit;", "💾 保存") : null; + const cancelBtn = el("button", "background:transparent;border:1px solid var(--xb-border);color:#fff;border-radius:4px;padding:8px 20px;font-size:14px;cursor:pointer;font-family:inherit;", "取消"); + const saveBtn = onCommit ? el("button", "background:var(--xb-btn-primary);color:#fff;border:none;border-radius:4px;padding:8px 20px;font-size:14px;font-weight:600;cursor:pointer;font-family:inherit;", "💾 保存") : null; footer.append(left, cancelBtn); if (saveBtn) footer.append(saveBtn); dialog.append(header, body, error, footer); @@ -3668,7 +3668,7 @@ function xbrRenderPreset(body, node, draft, ctx) { body.append(taP); const taRow = el("div", "display:flex;align-items:center;gap:10px;margin:6px 0 4px;"); if (modeDef) { - taRow.append(smallBtn("♻️ 恢复默认", "border:1px solid #555;background:#2a2a2a;color:#ccc;padding:4px 10px;", + taRow.append(smallBtn("♻️ 恢复默认", "border:1px solid var(--xb-border);background:var(--xb-bg-raise);color:var(--xb-text);padding:4px 10px;", "把设定词恢复为该模式 × 该模版 × 该语言的默认文本", () => { draft.presetText = modeDef; draft.presetTouched = false; ctx.rerender(); })); } @@ -3705,7 +3705,7 @@ function xbrRenderPreset(body, node, draft, ctx) { "不用:SKILL 完全不生效(即使下面选过文件也不会送进模型)。")); } const foxRow = el("div", "display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin:2px 0 8px;"); - foxRow.append(smallBtn("📂 打开 SKILL 文件夹", "border:1px solid #555;background:#2a2a2a;color:#ccc;padding:4px 10px;", + foxRow.append(smallBtn("📂 打开 SKILL 文件夹", "border:1px solid var(--xb-border);background:var(--xb-bg-raise);color:var(--xb-text);padding:4px 10px;", "在资源管理器里打开 support_llama/skills,方便新增 / 编辑技能文件", async () => { try { @@ -3867,7 +3867,7 @@ function xbrRenderHelp(body) { " · 弹窗底部:☑ 自动保存(默认开)、字号 A− / A+、界面 − / %", " · 所有配置随工作流保存;「取消」丢弃未保存改动", ]; - body.append(el("div", "font-size:12px;color:#bbb;line-height:1.9;white-space:pre-wrap;font-family:ui-monospace,Consolas,monospace;", lines.join("\n"))); + body.append(el("div", "font-size:12px;color:var(--xb-text-dim);line-height:1.9;white-space:pre-wrap;font-family:ui-monospace,Consolas,monospace;", lines.join("\n"))); } const XBR_PANEL_RENDER = { llm: xbrRenderLlm, preset: xbrRenderPreset, help: xbrRenderHelp }; @@ -3986,12 +3986,12 @@ async function xbrOpenModal(node, panelId) { function xbrBuildButtonRow(node) { const grid = el("div", "display:grid;grid-template-columns:repeat(" + Math.max(1, XBR_PANEL_BUTTONS.length) + ",1fr);grid-auto-rows:30px;gap:6px;flex:0 0 auto;"); for (const b of XBR_PANEL_BUTTONS) { - const btn = el("button", "width:100%;height:30px;box-sizing:border-box;display:flex;align-items:center;justify-content:center;border-radius:6px;border:2px solid #5b9bd5;background:#3a3a3a;color:#eee;font-size:13px;cursor:pointer;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-family:inherit;", b.label); + const btn = el("button", "width:100%;height:30px;box-sizing:border-box;display:flex;align-items:center;justify-content:center;border-radius:6px;border:2px solid #5b9bd5;background:var(--xb-border);color:var(--xb-text);font-size:13px;cursor:pointer;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-family:inherit;", b.label); btn.title = `打开「${b.title}」弹窗`; btn.dataset.xbrProBtn = b.id; btn.addEventListener("mousedown", (e) => e.stopPropagation()); - btn.addEventListener("mouseenter", () => { btn.style.background = "#4a4a4a"; }); - btn.addEventListener("mouseleave", () => { btn.style.background = "#3a3a3a"; }); + btn.addEventListener("mouseenter", () => { btn.style.background = "var(--xb-border)"; }); + btn.addEventListener("mouseleave", () => { btn.style.background = "var(--xb-border)"; }); btn.addEventListener("click", (e) => { e.stopPropagation(); xbrOpenModal(node, b.id); }); grid.append(btn); } @@ -4027,11 +4027,11 @@ function xbrInfoLines(node, promptText) { /** 提示词框标题行(左标签 + 右复制)+ 参数设定显示;并挂上端口锁定/执行回写/摘要刷新 */ function xbrBuildPromptHeader(node, promptBox) { const head = el("div", "display:flex;align-items:center;gap:8px;flex:0 0 auto;"); - const lab = el("div", "font-size:12px;color:#bbb;", "📝 节点提示词(与面板预览框双向同步)"); + const lab = el("div", "font-size:12px;color:var(--xb-text-dim);", "📝 节点提示词(与面板预览框双向同步)"); const copy = smallBtn("📋 复制", "margin-left:auto;", "复制提示词框内容", () => { copyText(promptBox.value || ""); }); head.append(lab, copy); - const infoBox = el("div", "flex:0 0 auto;height:150px;overflow:auto;background:#1c1c1e;border:1px solid #333;border-radius:6px;padding:6px 9px;font-size:11px;line-height:1.65;color:#b9c6d2;white-space:pre-wrap;box-sizing:border-box;font-family:ui-monospace,Consolas,monospace;"); + const infoBox = el("div", "flex:0 0 auto;height:150px;overflow:auto;background:var(--xb-bg);border:1px solid var(--xb-border);border-radius:6px;padding:6px 9px;font-size:11px;line-height:1.65;color:#b9c6d2;white-space:pre-wrap;box-sizing:border-box;font-family:ui-monospace,Consolas,monospace;"); infoBox.dataset.captureWheel = "true"; infoBox.title = "当前配置摘要(只读)"; @@ -4045,8 +4045,8 @@ function xbrBuildPromptHeader(node, promptBox) { const inp = (node.inputs || []).find((i) => i.name === "text"); const linked = !!inp && inp.link != null; promptBox.readOnly = linked; - promptBox.style.background = linked ? "#141416" : "#1d1d1d"; - promptBox.style.color = linked ? "#8a8a8a" : "#ccc"; + promptBox.style.background = linked ? "#141416" : "var(--xb-bg)"; + promptBox.style.color = linked ? "var(--xb-text-dim)" : "var(--xb-text)"; promptBox.title = linked ? "已由「📝 提示词」端口控制,断开连线后恢复编辑" : ""; lab.textContent = linked ? "📝 提示词已锁定(由「📝 提示词」端口控制)" : "📝 节点提示词(与面板预览框双向同步)"; lab.style.color = linked ? "#d9a441" : "#bbb"; diff --git a/web/xb_theme.js b/web/xb_theme.js new file mode 100644 index 0000000..8ea4a19 --- /dev/null +++ b/web/xb_theme.js @@ -0,0 +1,90 @@ +/** + * XB_ToolBox — 主题变量 + * ============================================================ + * 插件的 DOM widget(提示词面板、选择器、弹窗等)里的大量内联样式原本写死深色, + * 在 ComfyUI 浅色主题下会变成"黑块"。这里统一注入一组 CSS 变量, + * 各 widget 改用 var(--xb-*) 后即可自动跟随明暗主题。 + * + * ComfyUI 通过调色板设置切换主题(不同版本可能改 / 的 class), + * 所以这里监听 class 变化实时更新,无需刷新页面。 + */ + +import { app } from "../../scripts/app.js"; + +const PALETTE = { + // 名称: [深色值, 浅色值] + "--xb-bg": ["#1c1c1e", "#ffffff"], + "--xb-bg-soft": ["#232326", "#f2f3f5"], + "--xb-bg-raise": ["#2b2b2b", "#e9eaee"], + "--xb-border": ["#3a3a3a", "#c9ced6"], + "--xb-text": ["#dddddd", "#23262a"], + "--xb-text-dim": ["#8a97a5", "#5c626b"], + "--xb-accent-bg": ["#2d5a88", "#2f6fbf"], + "--xb-info": ["#cfe3f7", "#1b5e9c"], + "--xb-ok": ["#d7f0c8", "#1f7a3a"], + "--xb-warn": ["#ffd98a", "#8a5f10"], + "--xb-error": ["#ffd0d0", "#a32626"], + "--xb-orange": ["#e8a87c", "#8a4a1f"], + "--xb-on-accent": ["#eaf4ff", "#ffffff"], + "--xb-tint-info": ["#2a4a6a", "#e3edf9"], + "--xb-tint-warn": ["#5a4a2a", "#fdf1d6"], + "--xb-tint-ok": ["#3a5a2a", "#e7f5e9"], + "--xb-tint-error": ["#5a2a2a", "#fdeaea"], + "--xb-btn-primary": ["#2d5a88", "#1f6fd0"], + "--xb-inset": ["#191919", "#f5f6f8"], + "--xb-float": ["#2a2a2e", "#ffffff"], + "--xb-btn-info": ["#2a4a6a", "#1f6fd0"], + "--xb-btn-warn": ["#b45309", "#c2410c"], + "--xb-btn-ok": ["#2d5a3a", "#1f7a3a"], + "--xb-btn-error": ["#a33a3a", "#b3261e"], + "--xb-accent-bg-hover": ["#22456b", "#1a5cb0"], + "--xb-accent-text": ["#eaf4ff", "#ffffff"], +}; + +function prefersLight() { + const html = document.documentElement; + const body = document.body; + if (html?.classList?.contains("light-theme")) return true; + if (html?.classList?.contains("dark-theme")) return false; + if (body?.classList?.contains("light-theme")) return true; + if (body?.classList?.contains("dark-theme")) return false; + try { + const palette = app?.ui?.settings?.getSettingValue?.("Comfy.ColorPalette"); + if (palette === "light") return true; + if (palette === "dark") return false; + } catch (_) { /* ignore */ } + return false; +} + +function applyTheme() { + const light = prefersLight(); + const root = document.documentElement; + for (const [name, [dark, bright]] of Object.entries(PALETTE)) { + root.style.setProperty(name, light ? bright : dark); + } + root.dataset.xbTheme = light ? "light" : "dark"; +} + +let installed = false; + +function install() { + if (installed) return; + installed = true; + applyTheme(); + + const observer = new MutationObserver(applyTheme); + observer.observe(document.documentElement, { + attributes: true, + attributeFilter: ["class", "data-theme"], + }); + if (document.body) { + observer.observe(document.body, { attributes: true, attributeFilter: ["class"] }); + } +} + +app.registerExtension({ + name: "XB_ToolBox.Theme", + async setup() { + install(); + }, +}); diff --git a/web/xb_wan_dancer_combo.js b/web/xb_wan_dancer_combo.js index 89c8b2f..6c15f48 100644 --- a/web/xb_wan_dancer_combo.js +++ b/web/xb_wan_dancer_combo.js @@ -31,7 +31,7 @@ app.registerExtension({ const el = wDur.inputEl || wDur.element; if (el) { el.readOnly = true; - el.style.cssText = "background-color:#1a1a1a;color:#00E676;text-align:center;font-weight:bold;font-size:15px;min-width:120px;white-space:nowrap;overflow:hidden;"; + el.style.cssText = "background-color:var(--xb-bg);color:#00E676;text-align:center;font-weight:bold;font-size:15px;min-width:120px;white-space:nowrap;overflow:hidden;"; } }, 100); } @@ -56,14 +56,14 @@ app.registerExtension({ } const ctr = document.createElement("div"); - Object.assign(ctr.style, { display:"flex", flexDirection:"column", gap:"6px", width:"100%", padding:"6px", boxSizing:"border-box", background:"#161616", borderRadius:"6px", color:"#ccc", fontFamily:"sans-serif", marginTop:"4px" }); + Object.assign(ctr.style, { display:"flex", flexDirection:"column", gap:"6px", width:"100%", padding:"6px", boxSizing:"border-box", background:"#161616", borderRadius:"6px", color:"var(--xb-text)", fontFamily:"sans-serif", marginTop:"4px" }); const audioEl = document.createElement("audio"); audioEl.controls = true; audioEl.style.cssText = "width:100%;height:34px;outline:none;"; ctr.appendChild(audioEl); const canvas = document.createElement("canvas"); - canvas.style.cssText = "width:100%;height:40px;border-radius:4px;background:#111;cursor:pointer;display:block;"; + canvas.style.cssText = "width:100%;height:40px;border-radius:4px;background:var(--xb-inset);cursor:pointer;display:block;"; const dpr = window.devicePixelRatio || 1; canvas.width = 1200 * dpr; canvas.height = 80 * dpr; ctr.appendChild(canvas); diff --git a/web/xb_yue2_lyrics.js b/web/xb_yue2_lyrics.js index 68b7405..03cfff1 100644 --- a/web/xb_yue2_lyrics.js +++ b/web/xb_yue2_lyrics.js @@ -164,7 +164,7 @@ function makeButton(node, w, item) { const button = document.createElement("button"); button.textContent = label; if (tip) button.title = tip; - button.style.cssText = "padding:7px 6px;border:1px solid #555;border-radius:6px;background:#2a2a2e;color:#eee;cursor:pointer;font-size:12px;line-height:1.25;text-align:left;"; + button.style.cssText = "padding:7px 6px;border:1px solid var(--xb-border);border-radius:6px;background:#2a2a2e;color:var(--xb-text);cursor:pointer;font-size:12px;line-height:1.25;text-align:left;"; button.addEventListener("mouseenter", () => { button.style.background = "#3a3a40"; }); button.addEventListener("mouseleave", () => { button.style.background = "#2a2a2e"; }); button.addEventListener("click", () => { @@ -202,10 +202,10 @@ function addCustomTagRow(panel, node, w) { const input = document.createElement("input"); input.type = "text"; input.placeholder = "例如 Rap / 高潮 / 吟唱"; - input.style.cssText = "flex:1;padding:7px 8px;border:1px solid #555;border-radius:6px;background:#2a2a2e;color:#eee;font-size:12px;"; + input.style.cssText = "flex:1;padding:7px 8px;border:1px solid var(--xb-border);border-radius:6px;background:#2a2a2e;color:var(--xb-text);font-size:12px;"; const button = document.createElement("button"); button.textContent = "插入标签"; - button.style.cssText = "padding:7px 12px;border:1px solid #555;border-radius:6px;background:#2a2a2e;color:#eee;cursor:pointer;font-size:12px;"; + button.style.cssText = "padding:7px 12px;border:1px solid var(--xb-border);border-radius:6px;background:#2a2a2e;color:var(--xb-text);cursor:pointer;font-size:12px;"; const commit = () => { const raw = input.value.trim().replace(/^\[|\]$/g, "").trim(); if (!raw) return; @@ -228,13 +228,13 @@ function showTagPicker(node, w) { overlay.addEventListener("mousedown", (event) => { if (event.target === overlay) closeTagPicker(); }); const panel = document.createElement("div"); - panel.style.cssText = "background:#1b1b1f;border:1px solid #444;border-radius:10px;padding:14px;max-width:700px;width:92%;max-height:86vh;overflow:auto;box-shadow:0 10px 40px rgba(0,0,0,.6);"; + panel.style.cssText = "background:#1b1b1f;border:1px solid var(--xb-border);border-radius:10px;padding:14px;max-width:700px;width:92%;max-height:86vh;overflow:auto;box-shadow:0 10px 40px rgba(0,0,0,.6);"; const title = document.createElement("div"); title.textContent = "添加元素"; title.title = "点击标签插入到光标处。段落标签会独占一行并自动换行;" + "行级角色标注直接插在光标处,接着写歌词即可。"; - title.style.cssText = "color:#eee;font-size:14px;font-weight:600;margin-bottom:8px;cursor:help;"; + title.style.cssText = "color:var(--xb-text);font-size:14px;font-weight:600;margin-bottom:8px;cursor:help;"; panel.appendChild(title); addGroup(panel, "① 官方段落词表(8 个)", GROUP_TIPS.OFFICIAL,