dsh-any-background 0.1.9 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -65,8 +65,8 @@ A **DeepSeek Harness** appearance plugin that lets you fully customize the Web U
65
65
  - **Position Editor** — One shared editor for images and videos: drag to pan, scroll to zoom, one-click reset. Image and video placements are stored separately and never overwrite each other.
66
66
  - **Layout Modes** — Fit / Fill / Stretch / Tile / Center for both images and videos; in Fit mode the editor-committed framing stays consistent across window resizes and cross-monitor moves.
67
67
  - **Generated Dynamic Backgrounds** — Choose mesh gradient, Shader, or geometric patterns with adjustable spread, intensity, and seed locking.
68
- - **Per-part Interface Opacity** — Independent sliders for main background, sidebar, cards & panels, plus the settings panel and wallpaper.
69
- - **Per-part Interface Blur** — Frosted-glass `backdrop-filter` blur (0–60 px) for each interface part.
68
+ - **Per-part Interface Opacity** — Independent sliders for the main background, sidebar, cards & panels (including the dropdowns and menus around the dialog), the input & controls (composer box, Cordis panel), plus the settings panel and wallpaper.
69
+ - **Per-part Interface Blur** — Frosted-glass `backdrop-filter` blur (0–60 px) for each interface part, including a real backdrop on the composer and Cordis panel via stable host selectors.
70
70
  - **Conversation View Cards** — The message list is wrapped in a translucent card automatically, and the trajectory page gets whole-page opacity & blur controls, letting the wallpaper shine through the content.
71
71
  - **Theme Export / Import** — One-click export to a self-contained `dsh-any-theme.json` (config + wallpaper, video embedded as a data URL) and import to restore it anywhere.
72
72
  - **File-based Persistence** — All settings are stored on the filesystem under `~/.dsh/.dsh-any-background-data/`, not `localStorage`.
@@ -84,6 +84,13 @@ A **DeepSeek Harness** appearance plugin that lets you fully customize the Web U
84
84
  - **Inspiration palette selection cleared** — Picking a theme color from the wheel deselects any previously selected inspiration swatch.
85
85
  - **Debug telemetry removed** — Temporary boot-time logging and `MutationObserver` instrumentation have been cleaned out.
86
86
  - **Per-part blur isolated** — Blur is applied on `::before` underlays so it never traps the host's fixed-position settings dialog.
87
+ - **New "Input & controls" part** — The composer box, slash menu, and Cordis plugin panel own a dedicated opacity slider; their translucent surfaces get real frosted-glass backdrop blur attached through stable host data attributes (`[data-composer-card]`, `[data-cordis-panel]`).
88
+ - **Native control contrast fixed** — Solid surface tokens (composer, menus, Cordis panel) now carry per-part alpha, and a forced `color-scheme` keeps native `<select>` popups legible — no more white-on-white when the wallpaper brightness verdict flips labels to white.
89
+ - **Translucency without self-damage** — The "Input & controls" opacity slider is deliberately decoupled from the settings panel's own controls (which share the same button tokens), so adjusting it can never bleach the plugin's own sliders and buttons.
90
+ - **Drag-time performance** — Opacity tokens are written as CSS variables on `<html>` instead of rebuilding the whole token stylesheet per tick; slider updates are coalesced to one rAF per frame; the main-background columns are only retinted when their own slider changes.
91
+ - **Drag-time wallpaper downscaling** — While any slider is being dragged the wallpaper swaps to a (~720px) low-res copy for cheap rasterization over large images, then restores full resolution on release.
92
+ - **Instant video playback on import** — A picked video starts playing immediately from a local object URL while its raw bytes stream to disk in the background; the persisted serve URL takes over on the next reload — no more upload + first-buffer wait after import.
93
+ - **Option boxes follow the card slider** — The dropdowns, slash-trigger menu, model selector, and popovers around the dialog now follow the "Cards & panels" opacity slider instead of "Input & controls"; the Cordis panel keeps its own input-slider binding.
87
94
 
88
95
  ## Installation
89
96
 
package/README.zh.md CHANGED
@@ -65,8 +65,8 @@
65
65
  - **位置编辑器** — 图片与视频共用同一套编辑器:拖动平移、滚轮缩放、一键重置;图片与视频的位置状态各自独立保存,互不覆盖。
66
66
  - **布局模式** — 适应 / 填充 / 拉伸 / 平铺 / 居中五种排布,图片与视频通用;「适应」模式下编辑器提交的构图在窗口缩放、跨屏移动后保持一致。
67
67
  - **动态生成背景** — 支持网格渐变、Shader、几何图案,可调节扩散范围、色彩强度并锁定种子。
68
- - **分部位界面透明度** — 主背景、侧边栏、卡片面板、设置面板与壁纸各自独立滑块。
69
- - **分部位界面模糊度** — 每个界面部位可独立调整毛玻璃 `backdrop-filter` 模糊(0–60 px)。
68
+ - **分部位界面透明度** — 主背景、侧边栏、卡片面板(含对话框周围的选项框/菜单)、输入框与控件(发送框、Cordis 插件面板)、设置面板与壁纸各自独立滑块。
69
+ - **分部位界面模糊度** — 每个界面部位可独立调整毛玻璃 `backdrop-filter` 模糊(0–60 px),并通过宿主的稳定选择器为发送框与 Cordis 面板提供真实背景模糊。
70
70
  - **对话视图卡片** — 消息列表自动包裹为半透明卡片,轨迹页可整页调节透明度与模糊,让壁纸从内容后方透出来。
71
71
  - **主题导出 / 导入** — 一键导出为自包含的 `dsh-any-theme.json`(配置 + 壁纸,视频以 data URL 内嵌),可随时导入还原。
72
72
  - **文件持久化** — 所有设置保存到文件系统 `~/.dsh/.dsh-any-background-data/`,不再依赖 `localStorage`。
@@ -84,6 +84,13 @@
84
84
  - **灵感色板选中重置** — 在色轮中选取主题色后,灵感色板中的之前选中状态自动清除。
85
85
  - **移除调试埋点** — 清理了临时的启动日志与 `MutationObserver` 探针代码。
86
86
  - **分部位模糊隔离** — 模糊效果施加在 `::before` 伪元素上,避免影响宿主固定定位的设置弹窗。
87
+ - **新增「输入框与控件」部件** — 发送框、斜杠菜单、Cordis 插件面板拥有独立的透明度滑块;半透明表面通过宿主的稳定 data 属性(`[data-composer-card]`、`[data-cordis-panel]`)获得真正的毛玻璃背景模糊。
88
+ - **修复原生控件明暗对比** — 实心表面令牌(发送框、菜单、Cordis 面板)现在参与分部位 alpha,并强制 `color-scheme` 让原生 `<select>` 弹层保持可读——消除壁纸判暗把标签转白后出现的白底白字。
89
+ - **透明化不反噬自身** — 「输入框与控件」透明度滑块刻意与该面板自己的控件(共享同一批按钮令牌)解绑,调节它不会把插件自身的滑块和按钮也变透明。
90
+ - **拖动性能优化** — 透明度令牌改为写 `<html>` 上的 CSS 变量(拖动时不再整表重建),滑块更新按 rAF 合帧为每帧一次;「主背景」列仅在自身滑块变化时才重染。
91
+ - **拖动时壁纸降采样** — 拖动任意滑块期间,壁纸临时切换到(~720px)低清副本以降低大图上的合成成本,松手后恢复全分辨率。
92
+ - **导入即播视频壁纸** — 选中的视频立即通过本地对象 URL 开始播放,原始字节在后台流式落盘,下次刷新自动切回持久化服务地址——不再有「上传 + 首帧缓冲」的等待。
93
+ - **选项框跟随卡片滑块** — 对话框周围的下拉菜单、斜杠命令菜单、模型选择器与弹层现在跟随「对话框中选项面板」透明度滑块,而非「输入框与控件」;Cordis 插件面板保持自己的输入滑块绑定。
87
94
 
88
95
  ## 安装
89
96
 
package/lib/client.js CHANGED
@@ -32,6 +32,7 @@ window.__ModuleLoader__.load({
32
32
  uiOpacityBg: "主背景",
33
33
  uiOpacitySide: "侧边栏",
34
34
  uiOpacityCard: "对话框中选项面板",
35
+ uiOpacityInput: "输入框与控件",
35
36
  uiSop: "设置界面透明度",
36
37
  uiChatRegion: "对话文本框",
37
38
  uiTrajectory: "轨迹页",
@@ -129,6 +130,7 @@ window.__ModuleLoader__.load({
129
130
  uiOpacityBg: "Main background",
130
131
  uiOpacitySide: "Sidebar",
131
132
  uiOpacityCard: "Cards & panels",
133
+ uiOpacityInput: "Input & controls",
132
134
  uiSop: "Settings interface opacity",
133
135
  uiChatRegion: "Conversation text frame",
134
136
  uiTrajectory: "Trajectory view",
@@ -211,7 +213,8 @@ window.__ModuleLoader__.load({
211
213
  opacities: {
212
214
  bg: .85,
213
215
  sidebar: .93,
214
- card: 1
216
+ card: 1,
217
+ input: 1
215
218
  },
216
219
  blurs: {
217
220
  bg: 0,
@@ -219,7 +222,8 @@ window.__ModuleLoader__.load({
219
222
  card: 0,
220
223
  settings: 0,
221
224
  chat: 0,
222
- trajectory: 0
225
+ trajectory: 0,
226
+ input: 0
223
227
  },
224
228
  settingsOpacity: 1,
225
229
  wallpaperOpacity: 1,
@@ -259,6 +263,8 @@ window.__ModuleLoader__.load({
259
263
  let wpVideoUrl = null;
260
264
  /** Captured video frame standing in for previews/color extraction (still-image APIs). */
261
265
  let wpVideoSnapshot = null;
266
+ /** Local blob URL backing an in-session video; revoked when replaced or cleared. */
267
+ let wpVideoObjectUrl = null;
262
268
  function setWpImageUrl(url) {
263
269
  wpImageUrl = url;
264
270
  }
@@ -272,13 +278,25 @@ window.__ModuleLoader__.load({
272
278
  wpVideoSnapshot = null;
273
279
  } else {
274
280
  videoRev++;
275
- wpVideoUrl = `${url}${url.includes("?") ? "&" : "?"}r=${videoRev}`;
281
+ wpVideoUrl = url.startsWith("blob:") ? url : `${url}${url.includes("?") ? "&" : "?"}r=${videoRev}`;
276
282
  }
283
+ if (wpVideoObjectUrl !== null && wpVideoObjectUrl !== url) {
284
+ URL.revokeObjectURL(wpVideoObjectUrl);
285
+ wpVideoObjectUrl = null;
286
+ }
287
+ if (url !== null && url.startsWith("blob:")) wpVideoObjectUrl = url;
277
288
  cfg.videoMime = url ? mime : null;
278
289
  }
279
290
  function setWpVideoSnapshot(url) {
280
291
  wpVideoSnapshot = url;
281
292
  }
293
+ /** Release the in-session video object URL (plugin teardown). */
294
+ function disposeVideoObjectUrl() {
295
+ if (wpVideoObjectUrl !== null) {
296
+ URL.revokeObjectURL(wpVideoObjectUrl);
297
+ wpVideoObjectUrl = null;
298
+ }
299
+ }
282
300
  function setBgState(s) {
283
301
  cfg.bgState = s;
284
302
  }
@@ -326,7 +344,8 @@ window.__ModuleLoader__.load({
326
344
  for (const k of [
327
345
  "bg",
328
346
  "sidebar",
329
- "card"
347
+ "card",
348
+ "input"
330
349
  ]) out[k] = clamp01(o[k], DEFAULT_CONFIG.opacities[k]);
331
350
  return out;
332
351
  }
@@ -339,7 +358,8 @@ window.__ModuleLoader__.load({
339
358
  "card",
340
359
  "settings",
341
360
  "chat",
342
- "trajectory"
361
+ "trajectory",
362
+ "input"
343
363
  ]) {
344
364
  const v = b[k];
345
365
  out[k] = typeof v === "number" ? Math.min(60, Math.max(0, v)) : DEFAULT_CONFIG.blurs[k];
@@ -390,7 +410,8 @@ window.__ModuleLoader__.load({
390
410
  "card",
391
411
  "settings",
392
412
  "chat",
393
- "trajectory"
413
+ "trajectory",
414
+ "input"
394
415
  ]) blurs[k] = num(bl[k], DEFAULT_CONFIG.blurs[k]);
395
416
  const bgType = [
396
417
  "video",
@@ -412,7 +433,8 @@ window.__ModuleLoader__.load({
412
433
  opacities: {
413
434
  bg: num(ops.bg, legacy ?? DEFAULT_CONFIG.opacities.bg),
414
435
  sidebar: num(ops.sidebar, legacy !== null ? Math.min(1, legacy + .08) : DEFAULT_CONFIG.opacities.sidebar),
415
- card: num(ops.card, DEFAULT_CONFIG.opacities.card)
436
+ card: num(ops.card, DEFAULT_CONFIG.opacities.card),
437
+ input: num(ops.input, DEFAULT_CONFIG.opacities.input)
416
438
  },
417
439
  blurs,
418
440
  settingsOpacity: num(c.settingsOpacity, DEFAULT_CONFIG.settingsOpacity),
@@ -601,6 +623,7 @@ window.__ModuleLoader__.load({
601
623
  "--dsw-alias-button-primary-hover": hsl(h(0), s(.1), Math.max(l(.28), .58)),
602
624
  "--dsw-alias-button-primary-dimmed": hsl(h(0), s(0), l(.07)),
603
625
  "--dsw-alias-button-elevated-fill": hsl(h(0), s(0), l(.04)),
626
+ "--dsw-alias-button-floating-hover": hsl(h(0), s(0), l(.1)),
604
627
  "--dsw-alias-interactive-bg-hover": rgba(h(0), s(0), Math.max(l(.15), .4), .12),
605
628
  "--dsw-alias-interactive-bg-active": rgba(h(0), s(0), Math.max(l(.15), .4), .2),
606
629
  "--dsw-alias-markdown-code-block": hsl(h(0), s(0), l(-.06)),
@@ -612,6 +635,7 @@ window.__ModuleLoader__.load({
612
635
  "--dsw-specific-sidebar-nav-item-active": hsl(h(0), s(0), l(.04)),
613
636
  "--dsw-specific-sidebar-nav-item-hover": hsl(h(0), s(0), l(0)),
614
637
  "--dsw-specific-input-major": hsl(h(0), s(0), l(.02)),
638
+ "--dsw-specific-menu": hsl(h(0), s(0), l(.08)),
615
639
  "--dsw-alias-scrollbar-bg-l1": hsl(h(0), s(-.05), l(.12)),
616
640
  "--dsw-alias-scrollbar-bg-l2": hsl(h(0), s(-.05), l(.16)),
617
641
  "--dsw-alias-scrollbar-hover-l1": hsl(h(0), s(-.05), l(.22)),
@@ -639,6 +663,7 @@ window.__ModuleLoader__.load({
639
663
  "--dsw-alias-button-primary-hover": hsl(h(0), s(.05), Math.min(l(-.12), .5)),
640
664
  "--dsw-alias-button-primary-dimmed": hsl(h(0), s(-.1), l(-.03)),
641
665
  "--dsw-alias-button-elevated-fill": hsl(h(0), s(-.1), l(.1)),
666
+ "--dsw-alias-button-floating-hover": hsl(h(0), s(-.1), l(.16)),
642
667
  "--dsw-alias-interactive-bg-hover": rgba(h(0), s(0), l(-.3), .08),
643
668
  "--dsw-alias-interactive-bg-active": rgba(h(0), s(0), l(-.3), .14),
644
669
  "--dsw-alias-markdown-code-block": hsl(h(0), s(-.1), l(-.03)),
@@ -647,6 +672,7 @@ window.__ModuleLoader__.load({
647
672
  "--dsw-specific-sidebar-nav-item-active": hsl(h(0), s(-.08), l(.05)),
648
673
  "--dsw-specific-sidebar-nav-item-hover": hsl(h(0), s(-.12), l(0)),
649
674
  "--dsw-specific-input-major": hsl(h(0), s(-.12), l(.1)),
675
+ "--dsw-specific-menu": hsl(h(0), s(-.12), l(.15)),
650
676
  "--dsw-alias-scrollbar-bg-l1": hsl(h(0), s(-.1), l(-.08)),
651
677
  "--dsw-alias-scrollbar-bg-l2": hsl(h(0), s(-.08), l(-.12)),
652
678
  "--dsw-alias-scrollbar-hover-l1": hsl(h(0), s(-.08), l(-.16)),
@@ -1472,7 +1498,54 @@ window.__ModuleLoader__.load({
1472
1498
  "--dsw-alias-label-dimmed",
1473
1499
  "--dsw-alias-label-quaternary"
1474
1500
  ];
1501
+ const OPACITY_TOKEN_GROUPS = [
1502
+ {
1503
+ part: "bg",
1504
+ names: ["--dsw-alias-bg-base"]
1505
+ },
1506
+ {
1507
+ part: "sidebar",
1508
+ names: ["--dsw-specific-sidebar-fill"]
1509
+ },
1510
+ {
1511
+ part: "card",
1512
+ names: [
1513
+ "--dsw-alias-bg-layer-1",
1514
+ "--dsw-alias-bg-layer-2",
1515
+ "--dsw-alias-bg-layer-3",
1516
+ "--dsw-specific-menu"
1517
+ ]
1518
+ },
1519
+ {
1520
+ part: "input",
1521
+ names: ["--dsw-specific-input-major"]
1522
+ }
1523
+ ];
1524
+ const OPACITY_VARS = {
1525
+ "--dsw-alias-bg-base": "--dsh-any-op-bg",
1526
+ "--dsw-specific-sidebar-fill": "--dsh-any-op-sidebar",
1527
+ "--dsw-alias-bg-layer-1": "--dsh-any-op-card-1",
1528
+ "--dsw-alias-bg-layer-2": "--dsh-any-op-card-2",
1529
+ "--dsw-alias-bg-layer-3": "--dsh-any-op-card-3",
1530
+ "--dsw-specific-input-major": "--dsh-any-op-input",
1531
+ "--dsw-specific-menu": "--dsh-any-op-menu"
1532
+ };
1533
+ let baseTokenKey = "";
1534
+ let pendingOps = null;
1535
+ let tokensRaf = null;
1475
1536
  function applyCustomTokens(ops) {
1537
+ pendingOps = ops;
1538
+ if (tokensRaf !== null) return;
1539
+ tokensRaf = requestAnimationFrame(() => {
1540
+ tokensRaf = null;
1541
+ if (pendingOps === null) return;
1542
+ const o = pendingOps;
1543
+ pendingOps = null;
1544
+ applyCustomTokensNow(o);
1545
+ });
1546
+ }
1547
+ let lastBgKey = "";
1548
+ function applyCustomTokensNow(ops) {
1476
1549
  const [h, s, l] = rColor();
1477
1550
  let { tokens } = genTokens(h, s, l);
1478
1551
  try {
@@ -1482,24 +1555,37 @@ window.__ModuleLoader__.load({
1482
1555
  const font = dark ? "#fff" : "#000";
1483
1556
  for (const name of LABEL_TOKENS) tokens[name] = font;
1484
1557
  }
1485
- if (dark ?? l < .55) document.body.setAttribute("data-ds-dark-theme", "dsh-any-background");
1486
- else document.body.removeAttribute("data-ds-dark-theme");
1487
- const decls = [];
1488
- for (const [name, value] of Object.entries(tokens)) {
1489
- let v = value;
1490
- if (name === "--dsw-alias-bg-base") v = toRgba(value, ops.bg);
1491
- else if (name === "--dsw-specific-sidebar-fill") v = toRgba(value, ops.sidebar);
1492
- else if (name === "--dsw-alias-bg-layer-1" || name === "--dsw-alias-bg-layer-2" || name === "--dsw-alias-bg-layer-3") v = toRgba(value, ops.card);
1493
- decls.push(`${name}:${v}!important`);
1558
+ const forceDark = dark ?? l < .55;
1559
+ if (`${h}|${s}|${l}|${dark}` !== baseTokenKey) {
1560
+ baseTokenKey = `${h}|${s}|${l}|${dark}`;
1561
+ if (forceDark) document.body.setAttribute("data-ds-dark-theme", "dsh-any-background");
1562
+ else document.body.removeAttribute("data-ds-dark-theme");
1563
+ const decls = [`color-scheme:${forceDark ? "dark" : "light"}`];
1564
+ for (const [name, value] of Object.entries(tokens)) {
1565
+ const opVar = OPACITY_VARS[name];
1566
+ decls.push(`${name}:${opVar !== void 0 ? `var(${opVar})` : value}!important`);
1567
+ }
1568
+ ensureTokenStyle().textContent = `body{${decls.join(";")}}`;
1569
+ for (const name of appliedTokenNames) document.body.style.removeProperty(name);
1570
+ appliedTokenNames = Object.keys(tokens);
1571
+ }
1572
+ const root = document.documentElement;
1573
+ for (const g of OPACITY_TOKEN_GROUPS) for (const name of g.names) root.style.setProperty(OPACITY_VARS[name], toRgba(tokens[name] ?? "#000", ops[g.part]));
1574
+ root.style.setProperty("--dsh-any-op-menu-cordis", toRgba(tokens["--dsw-specific-menu"] ?? "#000", ops.input));
1575
+ const bgKey = `${baseTokenKey}|${ops.bg}`;
1576
+ if (bgKey !== lastBgKey) {
1577
+ lastBgKey = bgKey;
1578
+ applyPartOpacities(ops);
1494
1579
  }
1495
- ensureTokenStyle().textContent = `body{${decls.join(";")}}`;
1496
- for (const name of appliedTokenNames) document.body.style.removeProperty(name);
1497
- appliedTokenNames = Object.keys(tokens);
1498
- applyPartOpacities(ops);
1499
1580
  } catch {}
1500
1581
  }
1501
1582
  const SETTINGS_PANEL_SEL = "[role=\"dialog\"][aria-modal=\"true\"][aria-labelledby]";
1502
1583
  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)}`;
1584
+ 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}";
1585
+ function applyInputBlur(px) {
1586
+ if (px > 0) document.documentElement.style.setProperty("--dsh-any-input-blur", `blur(${px}px)`);
1587
+ else document.documentElement.style.removeProperty("--dsh-any-input-blur");
1588
+ }
1503
1589
  function applySettingsOverrides(op) {
1504
1590
  const [h, s, l] = rColor();
1505
1591
  const tokens = genTokens(h, s, l).tokens;
@@ -1688,6 +1774,7 @@ window.__ModuleLoader__.load({
1688
1774
  setBlur(detailsEl, blurs.bg);
1689
1775
  applyCardPanelsBlur(blurs.card);
1690
1776
  applySettingsBlur(blurs.settings);
1777
+ applyInputBlur(blurs.input);
1691
1778
  applyViewCards();
1692
1779
  }
1693
1780
  /** Live per-part blur update during slider drag (no full re-apply). */
@@ -1700,6 +1787,10 @@ window.__ModuleLoader__.load({
1700
1787
  applyCardPanelsBlur(v);
1701
1788
  return;
1702
1789
  }
1790
+ if (part === "input") {
1791
+ applyInputBlur(v);
1792
+ return;
1793
+ }
1703
1794
  if (part === "chat" || part === "trajectory") {
1704
1795
  applyViewCards();
1705
1796
  return;
@@ -1923,6 +2014,78 @@ window.__ModuleLoader__.load({
1923
2014
  img.onerror = () => cb(0, 0);
1924
2015
  img.src = url;
1925
2016
  }
2017
+ const DRAG_MAX_SIDE = 720;
2018
+ let lowResUrl = null;
2019
+ let lowResFor = "";
2020
+ let dragLow = false;
2021
+ function captureLowRes(url, cb) {
2022
+ if (lowResFor === url) {
2023
+ cb(lowResUrl);
2024
+ return;
2025
+ }
2026
+ const img = new Image();
2027
+ img.onload = () => {
2028
+ const k = Math.min(1, DRAG_MAX_SIDE / Math.max(img.naturalWidth, img.naturalHeight));
2029
+ if (k >= 1) {
2030
+ lowResFor = url;
2031
+ lowResUrl = null;
2032
+ cb(null);
2033
+ return;
2034
+ }
2035
+ const c = document.createElement("canvas");
2036
+ c.width = Math.max(1, Math.round(img.naturalWidth * k));
2037
+ c.height = Math.max(1, Math.round(img.naturalHeight * k));
2038
+ const g = c.getContext("2d");
2039
+ if (!g) {
2040
+ lowResFor = url;
2041
+ lowResUrl = null;
2042
+ cb(null);
2043
+ return;
2044
+ }
2045
+ g.drawImage(img, 0, 0, c.width, c.height);
2046
+ const low = c.toDataURL("image/jpeg", .85);
2047
+ lowResFor = url;
2048
+ lowResUrl = low;
2049
+ cb(low);
2050
+ };
2051
+ img.onerror = () => cb(null);
2052
+ img.src = url;
2053
+ }
2054
+ function setDragLow(on) {
2055
+ if (cfg.backgroundType !== "image" || on === dragLow || !wpEl) return;
2056
+ const full = rWpImage();
2057
+ if (!full) return;
2058
+ if (on) {
2059
+ dragLow = true;
2060
+ captureLowRes(full, (low) => {
2061
+ if (!dragLow || !wpEl || low === null) return;
2062
+ if (wpEl.style.backgroundImage !== `url("${low}")`) wpEl.style.backgroundImage = `url("${low}")`;
2063
+ });
2064
+ } else {
2065
+ dragLow = false;
2066
+ if (wpEl.style.backgroundImage !== `url("${full}")`) wpEl.style.backgroundImage = `url("${full}")`;
2067
+ }
2068
+ }
2069
+ /** While any range slider in the app is being dragged, run the wallpaper at
2070
+ * reduced resolution; restore on release. Returns a disposer for teardown. */
2071
+ function watchWallpaperDragQuality() {
2072
+ const isRange = (t) => t instanceof HTMLInputElement && t.type === "range";
2073
+ const down = (e) => {
2074
+ if (isRange(e.target)) setDragLow(true);
2075
+ };
2076
+ const up = () => {
2077
+ if (dragLow) setDragLow(false);
2078
+ };
2079
+ window.addEventListener("pointerdown", down, true);
2080
+ window.addEventListener("pointerup", up, true);
2081
+ window.addEventListener("pointercancel", up, true);
2082
+ return () => {
2083
+ window.removeEventListener("pointerdown", down, true);
2084
+ window.removeEventListener("pointerup", up, true);
2085
+ window.removeEventListener("pointercancel", up, true);
2086
+ if (dragLow) setDragLow(false);
2087
+ };
2088
+ }
1926
2089
  function applyImageWp(url) {
1927
2090
  clearDynamicBg();
1928
2091
  clearVideoEl();
@@ -1967,6 +2130,7 @@ window.__ModuleLoader__.load({
1967
2130
  }
1968
2131
  });
1969
2132
  }
2133
+ captureLowRes(url, () => void 0);
1970
2134
  applyWpEffects();
1971
2135
  }
1972
2136
  /** Video wallpaper: a muted looping <video> inside the wallpaper layer.
@@ -1982,11 +2146,12 @@ window.__ModuleLoader__.load({
1982
2146
  videoEl.loop = true;
1983
2147
  videoEl.autoplay = true;
1984
2148
  videoEl.playsInline = true;
2149
+ videoEl.preload = "auto";
1985
2150
  videoEl.setAttribute("playsinline", "");
1986
2151
  videoEl.style.cssText = "position:absolute;inset:0;width:100%;height:100%;object-position:center;";
2152
+ wpEl.appendChild(videoEl);
1987
2153
  videoEl.setAttribute("src", url);
1988
2154
  videoEl.play().catch(() => void 0);
1989
- wpEl.appendChild(videoEl);
1990
2155
  } else if (videoEl.getAttribute("src") !== url) {
1991
2156
  videoEl.setAttribute("src", url);
1992
2157
  videoEl.play().catch(() => void 0);
@@ -2047,6 +2212,7 @@ window.__ModuleLoader__.load({
2047
2212
  function teardownWp() {
2048
2213
  clearDynamicBg();
2049
2214
  clearVideoEl();
2215
+ disposeVideoObjectUrl();
2050
2216
  setBgDark(null);
2051
2217
  wpEl?.remove();
2052
2218
  wpEl = null;
@@ -2054,6 +2220,8 @@ window.__ModuleLoader__.load({
2054
2220
  tokenStyleEl?.remove();
2055
2221
  tokenStyleEl = null;
2056
2222
  removeViewCards();
2223
+ document.body.removeAttribute("data-ds-dark-theme");
2224
+ document.body.style.removeProperty("color-scheme");
2057
2225
  document.documentElement.style.removeProperty("--dsh-any-bg-settings-surface");
2058
2226
  document.documentElement.style.removeProperty("--dsh-any-bg-settings-layer-1");
2059
2227
  document.documentElement.style.removeProperty("--dsh-any-bg-settings-layer-2");
@@ -2064,6 +2232,15 @@ window.__ModuleLoader__.load({
2064
2232
  document.documentElement.style.removeProperty("--dsh-any-bg-settings-card-surface");
2065
2233
  document.documentElement.style.removeProperty("--dsh-any-blur-settings");
2066
2234
  document.documentElement.style.removeProperty("--dsh-any-blur-card-panels");
2235
+ document.documentElement.style.removeProperty("--dsh-any-input-blur");
2236
+ for (const v of Object.values(OPACITY_VARS)) document.documentElement.style.removeProperty(v);
2237
+ baseTokenKey = "";
2238
+ lastBgKey = "";
2239
+ if (tokensRaf !== null) {
2240
+ cancelAnimationFrame(tokensRaf);
2241
+ tokensRaf = null;
2242
+ }
2243
+ pendingOps = null;
2067
2244
  setBlur(frameEl, 0);
2068
2245
  setBlur(sidebarEl, 0);
2069
2246
  setBlur(centerEl, 0);
@@ -2589,6 +2766,17 @@ window.__ModuleLoader__.load({
2589
2766
  r: "2.1"
2590
2767
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M8 1.7v1.8M8 12.5v1.8M1.7 8h1.8M12.5 8h1.8M3.6 3.6l1.3 1.3M11.1 11.1l1.3 1.3M12.4 3.6l-1.3 1.3M4.9 11.1l-1.3 1.3" })]
2591
2768
  });
2769
+ const InputIcon = ({ size, className }) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Glyph, {
2770
+ size,
2771
+ className,
2772
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("rect", {
2773
+ x: "2.2",
2774
+ y: "4",
2775
+ width: "11.6",
2776
+ height: "8",
2777
+ rx: "2"
2778
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M8 6.6v2M7 8h2" })]
2779
+ });
2592
2780
  //#endregion
2593
2781
  //#region src/client/components/ErrorBoundary.tsx
2594
2782
  /**
@@ -3482,6 +3670,11 @@ window.__ModuleLoader__.load({
3482
3670
  labelKey: "uiOpacityCard",
3483
3671
  Icon: ChatIcon
3484
3672
  },
3673
+ {
3674
+ opKey: "input",
3675
+ labelKey: "uiOpacityInput",
3676
+ Icon: InputIcon
3677
+ },
3485
3678
  {
3486
3679
  isSettings: true,
3487
3680
  labelKey: "uiSop",
@@ -4556,11 +4749,13 @@ window.__ModuleLoader__.load({
4556
4749
  }, "dsh-any-background: skin dispose");
4557
4750
  const styleEl = document.createElement("style");
4558
4751
  styleEl.dataset.plugin = "dsh-any-background";
4559
- 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}`;
4752
+ 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}`;
4560
4753
  document.head.appendChild(styleEl);
4561
4754
  ctx.effect(() => () => {
4562
4755
  styleEl?.parentNode?.removeChild(styleEl);
4563
4756
  }, "dsh-any-background: gradient");
4757
+ const disposeDragQuality = watchWallpaperDragQuality();
4758
+ ctx.effect(() => () => disposeDragQuality(), "dsh-any-background: drag quality");
4564
4759
  let rev = 0;
4565
4760
  let colorRev = 0;
4566
4761
  let bgRev = 0;
@@ -4675,6 +4870,25 @@ window.__ModuleLoader__.load({
4675
4870
  const sectionInject = (actions) => {
4676
4871
  bound = actions;
4677
4872
  syncBg();
4873
+ const playVideoFromBlob = (blob, mime) => {
4874
+ const localUrl = URL.createObjectURL(blob);
4875
+ cfg.backgroundType = "video";
4876
+ setWpUrl(null);
4877
+ cfg.videoBgState = { ...DEFAULT_CONFIG.bgState };
4878
+ setWpVideoUrl(localUrl, mime ?? blob.type ?? "video/mp4");
4879
+ applyWp();
4880
+ syncBg();
4881
+ const applied = rWpVideo();
4882
+ captureVideoSnapshot(localUrl).then((snap) => {
4883
+ if (rWpVideo() !== applied) return;
4884
+ setWpVideoSnapshot(snap);
4885
+ applyThemeColor();
4886
+ syncBg();
4887
+ });
4888
+ uploadVideo(blob, mime ?? blob.type ?? "video/mp4").then((ok) => {
4889
+ if (ok) persistConfig();
4890
+ });
4891
+ };
4678
4892
  const [wh, ws, wl] = rColor();
4679
4893
  const [dh, ds, dv] = hslToHsv(wh, ws, wl);
4680
4894
  return {
@@ -4729,15 +4943,15 @@ window.__ModuleLoader__.load({
4729
4943
  saveConfig();
4730
4944
  return;
4731
4945
  }
4732
- let ok;
4733
- if (typeof u === "string") ok = await persistVideo(u);
4734
- else ok = await uploadVideo(u, mime ?? u.type ?? "video/mp4");
4735
- const live = ok ? VIDEO_SERVE_URL : typeof u === "string" ? u : null;
4736
- if (live === null) return;
4946
+ if (typeof u !== "string") {
4947
+ playVideoFromBlob(u, mime);
4948
+ return;
4949
+ }
4950
+ const live = await persistVideo(u) ? VIDEO_SERVE_URL : u;
4737
4951
  cfg.backgroundType = "video";
4738
4952
  setWpUrl(null);
4739
4953
  cfg.videoBgState = { ...DEFAULT_CONFIG.bgState };
4740
- setWpVideoUrl(live, mime ?? (typeof u === "string" ? null : u.type) ?? null);
4954
+ setWpVideoUrl(live, mime ?? null);
4741
4955
  applyWp();
4742
4956
  saveConfig();
4743
4957
  syncBg();
@@ -4854,24 +5068,25 @@ window.__ModuleLoader__.load({
4854
5068
  if (cfg.backgroundType === "video") {
4855
5069
  const video = typeof d.video === "string" && /^data:video\//.test(d.video) ? d.video : null;
4856
5070
  if (video !== null) {
4857
- let ok = false;
5071
+ let blob = null;
4858
5072
  try {
4859
- const blob = await fetch(video).then((r) => r.blob());
4860
- ok = await uploadVideo(blob, cfg.videoMime ?? blob.type ?? "video/mp4");
5073
+ blob = await fetch(video).then((r) => r.blob());
4861
5074
  } catch {
4862
- ok = false;
5075
+ blob = null;
5076
+ }
5077
+ if (blob !== null) playVideoFromBlob(blob, cfg.videoMime);
5078
+ else {
5079
+ const live = await persistVideo(video) ? VIDEO_SERVE_URL : video;
5080
+ setWpVideoUrl(live, cfg.videoMime);
5081
+ applyWp();
5082
+ const applied = rWpVideo();
5083
+ captureVideoSnapshot(live).then((snap) => {
5084
+ if (rWpVideo() !== applied) return;
5085
+ setWpVideoSnapshot(snap);
5086
+ applyThemeColor();
5087
+ syncBg();
5088
+ });
4863
5089
  }
4864
- if (!ok) ok = await persistVideo(video);
4865
- const live = ok ? VIDEO_SERVE_URL : video;
4866
- setWpVideoUrl(live, cfg.videoMime);
4867
- applyWp();
4868
- const applied = rWpVideo();
4869
- captureVideoSnapshot(live).then((snap) => {
4870
- if (rWpVideo() !== applied) return;
4871
- setWpVideoSnapshot(snap);
4872
- applyThemeColor();
4873
- syncBg();
4874
- });
4875
5090
  } else {
4876
5091
  setWpVideoUrl(null, null);
4877
5092
  persistVideo(null);