dsh-any-background 0.1.3 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js CHANGED
@@ -1,1248 +1,1291 @@
1
- window.__ModuleLoader__.load({
2
- id: "dsh-any-background",
3
- factory: (require) => {
4
- var module = { exports: {} };
5
- var exports = module.exports;
6
- Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
- let react = require("react");
8
- let _deepseek_ai_dsh_client_runtime_client = require("@deepseek-ai/dsh-client-runtime/client");
9
- let react_jsx_runtime = require("react/jsx-runtime");
10
- //#region src/client/index.tsx
11
- const name = "dsh-any-background";
12
- const inject = [
13
- "slots",
14
- "locale",
15
- "theme"
16
- ];
17
- const NS = "settings.anyBg";
18
- const LS_COLOR = "dsh-any-background:color";
19
- const LS_WP = "dsh-any-background:wallpaper";
20
- const LS_OP = "dsh-any-background:opacity";
21
- const LS_WOP = "dsh-any-background:wallpaper-opacity";
22
- const LS_BL = "dsh-any-background:blur";
23
- const LS_BG = "dsh-any-background:bgState";
24
- const LS_SOP = "dsh-any-background:settings-opacity";
25
- const DEF_OP = .85;
26
- const DEF_BL = 0;
27
- const DEF_SOP = 1;
28
- const CUSTOM_ID = "custom-color";
29
- const zh = {
30
- nav: "主题",
31
- subtitle: "自定义界面外观",
32
- colorTitle: "主题色",
33
- colorHint: "在色轮上选择色相,在方形中调整饱和度和明度",
34
- uiTitle: "界面",
35
- uiOpacity: "主界面透明度",
36
- uiOpacityHint: "拖动滑块调整主页界面背景的透明度",
37
- uiSop: "设置界面透明度",
38
- uiSopHint: "拖动滑块调整设置页界面背景的透明度",
39
- bgTitle: "背景图片",
40
- bgChoose: "选择图片",
41
- bgRemove: "移除图片",
42
- bgEdit: "编辑位置",
43
- wpOpacity: "壁纸透明度",
44
- bgBlur: "壁纸模糊",
45
- bgHint: "拖动滑块实时调整。点击背景图可打开编辑器调整位置和大小",
46
- editorTitle: "背景编辑器",
47
- editorHint: "拖动移动图片,滚轮缩放大小",
48
- editorCommit: "确认",
49
- editorCancel: "取消",
50
- editorReset: "重置"
51
- };
52
- const en = {
53
- nav: "Theme",
54
- subtitle: "Customize appearance",
55
- colorTitle: "Theme color",
56
- colorHint: "Pick hue on the ring, adjust saturation & lightness in the square",
57
- uiTitle: "Interface",
58
- uiOpacity: "Main interface opacity",
59
- uiOpacityHint: "Drag to adjust homepage interface background opacity",
60
- uiSop: "Settings interface opacity",
61
- uiSopHint: "Drag to adjust the settings page background opacity",
62
- bgTitle: "Wallpaper",
63
- bgChoose: "Choose image",
64
- bgRemove: "Remove image",
65
- bgEdit: "Edit position",
66
- wpOpacity: "Wallpaper opacity",
67
- bgBlur: "Wallpaper blur",
68
- bgHint: "Drag sliders for real-time adjustment. Click the image to open the editor",
69
- editorTitle: "Background editor",
70
- editorHint: "Drag to move, scroll to zoom",
71
- editorCommit: "Confirm",
72
- editorCancel: "Cancel",
73
- editorReset: "Reset"
74
- };
75
- function rLS(k) {
76
- try {
77
- const v = localStorage.getItem(k);
78
- return typeof v === "string" ? v : null;
79
- } catch {
80
- return null;
81
- }
82
- }
83
- function wLS(k, v) {
84
- try {
85
- v === null ? localStorage.removeItem(k) : localStorage.setItem(k, v);
86
- } catch (e) {
87
- console.warn(`dsh-any-background: localStorage write failed for "${k}"`, e);
88
- }
89
- }
90
- function rColor() {
91
- try {
92
- const v = JSON.parse(rLS(LS_COLOR) || "");
93
- if (Array.isArray(v) && v.length === 3) return v;
94
- } catch {}
95
- return [
96
- 220,
97
- .55,
98
- .25
99
- ];
100
- }
101
- function rWp() {
102
- const v = rLS(LS_WP);
103
- return v?.length ? v : null;
104
- }
105
- function rOp() {
106
- const v = rLS(LS_OP);
107
- if (!v) return DEF_OP;
108
- const n = +v;
109
- return isFinite(n) ? Math.min(1, Math.max(0, n)) : DEF_OP;
110
- }
111
- function rWop() {
112
- const v = rLS(LS_WOP);
113
- if (!v) return 1;
114
- const n = +v;
115
- return isFinite(n) ? Math.min(1, Math.max(0, n)) : 1;
116
- }
117
- function rBl() {
118
- const v = rLS(LS_BL);
119
- if (!v) return DEF_BL;
120
- const n = +v;
121
- return isFinite(n) ? Math.min(60, Math.max(0, n)) : DEF_BL;
122
- }
123
- function rSop() {
124
- const v = rLS(LS_SOP);
125
- if (!v) return DEF_SOP;
126
- const n = +v;
127
- return isFinite(n) ? Math.min(1, Math.max(0, n)) : DEF_SOP;
128
- }
129
- function rBgState() {
130
- try {
131
- const v = JSON.parse(rLS(LS_BG) || "");
132
- if (v && typeof v.zoom === "number" && typeof v.iw === "number" && v.iw > 0 && v.ih > 0) return v;
133
- } catch {}
134
- return {
135
- zoom: 1,
136
- x: 0,
137
- y: 0,
138
- iw: 0,
139
- ih: 0
140
- };
141
- }
142
- function hsvToHsl(h, s, v) {
143
- const l = v * (1 - s / 2);
144
- return [
145
- h,
146
- l === 0 || l === 1 ? 0 : (v - l) / Math.min(l, 1 - l),
147
- l
148
- ];
149
- }
150
- function hslToHsv(h, s, l) {
151
- const v = l + s * Math.min(l, 1 - l);
152
- return [
153
- h,
154
- v === 0 ? 0 : 2 * (1 - l / v),
155
- v
156
- ];
157
- }
158
- function genTokens(hue, sat, lit) {
159
- const dark = lit < .55;
160
- const h = (d) => ((hue + d) % 360 + 360) % 360;
161
- const s = (d) => Math.max(0, Math.min(1, sat + d));
162
- const l = (d) => Math.max(0, Math.min(1, lit + d));
163
- const hsl = (hh, ss, ll) => `hsl(${Math.round(hh)},${Math.round(ss * 100)}%,${Math.round(ll * 100)}%)`;
164
- const rgba = (hh, ss, ll, a) => {
165
- `${Math.round(hh)}${Math.round(ss * 100)}${Math.round(ll * 100)}`;
166
- return `hsla(${Math.round(hh)},${Math.round(ss * 100)}%,${Math.round(ll * 100)}%,${a})`;
167
- };
168
- if (dark) return {
169
- colorScheme: "dark",
170
- tokens: {
171
- "--dsw-alias-bg-base": hsl(h(0), s(0), l(-.04)),
172
- "--dsw-alias-bg-layer-1": hsl(h(0), s(0), l(.02)),
173
- "--dsw-alias-bg-layer-2": hsl(h(0), s(0), l(.07)),
174
- "--dsw-alias-bg-layer-3": hsl(h(0), s(-.05), l(.12)),
175
- "--dsw-alias-bg-overlay": hsl(h(0), s(-.05), l(.12)),
176
- "--dsw-alias-border-l1": rgba(h(0), s(-.1), l(.18), .12),
177
- "--dsw-alias-border-l2": rgba(h(0), s(-.1), l(.22), .22),
178
- "--dsw-alias-label-primary": hsl(0, 0, 1),
179
- "--dsw-alias-label-secondary": hsl(0, 0, 1),
180
- "--dsw-alias-label-tertiary": hsl(0, 0, 1),
181
- "--dsw-alias-brand-primary": hsl(h(0), s(.1), Math.max(l(.2), .5)),
182
- "--dsw-alias-brand-text": l(.2) > .6 ? "#000" : "#fff",
183
- "--dsw-alias-button-primary-hover": hsl(h(0), s(.1), Math.max(l(.28), .58)),
184
- "--dsw-alias-button-primary-dimmed": hsl(h(0), s(0), l(.07)),
185
- "--dsw-alias-button-elevated-fill": hsl(h(0), s(0), l(.04)),
186
- "--dsw-alias-interactive-bg-hover": rgba(h(0), s(0), Math.max(l(.15), .4), .12),
187
- "--dsw-alias-interactive-bg-active": rgba(h(0), s(0), Math.max(l(.15), .4), .2),
188
- "--dsw-alias-markdown-code-block": hsl(h(0), s(0), l(-.06)),
189
- "--dsw-alias-markdown-inline-code": hsl(h(0), s(0), l(.04)),
190
- "--dsw-alias-state-error-primary": "#ff5c72",
191
- "--dsw-alias-state-success-primary": "#3ddc84",
192
- "--dsw-alias-state-warn-primary": "#ffb347",
193
- "--dsw-specific-sidebar-fill": hsl(h(0), s(0), l(-.06)),
194
- "--dsw-specific-sidebar-nav-item-active": hsl(h(0), s(0), l(.04)),
195
- "--dsw-specific-sidebar-nav-item-hover": hsl(h(0), s(0), l(0)),
196
- "--dsw-specific-input-major": hsl(h(0), s(0), l(.02)),
197
- "--dsw-alias-scrollbar-bg-l1": hsl(h(0), s(-.05), l(.12)),
198
- "--dsw-alias-scrollbar-bg-l2": hsl(h(0), s(-.05), l(.16)),
199
- "--dsw-alias-scrollbar-hover-l1": hsl(h(0), s(-.05), l(.22)),
200
- "--dsw-alias-scrollbar-hover-l2": hsl(h(0), s(-.05), l(.22))
201
- }
202
- };
203
- return {
204
- colorScheme: "light",
205
- tokens: {
206
- "--dsw-alias-bg-base": hsl(h(0), s(-.08), l(.03)),
207
- "--dsw-alias-bg-layer-1": hsl(h(0), s(-.12), l(.07)),
208
- "--dsw-alias-bg-layer-2": hsl(h(0), s(-.1), l(-.03)),
209
- "--dsw-alias-bg-layer-3": hsl(h(0), s(-.08), l(-.09)),
210
- "--dsw-alias-bg-overlay": hsl(h(0), s(-.12), l(.08)),
211
- "--dsw-alias-border-l1": rgba(h(0), s(-.15), l(-.35), .18),
212
- "--dsw-alias-border-l2": rgba(h(0), s(-.15), l(-.35), .3),
213
- "--dsw-alias-label-primary": hsl(0, 0, 0),
214
- "--dsw-alias-label-secondary": hsl(0, 0, 0),
215
- "--dsw-alias-label-tertiary": hsl(0, 0, 0),
216
- "--dsw-alias-brand-primary": hsl(h(0), s(.05), Math.min(l(-.18), .45)),
217
- "--dsw-alias-brand-text": "#fff",
218
- "--dsw-alias-button-primary-hover": hsl(h(0), s(.05), Math.min(l(-.12), .5)),
219
- "--dsw-alias-button-primary-dimmed": hsl(h(0), s(-.1), l(-.03)),
220
- "--dsw-alias-button-elevated-fill": hsl(h(0), s(-.1), l(.1)),
221
- "--dsw-alias-interactive-bg-hover": rgba(h(0), s(0), l(-.3), .08),
222
- "--dsw-alias-interactive-bg-active": rgba(h(0), s(0), l(-.3), .14),
223
- "--dsw-alias-markdown-code-block": hsl(h(0), s(-.1), l(-.03)),
224
- "--dsw-alias-markdown-inline-code": hsl(h(0), s(-.08), l(.04)),
225
- "--dsw-specific-sidebar-fill": hsl(h(0), s(-.1), l(-.03)),
226
- "--dsw-specific-sidebar-nav-item-active": hsl(h(0), s(-.08), l(.05)),
227
- "--dsw-specific-sidebar-nav-item-hover": hsl(h(0), s(-.12), l(0)),
228
- "--dsw-specific-input-major": hsl(h(0), s(-.12), l(.1)),
229
- "--dsw-alias-scrollbar-bg-l1": hsl(h(0), s(-.1), l(-.08)),
230
- "--dsw-alias-scrollbar-bg-l2": hsl(h(0), s(-.08), l(-.12)),
231
- "--dsw-alias-scrollbar-hover-l1": hsl(h(0), s(-.08), l(-.16)),
232
- "--dsw-alias-scrollbar-hover-l2": hsl(h(0), s(-.08), l(-.16))
233
- }
234
- };
235
- }
236
- let wpEl = null;
237
- let ctxRef = null;
238
- function toRgba(c, a) {
239
- const hx = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(c.trim());
240
- if (hx) {
241
- let d = hx[1];
242
- if (d.length === 3) d = d.split("").map((x) => x + x).join("");
243
- const n = parseInt(d, 16);
244
- return `rgba(${n >> 16 & 255},${n >> 8 & 255},${n & 255},${a})`;
245
- }
246
- const rgb = /^rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)(?:\s*,\s*([\d.]+))?\s*\)$/i.exec(c.trim());
247
- if (rgb) return `rgba(${rgb[1]},${rgb[2]},${rgb[3]},${a})`;
248
- const hsl = /^hsla?\(\s*([\d.]+)\s*,\s*([\d.]+)%\s*,\s*([\d.]+)%/i.exec(c.trim());
249
- if (hsl) return `hsla(${hsl[1]},${hsl[2]}%,${hsl[3]}%,${a})`;
250
- return c.trim();
251
- }
252
- let appliedTokenNames = [];
253
- /** Remove every inline token this plugin wrote (teardown symmetry). */
254
- function clearCustomTokens() {
255
- for (const name of appliedTokenNames) document.body.style.removeProperty(name);
256
- appliedTokenNames = [];
257
- }
258
- /**
259
- * Write the saved color's full token set as inline variables on body — the
260
- * same write surface the theme presenter owns, but derived DIRECTLY from the
261
- * saved pick, so the theme color never depends on the theme service's active
262
- * state or the presenter's timing. The bg-base and sidebar tokens are
263
- * re-emitted at the requested alpha; every other token (layers, labels,
264
- * borders, brand) is written verbatim. No reads: nothing can observe a stale
265
- * or reset theme value and leave the homepage on the system color.
266
- */
267
- function applyCustomTokens(op) {
268
- const [h, s, l] = rColor();
269
- const { tokens } = genTokens(h, s, l);
270
- const sideOp = Math.min(1, op + .08);
271
- clearCustomTokens();
272
- if (l < .55) document.body.setAttribute("data-ds-dark-theme", "");
273
- else document.body.removeAttribute("data-ds-dark-theme");
274
- for (const [name, value] of Object.entries(tokens)) {
275
- let v = value;
276
- if (name === "--dsw-alias-bg-base") v = toRgba(value, op);
277
- else if (name === "--dsw-specific-sidebar-fill") v = toRgba(value, sideOp);
278
- document.body.style.setProperty(name, v);
279
- appliedTokenNames.push(name);
280
- }
281
- }
282
- const SETTINGS_STYLE_RULE = `[role="dialog"][aria-modal="true"][aria-labelledby]{background:var(--dsh-any-bg-settings-surface,var(--dsw-alias-bg-layer-2))}`;
283
- function applySettingsOverrides(op) {
284
- if (op >= 1) {
285
- document.documentElement.style.removeProperty("--dsh-any-bg-settings-surface");
286
- return;
287
- }
288
- const [h, s, l] = rColor();
289
- const layer2 = genTokens(h, s, l).tokens["--dsw-alias-bg-layer-2"];
290
- if (layer2 !== void 0) document.documentElement.style.setProperty("--dsh-any-bg-settings-surface", toRgba(layer2, op));
291
- }
292
- function applyWp(ctx) {
293
- const url = rWp();
294
- if (!url) {
295
- wpEl?.remove();
296
- wpEl = null;
297
- } else {
298
- if (!wpEl || !document.body.contains(wpEl)) {
299
- wpEl = document.createElement("div");
300
- wpEl.style.cssText = "position:fixed;inset:0;z-index:-1;pointer-events:none;background-repeat:no-repeat;";
301
- document.body.prepend(wpEl);
302
- }
303
- const bg = rBgState();
304
- wpEl.style.backgroundImage = `url("${url}")`;
305
- if (bg.iw > 0) {
306
- const fit = Math.min(window.innerWidth / bg.iw, window.innerHeight / bg.ih);
307
- const w = bg.iw * fit * bg.zoom;
308
- const h = bg.ih * fit * bg.zoom;
309
- wpEl.style.backgroundSize = `${w}px ${h}px`;
310
- wpEl.style.backgroundPosition = `${bg.x * window.innerWidth - w / 2}px ${bg.y * window.innerHeight - h / 2}px`;
311
- } else {
312
- wpEl.style.backgroundSize = "contain";
313
- wpEl.style.backgroundPosition = "center";
314
- }
315
- const blur = rBl();
316
- wpEl.style.filter = blur > 0 ? `blur(${blur}px)` : "none";
317
- wpEl.style.opacity = String(rWop());
318
- }
319
- applyCustomTokens(rOp());
320
- applySettingsOverrides(rSop());
321
- }
322
- function teardownWp() {
323
- wpEl?.remove();
324
- wpEl = null;
325
- clearCustomTokens();
326
- document.documentElement.style.removeProperty("--dsh-any-bg-settings-surface");
327
- }
328
- const WHEEL_SIZE = 220;
329
- const CX = WHEEL_SIZE / 2;
330
- const RING_OUTER = 106;
331
- const RING_INNER = 82;
332
- const SQ_HALF = Math.round(RING_INNER / Math.SQRT2);
333
- function drawWheel(cvs, hue, sat, lit) {
334
- const c = cvs.getContext("2d");
335
- c.clearRect(0, 0, WHEEL_SIZE, WHEEL_SIZE);
336
- for (let a = 0; a < 360; a++) {
337
- const r1 = (a - 90) * Math.PI / 180;
338
- const r2 = (a + 1.5 - 90) * Math.PI / 180;
339
- c.beginPath();
340
- c.arc(CX, CX, RING_OUTER, r1, r2);
341
- c.arc(CX, CX, RING_INNER, r2, r1, true);
342
- c.closePath();
343
- c.fillStyle = `hsl(${a},100%,50%)`;
344
- c.fill();
345
- }
346
- const gx = CX - SQ_HALF, gy = CX - SQ_HALF, sz = SQ_HALF * 2;
347
- c.fillStyle = "#fff";
348
- c.fillRect(gx, gy, sz, sz);
349
- const gh = c.createLinearGradient(gx, 0, gx + sz, 0);
350
- gh.addColorStop(0, "rgba(255,255,255,1)");
351
- gh.addColorStop(1, `hsl(${hue},100%,50%)`);
352
- c.fillStyle = gh;
353
- c.fillRect(gx, gy, sz, sz);
354
- const gv = c.createLinearGradient(0, gy, 0, gy + sz);
355
- gv.addColorStop(0, "rgba(0,0,0,0)");
356
- gv.addColorStop(1, "rgba(0,0,0,1)");
357
- c.fillStyle = gv;
358
- c.fillRect(gx, gy, sz, sz);
359
- const hRad = (hue - 90) * Math.PI / 180;
360
- const hR = 188 / 2;
361
- const hmx = CX + Math.cos(hRad) * hR, hmy = CX + Math.sin(hRad) * hR;
362
- c.beginPath();
363
- c.arc(hmx, hmy, 8, 0, Math.PI * 2);
364
- c.fillStyle = "rgba(0,0,0,0.25)";
365
- c.fill();
366
- c.beginPath();
367
- c.arc(hmx, hmy, 6.5, 0, Math.PI * 2);
368
- c.strokeStyle = "#fff";
369
- c.lineWidth = 2;
370
- c.stroke();
371
- const smx = gx + sat * sz, smy = gy + (1 - lit) * sz;
372
- c.beginPath();
373
- c.arc(smx, smy, 7, 0, Math.PI * 2);
374
- c.fillStyle = "rgba(0,0,0,0.25)";
375
- c.fill();
376
- c.beginPath();
377
- c.arc(smx, smy, 5.5, 0, Math.PI * 2);
378
- c.strokeStyle = "#fff";
379
- c.lineWidth = 2;
380
- c.stroke();
381
- c.beginPath();
382
- c.arc(smx, smy, 3.5, 0, Math.PI * 2);
383
- c.strokeStyle = "#000";
384
- c.lineWidth = 1;
385
- c.stroke();
386
- }
387
- function hitTest(x, y) {
388
- if (Math.abs(x - CX) <= SQ_HALF && Math.abs(y - CX) <= SQ_HALF) return "square";
389
- const dx = x - CX, dy = y - CX;
390
- const dist = Math.sqrt(dx * dx + dy * dy);
391
- if (dist >= RING_INNER - 4 && dist <= 110) return "ring";
392
- return null;
393
- }
394
- function pickHue(x, y) {
395
- let angle = Math.atan2(y - CX, x - CX) * 180 / Math.PI + 90;
396
- if (angle < 0) angle += 360;
397
- return angle;
398
- }
399
- function pickSL(x, y) {
400
- const gx = CX - SQ_HALF, gy = CX - SQ_HALF, sz = SQ_HALF * 2;
401
- return [Math.max(0, Math.min(1, (x - gx) / sz)), Math.max(.02, Math.min(.98, 1 - (y - gy) / sz))];
402
- }
403
- function ColorWheel({ hue, sat, lit, onChange }) {
404
- const cvsRef = (0, react.useRef)(null);
405
- const [col, setCol] = (0, react.useState)({
406
- hue,
407
- sat,
408
- lit
409
- });
410
- const colRef = (0, react.useRef)(col);
411
- colRef.current = col;
412
- (0, react.useEffect)(() => {
413
- setCol((c) => c.hue === hue && c.sat === sat && c.lit === lit ? c : {
414
- hue,
415
- sat,
416
- lit
417
- });
418
- }, [
419
- hue,
420
- sat,
421
- lit
422
- ]);
423
- (0, react.useEffect)(() => {
424
- if (cvsRef.current) drawWheel(cvsRef.current, col.hue, col.sat, col.lit);
425
- }, [col]);
426
- const apply = (0, react.useCallback)((nh, ns, nl) => {
427
- setCol({
428
- hue: nh,
429
- sat: ns,
430
- lit: nl
431
- });
432
- onChange(nh, ns, nl);
433
- }, [onChange]);
434
- const onDown = (0, react.useCallback)((e) => {
435
- const r = cvsRef.current.getBoundingClientRect();
436
- const x = e.clientX - r.left, y = e.clientY - r.top;
437
- const region = hitTest(x, y);
438
- if (!region) return;
439
- if (region === "ring") apply(pickHue(x, y), colRef.current.sat, colRef.current.lit);
440
- else {
441
- const [s, l] = pickSL(x, y);
442
- apply(colRef.current.hue, s, l);
443
- }
444
- const onMove = (ev) => {
445
- const rr = cvsRef.current.getBoundingClientRect();
446
- const mx = ev.clientX - rr.left, my = ev.clientY - rr.top;
447
- if (region === "ring") {
448
- const d = Math.sqrt((mx - CX) ** 2 + (my - CX) ** 2);
449
- if (d >= RING_INNER - 10 && d <= 116) apply(pickHue(mx, my), colRef.current.sat, colRef.current.lit);
450
- } else {
451
- const [s, l] = pickSL(mx, my);
452
- apply(colRef.current.hue, s, l);
453
- }
454
- };
455
- const onUp = () => {
456
- document.removeEventListener("mousemove", onMove);
457
- document.removeEventListener("mouseup", onUp);
458
- };
459
- document.addEventListener("mousemove", onMove);
460
- document.addEventListener("mouseup", onUp);
461
- }, [apply]);
462
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("canvas", {
463
- ref: cvsRef,
464
- width: WHEEL_SIZE,
465
- height: WHEEL_SIZE,
466
- style: ST.wheelCanvas,
467
- onMouseDown: onDown
468
- });
469
- }
470
- function compress(img, side, q) {
471
- const s = Math.min(1, side / Math.max(img.width, img.height));
472
- const c = document.createElement("canvas");
473
- c.width = Math.max(1, Math.round(img.width * s));
474
- c.height = Math.max(1, Math.round(img.height * s));
475
- c.getContext("2d").drawImage(img, 0, 0, c.width, c.height);
476
- return c.toDataURL("image/jpeg", q);
477
- }
478
- function readImg(file, cb) {
479
- const r = new FileReader();
480
- r.onerror = () => cb(null);
481
- r.onload = () => {
482
- const img = new Image();
483
- img.onerror = () => cb(null);
484
- img.onload = () => {
485
- try {
486
- let u = compress(img, 1600, .75);
487
- if (u.length > 2e6) u = compress(img, 1e3, .6);
488
- if (u.length > 2e6) u = compress(img, 800, .5);
489
- cb(u);
490
- } catch {
491
- cb(null);
492
- }
493
- };
494
- img.src = r.result;
495
- };
496
- r.readAsDataURL(file);
497
- }
498
- const ST = {
499
- root: {
500
- display: "flex",
501
- flexDirection: "column",
502
- gap: "20px",
503
- padding: "4px 0",
504
- maxWidth: "640px"
505
- },
506
- h2: {
507
- color: "var(--dsw-alias-label-primary)",
508
- fontSize: "18px",
509
- fontWeight: 600,
510
- margin: 0
511
- },
512
- sub: {
513
- color: "var(--dsw-alias-label-tertiary)",
514
- fontSize: "13px"
515
- },
516
- hr: {
517
- height: "1px",
518
- background: "var(--dsw-alias-border-l2)",
519
- border: "none",
520
- margin: "4px 0"
521
- },
522
- label: {
523
- color: "var(--dsw-alias-label-primary)",
524
- fontSize: "16px",
525
- fontWeight: 600,
526
- marginBottom: "8px"
527
- },
528
- hint: {
529
- color: "var(--dsw-alias-label-tertiary)",
530
- fontSize: "12px",
531
- lineHeight: "18px",
532
- marginTop: "4px"
533
- },
534
- colorHint: {
535
- color: "var(--dsw-alias-label-tertiary)",
536
- fontSize: "12px",
537
- lineHeight: "18px",
538
- marginTop: "7px",
539
- textAlign: "center"
540
- },
541
- wheelCanvas: {
542
- cursor: "crosshair",
543
- borderRadius: "50%"
544
- },
545
- center: {
546
- display: "flex",
547
- justifyContent: "center"
548
- },
549
- btnGroup: {
550
- display: "flex",
551
- justifyContent: "center",
552
- marginTop: "10px"
553
- },
554
- row: {
555
- display: "flex",
556
- alignItems: "center",
557
- gap: "10px",
558
- flexWrap: "wrap"
559
- },
560
- sliderBlock: {
561
- display: "flex",
562
- flexDirection: "column",
563
- gap: "4px"
564
- },
565
- sliderRow: {
566
- display: "flex",
567
- alignItems: "center",
568
- gap: "10px"
569
- },
570
- sliderLabel: {
571
- color: "var(--dsw-alias-label-secondary)",
572
- fontSize: "13px"
573
- },
574
- smallHint: {
575
- color: "var(--dsw-alias-label-tertiary)",
576
- fontSize: "11px",
577
- lineHeight: "16px"
578
- },
579
- slider: {
580
- flex: 1,
581
- accentColor: "var(--dsw-alias-brand-primary)",
582
- minWidth: "160px"
583
- },
584
- sliderVal: {
585
- color: "var(--dsw-alias-label-secondary)",
586
- fontSize: "12px",
587
- whiteSpace: "nowrap",
588
- width: "44px",
589
- textAlign: "right"
590
- },
591
- btn: {
592
- height: "32px",
593
- padding: "0 14px",
594
- borderRadius: "8px",
595
- border: "1px solid var(--dsw-alias-border-l2)",
596
- background: "var(--dsw-alias-button-elevated-fill)",
597
- color: "var(--dsw-alias-label-primary)",
598
- cursor: "pointer",
599
- fontSize: "13px",
600
- font: "inherit",
601
- boxSizing: "border-box"
602
- },
603
- btnDanger: { color: "var(--dsw-alias-state-error-primary)" },
604
- btnPrimary: {
605
- background: "var(--dsw-alias-brand-primary)",
606
- color: "var(--dsw-alias-brand-text)",
607
- border: "none"
608
- },
609
- preview: {
610
- width: "368px",
611
- height: "225px",
612
- objectFit: "cover",
613
- borderRadius: "6px",
614
- border: "1px solid var(--dsw-alias-border-l2)",
615
- cursor: "pointer"
616
- },
617
- sliders: {
618
- display: "flex",
619
- flexDirection: "column",
620
- gap: "12px",
621
- marginTop: "8px"
622
- },
623
- overlay: {
624
- position: "fixed",
625
- inset: 0,
626
- zIndex: 9999,
627
- background: "rgba(0,0,0,0.7)",
628
- display: "flex",
629
- flexDirection: "column",
630
- alignItems: "center",
631
- justifyContent: "center",
632
- gap: "12px"
633
- },
634
- modalTitle: {
635
- color: "#fff",
636
- fontSize: "16px",
637
- fontWeight: 500
638
- },
639
- modalHint: {
640
- color: "rgba(255,255,255,0.6)",
641
- fontSize: "12px"
642
- },
643
- previewRect: {
644
- position: "relative",
645
- overflow: "hidden",
646
- border: "2px solid rgba(255,255,255,0.3)",
647
- borderRadius: "8px",
648
- background: "#000",
649
- cursor: "grab"
650
- },
651
- previewImg: {
652
- position: "absolute",
653
- transformOrigin: "0 0",
654
- pointerEvents: "none"
655
- },
656
- modalBtns: {
657
- display: "flex",
658
- gap: "10px"
659
- }
660
- };
661
- function BgEditor({ url, t, onClose, onCommit }) {
662
- const pw = Math.min(window.innerWidth * .75, 860);
663
- const ph = Math.round(pw * window.innerHeight / window.innerWidth);
664
- const saved = rBgState();
665
- const [zoom, setZoom] = (0, react.useState)(saved.iw > 0 ? saved.zoom : 1);
666
- const [pos, setPos] = (0, react.useState)(saved.iw > 0 ? {
667
- x: saved.x * pw,
668
- y: saved.y * ph
669
- } : {
670
- x: 0,
671
- y: 0
672
- });
673
- const [imgSize, setImgSize] = (0, react.useState)({
674
- w: 0,
675
- h: 0
676
- });
677
- const containerRef = (0, react.useRef)(null);
678
- const imgRef = (0, react.useRef)(null);
679
- const dragRef = (0, react.useRef)({
680
- active: false,
681
- sx: 0,
682
- sy: 0,
683
- spx: 0,
684
- spy: 0
685
- });
686
- (0, react.useEffect)(() => {
687
- const img = new Image();
688
- img.onload = () => {
689
- const scale = Math.min(pw / img.width, ph / img.height);
690
- const w = img.width * scale, h = img.height * scale;
691
- setImgSize({
692
- w,
693
- h
694
- });
695
- const s = rBgState();
696
- if (s.iw > 0 && s.iw === img.width && s.ih === img.height) {
697
- setZoom(s.zoom);
698
- setPos({
699
- x: s.x * pw - w * s.zoom / 2,
700
- y: s.y * ph - h * s.zoom / 2
701
- });
702
- } else {
703
- setZoom(1);
704
- setPos({
705
- x: (pw - w) / 2,
706
- y: (ph - h) / 2
707
- });
708
- }
709
- };
710
- img.src = url;
711
- }, [
712
- url,
713
- pw,
714
- ph
715
- ]);
716
- const onDown = (0, react.useCallback)((e) => {
717
- e.preventDefault();
718
- dragRef.current = {
719
- active: true,
720
- sx: e.clientX,
721
- sy: e.clientY,
722
- spx: pos.x,
723
- spy: pos.y
724
- };
725
- const onMove = (ev) => {
726
- if (!dragRef.current.active) return;
727
- setPos({
728
- x: dragRef.current.spx + ev.clientX - dragRef.current.sx,
729
- y: dragRef.current.spy + ev.clientY - dragRef.current.sy
730
- });
731
- };
732
- const onUp = () => {
733
- dragRef.current.active = false;
734
- document.removeEventListener("mousemove", onMove);
735
- document.removeEventListener("mouseup", onUp);
736
- };
737
- document.addEventListener("mousemove", onMove);
738
- document.addEventListener("mouseup", onUp);
739
- }, [pos]);
740
- const onWheelCb = (0, react.useCallback)((e) => {
741
- e.preventDefault();
742
- const rect = containerRef.current.getBoundingClientRect();
743
- const mx = e.clientX - rect.left, my = e.clientY - rect.top;
744
- const factor = e.deltaY > 0 ? .92 : 1.08;
745
- const nz = Math.max(.1, Math.min(10, zoom * factor));
746
- const nx = mx - (mx - pos.x) * (nz / zoom);
747
- const ny = my - (my - pos.y) * (nz / zoom);
748
- setZoom(nz);
749
- setPos({
750
- x: nx,
751
- y: ny
752
- });
753
- }, [zoom, pos]);
754
- (0, react.useEffect)(() => {
755
- const el = containerRef.current;
756
- if (!el) return;
757
- el.addEventListener("wheel", onWheelCb, { passive: false });
758
- return () => el.removeEventListener("wheel", onWheelCb);
759
- }, [onWheelCb]);
760
- const resetView = (0, react.useCallback)(() => {
761
- if (imgSize.w === 0) return;
762
- setZoom(1);
763
- setPos({
764
- x: (pw - imgSize.w) / 2,
765
- y: (ph - imgSize.h) / 2
766
- });
767
- }, [
768
- pw,
769
- ph,
770
- imgSize
771
- ]);
772
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
773
- style: ST.overlay,
774
- onClick: (e) => {
775
- if (e.target === e.currentTarget) onClose();
776
- },
777
- children: [
778
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
779
- style: ST.modalTitle,
780
- children: t("editorTitle")
781
- }),
782
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
783
- ref: containerRef,
784
- style: {
785
- ...ST.previewRect,
786
- width: pw,
787
- height: ph
788
- },
789
- onMouseDown: onDown,
790
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
791
- ref: imgRef,
792
- src: url,
793
- alt: "",
794
- draggable: false,
795
- style: {
796
- ...ST.previewImg,
797
- width: imgSize.w,
798
- height: imgSize.h,
799
- transform: `translate(${pos.x}px,${pos.y}px) scale(${zoom})`
800
- }
801
- })
802
- }),
803
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
804
- style: ST.modalHint,
805
- children: t("editorHint")
806
- }),
807
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
808
- style: ST.modalBtns,
809
- children: [
810
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
811
- style: ST.btn,
812
- onClick: resetView,
813
- children: t("editorReset")
814
- }),
815
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
816
- style: ST.btn,
817
- onClick: onClose,
818
- children: t("editorCancel")
819
- }),
820
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
821
- style: {
822
- ...ST.btn,
823
- ...ST.btnPrimary
824
- },
825
- onClick: () => onCommit(zoom, (pos.x + imgSize.w * zoom / 2) / pw, (pos.y + imgSize.h * zoom / 2) / ph, imgRef.current?.naturalWidth ?? 0, imgRef.current?.naturalHeight ?? 0),
826
- children: t("editorCommit")
827
- })
828
- ]
829
- })
830
- ]
831
- });
832
- }
833
- function LiveSlider({ min, max, step, def, fmt, onInput, onChange }) {
834
- const valRef = (0, react.useRef)(null);
835
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
836
- style: ST.sliderRow,
837
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
838
- type: "range",
839
- min,
840
- max,
841
- step,
842
- defaultValue: def,
843
- style: ST.slider,
844
- onInput: (e) => {
845
- const v = Number(e.target.value);
846
- onInput(v);
847
- if (valRef.current) valRef.current.textContent = fmt(v);
848
- },
849
- onChange: (e) => onChange(Number(e.target.value))
850
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
851
- ref: valRef,
852
- style: ST.sliderVal,
853
- children: fmt(def)
854
- })]
855
- });
856
- }
857
- function ThemeSection(props) {
858
- const { t, hue, sat, lit, setColor, url, setWp, setOp, setWop, setBl, setSop, useStore } = props;
859
- const storeUrl = useStore((s) => s.url);
860
- const fileRef = (0, react.useRef)(null);
861
- const [editorOpen, setEditorOpen] = (0, react.useState)(false);
862
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
863
- style: ST.root,
864
- children: [
865
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
866
- style: ST.h2,
867
- children: t("nav")
868
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
869
- style: ST.sub,
870
- children: t("subtitle")
871
- })] }),
872
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [
873
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
874
- style: ST.label,
875
- children: t("colorTitle")
876
- }),
877
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
878
- style: ST.center,
879
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ColorWheel, {
880
- hue,
881
- sat,
882
- lit,
883
- onChange: setColor
884
- })
885
- }),
886
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
887
- style: ST.colorHint,
888
- children: t("colorHint")
889
- })
890
- ] }),
891
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("hr", { style: ST.hr }),
892
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
893
- style: ST.label,
894
- children: t("uiTitle")
895
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
896
- style: ST.sliders,
897
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
898
- style: ST.sliderBlock,
899
- children: [
900
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
901
- style: ST.sliderLabel,
902
- children: t("uiOpacity")
903
- }),
904
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
905
- style: ST.smallHint,
906
- children: t("uiOpacityHint")
907
- }),
908
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LiveSlider, {
909
- min: 0,
910
- max: 100,
911
- step: 1,
912
- def: Math.round(rOp() * 100),
913
- fmt: (v) => `${v}%`,
914
- onInput: (v) => {
915
- const op = v / 100;
916
- wLS(LS_OP, String(op));
917
- applyCustomTokens(op);
918
- },
919
- onChange: (v) => setOp(v / 100)
920
- })
921
- ]
922
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
923
- style: ST.sliderBlock,
924
- children: [
925
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
926
- style: ST.sliderLabel,
927
- children: t("uiSop")
928
- }),
929
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
930
- style: ST.smallHint,
931
- children: t("uiSopHint")
932
- }),
933
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LiveSlider, {
934
- min: 0,
935
- max: 100,
936
- step: 1,
937
- def: Math.round(rSop() * 100),
938
- fmt: (v) => `${v}%`,
939
- onInput: (v) => {
940
- const op = v / 100;
941
- wLS(LS_SOP, String(op));
942
- applySettingsOverrides(op);
943
- },
944
- onChange: (v) => setSop(v / 100)
945
- })
946
- ]
947
- })]
948
- })] }),
949
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("hr", { style: ST.hr }),
950
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [
951
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
952
- style: ST.label,
953
- children: t("bgTitle")
954
- }),
955
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
956
- style: ST.center,
957
- children: storeUrl ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
958
- src: storeUrl,
959
- alt: "",
960
- style: ST.preview,
961
- onClick: () => setEditorOpen(true)
962
- }) : null
963
- }),
964
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
965
- style: ST.btnGroup,
966
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
967
- style: ST.row,
968
- children: [
969
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
970
- type: "button",
971
- style: ST.btn,
972
- onClick: () => fileRef.current?.click(),
973
- children: t("bgChoose")
974
- }),
975
- storeUrl ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
976
- type: "button",
977
- style: ST.btn,
978
- onClick: () => setEditorOpen(true),
979
- children: t("bgEdit")
980
- }) : null,
981
- storeUrl ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
982
- type: "button",
983
- style: {
984
- ...ST.btn,
985
- ...ST.btnDanger
986
- },
987
- onClick: () => setWp(null),
988
- children: t("bgRemove")
989
- }) : null,
990
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
991
- ref: fileRef,
992
- type: "file",
993
- accept: "image/*",
994
- style: { display: "none" },
995
- onChange: (e) => {
996
- const f = e.target.files?.[0];
997
- if (!f) return;
998
- readImg(f, (d) => {
999
- if (d) setWp(d);
1000
- e.target.value = "";
1001
- });
1002
- }
1003
- })
1004
- ]
1005
- })
1006
- }),
1007
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1008
- style: ST.sliders,
1009
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1010
- style: ST.sliderBlock,
1011
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1012
- style: ST.sliderLabel,
1013
- children: t("wpOpacity")
1014
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LiveSlider, {
1015
- min: 0,
1016
- max: 100,
1017
- step: 1,
1018
- def: Math.round(rWop() * 100),
1019
- fmt: (v) => `${v}%`,
1020
- onInput: (v) => {
1021
- const op = v / 100;
1022
- wLS(LS_WOP, String(op));
1023
- if (wpEl) wpEl.style.opacity = String(op);
1024
- },
1025
- onChange: (v) => setWop(v / 100)
1026
- })]
1027
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1028
- style: ST.sliderBlock,
1029
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1030
- style: ST.sliderLabel,
1031
- children: t("bgBlur")
1032
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LiveSlider, {
1033
- min: 0,
1034
- max: 60,
1035
- step: 1,
1036
- def: rBl(),
1037
- fmt: (v) => `${v}px`,
1038
- onInput: (v) => {
1039
- wLS(LS_BL, String(v));
1040
- if (wpEl) wpEl.style.filter = v > 0 ? `blur(${v}px)` : "none";
1041
- },
1042
- onChange: (v) => setBl(v)
1043
- })]
1044
- })]
1045
- }),
1046
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1047
- style: ST.hint,
1048
- children: t("bgHint")
1049
- })
1050
- ] }),
1051
- editorOpen && storeUrl ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BgEditor, {
1052
- url: storeUrl,
1053
- t,
1054
- onClose: () => setEditorOpen(false),
1055
- onCommit: (z, x, y, iw, ih) => {
1056
- wLS(LS_BG, JSON.stringify({
1057
- zoom: z,
1058
- x,
1059
- y,
1060
- iw,
1061
- ih
1062
- }));
1063
- applyWp(ctxRef);
1064
- setEditorOpen(false);
1065
- }
1066
- }) : null
1067
- ]
1068
- });
1069
- }
1070
- function apply(ctx) {
1071
- ctxRef = ctx;
1072
- const [initH, initS, initL] = rColor();
1073
- let customDispose = null;
1074
- const registerCustom = (h, s, l) => {
1075
- customDispose?.();
1076
- try {
1077
- const { colorScheme, tokens } = genTokens(h, s, l);
1078
- customDispose = ctx.theme.register({
1079
- id: CUSTOM_ID,
1080
- colorScheme,
1081
- tokens
1082
- });
1083
- } catch {
1084
- customDispose = null;
1085
- }
1086
- ctx.theme.setTheme(CUSTOM_ID);
1087
- };
1088
- if (rLS(LS_COLOR)) registerCustom(initH, initS, initL);
1089
- ctx.effect(() => () => {
1090
- customDispose?.();
1091
- }, "dsh-any-background: skin dispose");
1092
- let styleEl;
1093
- if (typeof document !== "undefined") {
1094
- styleEl = document.createElement("style");
1095
- styleEl.dataset.plugin = "dsh-any-background";
1096
- styleEl.textContent = `body[data-ds-dark-theme="${CUSTOM_ID}"]::before{content:'';position:fixed;inset:0;z-index:-1;pointer-events:none;background:radial-gradient(ellipse 80% 60% at 50% 0%,rgba(255,255,255,0.03) 0%,transparent 60%)}${SETTINGS_STYLE_RULE}`;
1097
- document.head.appendChild(styleEl);
1098
- }
1099
- ctx.effect(() => () => {
1100
- styleEl?.parentNode?.removeChild(styleEl);
1101
- }, "dsh-any-background: gradient");
1102
- let rev = 0;
1103
- const store = (0, _deepseek_ai_dsh_client_runtime_client.defineStore)({
1104
- init: () => ({
1105
- url: null,
1106
- rev: -1
1107
- }),
1108
- actions: { syncBg: (d, url, r) => {
1109
- if (r <= d.rev) return;
1110
- d.url = url;
1111
- d.rev = r;
1112
- } }
1113
- });
1114
- let bound;
1115
- const syncBg = () => {
1116
- rev++;
1117
- bound?.syncBg(rWp(), rev);
1118
- };
1119
- applyWp(ctx);
1120
- syncBg();
1121
- ctx.effect(() => () => {
1122
- teardownWp();
1123
- }, "dsh-any-background: wp cleanup");
1124
- ctx.effect(() => ctx.on("theme/change", () => {
1125
- if (rLS(LS_COLOR)) {
1126
- const snapshot = ctx.theme.getTheme();
1127
- if (snapshot.preference !== CUSTOM_ID && snapshot.themes.some((t) => t.id === CUSTOM_ID)) ctx.theme.setTheme(CUSTOM_ID);
1128
- }
1129
- applyWp(ctx);
1130
- }), "dsh-any-background: theme change");
1131
- let frame = 0;
1132
- const applySoon = () => {
1133
- if (frame !== 0) return;
1134
- frame = requestAnimationFrame(() => {
1135
- frame = 0;
1136
- applyWp(ctx);
1137
- });
1138
- };
1139
- const sentinel = document.createElement("div");
1140
- sentinel.style.cssText = "position:fixed;inset:0;pointer-events:none;visibility:hidden";
1141
- document.body.append(sentinel);
1142
- const viewportObserver = new ResizeObserver(applySoon);
1143
- viewportObserver.observe(sentinel);
1144
- const dprQuery = window.matchMedia(`(resolution: ${window.devicePixelRatio}dppx)`);
1145
- dprQuery.addEventListener("change", applySoon);
1146
- ctx.effect(() => () => {
1147
- viewportObserver.disconnect();
1148
- dprQuery.removeEventListener("change", applySoon);
1149
- sentinel.remove();
1150
- }, "dsh-any-background: viewport watch");
1151
- ctx.effect(() => ctx.locale.register(NS, {
1152
- zh,
1153
- en
1154
- }), "dsh-any-background: i18n");
1155
- const sectionInject = (actions) => {
1156
- bound = actions;
1157
- syncBg();
1158
- const [wh, ws, wl] = rColor();
1159
- const [dh, ds, dv] = hslToHsv(wh, ws, wl);
1160
- return {
1161
- t: ctx.locale.bind(NS),
1162
- hue: dh,
1163
- sat: ds,
1164
- lit: dv,
1165
- setColor: (nh, ns, nl) => {
1166
- const [sh, ss, sl] = hsvToHsl(nh, ns, nl);
1167
- wLS(LS_COLOR, JSON.stringify([
1168
- sh,
1169
- ss,
1170
- sl
1171
- ]));
1172
- registerCustom(sh, ss, sl);
1173
- applyWp(ctx);
1174
- },
1175
- setWp: (u) => {
1176
- wLS(LS_WP, u);
1177
- wLS(LS_BG, null);
1178
- applyWp(ctx);
1179
- syncBg();
1180
- },
1181
- setOp: (v) => {
1182
- wLS(LS_OP, String(v));
1183
- applyWp(ctx);
1184
- syncBg();
1185
- },
1186
- setWop: (v) => {
1187
- wLS(LS_WOP, String(v));
1188
- applyWp(ctx);
1189
- syncBg();
1190
- },
1191
- setBl: (v) => {
1192
- wLS(LS_BL, String(v));
1193
- applyWp(ctx);
1194
- syncBg();
1195
- },
1196
- setSop: (v) => {
1197
- wLS(LS_SOP, String(v));
1198
- applySettingsOverrides(v);
1199
- }
1200
- };
1201
- };
1202
- ctx.slots.inject("settings.section", () => ctx.slots.register({
1203
- name: "settings.section",
1204
- id: "dsh-any-background",
1205
- order: 35,
1206
- label: () => ctx.locale.bind(NS)("nav"),
1207
- locale: NS,
1208
- store,
1209
- inject: sectionInject
1210
- }, ThemeSection));
1211
- const restoreSaved = () => {
1212
- if (rLS(LS_COLOR)) {
1213
- const [h, s, l] = rColor();
1214
- registerCustom(h, s, l);
1215
- }
1216
- applyWp(ctx);
1217
- };
1218
- const restoreTimers = [300, 1500].map((delay) => window.setTimeout(restoreSaved, delay));
1219
- ctx.effect(() => () => {
1220
- restoreTimers.forEach((id) => window.clearTimeout(id));
1221
- }, "dsh-any-background: boot restore");
1222
- const watchdogId = window.setInterval(() => {
1223
- if (!rLS(LS_COLOR)) return;
1224
- const snapshot = ctx.theme.getTheme();
1225
- let changed = false;
1226
- if (!snapshot.themes.some((t) => t.id === CUSTOM_ID)) {
1227
- const [h, s, l] = rColor();
1228
- registerCustom(h, s, l);
1229
- changed = true;
1230
- } else if (snapshot.preference !== CUSTOM_ID) {
1231
- ctx.theme.setTheme(CUSTOM_ID);
1232
- changed = true;
1233
- }
1234
- if (changed) applyWp(ctx);
1235
- }, 1e3);
1236
- ctx.effect(() => () => {
1237
- window.clearInterval(watchdogId);
1238
- }, "dsh-any-background: theme watchdog");
1239
- }
1240
- //#endregion
1241
- exports.apply = apply;
1242
- exports.inject = inject;
1243
- exports.name = name;
1244
- return module.exports;
1245
- }
1246
- });
1247
-
1
+ window.__ModuleLoader__.load({
2
+ id: "dsh-any-background",
3
+ factory: (require) => {
4
+ var module = { exports: {} };
5
+ var exports = module.exports;
6
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
+ let react = require("react");
8
+ let _deepseek_ai_dsh_client_runtime_client = require("@deepseek-ai/dsh-client-runtime/client");
9
+ let react_jsx_runtime = require("react/jsx-runtime");
10
+ //#region src/client/index.tsx
11
+ const name = "dsh-any-background";
12
+ const inject = [
13
+ "slots",
14
+ "locale",
15
+ "theme",
16
+ "connection"
17
+ ];
18
+ const NS = "settings.anyBg";
19
+ const DEF_OP = .85;
20
+ const DEF_BL = 0;
21
+ const DEF_SOP = 1;
22
+ const CUSTOM_ID = "custom-color";
23
+ const zh = {
24
+ nav: "主题",
25
+ subtitle: "自定义界面外观",
26
+ colorTitle: "主题色",
27
+ colorHint: "在色轮上选择色相,在方形中调整饱和度和明度",
28
+ uiTitle: "界面",
29
+ uiOpacity: "主界面透明度",
30
+ uiOpacityHint: "拖动滑块调整主页界面背景的透明度",
31
+ uiSop: "设置界面透明度",
32
+ uiSopHint: "拖动滑块调整设置页界面背景的透明度",
33
+ bgTitle: "背景图片",
34
+ bgChoose: "选择图片",
35
+ bgRemove: "移除图片",
36
+ bgEdit: "编辑位置",
37
+ wpOpacity: "壁纸透明度",
38
+ bgBlur: "壁纸模糊",
39
+ bgHint: "拖动滑块实时调整。点击背景图可打开编辑器调整位置和大小",
40
+ editorTitle: "背景编辑器",
41
+ editorHint: "拖动移动图片,滚轮缩放大小",
42
+ editorCommit: "确认",
43
+ editorCancel: "取消",
44
+ editorReset: "重置"
45
+ };
46
+ const en = {
47
+ nav: "Theme",
48
+ subtitle: "Customize appearance",
49
+ colorTitle: "Theme color",
50
+ colorHint: "Pick hue on the ring, adjust saturation & lightness in the square",
51
+ uiTitle: "Interface",
52
+ uiOpacity: "Main interface opacity",
53
+ uiOpacityHint: "Drag to adjust homepage interface background opacity",
54
+ uiSop: "Settings interface opacity",
55
+ uiSopHint: "Drag to adjust the settings page background opacity",
56
+ bgTitle: "Wallpaper",
57
+ bgChoose: "Choose image",
58
+ bgRemove: "Remove image",
59
+ bgEdit: "Edit position",
60
+ wpOpacity: "Wallpaper opacity",
61
+ bgBlur: "Wallpaper blur",
62
+ bgHint: "Drag sliders for real-time adjustment. Click the image to open the editor",
63
+ editorTitle: "Background editor",
64
+ editorHint: "Drag to move, scroll to zoom",
65
+ editorCommit: "Confirm",
66
+ editorCancel: "Cancel",
67
+ editorReset: "Reset"
68
+ };
69
+ const DEFAULT_CONFIG = {
70
+ color: null,
71
+ opacity: .85,
72
+ settingsOpacity: 1,
73
+ wallpaperOpacity: 1,
74
+ blur: 0,
75
+ bgState: {
76
+ zoom: 1,
77
+ x: 0,
78
+ y: 0,
79
+ iw: 0,
80
+ ih: 0
81
+ }
82
+ };
83
+ let cfg = {
84
+ ...DEFAULT_CONFIG,
85
+ bgState: { ...DEFAULT_CONFIG.bgState }
86
+ };
87
+ let wpUrl = null;
88
+ let rpcCallFn = null;
89
+ const RPC_CHANNEL = "/dsh-any-background";
90
+ const RPC_NS = "dshAnyBackground";
91
+ const rpcEndpoint = (method) => `${RPC_NS}/${method}`;
92
+ async function rpcCall(method, payload) {
93
+ if (!rpcCallFn) return void 0;
94
+ try {
95
+ const res = await rpcCallFn(rpcEndpoint(method), payload);
96
+ if (res && res.ok === true) return res.value;
97
+ console.warn(`dsh-any-background: rpc "${method}" failed`, res?.error);
98
+ return;
99
+ } catch (e) {
100
+ console.warn(`dsh-any-background: rpc "${method}" threw`, e);
101
+ return;
102
+ }
103
+ }
104
+ /** Move a possibly-absent partial config into the shape the UI reads. */
105
+ function adoptConfig(raw) {
106
+ const c = raw ?? {};
107
+ const color = Array.isArray(c.color) && c.color.length === 3 ? [
108
+ c.color[0],
109
+ c.color[1],
110
+ c.color[2]
111
+ ] : null;
112
+ const bg = c.bgState ?? {};
113
+ cfg = {
114
+ color,
115
+ opacity: typeof c.opacity === "number" ? c.opacity : DEFAULT_CONFIG.opacity,
116
+ settingsOpacity: typeof c.settingsOpacity === "number" ? c.settingsOpacity : DEFAULT_CONFIG.settingsOpacity,
117
+ wallpaperOpacity: typeof c.wallpaperOpacity === "number" ? c.wallpaperOpacity : DEFAULT_CONFIG.wallpaperOpacity,
118
+ blur: typeof c.blur === "number" ? c.blur : DEFAULT_CONFIG.blur,
119
+ bgState: {
120
+ zoom: typeof bg.zoom === "number" ? bg.zoom : 1,
121
+ x: typeof bg.x === "number" ? bg.x : 0,
122
+ y: typeof bg.y === "number" ? bg.y : 0,
123
+ iw: typeof bg.iw === "number" && bg.iw > 0 ? bg.iw : 0,
124
+ ih: typeof bg.ih === "number" && bg.ih > 0 ? bg.ih : 0
125
+ }
126
+ };
127
+ }
128
+ /** Persist the current in-memory config to the node half (fire-and-forget). */
129
+ function saveConfig() {
130
+ rpcCall("writeConfig", { config: cfg });
131
+ }
132
+ /** Load the persisted theme (config + wallpaper) from the node half. */
133
+ async function loadPersisted() {
134
+ const data = await rpcCall("read", {});
135
+ if (data) {
136
+ if (data.config) adoptConfig(data.config);
137
+ if (typeof data.wallpaper === "string") wpUrl = data.wallpaper;
138
+ else if (data.wallpaper === null) wpUrl = null;
139
+ }
140
+ }
141
+ function rHasColor() {
142
+ return cfg.color !== null;
143
+ }
144
+ function rColor() {
145
+ return cfg.color ?? [
146
+ 220,
147
+ .55,
148
+ .25
149
+ ];
150
+ }
151
+ function rWp() {
152
+ return wpUrl;
153
+ }
154
+ function rOp() {
155
+ return typeof cfg.opacity === "number" ? Math.min(1, Math.max(0, cfg.opacity)) : DEF_OP;
156
+ }
157
+ function rWop() {
158
+ return typeof cfg.wallpaperOpacity === "number" ? Math.min(1, Math.max(0, cfg.wallpaperOpacity)) : 1;
159
+ }
160
+ function rBl() {
161
+ return typeof cfg.blur === "number" ? Math.min(60, Math.max(0, cfg.blur)) : DEF_BL;
162
+ }
163
+ function rSop() {
164
+ return typeof cfg.settingsOpacity === "number" ? Math.min(1, Math.max(0, cfg.settingsOpacity)) : DEF_SOP;
165
+ }
166
+ function rBgState() {
167
+ return cfg.bgState;
168
+ }
169
+ function hsvToHsl(h, s, v) {
170
+ const l = v * (1 - s / 2);
171
+ return [
172
+ h,
173
+ l === 0 || l === 1 ? 0 : (v - l) / Math.min(l, 1 - l),
174
+ l
175
+ ];
176
+ }
177
+ function hslToHsv(h, s, l) {
178
+ const v = l + s * Math.min(l, 1 - l);
179
+ return [
180
+ h,
181
+ v === 0 ? 0 : 2 * (1 - l / v),
182
+ v
183
+ ];
184
+ }
185
+ function genTokens(hue, sat, lit) {
186
+ const dark = lit < .55;
187
+ const h = (d) => ((hue + d) % 360 + 360) % 360;
188
+ const s = (d) => Math.max(0, Math.min(1, sat + d));
189
+ const l = (d) => Math.max(0, Math.min(1, lit + d));
190
+ const hsl = (hh, ss, ll) => `hsl(${Math.round(hh)},${Math.round(ss * 100)}%,${Math.round(ll * 100)}%)`;
191
+ const rgba = (hh, ss, ll, a) => {
192
+ `${Math.round(hh)}${Math.round(ss * 100)}${Math.round(ll * 100)}`;
193
+ return `hsla(${Math.round(hh)},${Math.round(ss * 100)}%,${Math.round(ll * 100)}%,${a})`;
194
+ };
195
+ if (dark) return {
196
+ colorScheme: "dark",
197
+ tokens: {
198
+ "--dsw-alias-bg-base": hsl(h(0), s(0), l(-.04)),
199
+ "--dsw-alias-bg-layer-1": hsl(h(0), s(0), l(.02)),
200
+ "--dsw-alias-bg-layer-2": hsl(h(0), s(0), l(.07)),
201
+ "--dsw-alias-bg-layer-3": hsl(h(0), s(-.05), l(.12)),
202
+ "--dsw-alias-bg-overlay": hsl(h(0), s(-.05), l(.12)),
203
+ "--dsw-alias-border-l1": rgba(h(0), s(-.1), l(.18), .12),
204
+ "--dsw-alias-border-l2": rgba(h(0), s(-.1), l(.22), .22),
205
+ "--dsw-alias-label-primary": hsl(0, 0, 1),
206
+ "--dsw-alias-label-secondary": hsl(0, 0, 1),
207
+ "--dsw-alias-label-tertiary": hsl(0, 0, 1),
208
+ "--dsw-alias-brand-primary": hsl(h(0), s(.1), Math.max(l(.2), .5)),
209
+ "--dsw-alias-brand-text": l(.2) > .6 ? "#000" : "#fff",
210
+ "--dsw-alias-button-primary-hover": hsl(h(0), s(.1), Math.max(l(.28), .58)),
211
+ "--dsw-alias-button-primary-dimmed": hsl(h(0), s(0), l(.07)),
212
+ "--dsw-alias-button-elevated-fill": hsl(h(0), s(0), l(.04)),
213
+ "--dsw-alias-interactive-bg-hover": rgba(h(0), s(0), Math.max(l(.15), .4), .12),
214
+ "--dsw-alias-interactive-bg-active": rgba(h(0), s(0), Math.max(l(.15), .4), .2),
215
+ "--dsw-alias-markdown-code-block": hsl(h(0), s(0), l(-.06)),
216
+ "--dsw-alias-markdown-inline-code": hsl(h(0), s(0), l(.04)),
217
+ "--dsw-alias-state-error-primary": "#ff5c72",
218
+ "--dsw-alias-state-success-primary": "#3ddc84",
219
+ "--dsw-alias-state-warn-primary": "#ffb347",
220
+ "--dsw-specific-sidebar-fill": hsl(h(0), s(0), l(-.06)),
221
+ "--dsw-specific-sidebar-nav-item-active": hsl(h(0), s(0), l(.04)),
222
+ "--dsw-specific-sidebar-nav-item-hover": hsl(h(0), s(0), l(0)),
223
+ "--dsw-specific-input-major": hsl(h(0), s(0), l(.02)),
224
+ "--dsw-alias-scrollbar-bg-l1": hsl(h(0), s(-.05), l(.12)),
225
+ "--dsw-alias-scrollbar-bg-l2": hsl(h(0), s(-.05), l(.16)),
226
+ "--dsw-alias-scrollbar-hover-l1": hsl(h(0), s(-.05), l(.22)),
227
+ "--dsw-alias-scrollbar-hover-l2": hsl(h(0), s(-.05), l(.22))
228
+ }
229
+ };
230
+ return {
231
+ colorScheme: "light",
232
+ tokens: {
233
+ "--dsw-alias-bg-base": hsl(h(0), s(-.08), l(.03)),
234
+ "--dsw-alias-bg-layer-1": hsl(h(0), s(-.12), l(.07)),
235
+ "--dsw-alias-bg-layer-2": hsl(h(0), s(-.1), l(-.03)),
236
+ "--dsw-alias-bg-layer-3": hsl(h(0), s(-.08), l(-.09)),
237
+ "--dsw-alias-bg-overlay": hsl(h(0), s(-.12), l(.08)),
238
+ "--dsw-alias-border-l1": rgba(h(0), s(-.15), l(-.35), .18),
239
+ "--dsw-alias-border-l2": rgba(h(0), s(-.15), l(-.35), .3),
240
+ "--dsw-alias-label-primary": hsl(0, 0, 0),
241
+ "--dsw-alias-label-secondary": hsl(0, 0, 0),
242
+ "--dsw-alias-label-tertiary": hsl(0, 0, 0),
243
+ "--dsw-alias-brand-primary": hsl(h(0), s(.05), Math.min(l(-.18), .45)),
244
+ "--dsw-alias-brand-text": "#fff",
245
+ "--dsw-alias-button-primary-hover": hsl(h(0), s(.05), Math.min(l(-.12), .5)),
246
+ "--dsw-alias-button-primary-dimmed": hsl(h(0), s(-.1), l(-.03)),
247
+ "--dsw-alias-button-elevated-fill": hsl(h(0), s(-.1), l(.1)),
248
+ "--dsw-alias-interactive-bg-hover": rgba(h(0), s(0), l(-.3), .08),
249
+ "--dsw-alias-interactive-bg-active": rgba(h(0), s(0), l(-.3), .14),
250
+ "--dsw-alias-markdown-code-block": hsl(h(0), s(-.1), l(-.03)),
251
+ "--dsw-alias-markdown-inline-code": hsl(h(0), s(-.08), l(.04)),
252
+ "--dsw-specific-sidebar-fill": hsl(h(0), s(-.1), l(-.03)),
253
+ "--dsw-specific-sidebar-nav-item-active": hsl(h(0), s(-.08), l(.05)),
254
+ "--dsw-specific-sidebar-nav-item-hover": hsl(h(0), s(-.12), l(0)),
255
+ "--dsw-specific-input-major": hsl(h(0), s(-.12), l(.1)),
256
+ "--dsw-alias-scrollbar-bg-l1": hsl(h(0), s(-.1), l(-.08)),
257
+ "--dsw-alias-scrollbar-bg-l2": hsl(h(0), s(-.08), l(-.12)),
258
+ "--dsw-alias-scrollbar-hover-l1": hsl(h(0), s(-.08), l(-.16)),
259
+ "--dsw-alias-scrollbar-hover-l2": hsl(h(0), s(-.08), l(-.16))
260
+ }
261
+ };
262
+ }
263
+ let wpEl = null;
264
+ let ctxRef = null;
265
+ function toRgba(c, a) {
266
+ const hx = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(c.trim());
267
+ if (hx) {
268
+ let d = hx[1];
269
+ if (d.length === 3) d = d.split("").map((x) => x + x).join("");
270
+ const n = parseInt(d, 16);
271
+ return `rgba(${n >> 16 & 255},${n >> 8 & 255},${n & 255},${a})`;
272
+ }
273
+ const rgb = /^rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)(?:\s*,\s*([\d.]+))?\s*\)$/i.exec(c.trim());
274
+ if (rgb) return `rgba(${rgb[1]},${rgb[2]},${rgb[3]},${a})`;
275
+ const hsl = /^hsla?\(\s*([\d.]+)\s*,\s*([\d.]+)%\s*,\s*([\d.]+)%/i.exec(c.trim());
276
+ if (hsl) return `hsla(${hsl[1]},${hsl[2]}%,${hsl[3]}%,${a})`;
277
+ return c.trim();
278
+ }
279
+ let appliedTokenNames = [];
280
+ /** Remove every inline token this plugin wrote (teardown symmetry). */
281
+ function clearCustomTokens() {
282
+ for (const name of appliedTokenNames) document.body.style.removeProperty(name);
283
+ appliedTokenNames = [];
284
+ }
285
+ /**
286
+ * Write the saved color's full token set as inline variables on body — the
287
+ * same write surface the theme presenter owns, but derived DIRECTLY from the
288
+ * saved pick, so the theme color never depends on the theme service's active
289
+ * state or the presenter's timing. The bg-base and sidebar tokens are
290
+ * re-emitted at the requested alpha; every other token (layers, labels,
291
+ * borders, brand) is written verbatim. No reads: nothing can observe a stale
292
+ * or reset theme value and leave the homepage on the system color.
293
+ */
294
+ function applyCustomTokens(op) {
295
+ const [h, s, l] = rColor();
296
+ const { tokens } = genTokens(h, s, l);
297
+ const sideOp = Math.min(1, op + .08);
298
+ clearCustomTokens();
299
+ if (l < .55) document.body.setAttribute("data-ds-dark-theme", "");
300
+ else document.body.removeAttribute("data-ds-dark-theme");
301
+ for (const [name, value] of Object.entries(tokens)) {
302
+ let v = value;
303
+ if (name === "--dsw-alias-bg-base") v = toRgba(value, op);
304
+ else if (name === "--dsw-specific-sidebar-fill") v = toRgba(value, sideOp);
305
+ document.body.style.setProperty(name, v);
306
+ appliedTokenNames.push(name);
307
+ }
308
+ }
309
+ const SETTINGS_STYLE_RULE = `[role="dialog"][aria-modal="true"][aria-labelledby]{background:var(--dsh-any-bg-settings-surface,var(--dsw-alias-bg-layer-2))}`;
310
+ function applySettingsOverrides(op) {
311
+ if (op >= 1) {
312
+ document.documentElement.style.removeProperty("--dsh-any-bg-settings-surface");
313
+ return;
314
+ }
315
+ const [h, s, l] = rColor();
316
+ const layer2 = genTokens(h, s, l).tokens["--dsw-alias-bg-layer-2"];
317
+ if (layer2 !== void 0) document.documentElement.style.setProperty("--dsh-any-bg-settings-surface", toRgba(layer2, op));
318
+ }
319
+ function applyWp(ctx) {
320
+ const url = rWp();
321
+ if (!url) {
322
+ wpEl?.remove();
323
+ wpEl = null;
324
+ } else {
325
+ if (!wpEl || !document.body.contains(wpEl)) {
326
+ wpEl = document.createElement("div");
327
+ wpEl.style.cssText = "position:fixed;inset:0;z-index:-1;pointer-events:none;background-repeat:no-repeat;";
328
+ document.body.prepend(wpEl);
329
+ }
330
+ const bg = rBgState();
331
+ wpEl.style.backgroundImage = `url("${url}")`;
332
+ if (bg.iw > 0) {
333
+ const fit = Math.min(window.innerWidth / bg.iw, window.innerHeight / bg.ih);
334
+ const w = bg.iw * fit * bg.zoom;
335
+ const h = bg.ih * fit * bg.zoom;
336
+ wpEl.style.backgroundSize = `${w}px ${h}px`;
337
+ wpEl.style.backgroundPosition = `${bg.x * window.innerWidth - w / 2}px ${bg.y * window.innerHeight - h / 2}px`;
338
+ } else {
339
+ wpEl.style.backgroundSize = "contain";
340
+ wpEl.style.backgroundPosition = "center";
341
+ }
342
+ const blur = rBl();
343
+ wpEl.style.filter = blur > 0 ? `blur(${blur}px)` : "none";
344
+ wpEl.style.opacity = String(rWop());
345
+ }
346
+ applyCustomTokens(rOp());
347
+ applySettingsOverrides(rSop());
348
+ }
349
+ function teardownWp() {
350
+ wpEl?.remove();
351
+ wpEl = null;
352
+ clearCustomTokens();
353
+ document.documentElement.style.removeProperty("--dsh-any-bg-settings-surface");
354
+ }
355
+ const WHEEL_SIZE = 220;
356
+ const CX = WHEEL_SIZE / 2;
357
+ const RING_OUTER = 106;
358
+ const RING_INNER = 82;
359
+ const SQ_HALF = Math.round(RING_INNER / Math.SQRT2);
360
+ function drawWheel(cvs, hue, sat, lit) {
361
+ const c = cvs.getContext("2d");
362
+ c.clearRect(0, 0, WHEEL_SIZE, WHEEL_SIZE);
363
+ for (let a = 0; a < 360; a++) {
364
+ const r1 = (a - 90) * Math.PI / 180;
365
+ const r2 = (a + 1.5 - 90) * Math.PI / 180;
366
+ c.beginPath();
367
+ c.arc(CX, CX, RING_OUTER, r1, r2);
368
+ c.arc(CX, CX, RING_INNER, r2, r1, true);
369
+ c.closePath();
370
+ c.fillStyle = `hsl(${a},100%,50%)`;
371
+ c.fill();
372
+ }
373
+ const gx = CX - SQ_HALF, gy = CX - SQ_HALF, sz = SQ_HALF * 2;
374
+ c.fillStyle = "#fff";
375
+ c.fillRect(gx, gy, sz, sz);
376
+ const gh = c.createLinearGradient(gx, 0, gx + sz, 0);
377
+ gh.addColorStop(0, "rgba(255,255,255,1)");
378
+ gh.addColorStop(1, `hsl(${hue},100%,50%)`);
379
+ c.fillStyle = gh;
380
+ c.fillRect(gx, gy, sz, sz);
381
+ const gv = c.createLinearGradient(0, gy, 0, gy + sz);
382
+ gv.addColorStop(0, "rgba(0,0,0,0)");
383
+ gv.addColorStop(1, "rgba(0,0,0,1)");
384
+ c.fillStyle = gv;
385
+ c.fillRect(gx, gy, sz, sz);
386
+ const hRad = (hue - 90) * Math.PI / 180;
387
+ const hR = 94;
388
+ const hmx = CX + Math.cos(hRad) * hR, hmy = CX + Math.sin(hRad) * hR;
389
+ c.beginPath();
390
+ c.arc(hmx, hmy, 8, 0, Math.PI * 2);
391
+ c.fillStyle = "rgba(0,0,0,0.25)";
392
+ c.fill();
393
+ c.beginPath();
394
+ c.arc(hmx, hmy, 6.5, 0, Math.PI * 2);
395
+ c.strokeStyle = "#fff";
396
+ c.lineWidth = 2;
397
+ c.stroke();
398
+ const smx = gx + sat * sz, smy = gy + (1 - lit) * sz;
399
+ c.beginPath();
400
+ c.arc(smx, smy, 7, 0, Math.PI * 2);
401
+ c.fillStyle = "rgba(0,0,0,0.25)";
402
+ c.fill();
403
+ c.beginPath();
404
+ c.arc(smx, smy, 5.5, 0, Math.PI * 2);
405
+ c.strokeStyle = "#fff";
406
+ c.lineWidth = 2;
407
+ c.stroke();
408
+ c.beginPath();
409
+ c.arc(smx, smy, 3.5, 0, Math.PI * 2);
410
+ c.strokeStyle = "#000";
411
+ c.lineWidth = 1;
412
+ c.stroke();
413
+ }
414
+ function hitTest(x, y) {
415
+ if (Math.abs(x - CX) <= SQ_HALF && Math.abs(y - CX) <= SQ_HALF) return "square";
416
+ const dx = x - CX, dy = y - CX;
417
+ const dist = Math.sqrt(dx * dx + dy * dy);
418
+ if (dist >= 78 && dist <= 110) return "ring";
419
+ return null;
420
+ }
421
+ function pickHue(x, y) {
422
+ let angle = Math.atan2(y - CX, x - CX) * 180 / Math.PI + 90;
423
+ if (angle < 0) angle += 360;
424
+ return angle;
425
+ }
426
+ function pickSL(x, y) {
427
+ const gx = CX - SQ_HALF, gy = CX - SQ_HALF, sz = SQ_HALF * 2;
428
+ return [Math.max(0, Math.min(1, (x - gx) / sz)), Math.max(.02, Math.min(.98, 1 - (y - gy) / sz))];
429
+ }
430
+ function ColorWheel({ hue, sat, lit, onChange }) {
431
+ const cvsRef = (0, react.useRef)(null);
432
+ const [col, setCol] = (0, react.useState)({
433
+ hue,
434
+ sat,
435
+ lit
436
+ });
437
+ const colRef = (0, react.useRef)(col);
438
+ colRef.current = col;
439
+ (0, react.useEffect)(() => {
440
+ setCol((c) => c.hue === hue && c.sat === sat && c.lit === lit ? c : {
441
+ hue,
442
+ sat,
443
+ lit
444
+ });
445
+ }, [
446
+ hue,
447
+ sat,
448
+ lit
449
+ ]);
450
+ (0, react.useEffect)(() => {
451
+ if (cvsRef.current) drawWheel(cvsRef.current, col.hue, col.sat, col.lit);
452
+ }, [col]);
453
+ const apply = (0, react.useCallback)((nh, ns, nl) => {
454
+ setCol({
455
+ hue: nh,
456
+ sat: ns,
457
+ lit: nl
458
+ });
459
+ onChange(nh, ns, nl);
460
+ }, [onChange]);
461
+ const onDown = (0, react.useCallback)((e) => {
462
+ const r = cvsRef.current.getBoundingClientRect();
463
+ const x = e.clientX - r.left, y = e.clientY - r.top;
464
+ const region = hitTest(x, y);
465
+ if (!region) return;
466
+ if (region === "ring") apply(pickHue(x, y), colRef.current.sat, colRef.current.lit);
467
+ else {
468
+ const [s, l] = pickSL(x, y);
469
+ apply(colRef.current.hue, s, l);
470
+ }
471
+ const onMove = (ev) => {
472
+ const rr = cvsRef.current.getBoundingClientRect();
473
+ const mx = ev.clientX - rr.left, my = ev.clientY - rr.top;
474
+ if (region === "ring") {
475
+ const d = Math.sqrt((mx - CX) ** 2 + (my - CX) ** 2);
476
+ if (d >= 72 && d <= 116) apply(pickHue(mx, my), colRef.current.sat, colRef.current.lit);
477
+ } else {
478
+ const [s, l] = pickSL(mx, my);
479
+ apply(colRef.current.hue, s, l);
480
+ }
481
+ };
482
+ const onUp = () => {
483
+ document.removeEventListener("mousemove", onMove);
484
+ document.removeEventListener("mouseup", onUp);
485
+ };
486
+ document.addEventListener("mousemove", onMove);
487
+ document.addEventListener("mouseup", onUp);
488
+ }, [apply]);
489
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("canvas", {
490
+ ref: cvsRef,
491
+ width: WHEEL_SIZE,
492
+ height: WHEEL_SIZE,
493
+ style: ST.wheelCanvas,
494
+ onMouseDown: onDown
495
+ });
496
+ }
497
+ function compress(img, side, q) {
498
+ const s = Math.min(1, side / Math.max(img.width, img.height));
499
+ const c = document.createElement("canvas");
500
+ c.width = Math.max(1, Math.round(img.width * s));
501
+ c.height = Math.max(1, Math.round(img.height * s));
502
+ c.getContext("2d").drawImage(img, 0, 0, c.width, c.height);
503
+ return c.toDataURL("image/jpeg", q);
504
+ }
505
+ function readImg(file, cb) {
506
+ const r = new FileReader();
507
+ r.onerror = () => cb(null);
508
+ r.onload = () => {
509
+ const img = new Image();
510
+ img.onerror = () => cb(null);
511
+ img.onload = () => {
512
+ try {
513
+ let u = compress(img, 1600, .75);
514
+ if (u.length > 2e6) u = compress(img, 1e3, .6);
515
+ if (u.length > 2e6) u = compress(img, 800, .5);
516
+ cb(u);
517
+ } catch {
518
+ cb(null);
519
+ }
520
+ };
521
+ img.src = r.result;
522
+ };
523
+ r.readAsDataURL(file);
524
+ }
525
+ const ST = {
526
+ root: {
527
+ display: "flex",
528
+ flexDirection: "column",
529
+ gap: "20px",
530
+ padding: "4px 0",
531
+ maxWidth: "640px"
532
+ },
533
+ h2: {
534
+ color: "var(--dsw-alias-label-primary)",
535
+ fontSize: "18px",
536
+ fontWeight: 600,
537
+ margin: 0
538
+ },
539
+ sub: {
540
+ color: "var(--dsw-alias-label-tertiary)",
541
+ fontSize: "13px"
542
+ },
543
+ hr: {
544
+ height: "1px",
545
+ background: "var(--dsw-alias-border-l2)",
546
+ border: "none",
547
+ margin: "4px 0"
548
+ },
549
+ label: {
550
+ color: "var(--dsw-alias-label-primary)",
551
+ fontSize: "16px",
552
+ fontWeight: 600,
553
+ marginBottom: "8px"
554
+ },
555
+ hint: {
556
+ color: "var(--dsw-alias-label-tertiary)",
557
+ fontSize: "12px",
558
+ lineHeight: "18px",
559
+ marginTop: "4px"
560
+ },
561
+ colorHint: {
562
+ color: "var(--dsw-alias-label-tertiary)",
563
+ fontSize: "12px",
564
+ lineHeight: "18px",
565
+ marginTop: "7px",
566
+ textAlign: "center"
567
+ },
568
+ wheelCanvas: {
569
+ cursor: "crosshair",
570
+ borderRadius: "50%"
571
+ },
572
+ center: {
573
+ display: "flex",
574
+ justifyContent: "center"
575
+ },
576
+ btnGroup: {
577
+ display: "flex",
578
+ justifyContent: "center",
579
+ marginTop: "10px"
580
+ },
581
+ row: {
582
+ display: "flex",
583
+ alignItems: "center",
584
+ gap: "10px",
585
+ flexWrap: "wrap"
586
+ },
587
+ sliderBlock: {
588
+ display: "flex",
589
+ flexDirection: "column",
590
+ gap: "4px"
591
+ },
592
+ sliderRow: {
593
+ display: "flex",
594
+ alignItems: "center",
595
+ gap: "10px"
596
+ },
597
+ sliderLabel: {
598
+ color: "var(--dsw-alias-label-secondary)",
599
+ fontSize: "13px"
600
+ },
601
+ smallHint: {
602
+ color: "var(--dsw-alias-label-tertiary)",
603
+ fontSize: "11px",
604
+ lineHeight: "16px"
605
+ },
606
+ slider: {
607
+ flex: 1,
608
+ accentColor: "var(--dsw-alias-brand-primary)",
609
+ minWidth: "160px"
610
+ },
611
+ sliderVal: {
612
+ color: "var(--dsw-alias-label-secondary)",
613
+ fontSize: "12px",
614
+ whiteSpace: "nowrap",
615
+ width: "44px",
616
+ textAlign: "right"
617
+ },
618
+ btn: {
619
+ height: "32px",
620
+ padding: "0 14px",
621
+ borderRadius: "8px",
622
+ border: "1px solid var(--dsw-alias-border-l2)",
623
+ background: "var(--dsw-alias-button-elevated-fill)",
624
+ color: "var(--dsw-alias-label-primary)",
625
+ cursor: "pointer",
626
+ fontSize: "13px",
627
+ font: "inherit",
628
+ boxSizing: "border-box"
629
+ },
630
+ btnDanger: { color: "var(--dsw-alias-state-error-primary)" },
631
+ btnPrimary: {
632
+ background: "var(--dsw-alias-brand-primary)",
633
+ color: "var(--dsw-alias-brand-text)",
634
+ border: "none"
635
+ },
636
+ preview: {
637
+ width: "368px",
638
+ height: "225px",
639
+ objectFit: "cover",
640
+ borderRadius: "6px",
641
+ border: "1px solid var(--dsw-alias-border-l2)",
642
+ cursor: "pointer"
643
+ },
644
+ sliders: {
645
+ display: "flex",
646
+ flexDirection: "column",
647
+ gap: "12px",
648
+ marginTop: "8px"
649
+ },
650
+ overlay: {
651
+ position: "fixed",
652
+ inset: 0,
653
+ zIndex: 9999,
654
+ background: "rgba(0,0,0,0.7)",
655
+ display: "flex",
656
+ flexDirection: "column",
657
+ alignItems: "center",
658
+ justifyContent: "center",
659
+ gap: "12px"
660
+ },
661
+ modalTitle: {
662
+ color: "#fff",
663
+ fontSize: "16px",
664
+ fontWeight: 500
665
+ },
666
+ modalHint: {
667
+ color: "rgba(255,255,255,0.6)",
668
+ fontSize: "12px"
669
+ },
670
+ previewRect: {
671
+ position: "relative",
672
+ overflow: "hidden",
673
+ border: "2px solid rgba(255,255,255,0.3)",
674
+ borderRadius: "8px",
675
+ background: "#000",
676
+ cursor: "grab"
677
+ },
678
+ previewImg: {
679
+ position: "absolute",
680
+ transformOrigin: "0 0",
681
+ pointerEvents: "none"
682
+ },
683
+ modalBtns: {
684
+ display: "flex",
685
+ gap: "10px"
686
+ }
687
+ };
688
+ function BgEditor({ url, t, onClose, onCommit }) {
689
+ const pw = Math.min(window.innerWidth * .75, 860);
690
+ const ph = Math.round(pw * window.innerHeight / window.innerWidth);
691
+ const saved = rBgState();
692
+ const [zoom, setZoom] = (0, react.useState)(saved.iw > 0 ? saved.zoom : 1);
693
+ const [pos, setPos] = (0, react.useState)(saved.iw > 0 ? {
694
+ x: saved.x * pw,
695
+ y: saved.y * ph
696
+ } : {
697
+ x: 0,
698
+ y: 0
699
+ });
700
+ const [imgSize, setImgSize] = (0, react.useState)({
701
+ w: 0,
702
+ h: 0
703
+ });
704
+ const containerRef = (0, react.useRef)(null);
705
+ const imgRef = (0, react.useRef)(null);
706
+ const dragRef = (0, react.useRef)({
707
+ active: false,
708
+ sx: 0,
709
+ sy: 0,
710
+ spx: 0,
711
+ spy: 0
712
+ });
713
+ (0, react.useEffect)(() => {
714
+ const img = new Image();
715
+ img.onload = () => {
716
+ const scale = Math.min(pw / img.width, ph / img.height);
717
+ const w = img.width * scale, h = img.height * scale;
718
+ setImgSize({
719
+ w,
720
+ h
721
+ });
722
+ const s = rBgState();
723
+ if (s.iw > 0 && s.iw === img.width && s.ih === img.height) {
724
+ setZoom(s.zoom);
725
+ setPos({
726
+ x: s.x * pw - w * s.zoom / 2,
727
+ y: s.y * ph - h * s.zoom / 2
728
+ });
729
+ } else {
730
+ setZoom(1);
731
+ setPos({
732
+ x: (pw - w) / 2,
733
+ y: (ph - h) / 2
734
+ });
735
+ }
736
+ };
737
+ img.src = url;
738
+ }, [
739
+ url,
740
+ pw,
741
+ ph
742
+ ]);
743
+ const onDown = (0, react.useCallback)((e) => {
744
+ e.preventDefault();
745
+ dragRef.current = {
746
+ active: true,
747
+ sx: e.clientX,
748
+ sy: e.clientY,
749
+ spx: pos.x,
750
+ spy: pos.y
751
+ };
752
+ const onMove = (ev) => {
753
+ if (!dragRef.current.active) return;
754
+ setPos({
755
+ x: dragRef.current.spx + ev.clientX - dragRef.current.sx,
756
+ y: dragRef.current.spy + ev.clientY - dragRef.current.sy
757
+ });
758
+ };
759
+ const onUp = () => {
760
+ dragRef.current.active = false;
761
+ document.removeEventListener("mousemove", onMove);
762
+ document.removeEventListener("mouseup", onUp);
763
+ };
764
+ document.addEventListener("mousemove", onMove);
765
+ document.addEventListener("mouseup", onUp);
766
+ }, [pos]);
767
+ const onWheelCb = (0, react.useCallback)((e) => {
768
+ e.preventDefault();
769
+ const rect = containerRef.current.getBoundingClientRect();
770
+ const mx = e.clientX - rect.left, my = e.clientY - rect.top;
771
+ const factor = e.deltaY > 0 ? .92 : 1.08;
772
+ const nz = Math.max(.1, Math.min(10, zoom * factor));
773
+ const nx = mx - (mx - pos.x) * (nz / zoom);
774
+ const ny = my - (my - pos.y) * (nz / zoom);
775
+ setZoom(nz);
776
+ setPos({
777
+ x: nx,
778
+ y: ny
779
+ });
780
+ }, [zoom, pos]);
781
+ (0, react.useEffect)(() => {
782
+ const el = containerRef.current;
783
+ if (!el) return;
784
+ el.addEventListener("wheel", onWheelCb, { passive: false });
785
+ return () => el.removeEventListener("wheel", onWheelCb);
786
+ }, [onWheelCb]);
787
+ const resetView = (0, react.useCallback)(() => {
788
+ if (imgSize.w === 0) return;
789
+ setZoom(1);
790
+ setPos({
791
+ x: (pw - imgSize.w) / 2,
792
+ y: (ph - imgSize.h) / 2
793
+ });
794
+ }, [
795
+ pw,
796
+ ph,
797
+ imgSize
798
+ ]);
799
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
800
+ style: ST.overlay,
801
+ onClick: (e) => {
802
+ if (e.target === e.currentTarget) onClose();
803
+ },
804
+ children: [
805
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
806
+ style: ST.modalTitle,
807
+ children: t("editorTitle")
808
+ }),
809
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
810
+ ref: containerRef,
811
+ style: {
812
+ ...ST.previewRect,
813
+ width: pw,
814
+ height: ph
815
+ },
816
+ onMouseDown: onDown,
817
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
818
+ ref: imgRef,
819
+ src: url,
820
+ alt: "",
821
+ draggable: false,
822
+ style: {
823
+ ...ST.previewImg,
824
+ width: imgSize.w,
825
+ height: imgSize.h,
826
+ transform: `translate(${pos.x}px,${pos.y}px) scale(${zoom})`
827
+ }
828
+ })
829
+ }),
830
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
831
+ style: ST.modalHint,
832
+ children: t("editorHint")
833
+ }),
834
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
835
+ style: ST.modalBtns,
836
+ children: [
837
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
838
+ style: ST.btn,
839
+ onClick: resetView,
840
+ children: t("editorReset")
841
+ }),
842
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
843
+ style: ST.btn,
844
+ onClick: onClose,
845
+ children: t("editorCancel")
846
+ }),
847
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
848
+ style: {
849
+ ...ST.btn,
850
+ ...ST.btnPrimary
851
+ },
852
+ onClick: () => onCommit(zoom, (pos.x + imgSize.w * zoom / 2) / pw, (pos.y + imgSize.h * zoom / 2) / ph, imgRef.current?.naturalWidth ?? 0, imgRef.current?.naturalHeight ?? 0),
853
+ children: t("editorCommit")
854
+ })
855
+ ]
856
+ })
857
+ ]
858
+ });
859
+ }
860
+ function LiveSlider({ min, max, step, def, fmt, onInput, onChange }) {
861
+ const valRef = (0, react.useRef)(null);
862
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
863
+ style: ST.sliderRow,
864
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
865
+ type: "range",
866
+ min,
867
+ max,
868
+ step,
869
+ defaultValue: def,
870
+ style: ST.slider,
871
+ onInput: (e) => {
872
+ const v = Number(e.target.value);
873
+ onInput(v);
874
+ if (valRef.current) valRef.current.textContent = fmt(v);
875
+ },
876
+ onChange: (e) => onChange(Number(e.target.value))
877
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
878
+ ref: valRef,
879
+ style: ST.sliderVal,
880
+ children: fmt(def)
881
+ })]
882
+ });
883
+ }
884
+ function ThemeSection(props) {
885
+ const { t, hue, sat, lit, setColor, url, setWp, setOp, setWop, setBl, setSop, useStore } = props;
886
+ const storeUrl = useStore((s) => s.url);
887
+ const fileRef = (0, react.useRef)(null);
888
+ const [editorOpen, setEditorOpen] = (0, react.useState)(false);
889
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
890
+ style: ST.root,
891
+ children: [
892
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
893
+ style: ST.h2,
894
+ children: t("nav")
895
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
896
+ style: ST.sub,
897
+ children: t("subtitle")
898
+ })] }),
899
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [
900
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
901
+ style: ST.label,
902
+ children: t("colorTitle")
903
+ }),
904
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
905
+ style: ST.center,
906
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ColorWheel, {
907
+ hue,
908
+ sat,
909
+ lit,
910
+ onChange: setColor
911
+ })
912
+ }),
913
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
914
+ style: ST.colorHint,
915
+ children: t("colorHint")
916
+ })
917
+ ] }),
918
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("hr", { style: ST.hr }),
919
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
920
+ style: ST.label,
921
+ children: t("uiTitle")
922
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
923
+ style: ST.sliders,
924
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
925
+ style: ST.sliderBlock,
926
+ children: [
927
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
928
+ style: ST.sliderLabel,
929
+ children: t("uiOpacity")
930
+ }),
931
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
932
+ style: ST.smallHint,
933
+ children: t("uiOpacityHint")
934
+ }),
935
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LiveSlider, {
936
+ min: 0,
937
+ max: 100,
938
+ step: 1,
939
+ def: Math.round(rOp() * 100),
940
+ fmt: (v) => `${v}%`,
941
+ onInput: (v) => {
942
+ const op = v / 100;
943
+ cfg.opacity = op;
944
+ applyCustomTokens(op);
945
+ saveConfig();
946
+ },
947
+ onChange: (v) => setOp(v / 100)
948
+ })
949
+ ]
950
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
951
+ style: ST.sliderBlock,
952
+ children: [
953
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
954
+ style: ST.sliderLabel,
955
+ children: t("uiSop")
956
+ }),
957
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
958
+ style: ST.smallHint,
959
+ children: t("uiSopHint")
960
+ }),
961
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LiveSlider, {
962
+ min: 0,
963
+ max: 100,
964
+ step: 1,
965
+ def: Math.round(rSop() * 100),
966
+ fmt: (v) => `${v}%`,
967
+ onInput: (v) => {
968
+ const op = v / 100;
969
+ cfg.settingsOpacity = op;
970
+ applySettingsOverrides(op);
971
+ saveConfig();
972
+ },
973
+ onChange: (v) => setSop(v / 100)
974
+ })
975
+ ]
976
+ })]
977
+ })] }),
978
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("hr", { style: ST.hr }),
979
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [
980
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
981
+ style: ST.label,
982
+ children: t("bgTitle")
983
+ }),
984
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
985
+ style: ST.center,
986
+ children: storeUrl ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
987
+ src: storeUrl,
988
+ alt: "",
989
+ style: ST.preview,
990
+ onClick: () => setEditorOpen(true)
991
+ }) : null
992
+ }),
993
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
994
+ style: ST.btnGroup,
995
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
996
+ style: ST.row,
997
+ children: [
998
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
999
+ type: "button",
1000
+ style: ST.btn,
1001
+ onClick: () => fileRef.current?.click(),
1002
+ children: t("bgChoose")
1003
+ }),
1004
+ storeUrl ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1005
+ type: "button",
1006
+ style: ST.btn,
1007
+ onClick: () => setEditorOpen(true),
1008
+ children: t("bgEdit")
1009
+ }) : null,
1010
+ storeUrl ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1011
+ type: "button",
1012
+ style: {
1013
+ ...ST.btn,
1014
+ ...ST.btnDanger
1015
+ },
1016
+ onClick: () => setWp(null),
1017
+ children: t("bgRemove")
1018
+ }) : null,
1019
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1020
+ ref: fileRef,
1021
+ type: "file",
1022
+ accept: "image/*",
1023
+ style: { display: "none" },
1024
+ onChange: (e) => {
1025
+ const f = e.target.files?.[0];
1026
+ if (!f) return;
1027
+ readImg(f, (d) => {
1028
+ if (d) setWp(d);
1029
+ e.target.value = "";
1030
+ });
1031
+ }
1032
+ })
1033
+ ]
1034
+ })
1035
+ }),
1036
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1037
+ style: ST.sliders,
1038
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1039
+ style: ST.sliderBlock,
1040
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1041
+ style: ST.sliderLabel,
1042
+ children: t("wpOpacity")
1043
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LiveSlider, {
1044
+ min: 0,
1045
+ max: 100,
1046
+ step: 1,
1047
+ def: Math.round(rWop() * 100),
1048
+ fmt: (v) => `${v}%`,
1049
+ onInput: (v) => {
1050
+ const op = v / 100;
1051
+ cfg.wallpaperOpacity = op;
1052
+ if (wpEl) wpEl.style.opacity = String(op);
1053
+ saveConfig();
1054
+ },
1055
+ onChange: (v) => setWop(v / 100)
1056
+ })]
1057
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1058
+ style: ST.sliderBlock,
1059
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1060
+ style: ST.sliderLabel,
1061
+ children: t("bgBlur")
1062
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LiveSlider, {
1063
+ min: 0,
1064
+ max: 60,
1065
+ step: 1,
1066
+ def: rBl(),
1067
+ fmt: (v) => `${v}px`,
1068
+ onInput: (v) => {
1069
+ cfg.blur = v;
1070
+ if (wpEl) wpEl.style.filter = v > 0 ? `blur(${v}px)` : "none";
1071
+ saveConfig();
1072
+ },
1073
+ onChange: (v) => setBl(v)
1074
+ })]
1075
+ })]
1076
+ }),
1077
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1078
+ style: ST.hint,
1079
+ children: t("bgHint")
1080
+ })
1081
+ ] }),
1082
+ editorOpen && storeUrl ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BgEditor, {
1083
+ url: storeUrl,
1084
+ t,
1085
+ onClose: () => setEditorOpen(false),
1086
+ onCommit: (z, x, y, iw, ih) => {
1087
+ cfg.bgState = {
1088
+ zoom: z,
1089
+ x,
1090
+ y,
1091
+ iw,
1092
+ ih
1093
+ };
1094
+ applyWp(ctxRef);
1095
+ saveConfig();
1096
+ setEditorOpen(false);
1097
+ }
1098
+ }) : null
1099
+ ]
1100
+ });
1101
+ }
1102
+ function apply(ctx) {
1103
+ ctxRef = ctx;
1104
+ rpcCallFn = (endpoint, payload) => ctx.connection.rpc.call(RPC_CHANNEL, endpoint, payload).then((res) => res);
1105
+ const [initH, initS, initL] = rColor();
1106
+ let customDispose = null;
1107
+ const registerCustom = (h, s, l) => {
1108
+ customDispose?.();
1109
+ try {
1110
+ const { colorScheme, tokens } = genTokens(h, s, l);
1111
+ customDispose = ctx.theme.register({
1112
+ id: CUSTOM_ID,
1113
+ colorScheme,
1114
+ tokens
1115
+ });
1116
+ } catch {
1117
+ customDispose = null;
1118
+ }
1119
+ ctx.theme.setTheme(CUSTOM_ID);
1120
+ };
1121
+ if (rHasColor()) registerCustom(initH, initS, initL);
1122
+ ctx.effect(() => () => {
1123
+ customDispose?.();
1124
+ }, "dsh-any-background: skin dispose");
1125
+ let styleEl;
1126
+ if (typeof document !== "undefined") {
1127
+ styleEl = document.createElement("style");
1128
+ styleEl.dataset.plugin = "dsh-any-background";
1129
+ styleEl.textContent = `body[data-ds-dark-theme="${CUSTOM_ID}"]::before{content:'';position:fixed;inset:0;z-index:-1;pointer-events:none;background:radial-gradient(ellipse 80% 60% at 50% 0%,rgba(255,255,255,0.03) 0%,transparent 60%)}${SETTINGS_STYLE_RULE}`;
1130
+ document.head.appendChild(styleEl);
1131
+ }
1132
+ ctx.effect(() => () => {
1133
+ styleEl?.parentNode?.removeChild(styleEl);
1134
+ }, "dsh-any-background: gradient");
1135
+ let rev = 0;
1136
+ const store = (0, _deepseek_ai_dsh_client_runtime_client.defineStore)({
1137
+ init: () => ({
1138
+ url: null,
1139
+ rev: -1
1140
+ }),
1141
+ actions: { syncBg: (d, url, r) => {
1142
+ if (r <= d.rev) return;
1143
+ d.url = url;
1144
+ d.rev = r;
1145
+ } }
1146
+ });
1147
+ let bound;
1148
+ const syncBg = () => {
1149
+ rev++;
1150
+ bound?.syncBg(rWp(), rev);
1151
+ };
1152
+ applyWp(ctx);
1153
+ syncBg();
1154
+ loadPersisted().then(() => {
1155
+ applyWp(ctx);
1156
+ syncBg();
1157
+ });
1158
+ ctx.effect(() => () => {
1159
+ teardownWp();
1160
+ }, "dsh-any-background: wp cleanup");
1161
+ ctx.effect(() => ctx.on("theme/change", () => {
1162
+ if (rHasColor()) {
1163
+ const snapshot = ctx.theme.getTheme();
1164
+ if (snapshot.preference !== CUSTOM_ID && snapshot.themes.some((t) => t.id === CUSTOM_ID)) ctx.theme.setTheme(CUSTOM_ID);
1165
+ }
1166
+ applyWp(ctx);
1167
+ }), "dsh-any-background: theme change");
1168
+ let frame = 0;
1169
+ const applySoon = () => {
1170
+ if (frame !== 0) return;
1171
+ frame = requestAnimationFrame(() => {
1172
+ frame = 0;
1173
+ applyWp(ctx);
1174
+ });
1175
+ };
1176
+ const sentinel = document.createElement("div");
1177
+ sentinel.style.cssText = "position:fixed;inset:0;pointer-events:none;visibility:hidden";
1178
+ document.body.append(sentinel);
1179
+ const viewportObserver = new ResizeObserver(applySoon);
1180
+ viewportObserver.observe(sentinel);
1181
+ const dprQuery = window.matchMedia(`(resolution: ${window.devicePixelRatio}dppx)`);
1182
+ dprQuery.addEventListener("change", applySoon);
1183
+ ctx.effect(() => () => {
1184
+ viewportObserver.disconnect();
1185
+ dprQuery.removeEventListener("change", applySoon);
1186
+ sentinel.remove();
1187
+ }, "dsh-any-background: viewport watch");
1188
+ ctx.effect(() => ctx.locale.register(NS, {
1189
+ zh,
1190
+ en
1191
+ }), "dsh-any-background: i18n");
1192
+ const sectionInject = (actions) => {
1193
+ bound = actions;
1194
+ syncBg();
1195
+ const [wh, ws, wl] = rColor();
1196
+ const [dh, ds, dv] = hslToHsv(wh, ws, wl);
1197
+ return {
1198
+ t: ctx.locale.bind(NS),
1199
+ hue: dh,
1200
+ sat: ds,
1201
+ lit: dv,
1202
+ setColor: (nh, ns, nl) => {
1203
+ const [sh, ss, sl] = hsvToHsl(nh, ns, nl);
1204
+ cfg.color = [
1205
+ sh,
1206
+ ss,
1207
+ sl
1208
+ ];
1209
+ registerCustom(sh, ss, sl);
1210
+ applyWp(ctx);
1211
+ saveConfig();
1212
+ },
1213
+ setWp: (u) => {
1214
+ wpUrl = u;
1215
+ cfg.bgState = { ...DEFAULT_CONFIG.bgState };
1216
+ applyWp(ctx);
1217
+ syncBg();
1218
+ rpcCall("setWallpaper", { dataUrl: u });
1219
+ },
1220
+ setOp: (v) => {
1221
+ cfg.opacity = v;
1222
+ applyWp(ctx);
1223
+ syncBg();
1224
+ saveConfig();
1225
+ },
1226
+ setWop: (v) => {
1227
+ cfg.wallpaperOpacity = v;
1228
+ applyWp(ctx);
1229
+ syncBg();
1230
+ saveConfig();
1231
+ },
1232
+ setBl: (v) => {
1233
+ cfg.blur = v;
1234
+ applyWp(ctx);
1235
+ syncBg();
1236
+ saveConfig();
1237
+ },
1238
+ setSop: (v) => {
1239
+ cfg.settingsOpacity = v;
1240
+ applySettingsOverrides(v);
1241
+ saveConfig();
1242
+ }
1243
+ };
1244
+ };
1245
+ ctx.slots.inject("settings.section", () => ctx.slots.register({
1246
+ name: "settings.section",
1247
+ id: "dsh-any-background",
1248
+ order: 35,
1249
+ label: () => ctx.locale.bind(NS)("nav"),
1250
+ locale: NS,
1251
+ store,
1252
+ inject: sectionInject
1253
+ }, ThemeSection));
1254
+ const restoreSaved = () => {
1255
+ if (rHasColor()) {
1256
+ const [h, s, l] = rColor();
1257
+ registerCustom(h, s, l);
1258
+ }
1259
+ applyWp(ctx);
1260
+ };
1261
+ const restoreTimers = [300, 1500].map((delay) => window.setTimeout(restoreSaved, delay));
1262
+ ctx.effect(() => () => {
1263
+ restoreTimers.forEach((id) => window.clearTimeout(id));
1264
+ }, "dsh-any-background: boot restore");
1265
+ const watchdogId = window.setInterval(() => {
1266
+ if (!rHasColor()) return;
1267
+ const snapshot = ctx.theme.getTheme();
1268
+ let changed = false;
1269
+ if (!snapshot.themes.some((t) => t.id === CUSTOM_ID)) {
1270
+ const [h, s, l] = rColor();
1271
+ registerCustom(h, s, l);
1272
+ changed = true;
1273
+ } else if (snapshot.preference !== CUSTOM_ID) {
1274
+ ctx.theme.setTheme(CUSTOM_ID);
1275
+ changed = true;
1276
+ }
1277
+ if (changed) applyWp(ctx);
1278
+ }, 1e3);
1279
+ ctx.effect(() => () => {
1280
+ window.clearInterval(watchdogId);
1281
+ }, "dsh-any-background: theme watchdog");
1282
+ }
1283
+ //#endregion
1284
+ exports.apply = apply;
1285
+ exports.inject = inject;
1286
+ exports.name = name;
1287
+ return module.exports;
1288
+ }
1289
+ });
1290
+
1248
1291
  //# sourceMappingURL=client.js.map