dsh-any-background 0.2.4 → 0.2.6
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/README.md +33 -12
- package/README.zh.md +33 -12
- package/lib/client.js +2167 -305
- package/lib/client.js.map +1 -1
- package/lib/index.js +658 -25
- package/package.json +133 -121
package/lib/client.js
CHANGED
|
@@ -29,7 +29,7 @@ window.__ModuleLoader__.load({
|
|
|
29
29
|
descColor: "拖动色轮或输入精确数值,整个界面的配色将实时跟随变化",
|
|
30
30
|
descInterface: "为主界面的各个区域单独调节透明度与模糊,营造空间层次感",
|
|
31
31
|
descBackground: "上传一张图片或一段视频作为壁纸并自由摆放,或让算法为你实时生成动态背景",
|
|
32
|
-
descProfile: "
|
|
32
|
+
descProfile: "一键套用外观预设,保存多套配置随时切换,设置昼夜自动换装,或导出/导入完整主题",
|
|
33
33
|
colorTitle: "主题色",
|
|
34
34
|
colorHint: "在色轮外圈选择色相,在内部方形中调整饱和度和明度;双击滑块可恢复默认值",
|
|
35
35
|
swatchTitle: "灵感色板",
|
|
@@ -48,7 +48,7 @@ window.__ModuleLoader__.load({
|
|
|
48
48
|
bgChoose: "选择图片或视频",
|
|
49
49
|
bgRemove: "移除背景",
|
|
50
50
|
bgFromUrl: "从网址",
|
|
51
|
-
bgUrlPlaceholder: "
|
|
51
|
+
bgUrlPlaceholder: "粘贴图片或视频网址 https://…",
|
|
52
52
|
bgUrlApply: "应用",
|
|
53
53
|
bgUrlCancel: "取消",
|
|
54
54
|
bgUrlApplying: "加载中…",
|
|
@@ -121,7 +121,65 @@ window.__ModuleLoader__.load({
|
|
|
121
121
|
presetNoise: "流动噪声",
|
|
122
122
|
presetDots: "点阵",
|
|
123
123
|
presetWaves: "波浪",
|
|
124
|
-
presetPoly: "低多边形"
|
|
124
|
+
presetPoly: "低多边形",
|
|
125
|
+
presetStarfield: "星空",
|
|
126
|
+
presetRain: "雨滴",
|
|
127
|
+
presetContour: "等高线",
|
|
128
|
+
presetMeta: "流体光斑",
|
|
129
|
+
bgPause: "暂停",
|
|
130
|
+
bgResume: "播放",
|
|
131
|
+
bgUrlVideoFail: "获取视频失败,请确认链接可直接下载视频文件",
|
|
132
|
+
schemeTitle: "界面明暗",
|
|
133
|
+
schemeLight: "亮色",
|
|
134
|
+
schemeDark: "暗色",
|
|
135
|
+
schemeAuto: "自动",
|
|
136
|
+
schemeHint: "「自动」优先跟随背景画面的实际明暗决定界面明暗(无背景时依据主题色明度);强制亮色/暗色后整套配色按所选方向生成,未选主题色时使用中性灰配色",
|
|
137
|
+
presetGalleryTitle: "外观预设",
|
|
138
|
+
presetName_default: "默认",
|
|
139
|
+
presetDesc_default: "恢复插件的初始外观",
|
|
140
|
+
presetName_glass: "毛玻璃",
|
|
141
|
+
presetDesc_glass: "通透表面 + 磨砂玻璃层次",
|
|
142
|
+
presetName_minimal: "极简白",
|
|
143
|
+
presetDesc_minimal: "接近实色的表面,无模糊",
|
|
144
|
+
presetName_midnight: "暗夜紫",
|
|
145
|
+
presetDesc_midnight: "深色强调,重磨砂低透明度",
|
|
146
|
+
presetName_cyber: "赛博",
|
|
147
|
+
presetDesc_cyber: "艳丽洋红,界面近乎消失",
|
|
148
|
+
presetName_warm: "暖阳",
|
|
149
|
+
presetDesc_warm: "琥珀色调的明亮实色界面",
|
|
150
|
+
presetApplied: "预设已应用",
|
|
151
|
+
profilesTitle: "我的配置",
|
|
152
|
+
profileSave: "保存当前配置",
|
|
153
|
+
profileNamePlaceholder: "输入配置名称…",
|
|
154
|
+
profileNameRequired: "请先输入配置名称",
|
|
155
|
+
profileSaved: "配置已保存",
|
|
156
|
+
profileApplied: "配置已应用",
|
|
157
|
+
profileApply: "应用",
|
|
158
|
+
profileDelete: "删除配置",
|
|
159
|
+
profileDeleteConfirm: "确认删除",
|
|
160
|
+
profileNone: "不切换",
|
|
161
|
+
profilesEmpty: "还没有保存的配置。调整好外观后点击「保存当前配置」,即可随时一键切换。",
|
|
162
|
+
scheduleTitle: "昼夜自动切换",
|
|
163
|
+
scheduleHint: "按时间或系统深色模式,在两套已保存的配置之间自动切换(切换的是配色与表面参数,壁纸不变)",
|
|
164
|
+
scheduleMode: "切换依据",
|
|
165
|
+
scheduleModeTime: "固定时段",
|
|
166
|
+
scheduleModeSystem: "跟随系统",
|
|
167
|
+
scheduleTimes: "切换时间",
|
|
168
|
+
scheduleDayStart: "日间开始",
|
|
169
|
+
scheduleNightStart: "夜间开始",
|
|
170
|
+
scheduleProfiles: "对应配置",
|
|
171
|
+
scheduleDayProfile: "日间",
|
|
172
|
+
scheduleNightProfile: "夜间",
|
|
173
|
+
rotTitle: "壁纸轮换",
|
|
174
|
+
rotHint: "把多张图片加入轮换池,按所选频率自动更换壁纸;启用后立即生效",
|
|
175
|
+
rotAdd: "添加图片",
|
|
176
|
+
rotRemove: "移除",
|
|
177
|
+
rotShuffle: "随机",
|
|
178
|
+
rotOrder: "顺序",
|
|
179
|
+
rotReload: "每次刷新",
|
|
180
|
+
rotDaily: "每天",
|
|
181
|
+
rotWeekly: "每周",
|
|
182
|
+
rotNow: "立即切换"
|
|
125
183
|
};
|
|
126
184
|
const en = {
|
|
127
185
|
nav: "Theme",
|
|
@@ -134,7 +192,7 @@ window.__ModuleLoader__.load({
|
|
|
134
192
|
descColor: "Drag the wheel or type exact values — the whole UI follows live",
|
|
135
193
|
descInterface: "Tune opacity and blur per surface to build depth",
|
|
136
194
|
descBackground: "Upload an image or a video as wallpaper and place it freely, or let the algorithm paint a live background",
|
|
137
|
-
descProfile: "
|
|
195
|
+
descProfile: "Apply a one-click preset, save multiple named profiles, schedule a day/night switch, or export/import the full theme",
|
|
138
196
|
colorTitle: "Theme color",
|
|
139
197
|
colorHint: "Pick hue on the outer ring, adjust saturation & lightness in the square; double-click a slider to reset",
|
|
140
198
|
swatchTitle: "Quick swatches",
|
|
@@ -153,7 +211,7 @@ window.__ModuleLoader__.load({
|
|
|
153
211
|
bgChoose: "Choose image or video",
|
|
154
212
|
bgRemove: "Remove background",
|
|
155
213
|
bgFromUrl: "From URL",
|
|
156
|
-
bgUrlPlaceholder: "Paste an image URL https://…",
|
|
214
|
+
bgUrlPlaceholder: "Paste an image or video URL https://…",
|
|
157
215
|
bgUrlApply: "Apply",
|
|
158
216
|
bgUrlCancel: "Cancel",
|
|
159
217
|
bgUrlApplying: "Loading…",
|
|
@@ -226,7 +284,65 @@ window.__ModuleLoader__.load({
|
|
|
226
284
|
presetNoise: "Flowing noise",
|
|
227
285
|
presetDots: "Dots",
|
|
228
286
|
presetWaves: "Waves",
|
|
229
|
-
presetPoly: "Low poly"
|
|
287
|
+
presetPoly: "Low poly",
|
|
288
|
+
presetStarfield: "Starfield",
|
|
289
|
+
presetRain: "Rain",
|
|
290
|
+
presetContour: "Contours",
|
|
291
|
+
presetMeta: "Fluid orbs",
|
|
292
|
+
bgPause: "Pause",
|
|
293
|
+
bgResume: "Play",
|
|
294
|
+
bgUrlVideoFail: "Could not fetch the video — make sure the link downloads a video file directly",
|
|
295
|
+
schemeTitle: "Interface scheme",
|
|
296
|
+
schemeLight: "Light",
|
|
297
|
+
schemeDark: "Dark",
|
|
298
|
+
schemeAuto: "Auto",
|
|
299
|
+
schemeHint: "\"Auto\" follows the background brightness first (accent lightness as fallback only); forcing light/dark regenerates the palette in that direction - with no accent picked, a neutral gray palette is used",
|
|
300
|
+
presetGalleryTitle: "Appearance presets",
|
|
301
|
+
presetName_default: "Default",
|
|
302
|
+
presetDesc_default: "Restore the plugin's original look",
|
|
303
|
+
presetName_glass: "Frosted glass",
|
|
304
|
+
presetDesc_glass: "Translucent surfaces + frosted depth",
|
|
305
|
+
presetName_minimal: "Minimal",
|
|
306
|
+
presetDesc_minimal: "Near-solid surfaces, no frost",
|
|
307
|
+
presetName_midnight: "Midnight",
|
|
308
|
+
presetDesc_midnight: "Deep violet accent, heavy frost",
|
|
309
|
+
presetName_cyber: "Cyber",
|
|
310
|
+
presetDesc_cyber: "Vivid magenta, vanishing surfaces",
|
|
311
|
+
presetName_warm: "Warm daylight",
|
|
312
|
+
presetDesc_warm: "Amber accent on solid light surfaces",
|
|
313
|
+
presetApplied: "Preset applied",
|
|
314
|
+
profilesTitle: "Saved profiles",
|
|
315
|
+
profileSave: "Save current",
|
|
316
|
+
profileNamePlaceholder: "Profile name…",
|
|
317
|
+
profileNameRequired: "Enter a name first",
|
|
318
|
+
profileSaved: "Profile saved",
|
|
319
|
+
profileApplied: "Profile applied",
|
|
320
|
+
profileApply: "Apply",
|
|
321
|
+
profileDelete: "Delete profile",
|
|
322
|
+
profileDeleteConfirm: "Sure?",
|
|
323
|
+
profileNone: "None",
|
|
324
|
+
profilesEmpty: "No saved profiles yet. Tune the look, then hit \"Save current\" to switch back anytime.",
|
|
325
|
+
scheduleTitle: "Day/night auto switch",
|
|
326
|
+
scheduleHint: "Automatically swap between two saved profiles by clock time or the OS dark mode (colors and surfaces switch; the wallpaper stays)",
|
|
327
|
+
scheduleMode: "Trigger",
|
|
328
|
+
scheduleModeTime: "Fixed times",
|
|
329
|
+
scheduleModeSystem: "Follow system",
|
|
330
|
+
scheduleTimes: "Switch times",
|
|
331
|
+
scheduleDayStart: "Day starts",
|
|
332
|
+
scheduleNightStart: "Night starts",
|
|
333
|
+
scheduleProfiles: "Profiles",
|
|
334
|
+
scheduleDayProfile: "Day",
|
|
335
|
+
scheduleNightProfile: "Night",
|
|
336
|
+
rotTitle: "Wallpaper rotation",
|
|
337
|
+
rotHint: "Add images to a rotation pool and let the wallpaper change on your cadence; takes effect immediately when enabled",
|
|
338
|
+
rotAdd: "Add images",
|
|
339
|
+
rotRemove: "Remove",
|
|
340
|
+
rotShuffle: "Shuffle",
|
|
341
|
+
rotOrder: "In order",
|
|
342
|
+
rotReload: "Every refresh",
|
|
343
|
+
rotDaily: "Daily",
|
|
344
|
+
rotWeekly: "Weekly",
|
|
345
|
+
rotNow: "Switch now"
|
|
230
346
|
};
|
|
231
347
|
//#endregion
|
|
232
348
|
//#region src/client/state.ts
|
|
@@ -270,7 +386,26 @@ window.__ModuleLoader__.load({
|
|
|
270
386
|
generatedBg: null,
|
|
271
387
|
regenerateOnReload: false,
|
|
272
388
|
chatTextOpacity: 0,
|
|
273
|
-
trajectoryOpacity: 1
|
|
389
|
+
trajectoryOpacity: 1,
|
|
390
|
+
profiles: [],
|
|
391
|
+
rotation: {
|
|
392
|
+
enabled: false,
|
|
393
|
+
mode: "shuffle",
|
|
394
|
+
interval: "daily",
|
|
395
|
+
current: 0,
|
|
396
|
+
items: [],
|
|
397
|
+
lastRotate: null
|
|
398
|
+
},
|
|
399
|
+
schedule: {
|
|
400
|
+
enabled: false,
|
|
401
|
+
mode: "time",
|
|
402
|
+
dayProfile: null,
|
|
403
|
+
nightProfile: null,
|
|
404
|
+
dayStart: "07:00",
|
|
405
|
+
nightStart: "19:00"
|
|
406
|
+
},
|
|
407
|
+
schemeOverride: "auto",
|
|
408
|
+
activeProfile: null
|
|
274
409
|
};
|
|
275
410
|
const clamp01 = (n, def) => typeof n === "number" ? Math.min(1, Math.max(0, n)) : def;
|
|
276
411
|
let cfg = {
|
|
@@ -287,12 +422,19 @@ window.__ModuleLoader__.load({
|
|
|
287
422
|
let wpVideoSnapshot = null;
|
|
288
423
|
/** Local blob URL backing an in-session video; revoked when replaced or cleared. */
|
|
289
424
|
let wpVideoObjectUrl = null;
|
|
290
|
-
function setWpImageUrl(url) {
|
|
291
|
-
wpImageUrl = url;
|
|
292
|
-
}
|
|
293
425
|
function setWpUrl(url) {
|
|
294
426
|
wpUrl = url;
|
|
295
427
|
}
|
|
428
|
+
let imageRev = 0;
|
|
429
|
+
function bumpImageRev(url) {
|
|
430
|
+
if (url === null) return url;
|
|
431
|
+
if (!/^https?:\/\//i.test(url) && !url.startsWith("/")) return url;
|
|
432
|
+
imageRev++;
|
|
433
|
+
return `${url}${url.includes("?") ? "&" : "?"}r=${imageRev}`;
|
|
434
|
+
}
|
|
435
|
+
function setWpImageUrl(url) {
|
|
436
|
+
wpImageUrl = bumpImageRev(url);
|
|
437
|
+
}
|
|
296
438
|
let videoRev = 0;
|
|
297
439
|
function setWpVideoUrl(url, mime) {
|
|
298
440
|
if (url === null) {
|
|
@@ -403,8 +545,144 @@ window.__ModuleLoader__.load({
|
|
|
403
545
|
function rVideoBgState() {
|
|
404
546
|
return cfg.videoBgState;
|
|
405
547
|
}
|
|
548
|
+
function rProfiles() {
|
|
549
|
+
return Array.isArray(cfg.profiles) ? cfg.profiles : [];
|
|
550
|
+
}
|
|
551
|
+
function rRotation() {
|
|
552
|
+
const r = cfg.rotation;
|
|
553
|
+
return r && typeof r === "object" ? {
|
|
554
|
+
...DEFAULT_CONFIG.rotation,
|
|
555
|
+
...r,
|
|
556
|
+
items: Array.isArray(r.items) ? r.items : []
|
|
557
|
+
} : {
|
|
558
|
+
...DEFAULT_CONFIG.rotation,
|
|
559
|
+
items: []
|
|
560
|
+
};
|
|
561
|
+
}
|
|
562
|
+
function rSchedule() {
|
|
563
|
+
return cfg.schedule && typeof cfg.schedule === "object" ? {
|
|
564
|
+
...DEFAULT_CONFIG.schedule,
|
|
565
|
+
...cfg.schedule
|
|
566
|
+
} : { ...DEFAULT_CONFIG.schedule };
|
|
567
|
+
}
|
|
568
|
+
function rSchemeOverride() {
|
|
569
|
+
return cfg.schemeOverride === "light" || cfg.schemeOverride === "dark" ? cfg.schemeOverride : "auto";
|
|
570
|
+
}
|
|
571
|
+
/** Effective interface scheme (drives data-ds-dark-theme / color-scheme): a
|
|
572
|
+
* forced override wins; in auto a picked color decides through its palette
|
|
573
|
+
* direction (keeps native controls aligned with the drawn surfaces), and
|
|
574
|
+
* without a pick the background's brightness verdict does. Stay light as the
|
|
575
|
+
* last fallback. */
|
|
576
|
+
function rScheme() {
|
|
577
|
+
const o = rSchemeOverride();
|
|
578
|
+
if (o !== "auto") return o;
|
|
579
|
+
if (rHasColor()) return rColorScheme();
|
|
580
|
+
return rBgDark() ? "dark" : "light";
|
|
581
|
+
}
|
|
582
|
+
/** Direction the color palette itself is built in: a forced override wins; in
|
|
583
|
+
* auto the theme color's own lightness decides (0.2.4 behavior) so surfaces
|
|
584
|
+
* always contrast with the palette's label fonts — the wallpaper verdict must
|
|
585
|
+
* not drag a dark color's surfaces into a light build (and vice versa) or
|
|
586
|
+
* text and surfaces converge. */
|
|
587
|
+
function rColorScheme() {
|
|
588
|
+
const o = rSchemeOverride();
|
|
589
|
+
if (o !== "auto") return o;
|
|
590
|
+
return rHasColor() && rColor()[2] < .55 ? "dark" : "light";
|
|
591
|
+
}
|
|
592
|
+
/** Snapshot of the appearance fields a profile/preset restores. */
|
|
593
|
+
function currentAppearance() {
|
|
594
|
+
return {
|
|
595
|
+
color: cfg.color,
|
|
596
|
+
opacities: { ...rOps() },
|
|
597
|
+
blurs: { ...rBlurs() },
|
|
598
|
+
settingsOpacity: rSop(),
|
|
599
|
+
wallpaperOpacity: rWop(),
|
|
600
|
+
blur: rBl(),
|
|
601
|
+
chatTextOpacity: rChatTextOpacity(),
|
|
602
|
+
trajectoryOpacity: rTrajectoryOpacity()
|
|
603
|
+
};
|
|
604
|
+
}
|
|
605
|
+
/** Apply an appearance snapshot onto cfg (meta fields untouched). */
|
|
606
|
+
function applyAppearance(ap) {
|
|
607
|
+
cfg.color = Array.isArray(ap.color) && ap.color.length === 3 ? [...ap.color] : null;
|
|
608
|
+
cfg.opacities = {
|
|
609
|
+
...DEFAULT_CONFIG.opacities,
|
|
610
|
+
...ap.opacities ?? {}
|
|
611
|
+
};
|
|
612
|
+
cfg.blurs = {
|
|
613
|
+
...DEFAULT_CONFIG.blurs,
|
|
614
|
+
...ap.blurs ?? {}
|
|
615
|
+
};
|
|
616
|
+
cfg.settingsOpacity = clamp01(ap.settingsOpacity, DEFAULT_CONFIG.settingsOpacity);
|
|
617
|
+
cfg.wallpaperOpacity = clamp01(ap.wallpaperOpacity, DEFAULT_CONFIG.wallpaperOpacity);
|
|
618
|
+
cfg.blur = typeof ap.blur === "number" ? Math.min(60, Math.max(0, ap.blur)) : DEFAULT_CONFIG.blur;
|
|
619
|
+
cfg.chatTextOpacity = clamp01(ap.chatTextOpacity, DEFAULT_CONFIG.chatTextOpacity);
|
|
620
|
+
cfg.trajectoryOpacity = clamp01(ap.trajectoryOpacity, DEFAULT_CONFIG.trajectoryOpacity);
|
|
621
|
+
}
|
|
406
622
|
const num = (n, def) => typeof n === "number" ? n : def;
|
|
407
623
|
const cl = (n, lo, hi, def) => typeof n === "number" ? Math.min(hi, Math.max(lo, n)) : def;
|
|
624
|
+
const HHMM_RE = /^([01]\d|2[0-3]):[0-5]\d$/;
|
|
625
|
+
function adoptProfiles(raw) {
|
|
626
|
+
if (!Array.isArray(raw)) return [];
|
|
627
|
+
const out = [];
|
|
628
|
+
for (const item of raw.slice(0, 20)) {
|
|
629
|
+
const p = item ?? {};
|
|
630
|
+
if (typeof p.id !== "string" || p.id.length === 0 || p.id.length > 64) continue;
|
|
631
|
+
if (out.some((e) => e.id === p.id)) continue;
|
|
632
|
+
const ac = p.config ?? {};
|
|
633
|
+
out.push({
|
|
634
|
+
id: p.id,
|
|
635
|
+
name: typeof p.name === "string" && p.name.trim() ? p.name.slice(0, 60) : "Profile",
|
|
636
|
+
createdAt: typeof p.createdAt === "string" ? p.createdAt : "",
|
|
637
|
+
config: {
|
|
638
|
+
color: Array.isArray(ac.color) && ac.color.length === 3 ? [...ac.color] : null,
|
|
639
|
+
opacities: {
|
|
640
|
+
...DEFAULT_CONFIG.opacities,
|
|
641
|
+
...ac.opacities ?? {}
|
|
642
|
+
},
|
|
643
|
+
blurs: {
|
|
644
|
+
...DEFAULT_CONFIG.blurs,
|
|
645
|
+
...ac.blurs ?? {}
|
|
646
|
+
},
|
|
647
|
+
settingsOpacity: clamp01(ac.settingsOpacity, DEFAULT_CONFIG.settingsOpacity),
|
|
648
|
+
wallpaperOpacity: clamp01(ac.wallpaperOpacity, DEFAULT_CONFIG.wallpaperOpacity),
|
|
649
|
+
blur: num(ac.blur, DEFAULT_CONFIG.blur),
|
|
650
|
+
chatTextOpacity: clamp01(ac.chatTextOpacity, DEFAULT_CONFIG.chatTextOpacity),
|
|
651
|
+
trajectoryOpacity: clamp01(ac.trajectoryOpacity, DEFAULT_CONFIG.trajectoryOpacity)
|
|
652
|
+
}
|
|
653
|
+
});
|
|
654
|
+
}
|
|
655
|
+
return out;
|
|
656
|
+
}
|
|
657
|
+
function adoptRotation(raw) {
|
|
658
|
+
const r = raw ?? {};
|
|
659
|
+
const items = Array.isArray(r.items) ? r.items.filter((it) => {
|
|
660
|
+
const i = it ?? {};
|
|
661
|
+
return typeof i?.file === "string" && /^[\w-]+\.(jpg|jpeg|png|gif|webp)$/i.test(i.file);
|
|
662
|
+
}).slice(0, 30).map((it) => ({
|
|
663
|
+
file: it.file,
|
|
664
|
+
thumb: typeof it.thumb === "string" && it.thumb.startsWith("data:image/") && it.thumb.length <= 65536 ? it.thumb : ""
|
|
665
|
+
})) : [];
|
|
666
|
+
return {
|
|
667
|
+
enabled: r.enabled === true,
|
|
668
|
+
mode: r.mode === "order" ? "order" : "shuffle",
|
|
669
|
+
interval: r.interval === "reload" || r.interval === "weekly" ? r.interval : "daily",
|
|
670
|
+
current: num(r.current, 0),
|
|
671
|
+
items,
|
|
672
|
+
lastRotate: typeof r.lastRotate === "string" ? r.lastRotate : null
|
|
673
|
+
};
|
|
674
|
+
}
|
|
675
|
+
function adoptSchedule(raw) {
|
|
676
|
+
const r = raw ?? {};
|
|
677
|
+
return {
|
|
678
|
+
enabled: r.enabled === true,
|
|
679
|
+
mode: r.mode === "system" ? "system" : "time",
|
|
680
|
+
dayProfile: typeof r.dayProfile === "string" ? r.dayProfile : null,
|
|
681
|
+
nightProfile: typeof r.nightProfile === "string" ? r.nightProfile : null,
|
|
682
|
+
dayStart: typeof r.dayStart === "string" && HHMM_RE.test(r.dayStart) ? r.dayStart : DEFAULT_CONFIG.schedule.dayStart,
|
|
683
|
+
nightStart: typeof r.nightStart === "string" && HHMM_RE.test(r.nightStart) ? r.nightStart : DEFAULT_CONFIG.schedule.nightStart
|
|
684
|
+
};
|
|
685
|
+
}
|
|
408
686
|
function adoptBgState(s) {
|
|
409
687
|
return {
|
|
410
688
|
zoom: num(s.zoom, 1),
|
|
@@ -470,7 +748,12 @@ window.__ModuleLoader__.load({
|
|
|
470
748
|
generatedBg: generatedBg ? normalizeGeneratedBg(generatedBg) : null,
|
|
471
749
|
regenerateOnReload: typeof c.regenerateOnReload === "boolean" ? c.regenerateOnReload : DEFAULT_CONFIG.regenerateOnReload,
|
|
472
750
|
chatTextOpacity: clamp01(c.chatTextOpacity, DEFAULT_CONFIG.chatTextOpacity),
|
|
473
|
-
trajectoryOpacity: clamp01(c.trajectoryOpacity, DEFAULT_CONFIG.trajectoryOpacity)
|
|
751
|
+
trajectoryOpacity: clamp01(c.trajectoryOpacity, DEFAULT_CONFIG.trajectoryOpacity),
|
|
752
|
+
profiles: adoptProfiles(c.profiles),
|
|
753
|
+
rotation: adoptRotation(c.rotation),
|
|
754
|
+
schedule: adoptSchedule(c.schedule),
|
|
755
|
+
schemeOverride: c.schemeOverride === "light" || c.schemeOverride === "dark" ? c.schemeOverride : "auto",
|
|
756
|
+
activeProfile: typeof c.activeProfile === "string" ? c.activeProfile : null
|
|
474
757
|
};
|
|
475
758
|
}
|
|
476
759
|
function normalizeGeneratedBg(p) {
|
|
@@ -486,7 +769,8 @@ window.__ModuleLoader__.load({
|
|
|
486
769
|
preset: [
|
|
487
770
|
"aurora",
|
|
488
771
|
"nebula",
|
|
489
|
-
"noise"
|
|
772
|
+
"noise",
|
|
773
|
+
"starfield"
|
|
490
774
|
].includes(p.preset) ? p.preset : "aurora",
|
|
491
775
|
speed: cl(p.speed, 0, 2, .3),
|
|
492
776
|
scale: cl(p.scale, .3, 3, 1),
|
|
@@ -497,7 +781,10 @@ window.__ModuleLoader__.load({
|
|
|
497
781
|
preset: [
|
|
498
782
|
"dots",
|
|
499
783
|
"waves",
|
|
500
|
-
"poly"
|
|
784
|
+
"poly",
|
|
785
|
+
"rain",
|
|
786
|
+
"contour",
|
|
787
|
+
"meta"
|
|
501
788
|
].includes(p.preset) ? p.preset : "dots",
|
|
502
789
|
density: cl(p.density, 0, 1, .5),
|
|
503
790
|
scale: cl(p.scale, .3, 3, 1),
|
|
@@ -509,6 +796,10 @@ window.__ModuleLoader__.load({
|
|
|
509
796
|
const RPC_CHANNEL = "/dsh-any-background";
|
|
510
797
|
/** Same-origin serve URL of the persisted video (enough for <video src>/fetch). */
|
|
511
798
|
const VIDEO_SERVE_URL = "/dsh-any-background/video";
|
|
799
|
+
/** Same-origin serve URL of the persisted wallpaper (native <img> loading). */
|
|
800
|
+
const WALLPAPER_SERVE_URL = "/dsh-any-background/wallpaper";
|
|
801
|
+
/** Raw-bytes upload endpoint for the wallpaper slot (no base64 inflation). */
|
|
802
|
+
const WALLPAPER_UPLOAD_URL = "/dsh-any-background/wallpaper/upload";
|
|
512
803
|
/** HTTP route new videos are POSTed to as raw bytes (see uploadVideo). */
|
|
513
804
|
const VIDEO_UPLOAD_URL = "/dsh-any-background/video/upload";
|
|
514
805
|
const RPC_NS = "dshAnyBackground";
|
|
@@ -548,18 +839,23 @@ window.__ModuleLoader__.load({
|
|
|
548
839
|
function persistConfig() {
|
|
549
840
|
rpcCall("writeConfig", { config: cfg });
|
|
550
841
|
}
|
|
551
|
-
/** Load the persisted theme (config + wallpaper + video URL) from the node
|
|
842
|
+
/** Load the persisted theme (config + wallpaper URL + video URL) from the node
|
|
843
|
+
* half. Image and video both travel as serve URLs — never bytes — so this RPC
|
|
844
|
+
* stays tiny. Resolves true when the server advanced a due wallpaper rotation
|
|
845
|
+
* during the read — the restored wallpaper is then already the new pick. */
|
|
552
846
|
async function loadPersisted() {
|
|
553
847
|
const data = await rpcCall("read", {});
|
|
554
848
|
if (data && typeof data === "object") {
|
|
555
849
|
const d = data;
|
|
556
850
|
if (d.config) adoptConfig(d.config);
|
|
557
|
-
if (typeof d.
|
|
558
|
-
else if (d.
|
|
851
|
+
if (typeof d.wallpaperUrl === "string") setWpImageUrl(d.wallpaperUrl);
|
|
852
|
+
else if (d.wallpaperUrl === null) setWpImageUrl(null);
|
|
559
853
|
if (typeof d.videoUrl === "string") setWpVideoUrl(d.videoUrl, cfg.videoMime);
|
|
560
854
|
else if (d.videoUrl === null) setWpVideoUrl(null, null);
|
|
561
|
-
if (cfg.backgroundType === "image") setWpUrl(
|
|
855
|
+
if (cfg.backgroundType === "image") setWpUrl(rWpImage());
|
|
856
|
+
return d.rotated === true;
|
|
562
857
|
}
|
|
858
|
+
return false;
|
|
563
859
|
}
|
|
564
860
|
/** Persist a wallpaper (null removes it); one-shot, no debounce. */
|
|
565
861
|
function persistWallpaper(dataUrl) {
|
|
@@ -571,7 +867,7 @@ window.__ModuleLoader__.load({
|
|
|
571
867
|
return await rpcCall("setVideo", { dataUrl }) === true;
|
|
572
868
|
}
|
|
573
869
|
/** Download a wallpaper from a network URL and persist it into the local slot
|
|
574
|
-
* (the host replaces wallpaper.jpg). Returns the freshly stored
|
|
870
|
+
* (the host replaces wallpaper.jpg). Returns the freshly stored serve URL on
|
|
575
871
|
* success, or the host's failure message. */
|
|
576
872
|
async function setWallpaperFromUrl(url) {
|
|
577
873
|
if (!rpcCallFn) return {
|
|
@@ -598,7 +894,40 @@ window.__ModuleLoader__.load({
|
|
|
598
894
|
const v = res.value;
|
|
599
895
|
return v?.ok === true ? {
|
|
600
896
|
ok: true,
|
|
601
|
-
|
|
897
|
+
wallpaperUrl: v.wallpaperUrl ?? null
|
|
898
|
+
} : {
|
|
899
|
+
ok: false,
|
|
900
|
+
error: v?.error ?? "failed"
|
|
901
|
+
};
|
|
902
|
+
}
|
|
903
|
+
/** Download a background video from a network URL. The server streams it into
|
|
904
|
+
* the video slot and returns the resolved MIME for playback via the serve URL. */
|
|
905
|
+
async function setVideoFromUrl(url) {
|
|
906
|
+
if (!rpcCallFn) return {
|
|
907
|
+
ok: false,
|
|
908
|
+
error: "rpc not ready"
|
|
909
|
+
};
|
|
910
|
+
let res;
|
|
911
|
+
try {
|
|
912
|
+
res = await rpcCallFn(rpcEndpoint("setVideoUrl"), { url });
|
|
913
|
+
} catch (e) {
|
|
914
|
+
return {
|
|
915
|
+
ok: false,
|
|
916
|
+
error: e instanceof Error ? e.message : String(e)
|
|
917
|
+
};
|
|
918
|
+
}
|
|
919
|
+
if (!res) return {
|
|
920
|
+
ok: false,
|
|
921
|
+
error: "no response"
|
|
922
|
+
};
|
|
923
|
+
if (res.ok !== true) return {
|
|
924
|
+
ok: false,
|
|
925
|
+
error: res.error?.message ?? "request failed"
|
|
926
|
+
};
|
|
927
|
+
const v = res.value;
|
|
928
|
+
return v?.ok === true ? {
|
|
929
|
+
ok: true,
|
|
930
|
+
mime: v.mime ?? "video/mp4"
|
|
602
931
|
} : {
|
|
603
932
|
ok: false,
|
|
604
933
|
error: v?.error ?? "failed"
|
|
@@ -618,6 +947,175 @@ window.__ModuleLoader__.load({
|
|
|
618
947
|
return false;
|
|
619
948
|
}
|
|
620
949
|
}
|
|
950
|
+
/** Upload a wallpaper's raw bytes over HTTP — same streaming model as videos,
|
|
951
|
+
* original pixels preserved, zero base64 round-trips. */
|
|
952
|
+
async function uploadWallpaper(blob) {
|
|
953
|
+
try {
|
|
954
|
+
return (await fetch(WALLPAPER_UPLOAD_URL, {
|
|
955
|
+
method: "POST",
|
|
956
|
+
headers: { "Content-Type": blob.type || "image/jpeg" },
|
|
957
|
+
body: blob
|
|
958
|
+
})).ok;
|
|
959
|
+
} catch (e) {
|
|
960
|
+
console.warn("dsh-any-background: wallpaper upload failed", e);
|
|
961
|
+
return false;
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
/** Add an image (data URL + small thumbnail) to the server-side rotation pool. */
|
|
965
|
+
async function rotationAdd(dataUrl, thumb) {
|
|
966
|
+
if (!rpcCallFn) return {
|
|
967
|
+
ok: false,
|
|
968
|
+
error: "rpc not ready"
|
|
969
|
+
};
|
|
970
|
+
try {
|
|
971
|
+
const res = await rpcCallFn(rpcEndpoint("rotationAdd"), {
|
|
972
|
+
dataUrl,
|
|
973
|
+
thumb
|
|
974
|
+
});
|
|
975
|
+
if (res && res.ok === true) return res.value ?? {
|
|
976
|
+
ok: false,
|
|
977
|
+
error: "no value"
|
|
978
|
+
};
|
|
979
|
+
return {
|
|
980
|
+
ok: false,
|
|
981
|
+
error: res?.error?.message ?? "request failed"
|
|
982
|
+
};
|
|
983
|
+
} catch (e) {
|
|
984
|
+
return {
|
|
985
|
+
ok: false,
|
|
986
|
+
error: e instanceof Error ? e.message : String(e)
|
|
987
|
+
};
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
/** Remove a rotation item by index. Returns the updated items list. */
|
|
991
|
+
async function rotationRemove(index) {
|
|
992
|
+
if (!rpcCallFn) return {
|
|
993
|
+
ok: false,
|
|
994
|
+
error: "rpc not ready"
|
|
995
|
+
};
|
|
996
|
+
try {
|
|
997
|
+
const res = await rpcCallFn(rpcEndpoint("rotationRemove"), { index });
|
|
998
|
+
if (res && res.ok === true) return res.value ?? {
|
|
999
|
+
ok: false,
|
|
1000
|
+
error: "no value"
|
|
1001
|
+
};
|
|
1002
|
+
return {
|
|
1003
|
+
ok: false,
|
|
1004
|
+
error: res?.error?.message ?? "request failed"
|
|
1005
|
+
};
|
|
1006
|
+
} catch (e) {
|
|
1007
|
+
return {
|
|
1008
|
+
ok: false,
|
|
1009
|
+
error: e instanceof Error ? e.message : String(e)
|
|
1010
|
+
};
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
/** Activate a rotation item: the server copies its bytes into the wallpaper
|
|
1014
|
+
* slot and returns the serve URL for immediate display. */
|
|
1015
|
+
async function rotationActivate(index) {
|
|
1016
|
+
if (!rpcCallFn) return {
|
|
1017
|
+
ok: false,
|
|
1018
|
+
error: "rpc not ready"
|
|
1019
|
+
};
|
|
1020
|
+
try {
|
|
1021
|
+
const res = await rpcCallFn(rpcEndpoint("rotationSet"), { index });
|
|
1022
|
+
if (res && res.ok === true) return res.value ?? {
|
|
1023
|
+
ok: false,
|
|
1024
|
+
error: "no value"
|
|
1025
|
+
};
|
|
1026
|
+
return {
|
|
1027
|
+
ok: false,
|
|
1028
|
+
error: res?.error?.message ?? "request failed"
|
|
1029
|
+
};
|
|
1030
|
+
} catch (e) {
|
|
1031
|
+
return {
|
|
1032
|
+
ok: false,
|
|
1033
|
+
error: e instanceof Error ? e.message : String(e)
|
|
1034
|
+
};
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
//#endregion
|
|
1038
|
+
//#region src/client/utils/image.ts
|
|
1039
|
+
/**
|
|
1040
|
+
* Read a chosen image file as a data URL WITHOUT re-encoding: the original
|
|
1041
|
+
* pixels are kept as-is (no canvas downscale / JPEG re-compression), so the
|
|
1042
|
+
* wallpaper is stored and displayed at full fidelity. The tradeoff is a larger
|
|
1043
|
+
* payload over the RPC channel and on disk for big images.
|
|
1044
|
+
*/
|
|
1045
|
+
function readImg(file, cb) {
|
|
1046
|
+
const r = new FileReader();
|
|
1047
|
+
r.onerror = () => cb(null);
|
|
1048
|
+
r.onload = () => cb(r.result);
|
|
1049
|
+
r.readAsDataURL(file);
|
|
1050
|
+
}
|
|
1051
|
+
/** Promised readImg variant for async flows. */
|
|
1052
|
+
function readImgAsync(file) {
|
|
1053
|
+
return new Promise((resolve) => readImg(file, resolve));
|
|
1054
|
+
}
|
|
1055
|
+
/** Blob → data URL (used when a local asset must be embedded, e.g. theme export). */
|
|
1056
|
+
function blobToDataUrl(blob) {
|
|
1057
|
+
return new Promise((resolve, reject) => {
|
|
1058
|
+
const fr = new FileReader();
|
|
1059
|
+
fr.onload = () => resolve(fr.result);
|
|
1060
|
+
fr.onerror = () => reject(fr.error);
|
|
1061
|
+
fr.readAsDataURL(blob);
|
|
1062
|
+
});
|
|
1063
|
+
}
|
|
1064
|
+
const IMG_CACHE_MAX = 6;
|
|
1065
|
+
const imgCache = /* @__PURE__ */ new Map();
|
|
1066
|
+
/** Decode an image URL once and share the element across callers (LRU-capped). */
|
|
1067
|
+
function loadImage(url) {
|
|
1068
|
+
const hit = imgCache.get(url);
|
|
1069
|
+
if (hit) {
|
|
1070
|
+
imgCache.delete(url);
|
|
1071
|
+
imgCache.set(url, hit);
|
|
1072
|
+
return hit;
|
|
1073
|
+
}
|
|
1074
|
+
const p = new Promise((resolve) => {
|
|
1075
|
+
const img = new Image();
|
|
1076
|
+
img.onerror = () => resolve(null);
|
|
1077
|
+
img.onload = () => resolve(img);
|
|
1078
|
+
img.src = url;
|
|
1079
|
+
});
|
|
1080
|
+
imgCache.set(url, p);
|
|
1081
|
+
while (imgCache.size > IMG_CACHE_MAX) {
|
|
1082
|
+
const oldest = imgCache.keys().next().value;
|
|
1083
|
+
if (oldest !== void 0) imgCache.delete(oldest);
|
|
1084
|
+
}
|
|
1085
|
+
return p;
|
|
1086
|
+
}
|
|
1087
|
+
/** Build a small JPEG thumbnail (rotation-pool picker preview). Resolves null
|
|
1088
|
+
* when the image cannot be decoded. */
|
|
1089
|
+
function makeThumb(dataUrl, maxSide = 96) {
|
|
1090
|
+
return new Promise((resolve) => {
|
|
1091
|
+
const img = new Image();
|
|
1092
|
+
img.onerror = () => resolve(null);
|
|
1093
|
+
img.onload = () => {
|
|
1094
|
+
try {
|
|
1095
|
+
const iw = img.naturalWidth || img.width;
|
|
1096
|
+
const ih = img.naturalHeight || img.height;
|
|
1097
|
+
if (iw <= 0 || ih <= 0) {
|
|
1098
|
+
resolve(null);
|
|
1099
|
+
return;
|
|
1100
|
+
}
|
|
1101
|
+
const k = Math.min(1, maxSide / Math.max(iw, ih));
|
|
1102
|
+
const c = document.createElement("canvas");
|
|
1103
|
+
c.width = Math.max(1, Math.round(iw * k));
|
|
1104
|
+
c.height = Math.max(1, Math.round(ih * k));
|
|
1105
|
+
const g = c.getContext("2d");
|
|
1106
|
+
if (!g) {
|
|
1107
|
+
resolve(null);
|
|
1108
|
+
return;
|
|
1109
|
+
}
|
|
1110
|
+
g.drawImage(img, 0, 0, c.width, c.height);
|
|
1111
|
+
resolve(c.toDataURL("image/jpeg", .72));
|
|
1112
|
+
} catch {
|
|
1113
|
+
resolve(null);
|
|
1114
|
+
}
|
|
1115
|
+
};
|
|
1116
|
+
img.src = dataUrl;
|
|
1117
|
+
});
|
|
1118
|
+
}
|
|
621
1119
|
//#endregion
|
|
622
1120
|
//#region src/client/utils/color.ts
|
|
623
1121
|
function hsvToHsl(h, s, v) {
|
|
@@ -639,20 +1137,23 @@ window.__ModuleLoader__.load({
|
|
|
639
1137
|
let tokensCacheKey = "";
|
|
640
1138
|
let tokensCache = null;
|
|
641
1139
|
/**
|
|
642
|
-
* Memoized token generation: the same (hue, sat, lit) input always
|
|
643
|
-
* same token set, and applyWp / applyCustomTokens /
|
|
644
|
-
* this repeatedly (slider drags, viewport
|
|
645
|
-
* and skip the 30+ hsl() string builds
|
|
1140
|
+
* Memoized token generation: the same (hue, sat, lit, scheme) input always
|
|
1141
|
+
* yields the same token set, and applyWp / applyCustomTokens /
|
|
1142
|
+
* applySettingsOverrides call this repeatedly (slider drags, viewport
|
|
1143
|
+
* re-applies), so cache the last result and skip the 30+ hsl() string builds
|
|
1144
|
+
* when nothing changed. The optional scheme forces the palette direction
|
|
1145
|
+
* (light/dark) independently of the color's own lightness.
|
|
646
1146
|
*/
|
|
647
|
-
function genTokens(hue, sat, lit) {
|
|
648
|
-
const key = `${hue}|${sat}|${lit}`;
|
|
1147
|
+
function genTokens(hue, sat, lit, scheme) {
|
|
1148
|
+
const key = `${hue}|${sat}|${lit}|${scheme ?? "auto"}`;
|
|
649
1149
|
if (tokensCacheKey === key && tokensCache) return tokensCache;
|
|
650
1150
|
tokensCacheKey = key;
|
|
651
|
-
tokensCache = buildTokens(hue, sat, lit);
|
|
1151
|
+
tokensCache = buildTokens(hue, sat, lit, scheme);
|
|
652
1152
|
return tokensCache;
|
|
653
1153
|
}
|
|
654
|
-
function buildTokens(hue, sat, lit) {
|
|
655
|
-
const dark = lit < .55;
|
|
1154
|
+
function buildTokens(hue, sat, lit, scheme) {
|
|
1155
|
+
const dark = scheme !== void 0 ? scheme === "dark" : lit < .55;
|
|
1156
|
+
if (scheme !== void 0 && lit < .55 !== dark) lit = dark ? Math.min(.44, Math.max(.14, 1 - lit)) : Math.max(.6, Math.min(.88, 1 - lit));
|
|
656
1157
|
const h = (d) => ((hue + d) % 360 + 360) % 360;
|
|
657
1158
|
const s = (d) => Math.max(0, Math.min(1, sat + d));
|
|
658
1159
|
const l = (d) => Math.max(0, Math.min(1, lit + d));
|
|
@@ -822,124 +1323,119 @@ window.__ModuleLoader__.load({
|
|
|
822
1323
|
return rgbToHsl(b.r / b.count, b.g / b.count, b.b / b.count);
|
|
823
1324
|
}
|
|
824
1325
|
function extractWallpaperPalette(dataUrl, bgState) {
|
|
825
|
-
return
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
}
|
|
848
|
-
}
|
|
849
|
-
const c = document.createElement("canvas");
|
|
850
|
-
c.width = EXTRACT_SIDE;
|
|
851
|
-
c.height = EXTRACT_SIDE;
|
|
852
|
-
const g = c.getContext("2d", { willReadFrequently: true });
|
|
853
|
-
g.drawImage(img, sx, sy, sw, sh, 0, 0, EXTRACT_SIDE, EXTRACT_SIDE);
|
|
854
|
-
const px = g.getImageData(0, 0, EXTRACT_SIDE, EXTRACT_SIDE).data;
|
|
855
|
-
const counts = /* @__PURE__ */ new Uint32Array(4096);
|
|
856
|
-
const sums = /* @__PURE__ */ new Float64Array(12288);
|
|
857
|
-
let totalLum = 0;
|
|
858
|
-
let sampled = 0;
|
|
859
|
-
for (let i = 0; i < px.length; i += 4) {
|
|
860
|
-
const r = px[i], gg = px[i + 1], b = px[i + 2];
|
|
861
|
-
const max = Math.max(r, gg, b), min = Math.min(r, gg, b);
|
|
862
|
-
const v = max / 255;
|
|
863
|
-
const s = max === 0 ? 0 : (max - min) / max;
|
|
864
|
-
totalLum += v;
|
|
865
|
-
sampled++;
|
|
866
|
-
if (s < .08 || v < .12 || v > .97) continue;
|
|
867
|
-
const key = r >> 4 << 8 | gg >> 4 << 4 | b >> 4;
|
|
868
|
-
counts[key]++;
|
|
869
|
-
sums[key * 3] += r;
|
|
870
|
-
sums[key * 3 + 1] += gg;
|
|
871
|
-
sums[key * 3 + 2] += b;
|
|
872
|
-
}
|
|
873
|
-
const buckets = [];
|
|
874
|
-
for (let k = 0; k < 4096; k++) {
|
|
875
|
-
if (counts[k] === 0) continue;
|
|
876
|
-
buckets.push({
|
|
877
|
-
count: counts[k],
|
|
878
|
-
r: sums[k * 3],
|
|
879
|
-
g: sums[k * 3 + 1],
|
|
880
|
-
b: sums[k * 3 + 2],
|
|
881
|
-
s: (Math.max(sums[k * 3], sums[k * 3 + 1], sums[k * 3 + 2]) - Math.min(sums[k * 3], sums[k * 3 + 1], sums[k * 3 + 2])) / Math.max(sums[k * 3], sums[k * 3 + 1], sums[k * 3 + 2]) || 0
|
|
882
|
-
});
|
|
883
|
-
}
|
|
884
|
-
if (buckets.length === 0) {
|
|
885
|
-
resolve(null);
|
|
886
|
-
return;
|
|
1326
|
+
return loadImage(dataUrl).then((img) => {
|
|
1327
|
+
if (!img) return null;
|
|
1328
|
+
try {
|
|
1329
|
+
const iw = img.naturalWidth || img.width;
|
|
1330
|
+
const ih = img.naturalHeight || img.height;
|
|
1331
|
+
const bg = bgState;
|
|
1332
|
+
let sx = 0, sy = 0, sw = iw, sh = ih;
|
|
1333
|
+
if (bg.iw === iw && bg.ih === ih && bg.iw > 0) {
|
|
1334
|
+
const fit = Math.min(window.innerWidth / iw, window.innerHeight / ih);
|
|
1335
|
+
const w = iw * fit * bg.zoom;
|
|
1336
|
+
const h = ih * fit * bg.zoom;
|
|
1337
|
+
const px = bg.x * window.innerWidth - w / 2;
|
|
1338
|
+
const py = bg.y * window.innerHeight - h / 2;
|
|
1339
|
+
sx = Math.max(0, -px);
|
|
1340
|
+
sy = Math.max(0, -py);
|
|
1341
|
+
sw = Math.min(iw - sx, window.innerWidth - px - sx);
|
|
1342
|
+
sh = Math.min(ih - sy, window.innerHeight - py - sy);
|
|
1343
|
+
if (sw <= 0 || sh <= 0) {
|
|
1344
|
+
sx = 0;
|
|
1345
|
+
sy = 0;
|
|
1346
|
+
sw = iw;
|
|
1347
|
+
sh = ih;
|
|
887
1348
|
}
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
1349
|
+
}
|
|
1350
|
+
const c = document.createElement("canvas");
|
|
1351
|
+
c.width = EXTRACT_SIDE;
|
|
1352
|
+
c.height = EXTRACT_SIDE;
|
|
1353
|
+
const g = c.getContext("2d", { willReadFrequently: true });
|
|
1354
|
+
g.drawImage(img, sx, sy, sw, sh, 0, 0, EXTRACT_SIDE, EXTRACT_SIDE);
|
|
1355
|
+
const px = g.getImageData(0, 0, EXTRACT_SIDE, EXTRACT_SIDE).data;
|
|
1356
|
+
const counts = /* @__PURE__ */ new Uint32Array(4096);
|
|
1357
|
+
const sums = /* @__PURE__ */ new Float64Array(12288);
|
|
1358
|
+
let totalLum = 0;
|
|
1359
|
+
let sampled = 0;
|
|
1360
|
+
for (let i = 0; i < px.length; i += 4) {
|
|
1361
|
+
const r = px[i], gg = px[i + 1], b = px[i + 2];
|
|
1362
|
+
const max = Math.max(r, gg, b), min = Math.min(r, gg, b);
|
|
1363
|
+
const v = max / 255;
|
|
1364
|
+
const s = max === 0 ? 0 : (max - min) / max;
|
|
1365
|
+
totalLum += (.2126 * r + .7152 * gg + .0722 * b) / 255;
|
|
1366
|
+
sampled++;
|
|
1367
|
+
if (s < .08 || v < .12 || v > .97) continue;
|
|
1368
|
+
const key = r >> 4 << 8 | gg >> 4 << 4 | b >> 4;
|
|
1369
|
+
counts[key]++;
|
|
1370
|
+
sums[key * 3] += r;
|
|
1371
|
+
sums[key * 3 + 1] += gg;
|
|
1372
|
+
sums[key * 3 + 2] += b;
|
|
1373
|
+
}
|
|
1374
|
+
const buckets = [];
|
|
1375
|
+
for (let k = 0; k < 4096; k++) {
|
|
1376
|
+
if (counts[k] === 0) continue;
|
|
1377
|
+
buckets.push({
|
|
1378
|
+
count: counts[k],
|
|
1379
|
+
r: sums[k * 3],
|
|
1380
|
+
g: sums[k * 3 + 1],
|
|
1381
|
+
b: sums[k * 3 + 2],
|
|
1382
|
+
s: (Math.max(sums[k * 3], sums[k * 3 + 1], sums[k * 3 + 2]) - Math.min(sums[k * 3], sums[k * 3 + 1], sums[k * 3 + 2])) / Math.max(sums[k * 3], sums[k * 3 + 1], sums[k * 3 + 2]) || 0
|
|
1383
|
+
});
|
|
1384
|
+
}
|
|
1385
|
+
if (buckets.length === 0) return null;
|
|
1386
|
+
buckets.sort((a, b) => b.count * (.5 + b.s) - a.count * (.5 + a.s));
|
|
1387
|
+
const primary = bucketsToHsl(buckets[0]);
|
|
1388
|
+
const primaryHue = primary[0];
|
|
1389
|
+
let secondary = primary;
|
|
1390
|
+
for (const b of buckets.slice(1)) {
|
|
1391
|
+
const [h] = bucketsToHsl(b);
|
|
1392
|
+
if (Math.abs((h - primaryHue + 540) % 360 - 180) > 30) {
|
|
1393
|
+
secondary = bucketsToHsl(b);
|
|
1394
|
+
break;
|
|
898
1395
|
}
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
]
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
1396
|
+
}
|
|
1397
|
+
let tertiary = [
|
|
1398
|
+
(primaryHue + 180) % 360,
|
|
1399
|
+
Math.min(.7, primary[1]),
|
|
1400
|
+
Math.min(.7, primary[2])
|
|
1401
|
+
];
|
|
1402
|
+
for (const b of buckets.slice(1)) {
|
|
1403
|
+
const [h] = bucketsToHsl(b);
|
|
1404
|
+
const sep = Math.abs((h - primaryHue + 540) % 360 - 180);
|
|
1405
|
+
if (sep > 90 && sep < 150) {
|
|
1406
|
+
tertiary = bucketsToHsl(b);
|
|
1407
|
+
break;
|
|
911
1408
|
}
|
|
912
|
-
const surface = [
|
|
913
|
-
primaryHue,
|
|
914
|
-
Math.min(.06, primary[1] * .3),
|
|
915
|
-
primary[2]
|
|
916
|
-
];
|
|
917
|
-
const luminance = sampled > 0 ? totalLum / sampled : primary[2];
|
|
918
|
-
primary[2] = luminance < .5 ? Math.min(.44, Math.max(.2, primary[2])) : Math.max(.6, Math.min(.82, primary[2]));
|
|
919
|
-
resolve({
|
|
920
|
-
primary: [
|
|
921
|
-
primary[0],
|
|
922
|
-
Math.min(.9, Math.max(.15, primary[1])),
|
|
923
|
-
primary[2]
|
|
924
|
-
],
|
|
925
|
-
secondary: [
|
|
926
|
-
secondary[0],
|
|
927
|
-
Math.min(.85, Math.max(.2, secondary[1])),
|
|
928
|
-
Math.min(.75, Math.max(.35, secondary[2]))
|
|
929
|
-
],
|
|
930
|
-
tertiary: [
|
|
931
|
-
tertiary[0],
|
|
932
|
-
Math.min(.8, Math.max(.2, tertiary[1])),
|
|
933
|
-
Math.min(.7, Math.max(.35, tertiary[2]))
|
|
934
|
-
],
|
|
935
|
-
surface,
|
|
936
|
-
luminance
|
|
937
|
-
});
|
|
938
|
-
} catch {
|
|
939
|
-
resolve(null);
|
|
940
1409
|
}
|
|
941
|
-
|
|
942
|
-
|
|
1410
|
+
const surface = [
|
|
1411
|
+
primaryHue,
|
|
1412
|
+
Math.min(.06, primary[1] * .3),
|
|
1413
|
+
primary[2]
|
|
1414
|
+
];
|
|
1415
|
+
const luminance = sampled > 0 ? totalLum / sampled : primary[2];
|
|
1416
|
+
primary[2] = luminance < .5 ? Math.min(.44, Math.max(.2, primary[2])) : Math.max(.6, Math.min(.82, primary[2]));
|
|
1417
|
+
return {
|
|
1418
|
+
primary: [
|
|
1419
|
+
primary[0],
|
|
1420
|
+
Math.min(.9, Math.max(.15, primary[1])),
|
|
1421
|
+
primary[2]
|
|
1422
|
+
],
|
|
1423
|
+
secondary: [
|
|
1424
|
+
secondary[0],
|
|
1425
|
+
Math.min(.85, Math.max(.2, secondary[1])),
|
|
1426
|
+
Math.min(.75, Math.max(.35, secondary[2]))
|
|
1427
|
+
],
|
|
1428
|
+
tertiary: [
|
|
1429
|
+
tertiary[0],
|
|
1430
|
+
Math.min(.8, Math.max(.2, tertiary[1])),
|
|
1431
|
+
Math.min(.7, Math.max(.35, tertiary[2]))
|
|
1432
|
+
],
|
|
1433
|
+
surface,
|
|
1434
|
+
luminance
|
|
1435
|
+
};
|
|
1436
|
+
} catch {
|
|
1437
|
+
return null;
|
|
1438
|
+
}
|
|
943
1439
|
});
|
|
944
1440
|
}
|
|
945
1441
|
/** Backward-compatible single-color extraction: returns the primary HSL. */
|
|
@@ -952,26 +1448,22 @@ window.__ModuleLoader__.load({
|
|
|
952
1448
|
* reads dark (use white fonts), false when light (use black fonts), or null
|
|
953
1449
|
* when the frame cannot be decoded. */
|
|
954
1450
|
function analyzeFrameDark(dataUrl) {
|
|
955
|
-
return
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
resolve(null);
|
|
972
|
-
}
|
|
973
|
-
};
|
|
974
|
-
img.src = dataUrl;
|
|
1451
|
+
return loadImage(dataUrl).then((img) => {
|
|
1452
|
+
if (!img) return null;
|
|
1453
|
+
try {
|
|
1454
|
+
const c = document.createElement("canvas");
|
|
1455
|
+
c.width = ANALYZE_SIDE;
|
|
1456
|
+
c.height = ANALYZE_SIDE;
|
|
1457
|
+
const g = c.getContext("2d", { willReadFrequently: true });
|
|
1458
|
+
g.drawImage(img, 0, 0, ANALYZE_SIDE, ANALYZE_SIDE);
|
|
1459
|
+
const px = g.getImageData(0, 0, ANALYZE_SIDE, ANALYZE_SIDE).data;
|
|
1460
|
+
let lum = 0;
|
|
1461
|
+
const count = px.length / 4;
|
|
1462
|
+
for (let i = 0; i < px.length; i += 4) lum += .2126 * px[i] + .7152 * px[i + 1] + .0722 * px[i + 2];
|
|
1463
|
+
return lum / count / 255 < .5;
|
|
1464
|
+
} catch {
|
|
1465
|
+
return null;
|
|
1466
|
+
}
|
|
975
1467
|
});
|
|
976
1468
|
}
|
|
977
1469
|
/** HSL (h 0-360, s/l 0-1) → RGB (0-255 integers). */
|
|
@@ -1013,20 +1505,42 @@ window.__ModuleLoader__.load({
|
|
|
1013
1505
|
function newRaf(canvas, draw) {
|
|
1014
1506
|
canvas.dataset.dshAnyCanvas = "1";
|
|
1015
1507
|
let running = true;
|
|
1508
|
+
let paused = false;
|
|
1509
|
+
let rafId = 0;
|
|
1016
1510
|
let last = 0;
|
|
1017
1511
|
draw();
|
|
1512
|
+
if (typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches) return {
|
|
1513
|
+
stop: () => {
|
|
1514
|
+
running = false;
|
|
1515
|
+
},
|
|
1516
|
+
pause: () => void 0,
|
|
1517
|
+
resume: () => void 0
|
|
1518
|
+
};
|
|
1018
1519
|
const loop = (ts) => {
|
|
1019
|
-
if (!running) return;
|
|
1520
|
+
if (!running || paused) return;
|
|
1020
1521
|
if (ts - last >= FRAME_MS) {
|
|
1021
1522
|
last = ts;
|
|
1022
1523
|
draw();
|
|
1023
1524
|
}
|
|
1024
|
-
requestAnimationFrame(loop);
|
|
1525
|
+
rafId = requestAnimationFrame(loop);
|
|
1526
|
+
};
|
|
1527
|
+
rafId = requestAnimationFrame(loop);
|
|
1528
|
+
return {
|
|
1529
|
+
stop: () => {
|
|
1530
|
+
running = false;
|
|
1531
|
+
cancelAnimationFrame(rafId);
|
|
1532
|
+
},
|
|
1533
|
+
pause: () => {
|
|
1534
|
+
paused = true;
|
|
1535
|
+
cancelAnimationFrame(rafId);
|
|
1536
|
+
},
|
|
1537
|
+
resume: () => {
|
|
1538
|
+
if (!running || !paused) return;
|
|
1539
|
+
paused = false;
|
|
1540
|
+
last = 0;
|
|
1541
|
+
rafId = requestAnimationFrame(loop);
|
|
1542
|
+
}
|
|
1025
1543
|
};
|
|
1026
|
-
requestAnimationFrame(loop);
|
|
1027
|
-
return { stop: () => {
|
|
1028
|
-
running = false;
|
|
1029
|
-
} };
|
|
1030
1544
|
}
|
|
1031
1545
|
function createCanvas() {
|
|
1032
1546
|
const c = document.createElement("canvas");
|
|
@@ -1041,7 +1555,6 @@ window.__ModuleLoader__.load({
|
|
|
1041
1555
|
};
|
|
1042
1556
|
}
|
|
1043
1557
|
function fitLiveCanvas(c) {
|
|
1044
|
-
if (c.dataset.dshAnyStatic === "1") return;
|
|
1045
1558
|
const { w, h } = liveSize();
|
|
1046
1559
|
if (c.width !== w || c.height !== h) {
|
|
1047
1560
|
c.width = w;
|
|
@@ -1338,6 +1851,43 @@ window.__ModuleLoader__.load({
|
|
|
1338
1851
|
col = hueRotate(col, u_seed * 6.28318530718);
|
|
1339
1852
|
gl_FragColor = vec4(col, 1.0);
|
|
1340
1853
|
}
|
|
1854
|
+
`;
|
|
1855
|
+
if (preset === "starfield") return common + `
|
|
1856
|
+
float hash21(vec2 p) {
|
|
1857
|
+
p = fract(p * vec2(234.34, 435.345));
|
|
1858
|
+
p += dot(p, p + 34.23);
|
|
1859
|
+
return fract(p.x * p.y);
|
|
1860
|
+
}
|
|
1861
|
+
vec3 starLayer(vec2 uv, float t, float density) {
|
|
1862
|
+
vec3 col = vec3(0.0);
|
|
1863
|
+
vec2 grid = uv * density;
|
|
1864
|
+
vec2 cell = floor(grid);
|
|
1865
|
+
vec2 f = fract(grid) - 0.5;
|
|
1866
|
+
float h = hash21(cell);
|
|
1867
|
+
if (h > 0.8) {
|
|
1868
|
+
vec2 offs = (vec2(hash21(cell + 1.3), hash21(cell + 2.7)) - 0.5) * 0.7;
|
|
1869
|
+
float d = length(f - offs);
|
|
1870
|
+
float tw = 0.35 + 0.65 * sin(t * (1.0 + h * 2.5) + h * 40.0);
|
|
1871
|
+
col += vec3(0.85, 0.92, 1.0) * smoothstep(0.07, 0.0, d) * max(0.0, tw);
|
|
1872
|
+
}
|
|
1873
|
+
return col;
|
|
1874
|
+
}
|
|
1875
|
+
void main() {
|
|
1876
|
+
vec2 uv = gl_FragCoord.xy / u_resolution;
|
|
1877
|
+
vec2 p = (gl_FragCoord.xy - 0.5 * u_resolution) / u_resolution.y;
|
|
1878
|
+
float t = u_time * 0.5;
|
|
1879
|
+
vec3 col = mix(vec3(0.012, 0.014, 0.035), vec3(0.035, 0.035, 0.08), uv.y);
|
|
1880
|
+
// Faint drifting nebula veil behind the stars.
|
|
1881
|
+
float n = fbm(vec3(p * 2.2 * u_scale, t * 0.05));
|
|
1882
|
+
col += vec3(0.05, 0.045, 0.11) * smoothstep(0.05, 0.85, n);
|
|
1883
|
+
vec3 stars = vec3(0.0);
|
|
1884
|
+
stars += starLayer(p + vec2(t * 0.006, 0.0), t, 13.0 * u_scale);
|
|
1885
|
+
stars += starLayer(p * 1.8 + vec2(t * 0.013, 3.0), t * 1.25, 27.0 * u_scale);
|
|
1886
|
+
stars += starLayer(p * 3.1 + vec2(t * 0.021, 7.0), t * 0.85, 46.0 * u_scale);
|
|
1887
|
+
col += stars;
|
|
1888
|
+
col = hueRotate(col, u_seed * 6.28318530718);
|
|
1889
|
+
gl_FragColor = vec4(col, 1.0);
|
|
1890
|
+
}
|
|
1341
1891
|
`;
|
|
1342
1892
|
return common + `
|
|
1343
1893
|
void main() {
|
|
@@ -1361,44 +1911,52 @@ window.__ModuleLoader__.load({
|
|
|
1361
1911
|
function createPatternBg(params, canvas) {
|
|
1362
1912
|
if (params.preset === "waves") return createWaves(params, canvas);
|
|
1363
1913
|
if (params.preset === "poly") return createLowPoly(params, canvas);
|
|
1914
|
+
if (params.preset === "rain") return createRain(params, canvas);
|
|
1915
|
+
if (params.preset === "contour") return createContour(params, canvas);
|
|
1916
|
+
if (params.preset === "meta") return createMeta(params, canvas);
|
|
1364
1917
|
return createDots(params, canvas);
|
|
1365
1918
|
}
|
|
1366
|
-
function
|
|
1919
|
+
function createRain(params, canvas) {
|
|
1367
1920
|
const c = canvas ?? createCanvas();
|
|
1368
1921
|
fitLiveCanvas(c);
|
|
1369
1922
|
const g = c.getContext("2d", { alpha: false });
|
|
1370
1923
|
const rng = createRng(params.seed);
|
|
1371
1924
|
const dark = rng() < .5;
|
|
1372
|
-
const
|
|
1373
|
-
const
|
|
1374
|
-
const
|
|
1375
|
-
for (let
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
sat: Math.round(
|
|
1381
|
-
lit: dark ? Math.round(
|
|
1382
|
-
|
|
1383
|
-
|
|
1925
|
+
const hue = Math.round(rng() * 360);
|
|
1926
|
+
const count = Math.round(30 + params.density * 150);
|
|
1927
|
+
const drops = [];
|
|
1928
|
+
for (let i = 0; i < count; i++) drops.push({
|
|
1929
|
+
x: rng(),
|
|
1930
|
+
y: rng(),
|
|
1931
|
+
len: (.035 + rng() * .075) * (.7 + params.scale * .5),
|
|
1932
|
+
speed: (.25 + rng() * .55) * (.6 + params.scale * .5),
|
|
1933
|
+
sat: Math.round(30 + rng() * 40),
|
|
1934
|
+
lit: dark ? Math.round(55 + rng() * 25) : Math.round(40 + rng() * 25),
|
|
1935
|
+
alpha: .12 + rng() * .28,
|
|
1936
|
+
width: .8 + rng() * 1.4
|
|
1384
1937
|
});
|
|
1385
1938
|
let t = 0;
|
|
1386
1939
|
const draw = () => {
|
|
1387
1940
|
fitLiveCanvas(c);
|
|
1388
1941
|
const w = c.width, h = c.height;
|
|
1389
|
-
g.fillStyle = dark ? "#
|
|
1942
|
+
g.fillStyle = dark ? "#07080c" : "#f4f6f9";
|
|
1390
1943
|
g.fillRect(0, 0, w, h);
|
|
1391
|
-
for (const d of
|
|
1392
|
-
const
|
|
1393
|
-
const
|
|
1394
|
-
const
|
|
1395
|
-
const
|
|
1944
|
+
for (const d of drops) {
|
|
1945
|
+
const yy = ((d.y + t * d.speed) % 1 + 1) % 1;
|
|
1946
|
+
const x = d.x * w;
|
|
1947
|
+
const y0 = yy * h;
|
|
1948
|
+
const y1 = y0 - d.len * h;
|
|
1949
|
+
const grad = g.createLinearGradient(x, y0, x, y1);
|
|
1950
|
+
grad.addColorStop(0, `hsla(${hue},${d.sat}%,${d.lit}%,${d.alpha.toFixed(2)})`);
|
|
1951
|
+
grad.addColorStop(1, "hsla(0,0%,0%,0)");
|
|
1952
|
+
g.strokeStyle = grad;
|
|
1953
|
+
g.lineWidth = d.width;
|
|
1396
1954
|
g.beginPath();
|
|
1397
|
-
g.
|
|
1398
|
-
g.
|
|
1399
|
-
g.
|
|
1955
|
+
g.moveTo(x, y0);
|
|
1956
|
+
g.lineTo(x, y1);
|
|
1957
|
+
g.stroke();
|
|
1400
1958
|
}
|
|
1401
|
-
t += .
|
|
1959
|
+
t += .016;
|
|
1402
1960
|
};
|
|
1403
1961
|
return {
|
|
1404
1962
|
canvas: c,
|
|
@@ -1406,15 +1964,153 @@ window.__ModuleLoader__.load({
|
|
|
1406
1964
|
snapshot: () => c.toDataURL("image/jpeg", .94)
|
|
1407
1965
|
};
|
|
1408
1966
|
}
|
|
1409
|
-
function
|
|
1967
|
+
function createContour(params, canvas) {
|
|
1410
1968
|
const c = canvas ?? createCanvas();
|
|
1411
1969
|
fitLiveCanvas(c);
|
|
1412
1970
|
const g = c.getContext("2d", { alpha: false });
|
|
1413
|
-
const
|
|
1971
|
+
const noise = createNoise(params.seed);
|
|
1972
|
+
const rng = createRng(params.seed + 1013);
|
|
1414
1973
|
const dark = rng() < .5;
|
|
1415
1974
|
const hue = Math.round(rng() * 360);
|
|
1416
|
-
const layers = Math.round(
|
|
1417
|
-
const
|
|
1975
|
+
const layers = Math.round(16 + params.density * 46);
|
|
1976
|
+
const freq = .0016 / params.scale;
|
|
1977
|
+
const amp = .055 * params.scale;
|
|
1978
|
+
let t = 0;
|
|
1979
|
+
const draw = () => {
|
|
1980
|
+
fitLiveCanvas(c);
|
|
1981
|
+
const w = c.width, h = c.height;
|
|
1982
|
+
g.fillStyle = dark ? "#0a0b0e" : "#f6f7fa";
|
|
1983
|
+
g.fillRect(0, 0, w, h);
|
|
1984
|
+
const samples = Math.max(60, Math.floor(w / 8));
|
|
1985
|
+
for (let i = 0; i <= layers; i++) {
|
|
1986
|
+
const base = i / layers;
|
|
1987
|
+
const shift = t * .05;
|
|
1988
|
+
g.beginPath();
|
|
1989
|
+
for (let s = 0; s <= samples; s++) {
|
|
1990
|
+
const fx = s / samples;
|
|
1991
|
+
const y = (base + (noise(fx * 60 * freq * 400, i * .22 + shift) - .5) * amp) * h;
|
|
1992
|
+
if (s === 0) g.moveTo(0, y);
|
|
1993
|
+
else g.lineTo(fx * w, y);
|
|
1994
|
+
}
|
|
1995
|
+
const lit = dark ? 20 + i / layers * 22 : 62 - i / layers * 18;
|
|
1996
|
+
g.strokeStyle = `hsla(${(hue + i * 2) % 360},30%,${lit}%,${(.25 + .2 * (i / layers)).toFixed(2)})`;
|
|
1997
|
+
g.lineWidth = 1;
|
|
1998
|
+
g.stroke();
|
|
1999
|
+
}
|
|
2000
|
+
t += .032;
|
|
2001
|
+
};
|
|
2002
|
+
return {
|
|
2003
|
+
canvas: c,
|
|
2004
|
+
...newRaf(c, draw),
|
|
2005
|
+
snapshot: () => c.toDataURL("image/jpeg", .94)
|
|
2006
|
+
};
|
|
2007
|
+
}
|
|
2008
|
+
function createMeta(params, canvas) {
|
|
2009
|
+
const c = canvas ?? createCanvas();
|
|
2010
|
+
fitLiveCanvas(c);
|
|
2011
|
+
const g = c.getContext("2d", { alpha: false });
|
|
2012
|
+
const rng = createRng(params.seed);
|
|
2013
|
+
const dark = rng() < .5;
|
|
2014
|
+
const baseHue = Math.round(rng() * 360);
|
|
2015
|
+
const count = Math.round(5 + params.density * 11);
|
|
2016
|
+
const blobs = [];
|
|
2017
|
+
for (let i = 0; i < count; i++) blobs.push({
|
|
2018
|
+
x: .15 + rng() * .7,
|
|
2019
|
+
y: .15 + rng() * .7,
|
|
2020
|
+
r: (.09 + rng() * .14) * params.scale,
|
|
2021
|
+
ax: .08 + rng() * .22,
|
|
2022
|
+
ay: .08 + rng() * .22,
|
|
2023
|
+
speed: .15 + rng() * .3,
|
|
2024
|
+
phase: rng() * Math.PI * 2,
|
|
2025
|
+
hue: (baseHue + rng() * 70) % 360
|
|
2026
|
+
});
|
|
2027
|
+
let t = 0;
|
|
2028
|
+
const draw = () => {
|
|
2029
|
+
fitLiveCanvas(c);
|
|
2030
|
+
const w = c.width, h = c.height;
|
|
2031
|
+
g.fillStyle = dark ? "#08090d" : "#f5f6f9";
|
|
2032
|
+
g.fillRect(0, 0, w, h);
|
|
2033
|
+
g.globalCompositeOperation = dark ? "lighter" : "multiply";
|
|
2034
|
+
for (const b of blobs) {
|
|
2035
|
+
const cx = (b.x + Math.sin(t * b.speed + b.phase) * b.ax) * w;
|
|
2036
|
+
const cy = (b.y + Math.cos(t * b.speed * .83 + b.phase * 1.7) * b.ay) * h;
|
|
2037
|
+
const r = b.r * Math.min(w, h) * (.9 + .2 * Math.sin(t * b.speed + b.phase));
|
|
2038
|
+
const rad = g.createRadialGradient(cx, cy, 0, cx, cy, r);
|
|
2039
|
+
if (dark) {
|
|
2040
|
+
rad.addColorStop(0, `hsla(${b.hue},65%,58%,0.55)`);
|
|
2041
|
+
rad.addColorStop(.7, `hsla(${b.hue},60%,45%,0.18)`);
|
|
2042
|
+
rad.addColorStop(1, "hsla(0,0%,0%,0)");
|
|
2043
|
+
} else {
|
|
2044
|
+
rad.addColorStop(0, `hsla(${b.hue},55%,70%,0.5)`);
|
|
2045
|
+
rad.addColorStop(.7, `hsla(${b.hue},50%,80%,0.2)`);
|
|
2046
|
+
rad.addColorStop(1, "hsla(0,0%,100%,0)");
|
|
2047
|
+
}
|
|
2048
|
+
g.fillStyle = rad;
|
|
2049
|
+
g.beginPath();
|
|
2050
|
+
g.arc(cx, cy, r, 0, Math.PI * 2);
|
|
2051
|
+
g.fill();
|
|
2052
|
+
}
|
|
2053
|
+
g.globalCompositeOperation = "source-over";
|
|
2054
|
+
t += .03;
|
|
2055
|
+
};
|
|
2056
|
+
return {
|
|
2057
|
+
canvas: c,
|
|
2058
|
+
...newRaf(c, draw),
|
|
2059
|
+
snapshot: () => c.toDataURL("image/jpeg", .94)
|
|
2060
|
+
};
|
|
2061
|
+
}
|
|
2062
|
+
function createDots(params, canvas) {
|
|
2063
|
+
const c = canvas ?? createCanvas();
|
|
2064
|
+
fitLiveCanvas(c);
|
|
2065
|
+
const g = c.getContext("2d", { alpha: false });
|
|
2066
|
+
const rng = createRng(params.seed);
|
|
2067
|
+
const dark = rng() < .5;
|
|
2068
|
+
const baseHue = Math.round(rng() * 360);
|
|
2069
|
+
const spacing = Math.max(26, 150 * params.scale / (.25 + params.density));
|
|
2070
|
+
const dots = [];
|
|
2071
|
+
for (let y = 0; y < 8; y++) for (let x = 0; x < 8; x++) dots.push({
|
|
2072
|
+
cx: x / 7,
|
|
2073
|
+
cy: y / 7,
|
|
2074
|
+
r: spacing * .16 * params.scale * (.6 + rng() * .7),
|
|
2075
|
+
hue: (baseHue + rng() * 80) % 360,
|
|
2076
|
+
sat: Math.round(35 + rng() * 55),
|
|
2077
|
+
lit: dark ? Math.round(30 + rng() * 35) : Math.round(55 + rng() * 30),
|
|
2078
|
+
phase: rng() * Math.PI * 2,
|
|
2079
|
+
speed: .5 + rng() * 1.2
|
|
2080
|
+
});
|
|
2081
|
+
let t = 0;
|
|
2082
|
+
const draw = () => {
|
|
2083
|
+
fitLiveCanvas(c);
|
|
2084
|
+
const w = c.width, h = c.height;
|
|
2085
|
+
g.fillStyle = dark ? "#0a0b0d" : "#f6f7f9";
|
|
2086
|
+
g.fillRect(0, 0, w, h);
|
|
2087
|
+
for (const d of dots) {
|
|
2088
|
+
const pulse = .75 + .35 * Math.sin(t * d.speed + d.phase);
|
|
2089
|
+
const r = Math.max(1, d.r * pulse);
|
|
2090
|
+
const x = d.cx * w + d.r * .3 * Math.sin(t * d.speed * .5 + d.phase);
|
|
2091
|
+
const y = d.cy * h + d.r * .3 * Math.cos(t * d.speed * .7 + d.phase);
|
|
2092
|
+
g.beginPath();
|
|
2093
|
+
g.arc(x, y, r, 0, Math.PI * 2);
|
|
2094
|
+
g.fillStyle = `hsla(${d.hue},${d.sat}%,${d.lit}%,${(.18 + .25 * pulse).toFixed(2)})`;
|
|
2095
|
+
g.fill();
|
|
2096
|
+
}
|
|
2097
|
+
t += .032;
|
|
2098
|
+
};
|
|
2099
|
+
return {
|
|
2100
|
+
canvas: c,
|
|
2101
|
+
...newRaf(c, draw),
|
|
2102
|
+
snapshot: () => c.toDataURL("image/jpeg", .94)
|
|
2103
|
+
};
|
|
2104
|
+
}
|
|
2105
|
+
function createWaves(params, canvas) {
|
|
2106
|
+
const c = canvas ?? createCanvas();
|
|
2107
|
+
fitLiveCanvas(c);
|
|
2108
|
+
const g = c.getContext("2d", { alpha: false });
|
|
2109
|
+
const rng = createRng(params.seed);
|
|
2110
|
+
const dark = rng() < .5;
|
|
2111
|
+
const hue = Math.round(rng() * 360);
|
|
2112
|
+
const layers = Math.round(5 + params.density * 8);
|
|
2113
|
+
const waves = [];
|
|
1418
2114
|
for (let i = 0; i < layers; i++) waves.push({
|
|
1419
2115
|
yBase: .25 + i / layers * .55,
|
|
1420
2116
|
amp: (25 + rng() * 45) * params.scale,
|
|
@@ -1567,6 +2263,14 @@ window.__ModuleLoader__.load({
|
|
|
1567
2263
|
let wpController = null;
|
|
1568
2264
|
let snapshotListener = null;
|
|
1569
2265
|
let tokenStyleEl = null;
|
|
2266
|
+
/** Pause the live generated background's animation loop (session-only). */
|
|
2267
|
+
function pauseGeneratedBg() {
|
|
2268
|
+
wpController?.pause?.();
|
|
2269
|
+
}
|
|
2270
|
+
/** Resume the live generated background's animation loop. */
|
|
2271
|
+
function resumeGeneratedBg() {
|
|
2272
|
+
wpController?.resume?.();
|
|
2273
|
+
}
|
|
1570
2274
|
function clearDynamicBg() {
|
|
1571
2275
|
wpController?.stop();
|
|
1572
2276
|
wpController?.canvas.remove();
|
|
@@ -1589,10 +2293,20 @@ window.__ModuleLoader__.load({
|
|
|
1589
2293
|
for (const name of appliedTokenNames) document.body.style.removeProperty(name);
|
|
1590
2294
|
appliedTokenNames = [];
|
|
1591
2295
|
}
|
|
1592
|
-
/**
|
|
1593
|
-
*
|
|
1594
|
-
|
|
1595
|
-
|
|
2296
|
+
/** Drop every applied custom token and forget the token fingerprint, so a
|
|
2297
|
+
* later color-less profile (system theme) leaves no stale rule behind. */
|
|
2298
|
+
function clearThemeTokens() {
|
|
2299
|
+
clearCustomTokens();
|
|
2300
|
+
baseTokenKey = "";
|
|
2301
|
+
lastBgKey = "";
|
|
2302
|
+
document.body.removeAttribute("data-ds-dark-theme");
|
|
2303
|
+
document.body.style.removeProperty("color-scheme");
|
|
2304
|
+
}
|
|
2305
|
+
/** Label tokens flipped by the background brightness verdict. The faint tiers
|
|
2306
|
+
* (caption/dimmed) are deliberately NOT flipped: they back placeholder/hint
|
|
2307
|
+
* text, which must stay visibly weaker than real input even when the wallpaper
|
|
2308
|
+
* brightness flips the main label direction. Exported so the skin registration
|
|
2309
|
+
* (index) can flip fonts through the host theme service too. */
|
|
1596
2310
|
const LABEL_TOKENS = [
|
|
1597
2311
|
"--dsw-alias-label-primary",
|
|
1598
2312
|
"--dsw-alias-label-secondary",
|
|
@@ -1645,19 +2359,41 @@ window.__ModuleLoader__.load({
|
|
|
1645
2359
|
});
|
|
1646
2360
|
}
|
|
1647
2361
|
let lastBgKey = "";
|
|
2362
|
+
/** Palette source for the token rule: the picked color; a forced scheme
|
|
2363
|
+
* without a picked color builds a neutral near-gray palette in that
|
|
2364
|
+
* direction; auto without a color keeps the host's own palette (null — only
|
|
2365
|
+
* the label direction gets asserted). The palette direction follows the
|
|
2366
|
+
* color's own lightness (rColorScheme), not the wallpaper verdict, so the
|
|
2367
|
+
* surfaces keep contrasting with the fonts. */
|
|
2368
|
+
function paletteTokens() {
|
|
2369
|
+
const scheme = rColorScheme();
|
|
2370
|
+
if (rHasColor()) {
|
|
2371
|
+
const [h, s, l] = rColor();
|
|
2372
|
+
return genTokens(h, s, l, scheme).tokens;
|
|
2373
|
+
}
|
|
2374
|
+
if (rSchemeOverride() !== "auto") return genTokens(220, .04, scheme === "dark" ? .14 : .92, scheme).tokens;
|
|
2375
|
+
return null;
|
|
2376
|
+
}
|
|
1648
2377
|
function applyCustomTokensNow(ops) {
|
|
2378
|
+
const hasColor = rHasColor();
|
|
2379
|
+
const override = rSchemeOverride();
|
|
1649
2380
|
const [h, s, l] = rColor();
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
2381
|
+
const scheme = rScheme();
|
|
2382
|
+
const verdict = rBgDark();
|
|
2383
|
+
const palette = paletteTokens();
|
|
2384
|
+
const tokens = { ...palette };
|
|
2385
|
+
if (override === "auto") {
|
|
2386
|
+
const fontDark = hasColor ? rColorScheme() === "dark" : verdict;
|
|
2387
|
+
if (fontDark !== null && fontDark !== void 0) {
|
|
2388
|
+
const font = fontDark ? "#fff" : "#000";
|
|
1656
2389
|
for (const name of LABEL_TOKENS) tokens[name] = font;
|
|
1657
2390
|
}
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
2391
|
+
}
|
|
2392
|
+
try {
|
|
2393
|
+
const forceDark = scheme === "dark";
|
|
2394
|
+
const key = `${h}|${s}|${l}|${verdict}|${scheme}|${hasColor}`;
|
|
2395
|
+
if (key !== baseTokenKey) {
|
|
2396
|
+
baseTokenKey = key;
|
|
1661
2397
|
if (forceDark) document.body.setAttribute("data-ds-dark-theme", "dsh-any-background");
|
|
1662
2398
|
else document.body.removeAttribute("data-ds-dark-theme");
|
|
1663
2399
|
const decls = [`color-scheme:${forceDark ? "dark" : "light"}`];
|
|
@@ -1669,6 +2405,7 @@ window.__ModuleLoader__.load({
|
|
|
1669
2405
|
for (const name of appliedTokenNames) document.body.style.removeProperty(name);
|
|
1670
2406
|
appliedTokenNames = Object.keys(tokens);
|
|
1671
2407
|
}
|
|
2408
|
+
if (palette === null) return;
|
|
1672
2409
|
const root = document.documentElement;
|
|
1673
2410
|
for (const g of OPACITY_TOKEN_GROUPS) for (const name of g.names) root.style.setProperty(OPACITY_VARS[name], toRgba(tokens[name] ?? "#000", ops[g.part]));
|
|
1674
2411
|
root.style.setProperty("--dsh-any-op-menu-cordis", toRgba(tokens["--dsw-specific-menu"] ?? "#000", ops.input));
|
|
@@ -1686,9 +2423,17 @@ window.__ModuleLoader__.load({
|
|
|
1686
2423
|
if (px > 0) document.documentElement.style.setProperty("--dsh-any-input-blur", `blur(${px}px)`);
|
|
1687
2424
|
else document.documentElement.style.removeProperty("--dsh-any-input-blur");
|
|
1688
2425
|
}
|
|
2426
|
+
const MOBILE_HEADER_RULE = ".dsh-mobile-app-header{background:var(--dsh-any-op-bg,transparent)!important;backdrop-filter:var(--dsh-any-part-blur-global,none);-webkit-backdrop-filter:var(--dsh-any-part-blur-global,none)}";
|
|
2427
|
+
/** Mirror of the bg-part blur on :root. --dsh-any-part-blur is element-scoped
|
|
2428
|
+
* to the columns, so surfaces outside their subtree (the mobile header, or
|
|
2429
|
+
* third-party styles) can never inherit it. */
|
|
2430
|
+
function applyBgBlurGlobal(px) {
|
|
2431
|
+
if (px > 0) document.documentElement.style.setProperty("--dsh-any-part-blur-global", `blur(${px}px)`);
|
|
2432
|
+
else document.documentElement.style.removeProperty("--dsh-any-part-blur-global");
|
|
2433
|
+
}
|
|
1689
2434
|
function applySettingsOverrides(op) {
|
|
1690
2435
|
const [h, s, l] = rColor();
|
|
1691
|
-
const tokens = genTokens(h, s, l).tokens;
|
|
2436
|
+
const tokens = genTokens(h, s, l, rColorScheme()).tokens;
|
|
1692
2437
|
const layer1 = tokens["--dsw-alias-bg-layer-1"];
|
|
1693
2438
|
const layer2 = tokens["--dsw-alias-bg-layer-2"];
|
|
1694
2439
|
const layer3 = tokens["--dsw-alias-bg-layer-3"];
|
|
@@ -1700,7 +2445,7 @@ window.__ModuleLoader__.load({
|
|
|
1700
2445
|
const TRAJECTORY_STYLE_RULE = "[data-conversation-composer-overlay]{--dsw-alias-bg-layer-1:var(--dsh-any-traj-layer-1);--dsw-alias-bg-layer-2:var(--dsh-any-traj-layer-2);--dsw-alias-bg-layer-3:var(--dsh-any-traj-layer-3)}";
|
|
1701
2446
|
function applyTrajectoryOverrides(op) {
|
|
1702
2447
|
const [h, s, l] = rColor();
|
|
1703
|
-
const tokens = genTokens(h, s, l).tokens;
|
|
2448
|
+
const tokens = genTokens(h, s, l, rColorScheme()).tokens;
|
|
1704
2449
|
const layer1 = tokens["--dsw-alias-bg-layer-1"];
|
|
1705
2450
|
const layer2 = tokens["--dsw-alias-bg-layer-2"];
|
|
1706
2451
|
const layer3 = tokens["--dsw-alias-bg-layer-3"];
|
|
@@ -1708,6 +2453,14 @@ window.__ModuleLoader__.load({
|
|
|
1708
2453
|
if (layer2 !== void 0) document.documentElement.style.setProperty("--dsh-any-traj-layer-2", toRgba(layer2, op));
|
|
1709
2454
|
if (layer3 !== void 0) document.documentElement.style.setProperty("--dsh-any-traj-layer-3", toRgba(layer3, op));
|
|
1710
2455
|
}
|
|
2456
|
+
/** Register a callback fired when a wallpaper-extracted theme color is
|
|
2457
|
+
* adopted by the auto-adaptation path, so the section can re-register the
|
|
2458
|
+
* host skin, sync the editor UI and persist the pick (wallpaper.ts cannot
|
|
2459
|
+
* do those itself — they live in the section). */
|
|
2460
|
+
let colorAdoptedListener = null;
|
|
2461
|
+
function onColorAdopted(cb) {
|
|
2462
|
+
colorAdoptedListener = cb;
|
|
2463
|
+
}
|
|
1711
2464
|
/** Apply the theme color: use the saved pick directly, or fall back to
|
|
1712
2465
|
* extracting a dominant color from the current wallpaper. */
|
|
1713
2466
|
function applyThemeColor() {
|
|
@@ -1716,11 +2469,16 @@ window.__ModuleLoader__.load({
|
|
|
1716
2469
|
return;
|
|
1717
2470
|
}
|
|
1718
2471
|
const url = rWp();
|
|
1719
|
-
if (url)
|
|
1720
|
-
if (hsl) cfg.color = hsl;
|
|
2472
|
+
if (url) {
|
|
1721
2473
|
applyWp();
|
|
1722
|
-
|
|
1723
|
-
|
|
2474
|
+
extractWallpaperColor(url, cfg.backgroundType === "video" ? rVideoBgState() : rBgState()).then((hsl) => {
|
|
2475
|
+
if (hsl && rWp() === url) {
|
|
2476
|
+
cfg.color = hsl;
|
|
2477
|
+
colorAdoptedListener?.(hsl);
|
|
2478
|
+
}
|
|
2479
|
+
applyWp();
|
|
2480
|
+
});
|
|
2481
|
+
} else applyWp();
|
|
1724
2482
|
}
|
|
1725
2483
|
/** Switch the background source type. For generated types a new live canvas is
|
|
1726
2484
|
* attached to the wallpaper layer and a snapshot is kept for the store/preview. */
|
|
@@ -1782,7 +2540,7 @@ window.__ModuleLoader__.load({
|
|
|
1782
2540
|
setBgDark(null);
|
|
1783
2541
|
analyzeFrameDark(frame).then((dark) => {
|
|
1784
2542
|
if (dark === null || wpController !== controller) return;
|
|
1785
|
-
|
|
2543
|
+
applyVerdict(dark);
|
|
1786
2544
|
applyCustomTokens(rOps());
|
|
1787
2545
|
});
|
|
1788
2546
|
});
|
|
@@ -1848,11 +2606,11 @@ window.__ModuleLoader__.load({
|
|
|
1848
2606
|
* reducing the main-bg opacity stacked a second alpha onto the sidebar; moving
|
|
1849
2607
|
* the alpha onto the columns keeps the sidebar owned by its own slider. */
|
|
1850
2608
|
function applyPartOpacities(ops) {
|
|
1851
|
-
|
|
2609
|
+
const tokens = paletteTokens();
|
|
2610
|
+
if (tokens === null) return;
|
|
1852
2611
|
discoverParts();
|
|
1853
2612
|
if (frameEl === null) return;
|
|
1854
|
-
const
|
|
1855
|
-
const base = genTokens(h, s, l).tokens["--dsw-alias-bg-base"];
|
|
2613
|
+
const base = tokens["--dsw-alias-bg-base"];
|
|
1856
2614
|
frameEl.style.background = "transparent";
|
|
1857
2615
|
if (centerEl !== null) centerEl.style.background = base !== void 0 ? toRgba(base, ops.bg) : "transparent";
|
|
1858
2616
|
if (detailsEl !== null) detailsEl.style.background = base !== void 0 ? toRgba(base, ops.bg) : "transparent";
|
|
@@ -1872,6 +2630,7 @@ window.__ModuleLoader__.load({
|
|
|
1872
2630
|
setBlur(sidebarEl, blurs.sidebar);
|
|
1873
2631
|
setBlur(centerEl, blurs.bg);
|
|
1874
2632
|
setBlur(detailsEl, blurs.bg);
|
|
2633
|
+
applyBgBlurGlobal(blurs.bg);
|
|
1875
2634
|
applyCardPanelsBlur(blurs.card);
|
|
1876
2635
|
applySettingsBlur(blurs.settings);
|
|
1877
2636
|
applyInputBlur(blurs.input);
|
|
@@ -1899,6 +2658,7 @@ window.__ModuleLoader__.load({
|
|
|
1899
2658
|
if (part === "bg") {
|
|
1900
2659
|
setBlur(centerEl, v);
|
|
1901
2660
|
setBlur(detailsEl, v);
|
|
2661
|
+
applyBgBlurGlobal(v);
|
|
1902
2662
|
} else setBlur(sidebarEl, v);
|
|
1903
2663
|
}
|
|
1904
2664
|
const VIEW_CARDS = [{
|
|
@@ -2071,7 +2831,7 @@ window.__ModuleLoader__.load({
|
|
|
2071
2831
|
discoverParts();
|
|
2072
2832
|
if (centerEl === null) return;
|
|
2073
2833
|
const [h, s, l] = rColor();
|
|
2074
|
-
const surface = genTokens(h, s, l).tokens["--dsw-alias-bg-layer-1"];
|
|
2834
|
+
const surface = genTokens(h, s, l, rColorScheme()).tokens["--dsw-alias-bg-layer-1"];
|
|
2075
2835
|
VIEW_CARDS.forEach((spec, i) => {
|
|
2076
2836
|
const target = discoverViewTarget(i, spec);
|
|
2077
2837
|
if (target === null) return;
|
|
@@ -2114,8 +2874,7 @@ window.__ModuleLoader__.load({
|
|
|
2114
2874
|
let themeObserver = null;
|
|
2115
2875
|
let themeRaf = 0;
|
|
2116
2876
|
function reassertScheme() {
|
|
2117
|
-
|
|
2118
|
-
if (rBgDark() ?? l < .55) document.body.setAttribute("data-ds-dark-theme", "dsh-any-background");
|
|
2877
|
+
if (rScheme() === "dark") document.body.setAttribute("data-ds-dark-theme", "dsh-any-background");
|
|
2119
2878
|
else document.body.removeAttribute("data-ds-dark-theme");
|
|
2120
2879
|
applyCustomTokens(rOps());
|
|
2121
2880
|
}
|
|
@@ -2126,7 +2885,7 @@ window.__ModuleLoader__.load({
|
|
|
2126
2885
|
if (themeObserver !== null || typeof MutationObserver === "undefined") return () => void 0;
|
|
2127
2886
|
themeObserver = new MutationObserver(() => {
|
|
2128
2887
|
if (document.body.getAttribute("data-ds-dark-theme") === "dsh-any-background") return;
|
|
2129
|
-
if (!(rHasColor() || rBgDark() !== null)) return;
|
|
2888
|
+
if (!(rHasColor() || rBgDark() !== null || rSchemeOverride() !== "auto")) return;
|
|
2130
2889
|
if (themeRaf !== 0) return;
|
|
2131
2890
|
themeRaf = requestAnimationFrame(() => {
|
|
2132
2891
|
themeRaf = 0;
|
|
@@ -2169,17 +2928,18 @@ window.__ModuleLoader__.load({
|
|
|
2169
2928
|
cb(imgNat.w, imgNat.h);
|
|
2170
2929
|
return;
|
|
2171
2930
|
}
|
|
2172
|
-
|
|
2173
|
-
|
|
2931
|
+
loadImage(url).then((img) => {
|
|
2932
|
+
if (!img) {
|
|
2933
|
+
cb(0, 0);
|
|
2934
|
+
return;
|
|
2935
|
+
}
|
|
2174
2936
|
imgNat = {
|
|
2175
2937
|
url,
|
|
2176
2938
|
w: img.naturalWidth,
|
|
2177
2939
|
h: img.naturalHeight
|
|
2178
2940
|
};
|
|
2179
2941
|
cb(img.naturalWidth, img.naturalHeight);
|
|
2180
|
-
};
|
|
2181
|
-
img.onerror = () => cb(0, 0);
|
|
2182
|
-
img.src = url;
|
|
2942
|
+
});
|
|
2183
2943
|
}
|
|
2184
2944
|
const DRAG_MAX_SIDE = 720;
|
|
2185
2945
|
let lowResUrl = null;
|
|
@@ -2190,8 +2950,11 @@ window.__ModuleLoader__.load({
|
|
|
2190
2950
|
cb(lowResUrl);
|
|
2191
2951
|
return;
|
|
2192
2952
|
}
|
|
2193
|
-
|
|
2194
|
-
|
|
2953
|
+
loadImage(url).then((img) => {
|
|
2954
|
+
if (!img) {
|
|
2955
|
+
cb(null);
|
|
2956
|
+
return;
|
|
2957
|
+
}
|
|
2195
2958
|
const k = Math.min(1, DRAG_MAX_SIDE / Math.max(img.naturalWidth, img.naturalHeight));
|
|
2196
2959
|
if (k >= 1) {
|
|
2197
2960
|
lowResFor = url;
|
|
@@ -2214,9 +2977,7 @@ window.__ModuleLoader__.load({
|
|
|
2214
2977
|
lowResFor = url;
|
|
2215
2978
|
lowResUrl = low;
|
|
2216
2979
|
cb(low);
|
|
2217
|
-
};
|
|
2218
|
-
img.onerror = () => cb(null);
|
|
2219
|
-
img.src = url;
|
|
2980
|
+
});
|
|
2220
2981
|
}
|
|
2221
2982
|
function setDragLow(on) {
|
|
2222
2983
|
if (cfg.backgroundType !== "image" || on === dragLow || !wpEl) return;
|
|
@@ -2253,6 +3014,40 @@ window.__ModuleLoader__.load({
|
|
|
2253
3014
|
if (dragLow) setDragLow(false);
|
|
2254
3015
|
};
|
|
2255
3016
|
}
|
|
3017
|
+
let wpVerdict = null;
|
|
3018
|
+
let verdictListener = null;
|
|
3019
|
+
let verdictGen = 0;
|
|
3020
|
+
/** Register a callback fired when the background brightness verdict CHANGES
|
|
3021
|
+
* (a new wallpaper was analyzed, a generated bg regenerated), so the skin can
|
|
3022
|
+
* be re-registered through the host theme service. */
|
|
3023
|
+
function onVerdictApplied(cb) {
|
|
3024
|
+
verdictListener = cb;
|
|
3025
|
+
}
|
|
3026
|
+
function applyVerdict(dark) {
|
|
3027
|
+
if (rBgDark() === dark) return;
|
|
3028
|
+
setBgDark(dark);
|
|
3029
|
+
if (dark !== null) verdictListener?.();
|
|
3030
|
+
}
|
|
3031
|
+
function updateWpVerdict(url) {
|
|
3032
|
+
const gen = ++verdictGen;
|
|
3033
|
+
if (url === null) {
|
|
3034
|
+
applyVerdict(null);
|
|
3035
|
+
return;
|
|
3036
|
+
}
|
|
3037
|
+
if (wpVerdict !== null && wpVerdict.url === url) {
|
|
3038
|
+
applyVerdict(wpVerdict.dark);
|
|
3039
|
+
return;
|
|
3040
|
+
}
|
|
3041
|
+
analyzeFrameDark(url).then((dark) => {
|
|
3042
|
+
if (dark === null || gen !== verdictGen) return;
|
|
3043
|
+
wpVerdict = {
|
|
3044
|
+
url,
|
|
3045
|
+
dark
|
|
3046
|
+
};
|
|
3047
|
+
applyVerdict(dark);
|
|
3048
|
+
applyCustomTokens(rOps());
|
|
3049
|
+
});
|
|
3050
|
+
}
|
|
2256
3051
|
function applyImageWp(url) {
|
|
2257
3052
|
clearDynamicBg();
|
|
2258
3053
|
clearVideoEl();
|
|
@@ -2299,6 +3094,7 @@ window.__ModuleLoader__.load({
|
|
|
2299
3094
|
}
|
|
2300
3095
|
captureLowRes(url, () => void 0);
|
|
2301
3096
|
applyWpEffects();
|
|
3097
|
+
updateWpVerdict(url);
|
|
2302
3098
|
}
|
|
2303
3099
|
/** Video wallpaper: a muted looping <video> inside the wallpaper layer.
|
|
2304
3100
|
* Placement modes map onto object-fit (tile has no video equivalent and
|
|
@@ -2335,6 +3131,7 @@ window.__ModuleLoader__.load({
|
|
|
2335
3131
|
videoEl.style.objectFit = mode === "stretch" ? "fill" : mode === "fill" || mode === "tile" ? "cover" : "contain";
|
|
2336
3132
|
}
|
|
2337
3133
|
applyWpEffects();
|
|
3134
|
+
updateWpVerdict(rWp());
|
|
2338
3135
|
}
|
|
2339
3136
|
function applyWpEffects() {
|
|
2340
3137
|
if (!wpEl) return;
|
|
@@ -2368,8 +3165,11 @@ window.__ModuleLoader__.load({
|
|
|
2368
3165
|
clearVideoEl();
|
|
2369
3166
|
wpEl?.remove();
|
|
2370
3167
|
wpEl = null;
|
|
3168
|
+
wpVerdict = null;
|
|
3169
|
+
setBgDark(null);
|
|
2371
3170
|
}
|
|
2372
|
-
if (rHasColor() || rBgDark() !== null) applyCustomTokens(rOps());
|
|
3171
|
+
if (rHasColor() || rBgDark() !== null || rSchemeOverride() !== "auto") applyCustomTokens(rOps());
|
|
3172
|
+
else if (!url) clearThemeTokens();
|
|
2373
3173
|
if (rHasColor()) {
|
|
2374
3174
|
applySettingsOverrides(rSop());
|
|
2375
3175
|
applyTrajectoryOverrides(rTrajectoryOpacity());
|
|
@@ -2381,6 +3181,7 @@ window.__ModuleLoader__.load({
|
|
|
2381
3181
|
clearVideoEl();
|
|
2382
3182
|
disposeVideoObjectUrl();
|
|
2383
3183
|
setBgDark(null);
|
|
3184
|
+
wpVerdict = null;
|
|
2384
3185
|
wpEl?.remove();
|
|
2385
3186
|
wpEl = null;
|
|
2386
3187
|
clearCustomTokens();
|
|
@@ -2400,6 +3201,7 @@ window.__ModuleLoader__.load({
|
|
|
2400
3201
|
document.documentElement.style.removeProperty("--dsh-any-blur-settings");
|
|
2401
3202
|
document.documentElement.style.removeProperty("--dsh-any-blur-card-panels");
|
|
2402
3203
|
document.documentElement.style.removeProperty("--dsh-any-input-blur");
|
|
3204
|
+
document.documentElement.style.removeProperty("--dsh-any-part-blur-global");
|
|
2403
3205
|
for (const v of Object.values(OPACITY_VARS)) document.documentElement.style.removeProperty(v);
|
|
2404
3206
|
baseTokenKey = "";
|
|
2405
3207
|
lastBgKey = "";
|
|
@@ -2559,6 +3361,7 @@ window.__ModuleLoader__.load({
|
|
|
2559
3361
|
.dab-urlinput{flex:1;min-width:180px;height:34px;padding:0 12px;border-radius:10px;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);font-size:12.5px;outline:none;transition:border-color .2s}
|
|
2560
3362
|
.dab-urlinput::placeholder{color:var(--dsw-alias-label-quaternary)}
|
|
2561
3363
|
.dab-urlinput:focus{border-color:var(--dsw-alias-brand-primary);box-shadow:0 0 0 3px color-mix(in srgb,var(--dsw-alias-brand-primary) 18%,transparent)}
|
|
3364
|
+
.dab-urlrow{display:flex;flex-wrap:wrap;gap:8px;align-items:center;margin-top:12px;animation:dab-list-in .3s cubic-bezier(.22,1,.36,1) both}
|
|
2562
3365
|
.dab-swatch-lg{height:38px;border-radius:10px;border:1px solid var(--dsw-alias-border-l2);box-shadow:inset 0 0 14px rgba(0,0,0,.1);transition:transform .3s ease}
|
|
2563
3366
|
.dab-swatch-lg:hover{transform:scale(1.02)}
|
|
2564
3367
|
|
|
@@ -2569,11 +3372,19 @@ window.__ModuleLoader__.load({
|
|
|
2569
3372
|
.dab-btn:disabled{opacity:.5;cursor:not-allowed}
|
|
2570
3373
|
.dab-btn-primary{background:var(--dsw-alias-brand-primary);color:var(--dsw-alias-brand-text);border-color:transparent}
|
|
2571
3374
|
.dab-btn-danger{color:var(--dsw-alias-state-error-primary)}
|
|
3375
|
+
.dab-btn-danger-solid{background:var(--dsw-alias-state-error-primary);color:#fff;border-color:transparent}
|
|
3376
|
+
.dab-btn-danger-solid:hover:not(:disabled){background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 85%,#000)}
|
|
2572
3377
|
.dab-btn-ghost{background:transparent;border-color:transparent;color:var(--dsw-alias-label-secondary,var(--dsw-alias-label-tertiary))}
|
|
2573
3378
|
.dab-btn-ghost:hover:not(:disabled){background:var(--dsw-alias-bg-layer-2);box-shadow:none}
|
|
3379
|
+
/* Soft solid button: subtle layer background instead of transparent ghost. */
|
|
3380
|
+
.dab-btn-soft{background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary,var(--dsw-alias-label-tertiary))}
|
|
3381
|
+
.dab-btn-soft:hover:not(:disabled){background:var(--dsw-alias-bg-layer-3,var(--dsw-alias-bg-layer-2));color:var(--dsw-alias-label-primary)}
|
|
3382
|
+
/* White background with error-red text (inverse of danger-solid). */
|
|
3383
|
+
.dab-btn-danger-inverted{background:#fff;color:var(--dsw-alias-state-error-primary);border-color:transparent}
|
|
3384
|
+
.dab-btn-danger-inverted:hover:not(:disabled){background:#f3f4f6}
|
|
2574
3385
|
.dab-btn:focus-visible,.dab-nav-item:focus-visible,.dab-seg-item:focus-visible,.dab-swatch:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}
|
|
2575
3386
|
.dab-chip-row{display:flex;flex-wrap:wrap;gap:8px}
|
|
2576
|
-
.dab-chip{height:30px;padding:0 14px;border-radius:99px;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-secondary,var(--dsw-alias-label-tertiary));font-size:12px;cursor:pointer;display:inline-flex;align-items:center;gap:6px;transition:
|
|
3387
|
+
.dab-chip{height:30px;padding:0 14px;border-radius:99px;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-secondary,var(--dsw-alias-label-tertiary));font-size:12px;cursor:pointer;display:inline-flex;align-items:center;gap:6px;transition:background-color .22s ease,border-color .22s ease,color .22s ease}
|
|
2577
3388
|
.dab-chip:hover{border-color:var(--dsw-alias-brand-primary);color:var(--dsw-alias-label-primary)}
|
|
2578
3389
|
.dab-chip.is-active{background:var(--dsw-alias-brand-primary);color:var(--dsw-alias-brand-text);border-color:transparent}
|
|
2579
3390
|
|
|
@@ -2623,7 +3434,7 @@ window.__ModuleLoader__.load({
|
|
|
2623
3434
|
|
|
2624
3435
|
/* ── generated background type cards ─────────────────────────────────────── */
|
|
2625
3436
|
.dab-types{display:grid;grid-template-columns:repeat(3,1fr);gap:12px}
|
|
2626
|
-
.dab-type{position:relative;border:1.5px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);border-radius:14px;padding:10px;cursor:pointer;text-align:left;font:inherit;transition:border-color .25s,transform .25s,box-shadow .25s}
|
|
3437
|
+
.dab-type{position:relative;border:1.5px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);border-radius:14px;padding:10px;cursor:pointer;text-align:left;font:inherit;transition:border-color .25s,transform .25s,box-shadow .25s;animation:dab-type-in .42s cubic-bezier(.22,1,.36,1) both;animation-delay:calc(var(--i,0) * 60ms)}
|
|
2627
3438
|
.dab-type:hover{transform:translateY(-2px)}
|
|
2628
3439
|
.dab-type.is-active{border-color:var(--dsw-alias-brand-primary);box-shadow:0 0 0 3px color-mix(in srgb,var(--dsw-alias-brand-primary) 18%,transparent)}
|
|
2629
3440
|
.dab-type-thumb{height:62px;border-radius:9px;overflow:hidden;position:relative}
|
|
@@ -2651,6 +3462,52 @@ window.__ModuleLoader__.load({
|
|
|
2651
3462
|
.dab-footer{margin-top:6px;padding:14px 4px 0;border-top:1px solid var(--dsw-alias-border-l2);display:flex;align-items:center;justify-content:space-between;font-size:11px;color:var(--dsw-alias-label-tertiary)}
|
|
2652
3463
|
.dab-footer-mono{font-family:var(--dab-mono);letter-spacing:.02em}
|
|
2653
3464
|
|
|
3465
|
+
/* ── presets / profiles / schedule / rotation ────────────────────────────── */
|
|
3466
|
+
.dab-row-head{display:flex;align-items:center;justify-content:space-between;gap:12px}
|
|
3467
|
+
.dab-preset-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:10px}
|
|
3468
|
+
.dab-preset{display:flex;flex-direction:column;align-items:flex-start;gap:3px;padding:12px;border:1.5px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);border-radius:13px;cursor:pointer;font:inherit;text-align:left;transition:border-color .25s,transform .25s,box-shadow .25s;animation:dab-row-in .34s cubic-bezier(.22,1,.36,1) both;animation-delay:calc(var(--i,0) * 45ms)}
|
|
3469
|
+
.dab-preset:hover{transform:translateY(-2px);border-color:var(--dsw-alias-brand-primary);box-shadow:0 8px 22px -12px rgba(0,0,0,.3)}
|
|
3470
|
+
.dab-preset-dot{width:22px;height:22px;border-radius:50%;margin-bottom:4px;box-shadow:inset 0 0 0 1px rgba(0,0,0,.12),0 4px 10px -4px rgba(0,0,0,.35);background:var(--dsw-alias-bg-layer-2);display:grid;place-items:center;color:var(--dsw-alias-label-tertiary)}
|
|
3471
|
+
.dab-preset-name{font-size:12.5px;font-weight:600;color:var(--dsw-alias-label-primary)}
|
|
3472
|
+
.dab-preset-desc{font-size:10.5px;line-height:1.45;color:var(--dsw-alias-label-tertiary)}
|
|
3473
|
+
.dab-profile-list{display:flex;flex-direction:column;gap:8px;margin-top:12px;animation:dab-list-in .38s cubic-bezier(.22,1,.36,1) both}
|
|
3474
|
+
.dab-profile-row{display:flex;align-items:center;gap:11px;padding:9px 12px;border:1px solid var(--dsw-alias-border-l2);border-radius:12px;background:var(--dsw-alias-bg-layer-2);animation:dab-row-in .34s cubic-bezier(.22,1,.36,1) both;animation-delay:calc(var(--i,0) * 45ms);transition:border-color .22s ease,background .22s ease}
|
|
3475
|
+
.dab-profile-row:hover{border-color:color-mix(in srgb,var(--dsw-alias-brand-primary) 28%,transparent);background:color-mix(in srgb,var(--dsw-alias-label-primary) 4%,var(--dsw-alias-bg-layer-2))}
|
|
3476
|
+
.dab-profile-row.is-active{border-color:color-mix(in srgb,var(--dsw-alias-brand-primary) 45%,transparent);background:color-mix(in srgb,var(--dsw-alias-brand-primary) 7%,var(--dsw-alias-bg-layer-2))}
|
|
3477
|
+
.dab-profile-dot{width:22px;height:22px;flex:none;border-radius:50%;background:var(--dsw-alias-bg-layer-1);box-shadow:inset 0 0 0 1px rgba(0,0,0,.12);display:grid;place-items:center;color:var(--dsw-alias-label-tertiary)}
|
|
3478
|
+
.dab-profile-meta{flex:1;min-width:0;display:flex;flex-direction:column;gap:1px}
|
|
3479
|
+
.dab-profile-name{font-size:12.5px;font-weight:600;color:var(--dsw-alias-label-primary);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
3480
|
+
.dab-profile-sub{font-family:var(--dab-mono);font-size:10px;color:var(--dsw-alias-label-tertiary)}
|
|
3481
|
+
.dab-btn-confirm{background:var(--dsw-alias-state-error-primary)!important;color:#fff!important;border-color:transparent!important}
|
|
3482
|
+
.dab-select{height:32px;padding:0 10px;border-radius:9px;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);font-size:12px;outline:none;cursor:pointer;max-width:180px}
|
|
3483
|
+
.dab-select:focus{border-color:var(--dsw-alias-brand-primary)}
|
|
3484
|
+
.dab-timeinput{height:32px;padding:0 8px;border-radius:9px;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);font-family:var(--dab-mono);font-size:12px;outline:none}
|
|
3485
|
+
.dab-timeinput:focus{border-color:var(--dsw-alias-brand-primary)}
|
|
3486
|
+
/* Collapsible panel: animates grid-template-rows 0fr→1fr so the schedule
|
|
3487
|
+
block unfolds to its content height instead of popping in. visibility
|
|
3488
|
+
flips after the fold finishes (and instantly on open) so hidden inputs
|
|
3489
|
+
are never focusable. */
|
|
3490
|
+
.dab-schedule-wrap{display:grid;grid-template-rows:0fr;visibility:hidden;transition:grid-template-rows .42s cubic-bezier(.22,1,.36,1),visibility 0s linear .42s}
|
|
3491
|
+
.dab-schedule-wrap.is-open{grid-template-rows:1fr;visibility:visible;transition:grid-template-rows .42s cubic-bezier(.22,1,.36,1)}
|
|
3492
|
+
.dab-schedule-clip{overflow:hidden;min-height:0;opacity:0;transition:opacity .28s ease}
|
|
3493
|
+
.dab-schedule-wrap.is-open .dab-schedule-clip{opacity:1;transition:opacity .32s ease .1s}
|
|
3494
|
+
.dab-schedule-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:16px;padding-top:12px}
|
|
3495
|
+
.dab-schedule-cell{min-width:0;animation:dab-row-in .34s cubic-bezier(.22,1,.36,1) both;animation-delay:calc(var(--i,0) * 60ms)}
|
|
3496
|
+
.dab-time-row{display:flex;flex-direction:column;gap:9px}
|
|
3497
|
+
.dab-time-label{display:flex;align-items:center;gap:7px;font-size:12px;color:var(--dsw-alias-label-secondary,var(--dsw-alias-label-tertiary))}
|
|
3498
|
+
.dab-time-label svg{color:var(--dsw-alias-brand-primary);flex:none}
|
|
3499
|
+
.dab-time-label .dab-select,.dab-time-label .dab-timeinput{flex:1;min-width:0}
|
|
3500
|
+
.dab-thumbstrip{display:flex;flex-wrap:wrap;gap:9px;margin-top:13px}
|
|
3501
|
+
.dab-thumb{position:relative;width:64px;height:44px;border-radius:9px;overflow:hidden;border:1.5px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);display:grid;place-items:center;color:var(--dsw-alias-label-tertiary);animation:dab-list-in .3s cubic-bezier(.22,1,.36,1) both}
|
|
3502
|
+
.dab-thumb.is-current{border-color:var(--dsw-alias-brand-primary);box-shadow:0 0 0 2px color-mix(in srgb,var(--dsw-alias-brand-primary) 25%,transparent)}
|
|
3503
|
+
.dab-thumb img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}
|
|
3504
|
+
.dab-thumb-x{position:absolute;top:2px;right:2px;width:16px;height:16px;border:0;border-radius:50%;background:rgba(0,0,0,.55);color:#fff;cursor:pointer;display:grid;place-items:center;opacity:0;transition:opacity .18s ease,background .18s ease;padding:0}
|
|
3505
|
+
.dab-thumb:hover .dab-thumb-x{opacity:1}
|
|
3506
|
+
.dab-thumb-x:hover{background:var(--dsw-alias-state-error-primary)}
|
|
3507
|
+
.dab-thumb-add{border-style:dashed;cursor:pointer;background:transparent;color:var(--dsw-alias-label-tertiary);font:inherit;transition:border-color .22s ease,color .22s ease}
|
|
3508
|
+
.dab-thumb-add:hover{border-color:var(--dsw-alias-brand-primary);color:var(--dsw-alias-brand-primary)}
|
|
3509
|
+
.dab-chip-sep{width:1px;height:18px;background:var(--dsw-alias-border-l2);margin:0 4px;flex:none}
|
|
3510
|
+
|
|
2654
3511
|
/* ── toast ───────────────────────────────────────────────────────────────── */
|
|
2655
3512
|
.dab-toast{position:fixed;left:50%;bottom:30px;transform:translateX(-50%);display:flex;align-items:center;gap:8px;height:38px;padding:0 16px;border-radius:99px;background:var(--dsw-alias-bg-layer-3,var(--dsw-alias-bg-layer-2));border:1px solid var(--dsw-alias-border-l2);box-shadow:0 10px 30px -8px rgba(0,0,0,.38);font-size:12.5px;z-index:10001;animation:dab-toast-in .32s cubic-bezier(.22,1,.36,1) both}
|
|
2656
3513
|
.dab-toast-ok{color:var(--dsw-alias-brand-primary);display:grid;place-items:center}
|
|
@@ -2675,6 +3532,9 @@ window.__ModuleLoader__.load({
|
|
|
2675
3532
|
@keyframes dab-fade-in{from{opacity:0}to{opacity:1}}
|
|
2676
3533
|
@keyframes dab-page-in{from{opacity:0;transform:translateY(12px)}to{opacity:1;transform:none}}
|
|
2677
3534
|
@keyframes dab-rise-in{from{opacity:0;transform:translateY(10px)}to{opacity:1;transform:none}}
|
|
3535
|
+
@keyframes dab-list-in{from{opacity:0;transform:translateY(-7px)}to{opacity:1;transform:none}}
|
|
3536
|
+
@keyframes dab-row-in{from{opacity:0;transform:translateY(6px) scale(.985)}to{opacity:1;transform:none}}
|
|
3537
|
+
@keyframes dab-type-in{from{opacity:0;transform:translateY(8px) scale(.97)}to{opacity:1;transform:none}}
|
|
2678
3538
|
@keyframes dab-orb-in{from{opacity:0;transform:scale(.82)}to{opacity:1;transform:none}}
|
|
2679
3539
|
@keyframes dab-spin{to{transform:rotate(360deg)}}
|
|
2680
3540
|
@keyframes dab-rotate{to{transform:rotate(360deg)}}
|
|
@@ -2958,6 +3818,40 @@ window.__ModuleLoader__.load({
|
|
|
2958
3818
|
rx: "2"
|
|
2959
3819
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M8 6.6v2M7 8h2" })]
|
|
2960
3820
|
});
|
|
3821
|
+
const PlusIcon = ({ size, className }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Glyph, {
|
|
3822
|
+
size,
|
|
3823
|
+
className,
|
|
3824
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M8 3v10M3 8h10" })
|
|
3825
|
+
});
|
|
3826
|
+
const MoonIcon = ({ size, className }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Glyph, {
|
|
3827
|
+
size,
|
|
3828
|
+
className,
|
|
3829
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M13.2 9.6A5.6 5.6 0 0 1 6.4 2.8a5.6 5.6 0 1 0 6.8 6.8z" })
|
|
3830
|
+
});
|
|
3831
|
+
const ClockIcon = ({ size, className }) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Glyph, {
|
|
3832
|
+
size,
|
|
3833
|
+
className,
|
|
3834
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
|
|
3835
|
+
cx: "8",
|
|
3836
|
+
cy: "8",
|
|
3837
|
+
r: "5.8"
|
|
3838
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M8 4.8V8l2.2 1.6" })]
|
|
3839
|
+
});
|
|
3840
|
+
const XIcon = ({ size, className }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Glyph, {
|
|
3841
|
+
size,
|
|
3842
|
+
className,
|
|
3843
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M4 4l8 8M12 4l-8 8" })
|
|
3844
|
+
});
|
|
3845
|
+
const PlayIcon = ({ size, className }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Glyph, {
|
|
3846
|
+
size,
|
|
3847
|
+
className,
|
|
3848
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M5.2 3.4l7.4 4.6-7.4 4.6V3.4z" })
|
|
3849
|
+
});
|
|
3850
|
+
const PauseIcon = ({ size, className }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Glyph, {
|
|
3851
|
+
size,
|
|
3852
|
+
className,
|
|
3853
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M5.4 3.4v9.2M10.6 3.4v9.2" })
|
|
3854
|
+
});
|
|
2961
3855
|
//#endregion
|
|
2962
3856
|
//#region src/client/components/ErrorBoundary.tsx
|
|
2963
3857
|
/**
|
|
@@ -3600,12 +4494,13 @@ window.__ModuleLoader__.load({
|
|
|
3600
4494
|
return "#" + rgb.map((v) => Math.round(v).toString(16).padStart(2, "0")).join("");
|
|
3601
4495
|
}
|
|
3602
4496
|
function ColorPage({ p, notify }) {
|
|
3603
|
-
const { t, hue, sat, lit, setColor, extractColor, useStore } = p;
|
|
3604
|
-
const
|
|
3605
|
-
const
|
|
4497
|
+
const { t, hue, sat, lit, setColor, extractColor, setSchemeOverride, useStore } = p;
|
|
4498
|
+
const storeUrl = useStore((s) => s.url);
|
|
4499
|
+
const schemeOverride = useStore((s) => s.schemeOverride);
|
|
4500
|
+
const color = useStore((s) => s.color);
|
|
3606
4501
|
const [pickerOpen, setPickerOpen] = (0, react.useState)(false);
|
|
3607
4502
|
const [extracting, setExtracting] = (0, react.useState)(false);
|
|
3608
|
-
const wheel =
|
|
4503
|
+
const wheel = color ?? [
|
|
3609
4504
|
hue,
|
|
3610
4505
|
sat,
|
|
3611
4506
|
lit
|
|
@@ -3748,6 +4643,49 @@ window.__ModuleLoader__.load({
|
|
|
3748
4643
|
})
|
|
3749
4644
|
})]
|
|
3750
4645
|
}),
|
|
4646
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
4647
|
+
className: "dab-card dab-rise",
|
|
4648
|
+
style: { "--d": 4 },
|
|
4649
|
+
children: [
|
|
4650
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4651
|
+
className: "dab-swatch-title",
|
|
4652
|
+
children: t("schemeTitle")
|
|
4653
|
+
}),
|
|
4654
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4655
|
+
className: "dab-seg",
|
|
4656
|
+
style: { "--w": "86px" },
|
|
4657
|
+
children: [
|
|
4658
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4659
|
+
className: "dab-seg-thumb",
|
|
4660
|
+
style: { transform: `translateX(${schemeOverride === "light" ? 0 : schemeOverride === "dark" ? 86 : 172}px)` }
|
|
4661
|
+
}),
|
|
4662
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
4663
|
+
type: "button",
|
|
4664
|
+
className: `dab-seg-item${schemeOverride === "light" ? " is-active" : ""}`,
|
|
4665
|
+
onClick: () => setSchemeOverride("light"),
|
|
4666
|
+
children: t("schemeLight")
|
|
4667
|
+
}),
|
|
4668
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
4669
|
+
type: "button",
|
|
4670
|
+
className: `dab-seg-item${schemeOverride === "dark" ? " is-active" : ""}`,
|
|
4671
|
+
onClick: () => setSchemeOverride("dark"),
|
|
4672
|
+
children: t("schemeDark")
|
|
4673
|
+
}),
|
|
4674
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
4675
|
+
type: "button",
|
|
4676
|
+
className: `dab-seg-item${schemeOverride === "auto" ? " is-active" : ""}`,
|
|
4677
|
+
onClick: () => setSchemeOverride("auto"),
|
|
4678
|
+
children: t("schemeAuto")
|
|
4679
|
+
})
|
|
4680
|
+
]
|
|
4681
|
+
}),
|
|
4682
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
4683
|
+
className: "dab-hint",
|
|
4684
|
+
style: { marginTop: 10 },
|
|
4685
|
+
children: t("schemeHint")
|
|
4686
|
+
})
|
|
4687
|
+
]
|
|
4688
|
+
}),
|
|
3751
4689
|
pickerOpen && storeUrl ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ColorPicker, {
|
|
3752
4690
|
url: storeUrl,
|
|
3753
4691
|
t,
|
|
@@ -3989,20 +4927,6 @@ window.__ModuleLoader__.load({
|
|
|
3989
4927
|
})] });
|
|
3990
4928
|
}
|
|
3991
4929
|
//#endregion
|
|
3992
|
-
//#region src/client/utils/image.ts
|
|
3993
|
-
/**
|
|
3994
|
-
* Read a chosen image file as a data URL WITHOUT re-encoding: the original
|
|
3995
|
-
* pixels are kept as-is (no canvas downscale / JPEG re-compression), so the
|
|
3996
|
-
* wallpaper is stored and displayed at full fidelity. The tradeoff is a larger
|
|
3997
|
-
* payload over the RPC channel and on disk for big images.
|
|
3998
|
-
*/
|
|
3999
|
-
function readImg(file, cb) {
|
|
4000
|
-
const r = new FileReader();
|
|
4001
|
-
r.onerror = () => cb(null);
|
|
4002
|
-
r.onload = () => cb(r.result);
|
|
4003
|
-
r.readAsDataURL(file);
|
|
4004
|
-
}
|
|
4005
|
-
//#endregion
|
|
4006
4930
|
//#region src/client/components/BgEditor.tsx
|
|
4007
4931
|
/** Placement slot for the active background type: video framing lives in its
|
|
4008
4932
|
* own state so image and video edits never overwrite each other. */
|
|
@@ -4218,16 +5142,18 @@ window.__ModuleLoader__.load({
|
|
|
4218
5142
|
}
|
|
4219
5143
|
];
|
|
4220
5144
|
function BackgroundPage({ p }) {
|
|
4221
|
-
const { t,
|
|
4222
|
-
const
|
|
4223
|
-
const
|
|
4224
|
-
const
|
|
4225
|
-
const
|
|
4226
|
-
const
|
|
5145
|
+
const { t, setWpFromServer, setVideo, setWop, setBl, setBgType, setGeneratedBg, regenerateBg, setRegenerateOnReload, setRotation, addRotationItems, removeRotationItem, rotateNow, setVideoFromUrl, useStore } = p;
|
|
5146
|
+
const storeUrl = useStore((s) => s.url);
|
|
5147
|
+
const backgroundType = useStore((s) => s.backgroundType);
|
|
5148
|
+
const generatedBg = useStore((s) => s.generatedBg);
|
|
5149
|
+
const regenerateOnReload = useStore((s) => s.regenerateOnReload);
|
|
5150
|
+
const rotation = useStore((s) => s.rotation);
|
|
4227
5151
|
const fileRef = (0, react.useRef)(null);
|
|
5152
|
+
const rotFileRef = (0, react.useRef)(null);
|
|
4228
5153
|
const [editorOpen, setEditorOpen] = (0, react.useState)(false);
|
|
4229
5154
|
const [dragOver, setDragOver] = (0, react.useState)(false);
|
|
4230
5155
|
const [spinTick, setSpinTick] = (0, react.useState)(0);
|
|
5156
|
+
const [rotBusy, setRotBusy] = (0, react.useState)(false);
|
|
4231
5157
|
const [urlOpen, setUrlOpen] = (0, react.useState)(false);
|
|
4232
5158
|
const [urlVal, setUrlVal] = (0, react.useState)("");
|
|
4233
5159
|
const [urlBusy, setUrlBusy] = (0, react.useState)(false);
|
|
@@ -4237,13 +5163,22 @@ window.__ModuleLoader__.load({
|
|
|
4237
5163
|
const isStatic = backgroundType === "image" || isVideo;
|
|
4238
5164
|
const isGenerated = !isStatic;
|
|
4239
5165
|
const activeGenType = isGenerated && generatedBg ? generatedBg.type : "mesh";
|
|
5166
|
+
const [paused, setPaused] = (0, react.useState)(false);
|
|
5167
|
+
const genPreset = generatedBg !== null && generatedBg.type !== "mesh" ? generatedBg.preset : void 0;
|
|
5168
|
+
(0, react.useEffect)(() => {
|
|
5169
|
+
setPaused(false);
|
|
5170
|
+
}, [
|
|
5171
|
+
activeGenType,
|
|
5172
|
+
genPreset,
|
|
5173
|
+
generatedBg?.seed
|
|
5174
|
+
]);
|
|
4240
5175
|
const onFileSelect = (f) => {
|
|
4241
5176
|
if (f.type.startsWith("video/")) {
|
|
4242
5177
|
setVideo(f, f.type);
|
|
4243
5178
|
return;
|
|
4244
5179
|
}
|
|
4245
|
-
|
|
4246
|
-
if (
|
|
5180
|
+
uploadWallpaper(f).then((ok) => {
|
|
5181
|
+
if (ok) setWpFromServer(WALLPAPER_SERVE_URL);
|
|
4247
5182
|
});
|
|
4248
5183
|
};
|
|
4249
5184
|
const onDrop = (e) => {
|
|
@@ -4261,12 +5196,17 @@ window.__ModuleLoader__.load({
|
|
|
4261
5196
|
setUrlBusy(true);
|
|
4262
5197
|
setUrlErr(null);
|
|
4263
5198
|
const r = await setWallpaperFromUrl(u);
|
|
4264
|
-
setUrlBusy(false);
|
|
4265
5199
|
if (r.ok) {
|
|
4266
|
-
|
|
5200
|
+
setWpFromServer(r.wallpaperUrl ?? null);
|
|
4267
5201
|
setUrlOpen(false);
|
|
4268
5202
|
setUrlVal("");
|
|
5203
|
+
} else if (r.error === "not an image") {
|
|
5204
|
+
if (await setVideoFromUrl(u)) {
|
|
5205
|
+
setUrlOpen(false);
|
|
5206
|
+
setUrlVal("");
|
|
5207
|
+
} else setUrlErr(t("bgUrlVideoFail"));
|
|
4269
5208
|
} else setUrlErr(r.error === "invalid url" || r.error === "unsupported scheme" ? t("bgUrlBadHttp") : r.error ?? t("bgUrlFail"));
|
|
5209
|
+
setUrlBusy(false);
|
|
4270
5210
|
};
|
|
4271
5211
|
const switchToStatic = () => {
|
|
4272
5212
|
if (isStatic) return;
|
|
@@ -4281,10 +5221,12 @@ window.__ModuleLoader__.load({
|
|
|
4281
5221
|
};
|
|
4282
5222
|
const setGenType = (type) => {
|
|
4283
5223
|
if (type === activeGenType) return;
|
|
5224
|
+
setPaused(false);
|
|
4284
5225
|
setBgType(type);
|
|
4285
5226
|
};
|
|
4286
5227
|
const ensureGenParams = () => generatedBg ?? defaultParamsFor(activeGenType);
|
|
4287
5228
|
const updateGenerated = (patch) => {
|
|
5229
|
+
setPaused(false);
|
|
4288
5230
|
setGeneratedBg({
|
|
4289
5231
|
...ensureGenParams(),
|
|
4290
5232
|
...patch
|
|
@@ -4315,9 +5257,13 @@ window.__ModuleLoader__.load({
|
|
|
4315
5257
|
case "aurora": return t("presetAurora");
|
|
4316
5258
|
case "nebula": return t("presetNebula");
|
|
4317
5259
|
case "noise": return t("presetNoise");
|
|
5260
|
+
case "starfield": return t("presetStarfield");
|
|
4318
5261
|
case "dots": return t("presetDots");
|
|
4319
5262
|
case "waves": return t("presetWaves");
|
|
4320
5263
|
case "poly": return t("presetPoly");
|
|
5264
|
+
case "rain": return t("presetRain");
|
|
5265
|
+
case "contour": return t("presetContour");
|
|
5266
|
+
case "meta": return t("presetMeta");
|
|
4321
5267
|
default: return key;
|
|
4322
5268
|
}
|
|
4323
5269
|
};
|
|
@@ -4372,6 +5318,7 @@ window.__ModuleLoader__.load({
|
|
|
4372
5318
|
type: "button",
|
|
4373
5319
|
className: "dab-btn",
|
|
4374
5320
|
onClick: () => {
|
|
5321
|
+
setPaused(false);
|
|
4375
5322
|
regenerateBg();
|
|
4376
5323
|
setSpinTick((x) => x + 1);
|
|
4377
5324
|
},
|
|
@@ -4379,7 +5326,7 @@ window.__ModuleLoader__.load({
|
|
|
4379
5326
|
}) : null, /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
4380
5327
|
type: "button",
|
|
4381
5328
|
className: "dab-btn dab-btn-danger",
|
|
4382
|
-
onClick: () =>
|
|
5329
|
+
onClick: () => setWpFromServer(null),
|
|
4383
5330
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TrashIcon, { size: 13 }), t("bgRemove")]
|
|
4384
5331
|
})]
|
|
4385
5332
|
})
|
|
@@ -4425,7 +5372,7 @@ window.__ModuleLoader__.load({
|
|
|
4425
5372
|
]
|
|
4426
5373
|
})
|
|
4427
5374
|
}),
|
|
4428
|
-
!isGenerated ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
5375
|
+
!isGenerated ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
4429
5376
|
className: "dab-card dab-card-hover dab-rise",
|
|
4430
5377
|
style: { "--d": 3 },
|
|
4431
5378
|
children: [
|
|
@@ -4440,7 +5387,7 @@ window.__ModuleLoader__.load({
|
|
|
4440
5387
|
}),
|
|
4441
5388
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
4442
5389
|
type: "button",
|
|
4443
|
-
className: "dab-btn dab-btn-
|
|
5390
|
+
className: "dab-btn dab-btn-soft",
|
|
4444
5391
|
onClick: () => setUrlOpen((o) => !o),
|
|
4445
5392
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(LinkIcon, { size: 14 }), t("bgFromUrl")]
|
|
4446
5393
|
}),
|
|
@@ -4453,21 +5400,14 @@ window.__ModuleLoader__.load({
|
|
|
4453
5400
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(EditIcon, { size: 14 }), t("bgEdit")]
|
|
4454
5401
|
}) : null, /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
4455
5402
|
type: "button",
|
|
4456
|
-
className: "dab-btn dab-btn-
|
|
4457
|
-
onClick: () =>
|
|
5403
|
+
className: "dab-btn dab-btn-danger-inverted",
|
|
5404
|
+
onClick: () => setWpFromServer(null),
|
|
4458
5405
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TrashIcon, { size: 14 }), t("bgRemove")]
|
|
4459
5406
|
})] }) : null
|
|
4460
5407
|
]
|
|
4461
5408
|
}),
|
|
4462
5409
|
urlOpen ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4463
5410
|
className: "dab-urlrow",
|
|
4464
|
-
style: {
|
|
4465
|
-
marginTop: 12,
|
|
4466
|
-
display: "flex",
|
|
4467
|
-
flexWrap: "wrap",
|
|
4468
|
-
gap: 8,
|
|
4469
|
-
alignItems: "center"
|
|
4470
|
-
},
|
|
4471
5411
|
children: [
|
|
4472
5412
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
4473
5413
|
type: "text",
|
|
@@ -4527,15 +5467,130 @@ window.__ModuleLoader__.load({
|
|
|
4527
5467
|
})]
|
|
4528
5468
|
})
|
|
4529
5469
|
]
|
|
4530
|
-
})
|
|
5470
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
5471
|
+
className: "dab-card dab-rise",
|
|
5472
|
+
style: { "--d": 4 },
|
|
5473
|
+
children: [
|
|
5474
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5475
|
+
className: "dab-row-head",
|
|
5476
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5477
|
+
className: "dab-swatch-title",
|
|
5478
|
+
style: { marginBottom: 0 },
|
|
5479
|
+
children: t("rotTitle")
|
|
5480
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
5481
|
+
type: "button",
|
|
5482
|
+
className: `dab-toggle${rotation.enabled ? " is-on" : ""}`,
|
|
5483
|
+
role: "switch",
|
|
5484
|
+
"aria-checked": rotation.enabled,
|
|
5485
|
+
onClick: () => setRotation({ enabled: !rotation.enabled }),
|
|
5486
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "dab-toggle-knob" })
|
|
5487
|
+
})]
|
|
5488
|
+
}),
|
|
5489
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
5490
|
+
className: "dab-hint",
|
|
5491
|
+
style: { marginTop: 8 },
|
|
5492
|
+
children: t("rotHint")
|
|
5493
|
+
}),
|
|
5494
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5495
|
+
className: "dab-thumbstrip",
|
|
5496
|
+
children: [rotation.items.map((it, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5497
|
+
className: `dab-thumb${rotation.enabled && i === rotation.current ? " is-current" : ""}`,
|
|
5498
|
+
children: [it.thumb ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
|
|
5499
|
+
src: it.thumb,
|
|
5500
|
+
alt: "",
|
|
5501
|
+
draggable: false
|
|
5502
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PhotoIcon, { size: 15 }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
5503
|
+
type: "button",
|
|
5504
|
+
className: "dab-thumb-x",
|
|
5505
|
+
title: t("rotRemove"),
|
|
5506
|
+
disabled: rotBusy,
|
|
5507
|
+
onClick: () => {
|
|
5508
|
+
setRotBusy(true);
|
|
5509
|
+
removeRotationItem(i).finally(() => setRotBusy(false));
|
|
5510
|
+
},
|
|
5511
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(XIcon, { size: 10 })
|
|
5512
|
+
})]
|
|
5513
|
+
}, it.file)), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
5514
|
+
type: "button",
|
|
5515
|
+
className: "dab-thumb dab-thumb-add",
|
|
5516
|
+
title: t("rotAdd"),
|
|
5517
|
+
disabled: rotBusy,
|
|
5518
|
+
onClick: () => rotFileRef.current?.click(),
|
|
5519
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PlusIcon, { size: 16 })
|
|
5520
|
+
})]
|
|
5521
|
+
}),
|
|
5522
|
+
rotation.items.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5523
|
+
className: "dab-chip-row",
|
|
5524
|
+
style: { marginTop: 12 },
|
|
5525
|
+
children: [
|
|
5526
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
5527
|
+
type: "button",
|
|
5528
|
+
className: `dab-chip${rotation.mode === "shuffle" ? " is-active" : ""}`,
|
|
5529
|
+
onClick: () => setRotation({ mode: "shuffle" }),
|
|
5530
|
+
children: t("rotShuffle")
|
|
5531
|
+
}),
|
|
5532
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
5533
|
+
type: "button",
|
|
5534
|
+
className: `dab-chip${rotation.mode === "order" ? " is-active" : ""}`,
|
|
5535
|
+
onClick: () => setRotation({ mode: "order" }),
|
|
5536
|
+
children: t("rotOrder")
|
|
5537
|
+
}),
|
|
5538
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "dab-chip-sep" }),
|
|
5539
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
5540
|
+
type: "button",
|
|
5541
|
+
className: `dab-chip${rotation.interval === "reload" ? " is-active" : ""}`,
|
|
5542
|
+
onClick: () => setRotation({ interval: "reload" }),
|
|
5543
|
+
children: t("rotReload")
|
|
5544
|
+
}),
|
|
5545
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
5546
|
+
type: "button",
|
|
5547
|
+
className: `dab-chip${rotation.interval === "daily" ? " is-active" : ""}`,
|
|
5548
|
+
onClick: () => setRotation({ interval: "daily" }),
|
|
5549
|
+
children: t("rotDaily")
|
|
5550
|
+
}),
|
|
5551
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
5552
|
+
type: "button",
|
|
5553
|
+
className: `dab-chip${rotation.interval === "weekly" ? " is-active" : ""}`,
|
|
5554
|
+
onClick: () => setRotation({ interval: "weekly" }),
|
|
5555
|
+
children: t("rotWeekly")
|
|
5556
|
+
}),
|
|
5557
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
5558
|
+
type: "button",
|
|
5559
|
+
className: "dab-btn",
|
|
5560
|
+
disabled: rotBusy,
|
|
5561
|
+
onClick: () => {
|
|
5562
|
+
setRotBusy(true);
|
|
5563
|
+
rotateNow().finally(() => setRotBusy(false));
|
|
5564
|
+
},
|
|
5565
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(RefreshIcon, { size: 13 }), t("rotNow")]
|
|
5566
|
+
})
|
|
5567
|
+
]
|
|
5568
|
+
}) : null,
|
|
5569
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
5570
|
+
ref: rotFileRef,
|
|
5571
|
+
type: "file",
|
|
5572
|
+
accept: "image/*",
|
|
5573
|
+
multiple: true,
|
|
5574
|
+
style: { display: "none" },
|
|
5575
|
+
onChange: (e) => {
|
|
5576
|
+
const files = Array.from(e.target.files ?? []);
|
|
5577
|
+
e.target.value = "";
|
|
5578
|
+
if (files.length === 0) return;
|
|
5579
|
+
setRotBusy(true);
|
|
5580
|
+
addRotationItems(files).finally(() => setRotBusy(false));
|
|
5581
|
+
}
|
|
5582
|
+
})
|
|
5583
|
+
]
|
|
5584
|
+
})] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
4531
5585
|
className: "dab-card dab-rise",
|
|
4532
5586
|
style: { "--d": 3 },
|
|
4533
5587
|
children: [
|
|
4534
5588
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4535
5589
|
className: "dab-types",
|
|
4536
|
-
children: typeMeta.map((m) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
5590
|
+
children: typeMeta.map((m, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
4537
5591
|
type: "button",
|
|
4538
5592
|
className: `dab-type${activeGenType === m.type ? " is-active" : ""}`,
|
|
5593
|
+
style: { "--i": i },
|
|
4539
5594
|
onClick: () => setGenType(m.type),
|
|
4540
5595
|
children: [
|
|
4541
5596
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: `dab-type-thumb ${m.thumb}` }),
|
|
@@ -4564,7 +5619,8 @@ window.__ModuleLoader__.load({
|
|
|
4564
5619
|
children: [
|
|
4565
5620
|
"aurora",
|
|
4566
5621
|
"nebula",
|
|
4567
|
-
"noise"
|
|
5622
|
+
"noise",
|
|
5623
|
+
"starfield"
|
|
4568
5624
|
].map((pr) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
4569
5625
|
type: "button",
|
|
4570
5626
|
className: `dab-chip${generatedBg.preset === pr ? " is-active" : ""}`,
|
|
@@ -4583,7 +5639,10 @@ window.__ModuleLoader__.load({
|
|
|
4583
5639
|
children: [
|
|
4584
5640
|
"dots",
|
|
4585
5641
|
"waves",
|
|
4586
|
-
"poly"
|
|
5642
|
+
"poly",
|
|
5643
|
+
"rain",
|
|
5644
|
+
"contour",
|
|
5645
|
+
"meta"
|
|
4587
5646
|
].map((pr) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
4588
5647
|
type: "button",
|
|
4589
5648
|
className: `dab-chip${generatedBg.preset === pr ? " is-active" : ""}`,
|
|
@@ -4673,11 +5732,26 @@ window.__ModuleLoader__.load({
|
|
|
4673
5732
|
onClick: () => setRegenerateOnReload(!regenerateOnReload),
|
|
4674
5733
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "dab-toggle-knob" })
|
|
4675
5734
|
}),
|
|
5735
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
5736
|
+
type: "button",
|
|
5737
|
+
className: "dab-btn",
|
|
5738
|
+
onClick: () => {
|
|
5739
|
+
if (paused) {
|
|
5740
|
+
resumeGeneratedBg();
|
|
5741
|
+
setPaused(false);
|
|
5742
|
+
} else {
|
|
5743
|
+
pauseGeneratedBg();
|
|
5744
|
+
setPaused(true);
|
|
5745
|
+
}
|
|
5746
|
+
},
|
|
5747
|
+
children: [paused ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PlayIcon, { size: 13 }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PauseIcon, { size: 13 }), paused ? t("bgResume") : t("bgPause")]
|
|
5748
|
+
}),
|
|
4676
5749
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
4677
5750
|
type: "button",
|
|
4678
5751
|
className: "dab-btn dab-btn-primary",
|
|
4679
5752
|
onClick: () => {
|
|
4680
5753
|
regenerateBg();
|
|
5754
|
+
setPaused(false);
|
|
4681
5755
|
setSpinTick((x) => x + 1);
|
|
4682
5756
|
},
|
|
4683
5757
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
@@ -4692,7 +5766,7 @@ window.__ModuleLoader__.load({
|
|
|
4692
5766
|
}),
|
|
4693
5767
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
4694
5768
|
className: "dab-card dab-card-hover dab-rise",
|
|
4695
|
-
style: { "--d":
|
|
5769
|
+
style: { "--d": 5 },
|
|
4696
5770
|
children: [
|
|
4697
5771
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(LiveSlider, {
|
|
4698
5772
|
label: t("wpOpacity"),
|
|
@@ -4764,10 +5838,187 @@ window.__ModuleLoader__.load({
|
|
|
4764
5838
|
] });
|
|
4765
5839
|
}
|
|
4766
5840
|
//#endregion
|
|
5841
|
+
//#region src/client/utils/presets.ts
|
|
5842
|
+
const zeroBlurs = {
|
|
5843
|
+
bg: 0,
|
|
5844
|
+
sidebar: 0,
|
|
5845
|
+
card: 0,
|
|
5846
|
+
settings: 0,
|
|
5847
|
+
chat: 0,
|
|
5848
|
+
trajectory: 0,
|
|
5849
|
+
input: 0
|
|
5850
|
+
};
|
|
5851
|
+
const BUILTIN_PRESETS = [
|
|
5852
|
+
{
|
|
5853
|
+
key: "default",
|
|
5854
|
+
appearance: {
|
|
5855
|
+
color: null,
|
|
5856
|
+
opacities: {
|
|
5857
|
+
bg: .85,
|
|
5858
|
+
sidebar: .93,
|
|
5859
|
+
card: 1,
|
|
5860
|
+
input: 1
|
|
5861
|
+
},
|
|
5862
|
+
blurs: { ...zeroBlurs },
|
|
5863
|
+
settingsOpacity: 1,
|
|
5864
|
+
wallpaperOpacity: 1,
|
|
5865
|
+
blur: 0,
|
|
5866
|
+
chatTextOpacity: 0,
|
|
5867
|
+
trajectoryOpacity: 1
|
|
5868
|
+
}
|
|
5869
|
+
},
|
|
5870
|
+
{
|
|
5871
|
+
key: "glass",
|
|
5872
|
+
appearance: {
|
|
5873
|
+
color: [
|
|
5874
|
+
212,
|
|
5875
|
+
.5,
|
|
5876
|
+
.38
|
|
5877
|
+
],
|
|
5878
|
+
opacities: {
|
|
5879
|
+
bg: .62,
|
|
5880
|
+
sidebar: .55,
|
|
5881
|
+
card: .62,
|
|
5882
|
+
input: .58
|
|
5883
|
+
},
|
|
5884
|
+
blurs: {
|
|
5885
|
+
...zeroBlurs,
|
|
5886
|
+
bg: 20,
|
|
5887
|
+
sidebar: 14,
|
|
5888
|
+
card: 12,
|
|
5889
|
+
settings: 20,
|
|
5890
|
+
trajectory: 8,
|
|
5891
|
+
input: 14
|
|
5892
|
+
},
|
|
5893
|
+
settingsOpacity: .88,
|
|
5894
|
+
wallpaperOpacity: 1,
|
|
5895
|
+
blur: 0,
|
|
5896
|
+
chatTextOpacity: 0,
|
|
5897
|
+
trajectoryOpacity: .85
|
|
5898
|
+
}
|
|
5899
|
+
},
|
|
5900
|
+
{
|
|
5901
|
+
key: "minimal",
|
|
5902
|
+
appearance: {
|
|
5903
|
+
color: null,
|
|
5904
|
+
opacities: {
|
|
5905
|
+
bg: .97,
|
|
5906
|
+
sidebar: .97,
|
|
5907
|
+
card: 1,
|
|
5908
|
+
input: 1
|
|
5909
|
+
},
|
|
5910
|
+
blurs: { ...zeroBlurs },
|
|
5911
|
+
settingsOpacity: 1,
|
|
5912
|
+
wallpaperOpacity: 1,
|
|
5913
|
+
blur: 0,
|
|
5914
|
+
chatTextOpacity: 0,
|
|
5915
|
+
trajectoryOpacity: 1
|
|
5916
|
+
}
|
|
5917
|
+
},
|
|
5918
|
+
{
|
|
5919
|
+
key: "midnight",
|
|
5920
|
+
appearance: {
|
|
5921
|
+
color: [
|
|
5922
|
+
262,
|
|
5923
|
+
.45,
|
|
5924
|
+
.16
|
|
5925
|
+
],
|
|
5926
|
+
opacities: {
|
|
5927
|
+
bg: .5,
|
|
5928
|
+
sidebar: .45,
|
|
5929
|
+
card: .55,
|
|
5930
|
+
input: .6
|
|
5931
|
+
},
|
|
5932
|
+
blurs: {
|
|
5933
|
+
...zeroBlurs,
|
|
5934
|
+
bg: 24,
|
|
5935
|
+
sidebar: 18,
|
|
5936
|
+
card: 14,
|
|
5937
|
+
settings: 22,
|
|
5938
|
+
trajectory: 10,
|
|
5939
|
+
input: 16
|
|
5940
|
+
},
|
|
5941
|
+
settingsOpacity: .85,
|
|
5942
|
+
wallpaperOpacity: .92,
|
|
5943
|
+
blur: 2,
|
|
5944
|
+
chatTextOpacity: 0,
|
|
5945
|
+
trajectoryOpacity: .8
|
|
5946
|
+
}
|
|
5947
|
+
},
|
|
5948
|
+
{
|
|
5949
|
+
key: "cyber",
|
|
5950
|
+
appearance: {
|
|
5951
|
+
color: [
|
|
5952
|
+
315,
|
|
5953
|
+
.7,
|
|
5954
|
+
.22
|
|
5955
|
+
],
|
|
5956
|
+
opacities: {
|
|
5957
|
+
bg: .42,
|
|
5958
|
+
sidebar: .5,
|
|
5959
|
+
card: .45,
|
|
5960
|
+
input: .55
|
|
5961
|
+
},
|
|
5962
|
+
blurs: {
|
|
5963
|
+
...zeroBlurs,
|
|
5964
|
+
bg: 16,
|
|
5965
|
+
sidebar: 12,
|
|
5966
|
+
card: 10,
|
|
5967
|
+
settings: 18,
|
|
5968
|
+
trajectory: 6,
|
|
5969
|
+
input: 20
|
|
5970
|
+
},
|
|
5971
|
+
settingsOpacity: .8,
|
|
5972
|
+
wallpaperOpacity: 1,
|
|
5973
|
+
blur: 0,
|
|
5974
|
+
chatTextOpacity: 0,
|
|
5975
|
+
trajectoryOpacity: .78
|
|
5976
|
+
}
|
|
5977
|
+
},
|
|
5978
|
+
{
|
|
5979
|
+
key: "warm",
|
|
5980
|
+
appearance: {
|
|
5981
|
+
color: [
|
|
5982
|
+
28,
|
|
5983
|
+
.6,
|
|
5984
|
+
.72
|
|
5985
|
+
],
|
|
5986
|
+
opacities: {
|
|
5987
|
+
bg: .92,
|
|
5988
|
+
sidebar: .95,
|
|
5989
|
+
card: 1,
|
|
5990
|
+
input: 1
|
|
5991
|
+
},
|
|
5992
|
+
blurs: { ...zeroBlurs },
|
|
5993
|
+
settingsOpacity: 1,
|
|
5994
|
+
wallpaperOpacity: 1,
|
|
5995
|
+
blur: 0,
|
|
5996
|
+
chatTextOpacity: 0,
|
|
5997
|
+
trajectoryOpacity: 1
|
|
5998
|
+
}
|
|
5999
|
+
}
|
|
6000
|
+
];
|
|
6001
|
+
//#endregion
|
|
4767
6002
|
//#region src/client/components/pages/ProfilePage.tsx
|
|
6003
|
+
const hexOf = (color) => color === null ? "" : "#" + hslToRgb(color[0], color[1], color[2]).map((v) => Math.round(v).toString(16).padStart(2, "0")).join("");
|
|
6004
|
+
function ProfileDot({ entry }) {
|
|
6005
|
+
const hex = hexOf(entry.config.color);
|
|
6006
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
6007
|
+
className: "dab-profile-dot",
|
|
6008
|
+
style: hex ? { background: hex } : void 0,
|
|
6009
|
+
title: hex ? hex.toUpperCase() : void 0,
|
|
6010
|
+
children: hex ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CheckIcon, { size: 11 })
|
|
6011
|
+
});
|
|
6012
|
+
}
|
|
4768
6013
|
function ProfilePage({ p, notify }) {
|
|
4769
|
-
const { t, exportTheme, importTheme } = p;
|
|
6014
|
+
const { t, exportTheme, importTheme, saveProfile, applyProfile, deleteProfile, applyPreset, setSchedule, useStore } = p;
|
|
6015
|
+
const profiles = useStore((s) => s.profiles);
|
|
6016
|
+
const schedule = useStore((s) => s.schedule);
|
|
6017
|
+
const activeProfile = useStore((s) => s.activeProfile);
|
|
4770
6018
|
const importRef = (0, react.useRef)(null);
|
|
6019
|
+
const [nameOpen, setNameOpen] = (0, react.useState)(false);
|
|
6020
|
+
const [nameVal, setNameVal] = (0, react.useState)("");
|
|
6021
|
+
const [confirmId, setConfirmId] = (0, react.useState)(null);
|
|
4771
6022
|
const onImport = async (file) => {
|
|
4772
6023
|
try {
|
|
4773
6024
|
const ok = await importTheme(file);
|
|
@@ -4776,6 +6027,39 @@ window.__ModuleLoader__.load({
|
|
|
4776
6027
|
notify(t("importFail"), false);
|
|
4777
6028
|
}
|
|
4778
6029
|
};
|
|
6030
|
+
const onSave = () => {
|
|
6031
|
+
const name = nameVal.trim();
|
|
6032
|
+
if (!name) return;
|
|
6033
|
+
if (saveProfile(name)) {
|
|
6034
|
+
notify(t("profileSaved"), true);
|
|
6035
|
+
setNameVal("");
|
|
6036
|
+
setNameOpen(false);
|
|
6037
|
+
} else notify(t("profileNameRequired"), false);
|
|
6038
|
+
};
|
|
6039
|
+
const onApply = (entry) => {
|
|
6040
|
+
if (applyProfile(entry.id)) notify(t("profileApplied"), true);
|
|
6041
|
+
};
|
|
6042
|
+
const onDelete = (entry) => {
|
|
6043
|
+
if (confirmId !== entry.id) {
|
|
6044
|
+
setConfirmId(entry.id);
|
|
6045
|
+
return;
|
|
6046
|
+
}
|
|
6047
|
+
setConfirmId(null);
|
|
6048
|
+
deleteProfile(entry.id);
|
|
6049
|
+
};
|
|
6050
|
+
const profileOptions = (value, onChange) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
6051
|
+
className: "dab-select",
|
|
6052
|
+
value: value ?? "",
|
|
6053
|
+
onChange: (e) => onChange(e.target.value || null),
|
|
6054
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
6055
|
+
value: "",
|
|
6056
|
+
children: t("profileNone")
|
|
6057
|
+
}), profiles.map((pr) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
6058
|
+
value: pr.id,
|
|
6059
|
+
children: pr.name
|
|
6060
|
+
}, pr.id))]
|
|
6061
|
+
});
|
|
6062
|
+
const patchSchedule = (patch) => setSchedule(patch);
|
|
4779
6063
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
4780
6064
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
|
|
4781
6065
|
className: "dab-head dab-rise",
|
|
@@ -4795,11 +6079,263 @@ window.__ModuleLoader__.load({
|
|
|
4795
6079
|
})
|
|
4796
6080
|
]
|
|
4797
6081
|
}),
|
|
6082
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
6083
|
+
className: "dab-card dab-rise",
|
|
6084
|
+
style: { "--d": 1 },
|
|
6085
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
6086
|
+
className: "dab-swatch-title",
|
|
6087
|
+
children: t("presetGalleryTitle")
|
|
6088
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
6089
|
+
className: "dab-preset-grid",
|
|
6090
|
+
children: BUILTIN_PRESETS.map((ps, i) => {
|
|
6091
|
+
const hex = hexOf(ps.appearance.color);
|
|
6092
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
6093
|
+
type: "button",
|
|
6094
|
+
className: "dab-preset",
|
|
6095
|
+
style: { "--i": i },
|
|
6096
|
+
onClick: () => {
|
|
6097
|
+
applyPreset(ps.appearance);
|
|
6098
|
+
notify(t("presetApplied"));
|
|
6099
|
+
},
|
|
6100
|
+
children: [
|
|
6101
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
6102
|
+
className: "dab-preset-dot",
|
|
6103
|
+
style: hex ? { background: hex } : void 0,
|
|
6104
|
+
children: hex ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CheckIcon, { size: 12 })
|
|
6105
|
+
}),
|
|
6106
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
6107
|
+
className: "dab-preset-name",
|
|
6108
|
+
children: t(`presetName_${ps.key}`)
|
|
6109
|
+
}),
|
|
6110
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
6111
|
+
className: "dab-preset-desc",
|
|
6112
|
+
children: t(`presetDesc_${ps.key}`)
|
|
6113
|
+
})
|
|
6114
|
+
]
|
|
6115
|
+
}, ps.key);
|
|
6116
|
+
})
|
|
6117
|
+
})]
|
|
6118
|
+
}),
|
|
6119
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
6120
|
+
className: "dab-card dab-rise",
|
|
6121
|
+
style: { "--d": 2 },
|
|
6122
|
+
children: [
|
|
6123
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
6124
|
+
className: "dab-row-head",
|
|
6125
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
6126
|
+
className: "dab-swatch-title",
|
|
6127
|
+
style: { marginBottom: 0 },
|
|
6128
|
+
children: t("profilesTitle")
|
|
6129
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
6130
|
+
type: "button",
|
|
6131
|
+
className: "dab-btn dab-btn-primary",
|
|
6132
|
+
onClick: () => setNameOpen((o) => !o),
|
|
6133
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(PlusIcon, { size: 14 }), t("profileSave")]
|
|
6134
|
+
})]
|
|
6135
|
+
}),
|
|
6136
|
+
nameOpen ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
6137
|
+
className: "dab-urlrow",
|
|
6138
|
+
children: [
|
|
6139
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
6140
|
+
type: "text",
|
|
6141
|
+
className: "dab-urlinput",
|
|
6142
|
+
value: nameVal,
|
|
6143
|
+
placeholder: t("profileNamePlaceholder"),
|
|
6144
|
+
maxLength: 60,
|
|
6145
|
+
onChange: (e) => setNameVal(e.target.value),
|
|
6146
|
+
onKeyDown: (e) => {
|
|
6147
|
+
if (e.key === "Enter") {
|
|
6148
|
+
e.preventDefault();
|
|
6149
|
+
onSave();
|
|
6150
|
+
}
|
|
6151
|
+
},
|
|
6152
|
+
autoFocus: true
|
|
6153
|
+
}),
|
|
6154
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
6155
|
+
type: "button",
|
|
6156
|
+
className: "dab-btn dab-btn-primary",
|
|
6157
|
+
disabled: !nameVal.trim(),
|
|
6158
|
+
onClick: onSave,
|
|
6159
|
+
children: t("editorCommit")
|
|
6160
|
+
}),
|
|
6161
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
6162
|
+
type: "button",
|
|
6163
|
+
className: "dab-btn",
|
|
6164
|
+
onClick: () => {
|
|
6165
|
+
setNameOpen(false);
|
|
6166
|
+
setNameVal("");
|
|
6167
|
+
},
|
|
6168
|
+
children: t("bgUrlCancel")
|
|
6169
|
+
})
|
|
6170
|
+
]
|
|
6171
|
+
}) : null,
|
|
6172
|
+
profiles.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
6173
|
+
className: "dab-hint",
|
|
6174
|
+
style: { marginTop: 10 },
|
|
6175
|
+
children: t("profilesEmpty")
|
|
6176
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
6177
|
+
className: "dab-profile-list",
|
|
6178
|
+
children: profiles.map((entry, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
6179
|
+
className: `dab-profile-row${activeProfile === entry.id ? " is-active" : ""}`,
|
|
6180
|
+
style: { "--i": i },
|
|
6181
|
+
children: [
|
|
6182
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ProfileDot, { entry }),
|
|
6183
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
6184
|
+
className: "dab-profile-meta",
|
|
6185
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
6186
|
+
className: "dab-profile-name",
|
|
6187
|
+
children: entry.name
|
|
6188
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
6189
|
+
className: "dab-profile-sub",
|
|
6190
|
+
children: new Date(entry.createdAt).toLocaleDateString()
|
|
6191
|
+
})]
|
|
6192
|
+
}),
|
|
6193
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
6194
|
+
type: "button",
|
|
6195
|
+
className: "dab-btn",
|
|
6196
|
+
onClick: () => onApply(entry),
|
|
6197
|
+
children: t("profileApply")
|
|
6198
|
+
}),
|
|
6199
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
6200
|
+
type: "button",
|
|
6201
|
+
className: `dab-btn dab-btn-danger-solid${confirmId === entry.id ? " dab-btn-confirm" : ""}`,
|
|
6202
|
+
title: t("profileDelete"),
|
|
6203
|
+
onClick: () => onDelete(entry),
|
|
6204
|
+
children: confirmId === entry.id ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(CheckIcon, { size: 13 }), t("profileDeleteConfirm")] }) : t("profileDelete")
|
|
6205
|
+
})
|
|
6206
|
+
]
|
|
6207
|
+
}, entry.id))
|
|
6208
|
+
})
|
|
6209
|
+
]
|
|
6210
|
+
}),
|
|
6211
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
6212
|
+
className: "dab-card dab-rise",
|
|
6213
|
+
style: { "--d": 3 },
|
|
6214
|
+
children: [
|
|
6215
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
6216
|
+
className: "dab-row-head",
|
|
6217
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
6218
|
+
className: "dab-swatch-title",
|
|
6219
|
+
style: { marginBottom: 0 },
|
|
6220
|
+
children: t("scheduleTitle")
|
|
6221
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
6222
|
+
type: "button",
|
|
6223
|
+
className: `dab-toggle${schedule.enabled ? " is-on" : ""}`,
|
|
6224
|
+
role: "switch",
|
|
6225
|
+
"aria-checked": schedule.enabled,
|
|
6226
|
+
onClick: () => patchSchedule({ enabled: !schedule.enabled }),
|
|
6227
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "dab-toggle-knob" })
|
|
6228
|
+
})]
|
|
6229
|
+
}),
|
|
6230
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
6231
|
+
className: "dab-hint",
|
|
6232
|
+
style: { marginTop: 8 },
|
|
6233
|
+
children: t("scheduleHint")
|
|
6234
|
+
}),
|
|
6235
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
6236
|
+
className: `dab-schedule-wrap${schedule.enabled ? " is-open" : ""}`,
|
|
6237
|
+
"aria-hidden": !schedule.enabled,
|
|
6238
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
6239
|
+
className: "dab-schedule-clip",
|
|
6240
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
6241
|
+
className: "dab-schedule-grid",
|
|
6242
|
+
children: [
|
|
6243
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
6244
|
+
className: "dab-schedule-cell",
|
|
6245
|
+
style: { "--i": 0 },
|
|
6246
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
6247
|
+
className: "dab-swatch-title",
|
|
6248
|
+
children: t("scheduleMode")
|
|
6249
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
6250
|
+
className: "dab-chip-row",
|
|
6251
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
6252
|
+
type: "button",
|
|
6253
|
+
className: `dab-chip${schedule.mode === "time" ? " is-active" : ""}`,
|
|
6254
|
+
onClick: () => patchSchedule({ mode: "time" }),
|
|
6255
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ClockIcon, { size: 13 }), t("scheduleModeTime")]
|
|
6256
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
6257
|
+
type: "button",
|
|
6258
|
+
className: `dab-chip${schedule.mode === "system" ? " is-active" : ""}`,
|
|
6259
|
+
onClick: () => patchSchedule({ mode: "system" }),
|
|
6260
|
+
children: t("scheduleModeSystem")
|
|
6261
|
+
})]
|
|
6262
|
+
})]
|
|
6263
|
+
}),
|
|
6264
|
+
schedule.mode === "time" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
6265
|
+
className: "dab-schedule-cell",
|
|
6266
|
+
style: { "--i": 1 },
|
|
6267
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
6268
|
+
className: "dab-swatch-title",
|
|
6269
|
+
children: t("scheduleTimes")
|
|
6270
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
6271
|
+
className: "dab-time-row",
|
|
6272
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
6273
|
+
className: "dab-time-label",
|
|
6274
|
+
children: [
|
|
6275
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SunIcon, { size: 13 }),
|
|
6276
|
+
t("scheduleDayStart"),
|
|
6277
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
6278
|
+
type: "time",
|
|
6279
|
+
className: "dab-timeinput",
|
|
6280
|
+
value: schedule.dayStart,
|
|
6281
|
+
onChange: (e) => {
|
|
6282
|
+
if (/^([01]\d|2[0-3]):[0-5]\d$/.test(e.target.value)) patchSchedule({ dayStart: e.target.value });
|
|
6283
|
+
}
|
|
6284
|
+
})
|
|
6285
|
+
]
|
|
6286
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
6287
|
+
className: "dab-time-label",
|
|
6288
|
+
children: [
|
|
6289
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(MoonIcon, { size: 13 }),
|
|
6290
|
+
t("scheduleNightStart"),
|
|
6291
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
6292
|
+
type: "time",
|
|
6293
|
+
className: "dab-timeinput",
|
|
6294
|
+
value: schedule.nightStart,
|
|
6295
|
+
onChange: (e) => {
|
|
6296
|
+
if (/^([01]\d|2[0-3]):[0-5]\d$/.test(e.target.value)) patchSchedule({ nightStart: e.target.value });
|
|
6297
|
+
}
|
|
6298
|
+
})
|
|
6299
|
+
]
|
|
6300
|
+
})]
|
|
6301
|
+
})]
|
|
6302
|
+
}) : null,
|
|
6303
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
6304
|
+
className: "dab-schedule-cell",
|
|
6305
|
+
style: { "--i": 2 },
|
|
6306
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
6307
|
+
className: "dab-swatch-title",
|
|
6308
|
+
children: t("scheduleProfiles")
|
|
6309
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
6310
|
+
className: "dab-time-row",
|
|
6311
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
6312
|
+
className: "dab-time-label",
|
|
6313
|
+
children: [
|
|
6314
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SunIcon, { size: 13 }),
|
|
6315
|
+
t("scheduleDayProfile"),
|
|
6316
|
+
profileOptions(schedule.dayProfile, (id) => patchSchedule({ dayProfile: id }))
|
|
6317
|
+
]
|
|
6318
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
6319
|
+
className: "dab-time-label",
|
|
6320
|
+
children: [
|
|
6321
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(MoonIcon, { size: 13 }),
|
|
6322
|
+
t("scheduleNightProfile"),
|
|
6323
|
+
profileOptions(schedule.nightProfile, (id) => patchSchedule({ nightProfile: id }))
|
|
6324
|
+
]
|
|
6325
|
+
})]
|
|
6326
|
+
})]
|
|
6327
|
+
})
|
|
6328
|
+
]
|
|
6329
|
+
})
|
|
6330
|
+
})
|
|
6331
|
+
})
|
|
6332
|
+
]
|
|
6333
|
+
}),
|
|
4798
6334
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4799
6335
|
className: "dab-profile-grid",
|
|
4800
6336
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
4801
6337
|
className: "dab-card dab-card-hover dab-rise",
|
|
4802
|
-
style: { "--d":
|
|
6338
|
+
style: { "--d": 4 },
|
|
4803
6339
|
children: [
|
|
4804
6340
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4805
6341
|
className: "dab-profile-ico",
|
|
@@ -4825,7 +6361,7 @@ window.__ModuleLoader__.load({
|
|
|
4825
6361
|
]
|
|
4826
6362
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
4827
6363
|
className: "dab-card dab-card-hover dab-rise",
|
|
4828
|
-
style: { "--d":
|
|
6364
|
+
style: { "--d": 5 },
|
|
4829
6365
|
children: [
|
|
4830
6366
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4831
6367
|
className: "dab-profile-ico",
|
|
@@ -4862,7 +6398,7 @@ window.__ModuleLoader__.load({
|
|
|
4862
6398
|
}),
|
|
4863
6399
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("footer", {
|
|
4864
6400
|
className: "dab-footer dab-rise",
|
|
4865
|
-
style: { "--d":
|
|
6401
|
+
style: { "--d": 6 },
|
|
4866
6402
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4867
6403
|
className: "dab-footer-mono",
|
|
4868
6404
|
children: "dsh-any-background"
|
|
@@ -4996,7 +6532,30 @@ window.__ModuleLoader__.load({
|
|
|
4996
6532
|
const registerCustom = (h, s, l) => {
|
|
4997
6533
|
customDispose?.();
|
|
4998
6534
|
try {
|
|
4999
|
-
|
|
6535
|
+
let colorScheme;
|
|
6536
|
+
let tokens;
|
|
6537
|
+
if (rHasColor()) ({colorScheme, tokens} = genTokens(h ?? rColor()[0], s ?? rColor()[1], l ?? rColor()[2], rColorScheme()));
|
|
6538
|
+
else if (rSchemeOverride() !== "auto") {
|
|
6539
|
+
const dark = rScheme() === "dark";
|
|
6540
|
+
({colorScheme, tokens} = genTokens(220, .04, dark ? .14 : .92, dark ? "dark" : "light"));
|
|
6541
|
+
} else {
|
|
6542
|
+
const verdict = rBgDark();
|
|
6543
|
+
if (verdict === null) {
|
|
6544
|
+
customDispose = null;
|
|
6545
|
+
return false;
|
|
6546
|
+
}
|
|
6547
|
+
const snap = ctx.theme.getTheme();
|
|
6548
|
+
const wantScheme = verdict ? "dark" : "light";
|
|
6549
|
+
const source = snap.themes.find((t) => t.id !== CUSTOM_ID && t.colorScheme === wantScheme) ?? snap.themes.find((t) => t.id !== CUSTOM_ID);
|
|
6550
|
+
if (source === void 0) {
|
|
6551
|
+
customDispose = null;
|
|
6552
|
+
return false;
|
|
6553
|
+
}
|
|
6554
|
+
colorScheme = wantScheme;
|
|
6555
|
+
tokens = { ...source.tokens };
|
|
6556
|
+
const font = verdict ? "#fff" : "#000";
|
|
6557
|
+
for (const name of LABEL_TOKENS) tokens[name] = font;
|
|
6558
|
+
}
|
|
5000
6559
|
customDispose = ctx.theme.register({
|
|
5001
6560
|
id: CUSTOM_ID,
|
|
5002
6561
|
colorScheme,
|
|
@@ -5005,16 +6564,27 @@ window.__ModuleLoader__.load({
|
|
|
5005
6564
|
} catch {
|
|
5006
6565
|
customDispose = null;
|
|
5007
6566
|
}
|
|
5008
|
-
|
|
6567
|
+
const present = ctx.theme.getTheme().themes.some((t) => t.id === CUSTOM_ID);
|
|
6568
|
+
if (present) ctx.theme.setTheme(CUSTOM_ID);
|
|
6569
|
+
return present;
|
|
5009
6570
|
};
|
|
5010
6571
|
if (rHasColor()) registerCustom(initH, initS, initL);
|
|
6572
|
+
onVerdictApplied(() => {
|
|
6573
|
+
if (!rHasColor()) registerCustom();
|
|
6574
|
+
});
|
|
6575
|
+
onColorAdopted((hsl) => {
|
|
6576
|
+
registerCustom(hsl[0], hsl[1], hsl[2]);
|
|
6577
|
+
saveConfig();
|
|
6578
|
+
colorRev++;
|
|
6579
|
+
bound?.syncColor(hslToHsv(hsl[0], hsl[1], hsl[2]), colorRev);
|
|
6580
|
+
});
|
|
5011
6581
|
ctx.effect(() => () => {
|
|
5012
6582
|
customDispose?.();
|
|
5013
6583
|
if (colorTimerRef.current !== null) window.clearTimeout(colorTimerRef.current);
|
|
5014
6584
|
}, "dsh-any-background: skin dispose");
|
|
5015
6585
|
const styleEl = document.createElement("style");
|
|
5016
6586
|
styleEl.dataset.plugin = "dsh-any-background";
|
|
5017
|
-
styleEl.textContent = `body[data-ds-dark-theme="dsh-any-background"]::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}${TRAJECTORY_STYLE_RULE}${INPUT_BLUR_RULE}[data-composer-card] textarea::placeholder,[data-composer-card] input::placeholder,[data-composer-card] [contenteditable]::placeholder,[data-cordis-panel] input::placeholder,[data-cordis-panel] textarea::placeholder,.dab-input::placeholder,.dab-input textarea::placeholder,.dab-input input::placeholder{color:var(--dsh-any-placeholder,var(--dsw-alias-label-caption,#8a8f98))!important;font-style:italic;opacity:.85}`;
|
|
6587
|
+
styleEl.textContent = `body[data-ds-dark-theme="dsh-any-background"]::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}${TRAJECTORY_STYLE_RULE}${INPUT_BLUR_RULE}${MOBILE_HEADER_RULE}[data-composer-card] textarea::placeholder,[data-composer-card] input::placeholder,[data-composer-card] [contenteditable]::placeholder,[data-cordis-panel] input::placeholder,[data-cordis-panel] textarea::placeholder,.dab-input::placeholder,.dab-input textarea::placeholder,.dab-input input::placeholder{color:var(--dsh-any-placeholder,var(--dsw-alias-label-caption,#8a8f98))!important;font-style:italic;opacity:.85}`;
|
|
5018
6588
|
document.head.appendChild(styleEl);
|
|
5019
6589
|
ctx.effect(() => () => {
|
|
5020
6590
|
styleEl?.parentNode?.removeChild(styleEl);
|
|
@@ -5034,7 +6604,16 @@ window.__ModuleLoader__.load({
|
|
|
5034
6604
|
backgroundType: cfg.backgroundType,
|
|
5035
6605
|
generatedBg: cfg.generatedBg,
|
|
5036
6606
|
bgRev: -1,
|
|
5037
|
-
regenerateOnReload: cfg.regenerateOnReload
|
|
6607
|
+
regenerateOnReload: cfg.regenerateOnReload,
|
|
6608
|
+
profiles: [],
|
|
6609
|
+
rotation: {
|
|
6610
|
+
...DEFAULT_CONFIG.rotation,
|
|
6611
|
+
items: []
|
|
6612
|
+
},
|
|
6613
|
+
schedule: { ...DEFAULT_CONFIG.schedule },
|
|
6614
|
+
schemeOverride: "auto",
|
|
6615
|
+
activeProfile: null,
|
|
6616
|
+
metaRev: -1
|
|
5038
6617
|
}),
|
|
5039
6618
|
actions: {
|
|
5040
6619
|
syncBg: (d, url, r, bgType, genBg, bgr, reload) => {
|
|
@@ -5054,6 +6633,16 @@ window.__ModuleLoader__.load({
|
|
|
5054
6633
|
d.color = hsv;
|
|
5055
6634
|
d.colorRev = r;
|
|
5056
6635
|
}
|
|
6636
|
+
},
|
|
6637
|
+
syncMeta: (d, profiles, rotation, schedule, schemeOverride, activeProfile, r) => {
|
|
6638
|
+
if (r > d.metaRev) {
|
|
6639
|
+
d.profiles = profiles;
|
|
6640
|
+
d.rotation = rotation;
|
|
6641
|
+
d.schedule = schedule;
|
|
6642
|
+
d.schemeOverride = schemeOverride;
|
|
6643
|
+
d.activeProfile = activeProfile;
|
|
6644
|
+
d.metaRev = r;
|
|
6645
|
+
}
|
|
5057
6646
|
}
|
|
5058
6647
|
}
|
|
5059
6648
|
});
|
|
@@ -5064,14 +6653,145 @@ window.__ModuleLoader__.load({
|
|
|
5064
6653
|
bound?.syncBg(rWp(), rev, cfg.backgroundType, cfg.generatedBg, bgRev, cfg.regenerateOnReload);
|
|
5065
6654
|
};
|
|
5066
6655
|
onGeneratedSnapshot(syncBg);
|
|
6656
|
+
let metaRev = 0;
|
|
6657
|
+
const syncMetaNow = () => {
|
|
6658
|
+
metaRev++;
|
|
6659
|
+
bound?.syncMeta(rProfiles(), rRotation(), rSchedule(), rSchemeOverride(), cfg.activeProfile, metaRev);
|
|
6660
|
+
};
|
|
6661
|
+
/** Apply an appearance snapshot (profile or built-in preset) to the whole
|
|
6662
|
+
* interface: re-register the skin, re-emit tokens, persist. */
|
|
6663
|
+
const applyAppearanceLive = (ap) => {
|
|
6664
|
+
applyAppearance(ap);
|
|
6665
|
+
if (rHasColor()) {
|
|
6666
|
+
const [h, s, l] = rColor();
|
|
6667
|
+
registerCustom(h, s, l);
|
|
6668
|
+
} else if (!registerCustom()) {
|
|
6669
|
+
customDispose?.();
|
|
6670
|
+
customDispose = null;
|
|
6671
|
+
clearThemeTokens();
|
|
6672
|
+
const snap = ctx.theme.getTheme();
|
|
6673
|
+
const fallback = snap.themes.find((t) => t.id !== CUSTOM_ID);
|
|
6674
|
+
if (fallback !== void 0 && snap.preference === CUSTOM_ID) ctx.theme.setTheme(fallback.id);
|
|
6675
|
+
}
|
|
6676
|
+
applyWp();
|
|
6677
|
+
};
|
|
6678
|
+
const applyProfileById = (id) => {
|
|
6679
|
+
const entry = rProfiles().find((p) => p.id === id);
|
|
6680
|
+
if (entry === void 0) return false;
|
|
6681
|
+
applyAppearanceLive(entry.config);
|
|
6682
|
+
cfg.activeProfile = id;
|
|
6683
|
+
applyWp();
|
|
6684
|
+
persistConfig();
|
|
6685
|
+
syncMetaNow();
|
|
6686
|
+
return true;
|
|
6687
|
+
};
|
|
6688
|
+
/** Extract the theme color once from a freshly activated wallpaper and run
|
|
6689
|
+
* the full adaptation (host skin + editor wheel sync); the caller persists. */
|
|
6690
|
+
const adoptWallpaperColor = async (dataUrl) => {
|
|
6691
|
+
const hsl = await extractWallpaperColor(dataUrl, rBgState());
|
|
6692
|
+
if (!hsl || rWp() !== dataUrl) return;
|
|
6693
|
+
cfg.color = hsl;
|
|
6694
|
+
registerCustom(hsl[0], hsl[1], hsl[2]);
|
|
6695
|
+
colorRev++;
|
|
6696
|
+
bound?.syncColor(hslToHsv(hsl[0], hsl[1], hsl[2]), colorRev);
|
|
6697
|
+
};
|
|
6698
|
+
/** Activate a rotation item: the server copies its bytes into the wallpaper
|
|
6699
|
+
* slot; the client applies the returned serve URL through the normal image
|
|
6700
|
+
* path, then re-extracts the theme color from the new picture so the
|
|
6701
|
+
* palette follows the rotation. */
|
|
6702
|
+
const applyRotationIndex = async (idx, auto) => {
|
|
6703
|
+
const rot = rRotation();
|
|
6704
|
+
if (idx < 0 || idx >= rot.items.length) return false;
|
|
6705
|
+
const r = await rotationActivate(idx);
|
|
6706
|
+
if (!r.ok || !r.wallpaperUrl) {
|
|
6707
|
+
console.warn("dsh-any-background: rotation activate failed", r.error);
|
|
6708
|
+
return false;
|
|
6709
|
+
}
|
|
6710
|
+
cfg.backgroundType = "image";
|
|
6711
|
+
setBgDark(null);
|
|
6712
|
+
setWpImageUrl(r.wallpaperUrl);
|
|
6713
|
+
setWpUrl(rWpImage());
|
|
6714
|
+
setBgState({ ...DEFAULT_CONFIG.bgState });
|
|
6715
|
+
cfg.rotation = {
|
|
6716
|
+
...rot,
|
|
6717
|
+
current: idx,
|
|
6718
|
+
lastRotate: auto || rot.lastRotate === null ? (/* @__PURE__ */ new Date()).toISOString() : rot.lastRotate
|
|
6719
|
+
};
|
|
6720
|
+
if (cfg.backgroundType === "image") await adoptWallpaperColor(rWp());
|
|
6721
|
+
applyThemeColor();
|
|
6722
|
+
syncBg();
|
|
6723
|
+
saveConfig();
|
|
6724
|
+
syncMetaNow();
|
|
6725
|
+
return true;
|
|
6726
|
+
};
|
|
6727
|
+
const isoWeekKey = (d) => {
|
|
6728
|
+
const t = new Date(Date.UTC(d.getFullYear(), d.getMonth(), d.getDate()));
|
|
6729
|
+
const day = t.getUTCDay() || 7;
|
|
6730
|
+
t.setUTCDate(t.getUTCDate() + 4 - day);
|
|
6731
|
+
const yearStart = new Date(Date.UTC(t.getUTCFullYear(), 0, 1));
|
|
6732
|
+
const week = Math.ceil(((t.getTime() - yearStart.getTime()) / 864e5 + 1) / 7);
|
|
6733
|
+
return `${t.getUTCFullYear()}-W${week}`;
|
|
6734
|
+
};
|
|
6735
|
+
const rotationDue = () => {
|
|
6736
|
+
const rot = rRotation();
|
|
6737
|
+
if (!rot.enabled || rot.items.length === 0) return false;
|
|
6738
|
+
if (rot.interval === "reload") return true;
|
|
6739
|
+
const last = rot.lastRotate !== null ? new Date(rot.lastRotate) : null;
|
|
6740
|
+
if (last === null || isNaN(last.getTime())) return true;
|
|
6741
|
+
const now = /* @__PURE__ */ new Date();
|
|
6742
|
+
if (rot.interval === "daily") return last.toDateString() !== now.toDateString();
|
|
6743
|
+
return isoWeekKey(last) !== isoWeekKey(now);
|
|
6744
|
+
};
|
|
6745
|
+
const pickNextRotationIndex = () => {
|
|
6746
|
+
const rot = rRotation();
|
|
6747
|
+
const n = rot.items.length;
|
|
6748
|
+
if (n === 0) return -1;
|
|
6749
|
+
if (rot.mode === "shuffle" && n > 1) {
|
|
6750
|
+
let idx = rot.current;
|
|
6751
|
+
while (idx === rot.current) idx = Math.floor(Math.random() * n);
|
|
6752
|
+
return idx;
|
|
6753
|
+
}
|
|
6754
|
+
return (rot.current + 1) % n;
|
|
6755
|
+
};
|
|
6756
|
+
const maybeRotate = async () => {
|
|
6757
|
+
if (!rotationDue()) return;
|
|
6758
|
+
await applyRotationIndex(pickNextRotationIndex(), true);
|
|
6759
|
+
};
|
|
6760
|
+
const parseHHMM = (s) => {
|
|
6761
|
+
const m = /^([01]\d|2[0-3]):([0-5]\d)$/.exec(s);
|
|
6762
|
+
return m === null ? -1 : Number(m[1]) * 60 + Number(m[2]);
|
|
6763
|
+
};
|
|
6764
|
+
const isNightNow = (sc) => {
|
|
6765
|
+
const now = /* @__PURE__ */ new Date();
|
|
6766
|
+
const cur = now.getHours() * 60 + now.getMinutes();
|
|
6767
|
+
const day = parseHHMM(sc.dayStart);
|
|
6768
|
+
const night = parseHHMM(sc.nightStart);
|
|
6769
|
+
if (day < 0 || night < 0) return false;
|
|
6770
|
+
if (day <= night) return cur >= night || cur < day;
|
|
6771
|
+
return cur >= night && cur < day;
|
|
6772
|
+
};
|
|
6773
|
+
const scheduleTick = () => {
|
|
6774
|
+
const sc = rSchedule();
|
|
6775
|
+
if (!sc.enabled) return;
|
|
6776
|
+
const want = (sc.mode === "system" ? window.matchMedia?.("(prefers-color-scheme: dark)").matches ?? false : isNightNow(sc)) ? sc.nightProfile : sc.dayProfile;
|
|
6777
|
+
if (!want || want === cfg.activeProfile) return;
|
|
6778
|
+
applyProfileById(want);
|
|
6779
|
+
};
|
|
5067
6780
|
applyWp();
|
|
5068
6781
|
syncBg();
|
|
5069
6782
|
watchParts();
|
|
5070
|
-
loadPersisted().then(() => {
|
|
6783
|
+
loadPersisted().then(async (serverRotated) => {
|
|
6784
|
+
syncMetaNow();
|
|
5071
6785
|
if (rHasColor()) {
|
|
5072
6786
|
const [h, s, l] = rColor();
|
|
5073
6787
|
registerCustom(h, s, l);
|
|
5074
6788
|
}
|
|
6789
|
+
if (serverRotated) {
|
|
6790
|
+
if (cfg.backgroundType === "image" && rWp()) {
|
|
6791
|
+
await adoptWallpaperColor(rWp());
|
|
6792
|
+
saveConfig();
|
|
6793
|
+
}
|
|
6794
|
+
} else await maybeRotate();
|
|
5075
6795
|
if (cfg.backgroundType === "video") {
|
|
5076
6796
|
const v = rWpVideo();
|
|
5077
6797
|
if (v) {
|
|
@@ -5093,16 +6813,24 @@ window.__ModuleLoader__.load({
|
|
|
5093
6813
|
persistConfig();
|
|
5094
6814
|
} else applyThemeColor();
|
|
5095
6815
|
syncBg();
|
|
6816
|
+
scheduleTick();
|
|
5096
6817
|
if (rHasColor()) {
|
|
5097
6818
|
colorRev++;
|
|
5098
6819
|
bound?.syncColor(hslToHsv(...rColor()), colorRev);
|
|
5099
6820
|
}
|
|
5100
6821
|
});
|
|
6822
|
+
const schemeMq = window.matchMedia?.("(prefers-color-scheme: dark)");
|
|
6823
|
+
const scheduleTimer = window.setInterval(scheduleTick, 3e4);
|
|
6824
|
+
schemeMq?.addEventListener?.("change", scheduleTick);
|
|
6825
|
+
ctx.effect(() => () => {
|
|
6826
|
+
window.clearInterval(scheduleTimer);
|
|
6827
|
+
schemeMq?.removeEventListener?.("change", scheduleTick);
|
|
6828
|
+
}, "dsh-any-background: schedule timer");
|
|
5101
6829
|
ctx.effect(() => () => {
|
|
5102
6830
|
teardownWp();
|
|
5103
6831
|
}, "dsh-any-background: wp cleanup");
|
|
5104
6832
|
ctx.effect(() => ctx.on("theme/change", () => {
|
|
5105
|
-
if (rHasColor()) {
|
|
6833
|
+
if (rHasColor() || rBgDark() !== null || rSchemeOverride() !== "auto") {
|
|
5106
6834
|
const snapshot = ctx.theme.getTheme();
|
|
5107
6835
|
if (snapshot.preference !== CUSTOM_ID && snapshot.themes.some((t) => t.id === CUSTOM_ID)) ctx.theme.setTheme(CUSTOM_ID);
|
|
5108
6836
|
}
|
|
@@ -5135,6 +6863,7 @@ window.__ModuleLoader__.load({
|
|
|
5135
6863
|
const sectionInject = (actions) => {
|
|
5136
6864
|
bound = actions;
|
|
5137
6865
|
syncBg();
|
|
6866
|
+
syncMetaNow();
|
|
5138
6867
|
const playVideoFromBlob = (blob, mime) => {
|
|
5139
6868
|
const localUrl = URL.createObjectURL(blob);
|
|
5140
6869
|
cfg.backgroundType = "video";
|
|
@@ -5182,16 +6911,16 @@ window.__ModuleLoader__.load({
|
|
|
5182
6911
|
nl
|
|
5183
6912
|
], colorRev);
|
|
5184
6913
|
},
|
|
5185
|
-
|
|
6914
|
+
setWpFromServer: (u) => {
|
|
5186
6915
|
cfg.backgroundType = "image";
|
|
5187
6916
|
setBgDark(null);
|
|
5188
6917
|
setWpImageUrl(u);
|
|
5189
|
-
setWpUrl(
|
|
6918
|
+
setWpUrl(rWpImage());
|
|
5190
6919
|
setBgState({ ...DEFAULT_CONFIG.bgState });
|
|
5191
|
-
persistWallpaper(u);
|
|
5192
6920
|
if (u === null) {
|
|
5193
6921
|
setWpVideoUrl(null, null);
|
|
5194
6922
|
persistVideo(null);
|
|
6923
|
+
persistWallpaper(null);
|
|
5195
6924
|
}
|
|
5196
6925
|
applyThemeColor();
|
|
5197
6926
|
syncBg();
|
|
@@ -5296,23 +7025,29 @@ window.__ModuleLoader__.load({
|
|
|
5296
7025
|
if (cfg.backgroundType === "video") {
|
|
5297
7026
|
const vurl = rWpVideo();
|
|
5298
7027
|
if (vurl) try {
|
|
5299
|
-
|
|
5300
|
-
videoPayload = await new Promise((resolve, reject) => {
|
|
5301
|
-
const fr = new FileReader();
|
|
5302
|
-
fr.onload = () => resolve(fr.result);
|
|
5303
|
-
fr.onerror = () => reject(fr.error);
|
|
5304
|
-
fr.readAsDataURL(blob);
|
|
5305
|
-
});
|
|
7028
|
+
videoPayload = await blobToDataUrl(await fetch(vurl).then((r) => r.blob()));
|
|
5306
7029
|
if (videoPayload && !/^data:video\//.test(videoPayload)) videoPayload = videoPayload.replace(/^data:[^;,]*/, `data:${cfg.videoMime ?? "video/mp4"}`);
|
|
5307
7030
|
} catch {
|
|
5308
7031
|
videoPayload = null;
|
|
5309
7032
|
}
|
|
5310
7033
|
}
|
|
7034
|
+
let wallpaperPayload = null;
|
|
7035
|
+
if (cfg.backgroundType === "image") {
|
|
7036
|
+
const wurl = rWp();
|
|
7037
|
+
if (wurl) {
|
|
7038
|
+
if (wurl.startsWith("data:")) wallpaperPayload = wurl;
|
|
7039
|
+
else try {
|
|
7040
|
+
wallpaperPayload = await blobToDataUrl(await fetch(wurl).then((r) => r.blob()));
|
|
7041
|
+
} catch {
|
|
7042
|
+
wallpaperPayload = null;
|
|
7043
|
+
}
|
|
7044
|
+
}
|
|
7045
|
+
}
|
|
5311
7046
|
const payload = {
|
|
5312
7047
|
version: 2,
|
|
5313
7048
|
exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5314
7049
|
config: cfg,
|
|
5315
|
-
wallpaper:
|
|
7050
|
+
wallpaper: wallpaperPayload,
|
|
5316
7051
|
video: videoPayload
|
|
5317
7052
|
};
|
|
5318
7053
|
const blob = new Blob([JSON.stringify(payload, null, 2)], { type: "application/json" });
|
|
@@ -5388,6 +7123,134 @@ window.__ModuleLoader__.load({
|
|
|
5388
7123
|
} catch {
|
|
5389
7124
|
return false;
|
|
5390
7125
|
}
|
|
7126
|
+
},
|
|
7127
|
+
saveProfile: (name) => {
|
|
7128
|
+
const trimmed = name.trim();
|
|
7129
|
+
if (!trimmed) return false;
|
|
7130
|
+
const profiles = [...rProfiles()];
|
|
7131
|
+
if (profiles.length >= 20) profiles.shift();
|
|
7132
|
+
const id = `p-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
7133
|
+
profiles.push({
|
|
7134
|
+
id,
|
|
7135
|
+
name: trimmed.slice(0, 60),
|
|
7136
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7137
|
+
config: currentAppearance()
|
|
7138
|
+
});
|
|
7139
|
+
cfg.profiles = profiles;
|
|
7140
|
+
cfg.activeProfile = id;
|
|
7141
|
+
persistConfig();
|
|
7142
|
+
syncMetaNow();
|
|
7143
|
+
return true;
|
|
7144
|
+
},
|
|
7145
|
+
applyProfile: (id) => applyProfileById(id),
|
|
7146
|
+
deleteProfile: (id) => {
|
|
7147
|
+
const before = rProfiles();
|
|
7148
|
+
if (!before.some((p) => p.id === id)) return false;
|
|
7149
|
+
cfg.profiles = before.filter((p) => p.id !== id);
|
|
7150
|
+
if (cfg.activeProfile === id) cfg.activeProfile = null;
|
|
7151
|
+
const sc = rSchedule();
|
|
7152
|
+
if (sc.dayProfile === id || sc.nightProfile === id) cfg.schedule = {
|
|
7153
|
+
...sc,
|
|
7154
|
+
dayProfile: sc.dayProfile === id ? null : sc.dayProfile,
|
|
7155
|
+
nightProfile: sc.nightProfile === id ? null : sc.nightProfile
|
|
7156
|
+
};
|
|
7157
|
+
persistConfig();
|
|
7158
|
+
syncMetaNow();
|
|
7159
|
+
return true;
|
|
7160
|
+
},
|
|
7161
|
+
applyPreset: (appearance) => {
|
|
7162
|
+
applyAppearanceLive(appearance);
|
|
7163
|
+
cfg.activeProfile = null;
|
|
7164
|
+
applyWp();
|
|
7165
|
+
persistConfig();
|
|
7166
|
+
syncMetaNow();
|
|
7167
|
+
},
|
|
7168
|
+
setSchemeOverride: (v) => {
|
|
7169
|
+
cfg.schemeOverride = v;
|
|
7170
|
+
registerCustom();
|
|
7171
|
+
applyWp();
|
|
7172
|
+
saveConfig();
|
|
7173
|
+
syncMetaNow();
|
|
7174
|
+
},
|
|
7175
|
+
setSchedule: (patch) => {
|
|
7176
|
+
cfg.schedule = {
|
|
7177
|
+
...rSchedule(),
|
|
7178
|
+
...patch
|
|
7179
|
+
};
|
|
7180
|
+
if (patch.enabled === true) scheduleTick();
|
|
7181
|
+
persistConfig();
|
|
7182
|
+
syncMetaNow();
|
|
7183
|
+
},
|
|
7184
|
+
setRotation: (patch) => {
|
|
7185
|
+
cfg.rotation = {
|
|
7186
|
+
...rRotation(),
|
|
7187
|
+
...patch
|
|
7188
|
+
};
|
|
7189
|
+
if (patch.enabled === true) maybeRotate();
|
|
7190
|
+
persistConfig();
|
|
7191
|
+
syncMetaNow();
|
|
7192
|
+
},
|
|
7193
|
+
addRotationItems: async (files) => {
|
|
7194
|
+
let added = false;
|
|
7195
|
+
for (const f of files) {
|
|
7196
|
+
if (!f.type.startsWith("image/")) continue;
|
|
7197
|
+
const dataUrl = await readImgAsync(f);
|
|
7198
|
+
if (!dataUrl) continue;
|
|
7199
|
+
const r = await rotationAdd(dataUrl, await makeThumb(dataUrl) ?? "");
|
|
7200
|
+
if (r.ok && r.items !== void 0) {
|
|
7201
|
+
cfg.rotation = {
|
|
7202
|
+
...rRotation(),
|
|
7203
|
+
items: r.items
|
|
7204
|
+
};
|
|
7205
|
+
added = true;
|
|
7206
|
+
}
|
|
7207
|
+
}
|
|
7208
|
+
if (added) {
|
|
7209
|
+
persistConfig();
|
|
7210
|
+
syncMetaNow();
|
|
7211
|
+
syncBg();
|
|
7212
|
+
}
|
|
7213
|
+
return added;
|
|
7214
|
+
},
|
|
7215
|
+
removeRotationItem: async (index) => {
|
|
7216
|
+
const r = await rotationRemove(index);
|
|
7217
|
+
if (!r.ok || r.items === void 0) return false;
|
|
7218
|
+
const rot = rRotation();
|
|
7219
|
+
cfg.rotation = {
|
|
7220
|
+
...rot,
|
|
7221
|
+
items: r.items,
|
|
7222
|
+
current: Math.max(0, Math.min(rot.current >= index ? rot.current - 1 : rot.current, Math.max(0, r.items.length - 1)))
|
|
7223
|
+
};
|
|
7224
|
+
persistConfig();
|
|
7225
|
+
syncMetaNow();
|
|
7226
|
+
return true;
|
|
7227
|
+
},
|
|
7228
|
+
rotateNow: async () => {
|
|
7229
|
+
if (rRotation().items.length === 0) return false;
|
|
7230
|
+
return applyRotationIndex(pickNextRotationIndex(), true);
|
|
7231
|
+
},
|
|
7232
|
+
setVideoFromUrl: async (url) => {
|
|
7233
|
+
const r = await setVideoFromUrl(url);
|
|
7234
|
+
if (!r.ok || !r.mime) {
|
|
7235
|
+
console.warn("dsh-any-background: video url fetch failed", r.error);
|
|
7236
|
+
return false;
|
|
7237
|
+
}
|
|
7238
|
+
setBgDark(null);
|
|
7239
|
+
cfg.backgroundType = "video";
|
|
7240
|
+
setWpUrl(null);
|
|
7241
|
+
cfg.videoBgState = { ...DEFAULT_CONFIG.bgState };
|
|
7242
|
+
setWpVideoUrl(VIDEO_SERVE_URL, r.mime);
|
|
7243
|
+
applyWp();
|
|
7244
|
+
saveConfig();
|
|
7245
|
+
syncBg();
|
|
7246
|
+
const applied = rWpVideo();
|
|
7247
|
+
captureVideoSnapshot(applied).then((snap) => {
|
|
7248
|
+
if (rWpVideo() !== applied) return;
|
|
7249
|
+
setWpVideoSnapshot(snap);
|
|
7250
|
+
applyThemeColor();
|
|
7251
|
+
syncBg();
|
|
7252
|
+
});
|
|
7253
|
+
return true;
|
|
5391
7254
|
}
|
|
5392
7255
|
};
|
|
5393
7256
|
};
|
|
@@ -5463,12 +7326,11 @@ window.__ModuleLoader__.load({
|
|
|
5463
7326
|
restoreTimers.forEach((id) => window.clearTimeout(id));
|
|
5464
7327
|
}, "dsh-any-background: boot restore");
|
|
5465
7328
|
const watchdogId = window.setInterval(() => {
|
|
5466
|
-
if (!rHasColor()) return;
|
|
7329
|
+
if (!rHasColor() && rBgDark() === null && rSchemeOverride() === "auto") return;
|
|
5467
7330
|
const snapshot = ctx.theme.getTheme();
|
|
5468
7331
|
let changed = false;
|
|
5469
7332
|
if (!snapshot.themes.some((t) => t.id === CUSTOM_ID)) {
|
|
5470
|
-
|
|
5471
|
-
registerCustom(h, s, l);
|
|
7333
|
+
registerCustom();
|
|
5472
7334
|
changed = true;
|
|
5473
7335
|
} else if (snapshot.preference !== CUSTOM_ID) {
|
|
5474
7336
|
ctx.theme.setTheme(CUSTOM_ID);
|