dsh-mpkg-wallpaper 3.15.1 → 3.15.2

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.
Files changed (2) hide show
  1. package/lib/client.js +183 -71
  2. package/package.json +1 -1
package/lib/client.js CHANGED
@@ -3569,6 +3569,11 @@ function mpwThumbFail(el, why) {
3569
3569
  * state,统一经 window 事件 mpw:busy 通知组件渲染进度条。
3570
3570
  * payload: null=隐藏 | { label, percent(null=不确定) } */
3571
3571
  function mpwBusyEmit(payload) {
3572
+ /* ①(2026-10-02 用户拍板) 「转码中」条只在**用户主动配置转码**(分辨率上限/帧率上限/预缩档)
3573
+ 或真正在转码时显示。自动降档(≥4K 临时降 1080p)属于后台行为 ⇒ 不打扰;
3574
+ 纯外观设置(虚化程度/透明度)本来就不该触发媒体重挂,见 applyFromStorage 的 CSS-only 早退。 */
3575
+ try { if (payload && payload.auto === true) return } catch (e) {} // 占位静音(兼容旧调用)
3576
+ try { if (mpwAutoCapSilent && payload && typeof payload.label === "string" && payload.label.indexOf("tc:") === 0) return } catch (e) {} // 自动降档期间的真实转码进度也不弹
3572
3577
  try { window.dispatchEvent(new CustomEvent("mpw:busy", { detail: payload })); } catch {}
3573
3578
  }
3574
3579
  /** ⓪(2026-09-17 用户第 1 条「我没用转码,为什么有个 ffmpeg 常驻转我正在放的壁纸」)
@@ -3629,11 +3634,11 @@ function mpwThumbFail(el, why) {
3629
3634
  if (!d || d.phase === "idle") return;
3630
3635
  // ①(2026-09-28 H1) `cancelled` 是**终态**(切档/宿主取消)——旧实现只认
3631
3636
  // done/error ⇒ 已取消的任务会让进度条空转到 20min 上限。
3632
- if (d.phase === "done" || d.phase === "error" || d.phase === "cancelled") { clearInterval(iv); window.__mpwTcPollKey = null; mpwBusyEmit(null); return; }
3637
+ if (d.phase === "done" || d.phase === "error" || d.phase === "cancelled") { clearInterval(iv); window.__mpwTcPollKey = null; try { mpwAutoCapSilence(false) } catch (e) {} mpwBusyEmit(null); return; }
3633
3638
  mpwBusyEmit({ label: d.phase === "downloading" ? "ffdl:" + (d.percent || 0) : "tc:" + (d.percent || 0), percent: d.percent || 0 });
3634
3639
  }).catch(() => {});
3635
3640
  } catch { clearInterval(iv); window.__mpwTcPollKey = null; mpwBusyEmit(null); }
3636
- if (++tries > 120) { clearInterval(iv); window.__mpwTcPollKey = null; mpwBusyEmit(null); } // 20min 上限
3641
+ if (++tries > 120) { clearInterval(iv); window.__mpwTcPollKey = null; try { mpwAutoCapSilence(false) } catch (e) {} mpwBusyEmit(null); } // 20min 上限
3637
3642
  }, 1000);
3638
3643
  return iv;
3639
3644
  }
@@ -7993,7 +7998,8 @@ function mpwThumbFail(el, why) {
7993
7998
  // **不走转码**(host resolveTranscodeByImage 不识别 sv=1,会 404 黑屏)。
7994
7999
  const isSceneVideo = typeof image === "string" && image.indexOf("sv=1") >= 0;
7995
8000
  const fpsCapN = section.fpsCap !== void 0 && section.fpsCap !== null ? Number(section.fpsCap) : 0;
7996
- const resMaxN = section.resMax !== void 0 && section.resMax !== null ? Number(section.resMax) : 0;
8001
+ /* ①(2026-10-02) 上限的唯一读取口:用户设过用用户的,否则可能命中 ≥4K 自动降档的**临时**覆盖 */
8002
+ const resMaxN = mpwResMaxEffective(section);
7997
8003
  /* ①(2026-09-23 用户第 1 项 B) **预缩档(默认关)**:按屏幕物理尺寸 + lanczos
7998
8004
  预缩到正好够屏,复用**既有** /transcode 通道(不新起一套)。
7999
8005
  · 关(默认):preScaleN=0 ⇒ maxW/scale 一个都不加,URL 与行为**逐字不变**;
@@ -9263,7 +9269,15 @@ function mpwThumbFail(el, why) {
9263
9269
  function startFrostObserver() {
9264
9270
  try {
9265
9271
  if (frostObserver) return;
9266
- frostObserver = new MutationObserver(() => { applyFrostInline(); });
9272
+ /* ①(2026-10-02 用户第 7 条) 同上:帧内合并 + 动画/拖拽期间跳过(收起/展开动画时
9273
+ 宿主每帧都改 class/style,旧写法每帧同步跑一遍 applyFrostInline)。 */
9274
+ let fraf = 0;
9275
+ const frun = () => { fraf = 0; try { applyFrostInline(); } catch (e) {} };
9276
+ frostObserver = new MutationObserver(() => {
9277
+ if (fraf) return;
9278
+ try { fraf = (typeof requestAnimationFrame === "function") ? requestAnimationFrame(frun) : setTimeout(frun, 16); }
9279
+ catch (e) { frun(); }
9280
+ });
9267
9281
  frostObserver.observe(document.documentElement, { childList: true, subtree: true, attributes: true, attributeFilter: ['class', 'style'] });
9268
9282
  } catch (e) { mpwErr("startFrostObserver", e); }
9269
9283
  }
@@ -9334,7 +9348,26 @@ function mpwThumbFail(el, why) {
9334
9348
  function startCompatObserver() {
9335
9349
  try {
9336
9350
  if (compatObserver) return;
9337
- compatObserver = new MutationObserver(() => { applyCompatBridges(applyCompatBridges._sec); });
9351
+ /* ①(2026-10-02 用户第 7 条「左栏收起/展开动画很卡」) **帧内合并**:
9352
+ 宿主在动画期间每帧都会改 DOM(class/style),旧写法每个 mutation 都同步跑一遍
9353
+ `applyCompatBridges`(十几次 querySelectorAll + 写 style)⇒ 主线程被我们占满、
9354
+ 动画掉帧。改成 rAF 合并:一帧最多跑一次;并且动画/拖拽期间直接跳过
9355
+ (`data-dsh-sidebar-dragging` 是宿主自己打的标记;动画结束后观察器还会再来一次,
9356
+ 所以不会漏掉收尾状态)。 */
9357
+ let raf = 0;
9358
+ const run = () => {
9359
+ raf = 0;
9360
+ try {
9361
+ if (typeof document !== "undefined" && document.body && document.body.hasAttribute
9362
+ && (document.body.hasAttribute("data-dsh-sidebar-dragging") || document.body.hasAttribute("data-dragging"))) return;
9363
+ applyCompatBridges(applyCompatBridges._sec);
9364
+ } catch (e) {}
9365
+ };
9366
+ compatObserver = new MutationObserver(() => {
9367
+ if (raf) return;
9368
+ try { raf = (typeof requestAnimationFrame === "function") ? requestAnimationFrame(run) : setTimeout(run, 16); }
9369
+ catch (e) { run(); }
9370
+ });
9338
9371
  compatObserver.observe(document.documentElement, { childList: true, subtree: true });
9339
9372
  } catch (e) { mpwErr("startCompatObserver", e); }
9340
9373
  }
@@ -11462,14 +11495,21 @@ function mpwThumbFail(el, why) {
11462
11495
  // - 统一虚化开 → 跟随「不透明度」条(panel/100,与侧边栏/标题栏一致);
11463
11496
  // 聊天区壁纸模糊由壁纸层 blur 提供(chatFollow 决定归谁管,见 applyFromStorage)
11464
11497
  // - 统一虚化关 → 0.62(原默认,跟随 dialogBlur)
11498
+ /* ①(2026-10-02 用户拍板) 「界面透明度 = 0%」= 完全不透明 ⇒ 主画布/标题栏/右栏的底色
11499
+ 用**宿主主题自己的不透明底色**(这里就是"不覆盖成半透明")。此前即使是 0%,
11500
+ 悬浮档也会把 --dsw-alias-bg-base 压到 0.35 ⇒ 标题栏/右栏出现用户报的"淡白色底"。 */
11501
+ const chromeOpaqueNow = unifyTint
11502
+ && Math.max(0, Math.min(100, (section.sidebarAlpha !== void 0 ? section.sidebarAlpha : DEFAULT_SIDEBAR_ALPHA))) <= 0;
11465
11503
  const baseAlphaRaw = enabled && hasImage && dialogBlur
11466
- ? (unifyTint
11467
- ? Math.max(0, Math.min(1, (Math.max(50, Math.min(100, opacity)) / 100)))
11468
- : 0.62)
11504
+ ? (chromeOpaqueNow
11505
+ ? 1
11506
+ : (unifyTint
11507
+ ? Math.max(0, Math.min(1, (Math.max(50, Math.min(100, opacity)) / 100)))
11508
+ : 0.62))
11469
11509
  : 1;
11470
11510
  // A(新) 悬浮效果开启时:聊天区自动更通透(整体透出壁纸)
11471
11511
  const floatOn2 = section.float !== void 0 ? !!section.float : DEFAULT_FLOAT;
11472
- const baseAlpha = floatOn2 && enabled && hasImage ? Math.min(baseAlphaRaw, 0.35) : baseAlphaRaw;
11512
+ const baseAlpha = (floatOn2 && enabled && hasImage && !chromeOpaqueNow) ? Math.min(baseAlphaRaw, 0.35) : baseAlphaRaw;
11473
11513
  const overrides = {
11474
11514
  // 主画布背景半透明 → 壁纸透到所有区域(含输入框背后),
11475
11515
  // 这样输入框/弹层的 backdrop-filter 才能模糊到壁纸(Aqua 生效的关键)。
@@ -16146,7 +16186,33 @@ module.exports = {
16146
16186
  }
16147
16187
 
16148
16188
  const MPW_4K_MIN_PX = 3840 * 2160; // ≥4K 的判据(像素面积,与旧提示同口径)
16149
- const MPW_4K_AUTOCAP_RES = 1920; // 自动降档目标 = RES_LEVELS 里的 1080p 档(面板可改回)
16189
+ const MPW_4K_AUTOCAP_RES = 1920; // 自动降档目标 = RES_LEVELS 里的 1080p 档
16190
+ /* ①(2026-10-02 用户拍板:**只对当前壁纸临时生效、不写设置、不弹条**)
16191
+ 上一版把 `resMax` 写进设置 ⇒ 之后每次改外观设置都会重新走一遍转码判断、
16192
+ 弹出「转码中」进度条(用户第 1 条)。现在改成模块级**临时覆盖**:
16193
+ · key = 当前壁纸身份(mpkgKey|image|webUrl|sceneKey),换壁纸即失效;
16194
+ · `mpwResMaxEffective(sec)` 是唯一读取口(转码规格与 URL 都从这里取上限);
16195
+ · 不落盘、不打「用户设过」标记、不弹进度条(busy 事件带 auto:true 被拦掉)。 */
16196
+ let mpwAutoCapTransient = null; // { key, maxW } | null
16197
+ /* ①(2026-10-02 用户拍板"不弹条") 自动降档会触发一次真实转码,但那是**后台行为** ⇒
16198
+ 它引起的进度事件一律不派发。静音窗口由 pollTranscodeProgress 的终态清除,
16199
+ 另加 120s 上限兜底(即使轮询没起来也不会永久静音)。 */
16200
+ let mpwAutoCapSilent = false;
16201
+ function mpwAutoCapSilence(on) {
16202
+ mpwAutoCapSilent = !!on;
16203
+ if (on) { try { setTimeout(() => { mpwAutoCapSilent = false }, 120000) } catch (e) {} }
16204
+ }
16205
+ function mpwAutoCapKeyOf(sec) { try { return mpw4kAutoCapKey(sec || (typeof readSection === "function" ? readSection() : {}) || {}) } catch (e) { return "" } }
16206
+ function mpwResMaxEffective(sec) {
16207
+ try {
16208
+ const s2 = sec || {};
16209
+ const own = s2.resMax !== void 0 && s2.resMax !== null ? Number(s2.resMax) : 0;
16210
+ if (own > 0) return own; // 用户设过 ⇒ 用户优先
16211
+ const t = mpwAutoCapTransient;
16212
+ if (t && t.maxW > 0 && t.key === mpwAutoCapKeyOf(s2)) return t.maxW;
16213
+ } catch (e) {}
16214
+ return 0;
16215
+ }
16150
16216
  /** 回退口 `?autocap=off` ⇒ 完全不自动降档(行为同今天)。 */
16151
16217
  function mpwAutoCapOff() {
16152
16218
  try { return new URLSearchParams(location.search).get("autocap") === "off" } catch (e) { return false }
@@ -16163,8 +16229,8 @@ module.exports = {
16163
16229
  }
16164
16230
  /** 纯函数:自动降档的**唯一判据**(判据出处 tools/theme-assist-test.mjs D 组)。
16165
16231
  * why 的取值就是台账里的可观测原因:below-4k / autocap-off / not-transcodable /
16166
- * dup / resmax-set / no-transcode / await-transcode-probe / applied(落盘口拿不到时由
16167
- * 调用方改写为 no-commit-path;boot 补写成功时 __mpw4kAutoCap.why = applied-after-boot)。
16232
+ * dup / resmax-set / no-transcode / await-transcode-probe / applied。
16233
+ * ①(2026-10-02) `no-commit-path` / `applied-after-boot` 两个 why 已随"不写设置"一起删除。
16168
16234
  * 顺位即语义:先"该不该管这个源",再看回退口与去重,最后才是"用户已设上限"与"宿主能不能转"。 */
16169
16235
  function mpw4kAutoCapDecision(vw, vh, resMax, ffReady, autocapOff, dup, transcodable, ffInFlight) {
16170
16236
  const W = Number(vw) || 0, H = Number(vh) || 0;
@@ -16197,26 +16263,11 @@ module.exports = {
16197
16263
  const s = sec || {};
16198
16264
  return String(s.mpkgKey || s.image || s.webUrl || s.sceneKey || "?").slice(0, 200);
16199
16265
  }
16200
- /** 落盘口 = 面板同一个 commit(组件渲染时经 __mpwSceneUiBridge 注入)。
16201
- * instant=true(立刻持久化,与面板改档一致);第三个参数 deferApply=true 把重建挪到下一帧 ——
16202
- * 在 <video> 的 loadedmetadata 回调里同步重建壁纸层会重入媒体事件(真机卡顿/闪回的已知成因)。
16203
- * ⚠ 面板组件**只在设置页打开时挂载**(卸载时清 mpwSectionNotify,见本文件组件 effect),
16204
- * 而自动降档的触发点是页面加载期的 loadedmetadata ⇒ 桥拿不到时必须走**同一落盘路径的
16205
- * 模块级等价物**(commit 内部就是 readSection → mpwMarkUserSet → mpwSwitchPatchFull →
16206
- * writeSection),否则这一项在真机上永远不生效(设置面板没开 = 静默失效)。 */
16207
- function mpwAutoCapCommit(resMax) {
16208
- try {
16209
- const b = typeof mpwSceneUi === "function" ? mpwSceneUi() : null;
16210
- if (b && typeof b.commit === "function") { b.commit({ resMax: resMax }, true, true); return true }
16211
- if (typeof writeSection !== "function" || typeof readSection !== "function") return false;
16212
- const cur = readSection() || {};
16213
- const patch = (typeof mpwMarkUserSet === "function") ? mpwMarkUserSet({ resMax: resMax }, cur) : { resMax: resMax };
16214
- const full = (typeof mpwSwitchPatchFull === "function") ? mpwSwitchPatchFull(patch) : patch;
16215
- writeSection(Object.assign({}, cur, full), true);
16216
- try { if (typeof mpwSectionNotify === "function" && mpwSectionNotify) mpwSectionNotify(); } catch (e) {}
16217
- return true;
16218
- } catch (e) { return false }
16219
- }
16266
+ /* ①(2026-10-02 用户拍板:临时生效、不写设置、不弹条) 这里**不再有落盘口**:
16267
+ 自动降档只写模块级临时覆盖(`mpwAutoCapTransient`),由 `mpwResMaxEffective()` 唯一读取。
16268
+ 历史(已删除的落盘辅助)曾走"面板 commit 桥 → 模块级 writeSection 兜底",因为面板组件
16269
+ 只在设置页打开时挂载;现在不需要了 —— 不再写盘就没有"桥拿不到"这个问题。 */
16270
+
16220
16271
  /** ≥4K ⇒ 自动降档一次,返回台账片 { applied, resMax, at, why }(同时写进 __mpwPerfNote.autoCap)。
16221
16272
  * `expectKey`:只在"等读数落地后再判"那条路径上传入 —— 期间用户换了壁纸 ⇒ 这条观察作废
16222
16273
  * (why=source-changed:不写档、不留 seen,别拿旧源的分辨率去改新源的上限)。 */
@@ -16240,27 +16291,24 @@ module.exports = {
16240
16291
  led.at = out.at; led.why = d.why;
16241
16292
  }
16242
16293
  if (d.act) {
16243
- if (mpwAutoCapCommit(d.resMax)) {
16244
- out.applied = true; out.resMax = d.resMax;
16245
- if (led) { led.applied = true; led.resMax = d.resMax; }
16246
- /* boot 窗口内写下的可能被随后的两处存储合并盖回(见 mpwOnBootSettled 注释)⇒
16247
- 收尾后核对一次:不是我们写下的那一档就补写(幂等;只补 resMax,不动别的字段)。 */
16248
- if (typeof mpwBootPending !== "undefined" && mpwBootPending === true) {
16249
- mpwOnBootSettled(() => {
16250
- try {
16251
- const cur = (typeof readSection === "function" ? readSection() : null) || {};
16252
- if (Number(cur.resMax) === MPW_4K_AUTOCAP_RES) return; // 值还在 ⇒ 不动作
16253
- if (mpwAutoCapCommit(MPW_4K_AUTOCAP_RES)) {
16254
- out.reasserted = true; out.reassertAt = Date.now();
16255
- if (led) { led.why = "applied-after-boot"; led.at = out.reassertAt; }
16256
- }
16257
- } catch (e) {}
16258
- });
16294
+ /* ①(2026-10-02 用户拍板) **不写设置**:只设临时覆盖,然后软重挂当前壁纸
16295
+ (让这一次播放就走 1080p 档)。重挂期间把 busy 静音(auto:true)——
16296
+ ≥4K 自动降档是后台行为,不该弹「转码中」条。 */
16297
+ mpwAutoCapTransient = { key: key, maxW: d.resMax };
16298
+ out.applied = true; out.resMax = d.resMax;
16299
+ if (led) { led.applied = true; led.resMax = d.resMax; led.mode = "transient"; }
16300
+ try {
16301
+ const sec2 = (typeof readSection === "function" ? readSection() : null) || {};
16302
+ const img = typeof sec2.image === "string" ? sec2.image : "";
16303
+ const spec2 = img ? mpwTranscodeSpec(0, d.resMax, 0, img, String(sec2.image || "").indexOf("sv=1") >= 0, HOST_URL) : null;
16304
+ if (spec2 && spec2.useTranscode && spec2.playUrl) {
16305
+ mpwBusyEmit({ auto: true }); // 静音占位(payload.auto ⇒ 不派发)
16306
+ if (IS_EDGE) { try { showVideoEdge(spec2.playUrl, bgElements().video) } catch (e) { showVideoEl(spec2.playUrl) } }
16307
+ else showVideoEl(spec2.playUrl);
16308
+ mpwAutoCapSilence(true);
16309
+ try { pollTranscodeProgress(img, spec2.fps || 30, spec2.maxW || 0, spec2.scale || "") } catch (e) { mpwAutoCapSilence(false) }
16259
16310
  }
16260
- } else {
16261
- out.why = "no-commit-path"; // 桥与 writeSection 两条落盘口都拿不到 ⇒ 不做
16262
- if (led) led.why = out.why;
16263
- }
16311
+ } catch (e) {}
16264
16312
  } else if (d.why === "no-transcode") {
16265
16313
  mpwFfmpegProbeKick("4k"); // 为换壁纸/下次刷新留下读数;本次不动作
16266
16314
  } else if (d.why === "await-transcode-probe") {
@@ -16533,7 +16581,9 @@ ${decls}
16533
16581
  : `color-mix(in srgb, var(--dsw-alias-bg-base) ${U(55)}%, transparent)`);
16534
16582
  tok("--mpw-surface-plugin-row-unify-light", `color-mix(in srgb, var(--dsw-static-neutral-bluish-00) ${U(55)}%, transparent)`);
16535
16583
  /* 右栏全屏([data-rightbar-fullscreen])面板表面:带 fallback 的旧写法逐字保留在值里 */
16536
- tok("--mpw-surface-rs-full", "rgba(var(--mpw-chrome-bg, 255,255,255), calc(var(--mpw-rs-alpha, 0.45) * 0.55))");
16584
+ /* ①(2026-10-02) 同一类非法值:`--mpw-chrome-bg` 现在是颜色 ⇒ 改 color-mix(真机读数:
16585
+ 统一虚化接管时右栏/dock"有模糊没底"就是这条被丢弃造成的)。 */
16586
+ tok("--mpw-surface-rs-full", mpwSurfaceColor(surfacePlan.right.base, 0.55).replace(/[\d.]+%/, "calc(var(--mpw-rs-alpha, 0.45) * 55%)"));
16537
16587
  /* 「把侧栏底色交还宿主」用的 token:无壁纸档不覆盖宿主 token,只是原样读回来。
16538
16588
  SSOT 在 body 上,而宿主正是在 body 定义 --dsw-specific-sidebar-fill ⇒ var() 在这里能解析
16539
16589
  (写在 :root 会 guaranteed-invalid,见 emitSurfaceTokens 注释)。 */
@@ -17344,7 +17394,7 @@ html body:has([class*="_modal"]) [data-mpw-rs] {
17344
17394
  // 时"看起来还是没模糊"(这就是用户看到的"虚化消失=白纱");
17345
17395
  // 4) 抑制条件集中在选择器里(弹窗/展开面板/拖拽/用户关开关),不再靠 JS 摘除内联;
17346
17396
  // 5) 变量由 JS 写入 :root,改设置只改变量、不动 DOM。
17347
- if (bdSupported && !chromeInert) {
17397
+ if (bdSupported) {
17348
17398
  // sidebarBlur 是布尔(是否启用侧栏磨砂),数值在 sidebarBlurAmount;未启用时退回统一虚化程度
17349
17399
  // ①(2026-10-02 第3项) 跟随开关开 ⇒ 统一虚化接管(半径一律 unifyAmount,
17350
17400
  // 不再被 sidebarBlur 的数值分走);关 ⇒ 保持历史公式(侧栏磨砂开就用它自己的数值)。
@@ -17366,11 +17416,13 @@ html body:has([class*="_modal"]) [data-mpw-rs] {
17366
17416
  tok("--mpw-rs-blur", rsAmt2 + "px");
17367
17417
  tok("--mpw-rs-alpha", rsAlpha2);
17368
17418
  /* 侧栏(左)磨砂表面:底座色 + 透明度 → 一枚 token(原来散在规则里拼 rgba) */
17369
- tok("--mpw-surface-side-frost", mpwSurfaceColor(surfacePlan.left.base, surfacePlan.left.opacityPct / 100));
17419
+ /* ①(2026-10-02) 透明度 0(交还宿主)时这几枚"表面"token 也**不定义**:
17420
+ 没有规则引用它们,定义着只会让判据与诊断里出现"看似还在接管"的死 token。 */
17421
+ if (!chromeInert) tok("--mpw-surface-side-frost", mpwSurfaceColor(surfacePlan.left.base, surfacePlan.left.opacityPct / 100));
17370
17422
  /* 右侧栏 / dockkit 面板表面(亮色底 + 22% 档) */
17371
- tok("--mpw-surface-rs-dock", mpwSurfaceColor(surfacePlan.right.base, surfacePlan.right.opacityPct / 100));
17423
+ if (!rightInert) tok("--mpw-surface-rs-dock", mpwSurfaceColor(surfacePlan.right.base, surfacePlan.right.opacityPct / 100));
17372
17424
  /* 右侧栏 / dockkit 面板表面(暗色底 + 30% 档) */
17373
- tok("--mpw-surface-rs-dock-dark", mpwSurfaceColor(surfacePlan.right.base, surfacePlan.right.opacityPct / 100));
17425
+ if (!rightInert) tok("--mpw-surface-rs-dock-dark", mpwSurfaceColor(surfacePlan.right.base, surfacePlan.right.opacityPct / 100));
17374
17426
  /* ①(2026-10-02 第2项) 统一虚化接管时的**共同表面**:与左栏同色同厚度同半径。
17375
17427
  三枚 token 只在接管时被下面那块 CSS 引用;不接管时它们不生效(无引用)。
17376
17428
  厚度直接取雾模型(= 左侧边栏/标题栏透明度),0 ⇒ rgba(...,0) = 完全透明,
@@ -17415,21 +17467,21 @@ html body[data-ds-dark-theme][data-mpw-unify] [data-dsh-better-sidebar] [data-ds
17415
17467
  * 作为层叠上下文:浮层被"关"在 header 内部,无法压过 header 内的文字。
17416
17468
  修法:**header 本体彻底不加 backdrop-filter**(磨砂改由 .wSkVaW_header::before 承担,
17417
17469
  见上方标题栏规则),这里只保留左侧栏的规则。 */
17418
- html body:not([data-mpw-sblur-off]) [class*="sidebarCol"]:not([data-sidebar-right-panel]):not([data-dockkit-strip]):not([data-mpw-holds-layer]) {
17470
+ ${chromeInert ? "" : `html body:not([data-mpw-sblur-off]) [class*="sidebarCol"]:not([data-sidebar-right-panel]):not([data-dockkit-strip]):not([data-mpw-holds-layer]) {
17419
17471
  /* ①(2026-10-02 真机读数) 半径 0 ⇒ 写 **none**(不是 blur(0px):视觉等价但仍会建 containing block) */
17420
17472
  -webkit-backdrop-filter: ${chromeBlurCss} !important;
17421
17473
  backdrop-filter: ${chromeBlurCss} !important;
17422
17474
  background-color: var(--mpw-surface-side-frost) !important;
17423
- }
17475
+ }`}
17424
17476
  /* 右侧栏 / dockkit(V 形:面板 + 标签条 + 表面) */
17425
- html body[data-mpw-rsblur="on"] [data-sidebar-right-panel],
17477
+ ${rightInert ? "" : `html body[data-mpw-rsblur="on"] [data-sidebar-right-panel],
17426
17478
  html body[data-mpw-rsblur="on"] [data-dockkit-pane],
17427
17479
  html body[data-mpw-rsblur="on"] [data-dockkit-strip],
17428
17480
  html body[data-mpw-rsblur="on"] [data-dockkit-surface] {
17429
17481
  -webkit-backdrop-filter: blur(var(--mpw-rs-blur)) saturate(140%) !important;
17430
17482
  backdrop-filter: blur(var(--mpw-rs-blur)) saturate(140%) !important;
17431
17483
  background-color: var(--mpw-surface-rs-dock) !important;
17432
- }
17484
+ }`}
17433
17485
  /* 抑制:弹窗/设置面板打开时(backdrop-filter 会成为 fixed 弹窗的 containing block → 必须摘除) */
17434
17486
  html body[data-mpw-sblur-off] [class*="sidebarCol"],
17435
17487
  html body[data-mpw-sblur-off] header[class*="_header_"],
@@ -17545,9 +17597,12 @@ html body:has([class*="_overlay"]) [class*="_floatHeader_"] {
17545
17597
  html body [class*="_overlayLayer"], html body [class*="_overlay"], html body [class*="_modal"], html body [role="dialog"] {
17546
17598
  z-index: 2000 !important;
17547
17599
  }
17548
- /* 设置/对话弹层本身不做玻璃(避免 backdrop-filter 变成下拉的 containing block) */
17549
- [class*="_overlay"], [class*="_modal"], [role="dialog"], [class*="_overlayLayer"],
17550
- [class*="_overlay"] *, [class*="_overlayLayer"] * {
17600
+ /* 设置/对话弹层本身不做玻璃(避免 backdrop-filter 变成下拉的 containing block)。
17601
+ ①(2026-10-02 真机读数) **收窄到"真弹层"**:旧写法把 [class*="_overlay"] 及其**所有后代**都清掉模糊,
17602
+ 而宿主的聊天输入区锚点类名是 uV2eYG_overlayAnchor(常驻!)⇒ 挂在输入框上的那些弹层
17603
+ (「+」菜单 / 权限选择 / 模型与推理等级 / 上下文占用圆圈)的模糊**被永久清掉**,
17604
+ 表现为"完全透明、文字与聊天重叠"(用户第 3 条第②④⑤⑥项)。真弹层用 _overlayLayer / _modal / role=dialog 认。 */
17605
+ [role="dialog"][aria-modal="true"], [class*="_overlayLayer"], [class*="_modal"] {
17551
17606
  -webkit-backdrop-filter: none !important;
17552
17607
  backdrop-filter: none !important;
17553
17608
  }
@@ -17790,10 +17845,15 @@ html body[data-mpw-sblur-off] [data-mpw-rs] {
17790
17845
  })();
17791
17846
  // 深色主题用深底(浅底 + 浅色文字 = 看不见字),浅色主题用白底;
17792
17847
  // 若用户开了主题色(--mpw-theme-color)则跟随主题色,保持与整屏一致。
17793
- const popSurfaceRgb = `var(--mpw-chrome-bg, 255,255,255)`;
17848
+ /* ①(2026-10-02 真机读数定案的**真 bug**) 这里原来拼 `rgba(var(--mpw-chrome-bg, 255,255,255), α)`,
17849
+ 而 `--mpw-chrome-bg` 在表面模型重做后已经是**颜色**(`var(--dsw-specific-sidebar-fill)`)而不是
17850
+ 三元组 ⇒ 解析成 `rgba(var(--dsw-specific-sidebar-fill), 0.94)` 属**非法值**,整条声明在
17851
+ computed-value 阶段被丢弃 ⇒ 所有弹层"只有模糊、没有底"(用户第 3 条「完全透明、文字重叠」)。
17852
+ 改成一等公民 `color-mix()`:形态合法、运行时可调(探针在 Firefox 里逐条验过)。 */
17853
+ const popSurfaceRgb = ``;
17794
17854
  /* ①(2026-09-18 §5 第1项) 弹层(面板家族)表面 → 两枚表面 token(不透明度走 --mpw-pop-alpha,
17795
17855
  调用方可临时覆盖;默认值仍是「弹层不透明度」滑条)。 */
17796
- tok("--mpw-surface-pop", `rgba(${popSurfaceRgb}, var(--mpw-pop-alpha, ${(popSurfaceAlpha / 100).toFixed(2)}))`);
17856
+ tok("--mpw-surface-pop", mpwSurfaceColor(surfacePlan.left.base, popSurfaceAlpha / 100).replace(/[\d.]+%/, `calc(var(--mpw-pop-alpha, ${(popSurfaceAlpha / 100).toFixed(2)}) * 100%)`));
17797
17857
  tok("--mpw-surface-pop-dark", `rgba(18, 22, 30, var(--mpw-pop-alpha, ${(popSurfaceAlpha / 100).toFixed(2)}))`);
17798
17858
  // 弹层**表面**选择器(背景 + 模糊共用一份,避免两处写法漂移)。
17799
17859
  // ③(关键收窄 2026-09-13) 只用 [class*="_menu"] 会连**非表面**一起命中:
@@ -17802,9 +17862,20 @@ html body[data-mpw-sblur-off] [data-mpw-rs] {
17802
17862
  // `._G5b-a_menuStatus`(说明文字)都含 "_menu" ——
17803
17863
  // 给它们加 backdrop-filter 会在界面上糊出一块方块,加底色会糊出白块。
17804
17864
  // 这里显式排除 anchor/icon/status/open/item/row/trigger/card 等非表面后缀。
17805
- const POP_NOT = `:not([role="menuitem"]):not([role="option"]):not([class*="Anchor"]):not([class*="anchor"]):not([class*="Icon"]):not([class*="icon"]):not([class*="Status"]):not([class*="status"]):not([class*="Open"]):not([class*="open"]):not([class*="_item"]):not([class*="Item"]):not([class*="_row"]):not([class*="Row"]):not([class*="_trigger"]):not([class*="Trigger"]):not([class*="_card"]):not([class*="Card"]):not([class*="_groupTitle"])`;
17806
- const popSurfaceSel = `[role="menu"]${POP_NOT}, [role="listbox"]${POP_NOT}, [role="combobox"]${POP_NOT}, [data-dsh-surface]${POP_NOT}, [class*="_menu"]${POP_NOT}, [class*="_popover"]${POP_NOT}, [class*="_dropdown"]${POP_NOT}, [class*="_denseList"]${POP_NOT}`;
17865
+ const POP_NOT = `:not([role="menuitem"]):not([role="option"]):not([class*="Anchor"]):not([class*="anchor"]):not([class*="Icon"]):not([class*="icon"]):not([class*="Status"]):not([class*="status"]):not([class*="Open"]):not([class*="open"]):not([class*="_item"]):not([class*="Item"]):not([class*="_row"]):not([class*="Row"]):not([class*="_trigger"]):not([class*="Trigger"]):not([class*="_card"]):not([class*="Card"]):not([class*="_groupTitle"]):not([class*="_viewport"])`;
17866
+ /* ①(2026-10-02 用户第 3 条⑥:上下文占用圆圈点开的框是**纯白**、没有玻璃感)
17867
+ 它的真实结构是 div.JObwrW_panel[role="dialog"][aria-label="上下文已用"] ⇒ 既不在
17868
+ 下面这串弹层选择器里(所以没有我们的底),又被"真弹层不做玻璃"那条按 role=dialog
17869
+ 清掉了模糊 ⇒ 纯白。把它(以及同类小面板)纳入弹层表面集合。 */
17870
+ const popSurfaceSel = `[role="menu"]${POP_NOT}, [role="listbox"]${POP_NOT}, [role="combobox"]${POP_NOT}, [data-dsh-surface]${POP_NOT}, [class*="_menu"]${POP_NOT}, [class*="_popover"]${POP_NOT}, [class*="_dropdown"]${POP_NOT}, [class*="_denseList"]${POP_NOT}, [class*="JObwrW_panel"]${POP_NOT}`;
17807
17871
  const popTipSel = `[role="tooltip"], [role="alert"]`;
17872
+ /* ①(2026-10-02 用户第 3 条:「把这个好用的模糊效果独立成 token,因为还有几个地方也要实现」)
17873
+ 弹层表面 = **底色 + 模糊半径 + 圆角** 三件套,全部由这几枚 token 定义;任何弹层只要被
17874
+ 下面的选择器命中,就自动同一套效果(不再各处写死 rgba/blur)。
17875
+ 取值口径:底色 = 宿主底色 × 「弹层不透明度」(默认 94%);半径 = 「虚化弹层」的
17876
+ popoverAmount(关掉弹层虚化时为 0 ⇒ 只留底色);圆角沿用宿主自己的(不覆盖 host radius)。 */
17877
+ tok("--mpw-pop-blur", ((popoverBlur && Number(popoverAmount) > 0) ? `blur(${Number(popoverAmount)}px)` : "none"));
17878
+ tok("--mpw-pop-radius", "var(--dsw-alias-radius-l2, 12px)");
17808
17879
  const popSurfaceCss = (popSurfaceAlpha < 100 || popoverBlur || menuTranslucent)
17809
17880
  ? `/* 弹层表面底色兜底(浅色/深色两套;不透明度由「弹层不透明度」控制) */
17810
17881
  html body :is(${popSurfaceSel}) {
@@ -17825,13 +17896,37 @@ html body[data-ds-dark-theme] :is(${popSurfaceSel}) {
17825
17896
  表面选择器与上面的底色规则共用(POP_NOT 已排除 anchor/icon/status 等非表面类)。 */
17826
17897
  ${popTipSel},
17827
17898
  ${popSurfaceSel} {
17828
- backdrop-filter: ${popFilter} !important;
17829
- -webkit-backdrop-filter: ${popFilter} !important;
17899
+ /* ①(2026-10-02) 半径/模糊统一读自有 token(--mpw-pop-blur 是唯一来源;关掉弹层虚化时它就是 none) */
17900
+ backdrop-filter: var(--mpw-pop-blur, ${popFilter}) !important;
17901
+ -webkit-backdrop-filter: var(--mpw-pop-blur, ${popFilter}) !important;
17902
+ /* ①(2026-10-02 用户第 3 条:有的弹层"完全透明、文字重叠") **模糊与底色同一条规则**:
17903
+ 以前模糊用 popTipSel, popSurfaceSel 两个选择器集、底色只用 popSurfaceSel ⇒
17904
+ 命中前者而不命中后者的元素就变成"有模糊、没有底"(真机读数:「+」菜单/权限/模型
17905
+ 三处最外层容器 bg 全透明但 bf=blur(11px))。拍平成一个集:**给谁加模糊,就给谁加底**。 */
17906
+ background-color: var(--mpw-surface-pop) !important;
17907
+ }
17908
+ html body[data-ds-dark-theme] ${popTipSel},
17909
+ html body[data-ds-dark-theme] ${popSurfaceSel} {
17910
+ background-color: var(--mpw-surface-pop-dark) !important;
17830
17911
  }
17831
17912
  [role="menu"] [class*="card"],
17832
17913
  [role="tooltip"] [class*="card"] {
17833
17914
  backdrop-filter: none !important;
17834
17915
  }
17916
+ /* ①(2026-10-02 用户第 3 条⑤) 弹层里的**分组标题条**:宿主给 div._7KE1Ra_groupTitle 之类一块
17917
+ 不透明白底,于是"提供商标题是白色长条,而下面具体模型行是好的玻璃底"。这里把标题让成透明,
17918
+ 透出容器的玻璃表面(与模型行一致)。POP_NOT 里原本把它排除在"表面"之外是对的(它不该有
17919
+ 自己的底),但它自己的宿主白底必须显式清掉。 */
17920
+ html body [role="menu"] [class*="_groupTitle"],
17921
+ html body [role="listbox"] [class*="_groupTitle"],
17922
+ html body [class*="_menu"] [class*="_groupTitle"],
17923
+ html body [class*="JObwrW_panel"] [class*="_groupTitle"],
17924
+ html body [role="menu"] [class*="groupTitle"],
17925
+ html body [role="listbox"] [class*="groupTitle"],
17926
+ html body [class*="_menu"] [class*="groupTitle"],
17927
+ html body [class*="JObwrW_panel"] [class*="groupTitle"] {
17928
+ background-color: transparent !important;
17929
+ }
17835
17930
  /* ── 弹层**表面底色**兜底(用户"浮层被压住"的根治项之一)──
17836
17931
  背景过去只来自宿主 token(--dsw-specific-menu / -tip / -selector …),
17837
17932
  不同组件用的 token 不一样,一旦某个链路把 token 改透明、或 backdrop 模糊在
@@ -24460,7 +24555,24 @@ body[data-mpw-modal] [role="dialog"] {
24460
24555
  } catch {}
24461
24556
  };
24462
24557
  try {
24463
- headerBlurWatch = new MutationObserver(check);
24558
+ /* ①(2026-10-02 用户第 7 条:左栏收起/展开动画卡顿;真机对照 rAF 峰值 650–684ms vs 267ms)
24559
+ `check()` 每次都 `querySelectorAll` + `getBoundingClientRect`(强制布局),而宿主在动画
24560
+ 期间**每帧都改 DOM** ⇒ 旧写法等于每帧跑一次全量查询 + 布局。改成 rAF 帧内合并,
24561
+ 并在动画/拖拽期间直接跳过(动画结束后还会再来一次,不会漏掉收尾状态)。 */
24562
+ let hraf = 0;
24563
+ const hrun = () => {
24564
+ hraf = 0;
24565
+ try {
24566
+ if (document.body && document.body.hasAttribute
24567
+ && (document.body.hasAttribute("data-dsh-sidebar-dragging") || document.body.hasAttribute("data-dragging"))) return;
24568
+ check();
24569
+ } catch (e) {}
24570
+ };
24571
+ headerBlurWatch = new MutationObserver(() => {
24572
+ if (hraf) return;
24573
+ try { hraf = (typeof requestAnimationFrame === "function") ? requestAnimationFrame(hrun) : setTimeout(hrun, 16); }
24574
+ catch (e) { hrun(); }
24575
+ });
24464
24576
  headerBlurWatch.observe(document.body, { childList: true, subtree: true });
24465
24577
  } catch { headerBlurWatch = null; }
24466
24578
  check();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-mpkg-wallpaper",
3
- "version": "3.15.1",
3
+ "version": "3.15.2",
4
4
  "description": "DSH Web 壁纸引擎 Wallpaper Engine mpkg 背景插件:浏览器内直接解析 .mpkg(preview.gif 动态背景/内嵌 mp4 视频/多时段切换),整屏统一虚化/对话框/弹层/遮罩独立虚化、镜头缩放平移、时钟、冲突检测。",
5
5
  "private": false,
6
6
  "type": "module",