dsh-any-background 0.2.5 → 0.2.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js CHANGED
@@ -44,11 +44,13 @@ window.__ModuleLoader__.load({
44
44
  uiSop: "设置界面透明度",
45
45
  uiChatRegion: "对话文本框",
46
46
  uiTrajectory: "轨迹页",
47
+ uiPanelRegion: "better-sidebar 工作台",
48
+ uiProduced: "产出物/高亮内容",
47
49
  bgTitle: "背景",
48
50
  bgChoose: "选择图片或视频",
49
51
  bgRemove: "移除背景",
50
52
  bgFromUrl: "从网址",
51
- bgUrlPlaceholder: "粘贴图片网址 https://…",
53
+ bgUrlPlaceholder: "粘贴图片或视频网址 https://…",
52
54
  bgUrlApply: "应用",
53
55
  bgUrlCancel: "取消",
54
56
  bgUrlApplying: "加载中…",
@@ -122,6 +124,13 @@ window.__ModuleLoader__.load({
122
124
  presetDots: "点阵",
123
125
  presetWaves: "波浪",
124
126
  presetPoly: "低多边形",
127
+ presetStarfield: "星空",
128
+ presetRain: "雨滴",
129
+ presetContour: "等高线",
130
+ presetMeta: "流体光斑",
131
+ bgPause: "暂停",
132
+ bgResume: "播放",
133
+ bgUrlVideoFail: "获取视频失败,请确认链接可直接下载视频文件",
125
134
  schemeTitle: "界面明暗",
126
135
  schemeLight: "亮色",
127
136
  schemeDark: "暗色",
@@ -200,11 +209,13 @@ window.__ModuleLoader__.load({
200
209
  uiSop: "Settings interface opacity",
201
210
  uiChatRegion: "Conversation text frame",
202
211
  uiTrajectory: "Trajectory view",
212
+ uiPanelRegion: "better-sidebar workbench",
213
+ uiProduced: "Produced / highlights",
203
214
  bgTitle: "Background",
204
215
  bgChoose: "Choose image or video",
205
216
  bgRemove: "Remove background",
206
217
  bgFromUrl: "From URL",
207
- bgUrlPlaceholder: "Paste an image URL https://…",
218
+ bgUrlPlaceholder: "Paste an image or video URL https://…",
208
219
  bgUrlApply: "Apply",
209
220
  bgUrlCancel: "Cancel",
210
221
  bgUrlApplying: "Loading…",
@@ -278,6 +289,13 @@ window.__ModuleLoader__.load({
278
289
  presetDots: "Dots",
279
290
  presetWaves: "Waves",
280
291
  presetPoly: "Low poly",
292
+ presetStarfield: "Starfield",
293
+ presetRain: "Rain",
294
+ presetContour: "Contours",
295
+ presetMeta: "Fluid orbs",
296
+ bgPause: "Pause",
297
+ bgResume: "Play",
298
+ bgUrlVideoFail: "Could not fetch the video — make sure the link downloads a video file directly",
281
299
  schemeTitle: "Interface scheme",
282
300
  schemeLight: "Light",
283
301
  schemeDark: "Dark",
@@ -347,7 +365,9 @@ window.__ModuleLoader__.load({
347
365
  settings: 0,
348
366
  chat: 0,
349
367
  trajectory: 0,
350
- input: 0
368
+ input: 0,
369
+ panel: 0,
370
+ produced: 0
351
371
  },
352
372
  settingsOpacity: 1,
353
373
  wallpaperOpacity: 1,
@@ -373,6 +393,8 @@ window.__ModuleLoader__.load({
373
393
  regenerateOnReload: false,
374
394
  chatTextOpacity: 0,
375
395
  trajectoryOpacity: 1,
396
+ panelOpacity: 1,
397
+ producedOpacity: 1,
376
398
  profiles: [],
377
399
  rotation: {
378
400
  enabled: false,
@@ -408,12 +430,19 @@ window.__ModuleLoader__.load({
408
430
  let wpVideoSnapshot = null;
409
431
  /** Local blob URL backing an in-session video; revoked when replaced or cleared. */
410
432
  let wpVideoObjectUrl = null;
411
- function setWpImageUrl(url) {
412
- wpImageUrl = url;
413
- }
414
433
  function setWpUrl(url) {
415
434
  wpUrl = url;
416
435
  }
436
+ let imageRev = 0;
437
+ function bumpImageRev(url) {
438
+ if (url === null) return url;
439
+ if (!/^https?:\/\//i.test(url) && !url.startsWith("/")) return url;
440
+ imageRev++;
441
+ return `${url}${url.includes("?") ? "&" : "?"}r=${imageRev}`;
442
+ }
443
+ function setWpImageUrl(url) {
444
+ wpImageUrl = bumpImageRev(url);
445
+ }
417
446
  let videoRev = 0;
418
447
  function setWpVideoUrl(url, mime) {
419
448
  if (url === null) {
@@ -502,7 +531,9 @@ window.__ModuleLoader__.load({
502
531
  "settings",
503
532
  "chat",
504
533
  "trajectory",
505
- "input"
534
+ "input",
535
+ "panel",
536
+ "produced"
506
537
  ]) {
507
538
  const v = b[k];
508
539
  out[k] = typeof v === "number" ? Math.min(60, Math.max(0, v)) : DEFAULT_CONFIG.blurs[k];
@@ -518,6 +549,12 @@ window.__ModuleLoader__.load({
518
549
  function rSop() {
519
550
  return clamp01(cfg.settingsOpacity, DEFAULT_CONFIG.settingsOpacity);
520
551
  }
552
+ function rPanelOpacity() {
553
+ return clamp01(cfg.panelOpacity, DEFAULT_CONFIG.panelOpacity);
554
+ }
555
+ function rProducedOpacity() {
556
+ return clamp01(cfg.producedOpacity, DEFAULT_CONFIG.producedOpacity);
557
+ }
521
558
  function rBgState() {
522
559
  return cfg.bgState;
523
560
  }
@@ -578,7 +615,9 @@ window.__ModuleLoader__.load({
578
615
  wallpaperOpacity: rWop(),
579
616
  blur: rBl(),
580
617
  chatTextOpacity: rChatTextOpacity(),
581
- trajectoryOpacity: rTrajectoryOpacity()
618
+ trajectoryOpacity: rTrajectoryOpacity(),
619
+ panelOpacity: rPanelOpacity(),
620
+ producedOpacity: rProducedOpacity()
582
621
  };
583
622
  }
584
623
  /** Apply an appearance snapshot onto cfg (meta fields untouched). */
@@ -597,6 +636,8 @@ window.__ModuleLoader__.load({
597
636
  cfg.blur = typeof ap.blur === "number" ? Math.min(60, Math.max(0, ap.blur)) : DEFAULT_CONFIG.blur;
598
637
  cfg.chatTextOpacity = clamp01(ap.chatTextOpacity, DEFAULT_CONFIG.chatTextOpacity);
599
638
  cfg.trajectoryOpacity = clamp01(ap.trajectoryOpacity, DEFAULT_CONFIG.trajectoryOpacity);
639
+ cfg.panelOpacity = clamp01(ap.panelOpacity, DEFAULT_CONFIG.panelOpacity);
640
+ cfg.producedOpacity = clamp01(ap.producedOpacity, DEFAULT_CONFIG.producedOpacity);
600
641
  }
601
642
  const num = (n, def) => typeof n === "number" ? n : def;
602
643
  const cl = (n, lo, hi, def) => typeof n === "number" ? Math.min(hi, Math.max(lo, n)) : def;
@@ -627,7 +668,9 @@ window.__ModuleLoader__.load({
627
668
  wallpaperOpacity: clamp01(ac.wallpaperOpacity, DEFAULT_CONFIG.wallpaperOpacity),
628
669
  blur: num(ac.blur, DEFAULT_CONFIG.blur),
629
670
  chatTextOpacity: clamp01(ac.chatTextOpacity, DEFAULT_CONFIG.chatTextOpacity),
630
- trajectoryOpacity: clamp01(ac.trajectoryOpacity, DEFAULT_CONFIG.trajectoryOpacity)
671
+ trajectoryOpacity: clamp01(ac.trajectoryOpacity, DEFAULT_CONFIG.trajectoryOpacity),
672
+ panelOpacity: clamp01(ac.panelOpacity, DEFAULT_CONFIG.panelOpacity),
673
+ producedOpacity: clamp01(ac.producedOpacity, DEFAULT_CONFIG.producedOpacity)
631
674
  }
632
675
  });
633
676
  }
@@ -690,7 +733,9 @@ window.__ModuleLoader__.load({
690
733
  "settings",
691
734
  "chat",
692
735
  "trajectory",
693
- "input"
736
+ "input",
737
+ "panel",
738
+ "produced"
694
739
  ]) blurs[k] = num(bl[k], DEFAULT_CONFIG.blurs[k]);
695
740
  const bgType = [
696
741
  "video",
@@ -728,6 +773,8 @@ window.__ModuleLoader__.load({
728
773
  regenerateOnReload: typeof c.regenerateOnReload === "boolean" ? c.regenerateOnReload : DEFAULT_CONFIG.regenerateOnReload,
729
774
  chatTextOpacity: clamp01(c.chatTextOpacity, DEFAULT_CONFIG.chatTextOpacity),
730
775
  trajectoryOpacity: clamp01(c.trajectoryOpacity, DEFAULT_CONFIG.trajectoryOpacity),
776
+ panelOpacity: clamp01(c.panelOpacity, DEFAULT_CONFIG.panelOpacity),
777
+ producedOpacity: clamp01(c.producedOpacity, DEFAULT_CONFIG.producedOpacity),
731
778
  profiles: adoptProfiles(c.profiles),
732
779
  rotation: adoptRotation(c.rotation),
733
780
  schedule: adoptSchedule(c.schedule),
@@ -748,7 +795,8 @@ window.__ModuleLoader__.load({
748
795
  preset: [
749
796
  "aurora",
750
797
  "nebula",
751
- "noise"
798
+ "noise",
799
+ "starfield"
752
800
  ].includes(p.preset) ? p.preset : "aurora",
753
801
  speed: cl(p.speed, 0, 2, .3),
754
802
  scale: cl(p.scale, .3, 3, 1),
@@ -759,7 +807,10 @@ window.__ModuleLoader__.load({
759
807
  preset: [
760
808
  "dots",
761
809
  "waves",
762
- "poly"
810
+ "poly",
811
+ "rain",
812
+ "contour",
813
+ "meta"
763
814
  ].includes(p.preset) ? p.preset : "dots",
764
815
  density: cl(p.density, 0, 1, .5),
765
816
  scale: cl(p.scale, .3, 3, 1),
@@ -771,6 +822,10 @@ window.__ModuleLoader__.load({
771
822
  const RPC_CHANNEL = "/dsh-any-background";
772
823
  /** Same-origin serve URL of the persisted video (enough for <video src>/fetch). */
773
824
  const VIDEO_SERVE_URL = "/dsh-any-background/video";
825
+ /** Same-origin serve URL of the persisted wallpaper (native <img> loading). */
826
+ const WALLPAPER_SERVE_URL = "/dsh-any-background/wallpaper";
827
+ /** Raw-bytes upload endpoint for the wallpaper slot (no base64 inflation). */
828
+ const WALLPAPER_UPLOAD_URL = "/dsh-any-background/wallpaper/upload";
774
829
  /** HTTP route new videos are POSTed to as raw bytes (see uploadVideo). */
775
830
  const VIDEO_UPLOAD_URL = "/dsh-any-background/video/upload";
776
831
  const RPC_NS = "dshAnyBackground";
@@ -810,19 +865,20 @@ window.__ModuleLoader__.load({
810
865
  function persistConfig() {
811
866
  rpcCall("writeConfig", { config: cfg });
812
867
  }
813
- /** Load the persisted theme (config + wallpaper + video URL) from the node half.
814
- * Resolves true when the server advanced a due wallpaper rotation during the
815
- * read — the restored wallpaper is then already the new pick. */
868
+ /** Load the persisted theme (config + wallpaper URL + video URL) from the node
869
+ * half. Image and video both travel as serve URLs — never bytes — so this RPC
870
+ * stays tiny. Resolves true when the server advanced a due wallpaper rotation
871
+ * during the read — the restored wallpaper is then already the new pick. */
816
872
  async function loadPersisted() {
817
873
  const data = await rpcCall("read", {});
818
874
  if (data && typeof data === "object") {
819
875
  const d = data;
820
876
  if (d.config) adoptConfig(d.config);
821
- if (typeof d.wallpaper === "string") setWpImageUrl(d.wallpaper);
822
- else if (d.wallpaper === null) setWpImageUrl(null);
877
+ if (typeof d.wallpaperUrl === "string") setWpImageUrl(d.wallpaperUrl);
878
+ else if (d.wallpaperUrl === null) setWpImageUrl(null);
823
879
  if (typeof d.videoUrl === "string") setWpVideoUrl(d.videoUrl, cfg.videoMime);
824
880
  else if (d.videoUrl === null) setWpVideoUrl(null, null);
825
- if (cfg.backgroundType === "image") setWpUrl(d.wallpaper === null ? null : d.wallpaper);
881
+ if (cfg.backgroundType === "image") setWpUrl(rWpImage());
826
882
  return d.rotated === true;
827
883
  }
828
884
  return false;
@@ -837,7 +893,7 @@ window.__ModuleLoader__.load({
837
893
  return await rpcCall("setVideo", { dataUrl }) === true;
838
894
  }
839
895
  /** Download a wallpaper from a network URL and persist it into the local slot
840
- * (the host replaces wallpaper.jpg). Returns the freshly stored data-URL on
896
+ * (the host replaces wallpaper.jpg). Returns the freshly stored serve URL on
841
897
  * success, or the host's failure message. */
842
898
  async function setWallpaperFromUrl(url) {
843
899
  if (!rpcCallFn) return {
@@ -864,7 +920,40 @@ window.__ModuleLoader__.load({
864
920
  const v = res.value;
865
921
  return v?.ok === true ? {
866
922
  ok: true,
867
- dataUrl: v.dataUrl ?? null
923
+ wallpaperUrl: v.wallpaperUrl ?? null
924
+ } : {
925
+ ok: false,
926
+ error: v?.error ?? "failed"
927
+ };
928
+ }
929
+ /** Download a background video from a network URL. The server streams it into
930
+ * the video slot and returns the resolved MIME for playback via the serve URL. */
931
+ async function setVideoFromUrl(url) {
932
+ if (!rpcCallFn) return {
933
+ ok: false,
934
+ error: "rpc not ready"
935
+ };
936
+ let res;
937
+ try {
938
+ res = await rpcCallFn(rpcEndpoint("setVideoUrl"), { url });
939
+ } catch (e) {
940
+ return {
941
+ ok: false,
942
+ error: e instanceof Error ? e.message : String(e)
943
+ };
944
+ }
945
+ if (!res) return {
946
+ ok: false,
947
+ error: "no response"
948
+ };
949
+ if (res.ok !== true) return {
950
+ ok: false,
951
+ error: res.error?.message ?? "request failed"
952
+ };
953
+ const v = res.value;
954
+ return v?.ok === true ? {
955
+ ok: true,
956
+ mime: v.mime ?? "video/mp4"
868
957
  } : {
869
958
  ok: false,
870
959
  error: v?.error ?? "failed"
@@ -884,6 +973,20 @@ window.__ModuleLoader__.load({
884
973
  return false;
885
974
  }
886
975
  }
976
+ /** Upload a wallpaper's raw bytes over HTTP — same streaming model as videos,
977
+ * original pixels preserved, zero base64 round-trips. */
978
+ async function uploadWallpaper(blob) {
979
+ try {
980
+ return (await fetch(WALLPAPER_UPLOAD_URL, {
981
+ method: "POST",
982
+ headers: { "Content-Type": blob.type || "image/jpeg" },
983
+ body: blob
984
+ })).ok;
985
+ } catch (e) {
986
+ console.warn("dsh-any-background: wallpaper upload failed", e);
987
+ return false;
988
+ }
989
+ }
887
990
  /** Add an image (data URL + small thumbnail) to the server-side rotation pool. */
888
991
  async function rotationAdd(dataUrl, thumb) {
889
992
  if (!rpcCallFn) return {
@@ -934,7 +1037,7 @@ window.__ModuleLoader__.load({
934
1037
  }
935
1038
  }
936
1039
  /** Activate a rotation item: the server copies its bytes into the wallpaper
937
- * slot and returns the freshly sniffed data URL for immediate display. */
1040
+ * slot and returns the serve URL for immediate display. */
938
1041
  async function rotationActivate(index) {
939
1042
  if (!rpcCallFn) return {
940
1043
  ok: false,
@@ -958,6 +1061,88 @@ window.__ModuleLoader__.load({
958
1061
  }
959
1062
  }
960
1063
  //#endregion
1064
+ //#region src/client/utils/image.ts
1065
+ /**
1066
+ * Read a chosen image file as a data URL WITHOUT re-encoding: the original
1067
+ * pixels are kept as-is (no canvas downscale / JPEG re-compression), so the
1068
+ * wallpaper is stored and displayed at full fidelity. The tradeoff is a larger
1069
+ * payload over the RPC channel and on disk for big images.
1070
+ */
1071
+ function readImg(file, cb) {
1072
+ const r = new FileReader();
1073
+ r.onerror = () => cb(null);
1074
+ r.onload = () => cb(r.result);
1075
+ r.readAsDataURL(file);
1076
+ }
1077
+ /** Promised readImg variant for async flows. */
1078
+ function readImgAsync(file) {
1079
+ return new Promise((resolve) => readImg(file, resolve));
1080
+ }
1081
+ /** Blob → data URL (used when a local asset must be embedded, e.g. theme export). */
1082
+ function blobToDataUrl(blob) {
1083
+ return new Promise((resolve, reject) => {
1084
+ const fr = new FileReader();
1085
+ fr.onload = () => resolve(fr.result);
1086
+ fr.onerror = () => reject(fr.error);
1087
+ fr.readAsDataURL(blob);
1088
+ });
1089
+ }
1090
+ const IMG_CACHE_MAX = 6;
1091
+ const imgCache = /* @__PURE__ */ new Map();
1092
+ /** Decode an image URL once and share the element across callers (LRU-capped). */
1093
+ function loadImage(url) {
1094
+ const hit = imgCache.get(url);
1095
+ if (hit) {
1096
+ imgCache.delete(url);
1097
+ imgCache.set(url, hit);
1098
+ return hit;
1099
+ }
1100
+ const p = new Promise((resolve) => {
1101
+ const img = new Image();
1102
+ img.onerror = () => resolve(null);
1103
+ img.onload = () => resolve(img);
1104
+ img.src = url;
1105
+ });
1106
+ imgCache.set(url, p);
1107
+ while (imgCache.size > IMG_CACHE_MAX) {
1108
+ const oldest = imgCache.keys().next().value;
1109
+ if (oldest !== void 0) imgCache.delete(oldest);
1110
+ }
1111
+ return p;
1112
+ }
1113
+ /** Build a small JPEG thumbnail (rotation-pool picker preview). Resolves null
1114
+ * when the image cannot be decoded. */
1115
+ function makeThumb(dataUrl, maxSide = 96) {
1116
+ return new Promise((resolve) => {
1117
+ const img = new Image();
1118
+ img.onerror = () => resolve(null);
1119
+ img.onload = () => {
1120
+ try {
1121
+ const iw = img.naturalWidth || img.width;
1122
+ const ih = img.naturalHeight || img.height;
1123
+ if (iw <= 0 || ih <= 0) {
1124
+ resolve(null);
1125
+ return;
1126
+ }
1127
+ const k = Math.min(1, maxSide / Math.max(iw, ih));
1128
+ const c = document.createElement("canvas");
1129
+ c.width = Math.max(1, Math.round(iw * k));
1130
+ c.height = Math.max(1, Math.round(ih * k));
1131
+ const g = c.getContext("2d");
1132
+ if (!g) {
1133
+ resolve(null);
1134
+ return;
1135
+ }
1136
+ g.drawImage(img, 0, 0, c.width, c.height);
1137
+ resolve(c.toDataURL("image/jpeg", .72));
1138
+ } catch {
1139
+ resolve(null);
1140
+ }
1141
+ };
1142
+ img.src = dataUrl;
1143
+ });
1144
+ }
1145
+ //#endregion
961
1146
  //#region src/client/utils/color.ts
962
1147
  function hsvToHsl(h, s, v) {
963
1148
  const l = v * (1 - s / 2);
@@ -1164,124 +1349,119 @@ window.__ModuleLoader__.load({
1164
1349
  return rgbToHsl(b.r / b.count, b.g / b.count, b.b / b.count);
1165
1350
  }
1166
1351
  function extractWallpaperPalette(dataUrl, bgState) {
1167
- return new Promise((resolve) => {
1168
- const img = new Image();
1169
- img.onerror = () => resolve(null);
1170
- img.onload = () => {
1171
- try {
1172
- const iw = img.naturalWidth || img.width;
1173
- const ih = img.naturalHeight || img.height;
1174
- const bg = bgState;
1175
- let sx = 0, sy = 0, sw = iw, sh = ih;
1176
- if (bg.iw === iw && bg.ih === ih && bg.iw > 0) {
1177
- const fit = Math.min(window.innerWidth / iw, window.innerHeight / ih);
1178
- const w = iw * fit * bg.zoom;
1179
- const h = ih * fit * bg.zoom;
1180
- sx = Math.max(0, bg.x * window.innerWidth - w / 2);
1181
- sy = Math.max(0, bg.y * window.innerHeight - h / 2);
1182
- sw = Math.min(iw - sx, w);
1183
- sh = Math.min(ih - sy, h);
1184
- if (sw <= 0 || sh <= 0) {
1185
- sx = 0;
1186
- sy = 0;
1187
- sw = iw;
1188
- sh = ih;
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;
1352
+ return loadImage(dataUrl).then((img) => {
1353
+ if (!img) return null;
1354
+ try {
1355
+ const iw = img.naturalWidth || img.width;
1356
+ const ih = img.naturalHeight || img.height;
1357
+ const bg = bgState;
1358
+ let sx = 0, sy = 0, sw = iw, sh = ih;
1359
+ if (bg.iw === iw && bg.ih === ih && bg.iw > 0) {
1360
+ const fit = Math.min(window.innerWidth / iw, window.innerHeight / ih);
1361
+ const w = iw * fit * bg.zoom;
1362
+ const h = ih * fit * bg.zoom;
1363
+ const px = bg.x * window.innerWidth - w / 2;
1364
+ const py = bg.y * window.innerHeight - h / 2;
1365
+ sx = Math.max(0, -px);
1366
+ sy = Math.max(0, -py);
1367
+ sw = Math.min(iw - sx, window.innerWidth - px - sx);
1368
+ sh = Math.min(ih - sy, window.innerHeight - py - sy);
1369
+ if (sw <= 0 || sh <= 0) {
1370
+ sx = 0;
1371
+ sy = 0;
1372
+ sw = iw;
1373
+ sh = ih;
1229
1374
  }
1230
- buckets.sort((a, b) => b.count * (.5 + b.s) - a.count * (.5 + a.s));
1231
- const primary = bucketsToHsl(buckets[0]);
1232
- const primaryHue = primary[0];
1233
- let secondary = primary;
1234
- for (const b of buckets.slice(1)) {
1235
- const [h] = bucketsToHsl(b);
1236
- if (Math.abs((h - primaryHue + 540) % 360 - 180) > 30) {
1237
- secondary = bucketsToHsl(b);
1238
- break;
1239
- }
1375
+ }
1376
+ const c = document.createElement("canvas");
1377
+ c.width = EXTRACT_SIDE;
1378
+ c.height = EXTRACT_SIDE;
1379
+ const g = c.getContext("2d", { willReadFrequently: true });
1380
+ g.drawImage(img, sx, sy, sw, sh, 0, 0, EXTRACT_SIDE, EXTRACT_SIDE);
1381
+ const px = g.getImageData(0, 0, EXTRACT_SIDE, EXTRACT_SIDE).data;
1382
+ const counts = /* @__PURE__ */ new Uint32Array(4096);
1383
+ const sums = /* @__PURE__ */ new Float64Array(12288);
1384
+ let totalLum = 0;
1385
+ let sampled = 0;
1386
+ for (let i = 0; i < px.length; i += 4) {
1387
+ const r = px[i], gg = px[i + 1], b = px[i + 2];
1388
+ const max = Math.max(r, gg, b), min = Math.min(r, gg, b);
1389
+ const v = max / 255;
1390
+ const s = max === 0 ? 0 : (max - min) / max;
1391
+ totalLum += (.2126 * r + .7152 * gg + .0722 * b) / 255;
1392
+ sampled++;
1393
+ if (s < .08 || v < .12 || v > .97) continue;
1394
+ const key = r >> 4 << 8 | gg >> 4 << 4 | b >> 4;
1395
+ counts[key]++;
1396
+ sums[key * 3] += r;
1397
+ sums[key * 3 + 1] += gg;
1398
+ sums[key * 3 + 2] += b;
1399
+ }
1400
+ const buckets = [];
1401
+ for (let k = 0; k < 4096; k++) {
1402
+ if (counts[k] === 0) continue;
1403
+ buckets.push({
1404
+ count: counts[k],
1405
+ r: sums[k * 3],
1406
+ g: sums[k * 3 + 1],
1407
+ b: sums[k * 3 + 2],
1408
+ 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
1409
+ });
1410
+ }
1411
+ if (buckets.length === 0) return null;
1412
+ buckets.sort((a, b) => b.count * (.5 + b.s) - a.count * (.5 + a.s));
1413
+ const primary = bucketsToHsl(buckets[0]);
1414
+ const primaryHue = primary[0];
1415
+ let secondary = primary;
1416
+ for (const b of buckets.slice(1)) {
1417
+ const [h] = bucketsToHsl(b);
1418
+ if (Math.abs((h - primaryHue + 540) % 360 - 180) > 30) {
1419
+ secondary = bucketsToHsl(b);
1420
+ break;
1240
1421
  }
1241
- let tertiary = [
1242
- (primaryHue + 180) % 360,
1243
- Math.min(.7, primary[1]),
1244
- Math.min(.7, primary[2])
1245
- ];
1246
- for (const b of buckets.slice(1)) {
1247
- const [h] = bucketsToHsl(b);
1248
- const sep = Math.abs((h - primaryHue + 540) % 360 - 180);
1249
- if (sep > 90 && sep < 150) {
1250
- tertiary = bucketsToHsl(b);
1251
- break;
1252
- }
1422
+ }
1423
+ let tertiary = [
1424
+ (primaryHue + 180) % 360,
1425
+ Math.min(.7, primary[1]),
1426
+ Math.min(.7, primary[2])
1427
+ ];
1428
+ for (const b of buckets.slice(1)) {
1429
+ const [h] = bucketsToHsl(b);
1430
+ const sep = Math.abs((h - primaryHue + 540) % 360 - 180);
1431
+ if (sep > 90 && sep < 150) {
1432
+ tertiary = bucketsToHsl(b);
1433
+ break;
1253
1434
  }
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
1435
  }
1283
- };
1284
- img.src = dataUrl;
1436
+ const surface = [
1437
+ primaryHue,
1438
+ Math.min(.06, primary[1] * .3),
1439
+ primary[2]
1440
+ ];
1441
+ const luminance = sampled > 0 ? totalLum / sampled : primary[2];
1442
+ primary[2] = luminance < .5 ? Math.min(.44, Math.max(.2, primary[2])) : Math.max(.6, Math.min(.82, primary[2]));
1443
+ return {
1444
+ primary: [
1445
+ primary[0],
1446
+ Math.min(.9, Math.max(.15, primary[1])),
1447
+ primary[2]
1448
+ ],
1449
+ secondary: [
1450
+ secondary[0],
1451
+ Math.min(.85, Math.max(.2, secondary[1])),
1452
+ Math.min(.75, Math.max(.35, secondary[2]))
1453
+ ],
1454
+ tertiary: [
1455
+ tertiary[0],
1456
+ Math.min(.8, Math.max(.2, tertiary[1])),
1457
+ Math.min(.7, Math.max(.35, tertiary[2]))
1458
+ ],
1459
+ surface,
1460
+ luminance
1461
+ };
1462
+ } catch {
1463
+ return null;
1464
+ }
1285
1465
  });
1286
1466
  }
1287
1467
  /** Backward-compatible single-color extraction: returns the primary HSL. */
@@ -1294,26 +1474,22 @@ window.__ModuleLoader__.load({
1294
1474
  * reads dark (use white fonts), false when light (use black fonts), or null
1295
1475
  * when the frame cannot be decoded. */
1296
1476
  function analyzeFrameDark(dataUrl) {
1297
- return new Promise((resolve) => {
1298
- const img = new Image();
1299
- img.onerror = () => resolve(null);
1300
- img.onload = () => {
1301
- try {
1302
- const c = document.createElement("canvas");
1303
- c.width = ANALYZE_SIDE;
1304
- c.height = ANALYZE_SIDE;
1305
- const g = c.getContext("2d", { willReadFrequently: true });
1306
- g.drawImage(img, 0, 0, ANALYZE_SIDE, ANALYZE_SIDE);
1307
- const px = g.getImageData(0, 0, ANALYZE_SIDE, ANALYZE_SIDE).data;
1308
- let lum = 0;
1309
- const count = px.length / 4;
1310
- for (let i = 0; i < px.length; i += 4) lum += .2126 * px[i] + .7152 * px[i + 1] + .0722 * px[i + 2];
1311
- resolve(lum / count / 255 < .5);
1312
- } catch {
1313
- resolve(null);
1314
- }
1315
- };
1316
- img.src = dataUrl;
1477
+ return loadImage(dataUrl).then((img) => {
1478
+ if (!img) return null;
1479
+ try {
1480
+ const c = document.createElement("canvas");
1481
+ c.width = ANALYZE_SIDE;
1482
+ c.height = ANALYZE_SIDE;
1483
+ const g = c.getContext("2d", { willReadFrequently: true });
1484
+ g.drawImage(img, 0, 0, ANALYZE_SIDE, ANALYZE_SIDE);
1485
+ const px = g.getImageData(0, 0, ANALYZE_SIDE, ANALYZE_SIDE).data;
1486
+ let lum = 0;
1487
+ const count = px.length / 4;
1488
+ for (let i = 0; i < px.length; i += 4) lum += .2126 * px[i] + .7152 * px[i + 1] + .0722 * px[i + 2];
1489
+ return lum / count / 255 < .5;
1490
+ } catch {
1491
+ return null;
1492
+ }
1317
1493
  });
1318
1494
  }
1319
1495
  /** HSL (h 0-360, s/l 0-1) → RGB (0-255 integers). */
@@ -1355,20 +1531,42 @@ window.__ModuleLoader__.load({
1355
1531
  function newRaf(canvas, draw) {
1356
1532
  canvas.dataset.dshAnyCanvas = "1";
1357
1533
  let running = true;
1534
+ let paused = false;
1535
+ let rafId = 0;
1358
1536
  let last = 0;
1359
1537
  draw();
1538
+ if (typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches) return {
1539
+ stop: () => {
1540
+ running = false;
1541
+ },
1542
+ pause: () => void 0,
1543
+ resume: () => void 0
1544
+ };
1360
1545
  const loop = (ts) => {
1361
- if (!running) return;
1546
+ if (!running || paused) return;
1362
1547
  if (ts - last >= FRAME_MS) {
1363
1548
  last = ts;
1364
1549
  draw();
1365
1550
  }
1366
- requestAnimationFrame(loop);
1551
+ rafId = requestAnimationFrame(loop);
1552
+ };
1553
+ rafId = requestAnimationFrame(loop);
1554
+ return {
1555
+ stop: () => {
1556
+ running = false;
1557
+ cancelAnimationFrame(rafId);
1558
+ },
1559
+ pause: () => {
1560
+ paused = true;
1561
+ cancelAnimationFrame(rafId);
1562
+ },
1563
+ resume: () => {
1564
+ if (!running || !paused) return;
1565
+ paused = false;
1566
+ last = 0;
1567
+ rafId = requestAnimationFrame(loop);
1568
+ }
1367
1569
  };
1368
- requestAnimationFrame(loop);
1369
- return { stop: () => {
1370
- running = false;
1371
- } };
1372
1570
  }
1373
1571
  function createCanvas() {
1374
1572
  const c = document.createElement("canvas");
@@ -1383,7 +1581,6 @@ window.__ModuleLoader__.load({
1383
1581
  };
1384
1582
  }
1385
1583
  function fitLiveCanvas(c) {
1386
- if (c.dataset.dshAnyStatic === "1") return;
1387
1584
  const { w, h } = liveSize();
1388
1585
  if (c.width !== w || c.height !== h) {
1389
1586
  c.width = w;
@@ -1680,6 +1877,43 @@ window.__ModuleLoader__.load({
1680
1877
  col = hueRotate(col, u_seed * 6.28318530718);
1681
1878
  gl_FragColor = vec4(col, 1.0);
1682
1879
  }
1880
+ `;
1881
+ if (preset === "starfield") return common + `
1882
+ float hash21(vec2 p) {
1883
+ p = fract(p * vec2(234.34, 435.345));
1884
+ p += dot(p, p + 34.23);
1885
+ return fract(p.x * p.y);
1886
+ }
1887
+ vec3 starLayer(vec2 uv, float t, float density) {
1888
+ vec3 col = vec3(0.0);
1889
+ vec2 grid = uv * density;
1890
+ vec2 cell = floor(grid);
1891
+ vec2 f = fract(grid) - 0.5;
1892
+ float h = hash21(cell);
1893
+ if (h > 0.8) {
1894
+ vec2 offs = (vec2(hash21(cell + 1.3), hash21(cell + 2.7)) - 0.5) * 0.7;
1895
+ float d = length(f - offs);
1896
+ float tw = 0.35 + 0.65 * sin(t * (1.0 + h * 2.5) + h * 40.0);
1897
+ col += vec3(0.85, 0.92, 1.0) * smoothstep(0.07, 0.0, d) * max(0.0, tw);
1898
+ }
1899
+ return col;
1900
+ }
1901
+ void main() {
1902
+ vec2 uv = gl_FragCoord.xy / u_resolution;
1903
+ vec2 p = (gl_FragCoord.xy - 0.5 * u_resolution) / u_resolution.y;
1904
+ float t = u_time * 0.5;
1905
+ vec3 col = mix(vec3(0.012, 0.014, 0.035), vec3(0.035, 0.035, 0.08), uv.y);
1906
+ // Faint drifting nebula veil behind the stars.
1907
+ float n = fbm(vec3(p * 2.2 * u_scale, t * 0.05));
1908
+ col += vec3(0.05, 0.045, 0.11) * smoothstep(0.05, 0.85, n);
1909
+ vec3 stars = vec3(0.0);
1910
+ stars += starLayer(p + vec2(t * 0.006, 0.0), t, 13.0 * u_scale);
1911
+ stars += starLayer(p * 1.8 + vec2(t * 0.013, 3.0), t * 1.25, 27.0 * u_scale);
1912
+ stars += starLayer(p * 3.1 + vec2(t * 0.021, 7.0), t * 0.85, 46.0 * u_scale);
1913
+ col += stars;
1914
+ col = hueRotate(col, u_seed * 6.28318530718);
1915
+ gl_FragColor = vec4(col, 1.0);
1916
+ }
1683
1917
  `;
1684
1918
  return common + `
1685
1919
  void main() {
@@ -1703,8 +1937,154 @@ window.__ModuleLoader__.load({
1703
1937
  function createPatternBg(params, canvas) {
1704
1938
  if (params.preset === "waves") return createWaves(params, canvas);
1705
1939
  if (params.preset === "poly") return createLowPoly(params, canvas);
1940
+ if (params.preset === "rain") return createRain(params, canvas);
1941
+ if (params.preset === "contour") return createContour(params, canvas);
1942
+ if (params.preset === "meta") return createMeta(params, canvas);
1706
1943
  return createDots(params, canvas);
1707
1944
  }
1945
+ function createRain(params, canvas) {
1946
+ const c = canvas ?? createCanvas();
1947
+ fitLiveCanvas(c);
1948
+ const g = c.getContext("2d", { alpha: false });
1949
+ const rng = createRng(params.seed);
1950
+ const dark = rng() < .5;
1951
+ const hue = Math.round(rng() * 360);
1952
+ const count = Math.round(30 + params.density * 150);
1953
+ const drops = [];
1954
+ for (let i = 0; i < count; i++) drops.push({
1955
+ x: rng(),
1956
+ y: rng(),
1957
+ len: (.035 + rng() * .075) * (.7 + params.scale * .5),
1958
+ speed: (.25 + rng() * .55) * (.6 + params.scale * .5),
1959
+ sat: Math.round(30 + rng() * 40),
1960
+ lit: dark ? Math.round(55 + rng() * 25) : Math.round(40 + rng() * 25),
1961
+ alpha: .12 + rng() * .28,
1962
+ width: .8 + rng() * 1.4
1963
+ });
1964
+ let t = 0;
1965
+ const draw = () => {
1966
+ fitLiveCanvas(c);
1967
+ const w = c.width, h = c.height;
1968
+ g.fillStyle = dark ? "#07080c" : "#f4f6f9";
1969
+ g.fillRect(0, 0, w, h);
1970
+ for (const d of drops) {
1971
+ const yy = ((d.y + t * d.speed) % 1 + 1) % 1;
1972
+ const x = d.x * w;
1973
+ const y0 = yy * h;
1974
+ const y1 = y0 - d.len * h;
1975
+ const grad = g.createLinearGradient(x, y0, x, y1);
1976
+ grad.addColorStop(0, `hsla(${hue},${d.sat}%,${d.lit}%,${d.alpha.toFixed(2)})`);
1977
+ grad.addColorStop(1, "hsla(0,0%,0%,0)");
1978
+ g.strokeStyle = grad;
1979
+ g.lineWidth = d.width;
1980
+ g.beginPath();
1981
+ g.moveTo(x, y0);
1982
+ g.lineTo(x, y1);
1983
+ g.stroke();
1984
+ }
1985
+ t += .016;
1986
+ };
1987
+ return {
1988
+ canvas: c,
1989
+ ...newRaf(c, draw),
1990
+ snapshot: () => c.toDataURL("image/jpeg", .94)
1991
+ };
1992
+ }
1993
+ function createContour(params, canvas) {
1994
+ const c = canvas ?? createCanvas();
1995
+ fitLiveCanvas(c);
1996
+ const g = c.getContext("2d", { alpha: false });
1997
+ const noise = createNoise(params.seed);
1998
+ const rng = createRng(params.seed + 1013);
1999
+ const dark = rng() < .5;
2000
+ const hue = Math.round(rng() * 360);
2001
+ const layers = Math.round(16 + params.density * 46);
2002
+ const freq = .0016 / params.scale;
2003
+ const amp = .055 * params.scale;
2004
+ let t = 0;
2005
+ const draw = () => {
2006
+ fitLiveCanvas(c);
2007
+ const w = c.width, h = c.height;
2008
+ g.fillStyle = dark ? "#0a0b0e" : "#f6f7fa";
2009
+ g.fillRect(0, 0, w, h);
2010
+ const samples = Math.max(60, Math.floor(w / 8));
2011
+ for (let i = 0; i <= layers; i++) {
2012
+ const base = i / layers;
2013
+ const shift = t * .05;
2014
+ g.beginPath();
2015
+ for (let s = 0; s <= samples; s++) {
2016
+ const fx = s / samples;
2017
+ const y = (base + (noise(fx * 60 * freq * 400, i * .22 + shift) - .5) * amp) * h;
2018
+ if (s === 0) g.moveTo(0, y);
2019
+ else g.lineTo(fx * w, y);
2020
+ }
2021
+ const lit = dark ? 20 + i / layers * 22 : 62 - i / layers * 18;
2022
+ g.strokeStyle = `hsla(${(hue + i * 2) % 360},30%,${lit}%,${(.25 + .2 * (i / layers)).toFixed(2)})`;
2023
+ g.lineWidth = 1;
2024
+ g.stroke();
2025
+ }
2026
+ t += .032;
2027
+ };
2028
+ return {
2029
+ canvas: c,
2030
+ ...newRaf(c, draw),
2031
+ snapshot: () => c.toDataURL("image/jpeg", .94)
2032
+ };
2033
+ }
2034
+ function createMeta(params, canvas) {
2035
+ const c = canvas ?? createCanvas();
2036
+ fitLiveCanvas(c);
2037
+ const g = c.getContext("2d", { alpha: false });
2038
+ const rng = createRng(params.seed);
2039
+ const dark = rng() < .5;
2040
+ const baseHue = Math.round(rng() * 360);
2041
+ const count = Math.round(5 + params.density * 11);
2042
+ const blobs = [];
2043
+ for (let i = 0; i < count; i++) blobs.push({
2044
+ x: .15 + rng() * .7,
2045
+ y: .15 + rng() * .7,
2046
+ r: (.09 + rng() * .14) * params.scale,
2047
+ ax: .08 + rng() * .22,
2048
+ ay: .08 + rng() * .22,
2049
+ speed: .15 + rng() * .3,
2050
+ phase: rng() * Math.PI * 2,
2051
+ hue: (baseHue + rng() * 70) % 360
2052
+ });
2053
+ let t = 0;
2054
+ const draw = () => {
2055
+ fitLiveCanvas(c);
2056
+ const w = c.width, h = c.height;
2057
+ g.fillStyle = dark ? "#08090d" : "#f5f6f9";
2058
+ g.fillRect(0, 0, w, h);
2059
+ g.globalCompositeOperation = dark ? "lighter" : "multiply";
2060
+ for (const b of blobs) {
2061
+ const cx = (b.x + Math.sin(t * b.speed + b.phase) * b.ax) * w;
2062
+ const cy = (b.y + Math.cos(t * b.speed * .83 + b.phase * 1.7) * b.ay) * h;
2063
+ const r = b.r * Math.min(w, h) * (.9 + .2 * Math.sin(t * b.speed + b.phase));
2064
+ const rad = g.createRadialGradient(cx, cy, 0, cx, cy, r);
2065
+ if (dark) {
2066
+ rad.addColorStop(0, `hsla(${b.hue},65%,58%,0.55)`);
2067
+ rad.addColorStop(.7, `hsla(${b.hue},60%,45%,0.18)`);
2068
+ rad.addColorStop(1, "hsla(0,0%,0%,0)");
2069
+ } else {
2070
+ rad.addColorStop(0, `hsla(${b.hue},55%,70%,0.5)`);
2071
+ rad.addColorStop(.7, `hsla(${b.hue},50%,80%,0.2)`);
2072
+ rad.addColorStop(1, "hsla(0,0%,100%,0)");
2073
+ }
2074
+ g.fillStyle = rad;
2075
+ g.beginPath();
2076
+ g.arc(cx, cy, r, 0, Math.PI * 2);
2077
+ g.fill();
2078
+ }
2079
+ g.globalCompositeOperation = "source-over";
2080
+ t += .03;
2081
+ };
2082
+ return {
2083
+ canvas: c,
2084
+ ...newRaf(c, draw),
2085
+ snapshot: () => c.toDataURL("image/jpeg", .94)
2086
+ };
2087
+ }
1708
2088
  function createDots(params, canvas) {
1709
2089
  const c = canvas ?? createCanvas();
1710
2090
  fitLiveCanvas(c);
@@ -1909,6 +2289,14 @@ window.__ModuleLoader__.load({
1909
2289
  let wpController = null;
1910
2290
  let snapshotListener = null;
1911
2291
  let tokenStyleEl = null;
2292
+ /** Pause the live generated background's animation loop (session-only). */
2293
+ function pauseGeneratedBg() {
2294
+ wpController?.pause?.();
2295
+ }
2296
+ /** Resume the live generated background's animation loop. */
2297
+ function resumeGeneratedBg() {
2298
+ wpController?.resume?.();
2299
+ }
1912
2300
  function clearDynamicBg() {
1913
2301
  wpController?.stop();
1914
2302
  wpController?.canvas.remove();
@@ -2056,11 +2444,77 @@ window.__ModuleLoader__.load({
2056
2444
  }
2057
2445
  const SETTINGS_PANEL_SEL = "[role=\"dialog\"][aria-modal=\"true\"][aria-labelledby]";
2058
2446
  const SETTINGS_STYLE_RULE = `${SETTINGS_PANEL_SEL}{background:var(--dsh-any-bg-settings-surface,var(--dsw-alias-bg-layer-2));backdrop-filter:var(--dsh-any-blur-settings,none);--dsw-alias-bg-layer-1:var(--dsh-any-bg-settings-layer-1);--dsw-alias-bg-layer-2:var(--dsh-any-bg-settings-layer-2);--dsw-alias-bg-layer-3:var(--dsh-any-bg-settings-layer-3)}${SETTINGS_PANEL_SEL} .dab-card{backdrop-filter:var(--dsh-any-blur-card-panels,none);-webkit-backdrop-filter:var(--dsh-any-blur-card-panels,none)}`;
2059
- const INPUT_BLUR_RULE = "[data-composer-card],[data-cordis-panel]{-webkit-backdrop-filter:var(--dsh-any-input-blur,none);backdrop-filter:var(--dsh-any-input-blur,none)}[data-cordis-panel]{--dsw-specific-menu:var(--dsh-any-op-menu-cordis)!important}";
2447
+ const POPOVER_BLUR_RULE = "[role=\"menu\"]:not([data-dockkit-tab-menu]),[role=\"listbox\"],body > [role=\"tree\"],body > [role=\"dialog\"]:not([aria-modal=\"true\"]){backdrop-filter:var(--dsh-any-blur-card-panels,none);-webkit-backdrop-filter:var(--dsh-any-blur-card-panels,none)}";
2448
+ const INPUT_BLUR_RULE = "[data-composer-card]{position:relative;isolation:isolate}[data-composer-card]::before{content:\"\";position:absolute;inset:0;z-index:-1;pointer-events:none;border-radius:inherit;-webkit-backdrop-filter:var(--dsh-any-input-blur,none);backdrop-filter:var(--dsh-any-input-blur,none)}[data-cordis-panel]{-webkit-backdrop-filter:var(--dsh-any-input-blur,none);backdrop-filter:var(--dsh-any-input-blur,none)}[data-cordis-panel]{--dsw-specific-menu:var(--dsh-any-op-menu-cordis)!important}";
2060
2449
  function applyInputBlur(px) {
2061
2450
  if (px > 0) document.documentElement.style.setProperty("--dsh-any-input-blur", `blur(${px}px)`);
2062
2451
  else document.documentElement.style.removeProperty("--dsh-any-input-blur");
2063
2452
  }
2453
+ 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)}";
2454
+ /** Mirror of the bg-part blur on :root. --dsh-any-part-blur is element-scoped
2455
+ * to the columns, so surfaces outside their subtree (the mobile header, or
2456
+ * third-party styles) can never inherit it. */
2457
+ function applyBgBlurGlobal(px) {
2458
+ if (px > 0) document.documentElement.style.setProperty("--dsh-any-part-blur-global", `blur(${px}px)`);
2459
+ else document.documentElement.style.removeProperty("--dsh-any-part-blur-global");
2460
+ }
2461
+ const PANEL_TOKEN_RULE = "[data-dsh-bottom-panel],[data-sidebar-right-panel]{--dsw-alias-bg-base:var(--dsh-any-panel-bg-base);--dsw-alias-bg-layer-1:var(--dsh-any-panel-layer-1);--dsw-alias-bg-layer-2:var(--dsh-any-panel-layer-2);--dsw-alias-bg-layer-3:var(--dsh-any-panel-layer-3);position:fixed!important;z-index:26!important}[data-sidebar-right-panel][data-sidebar-right-panel=\"fullscreen\"]{z-index:40!important}";
2462
+ const PANEL_BLUR_RULE = "[data-dsh-bottom-panel],[data-sidebar-right-panel]{-webkit-backdrop-filter:var(--dsh-any-blur-panel,none);backdrop-filter:var(--dsh-any-blur-panel,none)}";
2463
+ const PRODUCED_RULE = "[data-code-block-content] pre,[data-code-block-banner],[data-composer-chip],:not(pre)>code{-webkit-backdrop-filter:var(--dsh-any-blur-prod,none);backdrop-filter:var(--dsh-any-blur-prod,none)}@supports (background:color-mix(in srgb,red 50%,transparent)){.md-code-block{background:transparent!important}.md-code-block>div{background-color:transparent!important}.md-code-block [data-code-block-content] pre{background-color:color-mix(in srgb,var(--dsl-code-block-background,var(--dsw-alias-markdown-code-block,transparent)) var(--dsh-any-prod-pct,100%),transparent)!important}.md-code-block [data-code-block-banner]{background-color:color-mix(in srgb,var(--dsl-code-block-banner-background-color,var(--dsw-alias-markdown-code-block-banner,transparent)) var(--dsh-any-prod-pct,100%),transparent)!important}[data-code-block-content]{--shiki-background:color-mix(in srgb,var(--dsw-alias-markdown-code-block,var(--dsl-code-block-background,transparent)) var(--dsh-any-prod-pct,100%),transparent)}:not(pre)>code{background-color:color-mix(in srgb,var(--dsw-alias-markdown-inline-code,transparent) var(--dsh-any-prod-pct,100%),transparent)!important}[data-composer-chip]>*{background-color:color-mix(in srgb,var(--dsw-alias-interactive-bg-hover,transparent) var(--dsh-any-prod-pct,100%),transparent)!important}}";
2464
+ /** The host's own default colors for the panel layer tokens, used when the
2465
+ * plugin has no palette (no picked color, no wallpaper verdict, no forced
2466
+ * scheme). Without these the panel's re-scope would resolve to invalid vars
2467
+ * and the panel would have no background — reading the host's resolved value
2468
+ * keeps the panel opaque by default and lets the slider retint it like every
2469
+ * other homepage part. Reads from the live `:root` so a custom host skin or
2470
+ * theme override wins. */
2471
+ function readHostLayerTokens() {
2472
+ if (typeof getComputedStyle === "undefined") return null;
2473
+ const root = document.documentElement;
2474
+ const cs = getComputedStyle(root);
2475
+ const base = cs.getPropertyValue("--dsw-alias-bg-base").trim();
2476
+ const layer1 = cs.getPropertyValue("--dsw-alias-bg-layer-1").trim();
2477
+ const layer2 = cs.getPropertyValue("--dsw-alias-bg-layer-2").trim();
2478
+ const layer3 = cs.getPropertyValue("--dsw-alias-bg-layer-3").trim();
2479
+ if (!base && !layer1 && !layer2 && !layer3) return null;
2480
+ return {
2481
+ base,
2482
+ layer1,
2483
+ layer2,
2484
+ layer3
2485
+ };
2486
+ }
2487
+ function applyPanelOverrides(op) {
2488
+ const tokens = paletteTokens();
2489
+ const root = document.documentElement;
2490
+ if (tokens !== null) {
2491
+ const base = tokens["--dsw-alias-bg-base"];
2492
+ const layer1 = tokens["--dsw-alias-bg-layer-1"];
2493
+ const layer2 = tokens["--dsw-alias-bg-layer-2"];
2494
+ const layer3 = tokens["--dsw-alias-bg-layer-3"];
2495
+ if (base !== void 0) root.style.setProperty("--dsh-any-panel-bg-base", toRgba(base, op));
2496
+ if (layer1 !== void 0) root.style.setProperty("--dsh-any-panel-layer-1", toRgba(layer1, op));
2497
+ if (layer2 !== void 0) root.style.setProperty("--dsh-any-panel-layer-2", toRgba(layer2, op));
2498
+ if (layer3 !== void 0) root.style.setProperty("--dsh-any-panel-layer-3", toRgba(layer3, op));
2499
+ return;
2500
+ }
2501
+ const host = readHostLayerTokens();
2502
+ if (host === null) {
2503
+ root.style.setProperty("--dsh-any-panel-bg-base", "transparent");
2504
+ root.style.setProperty("--dsh-any-panel-layer-1", "transparent");
2505
+ root.style.setProperty("--dsh-any-panel-layer-2", "transparent");
2506
+ root.style.setProperty("--dsh-any-panel-layer-3", "transparent");
2507
+ return;
2508
+ }
2509
+ if (host.base) root.style.setProperty("--dsh-any-panel-bg-base", toRgba(host.base, op));
2510
+ if (host.layer1) root.style.setProperty("--dsh-any-panel-layer-1", toRgba(host.layer1, op));
2511
+ if (host.layer2) root.style.setProperty("--dsh-any-panel-layer-2", toRgba(host.layer2, op));
2512
+ if (host.layer3) root.style.setProperty("--dsh-any-panel-layer-3", toRgba(host.layer3, op));
2513
+ }
2514
+ function applyPanelBlur(px) {
2515
+ if (px > 0) document.documentElement.style.setProperty("--dsh-any-blur-panel", `blur(${px}px)`);
2516
+ else document.documentElement.style.removeProperty("--dsh-any-blur-panel");
2517
+ }
2064
2518
  function applySettingsOverrides(op) {
2065
2519
  const [h, s, l] = rColor();
2066
2520
  const tokens = genTokens(h, s, l, rColorScheme()).tokens;
@@ -2099,14 +2553,16 @@ window.__ModuleLoader__.load({
2099
2553
  return;
2100
2554
  }
2101
2555
  const url = rWp();
2102
- if (url) extractWallpaperColor(url, cfg.backgroundType === "video" ? rVideoBgState() : rBgState()).then((hsl) => {
2103
- if (hsl) {
2104
- cfg.color = hsl;
2105
- colorAdoptedListener?.(hsl);
2106
- }
2556
+ if (url) {
2107
2557
  applyWp();
2108
- });
2109
- else applyWp();
2558
+ extractWallpaperColor(url, cfg.backgroundType === "video" ? rVideoBgState() : rBgState()).then((hsl) => {
2559
+ if (hsl && rWp() === url) {
2560
+ cfg.color = hsl;
2561
+ colorAdoptedListener?.(hsl);
2562
+ }
2563
+ applyWp();
2564
+ });
2565
+ } else applyWp();
2110
2566
  }
2111
2567
  /** Switch the background source type. For generated types a new live canvas is
2112
2568
  * attached to the wallpaper layer and a snapshot is kept for the store/preview. */
@@ -2176,7 +2632,7 @@ window.__ModuleLoader__.load({
2176
2632
  let frameEl = null;
2177
2633
  let sidebarEl = null;
2178
2634
  let centerEl = null;
2179
- let detailsEl = null;
2635
+ let rightEl = null;
2180
2636
  const PART_BLUR_CLASS = "dab-part-blur";
2181
2637
  const PART_UNDERLAY_CLASS = "dab-part-underlay";
2182
2638
  const PART_BLUR_RULE = `${PART_BLUR_CLASS}{isolation:isolate}.${PART_UNDERLAY_CLASS}{position:absolute;inset:0;z-index:-1;pointer-events:none;border-radius:inherit;backdrop-filter:var(--dsh-any-part-blur,none);-webkit-backdrop-filter:var(--dsh-any-part-blur,none)}`;
@@ -2188,16 +2644,32 @@ window.__ModuleLoader__.load({
2188
2644
  partBlurStyleEl.textContent = PART_BLUR_RULE;
2189
2645
  document.head.appendChild(partBlurStyleEl);
2190
2646
  }
2647
+ /** Find one direct child of `frame` whose `[data-rightbar-col]` marker matches.
2648
+ * The rightbar is its own grid track, NOT a "details" twin of the center
2649
+ * column, so it must never receive the main-bg tint or blur underlay. */
2650
+ function findRightbarInFrame(frame) {
2651
+ for (let i = 0; i < frame.children.length; i++) {
2652
+ const child = frame.children[i];
2653
+ if (child instanceof HTMLElement && child.dataset.rightbarCol !== void 0) return child;
2654
+ }
2655
+ return null;
2656
+ }
2191
2657
  function discoverParts() {
2192
2658
  const overlay = document.querySelector("[data-shell-overlay]");
2193
2659
  if (overlay === null) return;
2194
2660
  const frame = overlay.parentElement;
2195
2661
  if (frame === null) return;
2196
2662
  frameEl = frame;
2663
+ rightEl = findRightbarInFrame(frame);
2197
2664
  const idx = Array.from(frame.children).indexOf(overlay);
2198
- sidebarEl = frame.children[idx - 3] ?? null;
2199
- centerEl = frame.children[idx - 2] ?? null;
2200
- detailsEl = frame.children[idx - 1] ?? null;
2665
+ if (rightEl !== null) {
2666
+ const rightIdx = Array.from(frame.children).indexOf(rightEl);
2667
+ sidebarEl = frame.children[rightIdx - 2] ?? null;
2668
+ centerEl = frame.children[rightIdx - 1] ?? null;
2669
+ } else {
2670
+ sidebarEl = frame.children[idx - 3] ?? null;
2671
+ centerEl = frame.children[idx - 2] ?? null;
2672
+ }
2201
2673
  }
2202
2674
  function setBlur(el, px) {
2203
2675
  if (el === null) return;
@@ -2229,10 +2701,14 @@ window.__ModuleLoader__.load({
2229
2701
  if (px > 0) document.documentElement.style.setProperty("--dsh-any-blur-settings", `blur(${px}px)`);
2230
2702
  else document.documentElement.style.removeProperty("--dsh-any-blur-settings");
2231
2703
  }
2232
- /** Apply the main-background opacity to the center/details columns instead of
2704
+ /** Apply the main-background opacity to the center column instead of
2233
2705
  * the frame. The frame's translucent bg-base sits UNDER the sidebar, so
2234
2706
  * reducing the main-bg opacity stacked a second alpha onto the sidebar; moving
2235
- * the alpha onto the columns keeps the sidebar owned by its own slider. */
2707
+ * the alpha onto the column keeps the sidebar owned by its own slider. The
2708
+ * rightbar (DSH 0.1.5-rc.1+) is intentionally NOT tinted here — it sits on
2709
+ * its own grid track, paints its own surfaces from its own tokens, and
2710
+ * inheriting the main-bg tint would blend it into the page and make the
2711
+ * right panel disappear. */
2236
2712
  function applyPartOpacities(ops) {
2237
2713
  const tokens = paletteTokens();
2238
2714
  if (tokens === null) return;
@@ -2241,12 +2717,12 @@ window.__ModuleLoader__.load({
2241
2717
  const base = tokens["--dsw-alias-bg-base"];
2242
2718
  frameEl.style.background = "transparent";
2243
2719
  if (centerEl !== null) centerEl.style.background = base !== void 0 ? toRgba(base, ops.bg) : "transparent";
2244
- if (detailsEl !== null) detailsEl.style.background = base !== void 0 ? toRgba(base, ops.bg) : "transparent";
2720
+ if (rightEl !== null) rightEl.style.background = "";
2245
2721
  }
2246
2722
  /** Blur of the option panels inside the settings dialog (.dab-card), owned by
2247
2723
  * the "dialog option panel" (card) blur slider. Written as a plugin-owned
2248
2724
  * variable consumed by SETTINGS_STYLE_RULE — deliberately NOT applied to the
2249
- * homepage center/details columns, which this slider must never touch. */
2725
+ * homepage center column, which this slider must never touch. */
2250
2726
  function applyCardPanelsBlur(px) {
2251
2727
  if (px > 0) document.documentElement.style.setProperty("--dsh-any-blur-card-panels", `blur(${px}px)`);
2252
2728
  else document.documentElement.style.removeProperty("--dsh-any-blur-card-panels");
@@ -2257,12 +2733,31 @@ window.__ModuleLoader__.load({
2257
2733
  setBlur(frameEl, 0);
2258
2734
  setBlur(sidebarEl, blurs.sidebar);
2259
2735
  setBlur(centerEl, blurs.bg);
2260
- setBlur(detailsEl, blurs.bg);
2736
+ setBlur(rightEl, 0);
2737
+ applyBgBlurGlobal(blurs.bg);
2261
2738
  applyCardPanelsBlur(blurs.card);
2262
2739
  applySettingsBlur(blurs.settings);
2263
2740
  applyInputBlur(blurs.input);
2741
+ applyPanelBlur(blurs.panel);
2742
+ applyProduced();
2264
2743
  applyViewCards();
2265
2744
  }
2745
+ /** Produced/artifact surfaces (conversation code blocks + their banner +
2746
+ * composer chips): re-write the root blur and the alpha percentage consumed by
2747
+ * PRODUCED_RULE. The percentage is an alpha for the surface's OWN host color —
2748
+ * 1 (100%) reproduces the untouched host look and lowering it fades exactly
2749
+ * that color out — so no palette sampling is involved and the surfaces keep
2750
+ * following the active theme/wallpaper color on their own. */
2751
+ function applyProduced() {
2752
+ const px = rBlurs().produced;
2753
+ const root = document.documentElement;
2754
+ if (px > 0) root.style.setProperty("--dsh-any-blur-prod", `blur(${px}px)`);
2755
+ else root.style.removeProperty("--dsh-any-blur-prod");
2756
+ let opacity = rProducedOpacity();
2757
+ if (opacity < 0) opacity = 0;
2758
+ if (opacity > 1) opacity = 1;
2759
+ root.style.setProperty("--dsh-any-prod-pct", `${Math.round(opacity * 100)}%`);
2760
+ }
2266
2761
  /** Live per-part blur update during slider drag (no full re-apply). */
2267
2762
  function setPartBlur(part, v) {
2268
2763
  if (part === "settings") {
@@ -2277,6 +2772,14 @@ window.__ModuleLoader__.load({
2277
2772
  applyInputBlur(v);
2278
2773
  return;
2279
2774
  }
2775
+ if (part === "panel") {
2776
+ applyPanelBlur(v);
2777
+ return;
2778
+ }
2779
+ if (part === "produced") {
2780
+ applyProduced();
2781
+ return;
2782
+ }
2280
2783
  if (part === "chat" || part === "trajectory") {
2281
2784
  applyViewCards();
2282
2785
  return;
@@ -2284,7 +2787,7 @@ window.__ModuleLoader__.load({
2284
2787
  discoverParts();
2285
2788
  if (part === "bg") {
2286
2789
  setBlur(centerEl, v);
2287
- setBlur(detailsEl, v);
2790
+ applyBgBlurGlobal(v);
2288
2791
  } else setBlur(sidebarEl, v);
2289
2792
  }
2290
2793
  const VIEW_CARDS = [{
@@ -2484,7 +2987,7 @@ window.__ModuleLoader__.load({
2484
2987
  function watchParts() {
2485
2988
  if (partsObserver !== null || typeof MutationObserver === "undefined") return;
2486
2989
  partsObserver = new MutationObserver(() => {
2487
- if (frameEl !== null && sidebarEl !== null && centerEl !== null && detailsEl !== null && document.body.contains(frameEl) && viewTargets.every((el) => el !== null && document.body.contains(el))) return;
2990
+ if (frameEl !== null && sidebarEl !== null && centerEl !== null && rightEl !== null && document.body.contains(frameEl) && viewTargets.every((el) => el !== null && document.body.contains(el))) return;
2488
2991
  applyPartBlurs(rBlurs());
2489
2992
  applyPartOpacities(rOps());
2490
2993
  });
@@ -2554,17 +3057,18 @@ window.__ModuleLoader__.load({
2554
3057
  cb(imgNat.w, imgNat.h);
2555
3058
  return;
2556
3059
  }
2557
- const img = new Image();
2558
- img.onload = () => {
3060
+ loadImage(url).then((img) => {
3061
+ if (!img) {
3062
+ cb(0, 0);
3063
+ return;
3064
+ }
2559
3065
  imgNat = {
2560
3066
  url,
2561
3067
  w: img.naturalWidth,
2562
3068
  h: img.naturalHeight
2563
3069
  };
2564
3070
  cb(img.naturalWidth, img.naturalHeight);
2565
- };
2566
- img.onerror = () => cb(0, 0);
2567
- img.src = url;
3071
+ });
2568
3072
  }
2569
3073
  const DRAG_MAX_SIDE = 720;
2570
3074
  let lowResUrl = null;
@@ -2575,8 +3079,11 @@ window.__ModuleLoader__.load({
2575
3079
  cb(lowResUrl);
2576
3080
  return;
2577
3081
  }
2578
- const img = new Image();
2579
- img.onload = () => {
3082
+ loadImage(url).then((img) => {
3083
+ if (!img) {
3084
+ cb(null);
3085
+ return;
3086
+ }
2580
3087
  const k = Math.min(1, DRAG_MAX_SIDE / Math.max(img.naturalWidth, img.naturalHeight));
2581
3088
  if (k >= 1) {
2582
3089
  lowResFor = url;
@@ -2599,9 +3106,7 @@ window.__ModuleLoader__.load({
2599
3106
  lowResFor = url;
2600
3107
  lowResUrl = low;
2601
3108
  cb(low);
2602
- };
2603
- img.onerror = () => cb(null);
2604
- img.src = url;
3109
+ });
2605
3110
  }
2606
3111
  function setDragLow(on) {
2607
3112
  if (cfg.backgroundType !== "image" || on === dragLow || !wpEl) return;
@@ -2640,6 +3145,7 @@ window.__ModuleLoader__.load({
2640
3145
  }
2641
3146
  let wpVerdict = null;
2642
3147
  let verdictListener = null;
3148
+ let verdictGen = 0;
2643
3149
  /** Register a callback fired when the background brightness verdict CHANGES
2644
3150
  * (a new wallpaper was analyzed, a generated bg regenerated), so the skin can
2645
3151
  * be re-registered through the host theme service. */
@@ -2652,6 +3158,7 @@ window.__ModuleLoader__.load({
2652
3158
  if (dark !== null) verdictListener?.();
2653
3159
  }
2654
3160
  function updateWpVerdict(url) {
3161
+ const gen = ++verdictGen;
2655
3162
  if (url === null) {
2656
3163
  applyVerdict(null);
2657
3164
  return;
@@ -2661,7 +3168,7 @@ window.__ModuleLoader__.load({
2661
3168
  return;
2662
3169
  }
2663
3170
  analyzeFrameDark(url).then((dark) => {
2664
- if (dark === null) return;
3171
+ if (dark === null || gen !== verdictGen) return;
2665
3172
  wpVerdict = {
2666
3173
  url,
2667
3174
  dark
@@ -2791,10 +3298,12 @@ window.__ModuleLoader__.load({
2791
3298
  setBgDark(null);
2792
3299
  }
2793
3300
  if (rHasColor() || rBgDark() !== null || rSchemeOverride() !== "auto") applyCustomTokens(rOps());
3301
+ else if (!url) clearThemeTokens();
2794
3302
  if (rHasColor()) {
2795
3303
  applySettingsOverrides(rSop());
2796
3304
  applyTrajectoryOverrides(rTrajectoryOpacity());
2797
3305
  }
3306
+ applyPanelOverrides(rPanelOpacity());
2798
3307
  applyPartBlurs(rBlurs());
2799
3308
  }
2800
3309
  function teardownWp() {
@@ -2822,6 +3331,14 @@ window.__ModuleLoader__.load({
2822
3331
  document.documentElement.style.removeProperty("--dsh-any-blur-settings");
2823
3332
  document.documentElement.style.removeProperty("--dsh-any-blur-card-panels");
2824
3333
  document.documentElement.style.removeProperty("--dsh-any-input-blur");
3334
+ document.documentElement.style.removeProperty("--dsh-any-part-blur-global");
3335
+ document.documentElement.style.removeProperty("--dsh-any-panel-bg-base");
3336
+ document.documentElement.style.removeProperty("--dsh-any-panel-layer-1");
3337
+ document.documentElement.style.removeProperty("--dsh-any-panel-layer-2");
3338
+ document.documentElement.style.removeProperty("--dsh-any-panel-layer-3");
3339
+ document.documentElement.style.removeProperty("--dsh-any-blur-panel");
3340
+ document.documentElement.style.removeProperty("--dsh-any-blur-prod");
3341
+ document.documentElement.style.removeProperty("--dsh-any-prod-pct");
2825
3342
  for (const v of Object.values(OPACITY_VARS)) document.documentElement.style.removeProperty(v);
2826
3343
  baseTokenKey = "";
2827
3344
  lastBgKey = "";
@@ -2835,10 +3352,10 @@ window.__ModuleLoader__.load({
2835
3352
  setBlur(frameEl, 0);
2836
3353
  setBlur(sidebarEl, 0);
2837
3354
  setBlur(centerEl, 0);
2838
- setBlur(detailsEl, 0);
3355
+ setBlur(rightEl, 0);
2839
3356
  if (frameEl !== null) frameEl.style.removeProperty("background");
2840
3357
  if (centerEl !== null) centerEl.style.removeProperty("background");
2841
- if (detailsEl !== null) detailsEl.style.removeProperty("background");
3358
+ if (rightEl !== null) rightEl.style.removeProperty("background");
2842
3359
  stopWatchingParts();
2843
3360
  }
2844
3361
  /** Live wallpaper-opacity updates during slider drag (no full re-apply). */
@@ -2912,56 +3429,6 @@ window.__ModuleLoader__.load({
2912
3429
  v.src = url;
2913
3430
  });
2914
3431
  }
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
3432
  const UI_CSS = `
2966
3433
  /* The section is rendered INLINE inside the host settings dialog's content
2967
3434
  * column: the host provides the modal chrome (backdrop, centering, closing).
@@ -3031,6 +3498,7 @@ window.__ModuleLoader__.load({
3031
3498
  .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
3499
  .dab-urlinput::placeholder{color:var(--dsw-alias-label-quaternary)}
3033
3500
  .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)}
3501
+ .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
3502
  .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
3503
  .dab-swatch-lg:hover{transform:scale(1.02)}
3036
3504
 
@@ -3041,11 +3509,19 @@ window.__ModuleLoader__.load({
3041
3509
  .dab-btn:disabled{opacity:.5;cursor:not-allowed}
3042
3510
  .dab-btn-primary{background:var(--dsw-alias-brand-primary);color:var(--dsw-alias-brand-text);border-color:transparent}
3043
3511
  .dab-btn-danger{color:var(--dsw-alias-state-error-primary)}
3512
+ .dab-btn-danger-solid{background:var(--dsw-alias-state-error-primary);color:#fff;border-color:transparent}
3513
+ .dab-btn-danger-solid:hover:not(:disabled){background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 85%,#000)}
3044
3514
  .dab-btn-ghost{background:transparent;border-color:transparent;color:var(--dsw-alias-label-secondary,var(--dsw-alias-label-tertiary))}
3045
3515
  .dab-btn-ghost:hover:not(:disabled){background:var(--dsw-alias-bg-layer-2);box-shadow:none}
3516
+ /* Soft solid button: subtle layer background instead of transparent ghost. */
3517
+ .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))}
3518
+ .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)}
3519
+ /* White background with error-red text (inverse of danger-solid). */
3520
+ .dab-btn-danger-inverted{background:#fff;color:var(--dsw-alias-state-error-primary);border-color:transparent}
3521
+ .dab-btn-danger-inverted:hover:not(:disabled){background:#f3f4f6}
3046
3522
  .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
3523
  .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:all .22s ease}
3524
+ .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
3525
  .dab-chip:hover{border-color:var(--dsw-alias-brand-primary);color:var(--dsw-alias-label-primary)}
3050
3526
  .dab-chip.is-active{background:var(--dsw-alias-brand-primary);color:var(--dsw-alias-brand-text);border-color:transparent}
3051
3527
 
@@ -3095,7 +3571,7 @@ window.__ModuleLoader__.load({
3095
3571
 
3096
3572
  /* ── generated background type cards ─────────────────────────────────────── */
3097
3573
  .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}
3574
+ .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
3575
  .dab-type:hover{transform:translateY(-2px)}
3100
3576
  .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
3577
  .dab-type-thumb{height:62px;border-radius:9px;overflow:hidden;position:relative}
@@ -3126,14 +3602,15 @@ window.__ModuleLoader__.load({
3126
3602
  /* ── presets / profiles / schedule / rotation ────────────────────────────── */
3127
3603
  .dab-row-head{display:flex;align-items:center;justify-content:space-between;gap:12px}
3128
3604
  .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}
3605
+ .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
3606
  .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
3607
  .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
3608
  .dab-preset-name{font-size:12.5px;font-weight:600;color:var(--dsw-alias-label-primary)}
3133
3609
  .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.is-active{border-color:color-mix(in srgb,var(--dsw-alias-brand-primary) 45%,transparent)}
3610
+ .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}
3611
+ .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}
3612
+ .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))}
3613
+ .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
3614
  .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
3615
  .dab-profile-meta{flex:1;min-width:0;display:flex;flex-direction:column;gap:1px}
3139
3616
  .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 +3620,22 @@ window.__ModuleLoader__.load({
3143
3620
  .dab-select:focus{border-color:var(--dsw-alias-brand-primary)}
3144
3621
  .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
3622
  .dab-timeinput:focus{border-color:var(--dsw-alias-brand-primary)}
3146
- .dab-schedule-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:16px;margin-top:12px}
3147
- .dab-schedule-cell{min-width:0}
3623
+ /* Collapsible panel: animates grid-template-rows 0fr→1fr so the schedule
3624
+ block unfolds to its content height instead of popping in. visibility
3625
+ flips after the fold finishes (and instantly on open) so hidden inputs
3626
+ are never focusable. */
3627
+ .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}
3628
+ .dab-schedule-wrap.is-open{grid-template-rows:1fr;visibility:visible;transition:grid-template-rows .42s cubic-bezier(.22,1,.36,1)}
3629
+ .dab-schedule-clip{overflow:hidden;min-height:0;opacity:0;transition:opacity .28s ease}
3630
+ .dab-schedule-wrap.is-open .dab-schedule-clip{opacity:1;transition:opacity .32s ease .1s}
3631
+ .dab-schedule-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:16px;padding-top:12px}
3632
+ .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
3633
  .dab-time-row{display:flex;flex-direction:column;gap:9px}
3149
3634
  .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
3635
  .dab-time-label svg{color:var(--dsw-alias-brand-primary);flex:none}
3151
3636
  .dab-time-label .dab-select,.dab-time-label .dab-timeinput{flex:1;min-width:0}
3152
3637
  .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)}
3638
+ .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
3639
  .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
3640
  .dab-thumb img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}
3156
3641
  .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 +3669,9 @@ window.__ModuleLoader__.load({
3184
3669
  @keyframes dab-fade-in{from{opacity:0}to{opacity:1}}
3185
3670
  @keyframes dab-page-in{from{opacity:0;transform:translateY(12px)}to{opacity:1;transform:none}}
3186
3671
  @keyframes dab-rise-in{from{opacity:0;transform:translateY(10px)}to{opacity:1;transform:none}}
3672
+ @keyframes dab-list-in{from{opacity:0;transform:translateY(-7px)}to{opacity:1;transform:none}}
3673
+ @keyframes dab-row-in{from{opacity:0;transform:translateY(6px) scale(.985)}to{opacity:1;transform:none}}
3674
+ @keyframes dab-type-in{from{opacity:0;transform:translateY(8px) scale(.97)}to{opacity:1;transform:none}}
3187
3675
  @keyframes dab-orb-in{from{opacity:0;transform:scale(.82)}to{opacity:1;transform:none}}
3188
3676
  @keyframes dab-spin{to{transform:rotate(360deg)}}
3189
3677
  @keyframes dab-rotate{to{transform:rotate(360deg)}}
@@ -3322,6 +3810,12 @@ window.__ModuleLoader__.load({
3322
3810
  })
3323
3811
  ]
3324
3812
  });
3813
+ /** Bottom workbench panel (dsh-better-sidebar): a window with a docked strip. */
3814
+ const PanelIcon = ({ size, className }) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Glyph, {
3815
+ size,
3816
+ className,
3817
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M2.4 2.7h11.2v10.6H2.4z" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M2.4 10.1h11.2" })]
3818
+ });
3325
3819
  const SlidersIcon = ({ size, className }) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Glyph, {
3326
3820
  size,
3327
3821
  className,
@@ -3491,6 +3985,16 @@ window.__ModuleLoader__.load({
3491
3985
  className,
3492
3986
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M4 4l8 8M12 4l-8 8" })
3493
3987
  });
3988
+ const PlayIcon = ({ size, className }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Glyph, {
3989
+ size,
3990
+ className,
3991
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M5.2 3.4l7.4 4.6-7.4 4.6V3.4z" })
3992
+ });
3993
+ const PauseIcon = ({ size, className }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Glyph, {
3994
+ size,
3995
+ className,
3996
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M5.4 3.4v9.2M10.6 3.4v9.2" })
3997
+ });
3494
3998
  //#endregion
3495
3999
  //#region src/client/components/ErrorBoundary.tsx
3496
4000
  /**
@@ -4134,12 +4638,12 @@ window.__ModuleLoader__.load({
4134
4638
  }
4135
4639
  function ColorPage({ p, notify }) {
4136
4640
  const { t, hue, sat, lit, setColor, extractColor, setSchemeOverride, useStore } = p;
4137
- const store = useStore((s) => s);
4138
- const storeUrl = store.url;
4139
- const schemeOverride = store.schemeOverride;
4641
+ const storeUrl = useStore((s) => s.url);
4642
+ const schemeOverride = useStore((s) => s.schemeOverride);
4643
+ const color = useStore((s) => s.color);
4140
4644
  const [pickerOpen, setPickerOpen] = (0, react.useState)(false);
4141
4645
  const [extracting, setExtracting] = (0, react.useState)(false);
4142
- const wheel = store.color ?? [
4646
+ const wheel = color ?? [
4143
4647
  hue,
4144
4648
  sat,
4145
4649
  lit
@@ -4411,6 +4915,65 @@ window.__ModuleLoader__.load({
4411
4915
  });
4412
4916
  }
4413
4917
  //#endregion
4918
+ //#region src/client/env.ts
4919
+ /**
4920
+ * Host capability detection — which third-party plugins are actually present.
4921
+ *
4922
+ * The host exposes no plugin registry on the client context, so presence is
4923
+ * inferred from the stable DOM markers those plugins mount. Detection runs
4924
+ * once (plugin boot) and is re-armed by a cheap body-level MutationObserver so
4925
+ * a plugin that mounts after this one still flips the cached verdict. The
4926
+ * verdict is sticky-true for the session: whether a detected plugin's surface
4927
+ * is currently collapsed must not hide the settings that govern it.
4928
+ */
4929
+ /** Persistent container + surfaces dsh-better-sidebar mounts when loaded.
4930
+ * `[data-dsh-better-sidebar]` is the plugin's host wrapper — always appended
4931
+ * to document.body on mount — so it is the authoritative "plugin is loaded"
4932
+ * signal and the body childList observer fires exactly when it appears. The
4933
+ * panel surfaces we actually style are listed only as reinforcement: they are
4934
+ * conditionally rendered (collapsed panels drop them), so they must never be
4935
+ * the sole signal. */
4936
+ const BETTER_SIDEBAR_MARKERS = [
4937
+ "[data-dsh-better-sidebar]",
4938
+ "[data-dsh-panel-host]",
4939
+ "[data-dsh-bottom-panel]",
4940
+ "[data-sidebar-right-panel]"
4941
+ ];
4942
+ let betterSidebar = false;
4943
+ const subs = /* @__PURE__ */ new Set();
4944
+ /** Read snapshot of whether the better-sidebar plugin is present. */
4945
+ function rBetterSidebar() {
4946
+ return betterSidebar;
4947
+ }
4948
+ function emit() {
4949
+ subs.forEach((cb) => cb());
4950
+ }
4951
+ function sync() {
4952
+ if (document.querySelector(BETTER_SIDEBAR_MARKERS.join(",")) !== null && !betterSidebar) {
4953
+ betterSidebar = true;
4954
+ emit();
4955
+ }
4956
+ }
4957
+ /** React hook: re-renders the caller when better-sidebar presence changes. */
4958
+ function useBetterSidebar() {
4959
+ return (0, react.useSyncExternalStore)(subscribe, rBetterSidebar);
4960
+ }
4961
+ /** Subscribe to verdict changes. */
4962
+ function subscribe(cb) {
4963
+ subs.add(cb);
4964
+ return () => {
4965
+ subs.delete(cb);
4966
+ };
4967
+ }
4968
+ /** Start detection (probe once, then watch body mounts). Returns an
4969
+ * unsubscribe for plugin teardown. */
4970
+ function startBetterSidebarWatch() {
4971
+ sync();
4972
+ const observer = new MutationObserver(sync);
4973
+ observer.observe(document.body, { childList: true });
4974
+ return () => observer.disconnect();
4975
+ }
4976
+ //#endregion
4414
4977
  //#region src/client/components/pages/InterfacePage.tsx
4415
4978
  const PARTS = [
4416
4979
  {
@@ -4447,10 +5010,22 @@ window.__ModuleLoader__.load({
4447
5010
  isTrajectory: true,
4448
5011
  labelKey: "uiTrajectory",
4449
5012
  Icon: TrajectoryIcon
5013
+ },
5014
+ {
5015
+ isProduced: true,
5016
+ labelKey: "uiProduced",
5017
+ Icon: TextIcon
5018
+ },
5019
+ {
5020
+ isPanel: true,
5021
+ labelKey: "uiPanelRegion",
5022
+ Icon: PanelIcon
4450
5023
  }
4451
5024
  ];
4452
5025
  function InterfacePage({ p }) {
4453
- const { t, setOps, setBlurs, setSop } = p;
5026
+ const { t, setOps, setBlurs, setSop, setPanelOp } = p;
5027
+ const hasBetterSidebar = useBetterSidebar();
5028
+ const parts = PARTS.filter((part) => !(part.isPanel && !hasBetterSidebar));
4454
5029
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
4455
5030
  className: "dab-head dab-rise",
4456
5031
  style: { "--d": 0 },
@@ -4470,11 +5045,11 @@ window.__ModuleLoader__.load({
4470
5045
  ]
4471
5046
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4472
5047
  className: "dab-grid-parts",
4473
- children: PARTS.map((part, i) => {
4474
- const { labelKey, Icon, isSettings, isChat, isTrajectory } = part;
5048
+ children: parts.map((part, i) => {
5049
+ const { labelKey, Icon, isSettings, isChat, isTrajectory, isPanel, isProduced } = part;
4475
5050
  const opKey = part.opKey;
4476
- const blurKey = isChat ? "chat" : isTrajectory ? "trajectory" : isSettings ? "settings" : opKey;
4477
- const opacity = isChat ? rChatTextOpacity() : isTrajectory ? rTrajectoryOpacity() : isSettings ? rSop() : rOps()[opKey];
5051
+ const blurKey = isChat ? "chat" : isTrajectory ? "trajectory" : isSettings ? "settings" : isPanel ? "panel" : isProduced ? "produced" : opKey;
5052
+ const opacity = isChat ? rChatTextOpacity() : isTrajectory ? rTrajectoryOpacity() : isSettings ? rSop() : isPanel ? rPanelOpacity() : isProduced ? rProducedOpacity() : rOps()[opKey];
4478
5053
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
4479
5054
  className: "dab-card dab-card-hover dab-rise",
4480
5055
  style: { "--d": i + 1 },
@@ -4514,6 +5089,12 @@ window.__ModuleLoader__.load({
4514
5089
  } else if (isSettings) {
4515
5090
  cfg.settingsOpacity = op;
4516
5091
  applySettingsOverrides(op);
5092
+ } else if (isPanel) {
5093
+ cfg.panelOpacity = op;
5094
+ applyPanelOverrides(op);
5095
+ } else if (isProduced) {
5096
+ cfg.producedOpacity = op;
5097
+ applyProduced();
4517
5098
  } else {
4518
5099
  const ops = { ...rOps() };
4519
5100
  ops[opKey] = op;
@@ -4533,7 +5114,12 @@ window.__ModuleLoader__.load({
4533
5114
  applyTrajectoryOverrides(op);
4534
5115
  saveConfig();
4535
5116
  } else if (isSettings) setSop(op);
4536
- else {
5117
+ else if (isPanel) setPanelOp(op);
5118
+ else if (isProduced) {
5119
+ cfg.producedOpacity = op;
5120
+ applyProduced();
5121
+ saveConfig();
5122
+ } else {
4537
5123
  const ops = { ...rOps() };
4538
5124
  ops[opKey] = op;
4539
5125
  setOps(ops);
@@ -4781,13 +5367,12 @@ window.__ModuleLoader__.load({
4781
5367
  }
4782
5368
  ];
4783
5369
  function BackgroundPage({ p }) {
4784
- const { t, setWp, setVideo, setWop, setBl, setBgType, setGeneratedBg, regenerateBg, setRegenerateOnReload, setRotation, addRotationItems, removeRotationItem, rotateNow, useStore } = p;
4785
- const store = useStore((s) => s);
4786
- const storeUrl = store.url;
4787
- const backgroundType = store.backgroundType;
4788
- const generatedBg = store.generatedBg;
4789
- const regenerateOnReload = store.regenerateOnReload;
4790
- const rotation = store.rotation;
5370
+ const { t, setWpFromServer, setVideo, setWop, setBl, setBgType, setGeneratedBg, regenerateBg, setRegenerateOnReload, setRotation, addRotationItems, removeRotationItem, rotateNow, setVideoFromUrl, useStore } = p;
5371
+ const storeUrl = useStore((s) => s.url);
5372
+ const backgroundType = useStore((s) => s.backgroundType);
5373
+ const generatedBg = useStore((s) => s.generatedBg);
5374
+ const regenerateOnReload = useStore((s) => s.regenerateOnReload);
5375
+ const rotation = useStore((s) => s.rotation);
4791
5376
  const fileRef = (0, react.useRef)(null);
4792
5377
  const rotFileRef = (0, react.useRef)(null);
4793
5378
  const [editorOpen, setEditorOpen] = (0, react.useState)(false);
@@ -4803,13 +5388,22 @@ window.__ModuleLoader__.load({
4803
5388
  const isStatic = backgroundType === "image" || isVideo;
4804
5389
  const isGenerated = !isStatic;
4805
5390
  const activeGenType = isGenerated && generatedBg ? generatedBg.type : "mesh";
5391
+ const [paused, setPaused] = (0, react.useState)(false);
5392
+ const genPreset = generatedBg !== null && generatedBg.type !== "mesh" ? generatedBg.preset : void 0;
5393
+ (0, react.useEffect)(() => {
5394
+ setPaused(false);
5395
+ }, [
5396
+ activeGenType,
5397
+ genPreset,
5398
+ generatedBg?.seed
5399
+ ]);
4806
5400
  const onFileSelect = (f) => {
4807
5401
  if (f.type.startsWith("video/")) {
4808
5402
  setVideo(f, f.type);
4809
5403
  return;
4810
5404
  }
4811
- readImg(f, (d) => {
4812
- if (d) setWp(d);
5405
+ uploadWallpaper(f).then((ok) => {
5406
+ if (ok) setWpFromServer(WALLPAPER_SERVE_URL);
4813
5407
  });
4814
5408
  };
4815
5409
  const onDrop = (e) => {
@@ -4827,12 +5421,17 @@ window.__ModuleLoader__.load({
4827
5421
  setUrlBusy(true);
4828
5422
  setUrlErr(null);
4829
5423
  const r = await setWallpaperFromUrl(u);
4830
- setUrlBusy(false);
4831
5424
  if (r.ok) {
4832
- setWp(r.dataUrl ?? null);
5425
+ setWpFromServer(r.wallpaperUrl ?? null);
4833
5426
  setUrlOpen(false);
4834
5427
  setUrlVal("");
5428
+ } else if (r.error === "not an image") {
5429
+ if (await setVideoFromUrl(u)) {
5430
+ setUrlOpen(false);
5431
+ setUrlVal("");
5432
+ } else setUrlErr(t("bgUrlVideoFail"));
4835
5433
  } else setUrlErr(r.error === "invalid url" || r.error === "unsupported scheme" ? t("bgUrlBadHttp") : r.error ?? t("bgUrlFail"));
5434
+ setUrlBusy(false);
4836
5435
  };
4837
5436
  const switchToStatic = () => {
4838
5437
  if (isStatic) return;
@@ -4847,10 +5446,12 @@ window.__ModuleLoader__.load({
4847
5446
  };
4848
5447
  const setGenType = (type) => {
4849
5448
  if (type === activeGenType) return;
5449
+ setPaused(false);
4850
5450
  setBgType(type);
4851
5451
  };
4852
5452
  const ensureGenParams = () => generatedBg ?? defaultParamsFor(activeGenType);
4853
5453
  const updateGenerated = (patch) => {
5454
+ setPaused(false);
4854
5455
  setGeneratedBg({
4855
5456
  ...ensureGenParams(),
4856
5457
  ...patch
@@ -4881,9 +5482,13 @@ window.__ModuleLoader__.load({
4881
5482
  case "aurora": return t("presetAurora");
4882
5483
  case "nebula": return t("presetNebula");
4883
5484
  case "noise": return t("presetNoise");
5485
+ case "starfield": return t("presetStarfield");
4884
5486
  case "dots": return t("presetDots");
4885
5487
  case "waves": return t("presetWaves");
4886
5488
  case "poly": return t("presetPoly");
5489
+ case "rain": return t("presetRain");
5490
+ case "contour": return t("presetContour");
5491
+ case "meta": return t("presetMeta");
4887
5492
  default: return key;
4888
5493
  }
4889
5494
  };
@@ -4938,6 +5543,7 @@ window.__ModuleLoader__.load({
4938
5543
  type: "button",
4939
5544
  className: "dab-btn",
4940
5545
  onClick: () => {
5546
+ setPaused(false);
4941
5547
  regenerateBg();
4942
5548
  setSpinTick((x) => x + 1);
4943
5549
  },
@@ -4945,7 +5551,7 @@ window.__ModuleLoader__.load({
4945
5551
  }) : null, /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
4946
5552
  type: "button",
4947
5553
  className: "dab-btn dab-btn-danger",
4948
- onClick: () => setWp(null),
5554
+ onClick: () => setWpFromServer(null),
4949
5555
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TrashIcon, { size: 13 }), t("bgRemove")]
4950
5556
  })]
4951
5557
  })
@@ -5006,7 +5612,7 @@ window.__ModuleLoader__.load({
5006
5612
  }),
5007
5613
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
5008
5614
  type: "button",
5009
- className: "dab-btn dab-btn-ghost",
5615
+ className: "dab-btn dab-btn-soft",
5010
5616
  onClick: () => setUrlOpen((o) => !o),
5011
5617
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(LinkIcon, { size: 14 }), t("bgFromUrl")]
5012
5618
  }),
@@ -5019,21 +5625,14 @@ window.__ModuleLoader__.load({
5019
5625
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(EditIcon, { size: 14 }), t("bgEdit")]
5020
5626
  }) : null, /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
5021
5627
  type: "button",
5022
- className: "dab-btn dab-btn-ghost dab-btn-danger",
5023
- onClick: () => setWp(null),
5628
+ className: "dab-btn dab-btn-danger-inverted",
5629
+ onClick: () => setWpFromServer(null),
5024
5630
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TrashIcon, { size: 14 }), t("bgRemove")]
5025
5631
  })] }) : null
5026
5632
  ]
5027
5633
  }),
5028
5634
  urlOpen ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5029
5635
  className: "dab-urlrow",
5030
- style: {
5031
- marginTop: 12,
5032
- display: "flex",
5033
- flexWrap: "wrap",
5034
- gap: 8,
5035
- alignItems: "center"
5036
- },
5037
5636
  children: [
5038
5637
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
5039
5638
  type: "text",
@@ -5213,9 +5812,10 @@ window.__ModuleLoader__.load({
5213
5812
  children: [
5214
5813
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5215
5814
  className: "dab-types",
5216
- children: typeMeta.map((m) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
5815
+ children: typeMeta.map((m, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
5217
5816
  type: "button",
5218
5817
  className: `dab-type${activeGenType === m.type ? " is-active" : ""}`,
5818
+ style: { "--i": i },
5219
5819
  onClick: () => setGenType(m.type),
5220
5820
  children: [
5221
5821
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: `dab-type-thumb ${m.thumb}` }),
@@ -5244,7 +5844,8 @@ window.__ModuleLoader__.load({
5244
5844
  children: [
5245
5845
  "aurora",
5246
5846
  "nebula",
5247
- "noise"
5847
+ "noise",
5848
+ "starfield"
5248
5849
  ].map((pr) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
5249
5850
  type: "button",
5250
5851
  className: `dab-chip${generatedBg.preset === pr ? " is-active" : ""}`,
@@ -5263,7 +5864,10 @@ window.__ModuleLoader__.load({
5263
5864
  children: [
5264
5865
  "dots",
5265
5866
  "waves",
5266
- "poly"
5867
+ "poly",
5868
+ "rain",
5869
+ "contour",
5870
+ "meta"
5267
5871
  ].map((pr) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
5268
5872
  type: "button",
5269
5873
  className: `dab-chip${generatedBg.preset === pr ? " is-active" : ""}`,
@@ -5353,11 +5957,26 @@ window.__ModuleLoader__.load({
5353
5957
  onClick: () => setRegenerateOnReload(!regenerateOnReload),
5354
5958
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "dab-toggle-knob" })
5355
5959
  }),
5960
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
5961
+ type: "button",
5962
+ className: "dab-btn",
5963
+ onClick: () => {
5964
+ if (paused) {
5965
+ resumeGeneratedBg();
5966
+ setPaused(false);
5967
+ } else {
5968
+ pauseGeneratedBg();
5969
+ setPaused(true);
5970
+ }
5971
+ },
5972
+ 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")]
5973
+ }),
5356
5974
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
5357
5975
  type: "button",
5358
5976
  className: "dab-btn dab-btn-primary",
5359
5977
  onClick: () => {
5360
5978
  regenerateBg();
5979
+ setPaused(false);
5361
5980
  setSpinTick((x) => x + 1);
5362
5981
  },
5363
5982
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
@@ -5452,7 +6071,9 @@ window.__ModuleLoader__.load({
5452
6071
  settings: 0,
5453
6072
  chat: 0,
5454
6073
  trajectory: 0,
5455
- input: 0
6074
+ input: 0,
6075
+ panel: 0,
6076
+ produced: 0
5456
6077
  };
5457
6078
  const BUILTIN_PRESETS = [
5458
6079
  {
@@ -5470,7 +6091,9 @@ window.__ModuleLoader__.load({
5470
6091
  wallpaperOpacity: 1,
5471
6092
  blur: 0,
5472
6093
  chatTextOpacity: 0,
5473
- trajectoryOpacity: 1
6094
+ trajectoryOpacity: 1,
6095
+ panelOpacity: 1,
6096
+ producedOpacity: 1
5474
6097
  }
5475
6098
  },
5476
6099
  {
@@ -5494,13 +6117,16 @@ window.__ModuleLoader__.load({
5494
6117
  card: 12,
5495
6118
  settings: 20,
5496
6119
  trajectory: 8,
5497
- input: 14
6120
+ input: 14,
6121
+ panel: 12
5498
6122
  },
5499
6123
  settingsOpacity: .88,
5500
6124
  wallpaperOpacity: 1,
5501
6125
  blur: 0,
5502
6126
  chatTextOpacity: 0,
5503
- trajectoryOpacity: .85
6127
+ trajectoryOpacity: .85,
6128
+ panelOpacity: .85,
6129
+ producedOpacity: 1
5504
6130
  }
5505
6131
  },
5506
6132
  {
@@ -5518,7 +6144,9 @@ window.__ModuleLoader__.load({
5518
6144
  wallpaperOpacity: 1,
5519
6145
  blur: 0,
5520
6146
  chatTextOpacity: 0,
5521
- trajectoryOpacity: 1
6147
+ trajectoryOpacity: 1,
6148
+ panelOpacity: 1,
6149
+ producedOpacity: 1
5522
6150
  }
5523
6151
  },
5524
6152
  {
@@ -5542,13 +6170,16 @@ window.__ModuleLoader__.load({
5542
6170
  card: 14,
5543
6171
  settings: 22,
5544
6172
  trajectory: 10,
5545
- input: 16
6173
+ input: 16,
6174
+ panel: 16
5546
6175
  },
5547
6176
  settingsOpacity: .85,
5548
6177
  wallpaperOpacity: .92,
5549
6178
  blur: 2,
5550
6179
  chatTextOpacity: 0,
5551
- trajectoryOpacity: .8
6180
+ trajectoryOpacity: .8,
6181
+ panelOpacity: .8,
6182
+ producedOpacity: 1
5552
6183
  }
5553
6184
  },
5554
6185
  {
@@ -5572,13 +6203,16 @@ window.__ModuleLoader__.load({
5572
6203
  card: 10,
5573
6204
  settings: 18,
5574
6205
  trajectory: 6,
5575
- input: 20
6206
+ input: 20,
6207
+ panel: 18
5576
6208
  },
5577
6209
  settingsOpacity: .8,
5578
6210
  wallpaperOpacity: 1,
5579
6211
  blur: 0,
5580
6212
  chatTextOpacity: 0,
5581
- trajectoryOpacity: .78
6213
+ trajectoryOpacity: .78,
6214
+ panelOpacity: .75,
6215
+ producedOpacity: 1
5582
6216
  }
5583
6217
  },
5584
6218
  {
@@ -5600,7 +6234,9 @@ window.__ModuleLoader__.load({
5600
6234
  wallpaperOpacity: 1,
5601
6235
  blur: 0,
5602
6236
  chatTextOpacity: 0,
5603
- trajectoryOpacity: 1
6237
+ trajectoryOpacity: 1,
6238
+ panelOpacity: 1,
6239
+ producedOpacity: 1
5604
6240
  }
5605
6241
  }
5606
6242
  ];
@@ -5618,10 +6254,9 @@ window.__ModuleLoader__.load({
5618
6254
  }
5619
6255
  function ProfilePage({ p, notify }) {
5620
6256
  const { t, exportTheme, importTheme, saveProfile, applyProfile, deleteProfile, applyPreset, setSchedule, useStore } = p;
5621
- const store = useStore((s) => s);
5622
- const profiles = store.profiles;
5623
- const schedule = store.schedule;
5624
- const activeProfile = store.activeProfile;
6257
+ const profiles = useStore((s) => s.profiles);
6258
+ const schedule = useStore((s) => s.schedule);
6259
+ const activeProfile = useStore((s) => s.activeProfile);
5625
6260
  const importRef = (0, react.useRef)(null);
5626
6261
  const [nameOpen, setNameOpen] = (0, react.useState)(false);
5627
6262
  const [nameVal, setNameVal] = (0, react.useState)("");
@@ -5694,11 +6329,12 @@ window.__ModuleLoader__.load({
5694
6329
  children: t("presetGalleryTitle")
5695
6330
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5696
6331
  className: "dab-preset-grid",
5697
- children: BUILTIN_PRESETS.map((ps) => {
6332
+ children: BUILTIN_PRESETS.map((ps, i) => {
5698
6333
  const hex = hexOf(ps.appearance.color);
5699
6334
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
5700
6335
  type: "button",
5701
6336
  className: "dab-preset",
6337
+ style: { "--i": i },
5702
6338
  onClick: () => {
5703
6339
  applyPreset(ps.appearance);
5704
6340
  notify(t("presetApplied"));
@@ -5741,13 +6377,6 @@ window.__ModuleLoader__.load({
5741
6377
  }),
5742
6378
  nameOpen ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5743
6379
  className: "dab-urlrow",
5744
- style: {
5745
- marginTop: 12,
5746
- display: "flex",
5747
- flexWrap: "wrap",
5748
- gap: 8,
5749
- alignItems: "center"
5750
- },
5751
6380
  children: [
5752
6381
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
5753
6382
  type: "text",
@@ -5788,8 +6417,9 @@ window.__ModuleLoader__.load({
5788
6417
  children: t("profilesEmpty")
5789
6418
  }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5790
6419
  className: "dab-profile-list",
5791
- children: profiles.map((entry) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6420
+ children: profiles.map((entry, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5792
6421
  className: `dab-profile-row${activeProfile === entry.id ? " is-active" : ""}`,
6422
+ style: { "--i": i },
5793
6423
  children: [
5794
6424
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ProfileDot, { entry }),
5795
6425
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -5810,10 +6440,10 @@ window.__ModuleLoader__.load({
5810
6440
  }),
5811
6441
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
5812
6442
  type: "button",
5813
- className: `dab-btn dab-btn-ghost dab-btn-danger${confirmId === entry.id ? " dab-btn-confirm" : ""}`,
6443
+ className: `dab-btn dab-btn-danger-solid${confirmId === entry.id ? " dab-btn-confirm" : ""}`,
5814
6444
  title: t("profileDelete"),
5815
6445
  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")] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TrashIcon, { size: 13 })
6446
+ 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
6447
  })
5818
6448
  ]
5819
6449
  }, entry.id))
@@ -5844,93 +6474,103 @@ window.__ModuleLoader__.load({
5844
6474
  style: { marginTop: 8 },
5845
6475
  children: t("scheduleHint")
5846
6476
  }),
5847
- schedule.enabled ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5848
- className: "dab-schedule-grid",
5849
- children: [
5850
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5851
- className: "dab-schedule-cell",
5852
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5853
- className: "dab-swatch-title",
5854
- children: t("scheduleMode")
5855
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5856
- className: "dab-chip-row",
5857
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
5858
- type: "button",
5859
- className: `dab-chip${schedule.mode === "time" ? " is-active" : ""}`,
5860
- onClick: () => patchSchedule({ mode: "time" }),
5861
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ClockIcon, { size: 13 }), t("scheduleModeTime")]
5862
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
5863
- type: "button",
5864
- className: `dab-chip${schedule.mode === "system" ? " is-active" : ""}`,
5865
- onClick: () => patchSchedule({ mode: "system" }),
5866
- children: t("scheduleModeSystem")
5867
- })]
5868
- })]
5869
- }),
5870
- schedule.mode === "time" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5871
- className: "dab-schedule-cell",
5872
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5873
- className: "dab-swatch-title",
5874
- children: t("scheduleTimes")
5875
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5876
- className: "dab-time-row",
5877
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
5878
- className: "dab-time-label",
5879
- children: [
5880
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SunIcon, { size: 13 }),
5881
- t("scheduleDayStart"),
5882
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
5883
- type: "time",
5884
- className: "dab-timeinput",
5885
- value: schedule.dayStart,
5886
- onChange: (e) => {
5887
- if (/^([01]\d|2[0-3]):[0-5]\d$/.test(e.target.value)) patchSchedule({ dayStart: e.target.value });
5888
- }
5889
- })
5890
- ]
5891
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
5892
- className: "dab-time-label",
5893
- children: [
5894
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MoonIcon, { size: 13 }),
5895
- t("scheduleNightStart"),
5896
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
5897
- type: "time",
5898
- className: "dab-timeinput",
5899
- value: schedule.nightStart,
5900
- onChange: (e) => {
5901
- if (/^([01]\d|2[0-3]):[0-5]\d$/.test(e.target.value)) patchSchedule({ nightStart: e.target.value });
5902
- }
5903
- })
5904
- ]
5905
- })]
5906
- })]
5907
- }) : null,
5908
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5909
- className: "dab-schedule-cell",
5910
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5911
- className: "dab-swatch-title",
5912
- children: t("scheduleProfiles")
5913
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5914
- className: "dab-time-row",
5915
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
5916
- className: "dab-time-label",
5917
- children: [
5918
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SunIcon, { size: 13 }),
5919
- t("scheduleDayProfile"),
5920
- profileOptions(schedule.dayProfile, (id) => patchSchedule({ dayProfile: id }))
5921
- ]
5922
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
5923
- className: "dab-time-label",
5924
- children: [
5925
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MoonIcon, { size: 13 }),
5926
- t("scheduleNightProfile"),
5927
- profileOptions(schedule.nightProfile, (id) => patchSchedule({ nightProfile: id }))
5928
- ]
5929
- })]
5930
- })]
6477
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6478
+ className: `dab-schedule-wrap${schedule.enabled ? " is-open" : ""}`,
6479
+ "aria-hidden": !schedule.enabled,
6480
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6481
+ className: "dab-schedule-clip",
6482
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6483
+ className: "dab-schedule-grid",
6484
+ children: [
6485
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6486
+ className: "dab-schedule-cell",
6487
+ style: { "--i": 0 },
6488
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6489
+ className: "dab-swatch-title",
6490
+ children: t("scheduleMode")
6491
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6492
+ className: "dab-chip-row",
6493
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
6494
+ type: "button",
6495
+ className: `dab-chip${schedule.mode === "time" ? " is-active" : ""}`,
6496
+ onClick: () => patchSchedule({ mode: "time" }),
6497
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ClockIcon, { size: 13 }), t("scheduleModeTime")]
6498
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
6499
+ type: "button",
6500
+ className: `dab-chip${schedule.mode === "system" ? " is-active" : ""}`,
6501
+ onClick: () => patchSchedule({ mode: "system" }),
6502
+ children: t("scheduleModeSystem")
6503
+ })]
6504
+ })]
6505
+ }),
6506
+ schedule.mode === "time" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6507
+ className: "dab-schedule-cell",
6508
+ style: { "--i": 1 },
6509
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6510
+ className: "dab-swatch-title",
6511
+ children: t("scheduleTimes")
6512
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6513
+ className: "dab-time-row",
6514
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
6515
+ className: "dab-time-label",
6516
+ children: [
6517
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SunIcon, { size: 13 }),
6518
+ t("scheduleDayStart"),
6519
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
6520
+ type: "time",
6521
+ className: "dab-timeinput",
6522
+ value: schedule.dayStart,
6523
+ onChange: (e) => {
6524
+ if (/^([01]\d|2[0-3]):[0-5]\d$/.test(e.target.value)) patchSchedule({ dayStart: e.target.value });
6525
+ }
6526
+ })
6527
+ ]
6528
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
6529
+ className: "dab-time-label",
6530
+ children: [
6531
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MoonIcon, { size: 13 }),
6532
+ t("scheduleNightStart"),
6533
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
6534
+ type: "time",
6535
+ className: "dab-timeinput",
6536
+ value: schedule.nightStart,
6537
+ onChange: (e) => {
6538
+ if (/^([01]\d|2[0-3]):[0-5]\d$/.test(e.target.value)) patchSchedule({ nightStart: e.target.value });
6539
+ }
6540
+ })
6541
+ ]
6542
+ })]
6543
+ })]
6544
+ }) : null,
6545
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6546
+ className: "dab-schedule-cell",
6547
+ style: { "--i": 2 },
6548
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6549
+ className: "dab-swatch-title",
6550
+ children: t("scheduleProfiles")
6551
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6552
+ className: "dab-time-row",
6553
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
6554
+ className: "dab-time-label",
6555
+ children: [
6556
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SunIcon, { size: 13 }),
6557
+ t("scheduleDayProfile"),
6558
+ profileOptions(schedule.dayProfile, (id) => patchSchedule({ dayProfile: id }))
6559
+ ]
6560
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
6561
+ className: "dab-time-label",
6562
+ children: [
6563
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MoonIcon, { size: 13 }),
6564
+ t("scheduleNightProfile"),
6565
+ profileOptions(schedule.nightProfile, (id) => patchSchedule({ nightProfile: id }))
6566
+ ]
6567
+ })]
6568
+ })]
6569
+ })
6570
+ ]
5931
6571
  })
5932
- ]
5933
- }) : null
6572
+ })
6573
+ })
5934
6574
  ]
5935
6575
  }),
5936
6576
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -6186,7 +6826,7 @@ window.__ModuleLoader__.load({
6186
6826
  }, "dsh-any-background: skin dispose");
6187
6827
  const styleEl = document.createElement("style");
6188
6828
  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}`;
6829
+ 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}${POPOVER_BLUR_RULE}${TRAJECTORY_STYLE_RULE}${INPUT_BLUR_RULE}${MOBILE_HEADER_RULE}${PANEL_TOKEN_RULE}${PANEL_BLUR_RULE}${PRODUCED_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
6830
  document.head.appendChild(styleEl);
6191
6831
  ctx.effect(() => () => {
6192
6832
  styleEl?.parentNode?.removeChild(styleEl);
@@ -6291,32 +6931,35 @@ window.__ModuleLoader__.load({
6291
6931
  * the full adaptation (host skin + editor wheel sync); the caller persists. */
6292
6932
  const adoptWallpaperColor = async (dataUrl) => {
6293
6933
  const hsl = await extractWallpaperColor(dataUrl, rBgState());
6294
- if (!hsl) return;
6934
+ if (!hsl || rWp() !== dataUrl) return;
6295
6935
  cfg.color = hsl;
6296
6936
  registerCustom(hsl[0], hsl[1], hsl[2]);
6297
6937
  colorRev++;
6298
6938
  bound?.syncColor(hslToHsv(hsl[0], hsl[1], hsl[2]), colorRev);
6299
6939
  };
6300
6940
  /** Activate a rotation item: the server copies its bytes into the wallpaper
6301
- * slot; the client applies the returned data URL through the normal image
6941
+ * slot; the client applies the returned serve URL through the normal image
6302
6942
  * path, then re-extracts the theme color from the new picture so the
6303
6943
  * palette follows the rotation. */
6304
6944
  const applyRotationIndex = async (idx, auto) => {
6305
6945
  const rot = rRotation();
6306
6946
  if (idx < 0 || idx >= rot.items.length) return false;
6307
6947
  const r = await rotationActivate(idx);
6308
- if (!r.ok || !r.dataUrl) {
6948
+ if (!r.ok || !r.wallpaperUrl) {
6309
6949
  console.warn("dsh-any-background: rotation activate failed", r.error);
6310
6950
  return false;
6311
6951
  }
6312
- setWpImageUrl(r.dataUrl);
6313
- if (cfg.backgroundType === "image") setWpUrl(r.dataUrl);
6952
+ cfg.backgroundType = "image";
6953
+ setBgDark(null);
6954
+ setWpImageUrl(r.wallpaperUrl);
6955
+ setWpUrl(rWpImage());
6956
+ setBgState({ ...DEFAULT_CONFIG.bgState });
6314
6957
  cfg.rotation = {
6315
6958
  ...rot,
6316
6959
  current: idx,
6317
6960
  lastRotate: auto || rot.lastRotate === null ? (/* @__PURE__ */ new Date()).toISOString() : rot.lastRotate
6318
6961
  };
6319
- if (cfg.backgroundType === "image") await adoptWallpaperColor(r.dataUrl);
6962
+ if (cfg.backgroundType === "image") await adoptWallpaperColor(rWp());
6320
6963
  applyThemeColor();
6321
6964
  syncBg();
6322
6965
  saveConfig();
@@ -6428,6 +7071,7 @@ window.__ModuleLoader__.load({
6428
7071
  ctx.effect(() => () => {
6429
7072
  teardownWp();
6430
7073
  }, "dsh-any-background: wp cleanup");
7074
+ ctx.effect(() => startBetterSidebarWatch(), "dsh-any-background: better-sidebar watch");
6431
7075
  ctx.effect(() => ctx.on("theme/change", () => {
6432
7076
  if (rHasColor() || rBgDark() !== null || rSchemeOverride() !== "auto") {
6433
7077
  const snapshot = ctx.theme.getTheme();
@@ -6510,16 +7154,16 @@ window.__ModuleLoader__.load({
6510
7154
  nl
6511
7155
  ], colorRev);
6512
7156
  },
6513
- setWp: (u) => {
7157
+ setWpFromServer: (u) => {
6514
7158
  cfg.backgroundType = "image";
6515
7159
  setBgDark(null);
6516
7160
  setWpImageUrl(u);
6517
- setWpUrl(u);
7161
+ setWpUrl(rWpImage());
6518
7162
  setBgState({ ...DEFAULT_CONFIG.bgState });
6519
- persistWallpaper(u);
6520
7163
  if (u === null) {
6521
7164
  setWpVideoUrl(null, null);
6522
7165
  persistVideo(null);
7166
+ persistWallpaper(null);
6523
7167
  }
6524
7168
  applyThemeColor();
6525
7169
  syncBg();
@@ -6605,6 +7249,11 @@ window.__ModuleLoader__.load({
6605
7249
  applySettingsOverrides(v);
6606
7250
  saveConfig();
6607
7251
  },
7252
+ setPanelOp: (v) => {
7253
+ cfg.panelOpacity = v;
7254
+ applyPanelOverrides(v);
7255
+ saveConfig();
7256
+ },
6608
7257
  extractColor: async () => {
6609
7258
  const url = rWp();
6610
7259
  if (!url) return false;
@@ -6624,23 +7273,29 @@ window.__ModuleLoader__.load({
6624
7273
  if (cfg.backgroundType === "video") {
6625
7274
  const vurl = rWpVideo();
6626
7275
  if (vurl) try {
6627
- const blob = await fetch(vurl).then((r) => r.blob());
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
- });
7276
+ videoPayload = await blobToDataUrl(await fetch(vurl).then((r) => r.blob()));
6634
7277
  if (videoPayload && !/^data:video\//.test(videoPayload)) videoPayload = videoPayload.replace(/^data:[^;,]*/, `data:${cfg.videoMime ?? "video/mp4"}`);
6635
7278
  } catch {
6636
7279
  videoPayload = null;
6637
7280
  }
6638
7281
  }
7282
+ let wallpaperPayload = null;
7283
+ if (cfg.backgroundType === "image") {
7284
+ const wurl = rWp();
7285
+ if (wurl) {
7286
+ if (wurl.startsWith("data:")) wallpaperPayload = wurl;
7287
+ else try {
7288
+ wallpaperPayload = await blobToDataUrl(await fetch(wurl).then((r) => r.blob()));
7289
+ } catch {
7290
+ wallpaperPayload = null;
7291
+ }
7292
+ }
7293
+ }
6639
7294
  const payload = {
6640
7295
  version: 2,
6641
7296
  exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
6642
7297
  config: cfg,
6643
- wallpaper: cfg.backgroundType === "image" ? rWp() : null,
7298
+ wallpaper: wallpaperPayload,
6644
7299
  video: videoPayload
6645
7300
  };
6646
7301
  const blob = new Blob([JSON.stringify(payload, null, 2)], { type: "application/json" });
@@ -6821,6 +7476,29 @@ window.__ModuleLoader__.load({
6821
7476
  rotateNow: async () => {
6822
7477
  if (rRotation().items.length === 0) return false;
6823
7478
  return applyRotationIndex(pickNextRotationIndex(), true);
7479
+ },
7480
+ setVideoFromUrl: async (url) => {
7481
+ const r = await setVideoFromUrl(url);
7482
+ if (!r.ok || !r.mime) {
7483
+ console.warn("dsh-any-background: video url fetch failed", r.error);
7484
+ return false;
7485
+ }
7486
+ setBgDark(null);
7487
+ cfg.backgroundType = "video";
7488
+ setWpUrl(null);
7489
+ cfg.videoBgState = { ...DEFAULT_CONFIG.bgState };
7490
+ setWpVideoUrl(VIDEO_SERVE_URL, r.mime);
7491
+ applyWp();
7492
+ saveConfig();
7493
+ syncBg();
7494
+ const applied = rWpVideo();
7495
+ captureVideoSnapshot(applied).then((snap) => {
7496
+ if (rWpVideo() !== applied) return;
7497
+ setWpVideoSnapshot(snap);
7498
+ applyThemeColor();
7499
+ syncBg();
7500
+ });
7501
+ return true;
6824
7502
  }
6825
7503
  };
6826
7504
  };