dsh-any-background 0.2.5 → 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 +15 -3
- package/README.zh.md +15 -3
- package/lib/client.js +812 -382
- package/lib/client.js.map +1 -1
- package/lib/index.js +347 -20
- package/package.json +133 -121
package/lib/client.js
CHANGED
|
@@ -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: "加载中…",
|
|
@@ -122,6 +122,13 @@ window.__ModuleLoader__.load({
|
|
|
122
122
|
presetDots: "点阵",
|
|
123
123
|
presetWaves: "波浪",
|
|
124
124
|
presetPoly: "低多边形",
|
|
125
|
+
presetStarfield: "星空",
|
|
126
|
+
presetRain: "雨滴",
|
|
127
|
+
presetContour: "等高线",
|
|
128
|
+
presetMeta: "流体光斑",
|
|
129
|
+
bgPause: "暂停",
|
|
130
|
+
bgResume: "播放",
|
|
131
|
+
bgUrlVideoFail: "获取视频失败,请确认链接可直接下载视频文件",
|
|
125
132
|
schemeTitle: "界面明暗",
|
|
126
133
|
schemeLight: "亮色",
|
|
127
134
|
schemeDark: "暗色",
|
|
@@ -204,7 +211,7 @@ window.__ModuleLoader__.load({
|
|
|
204
211
|
bgChoose: "Choose image or video",
|
|
205
212
|
bgRemove: "Remove background",
|
|
206
213
|
bgFromUrl: "From URL",
|
|
207
|
-
bgUrlPlaceholder: "Paste an image URL https://…",
|
|
214
|
+
bgUrlPlaceholder: "Paste an image or video URL https://…",
|
|
208
215
|
bgUrlApply: "Apply",
|
|
209
216
|
bgUrlCancel: "Cancel",
|
|
210
217
|
bgUrlApplying: "Loading…",
|
|
@@ -278,6 +285,13 @@ window.__ModuleLoader__.load({
|
|
|
278
285
|
presetDots: "Dots",
|
|
279
286
|
presetWaves: "Waves",
|
|
280
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",
|
|
281
295
|
schemeTitle: "Interface scheme",
|
|
282
296
|
schemeLight: "Light",
|
|
283
297
|
schemeDark: "Dark",
|
|
@@ -408,12 +422,19 @@ window.__ModuleLoader__.load({
|
|
|
408
422
|
let wpVideoSnapshot = null;
|
|
409
423
|
/** Local blob URL backing an in-session video; revoked when replaced or cleared. */
|
|
410
424
|
let wpVideoObjectUrl = null;
|
|
411
|
-
function setWpImageUrl(url) {
|
|
412
|
-
wpImageUrl = url;
|
|
413
|
-
}
|
|
414
425
|
function setWpUrl(url) {
|
|
415
426
|
wpUrl = url;
|
|
416
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
|
+
}
|
|
417
438
|
let videoRev = 0;
|
|
418
439
|
function setWpVideoUrl(url, mime) {
|
|
419
440
|
if (url === null) {
|
|
@@ -748,7 +769,8 @@ window.__ModuleLoader__.load({
|
|
|
748
769
|
preset: [
|
|
749
770
|
"aurora",
|
|
750
771
|
"nebula",
|
|
751
|
-
"noise"
|
|
772
|
+
"noise",
|
|
773
|
+
"starfield"
|
|
752
774
|
].includes(p.preset) ? p.preset : "aurora",
|
|
753
775
|
speed: cl(p.speed, 0, 2, .3),
|
|
754
776
|
scale: cl(p.scale, .3, 3, 1),
|
|
@@ -759,7 +781,10 @@ window.__ModuleLoader__.load({
|
|
|
759
781
|
preset: [
|
|
760
782
|
"dots",
|
|
761
783
|
"waves",
|
|
762
|
-
"poly"
|
|
784
|
+
"poly",
|
|
785
|
+
"rain",
|
|
786
|
+
"contour",
|
|
787
|
+
"meta"
|
|
763
788
|
].includes(p.preset) ? p.preset : "dots",
|
|
764
789
|
density: cl(p.density, 0, 1, .5),
|
|
765
790
|
scale: cl(p.scale, .3, 3, 1),
|
|
@@ -771,6 +796,10 @@ window.__ModuleLoader__.load({
|
|
|
771
796
|
const RPC_CHANNEL = "/dsh-any-background";
|
|
772
797
|
/** Same-origin serve URL of the persisted video (enough for <video src>/fetch). */
|
|
773
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";
|
|
774
803
|
/** HTTP route new videos are POSTed to as raw bytes (see uploadVideo). */
|
|
775
804
|
const VIDEO_UPLOAD_URL = "/dsh-any-background/video/upload";
|
|
776
805
|
const RPC_NS = "dshAnyBackground";
|
|
@@ -810,19 +839,20 @@ window.__ModuleLoader__.load({
|
|
|
810
839
|
function persistConfig() {
|
|
811
840
|
rpcCall("writeConfig", { config: cfg });
|
|
812
841
|
}
|
|
813
|
-
/** Load the persisted theme (config + wallpaper + video URL) from the node
|
|
814
|
-
*
|
|
815
|
-
*
|
|
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. */
|
|
816
846
|
async function loadPersisted() {
|
|
817
847
|
const data = await rpcCall("read", {});
|
|
818
848
|
if (data && typeof data === "object") {
|
|
819
849
|
const d = data;
|
|
820
850
|
if (d.config) adoptConfig(d.config);
|
|
821
|
-
if (typeof d.
|
|
822
|
-
else if (d.
|
|
851
|
+
if (typeof d.wallpaperUrl === "string") setWpImageUrl(d.wallpaperUrl);
|
|
852
|
+
else if (d.wallpaperUrl === null) setWpImageUrl(null);
|
|
823
853
|
if (typeof d.videoUrl === "string") setWpVideoUrl(d.videoUrl, cfg.videoMime);
|
|
824
854
|
else if (d.videoUrl === null) setWpVideoUrl(null, null);
|
|
825
|
-
if (cfg.backgroundType === "image") setWpUrl(
|
|
855
|
+
if (cfg.backgroundType === "image") setWpUrl(rWpImage());
|
|
826
856
|
return d.rotated === true;
|
|
827
857
|
}
|
|
828
858
|
return false;
|
|
@@ -837,7 +867,7 @@ window.__ModuleLoader__.load({
|
|
|
837
867
|
return await rpcCall("setVideo", { dataUrl }) === true;
|
|
838
868
|
}
|
|
839
869
|
/** Download a wallpaper from a network URL and persist it into the local slot
|
|
840
|
-
* (the host replaces wallpaper.jpg). Returns the freshly stored
|
|
870
|
+
* (the host replaces wallpaper.jpg). Returns the freshly stored serve URL on
|
|
841
871
|
* success, or the host's failure message. */
|
|
842
872
|
async function setWallpaperFromUrl(url) {
|
|
843
873
|
if (!rpcCallFn) return {
|
|
@@ -864,7 +894,40 @@ window.__ModuleLoader__.load({
|
|
|
864
894
|
const v = res.value;
|
|
865
895
|
return v?.ok === true ? {
|
|
866
896
|
ok: true,
|
|
867
|
-
|
|
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"
|
|
868
931
|
} : {
|
|
869
932
|
ok: false,
|
|
870
933
|
error: v?.error ?? "failed"
|
|
@@ -884,6 +947,20 @@ window.__ModuleLoader__.load({
|
|
|
884
947
|
return false;
|
|
885
948
|
}
|
|
886
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
|
+
}
|
|
887
964
|
/** Add an image (data URL + small thumbnail) to the server-side rotation pool. */
|
|
888
965
|
async function rotationAdd(dataUrl, thumb) {
|
|
889
966
|
if (!rpcCallFn) return {
|
|
@@ -934,7 +1011,7 @@ window.__ModuleLoader__.load({
|
|
|
934
1011
|
}
|
|
935
1012
|
}
|
|
936
1013
|
/** Activate a rotation item: the server copies its bytes into the wallpaper
|
|
937
|
-
* slot and returns the
|
|
1014
|
+
* slot and returns the serve URL for immediate display. */
|
|
938
1015
|
async function rotationActivate(index) {
|
|
939
1016
|
if (!rpcCallFn) return {
|
|
940
1017
|
ok: false,
|
|
@@ -958,6 +1035,88 @@ window.__ModuleLoader__.load({
|
|
|
958
1035
|
}
|
|
959
1036
|
}
|
|
960
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
|
+
}
|
|
1119
|
+
//#endregion
|
|
961
1120
|
//#region src/client/utils/color.ts
|
|
962
1121
|
function hsvToHsl(h, s, v) {
|
|
963
1122
|
const l = v * (1 - s / 2);
|
|
@@ -1164,124 +1323,119 @@ window.__ModuleLoader__.load({
|
|
|
1164
1323
|
return rgbToHsl(b.r / b.count, b.g / b.count, b.b / b.count);
|
|
1165
1324
|
}
|
|
1166
1325
|
function extractWallpaperPalette(dataUrl, bgState) {
|
|
1167
|
-
return
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
}
|
|
1190
|
-
}
|
|
1191
|
-
const c = document.createElement("canvas");
|
|
1192
|
-
c.width = EXTRACT_SIDE;
|
|
1193
|
-
c.height = EXTRACT_SIDE;
|
|
1194
|
-
const g = c.getContext("2d", { willReadFrequently: true });
|
|
1195
|
-
g.drawImage(img, sx, sy, sw, sh, 0, 0, EXTRACT_SIDE, EXTRACT_SIDE);
|
|
1196
|
-
const px = g.getImageData(0, 0, EXTRACT_SIDE, EXTRACT_SIDE).data;
|
|
1197
|
-
const counts = /* @__PURE__ */ new Uint32Array(4096);
|
|
1198
|
-
const sums = /* @__PURE__ */ new Float64Array(12288);
|
|
1199
|
-
let totalLum = 0;
|
|
1200
|
-
let sampled = 0;
|
|
1201
|
-
for (let i = 0; i < px.length; i += 4) {
|
|
1202
|
-
const r = px[i], gg = px[i + 1], b = px[i + 2];
|
|
1203
|
-
const max = Math.max(r, gg, b), min = Math.min(r, gg, b);
|
|
1204
|
-
const v = max / 255;
|
|
1205
|
-
const s = max === 0 ? 0 : (max - min) / max;
|
|
1206
|
-
totalLum += (.2126 * r + .7152 * gg + .0722 * b) / 255;
|
|
1207
|
-
sampled++;
|
|
1208
|
-
if (s < .08 || v < .12 || v > .97) continue;
|
|
1209
|
-
const key = r >> 4 << 8 | gg >> 4 << 4 | b >> 4;
|
|
1210
|
-
counts[key]++;
|
|
1211
|
-
sums[key * 3] += r;
|
|
1212
|
-
sums[key * 3 + 1] += gg;
|
|
1213
|
-
sums[key * 3 + 2] += b;
|
|
1214
|
-
}
|
|
1215
|
-
const buckets = [];
|
|
1216
|
-
for (let k = 0; k < 4096; k++) {
|
|
1217
|
-
if (counts[k] === 0) continue;
|
|
1218
|
-
buckets.push({
|
|
1219
|
-
count: counts[k],
|
|
1220
|
-
r: sums[k * 3],
|
|
1221
|
-
g: sums[k * 3 + 1],
|
|
1222
|
-
b: sums[k * 3 + 2],
|
|
1223
|
-
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
|
|
1224
|
-
});
|
|
1225
|
-
}
|
|
1226
|
-
if (buckets.length === 0) {
|
|
1227
|
-
resolve(null);
|
|
1228
|
-
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;
|
|
1229
1348
|
}
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
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;
|
|
1240
1395
|
}
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
]
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
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;
|
|
1253
1408
|
}
|
|
1254
|
-
const surface = [
|
|
1255
|
-
primaryHue,
|
|
1256
|
-
Math.min(.06, primary[1] * .3),
|
|
1257
|
-
primary[2]
|
|
1258
|
-
];
|
|
1259
|
-
const luminance = sampled > 0 ? totalLum / sampled : primary[2];
|
|
1260
|
-
primary[2] = luminance < .5 ? Math.min(.44, Math.max(.2, primary[2])) : Math.max(.6, Math.min(.82, primary[2]));
|
|
1261
|
-
resolve({
|
|
1262
|
-
primary: [
|
|
1263
|
-
primary[0],
|
|
1264
|
-
Math.min(.9, Math.max(.15, primary[1])),
|
|
1265
|
-
primary[2]
|
|
1266
|
-
],
|
|
1267
|
-
secondary: [
|
|
1268
|
-
secondary[0],
|
|
1269
|
-
Math.min(.85, Math.max(.2, secondary[1])),
|
|
1270
|
-
Math.min(.75, Math.max(.35, secondary[2]))
|
|
1271
|
-
],
|
|
1272
|
-
tertiary: [
|
|
1273
|
-
tertiary[0],
|
|
1274
|
-
Math.min(.8, Math.max(.2, tertiary[1])),
|
|
1275
|
-
Math.min(.7, Math.max(.35, tertiary[2]))
|
|
1276
|
-
],
|
|
1277
|
-
surface,
|
|
1278
|
-
luminance
|
|
1279
|
-
});
|
|
1280
|
-
} catch {
|
|
1281
|
-
resolve(null);
|
|
1282
1409
|
}
|
|
1283
|
-
|
|
1284
|
-
|
|
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
|
+
}
|
|
1285
1439
|
});
|
|
1286
1440
|
}
|
|
1287
1441
|
/** Backward-compatible single-color extraction: returns the primary HSL. */
|
|
@@ -1294,26 +1448,22 @@ window.__ModuleLoader__.load({
|
|
|
1294
1448
|
* reads dark (use white fonts), false when light (use black fonts), or null
|
|
1295
1449
|
* when the frame cannot be decoded. */
|
|
1296
1450
|
function analyzeFrameDark(dataUrl) {
|
|
1297
|
-
return
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
resolve(null);
|
|
1314
|
-
}
|
|
1315
|
-
};
|
|
1316
|
-
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
|
+
}
|
|
1317
1467
|
});
|
|
1318
1468
|
}
|
|
1319
1469
|
/** HSL (h 0-360, s/l 0-1) → RGB (0-255 integers). */
|
|
@@ -1355,20 +1505,42 @@ window.__ModuleLoader__.load({
|
|
|
1355
1505
|
function newRaf(canvas, draw) {
|
|
1356
1506
|
canvas.dataset.dshAnyCanvas = "1";
|
|
1357
1507
|
let running = true;
|
|
1508
|
+
let paused = false;
|
|
1509
|
+
let rafId = 0;
|
|
1358
1510
|
let last = 0;
|
|
1359
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
|
+
};
|
|
1360
1519
|
const loop = (ts) => {
|
|
1361
|
-
if (!running) return;
|
|
1520
|
+
if (!running || paused) return;
|
|
1362
1521
|
if (ts - last >= FRAME_MS) {
|
|
1363
1522
|
last = ts;
|
|
1364
1523
|
draw();
|
|
1365
1524
|
}
|
|
1366
|
-
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
|
+
}
|
|
1367
1543
|
};
|
|
1368
|
-
requestAnimationFrame(loop);
|
|
1369
|
-
return { stop: () => {
|
|
1370
|
-
running = false;
|
|
1371
|
-
} };
|
|
1372
1544
|
}
|
|
1373
1545
|
function createCanvas() {
|
|
1374
1546
|
const c = document.createElement("canvas");
|
|
@@ -1383,7 +1555,6 @@ window.__ModuleLoader__.load({
|
|
|
1383
1555
|
};
|
|
1384
1556
|
}
|
|
1385
1557
|
function fitLiveCanvas(c) {
|
|
1386
|
-
if (c.dataset.dshAnyStatic === "1") return;
|
|
1387
1558
|
const { w, h } = liveSize();
|
|
1388
1559
|
if (c.width !== w || c.height !== h) {
|
|
1389
1560
|
c.width = w;
|
|
@@ -1680,6 +1851,43 @@ window.__ModuleLoader__.load({
|
|
|
1680
1851
|
col = hueRotate(col, u_seed * 6.28318530718);
|
|
1681
1852
|
gl_FragColor = vec4(col, 1.0);
|
|
1682
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
|
+
}
|
|
1683
1891
|
`;
|
|
1684
1892
|
return common + `
|
|
1685
1893
|
void main() {
|
|
@@ -1703,8 +1911,154 @@ window.__ModuleLoader__.load({
|
|
|
1703
1911
|
function createPatternBg(params, canvas) {
|
|
1704
1912
|
if (params.preset === "waves") return createWaves(params, canvas);
|
|
1705
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);
|
|
1706
1917
|
return createDots(params, canvas);
|
|
1707
1918
|
}
|
|
1919
|
+
function createRain(params, canvas) {
|
|
1920
|
+
const c = canvas ?? createCanvas();
|
|
1921
|
+
fitLiveCanvas(c);
|
|
1922
|
+
const g = c.getContext("2d", { alpha: false });
|
|
1923
|
+
const rng = createRng(params.seed);
|
|
1924
|
+
const dark = rng() < .5;
|
|
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
|
|
1937
|
+
});
|
|
1938
|
+
let t = 0;
|
|
1939
|
+
const draw = () => {
|
|
1940
|
+
fitLiveCanvas(c);
|
|
1941
|
+
const w = c.width, h = c.height;
|
|
1942
|
+
g.fillStyle = dark ? "#07080c" : "#f4f6f9";
|
|
1943
|
+
g.fillRect(0, 0, w, h);
|
|
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;
|
|
1954
|
+
g.beginPath();
|
|
1955
|
+
g.moveTo(x, y0);
|
|
1956
|
+
g.lineTo(x, y1);
|
|
1957
|
+
g.stroke();
|
|
1958
|
+
}
|
|
1959
|
+
t += .016;
|
|
1960
|
+
};
|
|
1961
|
+
return {
|
|
1962
|
+
canvas: c,
|
|
1963
|
+
...newRaf(c, draw),
|
|
1964
|
+
snapshot: () => c.toDataURL("image/jpeg", .94)
|
|
1965
|
+
};
|
|
1966
|
+
}
|
|
1967
|
+
function createContour(params, canvas) {
|
|
1968
|
+
const c = canvas ?? createCanvas();
|
|
1969
|
+
fitLiveCanvas(c);
|
|
1970
|
+
const g = c.getContext("2d", { alpha: false });
|
|
1971
|
+
const noise = createNoise(params.seed);
|
|
1972
|
+
const rng = createRng(params.seed + 1013);
|
|
1973
|
+
const dark = rng() < .5;
|
|
1974
|
+
const hue = Math.round(rng() * 360);
|
|
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
|
+
}
|
|
1708
2062
|
function createDots(params, canvas) {
|
|
1709
2063
|
const c = canvas ?? createCanvas();
|
|
1710
2064
|
fitLiveCanvas(c);
|
|
@@ -1909,6 +2263,14 @@ window.__ModuleLoader__.load({
|
|
|
1909
2263
|
let wpController = null;
|
|
1910
2264
|
let snapshotListener = null;
|
|
1911
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
|
+
}
|
|
1912
2274
|
function clearDynamicBg() {
|
|
1913
2275
|
wpController?.stop();
|
|
1914
2276
|
wpController?.canvas.remove();
|
|
@@ -2061,6 +2423,14 @@ window.__ModuleLoader__.load({
|
|
|
2061
2423
|
if (px > 0) document.documentElement.style.setProperty("--dsh-any-input-blur", `blur(${px}px)`);
|
|
2062
2424
|
else document.documentElement.style.removeProperty("--dsh-any-input-blur");
|
|
2063
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
|
+
}
|
|
2064
2434
|
function applySettingsOverrides(op) {
|
|
2065
2435
|
const [h, s, l] = rColor();
|
|
2066
2436
|
const tokens = genTokens(h, s, l, rColorScheme()).tokens;
|
|
@@ -2099,14 +2469,16 @@ window.__ModuleLoader__.load({
|
|
|
2099
2469
|
return;
|
|
2100
2470
|
}
|
|
2101
2471
|
const url = rWp();
|
|
2102
|
-
if (url)
|
|
2103
|
-
if (hsl) {
|
|
2104
|
-
cfg.color = hsl;
|
|
2105
|
-
colorAdoptedListener?.(hsl);
|
|
2106
|
-
}
|
|
2472
|
+
if (url) {
|
|
2107
2473
|
applyWp();
|
|
2108
|
-
|
|
2109
|
-
|
|
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();
|
|
2110
2482
|
}
|
|
2111
2483
|
/** Switch the background source type. For generated types a new live canvas is
|
|
2112
2484
|
* attached to the wallpaper layer and a snapshot is kept for the store/preview. */
|
|
@@ -2258,6 +2630,7 @@ window.__ModuleLoader__.load({
|
|
|
2258
2630
|
setBlur(sidebarEl, blurs.sidebar);
|
|
2259
2631
|
setBlur(centerEl, blurs.bg);
|
|
2260
2632
|
setBlur(detailsEl, blurs.bg);
|
|
2633
|
+
applyBgBlurGlobal(blurs.bg);
|
|
2261
2634
|
applyCardPanelsBlur(blurs.card);
|
|
2262
2635
|
applySettingsBlur(blurs.settings);
|
|
2263
2636
|
applyInputBlur(blurs.input);
|
|
@@ -2285,6 +2658,7 @@ window.__ModuleLoader__.load({
|
|
|
2285
2658
|
if (part === "bg") {
|
|
2286
2659
|
setBlur(centerEl, v);
|
|
2287
2660
|
setBlur(detailsEl, v);
|
|
2661
|
+
applyBgBlurGlobal(v);
|
|
2288
2662
|
} else setBlur(sidebarEl, v);
|
|
2289
2663
|
}
|
|
2290
2664
|
const VIEW_CARDS = [{
|
|
@@ -2554,17 +2928,18 @@ window.__ModuleLoader__.load({
|
|
|
2554
2928
|
cb(imgNat.w, imgNat.h);
|
|
2555
2929
|
return;
|
|
2556
2930
|
}
|
|
2557
|
-
|
|
2558
|
-
|
|
2931
|
+
loadImage(url).then((img) => {
|
|
2932
|
+
if (!img) {
|
|
2933
|
+
cb(0, 0);
|
|
2934
|
+
return;
|
|
2935
|
+
}
|
|
2559
2936
|
imgNat = {
|
|
2560
2937
|
url,
|
|
2561
2938
|
w: img.naturalWidth,
|
|
2562
2939
|
h: img.naturalHeight
|
|
2563
2940
|
};
|
|
2564
2941
|
cb(img.naturalWidth, img.naturalHeight);
|
|
2565
|
-
};
|
|
2566
|
-
img.onerror = () => cb(0, 0);
|
|
2567
|
-
img.src = url;
|
|
2942
|
+
});
|
|
2568
2943
|
}
|
|
2569
2944
|
const DRAG_MAX_SIDE = 720;
|
|
2570
2945
|
let lowResUrl = null;
|
|
@@ -2575,8 +2950,11 @@ window.__ModuleLoader__.load({
|
|
|
2575
2950
|
cb(lowResUrl);
|
|
2576
2951
|
return;
|
|
2577
2952
|
}
|
|
2578
|
-
|
|
2579
|
-
|
|
2953
|
+
loadImage(url).then((img) => {
|
|
2954
|
+
if (!img) {
|
|
2955
|
+
cb(null);
|
|
2956
|
+
return;
|
|
2957
|
+
}
|
|
2580
2958
|
const k = Math.min(1, DRAG_MAX_SIDE / Math.max(img.naturalWidth, img.naturalHeight));
|
|
2581
2959
|
if (k >= 1) {
|
|
2582
2960
|
lowResFor = url;
|
|
@@ -2599,9 +2977,7 @@ window.__ModuleLoader__.load({
|
|
|
2599
2977
|
lowResFor = url;
|
|
2600
2978
|
lowResUrl = low;
|
|
2601
2979
|
cb(low);
|
|
2602
|
-
};
|
|
2603
|
-
img.onerror = () => cb(null);
|
|
2604
|
-
img.src = url;
|
|
2980
|
+
});
|
|
2605
2981
|
}
|
|
2606
2982
|
function setDragLow(on) {
|
|
2607
2983
|
if (cfg.backgroundType !== "image" || on === dragLow || !wpEl) return;
|
|
@@ -2640,6 +3016,7 @@ window.__ModuleLoader__.load({
|
|
|
2640
3016
|
}
|
|
2641
3017
|
let wpVerdict = null;
|
|
2642
3018
|
let verdictListener = null;
|
|
3019
|
+
let verdictGen = 0;
|
|
2643
3020
|
/** Register a callback fired when the background brightness verdict CHANGES
|
|
2644
3021
|
* (a new wallpaper was analyzed, a generated bg regenerated), so the skin can
|
|
2645
3022
|
* be re-registered through the host theme service. */
|
|
@@ -2652,6 +3029,7 @@ window.__ModuleLoader__.load({
|
|
|
2652
3029
|
if (dark !== null) verdictListener?.();
|
|
2653
3030
|
}
|
|
2654
3031
|
function updateWpVerdict(url) {
|
|
3032
|
+
const gen = ++verdictGen;
|
|
2655
3033
|
if (url === null) {
|
|
2656
3034
|
applyVerdict(null);
|
|
2657
3035
|
return;
|
|
@@ -2661,7 +3039,7 @@ window.__ModuleLoader__.load({
|
|
|
2661
3039
|
return;
|
|
2662
3040
|
}
|
|
2663
3041
|
analyzeFrameDark(url).then((dark) => {
|
|
2664
|
-
if (dark === null) return;
|
|
3042
|
+
if (dark === null || gen !== verdictGen) return;
|
|
2665
3043
|
wpVerdict = {
|
|
2666
3044
|
url,
|
|
2667
3045
|
dark
|
|
@@ -2791,6 +3169,7 @@ window.__ModuleLoader__.load({
|
|
|
2791
3169
|
setBgDark(null);
|
|
2792
3170
|
}
|
|
2793
3171
|
if (rHasColor() || rBgDark() !== null || rSchemeOverride() !== "auto") applyCustomTokens(rOps());
|
|
3172
|
+
else if (!url) clearThemeTokens();
|
|
2794
3173
|
if (rHasColor()) {
|
|
2795
3174
|
applySettingsOverrides(rSop());
|
|
2796
3175
|
applyTrajectoryOverrides(rTrajectoryOpacity());
|
|
@@ -2822,6 +3201,7 @@ window.__ModuleLoader__.load({
|
|
|
2822
3201
|
document.documentElement.style.removeProperty("--dsh-any-blur-settings");
|
|
2823
3202
|
document.documentElement.style.removeProperty("--dsh-any-blur-card-panels");
|
|
2824
3203
|
document.documentElement.style.removeProperty("--dsh-any-input-blur");
|
|
3204
|
+
document.documentElement.style.removeProperty("--dsh-any-part-blur-global");
|
|
2825
3205
|
for (const v of Object.values(OPACITY_VARS)) document.documentElement.style.removeProperty(v);
|
|
2826
3206
|
baseTokenKey = "";
|
|
2827
3207
|
lastBgKey = "";
|
|
@@ -2912,56 +3292,6 @@ window.__ModuleLoader__.load({
|
|
|
2912
3292
|
v.src = url;
|
|
2913
3293
|
});
|
|
2914
3294
|
}
|
|
2915
|
-
//#endregion
|
|
2916
|
-
//#region src/client/utils/image.ts
|
|
2917
|
-
/**
|
|
2918
|
-
* Read a chosen image file as a data URL WITHOUT re-encoding: the original
|
|
2919
|
-
* pixels are kept as-is (no canvas downscale / JPEG re-compression), so the
|
|
2920
|
-
* wallpaper is stored and displayed at full fidelity. The tradeoff is a larger
|
|
2921
|
-
* payload over the RPC channel and on disk for big images.
|
|
2922
|
-
*/
|
|
2923
|
-
function readImg(file, cb) {
|
|
2924
|
-
const r = new FileReader();
|
|
2925
|
-
r.onerror = () => cb(null);
|
|
2926
|
-
r.onload = () => cb(r.result);
|
|
2927
|
-
r.readAsDataURL(file);
|
|
2928
|
-
}
|
|
2929
|
-
/** Promised readImg variant for async flows. */
|
|
2930
|
-
function readImgAsync(file) {
|
|
2931
|
-
return new Promise((resolve) => readImg(file, resolve));
|
|
2932
|
-
}
|
|
2933
|
-
/** Build a small JPEG thumbnail (rotation-pool picker preview). Resolves null
|
|
2934
|
-
* when the image cannot be decoded. */
|
|
2935
|
-
function makeThumb(dataUrl, maxSide = 96) {
|
|
2936
|
-
return new Promise((resolve) => {
|
|
2937
|
-
const img = new Image();
|
|
2938
|
-
img.onerror = () => resolve(null);
|
|
2939
|
-
img.onload = () => {
|
|
2940
|
-
try {
|
|
2941
|
-
const iw = img.naturalWidth || img.width;
|
|
2942
|
-
const ih = img.naturalHeight || img.height;
|
|
2943
|
-
if (iw <= 0 || ih <= 0) {
|
|
2944
|
-
resolve(null);
|
|
2945
|
-
return;
|
|
2946
|
-
}
|
|
2947
|
-
const k = Math.min(1, maxSide / Math.max(iw, ih));
|
|
2948
|
-
const c = document.createElement("canvas");
|
|
2949
|
-
c.width = Math.max(1, Math.round(iw * k));
|
|
2950
|
-
c.height = Math.max(1, Math.round(ih * k));
|
|
2951
|
-
const g = c.getContext("2d");
|
|
2952
|
-
if (!g) {
|
|
2953
|
-
resolve(null);
|
|
2954
|
-
return;
|
|
2955
|
-
}
|
|
2956
|
-
g.drawImage(img, 0, 0, c.width, c.height);
|
|
2957
|
-
resolve(c.toDataURL("image/jpeg", .72));
|
|
2958
|
-
} catch {
|
|
2959
|
-
resolve(null);
|
|
2960
|
-
}
|
|
2961
|
-
};
|
|
2962
|
-
img.src = dataUrl;
|
|
2963
|
-
});
|
|
2964
|
-
}
|
|
2965
3295
|
const UI_CSS = `
|
|
2966
3296
|
/* The section is rendered INLINE inside the host settings dialog's content
|
|
2967
3297
|
* column: the host provides the modal chrome (backdrop, centering, closing).
|
|
@@ -3031,6 +3361,7 @@ window.__ModuleLoader__.load({
|
|
|
3031
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}
|
|
3032
3362
|
.dab-urlinput::placeholder{color:var(--dsw-alias-label-quaternary)}
|
|
3033
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}
|
|
3034
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}
|
|
3035
3366
|
.dab-swatch-lg:hover{transform:scale(1.02)}
|
|
3036
3367
|
|
|
@@ -3041,11 +3372,19 @@ window.__ModuleLoader__.load({
|
|
|
3041
3372
|
.dab-btn:disabled{opacity:.5;cursor:not-allowed}
|
|
3042
3373
|
.dab-btn-primary{background:var(--dsw-alias-brand-primary);color:var(--dsw-alias-brand-text);border-color:transparent}
|
|
3043
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)}
|
|
3044
3377
|
.dab-btn-ghost{background:transparent;border-color:transparent;color:var(--dsw-alias-label-secondary,var(--dsw-alias-label-tertiary))}
|
|
3045
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}
|
|
3046
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}
|
|
3047
3386
|
.dab-chip-row{display:flex;flex-wrap:wrap;gap:8px}
|
|
3048
|
-
.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}
|
|
3049
3388
|
.dab-chip:hover{border-color:var(--dsw-alias-brand-primary);color:var(--dsw-alias-label-primary)}
|
|
3050
3389
|
.dab-chip.is-active{background:var(--dsw-alias-brand-primary);color:var(--dsw-alias-brand-text);border-color:transparent}
|
|
3051
3390
|
|
|
@@ -3095,7 +3434,7 @@ window.__ModuleLoader__.load({
|
|
|
3095
3434
|
|
|
3096
3435
|
/* ── generated background type cards ─────────────────────────────────────── */
|
|
3097
3436
|
.dab-types{display:grid;grid-template-columns:repeat(3,1fr);gap:12px}
|
|
3098
|
-
.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)}
|
|
3099
3438
|
.dab-type:hover{transform:translateY(-2px)}
|
|
3100
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)}
|
|
3101
3440
|
.dab-type-thumb{height:62px;border-radius:9px;overflow:hidden;position:relative}
|
|
@@ -3126,14 +3465,15 @@ window.__ModuleLoader__.load({
|
|
|
3126
3465
|
/* ── presets / profiles / schedule / rotation ────────────────────────────── */
|
|
3127
3466
|
.dab-row-head{display:flex;align-items:center;justify-content:space-between;gap:12px}
|
|
3128
3467
|
.dab-preset-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:10px}
|
|
3129
|
-
.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}
|
|
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)}
|
|
3130
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)}
|
|
3131
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)}
|
|
3132
3471
|
.dab-preset-name{font-size:12.5px;font-weight:600;color:var(--dsw-alias-label-primary)}
|
|
3133
3472
|
.dab-preset-desc{font-size:10.5px;line-height:1.45;color:var(--dsw-alias-label-tertiary)}
|
|
3134
|
-
.dab-profile-list{display:flex;flex-direction:column;gap:8px;margin-top:12px}
|
|
3135
|
-
.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);transition:border-color .22s ease}
|
|
3136
|
-
.dab-profile-row
|
|
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))}
|
|
3137
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)}
|
|
3138
3478
|
.dab-profile-meta{flex:1;min-width:0;display:flex;flex-direction:column;gap:1px}
|
|
3139
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}
|
|
@@ -3143,14 +3483,22 @@ window.__ModuleLoader__.load({
|
|
|
3143
3483
|
.dab-select:focus{border-color:var(--dsw-alias-brand-primary)}
|
|
3144
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}
|
|
3145
3485
|
.dab-timeinput:focus{border-color:var(--dsw-alias-brand-primary)}
|
|
3146
|
-
|
|
3147
|
-
.
|
|
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)}
|
|
3148
3496
|
.dab-time-row{display:flex;flex-direction:column;gap:9px}
|
|
3149
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))}
|
|
3150
3498
|
.dab-time-label svg{color:var(--dsw-alias-brand-primary);flex:none}
|
|
3151
3499
|
.dab-time-label .dab-select,.dab-time-label .dab-timeinput{flex:1;min-width:0}
|
|
3152
3500
|
.dab-thumbstrip{display:flex;flex-wrap:wrap;gap:9px;margin-top:13px}
|
|
3153
|
-
.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)}
|
|
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}
|
|
3154
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)}
|
|
3155
3503
|
.dab-thumb img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}
|
|
3156
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}
|
|
@@ -3184,6 +3532,9 @@ window.__ModuleLoader__.load({
|
|
|
3184
3532
|
@keyframes dab-fade-in{from{opacity:0}to{opacity:1}}
|
|
3185
3533
|
@keyframes dab-page-in{from{opacity:0;transform:translateY(12px)}to{opacity:1;transform:none}}
|
|
3186
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}}
|
|
3187
3538
|
@keyframes dab-orb-in{from{opacity:0;transform:scale(.82)}to{opacity:1;transform:none}}
|
|
3188
3539
|
@keyframes dab-spin{to{transform:rotate(360deg)}}
|
|
3189
3540
|
@keyframes dab-rotate{to{transform:rotate(360deg)}}
|
|
@@ -3491,6 +3842,16 @@ window.__ModuleLoader__.load({
|
|
|
3491
3842
|
className,
|
|
3492
3843
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M4 4l8 8M12 4l-8 8" })
|
|
3493
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
|
+
});
|
|
3494
3855
|
//#endregion
|
|
3495
3856
|
//#region src/client/components/ErrorBoundary.tsx
|
|
3496
3857
|
/**
|
|
@@ -4134,12 +4495,12 @@ window.__ModuleLoader__.load({
|
|
|
4134
4495
|
}
|
|
4135
4496
|
function ColorPage({ p, notify }) {
|
|
4136
4497
|
const { t, hue, sat, lit, setColor, extractColor, setSchemeOverride, useStore } = p;
|
|
4137
|
-
const
|
|
4138
|
-
const
|
|
4139
|
-
const
|
|
4498
|
+
const storeUrl = useStore((s) => s.url);
|
|
4499
|
+
const schemeOverride = useStore((s) => s.schemeOverride);
|
|
4500
|
+
const color = useStore((s) => s.color);
|
|
4140
4501
|
const [pickerOpen, setPickerOpen] = (0, react.useState)(false);
|
|
4141
4502
|
const [extracting, setExtracting] = (0, react.useState)(false);
|
|
4142
|
-
const wheel =
|
|
4503
|
+
const wheel = color ?? [
|
|
4143
4504
|
hue,
|
|
4144
4505
|
sat,
|
|
4145
4506
|
lit
|
|
@@ -4781,13 +5142,12 @@ window.__ModuleLoader__.load({
|
|
|
4781
5142
|
}
|
|
4782
5143
|
];
|
|
4783
5144
|
function BackgroundPage({ p }) {
|
|
4784
|
-
const { t,
|
|
4785
|
-
const
|
|
4786
|
-
const
|
|
4787
|
-
const
|
|
4788
|
-
const
|
|
4789
|
-
const
|
|
4790
|
-
const rotation = store.rotation;
|
|
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);
|
|
4791
5151
|
const fileRef = (0, react.useRef)(null);
|
|
4792
5152
|
const rotFileRef = (0, react.useRef)(null);
|
|
4793
5153
|
const [editorOpen, setEditorOpen] = (0, react.useState)(false);
|
|
@@ -4803,13 +5163,22 @@ window.__ModuleLoader__.load({
|
|
|
4803
5163
|
const isStatic = backgroundType === "image" || isVideo;
|
|
4804
5164
|
const isGenerated = !isStatic;
|
|
4805
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
|
+
]);
|
|
4806
5175
|
const onFileSelect = (f) => {
|
|
4807
5176
|
if (f.type.startsWith("video/")) {
|
|
4808
5177
|
setVideo(f, f.type);
|
|
4809
5178
|
return;
|
|
4810
5179
|
}
|
|
4811
|
-
|
|
4812
|
-
if (
|
|
5180
|
+
uploadWallpaper(f).then((ok) => {
|
|
5181
|
+
if (ok) setWpFromServer(WALLPAPER_SERVE_URL);
|
|
4813
5182
|
});
|
|
4814
5183
|
};
|
|
4815
5184
|
const onDrop = (e) => {
|
|
@@ -4827,12 +5196,17 @@ window.__ModuleLoader__.load({
|
|
|
4827
5196
|
setUrlBusy(true);
|
|
4828
5197
|
setUrlErr(null);
|
|
4829
5198
|
const r = await setWallpaperFromUrl(u);
|
|
4830
|
-
setUrlBusy(false);
|
|
4831
5199
|
if (r.ok) {
|
|
4832
|
-
|
|
5200
|
+
setWpFromServer(r.wallpaperUrl ?? null);
|
|
4833
5201
|
setUrlOpen(false);
|
|
4834
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"));
|
|
4835
5208
|
} else setUrlErr(r.error === "invalid url" || r.error === "unsupported scheme" ? t("bgUrlBadHttp") : r.error ?? t("bgUrlFail"));
|
|
5209
|
+
setUrlBusy(false);
|
|
4836
5210
|
};
|
|
4837
5211
|
const switchToStatic = () => {
|
|
4838
5212
|
if (isStatic) return;
|
|
@@ -4847,10 +5221,12 @@ window.__ModuleLoader__.load({
|
|
|
4847
5221
|
};
|
|
4848
5222
|
const setGenType = (type) => {
|
|
4849
5223
|
if (type === activeGenType) return;
|
|
5224
|
+
setPaused(false);
|
|
4850
5225
|
setBgType(type);
|
|
4851
5226
|
};
|
|
4852
5227
|
const ensureGenParams = () => generatedBg ?? defaultParamsFor(activeGenType);
|
|
4853
5228
|
const updateGenerated = (patch) => {
|
|
5229
|
+
setPaused(false);
|
|
4854
5230
|
setGeneratedBg({
|
|
4855
5231
|
...ensureGenParams(),
|
|
4856
5232
|
...patch
|
|
@@ -4881,9 +5257,13 @@ window.__ModuleLoader__.load({
|
|
|
4881
5257
|
case "aurora": return t("presetAurora");
|
|
4882
5258
|
case "nebula": return t("presetNebula");
|
|
4883
5259
|
case "noise": return t("presetNoise");
|
|
5260
|
+
case "starfield": return t("presetStarfield");
|
|
4884
5261
|
case "dots": return t("presetDots");
|
|
4885
5262
|
case "waves": return t("presetWaves");
|
|
4886
5263
|
case "poly": return t("presetPoly");
|
|
5264
|
+
case "rain": return t("presetRain");
|
|
5265
|
+
case "contour": return t("presetContour");
|
|
5266
|
+
case "meta": return t("presetMeta");
|
|
4887
5267
|
default: return key;
|
|
4888
5268
|
}
|
|
4889
5269
|
};
|
|
@@ -4938,6 +5318,7 @@ window.__ModuleLoader__.load({
|
|
|
4938
5318
|
type: "button",
|
|
4939
5319
|
className: "dab-btn",
|
|
4940
5320
|
onClick: () => {
|
|
5321
|
+
setPaused(false);
|
|
4941
5322
|
regenerateBg();
|
|
4942
5323
|
setSpinTick((x) => x + 1);
|
|
4943
5324
|
},
|
|
@@ -4945,7 +5326,7 @@ window.__ModuleLoader__.load({
|
|
|
4945
5326
|
}) : null, /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
4946
5327
|
type: "button",
|
|
4947
5328
|
className: "dab-btn dab-btn-danger",
|
|
4948
|
-
onClick: () =>
|
|
5329
|
+
onClick: () => setWpFromServer(null),
|
|
4949
5330
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TrashIcon, { size: 13 }), t("bgRemove")]
|
|
4950
5331
|
})]
|
|
4951
5332
|
})
|
|
@@ -5006,7 +5387,7 @@ window.__ModuleLoader__.load({
|
|
|
5006
5387
|
}),
|
|
5007
5388
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
5008
5389
|
type: "button",
|
|
5009
|
-
className: "dab-btn dab-btn-
|
|
5390
|
+
className: "dab-btn dab-btn-soft",
|
|
5010
5391
|
onClick: () => setUrlOpen((o) => !o),
|
|
5011
5392
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(LinkIcon, { size: 14 }), t("bgFromUrl")]
|
|
5012
5393
|
}),
|
|
@@ -5019,21 +5400,14 @@ window.__ModuleLoader__.load({
|
|
|
5019
5400
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(EditIcon, { size: 14 }), t("bgEdit")]
|
|
5020
5401
|
}) : null, /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
5021
5402
|
type: "button",
|
|
5022
|
-
className: "dab-btn dab-btn-
|
|
5023
|
-
onClick: () =>
|
|
5403
|
+
className: "dab-btn dab-btn-danger-inverted",
|
|
5404
|
+
onClick: () => setWpFromServer(null),
|
|
5024
5405
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TrashIcon, { size: 14 }), t("bgRemove")]
|
|
5025
5406
|
})] }) : null
|
|
5026
5407
|
]
|
|
5027
5408
|
}),
|
|
5028
5409
|
urlOpen ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5029
5410
|
className: "dab-urlrow",
|
|
5030
|
-
style: {
|
|
5031
|
-
marginTop: 12,
|
|
5032
|
-
display: "flex",
|
|
5033
|
-
flexWrap: "wrap",
|
|
5034
|
-
gap: 8,
|
|
5035
|
-
alignItems: "center"
|
|
5036
|
-
},
|
|
5037
5411
|
children: [
|
|
5038
5412
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
5039
5413
|
type: "text",
|
|
@@ -5213,9 +5587,10 @@ window.__ModuleLoader__.load({
|
|
|
5213
5587
|
children: [
|
|
5214
5588
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5215
5589
|
className: "dab-types",
|
|
5216
|
-
children: typeMeta.map((m) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
5590
|
+
children: typeMeta.map((m, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
5217
5591
|
type: "button",
|
|
5218
5592
|
className: `dab-type${activeGenType === m.type ? " is-active" : ""}`,
|
|
5593
|
+
style: { "--i": i },
|
|
5219
5594
|
onClick: () => setGenType(m.type),
|
|
5220
5595
|
children: [
|
|
5221
5596
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: `dab-type-thumb ${m.thumb}` }),
|
|
@@ -5244,7 +5619,8 @@ window.__ModuleLoader__.load({
|
|
|
5244
5619
|
children: [
|
|
5245
5620
|
"aurora",
|
|
5246
5621
|
"nebula",
|
|
5247
|
-
"noise"
|
|
5622
|
+
"noise",
|
|
5623
|
+
"starfield"
|
|
5248
5624
|
].map((pr) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
5249
5625
|
type: "button",
|
|
5250
5626
|
className: `dab-chip${generatedBg.preset === pr ? " is-active" : ""}`,
|
|
@@ -5263,7 +5639,10 @@ window.__ModuleLoader__.load({
|
|
|
5263
5639
|
children: [
|
|
5264
5640
|
"dots",
|
|
5265
5641
|
"waves",
|
|
5266
|
-
"poly"
|
|
5642
|
+
"poly",
|
|
5643
|
+
"rain",
|
|
5644
|
+
"contour",
|
|
5645
|
+
"meta"
|
|
5267
5646
|
].map((pr) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
5268
5647
|
type: "button",
|
|
5269
5648
|
className: `dab-chip${generatedBg.preset === pr ? " is-active" : ""}`,
|
|
@@ -5353,11 +5732,26 @@ window.__ModuleLoader__.load({
|
|
|
5353
5732
|
onClick: () => setRegenerateOnReload(!regenerateOnReload),
|
|
5354
5733
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "dab-toggle-knob" })
|
|
5355
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
|
+
}),
|
|
5356
5749
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
5357
5750
|
type: "button",
|
|
5358
5751
|
className: "dab-btn dab-btn-primary",
|
|
5359
5752
|
onClick: () => {
|
|
5360
5753
|
regenerateBg();
|
|
5754
|
+
setPaused(false);
|
|
5361
5755
|
setSpinTick((x) => x + 1);
|
|
5362
5756
|
},
|
|
5363
5757
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
@@ -5618,10 +6012,9 @@ window.__ModuleLoader__.load({
|
|
|
5618
6012
|
}
|
|
5619
6013
|
function ProfilePage({ p, notify }) {
|
|
5620
6014
|
const { t, exportTheme, importTheme, saveProfile, applyProfile, deleteProfile, applyPreset, setSchedule, useStore } = p;
|
|
5621
|
-
const
|
|
5622
|
-
const
|
|
5623
|
-
const
|
|
5624
|
-
const activeProfile = store.activeProfile;
|
|
6015
|
+
const profiles = useStore((s) => s.profiles);
|
|
6016
|
+
const schedule = useStore((s) => s.schedule);
|
|
6017
|
+
const activeProfile = useStore((s) => s.activeProfile);
|
|
5625
6018
|
const importRef = (0, react.useRef)(null);
|
|
5626
6019
|
const [nameOpen, setNameOpen] = (0, react.useState)(false);
|
|
5627
6020
|
const [nameVal, setNameVal] = (0, react.useState)("");
|
|
@@ -5694,11 +6087,12 @@ window.__ModuleLoader__.load({
|
|
|
5694
6087
|
children: t("presetGalleryTitle")
|
|
5695
6088
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5696
6089
|
className: "dab-preset-grid",
|
|
5697
|
-
children: BUILTIN_PRESETS.map((ps) => {
|
|
6090
|
+
children: BUILTIN_PRESETS.map((ps, i) => {
|
|
5698
6091
|
const hex = hexOf(ps.appearance.color);
|
|
5699
6092
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
5700
6093
|
type: "button",
|
|
5701
6094
|
className: "dab-preset",
|
|
6095
|
+
style: { "--i": i },
|
|
5702
6096
|
onClick: () => {
|
|
5703
6097
|
applyPreset(ps.appearance);
|
|
5704
6098
|
notify(t("presetApplied"));
|
|
@@ -5741,13 +6135,6 @@ window.__ModuleLoader__.load({
|
|
|
5741
6135
|
}),
|
|
5742
6136
|
nameOpen ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5743
6137
|
className: "dab-urlrow",
|
|
5744
|
-
style: {
|
|
5745
|
-
marginTop: 12,
|
|
5746
|
-
display: "flex",
|
|
5747
|
-
flexWrap: "wrap",
|
|
5748
|
-
gap: 8,
|
|
5749
|
-
alignItems: "center"
|
|
5750
|
-
},
|
|
5751
6138
|
children: [
|
|
5752
6139
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
5753
6140
|
type: "text",
|
|
@@ -5788,8 +6175,9 @@ window.__ModuleLoader__.load({
|
|
|
5788
6175
|
children: t("profilesEmpty")
|
|
5789
6176
|
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5790
6177
|
className: "dab-profile-list",
|
|
5791
|
-
children: profiles.map((entry) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
6178
|
+
children: profiles.map((entry, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5792
6179
|
className: `dab-profile-row${activeProfile === entry.id ? " is-active" : ""}`,
|
|
6180
|
+
style: { "--i": i },
|
|
5793
6181
|
children: [
|
|
5794
6182
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ProfileDot, { entry }),
|
|
5795
6183
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -5810,10 +6198,10 @@ window.__ModuleLoader__.load({
|
|
|
5810
6198
|
}),
|
|
5811
6199
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
5812
6200
|
type: "button",
|
|
5813
|
-
className: `dab-btn dab-btn-
|
|
6201
|
+
className: `dab-btn dab-btn-danger-solid${confirmId === entry.id ? " dab-btn-confirm" : ""}`,
|
|
5814
6202
|
title: t("profileDelete"),
|
|
5815
6203
|
onClick: () => onDelete(entry),
|
|
5816
|
-
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")] }) :
|
|
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")
|
|
5817
6205
|
})
|
|
5818
6206
|
]
|
|
5819
6207
|
}, entry.id))
|
|
@@ -5844,93 +6232,103 @@ window.__ModuleLoader__.load({
|
|
|
5844
6232
|
style: { marginTop: 8 },
|
|
5845
6233
|
children: t("scheduleHint")
|
|
5846
6234
|
}),
|
|
5847
|
-
|
|
5848
|
-
className:
|
|
5849
|
-
|
|
5850
|
-
|
|
5851
|
-
|
|
5852
|
-
|
|
5853
|
-
|
|
5854
|
-
|
|
5855
|
-
|
|
5856
|
-
|
|
5857
|
-
|
|
5858
|
-
|
|
5859
|
-
|
|
5860
|
-
|
|
5861
|
-
|
|
5862
|
-
|
|
5863
|
-
|
|
5864
|
-
|
|
5865
|
-
|
|
5866
|
-
|
|
5867
|
-
|
|
5868
|
-
|
|
5869
|
-
|
|
5870
|
-
|
|
5871
|
-
|
|
5872
|
-
|
|
5873
|
-
|
|
5874
|
-
|
|
5875
|
-
|
|
5876
|
-
|
|
5877
|
-
|
|
5878
|
-
|
|
5879
|
-
children: [
|
|
5880
|
-
|
|
5881
|
-
t("
|
|
5882
|
-
|
|
5883
|
-
|
|
5884
|
-
|
|
5885
|
-
|
|
5886
|
-
|
|
5887
|
-
|
|
5888
|
-
|
|
5889
|
-
|
|
5890
|
-
|
|
5891
|
-
|
|
5892
|
-
|
|
5893
|
-
|
|
5894
|
-
|
|
5895
|
-
|
|
5896
|
-
|
|
5897
|
-
|
|
5898
|
-
|
|
5899
|
-
|
|
5900
|
-
|
|
5901
|
-
|
|
5902
|
-
|
|
5903
|
-
|
|
5904
|
-
|
|
5905
|
-
|
|
5906
|
-
|
|
5907
|
-
|
|
5908
|
-
|
|
5909
|
-
|
|
5910
|
-
|
|
5911
|
-
|
|
5912
|
-
|
|
5913
|
-
|
|
5914
|
-
|
|
5915
|
-
|
|
5916
|
-
className: "dab-
|
|
5917
|
-
|
|
5918
|
-
|
|
5919
|
-
|
|
5920
|
-
|
|
5921
|
-
|
|
5922
|
-
|
|
5923
|
-
|
|
5924
|
-
|
|
5925
|
-
|
|
5926
|
-
|
|
5927
|
-
|
|
5928
|
-
|
|
5929
|
-
|
|
5930
|
-
|
|
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
|
+
]
|
|
5931
6329
|
})
|
|
5932
|
-
|
|
5933
|
-
})
|
|
6330
|
+
})
|
|
6331
|
+
})
|
|
5934
6332
|
]
|
|
5935
6333
|
}),
|
|
5936
6334
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -6186,7 +6584,7 @@ window.__ModuleLoader__.load({
|
|
|
6186
6584
|
}, "dsh-any-background: skin dispose");
|
|
6187
6585
|
const styleEl = document.createElement("style");
|
|
6188
6586
|
styleEl.dataset.plugin = "dsh-any-background";
|
|
6189
|
-
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}`;
|
|
6190
6588
|
document.head.appendChild(styleEl);
|
|
6191
6589
|
ctx.effect(() => () => {
|
|
6192
6590
|
styleEl?.parentNode?.removeChild(styleEl);
|
|
@@ -6291,32 +6689,35 @@ window.__ModuleLoader__.load({
|
|
|
6291
6689
|
* the full adaptation (host skin + editor wheel sync); the caller persists. */
|
|
6292
6690
|
const adoptWallpaperColor = async (dataUrl) => {
|
|
6293
6691
|
const hsl = await extractWallpaperColor(dataUrl, rBgState());
|
|
6294
|
-
if (!hsl) return;
|
|
6692
|
+
if (!hsl || rWp() !== dataUrl) return;
|
|
6295
6693
|
cfg.color = hsl;
|
|
6296
6694
|
registerCustom(hsl[0], hsl[1], hsl[2]);
|
|
6297
6695
|
colorRev++;
|
|
6298
6696
|
bound?.syncColor(hslToHsv(hsl[0], hsl[1], hsl[2]), colorRev);
|
|
6299
6697
|
};
|
|
6300
6698
|
/** Activate a rotation item: the server copies its bytes into the wallpaper
|
|
6301
|
-
* slot; the client applies the returned
|
|
6699
|
+
* slot; the client applies the returned serve URL through the normal image
|
|
6302
6700
|
* path, then re-extracts the theme color from the new picture so the
|
|
6303
6701
|
* palette follows the rotation. */
|
|
6304
6702
|
const applyRotationIndex = async (idx, auto) => {
|
|
6305
6703
|
const rot = rRotation();
|
|
6306
6704
|
if (idx < 0 || idx >= rot.items.length) return false;
|
|
6307
6705
|
const r = await rotationActivate(idx);
|
|
6308
|
-
if (!r.ok || !r.
|
|
6706
|
+
if (!r.ok || !r.wallpaperUrl) {
|
|
6309
6707
|
console.warn("dsh-any-background: rotation activate failed", r.error);
|
|
6310
6708
|
return false;
|
|
6311
6709
|
}
|
|
6312
|
-
|
|
6313
|
-
|
|
6710
|
+
cfg.backgroundType = "image";
|
|
6711
|
+
setBgDark(null);
|
|
6712
|
+
setWpImageUrl(r.wallpaperUrl);
|
|
6713
|
+
setWpUrl(rWpImage());
|
|
6714
|
+
setBgState({ ...DEFAULT_CONFIG.bgState });
|
|
6314
6715
|
cfg.rotation = {
|
|
6315
6716
|
...rot,
|
|
6316
6717
|
current: idx,
|
|
6317
6718
|
lastRotate: auto || rot.lastRotate === null ? (/* @__PURE__ */ new Date()).toISOString() : rot.lastRotate
|
|
6318
6719
|
};
|
|
6319
|
-
if (cfg.backgroundType === "image") await adoptWallpaperColor(
|
|
6720
|
+
if (cfg.backgroundType === "image") await adoptWallpaperColor(rWp());
|
|
6320
6721
|
applyThemeColor();
|
|
6321
6722
|
syncBg();
|
|
6322
6723
|
saveConfig();
|
|
@@ -6510,16 +6911,16 @@ window.__ModuleLoader__.load({
|
|
|
6510
6911
|
nl
|
|
6511
6912
|
], colorRev);
|
|
6512
6913
|
},
|
|
6513
|
-
|
|
6914
|
+
setWpFromServer: (u) => {
|
|
6514
6915
|
cfg.backgroundType = "image";
|
|
6515
6916
|
setBgDark(null);
|
|
6516
6917
|
setWpImageUrl(u);
|
|
6517
|
-
setWpUrl(
|
|
6918
|
+
setWpUrl(rWpImage());
|
|
6518
6919
|
setBgState({ ...DEFAULT_CONFIG.bgState });
|
|
6519
|
-
persistWallpaper(u);
|
|
6520
6920
|
if (u === null) {
|
|
6521
6921
|
setWpVideoUrl(null, null);
|
|
6522
6922
|
persistVideo(null);
|
|
6923
|
+
persistWallpaper(null);
|
|
6523
6924
|
}
|
|
6524
6925
|
applyThemeColor();
|
|
6525
6926
|
syncBg();
|
|
@@ -6624,23 +7025,29 @@ window.__ModuleLoader__.load({
|
|
|
6624
7025
|
if (cfg.backgroundType === "video") {
|
|
6625
7026
|
const vurl = rWpVideo();
|
|
6626
7027
|
if (vurl) try {
|
|
6627
|
-
|
|
6628
|
-
videoPayload = await new Promise((resolve, reject) => {
|
|
6629
|
-
const fr = new FileReader();
|
|
6630
|
-
fr.onload = () => resolve(fr.result);
|
|
6631
|
-
fr.onerror = () => reject(fr.error);
|
|
6632
|
-
fr.readAsDataURL(blob);
|
|
6633
|
-
});
|
|
7028
|
+
videoPayload = await blobToDataUrl(await fetch(vurl).then((r) => r.blob()));
|
|
6634
7029
|
if (videoPayload && !/^data:video\//.test(videoPayload)) videoPayload = videoPayload.replace(/^data:[^;,]*/, `data:${cfg.videoMime ?? "video/mp4"}`);
|
|
6635
7030
|
} catch {
|
|
6636
7031
|
videoPayload = null;
|
|
6637
7032
|
}
|
|
6638
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
|
+
}
|
|
6639
7046
|
const payload = {
|
|
6640
7047
|
version: 2,
|
|
6641
7048
|
exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6642
7049
|
config: cfg,
|
|
6643
|
-
wallpaper:
|
|
7050
|
+
wallpaper: wallpaperPayload,
|
|
6644
7051
|
video: videoPayload
|
|
6645
7052
|
};
|
|
6646
7053
|
const blob = new Blob([JSON.stringify(payload, null, 2)], { type: "application/json" });
|
|
@@ -6821,6 +7228,29 @@ window.__ModuleLoader__.load({
|
|
|
6821
7228
|
rotateNow: async () => {
|
|
6822
7229
|
if (rRotation().items.length === 0) return false;
|
|
6823
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;
|
|
6824
7254
|
}
|
|
6825
7255
|
};
|
|
6826
7256
|
};
|