dsh-mpkg-wallpaper 3.14.0 → 3.15.0

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.en.md CHANGED
@@ -265,24 +265,24 @@ Contains the *Show wallpaper* sub-section.
265
265
  |---|---|---|---|---|
266
266
  | Unify blur | `unifyTint` | on | One slider drives the whole-screen blur; takes over sidebar/title-bar/right-sidebar frost | off |
267
267
  | Full-screen blur degree | `unifyAmount` | 30px | 0–40 (drives the wallpaper-layer blur) | — |
268
- | Left sidebar / title-bar opacity | `sidebarAlpha` | 35% | Frost thickness 0–100% | — |
268
+ | UI transparency | `sidebarAlpha` | 65% | **0% = fully opaque (normal UI), 100% = fully transparent (wallpaper visible)**; drives left sidebar / title bar / right sidebar · dock alike; independent of the blur | — |
269
269
  | Chat follows full-screen blur | `chatFollow` | on | off = the chat area is driven by the *Frosted blur* slider | off |
270
270
  | New-chat button follows panel opacity | `sessionFollow` | on | off = back to the host's original colour | off |
271
271
  | Unified fog (full-screen mask) | `aquaMask` | off | All surfaces share one fog colour (moved here from the Aqua section) | off |
272
272
  | Unified fog strength | `aquaMaskAlpha` | 82% | 0–100% | — |
273
273
 
274
274
  > **What the two sliders mean (fixed since 2026-10-02; judged by `tools/fog-model-test.mjs`)**:
275
- > *Full-screen blur degree* = blur radius (**0 = no blur at all**: the title bar / sidebar / right sidebar / dock
276
- > return to a **solid** surface, so the un-blurred raw wallpaper is never shown through);
277
- > *Left sidebar / title-bar opacity* = fog thickness on every surface (**0 = fully transparent**: the *blurred*
278
- > wallpaper only, no fog and no wallpaper-sampled tint) — it only matters while the blur degree is > 0.
275
+ > *Full-screen blur degree* = blur radius (**0 = no blur at all**);
276
+ > *UI transparency* = how transparent the whole UI shell is (**0% = fully opaque — the normal UI, painted with the
277
+ > host's own surface colour; 100% = fully transparent, wallpaper visible**) — it is independent of the blur, and
278
+ > with transparency at 0 the plugin does not override the host at all (no background, no blur, no token changes).
279
279
  > Both apply to the **whole UI shell** (left sidebar, title bar, right sidebar and dock share one set of tokens).
280
280
 
281
281
  ### 5. UI blur (blur)
282
282
 
283
283
  | Label | Key | Default | Amount key / default | Off |
284
284
  |---|---|---|---|---|
285
- | UI blur follows Unified blur | `blurFollowUnify` | **on** | on = the taken-over rows on this page follow the *Full-screen blur degree* + *Left sidebar / title-bar opacity*; off = they become independently adjustable | — |
285
+ | Follow Unified blur | `blurFollowUnify` | **on** | on = the taken-over rows on this page follow the *Full-screen blur degree* + *Left sidebar / title-bar opacity*; off = they become independently adjustable | — |
286
286
  | Blur dialogs | `dialogBlur` | on | `dialogAmount` 14px | off |
287
287
  | Blur settings panel | `settingsBlur` | on | `settingsAmount` 14px | off |
288
288
  | Blur download/confirm popups | `confirmBlur` | on | `confirmAmount` 12px | off |
package/README.md CHANGED
@@ -280,24 +280,24 @@ node tools/build-bundle.mjs
280
280
  |---|---|---|---|---|
281
281
  | 统一虚化 | `unifyTint` | 开 | 整屏模糊感由一个条控制;开启后接管侧边栏/标题栏/右栏磨砂 | 关 |
282
282
  | 整屏虚化程度 | `unifyAmount` | 30px | 0–40(控制壁纸层 blur) | — |
283
- | 左侧边栏/标题栏透明度 | `sidebarAlpha` | 35% | 白雾厚度 0–100% | — |
283
+ | 界面透明度 | `sidebarAlpha` | 65% | **0% = 完全不透明(正常界面)、100% = 完全透明(看到壁纸)**;同时驱动左栏/标题栏/右栏·dock;与模糊互不影响 | — |
284
284
  | 聊天区跟随整屏虚化 | `chatFollow` | 开 | 关 = 聊天区由「磨砂模糊」条控制 | 关 |
285
285
  | 新会话按钮跟随面板不透明度 | `sessionFollow` | 开 | 关 = 回到宿主原色 | 关 |
286
286
  | 统一雾(全屏遮罩) | `aquaMask` | 关 | 所有表面共享一种雾色(原 Aqua 实验项) | 关 |
287
287
  | 统一雾强度 | `aquaMaskAlpha` | 82% | 0–100% | — |
288
288
 
289
289
  > **两个条的语义(2026-10-02 起口径固定,判据 `tools/fog-model-test.mjs`)**:
290
- > 「整屏虚化程度」= 模糊半径(**0 = 完全不虚化**:标题栏/侧边栏/右栏/dock 也回到**实心**,
291
- > 不会把未模糊的原始壁纸透出来);
292
- > 「左侧边栏/标题栏透明度」= 各表面白雾厚度(**0 = 完全透明**:只看到**模糊**壁纸,不留雾、不留面板取色)——
293
- > 它只在「整屏虚化程度 > 0」时决定观感。
290
+ > 「整屏虚化程度」= 模糊半径(**0 = 完全不虚化**);
291
+ > 「界面透明度」= 整个界面外壳的透明程度(**0% = 完全不透明(正常界面,用宿主自己的表面色)、100% = 完全透明(看到壁纸)**)——
292
+ > 它与模糊**互不影响**:模糊 0 时照样按它透出,只是背后壁纸是清晰的。
293
+ > 另外:**透明度 = 0 时插件完全不覆盖宿主**(不写背景/不写模糊/不动宿主 token)。
294
294
  > 两者对**整个界面外壳**生效(左栏/标题栏/右栏/dock 共用同一套表面 token)。
295
295
 
296
296
  ### 5. 界面虚化(blur)
297
297
 
298
298
  | 面板文案 | 内部键 | 默认 | 程度键 / 默认 | 关 |
299
299
  |---|---|---|---|---|
300
- | 界面虚化跟随统一虚化 | `blurFollowUnify` | **开** | 开 = 本页被接管的项交给「整屏虚化程度 + 左侧边栏/标题栏透明度」统一驱动;关 = 各自独立可调 | — |
300
+ | 跟随统一虚化 | `blurFollowUnify` | **开** | 开 = 被接管的项(左栏磨砂/标题栏磨砂/右侧边栏·dock)与统一值一致;**其中任一项你自己调过之后就独立**;关 = 全部独立;重新打开 = 清空独立标记 | — |
301
301
  | 虚化对话框 | `dialogBlur` | 开 | `dialogAmount` 14px | 关 |
302
302
  | 虚化设置面板 | `settingsBlur` | 开 | `settingsAmount` 14px | 关 |
303
303
  | 虚化下载/确认弹窗 | `confirmBlur` | 开 | `confirmAmount` 12px | 关 |
@@ -315,6 +315,7 @@ node tools/build-bundle.mjs
315
315
  | 第三方 UI 圆角兼容 | `roundCompat` | 关 | 兼容第三方插件圆角 | 关 |
316
316
  | 自适应文字色 + 蓝色清理 | `aquaInk` | 关 | 文字色随遮罩亮度自适应 + 品牌色统一(配 `aquaInkColor`) | 关 |
317
317
  | 深底文字可读增强 | `aquaTextEnhance` | 关 | 文字双色描边(近似方案) | 关 |
318
+ | ≥4K 源自动降 1080p | `resMax`(自动写 1920) | 自动 | 检测到 ≥4K 且宿主转码可用、且你没设过「分辨率上限」时,自动把该壁纸降到 1080p(每张壁纸一次;`?autocap=off` 关闭;设置里可改回) | `?autocap=off` |
318
319
  | 纯 Scene 壁纸渲染方式 | `sceneRender` | — | ⚠ 有按钮、有写入,**无读取点**(未接线) | — |
319
320
  | 场景渲染上报(排障) | `sceneReport` | 开 | 壁纸每 10s 把渲染器现场写到 `reports/` | 关 |
320
321
  | 场景首帧看门狗 | `sceneWatchdog` | 开 | 超时未出画自动回退静态帧;等待时长 `sceneWatchdogSecs` 8s(3–30) | 关 |
package/lib/client.js CHANGED
@@ -371,7 +371,11 @@ function mpwTrace(ev, data) {
371
371
  const DEFAULT_UNIFY_TINT = true; // 统一虚化:整屏模糊感由一个独立条控制
372
372
  const DEFAULT_UNIFY_AMOUNT = 30; // 统一虚化程度 0-40:0=清晰,40=强模糊(控制壁纸层 blur)
373
373
  const DEFAULT_CHAT_FOLLOW = true; // ①(新) 统一虚化是否接管聊天区壁纸(开=整屏统一;关=磨砂条独立控制聊天区,统一虚化只管侧边栏/标题栏)
374
- const DEFAULT_SIDEBAR_ALPHA = 35; // ①(新) 统一虚化下侧边栏/标题栏白雾厚度 0-100(默认 35:顶栏/侧边栏一致且白底淡,session log/模式标签后不会太突兀;用户实测 55 太厚)
374
+ /* ①(2026-10-02 语义重做,用户拍板) 「界面透明度」:**0 = 完全不透明(正常界面)**、
375
+ 100 = 完全透明(直接看到壁纸)⇒ 生效不透明度 = 100 - 透明度。
376
+ 默认 65 ≡ 旧版"35% 白雾"的观感,不让存量用户的默认外观变化。
377
+ 归属与基色的完整口径见 mpwSurfacePlan()。 */
378
+ const DEFAULT_SIDEBAR_ALPHA = 65;
375
379
  const DEFAULT_SESSION_FOLLOW = true; // 统一虚化下,「新会话」按钮是否随整屏虚化(关=随面板不透明度)
376
380
  // ①(2026-10-02 第3项) 「界面虚化」是否跟随统一虚化(默认跟随 = 保持历史接管语义):
377
381
  // 开(默认) → 统一虚化开时,界面虚化里被点名接管的项(左侧边栏磨砂/右栏/dock…
@@ -815,6 +819,38 @@ function sectionSigNow() {
815
819
  * ⑤(新) host 可用时同时 PUT 到宿主端文件(跨端口/清浏览器数据不丢)。
816
820
  * ①(修正) PUT 失败不再永久禁用:hostSettingsOk 只做乐观标记,
817
821
  * 下次写仍会重试(GET 失败一次 ≠ 整个会话失去持久化)。 */
822
+ /* ①(2026-10-02 用户拍板的口径) 被"统一虚化"接管的项,只要**用户单独动过**(那条滑条或
823
+ 那个开关),就从那一刻起**独立**:以后调统一虚化不再改它。标记就是这里的 `*UserSet`。
824
+ 逆向入口:把「跟随统一虚化」重新打开(patch.blurFollowUnify === true)⇒ 清空全部标记,
825
+ 回到"四个表面完全一致"。滑块旁边那个「恢复跟随」按钮走的就是这条。
826
+ 为什么放在 commit() 这个唯一落点:面板上每个滑条/开关都经它落盘,漏一处就会出现
827
+ "动过却不独立"(或反之)—— 判据见 tools/fog-model-test.mjs 的 F 组。 */
828
+ const MPW_USERSET_OF = {
829
+ sidebarBlur: 'sidebarBlurUserSet', sidebarBlurAmount: 'sidebarBlurUserSet',
830
+ headerBlur: 'headerFrostUserSet', headerBlurAmount: 'headerFrostUserSet',
831
+ headerFrostOwn: 'headerFrostUserSet', headerFrostAmount: 'headerFrostUserSet',
832
+ rightSidebarBlur: 'rightSidebarBlurUserSet', rightSidebarBlurAmount: 'rightSidebarBlurUserSet', rightSidebarAlpha: 'rightSidebarBlurUserSet',
833
+ /* ①(2026-10-02 用户第 2 项) 输入框磨砂:面板上动过一次(开或关)⇒ 打 `composerBlurUserSet`,
834
+ 从此**完全按用户的值**(半透明宿主的自动档不再接管)。为什么也放在这个唯一落点:与上面
835
+ 三条同源 —— 任何绕过 commit() 的写法都会让"手动关过"被自动档覆盖(判据 C 组)。 */
836
+ composerBlur: 'composerBlurUserSet',
837
+ };
838
+ const MPW_USERSET_KEYS = ['sidebarBlurUserSet', 'headerFrostUserSet', 'rightSidebarBlurUserSet'];
839
+ function mpwMarkUserSet(patch, cur) {
840
+ const out = Object.assign({}, patch || {});
841
+ try {
842
+ if (out.blurFollowUnify === true) {
843
+ for (const k of MPW_USERSET_KEYS) out[k] = false; // 重新跟随 = 清空标记
844
+ return out;
845
+ }
846
+ for (const k of Object.keys(out)) {
847
+ if (out[k] === void 0) continue;
848
+ const mk = MPW_USERSET_OF[k];
849
+ if (mk) out[mk] = true;
850
+ }
851
+ } catch (e) {}
852
+ return out;
853
+ }
818
854
  function writeSection(next, instant) {
819
855
  // ①(2026-09-19 设置持久化轮) **一次写不得静默抹掉字段** —— 见上面"唯一契约"块。
820
856
  // 三道护栏,顺序有讲究:
@@ -930,12 +966,26 @@ function sectionSigNow() {
930
966
  }).then((r) => { if (r.ok) hostSettingsOk = true; }).catch(() => { hostSettingsOk = false; });
931
967
  } catch { hostSettingsOk = false; }
932
968
  }
969
+ /** ①(2026-10-02 用户第 1 项) boot 收尾后的待办队列(≥4K 自动降档的"补写一次"用它)。
970
+ 为什么需要:boot 期间两处存储还没合并完,writeSection 只置脏不落盘;随后 initHostSettings
971
+ 的 sectionDirty 合并且**以带时间戳的赢家为准**(宿主档若显式带 resMax=0,就会把刚写下的
972
+ 1920 盖回去)⇒ 那一笔会静默失效(自动降档的触发点正是页面加载期的 loadedmetadata,
973
+ 与 boot 窗口重叠)。收尾后核对一次即可覆盖这个窗口:值还在就什么都不做(幂等)。 */
974
+ let mpwAfterBoot = [];
975
+ function mpwOnBootSettled(fn) {
976
+ try { if (!mpwBootPending) { fn(); return } mpwAfterBoot.push(fn) } catch (e) {}
977
+ }
933
978
  /** boot 收尾(宿主设置已合并 / 或超时):把延迟的那一次落盘补上,并把 boot 置为收尾。 */
934
979
  function mpwBootSettle(source) {
935
980
  if (!mpwBootPending) return false;
936
981
  mpwBootPending = false;
937
982
  try { mpwTrace("settings:boot-settled", { source: source || "", dirty: !!mpwStoreDirty, at: Date.now() }) } catch {}
938
983
  if (mpwStoreDirty) { try { mpwFlushStore() } catch (e) { mpwErr("mpwFlushStore(boot 收尾)", e) } }
984
+ // ①(2026-10-02) boot 收尾后的待办:跑在"合并结果定稿"之后(顺序即语义,见上面队列注释)。
985
+ try {
986
+ const q = mpwAfterBoot; mpwAfterBoot = [];
987
+ for (const fn of q) { try { fn() } catch (e) { mpwErr("mpwAfterBoot", e) } }
988
+ } catch (e) {}
939
989
  // 自愈排在最后:两处存储都就位之后再判断"是不是半残档",避免把"还没读回来"误判成"丢了"。
940
990
  try { mpwHealSchedule() } catch {}
941
991
  return true;
@@ -3289,7 +3339,10 @@ function mpwThumbFail(el, why) {
3289
3339
  console.warn("[dsh-mpkg-wallpaper] 视频背景加载失败(编码可能不被浏览器支持):", video.src);
3290
3340
  try { window.__mpwVideoFailed = true; } catch {}
3291
3341
  });
3292
- /* ①(2026-10-01) ≥4K 源 + 虚化 ⇒ 一次性性能提示(不改默认值;台账 __mpwPerfNote)。 */
3342
+ /* ①(2026-10-01) ≥4K 源 + 虚化 ⇒ 一次性性能提示(台账 __mpwPerfNote)。
3343
+ ①(2026-10-02 用户第 1 项) 这里同时是**自动降档的触发点**:≥4K 且 resMax 没设过、
3344
+ 宿主 ffmpeg 可用 ⇒ mpwPerfNoteForSource 内部一次性 commit({resMax:1920})(见该函数;
3345
+ 拿不到转码读数就只提示、不动作)。 */
3293
3346
  video.addEventListener("loadedmetadata", () => {
3294
3347
  try { mpwPerfNoteForSource(video.videoWidth, video.videoHeight); } catch (e) {}
3295
3348
  });
@@ -7649,11 +7702,20 @@ function mpwThumbFail(el, why) {
7649
7702
  // ①(2026-10-02 第3项) 「界面虚化是否跟随统一虚化」(新开关,默认跟随):
7650
7703
  // 跟随 = 标题栏磨砂半径由 unifyAmount 接管(历史语义);不跟随 = 标题栏自己的
7651
7704
  // 磨砂条(headerBlurAmount / headerFrostOwn)说了算,统一虚化只管壁纸与表面雾。
7652
- const followUnifyNow = un && mpwFogModel(s).followUnify;
7705
+ /* ①(2026-10-02 收口) 统一虚化开时,标题栏半径**只由表面模型给**:
7706
+ 未被用户单独动过(无 headerFrostUserSet)⇒ = 整屏虚化程度(与左栏完全一致);
7707
+ 动过 ⇒ 用它自己那条(headerFrostAmount / headerBlurAmount÷4)。
7708
+ 统一虚化关时保留历史链(独立强度 > 磨砂条 > 自动 24px)。 */
7709
+ const planHdr = mpwSurfacePlan(s);
7710
+ /* ①(2026-10-02 真机钉出的机制) 用户说的「标题栏」= 聊天区顶部那条 `.wSkVaW_header`:
7711
+ 旧实现给它写死 `rgba(255,255,255,0.38)` 白底 + 无条件注入磨砂层 ⇒ 颜色不跟滑条、
7712
+ 透明度 100% 时还留一层模糊(用户 ④⑤⑦)。现在:**透明度 = 0(完全不透明)⇒ 什么都不注入**
7713
+ (层移除 + 属性撤掉,宿主自己的样子);否则底色/半径都走表面模型。 */
7714
+ if (planHdr.header.opacityPct >= 100) { cleanup("透明度=0(完全不透明)⇒ 标题栏交还宿主,不注入磨砂层"); return; }
7653
7715
  const px = !wanted ? 0
7654
- : (own ? pxFloor(ownAmt, 8)
7655
- : (followUnifyNow ? pxFloor(unAmt, 12)
7656
- : (hbAmt > 0 ? Math.max(12, Math.round(hbAmt / 4)) : (un ? pxFloor(unAmt, 12) : 24))));
7716
+ : (un ? Math.max(0, Math.round(planHdr.header.blur || 0))
7717
+ : (own ? pxFloor(ownAmt, 8)
7718
+ : (hbAmt > 0 ? Math.max(12, Math.round(hbAmt / 4)) : 24)));
7657
7719
  // 自动半透明底:让模糊真的看得见(否则顶栏是不透明的,磨砂被挡在后面)。
7658
7720
  // ①(2026-09-16) 半透底色改用**我们自己的命名空间 token**(--mpw-hdr-frost-bg),
7659
7721
  // 不再依赖宿主静态色 token;亮/暗两档由 JS 写入(见下)。
@@ -7662,15 +7724,18 @@ function mpwThumbFail(el, why) {
7662
7724
  if (wanted) {
7663
7725
  hdrNow.setAttribute("data-mpw-hdr-translucent", "");
7664
7726
  try {
7665
- const dark = !!(document.body && document.body.hasAttribute && document.body.hasAttribute("data-ds-dark-theme"));
7666
- document.documentElement.style.setProperty("--mpw-hdr-frost-bg", dark ? "rgba(18,22,30,0.45)" : "rgba(255,255,255,0.38)");
7727
+ /* ①(2026-10-02) 底色 = **宿主底色** × 标题栏不透明度(与左栏同一套模型):
7728
+ 旧写法是固定的 rgba(255,255,255,.38) / rgba(18,22,30,.45) ⇒ 用户看到
7729
+ "标题栏一层固定白底、不受任何滑条影响"就是这条。 */
7730
+ const pl = mpwSurfacePlan(s);
7731
+ document.documentElement.style.setProperty("--mpw-hdr-frost-bg", mpwSurfaceColor(pl.header.base, pl.header.opacityPct / 100));
7667
7732
  } catch {}
7668
7733
  } else {
7669
7734
  hdrNow.removeAttribute("data-mpw-hdr-translucent");
7670
7735
  }
7671
7736
  }
7672
7737
  } catch {}
7673
- const pxWhy = own ? ("独立强度 " + px + "px") : (un ? ("跟随整屏虚化 " + px + "px") : ("标题栏磨砂强度 " + px + "px"));
7738
+ const pxWhy = planHdr ? (planHdr.header.indep ? ("独立档 " + px + "px") : ("跟随整屏虚化 " + px + "px")) : (own ? ("独立强度 " + px + "px") : ("标题栏磨砂强度 " + px + "px"));
7674
7739
  // ②(2026-09-16) 与描边删除配套的一致性修正:headerBg=false 是用户明确要的
7675
7740
  // "顶栏不透明(不透出壁纸)",此时**不该**留一层无意义的模糊层 —— 它要么什么也
7676
7741
  // 透不出来(底色不透明),要么反而把壁纸透出来(与用户选择相反)。
@@ -8357,7 +8422,8 @@ function mpwThumbFail(el, why) {
8357
8422
  // CSS 侧 [data-mpw-unify] 的右栏/dock/better-sidebar 规则据此生效(与
8358
8423
  // buildCss 里 unifyTakeover 同源:都走 mpwFogModel(section).unifyOn)。
8359
8424
  try {
8360
- if (mpwFogModel(section).unifyOn) document.body.setAttribute("data-mpw-unify", "");
8425
+ const planNow = mpwSurfacePlan(section);
8426
+ if (planNow.unifyOn && planNow.left.opacityPct < 100) document.body.setAttribute("data-mpw-unify", "");
8361
8427
  else document.body.removeAttribute("data-mpw-unify");
8362
8428
  } catch {}
8363
8429
  // ①(2026-09-13) 标题栏磨砂实现方式开关:默认"本体"(观感稳定);
@@ -8409,7 +8475,9 @@ function mpwThumbFail(el, why) {
8409
8475
  // 关 → 历史档 62%)。厚度 0 ⇒ 写 0% ⇒ color-mix(...,0%) = 透明,标题栏不被采样色盖住。
8410
8476
  try {
8411
8477
  const fog = mpwFogModel(section);
8412
- const pct = fog.unifyOn ? fog.sidePct : 62;
8478
+ /* ①(2026-10-02 方向修正,门禁 D4 观察项) 这里写进的是 CSS 的 **alpha**(可见程度)
8479
+ ⇒ 用生效**不透明度**(shellPct = 100 - 透明度),不是透明度本身。 */
8480
+ const pct = fog.unifyOn ? fog.shellPct : 62;
8413
8481
  if (headerTint) document.documentElement.style.setProperty("--mpw-aqua-header-alpha", pct + "%");
8414
8482
  else document.documentElement.style.removeProperty("--mpw-aqua-header-alpha");
8415
8483
  } catch {}
@@ -11188,12 +11256,67 @@ function mpwThumbFail(el, why) {
11188
11256
  * (没有模糊时透出原始壁纸 = "零设置却有观感",用户第1项真机反馈)
11189
11257
  * chatPct 聊天区厚度(面板不透明度,历史下限 50)
11190
11258
  */
11259
+ /* ═══ 界面外壳表面模型(唯一源,2026-10-02 重做)═══
11260
+ 为什么要有它:以前"颜色 / 透出 / 模糊"三件事散在 token、内联、注入层三处各写一套,
11261
+ 于是同一时刻左侧边栏是"壁纸色的模糊"、标题栏是"固定白底 + 自己的模糊"、右栏是
11262
+ "全透明没模糊"——三块互不一致(用户真机报告 ①④⑤⑦)。现在四个表面只从这一个函数取值。
11263
+
11264
+ 语义(用户拍板,逐条有判据):
11265
+ · 「界面透明度」t:0 = 完全不透明(就是正常界面)、100 = 完全透明(看到壁纸);
11266
+ 生效不透明度 = 100 - t。**与模糊解耦**:模糊为 0 时照样按 t 透出(只是壁纸是清晰的)。
11267
+ · 「整屏虚化程度」= 模糊半径(壁纸层 + 各表面 backdrop)。
11268
+ · 基色 = **宿主自己的表面 token**(t = 0 时逐像素等于"没装插件"的外观)。
11269
+ 以前刷我们的纯白 ⇒ 就是用户报的"变成白色不透明"与"标题栏固定白底"。
11270
+ · 归属:统一虚化开 + 「跟随统一虚化」开 + 该项**没被用户单独动过**(*UserSet 标记)
11271
+ ⇒ 用统一值(四个表面完全一致);否则该项用自己那套(自己的开关 / 半径 / 透明度)。 */
11272
+ function mpwSurfacePlan(section) {
11273
+ const s = section || {};
11274
+ const num = (v, d, lo, hi) => { const n = Number(v !== void 0 ? v : d); return Math.max(lo, Math.min(hi, Number.isFinite(n) ? n : d)); };
11275
+ const on = (k, d) => (s[k] !== void 0 ? !!s[k] : d);
11276
+ const unifyOn = on('unifyTint', DEFAULT_UNIFY_TINT);
11277
+ const follow = on('blurFollowUnify', DEFAULT_BLUR_FOLLOW_UNIFY);
11278
+ const blurU = num(s.unifyAmount, DEFAULT_UNIFY_AMOUNT, 0, 40);
11279
+ const tU = num(s.sidebarAlpha, DEFAULT_SIDEBAR_ALPHA, 0, 100);
11280
+ const indepLeft = !unifyOn || !follow || !!s.sidebarBlurUserSet;
11281
+ const indepHeader = !unifyOn || !follow || !!s.headerFrostUserSet;
11282
+ const indepRight = !unifyOn || !follow || !!s.rightSidebarBlurUserSet;
11283
+ const leftBlur = indepLeft
11284
+ ? (on('sidebarBlur', DEFAULT_SIDEBAR_BLUR) ? num(s.sidebarBlurAmount, DEFAULT_SIDEBAR_BLUR_AMOUNT, 0, 40) : 0)
11285
+ : blurU;
11286
+ const headerBlur = indepHeader
11287
+ ? (on('headerFrostOwn', DEFAULT_HEADER_FROST_OWN)
11288
+ ? num(s.headerFrostAmount, DEFAULT_HEADER_FROST_AMOUNT, 0, 60)
11289
+ : (on('headerBlur', DEFAULT_HEADER) ? num(Math.round(num(s.headerBlurAmount, DEFAULT_HEADER_BLUR_AMOUNT, 0, 100) / 4), 0, 0, 40) : 0))
11290
+ : blurU;
11291
+ const rightBlur = indepRight
11292
+ ? (on('rightSidebarBlur', DEFAULT_RIGHT_SIDEBAR_BLUR) ? num(s.rightSidebarBlurAmount, DEFAULT_RIGHT_SIDEBAR_AMOUNT, 0, 40) : 0)
11293
+ : blurU;
11294
+ const rightT = indepRight ? num(s.rightSidebarAlpha, DEFAULT_RIGHT_SIDEBAR_ALPHA, 0, 100) : tU;
11295
+ const BASE_SIDE = 'var(--dsw-specific-sidebar-fill)';
11296
+ const BASE_TOP = 'var(--dsw-alias-bg-base)';
11297
+ const surf = (blur, t, base, indep) => ({ blur: blur, t: t, opacityPct: 100 - t, base: base, indep: indep });
11298
+ return {
11299
+ unifyOn: unifyOn, follow: follow, blurU: blurU, tU: tU,
11300
+ left: surf(leftBlur, tU, BASE_SIDE, indepLeft),
11301
+ header: surf(headerBlur, tU, BASE_TOP, indepHeader),
11302
+ right: surf(rightBlur, rightT, BASE_SIDE, indepRight),
11303
+ chatFollow: on('chatFollow', DEFAULT_CHAT_FOLLOW),
11304
+ };
11305
+ }
11306
+ /** 表面色:宿主基色 + 不透明度 ⇒ 一条 CSS 表达式(所有表面共用,杜绝"某处又用了纯白")。 */
11307
+ function mpwSurfaceColor(base, opacity01) {
11308
+ const pct = (Math.max(0, Math.min(1, Number(opacity01) || 0)) * 100).toFixed(1);
11309
+ return 'color-mix(in srgb, ' + base + ' ' + pct + '%, transparent)';
11310
+ }
11191
11311
  function mpwFogModel(section) {
11192
11312
  const s = section || {};
11193
- const followUnify = s.blurFollowUnify !== void 0 ? !!s.blurFollowUnify : DEFAULT_BLUR_FOLLOW_UNIFY;
11194
- const unifyOn = (s.unifyTint !== void 0 ? !!s.unifyTint : DEFAULT_UNIFY_TINT) && followUnify;
11195
- const rawAmt = Number(s.unifyAmount !== void 0 ? s.unifyAmount : DEFAULT_UNIFY_AMOUNT);
11196
- const rawSide = Number(s.sidebarAlpha !== void 0 ? s.sidebarAlpha : DEFAULT_SIDEBAR_ALPHA);
11313
+ /* ①(2026-10-02) 本函数是 `mpwSurfacePlan()` 的**薄包装**(保留旧字段名给诊断/门禁用):
11314
+ 唯一源只有 surfacePlan 一处,避免"两个模型各说一套"。 */
11315
+ const P = mpwSurfacePlan(s);
11316
+ const followUnify = P.follow;
11317
+ const unifyOn = P.unifyOn && P.follow;
11318
+ const rawAmt = P.blurU;
11319
+ const rawSide = P.tU;
11197
11320
  const rawChat = Number(s.opacity !== void 0 ? s.opacity : DEFAULT_OPACITY);
11198
11321
  const amountPx = Math.max(0, Math.min(40, Number.isFinite(rawAmt) ? rawAmt : DEFAULT_UNIFY_AMOUNT));
11199
11322
  const sidePct = Math.max(0, Math.min(100, Number.isFinite(rawSide) ? rawSide : DEFAULT_SIDEBAR_ALPHA));
@@ -11202,9 +11325,10 @@ function mpwThumbFail(el, why) {
11202
11325
  unifyOn,
11203
11326
  amountPx,
11204
11327
  sidePct,
11205
- /* ①(2026-10-02 第1项) 有效厚度:不虚化 ⇒ 实心(与 buildCss 的 uAlpha 同一条纪律,
11206
- 两处必须一致:一处管左栏/标题栏,一处管右栏/dock/better-sidebar 的接管 token)。 */
11207
- shellPct: amountPx > 0 ? sidePct : 100,
11328
+ /* ①(2026-10-02 语义重做) `shellPct` 保留旧名 = **生效不透明度** = 100 - 透明度
11329
+ (旧实现里它表示"厚度"且在不虚化时钉到 100;现在语义统一由 surfacePlan 给,
11330
+ 不虚化也照样按透明度透出 —— 用户拍板:两个条彻底解耦)。 */
11331
+ shellPct: Math.max(0, Math.min(100, 100 - sidePct)),
11208
11332
  chatPct: Math.max(50, Math.min(100, Number.isFinite(rawChat) ? rawChat : DEFAULT_OPACITY)),
11209
11333
  };
11210
11334
  }
@@ -11230,8 +11354,11 @@ function mpwThumbFail(el, why) {
11230
11354
  现在统一虚化开 → 用 sidebarAlpha(0 ⇒ color-mix(...,0%) = 完全透明,一个像素不刷);
11231
11355
  统一虚化关 → 保留历史档(62%/85%),观感与刷新前逐字节一致。 */
11232
11356
  const fog = mpwFogModel(section);
11233
- const baseAlpha = fog.unifyOn ? fog.chatPct / 100 : 0.62;
11234
- const sideAlpha = fog.unifyOn ? fog.sidePct / 100 : 0.85;
11357
+ const planAqua = mpwSurfacePlan(section);
11358
+ /* 取色的 alpha = 该表面的**不透明度**(= 100 - 界面透明度):厚度 0 ⇒ 0%(不刷),
11359
+ 完全不透明 ⇒ 100%(取色表面就是它的本色)。 */
11360
+ const baseAlpha = planAqua.unifyOn ? planAqua.left.opacityPct / 100 : 0.62;
11361
+ const sideAlpha = planAqua.unifyOn ? planAqua.right.opacityPct / 100 : 0.85;
11235
11362
  const out = {};
11236
11363
  // 主画布 + 面板/表面类(取色或统一雾开启时生效)
11237
11364
  if (tintOn || maskOn) {
@@ -15881,15 +16008,36 @@ module.exports = {
15881
16008
  try { window.__mpwThemeAssist = { mode: "off", at: Date.now() }; } catch (e) {}
15882
16009
  }
15883
16010
 
15884
- /** 幂等应用:`section` 里 `themeAssist`(默认 auto)/`composerBlur`(默认关)。返回台账对象。 */
16011
+ /** ①(2026-10-02 用户第 2 项) `composerBlur` 的**生效值**(判据出处 tools/theme-assist-test.mjs C 组):
16012
+ · 用户动过(writeSection 打的 `composerBlurUserSet` 标记)⇒ 完全按他的值 —— "显式关过就
16013
+ 永不自动开"(与 bsCompatUserSet 同一口径,见 writeSection 的标记块);
16014
+ · 没动过:档里 true(存量档/旧版本写的)⇒ 开;否则 **仅当** themeAssist 判定宿主底色
16015
+ 半透明时才自动开 —— 默认(不透明)主题下不动作,与今天一致。
16016
+ 返回 { on, mode };`mode === "auto-translucent"` 就是台账里那个"自动开"的来源标记。 */
16017
+ function mpwComposerBlurEffective(sec, assistOn, translucent) {
16018
+ const userSet = !!(sec && sec.composerBlurUserSet === true);
16019
+ const raw = !!(sec && sec.composerBlur !== void 0 ? sec.composerBlur : DEFAULT_COMPOSER_BLUR);
16020
+ if (userSet) return { on: raw, mode: raw ? "user-on" : "user-off" };
16021
+ if (raw) return { on: true, mode: "user-on" };
16022
+ if (assistOn && translucent) return { on: true, mode: "auto-translucent" };
16023
+ return { on: false, mode: "off" };
16024
+ }
16025
+
16026
+ /** 幂等应用:`section` 里 `themeAssist`(默认 auto)/`composerBlur`(默认关,半透明宿主下自动开)。返回台账对象。 */
15885
16027
  function mpwApplyThemeAssist(section) {
15886
16028
  const assist = section && section.themeAssist !== void 0 ? !!section.themeAssist : DEFAULT_THEME_ASSIST;
15887
- const composer = section && section.composerBlur !== void 0 ? !!section.composerBlur : DEFAULT_COMPOSER_BLUR;
15888
16029
  const enabled = !(section && section.enabled === false);
15889
16030
  const hasSource = !!(section && (section.image || section.webUrl));
15890
16031
  const theme = mpwThemeState();
15891
- const led = { mode: "off", assist, composer, enabled, hasSource, translucent: theme.translucent,
16032
+ /* ①(2026-10-02 用户第 2 项) 输入框磨砂取**生效值**(自动档见 mpwComposerBlurEffective)。 */
16033
+ const composerRule = mpwComposerBlurEffective(section, assist, theme.translucent);
16034
+ const composer = composerRule.on;
16035
+ const led = { mode: "off", assist, enabled, hasSource, translucent: theme.translucent,
15892
16036
  bgBase: theme.bgBase, bgBaseAlpha: theme.bgBaseAlpha, sidebarFillAlpha: theme.sidebarFillAlpha,
16037
+ /* `composer`:生效来源 —— 布尔=用户/默认档,"auto-translucent"=半透明宿主自动开(本项要求
16038
+ 台账里能一眼看出"不是用户开的");`composerMode` 是**类型稳定**的同一信息,供程序读。 */
16039
+ composer: composerRule.mode === "auto-translucent" ? "auto-translucent" : composer,
16040
+ composerMode: composerRule.mode,
15893
16041
  frames: 0, composerOn: false, at: Date.now() };
15894
16042
  try {
15895
16043
  /* (A) 应用外框透明化:要**有壁纸** + 开关开 + 宿主确实半透明,才动手。 */
@@ -15924,21 +16072,245 @@ module.exports = {
15924
16072
  return led;
15925
16073
  }
15926
16074
 
15927
- /** (C) ≥4K 源 + 有虚化时的**一次性**性能提示(不改默认值;只提示可选项)。 */
15928
- function mpwPerfNoteForSource(vw, vh) {
16075
+ /* ══ ①(2026-10-02 用户第 1 项) ≥4K 源默认降到 1080p ══════════════════════════════════
16076
+ 口径:**≥4K 才动、1080p 及以下一字不动**。满足全部条件时一次性 `commit({resMax:1920}, true)`
16077
+ (与面板改档同一条落盘路径 ⇒ 用户在设置里能看到「分辨率上限 = 1920」并自行改回):
16078
+ · ≥3840×2160;
16079
+ · resMax 未显式设置(void 0)或为 0(= 用户没设过上限,DEFAULT_RES_MAX 同值);
16080
+ · 这条源**确实可走宿主转码**(复用 mpwTranscodeSpec 判据链 ⇒ scene 内嵌视频/纯浏览器源
16081
+ 一律不写:写了也降不下来,还会骗用户"已降 1080p");
16082
+ · 宿主 ffmpeg 可用(/ffmpeg-check 那条既有链路,拿不到就不做);
16083
+ · 每个壁纸只做一次(`window.__mpw4kAutoCap.seen` 按 mpkgKey/image 去重;**失败不重试**);
16084
+ · 回退口:URL 加 `?autocap=off` ⇒ 完全不自动降(行为同今天,旧的一次性建议提示照旧)。
16085
+ 判据出处:tools/theme-assist-test.mjs D 组(①–⑬ 逐条翻转 + E6/E7 真变异自证)。 */
16086
+
16087
+ /** 宿主**转码(ffmpeg)可用性**的模块级读数口。为什么要模块级缓存:≥4K 的判定发生在 <video>
16088
+ * 的 loadedmetadata(工厂层),拿不到组件里的 `ffmpegState`;而"拿不到就不做"是本项的硬口径
16089
+ * ⇒ 未探测过一律按未知(null)处理、不动作。两个进料口:
16090
+ * ① 面板打开壁纸 tab 时的 checkFfmpeg(既有 /ffmpeg-check 链路,判定源不新起一套);
16091
+ * ② applyInner 的一次性预热探测(否则用户从没打开过设置面板时永远"拿不到")。 */
16092
+ let mpwFfmpegProbe = null; // { ready:boolean, source:string, at:number }
16093
+ const MPW_FFMPEG_TTL_MS = 5 * 60 * 1000; // 读数保鲜期:过期按未知(宁可不动,不用旧结论)
16094
+ let mpwFfmpegInFlight = false; // "探测已发出、答案还没回来"
16095
+ let mpwFfmpegWaiters = []; // 等读数的补判(≥4K 自动降档用;见 mpwOnFfmpegProbe)
16096
+ function mpwNoteFfmpegReady(ready, source) {
16097
+ try { mpwFfmpegProbe = { ready: !!ready, source: String(source || ""), at: Date.now() }; } catch (e) {}
16098
+ return mpwFfmpegProbe;
16099
+ }
16100
+ /** 三态读数:true=可用 / false=明确不可用 / null=未知(没探过或过期)⇒ 调用方不得动作。 */
16101
+ function mpwFfmpegReadyKnown() {
16102
+ try {
16103
+ const p = mpwFfmpegProbe;
16104
+ if (!p || !(Math.abs(Date.now() - Number(p.at || 0)) < MPW_FFMPEG_TTL_MS)) return null;
16105
+ return p.ready === true;
16106
+ } catch (e) { return null }
16107
+ }
16108
+ /** 预热/补探一次(一次一飞、不重试;失败或未回一律留在"未知")。
16109
+ * `why` 只进留痕(真机上 `window.__mpwFfmpegProbeKick` 可看出这次是谁触发的)。
16110
+ * 探测落地(不论成败)时叫醒等待者:等的人拿到的是**最终读数**,不是猜测。 */
16111
+ function mpwFfmpegProbeKick(why) {
16112
+ try {
16113
+ if (mpwFfmpegProbe) return; // 已有读数(不论真假)就不重复探
16114
+ if (window.__mpwFfmpegProbeKicked) return; // 探过没回/失败 ⇒ 不重试
16115
+ window.__mpwFfmpegProbeKicked = true;
16116
+ mpwFfmpegInFlight = true;
16117
+ try { window.__mpwFfmpegProbeKick = { by: String(why || ""), at: Date.now() } } catch (e) {}
16118
+ const done = () => {
16119
+ mpwFfmpegInFlight = false;
16120
+ const q = mpwFfmpegWaiters; mpwFfmpegWaiters = [];
16121
+ for (const fn of q) { try { fn() } catch (e) {} }
16122
+ };
16123
+ fetch(HOST_URL + "/ffmpeg-check", { method: "GET" })
16124
+ .then((r) => (r.ok ? r.json() : null))
16125
+ .then((d) => { if (d && d.ok) mpwNoteFfmpegReady(!!d.found, d.source); })
16126
+ .catch(() => { /* 网络错 ⇒ 保持未知 */ })
16127
+ .then(done, done);
16128
+ } catch (e) { mpwFfmpegInFlight = false /* 桩环境无 fetch:保持未知 */ }
16129
+ }
16130
+ /** 读数是否"在飞":在飞 ≠ 拿不到 —— 在飞时判据要等落地再下结论(见 mpwAutoCap4kForSource)。 */
16131
+ function mpwFfmpegProbeInFlight() { return mpwFfmpegInFlight === true }
16132
+ /** 注册一次"读数落地后"的补判(一次性;落地即清空)。 */
16133
+ function mpwOnFfmpegProbe(fn) {
16134
+ try { mpwFfmpegWaiters.push(fn) } catch (e) {}
16135
+ }
16136
+
16137
+ /** 取词(模块级):`mpwT` 未接线/取不到时回退中文,**绝不把键名当文案弹给用户**。 */
16138
+ function mpwPerfMsg(key, zh) {
16139
+ try { const v = mpwT(key); if (v && v !== key) return String(v) } catch (e) {}
16140
+ return zh;
16141
+ }
16142
+
16143
+ const MPW_4K_MIN_PX = 3840 * 2160; // ≥4K 的判据(像素面积,与旧提示同口径)
16144
+ const MPW_4K_AUTOCAP_RES = 1920; // 自动降档目标 = RES_LEVELS 里的 1080p 档(面板可改回)
16145
+ /** 回退口 `?autocap=off` ⇒ 完全不自动降档(行为同今天)。 */
16146
+ function mpwAutoCapOff() {
16147
+ try { return new URLSearchParams(location.search).get("autocap") === "off" } catch (e) { return false }
16148
+ }
16149
+ /** 这条源能不能被降档:**复用** mpwTranscodeSpec(转码判据链的唯一出口)——
16150
+ * scene 内嵌视频(sv=1)与非 `host:` 源在链上就 `useTranscode=false`(宿主认不出该形状)。 */
16151
+ function mpw4kAutoCapTranscodable(sec) {
16152
+ try {
16153
+ const image = String((sec && sec.image) || "");
16154
+ const isSceneVideo = image.indexOf("sv=1") >= 0;
16155
+ const spec = mpwTranscodeSpec(0, MPW_4K_AUTOCAP_RES, 0, image, isSceneVideo, HOST_URL);
16156
+ return !!(spec && spec.useTranscode && spec.maxW === MPW_4K_AUTOCAP_RES);
16157
+ } catch (e) { return false }
16158
+ }
16159
+ /** 纯函数:自动降档的**唯一判据**(判据出处 tools/theme-assist-test.mjs D 组)。
16160
+ * why 的取值就是台账里的可观测原因:below-4k / autocap-off / not-transcodable /
16161
+ * dup / resmax-set / no-transcode / await-transcode-probe / applied(落盘口拿不到时由
16162
+ * 调用方改写为 no-commit-path;boot 补写成功时 __mpw4kAutoCap.why = applied-after-boot)。
16163
+ * 顺位即语义:先"该不该管这个源",再看回退口与去重,最后才是"用户已设上限"与"宿主能不能转"。 */
16164
+ function mpw4kAutoCapDecision(vw, vh, resMax, ffReady, autocapOff, dup, transcodable, ffInFlight) {
16165
+ const W = Number(vw) || 0, H = Number(vh) || 0;
16166
+ if (W * H < MPW_4K_MIN_PX) return { act: false, why: "below-4k", resMax: 0 };
16167
+ if (autocapOff) return { act: false, why: "autocap-off", resMax: 0 };
16168
+ if (!transcodable) return { act: false, why: "not-transcodable", resMax: 0 };
16169
+ if (dup) return { act: false, why: "dup", resMax: 0 };
16170
+ const r = Number(resMax);
16171
+ if (isFinite(r) && r > 0) return { act: false, why: "resmax-set", resMax: 0 };
16172
+ if (ffReady !== true) {
16173
+ /* 探测**已发出、答案还在路上** ⇒ 这不是"拿不到":交给调用方等读数落地再判一次
16174
+ (真机上视频 metadata 常与预热门探测并行到达;直接判"拿不到"会让它在快机器上时灵时不灵)。 */
16175
+ if (ffInFlight === true) return { act: false, why: "await-transcode-probe", resMax: 0 };
16176
+ return { act: false, why: "no-transcode", resMax: 0 };
16177
+ }
16178
+ return { act: true, why: "applied", resMax: MPW_4K_AUTOCAP_RES };
16179
+ }
16180
+ /** 去重/留痕台账(`window.__mpw4kAutoCap`):同一壁纸只做一次;失败也记 seen(不重试)。 */
16181
+ function mpw4kAutoCapLedger() {
16182
+ try {
16183
+ if (!window.__mpw4kAutoCap || typeof window.__mpw4kAutoCap !== "object") {
16184
+ window.__mpw4kAutoCap = { seen: {}, applied: false, resMax: 0, at: 0, why: "" };
16185
+ }
16186
+ if (!window.__mpw4kAutoCap.seen || typeof window.__mpw4kAutoCap.seen !== "object") window.__mpw4kAutoCap.seen = {};
16187
+ return window.__mpw4kAutoCap;
16188
+ } catch (e) { return null }
16189
+ }
16190
+ /** 壁纸身份(去重键):mpkgKey 优先,其次 image/webUrl/sceneKey(与 npWallpaperIdentity 同序)。 */
16191
+ function mpw4kAutoCapKey(sec) {
16192
+ const s = sec || {};
16193
+ return String(s.mpkgKey || s.image || s.webUrl || s.sceneKey || "?").slice(0, 200);
16194
+ }
16195
+ /** 落盘口 = 面板同一个 commit(组件渲染时经 __mpwSceneUiBridge 注入)。
16196
+ * instant=true(立刻持久化,与面板改档一致);第三个参数 deferApply=true 把重建挪到下一帧 ——
16197
+ * 在 <video> 的 loadedmetadata 回调里同步重建壁纸层会重入媒体事件(真机卡顿/闪回的已知成因)。
16198
+ * ⚠ 面板组件**只在设置页打开时挂载**(卸载时清 mpwSectionNotify,见本文件组件 effect),
16199
+ * 而自动降档的触发点是页面加载期的 loadedmetadata ⇒ 桥拿不到时必须走**同一落盘路径的
16200
+ * 模块级等价物**(commit 内部就是 readSection → mpwMarkUserSet → mpwSwitchPatchFull →
16201
+ * writeSection),否则这一项在真机上永远不生效(设置面板没开 = 静默失效)。 */
16202
+ function mpwAutoCapCommit(resMax) {
16203
+ try {
16204
+ const b = typeof mpwSceneUi === "function" ? mpwSceneUi() : null;
16205
+ if (b && typeof b.commit === "function") { b.commit({ resMax: resMax }, true, true); return true }
16206
+ if (typeof writeSection !== "function" || typeof readSection !== "function") return false;
16207
+ const cur = readSection() || {};
16208
+ const patch = (typeof mpwMarkUserSet === "function") ? mpwMarkUserSet({ resMax: resMax }, cur) : { resMax: resMax };
16209
+ const full = (typeof mpwSwitchPatchFull === "function") ? mpwSwitchPatchFull(patch) : patch;
16210
+ writeSection(Object.assign({}, cur, full), true);
16211
+ try { if (typeof mpwSectionNotify === "function" && mpwSectionNotify) mpwSectionNotify(); } catch (e) {}
16212
+ return true;
16213
+ } catch (e) { return false }
16214
+ }
16215
+ /** ≥4K ⇒ 自动降档一次,返回台账片 { applied, resMax, at, why }(同时写进 __mpwPerfNote.autoCap)。
16216
+ * `expectKey`:只在"等读数落地后再判"那条路径上传入 —— 期间用户换了壁纸 ⇒ 这条观察作废
16217
+ * (why=source-changed:不写档、不留 seen,别拿旧源的分辨率去改新源的上限)。 */
16218
+ function mpwAutoCap4kForSource(vw, vh, expectKey) {
16219
+ const out = { applied: false, resMax: 0, at: Date.now(), why: "below-4k" };
15929
16220
  try {
15930
16221
  const W = Number(vw) || 0, H = Number(vh) || 0;
15931
- if (W * H < 3840 * 2160) return null;
15932
- if (window.__mpwPerfNoteShown === true) return null;
16222
+ const sec = (typeof readSection === "function" ? readSection() : null) || {};
16223
+ const led = mpw4kAutoCapLedger();
16224
+ const key = mpw4kAutoCapKey(sec);
16225
+ const dup = !!(led && led.seen && led.seen[key]);
16226
+ const d = (expectKey && expectKey !== key)
16227
+ ? { act: false, why: "source-changed", resMax: 0 }
16228
+ : mpw4kAutoCapDecision(W, H, sec.resMax, mpwFfmpegReadyKnown(), mpwAutoCapOff(),
16229
+ dup, mpw4kAutoCapTranscodable(sec), mpwFfmpegProbeInFlight());
16230
+ out.why = d.why;
16231
+ /* 只在"确实 ≥4K 且没被回退口挡住、且已有结论"时记账:
16232
+ <4K / `?autocap=off` 完全不参与(行为同今天);"等读数"不是结论 ⇒ 不写 seen。 */
16233
+ if (led && d.why !== "below-4k" && d.why !== "autocap-off" && d.why !== "await-transcode-probe") {
16234
+ if (!led.seen[key]) led.seen[key] = { at: out.at, why: d.why }; // 首次结论为准(失败不重试)
16235
+ led.at = out.at; led.why = d.why;
16236
+ }
16237
+ if (d.act) {
16238
+ if (mpwAutoCapCommit(d.resMax)) {
16239
+ out.applied = true; out.resMax = d.resMax;
16240
+ if (led) { led.applied = true; led.resMax = d.resMax; }
16241
+ /* boot 窗口内写下的可能被随后的两处存储合并盖回(见 mpwOnBootSettled 注释)⇒
16242
+ 收尾后核对一次:不是我们写下的那一档就补写(幂等;只补 resMax,不动别的字段)。 */
16243
+ if (typeof mpwBootPending !== "undefined" && mpwBootPending === true) {
16244
+ mpwOnBootSettled(() => {
16245
+ try {
16246
+ const cur = (typeof readSection === "function" ? readSection() : null) || {};
16247
+ if (Number(cur.resMax) === MPW_4K_AUTOCAP_RES) return; // 值还在 ⇒ 不动作
16248
+ if (mpwAutoCapCommit(MPW_4K_AUTOCAP_RES)) {
16249
+ out.reasserted = true; out.reassertAt = Date.now();
16250
+ if (led) { led.why = "applied-after-boot"; led.at = out.reassertAt; }
16251
+ }
16252
+ } catch (e) {}
16253
+ });
16254
+ }
16255
+ } else {
16256
+ out.why = "no-commit-path"; // 桥与 writeSection 两条落盘口都拿不到 ⇒ 不做
16257
+ if (led) led.why = out.why;
16258
+ }
16259
+ } else if (d.why === "no-transcode") {
16260
+ mpwFfmpegProbeKick("4k"); // 为换壁纸/下次刷新留下读数;本次不动作
16261
+ } else if (d.why === "await-transcode-probe") {
16262
+ /* 读数还在飞 ⇒ 不写 seen(这不是结论),等落地后用同一判据判定**一次**
16263
+ (仍只做一次、失败不重试;期间换壁纸由 expectKey 判废)。 */
16264
+ mpwOnFfmpegProbe(() => {
16265
+ try { mpwAutoCapAnnounce(W, H, mpwAutoCap4kForSource(W, H, key)) } catch (e) {}
16266
+ });
16267
+ }
16268
+ } catch (e) { out.why = "err"; }
16269
+ try {
16270
+ const n = (window.__mpwPerfNote && typeof window.__mpwPerfNote === "object") ? window.__mpwPerfNote : {};
16271
+ n.autoCap = out;
16272
+ window.__mpwPerfNote = n;
16273
+ } catch (e) {}
16274
+ return out;
16275
+ }
16276
+
16277
+ /** 降档判定之后的**通知/台账收尾**("立即判定"与"等读数落地再判"两条路径共用;
16278
+ * 提示仍只发一次:闸门是 `__mpwPerfNoteShown`)。返回窗口对象 __mpwPerfNote;不提示返回 null。
16279
+ * 降成了 ⇒ 提示"已自动降到 1080p(可在设置里改回)"(t() 键 perfNote4kAuto,zh/en 成对);
16280
+ * 没降成 ⇒ 保留**原来那条建议**(≥4K 且开着虚化才提示,且只在"说了有用"的两种原因下:
16281
+ * no-transcode=不知道宿主能不能转、autocap-off=用户用 URL 关掉了)—— 用户没装 ffmpeg 时
16282
+ * 仍然知道可以手动设 1080p,而不是被静默换档。 */
16283
+ function mpwAutoCapAnnounce(W, H, cap) {
16284
+ try {
15933
16285
  const sec = typeof readSection === "function" ? (readSection() || {}) : {};
15934
16286
  const blur = Number(sec.blur !== void 0 ? sec.blur : DEFAULT_BLUR) || 0;
15935
16287
  const unify = sec.unifyTint !== false && Number(sec.unifyAmount !== void 0 ? sec.unifyAmount : DEFAULT_UNIFY_AMOUNT) > 0;
16288
+ const note = Object.assign({}, (window.__mpwPerfNote && typeof window.__mpwPerfNote === "object") ? window.__mpwPerfNote : {},
16289
+ { vw: W, vh: H, blur, unify, at: Date.now(), autoCap: cap });
16290
+ window.__mpwPerfNote = note;
16291
+ if (window.__mpwPerfNoteShown === true) return (cap && cap.applied) ? note : null; // 提示只发一次
16292
+ if (cap && cap.applied) {
16293
+ window.__mpwPerfNoteShown = true;
16294
+ mpwPersistEmit(mpwPerfMsg("perfNote4kAuto", "检测到 ≥4K 视频源:已自动降到 1080p(可在设置里改回)"));
16295
+ return note;
16296
+ }
16297
+ /* 未降成:只有还能给用户别的出路时才提旧建议(已设过上限/这条源不可转 ⇒ 不必打扰)。 */
16298
+ const why = cap && cap.why;
16299
+ if (why !== "no-transcode" && why !== "autocap-off") return null;
15936
16300
  if (!blur && !unify) return null; // 没开虚化 ⇒ 不必提示
15937
16301
  window.__mpwPerfNoteShown = true;
15938
- window.__mpwPerfNote = { vw: W, vh: H, blur, unify, at: Date.now() };
15939
- mpwPersistEmit("检测到 " + W + "×" + H + " 的高分辨率视频源,同时开着壁纸层虚化:桌面端可能明显卡顿。"
15940
- + "建议在设置里把「分辨率上限」设为 1080p(走宿主 ffmpeg 转码),或把虚化调 0。");
15941
- return window.__mpwPerfNote;
16302
+ mpwPersistEmit(mpwPerfMsg("perfNote4kAdvisory", "检测到高分辨率视频源,同时开着壁纸层虚化:桌面端可能明显卡顿。"
16303
+ + "建议在设置里把「分辨率上限」设为 1080p(走宿主 ffmpeg 转码),或把虚化调 0。"));
16304
+ return note;
16305
+ } catch (e) { return null }
16306
+ }
16307
+
16308
+ /** (C) ≥4K 源:**先自动降档,再一次性提示**(判据出处 tools/theme-assist-test.mjs D 组)。 */
16309
+ function mpwPerfNoteForSource(vw, vh) {
16310
+ try {
16311
+ const W = Number(vw) || 0, H = Number(vh) || 0;
16312
+ if (W * H < MPW_4K_MIN_PX) return null; // <4K:一字不动
16313
+ return mpwAutoCapAnnounce(W, H, mpwAutoCap4kForSource(W, H));
15942
16314
  } catch (e) { return null }
15943
16315
  }
15944
16316
 
@@ -16045,8 +16417,20 @@ module.exports = {
16045
16417
  // 「左侧边栏/标题栏透明度」才决定"透出多少模糊壁纸"。
16046
16418
  // ⚠️ 取色(面板取色/统一雾)的 alpha **不看这个钳制**:它仍按那条滑条的原始值给,
16047
16419
  // 所以厚度 0 时不会有任何采样色层(见 mpwFogModel 与 fog-model D 组判据)。
16048
- const uAlphaRaw = Math.max(0, Math.min(100, (section.sidebarAlpha !== void 0 ? section.sidebarAlpha : DEFAULT_SIDEBAR_ALPHA)));
16049
- const uAlpha = Number(unifyAmount) > 0 ? uAlphaRaw : 100;
16420
+ /* ①(2026-10-02 语义重做) 统一值全部来自 mpwSurfacePlan():
16421
+ uAlpha = 生效**不透明度** = 100 - 「界面透明度」(0 = 不透明、100 = 全透明)。
16422
+ 与模糊彻底解耦:模糊 0 时也照样按透明度透出(背后壁纸是清晰的)。 */
16423
+ const surfacePlan = mpwSurfacePlan(section);
16424
+ /* ①(2026-10-02 用户拍板 + 真机读数) **透明度 = 0(完全不透明)⇒ 该表面完全不覆盖宿主**:
16425
+ 不写背景色、不写 backdrop-filter、也不透明化宿主的 sidebar-fill token —— 就是"没装插件"的样子。
16426
+ 为什么必须"不写"而不是"写一个 100% 的宿主色":真机读数证实宿主在**打开设置面板**时会把左栏
16427
+ 换成自己的深色导航(`color(srgb 0.082 0.082 0.090)`),而我们的 `!important` 会把它钉回旧 token
16428
+ ⇒ 用户看到"设置一开左栏就变黑"。交还宿主后那层黑是宿主自己的行为。
16429
+ 判据:tools/fog-model-test.mjs 的 F7 组(透明度 0 ⇒ chrome 覆盖规则 0 处)。 */
16430
+ const chromeInert = surfacePlan.left.opacityPct >= 100;
16431
+ const unifyChrome = unifyTint && !chromeInert;
16432
+ const rightInert = surfacePlan.right.opacityPct >= 100;
16433
+ const uAlpha = surfacePlan.left.opacityPct;
16050
16434
  const U = (v) => unifyTint ? uAlpha : v;
16051
16435
  /* ①(2026-09-18 §5「功能静默无效」第 2 条 / 用户裁定候选 A)「新会话按钮跟随面板不透明度」:
16052
16436
  开 = 跟随那条透明度(现状公式 U(panel):统一虚化开=sidebarAlpha,关=panel);
@@ -16248,13 +16632,14 @@ html[data-mpw-interact="on"] .mpw-bgWrap .mpw-webInteractExit{display:block;}
16248
16632
  /* ①(2026-09-18 §5 第1项) 侧栏表面 token:宿主 token 只在这里被消费。
16249
16633
  亮/暗两套值的差异(unify 开时暗色也用 950;关时亮色用 00、暗色用 950)原样保留。 */
16250
16634
  tok("--mpw-surface-side", unifyTint
16251
- ? `color-mix(in srgb, var(--dsw-static-neutral-bluish-950) ${U(panel)}%, transparent)`
16635
+ ? mpwSurfaceColor(surfacePlan.left.base, surfacePlan.left.opacityPct / 100)
16252
16636
  : `color-mix(in srgb, var(--dsw-static-neutral-bluish-00) ${Math.min(30, panel)}%, transparent)`);
16253
16637
  tok("--mpw-surface-side-dark", unifyTint
16254
- ? `color-mix(in srgb, var(--dsw-static-neutral-bluish-950) ${U(panel)}%, transparent)`
16638
+ ? mpwSurfaceColor(surfacePlan.left.base, surfacePlan.left.opacityPct / 100)
16255
16639
  : `color-mix(in srgb, var(--dsw-static-neutral-bluish-950) ${Math.min(30, panel)}%, transparent)`);
16256
- /* 统一虚化档的亮色覆盖(原 `body:not([data-ds-dark-theme]) .pI_x6G_sidebarCol` 那条) */
16257
- tok("--mpw-surface-side-unify-light", `color-mix(in srgb, var(--dsw-static-neutral-bluish-00) ${U(panel)}%, transparent)`);
16640
+ /* 亮/暗两档同值:基色是**宿主自己的 token**,主题切换由它自己解析 ⇒ 不会再出现
16641
+ "暗色主题里被刷成白底"(旧实现用我们自己的中性色,暗档另写一套)。 */
16642
+ tok("--mpw-surface-side-unify-light", mpwSurfaceColor(surfacePlan.left.base, surfacePlan.left.opacityPct / 100));
16258
16643
  css += `
16259
16644
  html, body {
16260
16645
  background: transparent !important;
@@ -16318,7 +16703,7 @@ html, body {
16318
16703
  ①(修正) sidebar(侧边栏透出壁纸开关)开时**独立于面板不透明度**:用低 alpha
16319
16704
  真正透出壁纸(用户实测:opacity=82 时 sidebar-fill 82% 混合几乎不透,开关
16320
16705
  看似失效)。关 = 走下方 if(!sidebar) 不透明分支。 */
16321
- ${sidebar ? `/* ①(修正 2026-09-13 用户第3项:侧边栏透出没效果) 三层一起上,避免"某层不透明把效果挡住":
16706
+ ${(sidebar && !chromeInert) ? `/* ①(修正 2026-09-13 用户第3项:侧边栏透出没效果) 三层一起上,避免"某层不透明把效果挡住":
16322
16707
  ①token 层:--dsw-specific-sidebar-fill 改成 **transparent**(关键:DSH 的"工作区/会话列表"等
16323
16708
  内层也用这个 token 上底色,若 token 仍是半透明,就会在侧边栏之上再叠一层 → 用户实测
16324
16709
  "出现一个深灰色半透明块"。置为透明后整栏只有容器那一层半透明,观感干净);
@@ -16340,15 +16725,15 @@ html body [class*="sidebarCol"]:not([class*="_tab"]) {
16340
16725
  html body[data-ds-dark-theme] .pI_x6G_sidebarCol,
16341
16726
  html body[data-ds-dark-theme] [class*="sidebarCol"]:not([class*="_tab"]) {
16342
16727
  background-color: var(--mpw-surface-side-dark) !important;
16343
- }` : `.pI_x6G_sidebarCol,
16728
+ }` : (chromeInert ? "" : `.pI_x6G_sidebarCol,
16344
16729
  .hHd-Xa_root {
16345
16730
  background-color: var(--mpw-surface-opaque-side) !important;
16346
16731
  }
16347
16732
  body[data-ds-dark-theme] .pI_x6G_sidebarCol,
16348
16733
  body[data-ds-dark-theme] .hHd-Xa_root {
16349
16734
  background-color: var(--mpw-surface-opaque-side-dark) !important;
16350
- }`}
16351
- ${unifyTint ? `body:not([data-ds-dark-theme]) .pI_x6G_sidebarCol,
16735
+ }`)}
16736
+ ${unifyChrome ? `body:not([data-ds-dark-theme]) .pI_x6G_sidebarCol,
16352
16737
  body:not([data-ds-dark-theme]) .hHd-Xa_root {
16353
16738
  background-color: var(--mpw-surface-side-unify-light) !important;
16354
16739
  }` : ""}
@@ -16520,11 +16905,13 @@ html body[data-mpw-hdr-frost-el] .wSkVaW_header::before {
16520
16905
  // 标题栏内部按钮/logo 白框在 unify 下保持透明(用户此前要求去白框,
16521
16906
  // 靠 hover 交互态保留可读性)。
16522
16907
  const hblurAmt = section.headerBlurAmount !== void 0 ? section.headerBlurAmount : DEFAULT_HEADER_BLUR_AMOUNT;
16523
- const hdrAlpha = unifyTint ? U(panel) : Math.max(0, Math.min(100, hblurAmt));
16908
+ /* ①(2026-10-02) 标题栏与左栏**同一套**(用户拍板:完全同一套):统一虚化开时
16909
+ 用 plan.header 的不透明度与半径;关时保留它自己的雾厚度语义(历史档)。 */
16910
+ const hdrAlpha = unifyTint ? surfacePlan.header.opacityPct : Math.max(0, Math.min(100, hblurAmt));
16524
16911
  /* ①(2026-09-18 §5 第1项) 宿主 token → 我们命名空间的**表面 token**(唯一来源)。
16525
16912
  值就是原来的表达式,公式不搬家 ⇒ 默认档逐字节等价。 */
16526
- tok("--mpw-surface-frost-top", `color-mix(in srgb, var(--dsw-static-neutral-bluish-950) ${hdrAlpha}%, transparent)`);
16527
- tok("--mpw-surface-frost-top-light", `color-mix(in srgb, var(--dsw-static-neutral-bluish-00) ${hdrAlpha}%, transparent)`);
16913
+ tok("--mpw-surface-frost-top", mpwSurfaceColor(surfacePlan.header.base, hdrAlpha / 100));
16914
+ tok("--mpw-surface-frost-top-light", mpwSurfaceColor(surfacePlan.header.base, hdrAlpha / 100));
16528
16915
  // ①(新 2026-09-13) 「标题栏磨砂强度」独立滑条:开了才覆盖半径,默认仍与整屏虚化联动。
16529
16916
  const frostOwn = section.headerFrostOwn !== void 0 ? !!section.headerFrostOwn : DEFAULT_HEADER_FROST_OWN;
16530
16917
  const frostAmtRaw = section.headerFrostAmount !== void 0 ? Number(section.headerFrostAmount) : DEFAULT_HEADER_FROST_AMOUNT;
@@ -16826,7 +17213,7 @@ body:not([data-ds-dark-theme]) .mpw_dialog {
16826
17213
  // ── E. 侧边栏磨砂(sidebarBlur,Aqua 方案)──
16827
17214
  // unify 开(且「界面虚化跟随统一虚化」开着)时由整屏虚化(壁纸层 blur)接管,
16828
17215
  // 不输出自身 backdrop-filter(避免双重模糊);跟随开关关掉 ⇒ 恢复自己那条半径。
16829
- if (sidebarFrosted && !unifyFollowNow) {
17216
+ if (sidebarFrosted && !unifyFollowNow && !chromeInert) {
16830
17217
  const sblFilter = `blur(${sidebarBlurAmount}px)`;
16831
17218
  css += `
16832
17219
  /* ── 侧边栏磨砂(Aqua 方案:sidebarCol 自身 backdrop-filter) ──
@@ -16863,7 +17250,7 @@ html body[data-mpw-sblur][data-mpw-sblur-off] [class*="sidebarCol"] {
16863
17250
  }
16864
17251
 
16865
17252
  // ── F. 右侧边栏/dock 虚化(0.1.5 自带右栏与 dockkit 浮层,独立参数)──
16866
- if (rsBlur) {
17253
+ if (rsBlur && !rightInert) {
16867
17254
  const rsBg = `rgba(255,255,255,${Math.max(0, Math.min(1, rsAlpha / 100)) * 0.55})`;
16868
17255
  css += `
16869
17256
  /* ── F. 右侧边栏 / dock 虚化(独立开关+程度+透明度)── */
@@ -16895,7 +17282,7 @@ body[data-mpw-rsblur="off"] [data-dockkit-strip], body[data-mpw-rsblur="off"] [d
16895
17282
  // 它们的 containing block → 面板定位错乱、z-index 失效。展开面板出现时
16896
17283
  // JS 打 data-mpw-header-blur-off 摘除(见 setupHeaderBlurWatch)。
16897
17284
  // 弹窗打开时 body[data-mpw-sblur-off] 同样摘除(复用侧边栏磨砂机制)。
16898
- if (unifyAmount > 0 && bdSupported) { // ③(2026-09-11 一行修复) 去掉 unifyTint/chatFollow 前置条件——默认 chatFollow=true 会让该块永不输出,导致栏/标题栏磨砂在有壁纸时也缺失(用户实测)
17285
+ if (unifyAmount > 0 && bdSupported && !chromeInert) { // ③(2026-09-11 一行修复) 去掉 unifyTint/chatFollow 前置条件——默认 chatFollow=true 会让该块永不输出,导致栏/标题栏磨砂在有壁纸时也缺失(用户实测)
16899
17286
  css += `
16900
17287
  /* ── 统一虚化 + 聊天区不跟随:侧边栏/标题栏背后叠加整屏虚化度(标题栏展开面板
16901
17288
  出现时由 data-mpw-header-blur-off 摘除,防困住面板) ── */
@@ -16952,49 +17339,46 @@ html body:has([class*="_modal"]) [data-mpw-rs] {
16952
17339
  // 时"看起来还是没模糊"(这就是用户看到的"虚化消失=白纱");
16953
17340
  // 4) 抑制条件集中在选择器里(弹窗/展开面板/拖拽/用户关开关),不再靠 JS 摘除内联;
16954
17341
  // 5) 变量由 JS 写入 :root,改设置只改变量、不动 DOM。
16955
- if (bdSupported) {
17342
+ if (bdSupported && !chromeInert) {
16956
17343
  // sidebarBlur 是布尔(是否启用侧栏磨砂),数值在 sidebarBlurAmount;未启用时退回统一虚化程度
16957
17344
  // ①(2026-10-02 第3项) 跟随开关开 ⇒ 统一虚化接管(半径一律 unifyAmount,
16958
17345
  // 不再被 sidebarBlur 的数值分走);关 ⇒ 保持历史公式(侧栏磨砂开就用它自己的数值)。
16959
- const chromeBlur = Math.max(0, Math.round((unifyFollowNow ? unifyAmount : (sidebarBlur ? sidebarBlurAmount : unifyAmount)) || 0));
16960
- const chromeAlphaRaw = (section && section.sidebarAlpha !== void 0) ? section.sidebarAlpha : DEFAULT_SIDEBAR_ALPHA;
16961
- /* ①(2026-10-02 第1项) 与 buildCss 的 uAlpha 同一条纪律、同一判据:
16962
- 统一虚化开着但「整屏虚化程度」= 0(不虚化)⇒ chrome 表面实心(alpha 1),
16963
- 否则左栏/标题栏会把**原始未模糊**的壁纸透出来(用户第1项真机反馈)。
16964
- 统一虚化关着时保持历史语义:sidebarAlpha 决定"透出多少模糊壁纸"。 */
16965
- const chromeAlphaPct = (unifyTint && !(Number(unifyAmount) > 0)) ? 100 : Number(chromeAlphaRaw);
16966
- const chromeAlpha = Math.max(0, Math.min(1, Number(chromeAlphaPct) / 100));
16967
- const chromeBg = (typeof unifyTint === 'string' && unifyTint) ? unifyTint : '255,255,255';
16968
- const rsAmt2 = Math.max(0, Math.round(typeof rsAmount === 'number' ? rsAmount : 18));
16969
- const rsAlpha2 = Math.max(0, Math.min(1, (typeof rsAlpha === 'number' ? rsAlpha : 60) / 100));
17346
+ /* ①(2026-10-02 收口) 颜色 / 透出 / 模糊三件事**只从 surfacePlan 取**:
17347
+ · 基色 = 宿主的侧栏填充色(不再是我们的纯白 —— 白底/暗色错色都是那条引起的);
17348
+ · 不透明度 = 100 - 界面透明度(0 = 不透明、100 = 全透明);
17349
+ · 半径 = 该项独立档用自己那条,否则用统一虚化的(plan 里已算好)。 */
17350
+ const chromeBlur = Math.max(0, Math.round(surfacePlan.left.blur || 0));
17351
+ const chromeAlpha = Math.max(0, Math.min(1, surfacePlan.left.opacityPct / 100));
17352
+ const rsAmt2 = Math.max(0, Math.round(surfacePlan.right.blur || 0));
17353
+ const rsAlpha2 = Math.max(0, Math.min(1, surfacePlan.right.opacityPct / 100));
16970
17354
  /* ①(2026-09-18 §5 第1项) 这几枚原来是这里自建的一个 :root 块 —— 现在统一登记进
16971
17355
  SSOT(emitSurfaceTokens 输出的那个 :root),值逐字不变;原来是"本块自己的
16972
17356
  :root",与顶栏/面板各自为政 → 正是"四处各写一套"的来源。 */
16973
17357
  tok("--mpw-chrome-blur", chromeBlur + "px");
16974
17358
  tok("--mpw-chrome-alpha", chromeAlpha);
16975
- tok("--mpw-chrome-bg", chromeBg);
17359
+ tok("--mpw-chrome-bg", surfacePlan.left.base);
16976
17360
  tok("--mpw-rs-blur", rsAmt2 + "px");
16977
17361
  tok("--mpw-rs-alpha", rsAlpha2);
16978
17362
  /* 侧栏(左)磨砂表面:底座色 + 透明度 → 一枚 token(原来散在规则里拼 rgba) */
16979
- tok("--mpw-surface-side-frost", "rgba(var(--mpw-chrome-bg), var(--mpw-chrome-alpha))");
17363
+ tok("--mpw-surface-side-frost", mpwSurfaceColor(surfacePlan.left.base, surfacePlan.left.opacityPct / 100));
16980
17364
  /* 右侧栏 / dockkit 面板表面(亮色底 + 22% 档) */
16981
- tok("--mpw-surface-rs-dock", `rgba(${chromeBg === '255,255,255' ? '255,255,255' : chromeBg}, calc(var(--mpw-rs-alpha) * 0.55))`);
17365
+ tok("--mpw-surface-rs-dock", mpwSurfaceColor(surfacePlan.right.base, surfacePlan.right.opacityPct / 100));
16982
17366
  /* 右侧栏 / dockkit 面板表面(暗色底 + 30% 档) */
16983
- tok("--mpw-surface-rs-dock-dark", "rgba(18, 22, 30, calc(var(--mpw-rs-alpha) * 0.75))");
17367
+ tok("--mpw-surface-rs-dock-dark", mpwSurfaceColor(surfacePlan.right.base, surfacePlan.right.opacityPct / 100));
16984
17368
  /* ①(2026-10-02 第2项) 统一虚化接管时的**共同表面**:与左栏同色同厚度同半径。
16985
17369
  三枚 token 只在接管时被下面那块 CSS 引用;不接管时它们不生效(无引用)。
16986
17370
  厚度直接取雾模型(= 左侧边栏/标题栏透明度),0 ⇒ rgba(...,0) = 完全透明,
16987
17371
  所以"统一虚化程度/透明度都拉到 0"时右栏/dock 也是干净的透出,不留采样色。 */
16988
17372
  const fogCss = mpwFogModel(section);
16989
- const unifyTakeover = unifyFollowNow && hasImage;
17373
+ const unifyTakeover = unifyFollowNow && hasImage && !rightInert;
16990
17374
  const unifyBlurCss = fogCss.amountPx > 0 ? `blur(${fogCss.amountPx}px) saturate(140%)` : "none";
16991
17375
  /* ⚠️ 三枚 token **只在接管时**输出:不接管时产物里连变量名都不该出现
16992
17376
  (tools/fog-model-test 的 B3/E3 判据:不跟随时 `--mpw-unify-*` 必须为 0 处),
16993
17377
  否则"有一枚没人用的 token"会被当成"接管逻辑还在"。 */
16994
17378
  if (unifyTakeover) {
16995
- tok("--mpw-unify-blur", fogCss.amountPx + "px");
16996
- tok("--mpw-unify-surface", `rgba(255, 255, 255, ${(fogCss.shellPct / 100).toFixed(3)})`);
16997
- tok("--mpw-unify-surface-dark", `rgba(18, 22, 30, ${(fogCss.shellPct / 100).toFixed(3)})`);
17379
+ tok("--mpw-unify-blur", Math.max(0, Math.round(surfacePlan.right.blur || 0)) + "px");
17380
+ tok("--mpw-unify-surface", mpwSurfaceColor(surfacePlan.right.base, surfacePlan.right.opacityPct / 100));
17381
+ tok("--mpw-unify-surface-dark", mpwSurfaceColor(surfacePlan.right.base, surfacePlan.right.opacityPct / 100));
16998
17382
  }
16999
17383
  /* ①(2026-10-02 第2项) better-sidebar 那几条规则**受 bsCompat 总闸门控**(见下方注释):
17000
17384
  用户手动关掉适配时产物里不许出现 [data-dsh-better-sidebar] 选择器。
@@ -19198,7 +19582,11 @@ body[data-mpw-modal] [role="dialog"] {
19198
19582
  setFfmpegState({ checking: true });
19199
19583
  fetch(HOST_URL + "/ffmpeg-check", { method: "GET" }).then(async (r) => {
19200
19584
  let d = null; try { d = await r.json(); } catch {}
19201
- if (r.ok && d && d.ok && d.found) setFfmpegState({ ready: true, source: d.source, path: d.path, version: d.version });
19585
+ /* ①(2026-10-02 用户第 1 项) 同一次探测的结论**镜像**给模块级读数口:≥4K 自动降档
19586
+ 要用它(工厂层拿不到组件 state)。只在宿主明确答复时落 true/false;网络错/非 JSON
19587
+ ⇒ 保持"未知"(未知 = 不动作,宁可不动也不乱写用户的档)。 */
19588
+ if (r.ok && d && d.ok && d.found) { setFfmpegState({ ready: true, source: d.source, path: d.path, version: d.version }); mpwNoteFfmpegReady(true, d.source); }
19589
+ else if (r.ok && d && d.ok) { setFfmpegState({ missing: true }); mpwNoteFfmpegReady(false); }
19202
19590
  else setFfmpegState({ missing: true });
19203
19591
  }).catch((e) => setFfmpegState({ error: String(e && e.message || e) }));
19204
19592
  };
@@ -19358,7 +19746,7 @@ body[data-mpw-modal] [role="dialog"] {
19358
19746
  // "这个字段不属于新壁纸",而不变量Ⅰ规定 undefined = 不覆盖(粘性护栏还会把它带回来)
19359
19747
  // ⇒ 旧 webUrl/sceneKey 永远删不掉,壁纸层就会"先走错路再回退"(真机黑屏级联)。
19360
19748
  // 这里统一转成显式删除(null = 唯一删除语义);只转壁纸身份字段,见 MPW_SWITCH_SRC_KEYS。
19361
- const next = Object.assign({}, readSection(), mpwSwitchPatchFull(patch));
19749
+ const next = Object.assign({}, readSection(), mpwSwitchPatchFull(mpwMarkUserSet(patch, readSection())));
19362
19750
  writeSection(next, instant);
19363
19751
  setSection(next);
19364
19752
  // ①(2026-09-17 持久化轮) 用户又动了一次壁纸 ⇒ 上一次的持久化失败告警已过期,清掉面板那一行。
@@ -20329,7 +20717,7 @@ body[data-mpw-modal] [role="dialog"] {
20329
20717
  // ①(修正) 类型校验(评审指出:原实现直接透传,`opacity:"abc"` 会污染设置):
20330
20718
  // 数值类必须为有限数、布尔类必须为 boolean、字符串类必须为 string。
20331
20719
  const numFields = ["opacity", "blur", "zoom", "headerBlurAmount", "headerFrostAmount", "dialogAmount", "popoverAmount", "popoverAlpha", "maskAmount", "unifyAmount", "sidebarAlpha", "aquaMaskAlpha", "aquaTintStrength", "glassAlpha", "lgCssAmount", "rotateMin", "brightness", "playbackRate", "clockSize", "fpsCap", "resMax", "preScale", "bsRevealAlpha", "sceneWatchdogSecs", "npVolume"]; // ①(批次15) 四处接线③:导入数值净化;①(NP-4)① 追加 npVolume
20332
- const boolFields = ["sidebar", "sharp", "headerBlur", "headerBg", "rightSidebarBlur", "dialogBlur", "popoverBlur", "maskBlur", "unifyTint", "blurFollowUnify", "chatFollow", "sessionFollow", "aquaMask", "aquaTint", "aquaInk", "aquaTextEnhance", "todoBlur", "hybrid", "roundCompat", "rotate", "float", "newStyle", "mute", "thinkBg", "enabled", "forceEnabled", "clock", "clock24h", "clockSec", "clockDate", "bsCompat", "bsFloat", "bsFont", "bsReveal", "bsAlpha", "bsAqua", "bsBottomAvoid", "lgTest", "lgCss", "lgComposer", "lgSidebar", "lgHeader", "powPauseHidden", "powPauseBlur", "powPauseBattery", "sceneWatchdog", "npNowPlaying", "npLinkWallpaper", "npPaused"]; // ①(批次15) 四处接线④:导入布尔净化;①(NP-1) 末尾追加 npNowPlaying;①(NP-4)② 追加 npLinkWallpaper;①(NP-5) 追加 npPaused;①(2026-10-02) 追加 blurFollowUnify
20720
+ const boolFields = ["sidebar", "sharp", "headerBlur", "headerBg", "rightSidebarBlur", "dialogBlur", "popoverBlur", "maskBlur", "unifyTint", "blurFollowUnify", "chatFollow", "sessionFollow", "sidebarBlurUserSet", "rightSidebarBlurUserSet", "aquaMask", "aquaTint", "aquaInk", "aquaTextEnhance", "todoBlur", "hybrid", "roundCompat", "rotate", "float", "newStyle", "mute", "thinkBg", "enabled", "forceEnabled", "clock", "clock24h", "clockSec", "clockDate", "bsCompat", "bsFloat", "bsFont", "bsReveal", "bsAlpha", "bsAqua", "bsBottomAvoid", "lgTest", "lgCss", "lgComposer", "lgSidebar", "lgHeader", "powPauseHidden", "powPauseBlur", "powPauseBattery", "sceneWatchdog", "npNowPlaying", "npLinkWallpaper", "npPaused"]; // ①(批次15) 四处接线④:导入布尔净化;①(NP-1) 末尾追加 npNowPlaying;①(NP-4)② 追加 npLinkWallpaper;①(NP-5) 追加 npPaused;①(2026-10-02) 追加 blurFollowUnify
20333
20721
  const strFields = ["aquaColor", "aquaInkColor", "themeColor", "accent", "glassColor", "clockPos", "fontColorGrayColor", "sceneDebugParams"]; // ①(批次15) sceneDebugParams 走字符串净化(非法键/值在写入端丢弃)
20334
20722
  for (const k of BACKUP_FIELDS) {
20335
20723
  const v = settings[k];
@@ -20970,8 +21358,11 @@ body[data-mpw-modal] [role="dialog"] {
20970
21358
  ])
20971
21359
  ]);
20972
21360
  };
20973
- const toggleRow = (label, desc, field, def, disabled, forceOn) => {
20974
- const checked = forceOn ? true : (section[field] !== void 0 ? !!section[field] : def);
21361
+ // ①(2026-10-02 用户第 2 项) `shown` = 显示用的**生效值**(可选,第 7 参):半透明宿主会
21362
+ // 自动打开输入框磨砂,若仍显示档里的 false,用户看到的是"开关关着但磨砂已生效"(再点一下
21363
+ // 还会以为没生效)⇒ 显示生效值,点一下 = 显式关(commit 打 composerBlurUserSet)。
21364
+ const toggleRow = (label, desc, field, def, disabled, forceOn, shown) => {
21365
+ const checked = shown !== void 0 ? !!shown : (forceOn ? true : (section[field] !== void 0 ? !!section[field] : def));
20975
21366
  return h("div", { className: "mpw_field" }, [
20976
21367
  h("label", { className: "mpw_label" }, label),
20977
21368
  h("div", { className: "mpw_inline" }, [
@@ -20989,6 +21380,16 @@ body[data-mpw-modal] [role="dialog"] {
20989
21380
  // ⑳(新) 壁纸设置 tab:计算激活的帧率上限值(缺省=0 无限制)与 ffmpeg 状态文本(渲染用)
20990
21381
  const fpsCapVal = section.fpsCap !== void 0 && section.fpsCap !== null ? section.fpsCap : 0;
20991
21382
  const resMaxVal = section.resMax !== void 0 && section.resMax !== null ? section.resMax : DEFAULT_RES_MAX;
21383
+ /* ①(2026-10-02 用户第 2 项) 输入框磨砂开关的**显示值** = mpwComposerBlurEffective 的生效值
21384
+ (半透明宿主上自动开时也照实显示"开",让"关掉 = 永不自动开"这个动作可发现、可逆)。
21385
+ 读样式失败时回退档里的原值(不让面板因探测失败而渲染异常)。 */
21386
+ const composerBlurShown = (() => {
21387
+ try {
21388
+ return mpwComposerBlurEffective(section,
21389
+ section.themeAssist !== void 0 ? !!section.themeAssist : DEFAULT_THEME_ASSIST,
21390
+ mpwThemeState().translucent).on;
21391
+ } catch (e) { return section.composerBlur !== void 0 ? !!section.composerBlur : DEFAULT_COMPOSER_BLUR }
21392
+ })();
20992
21393
  // ①(2026-09-23 第 1 项 B) 预缩档派生值:档位 + 当前屏幕物理宽(按钮文案上直接写出来,
20993
21394
  // 用户一眼知道"按屏幕"到底是多少像素;拿不到尺寸就退回只写"按屏幕物理尺寸")。
20994
21395
  const preScaleVal = section.preScale !== void 0 && section.preScale !== null ? section.preScale : DEFAULT_PRE_SCALE;
@@ -22044,63 +22445,17 @@ body[data-mpw-modal] [role="dialog"] {
22044
22445
  // ①(2026-10-01) 半透明主题适配:宿主 `--dsw-alias-bg-base` 被调低时,应用外框仍不透明地盖住壁纸
22045
22446
  // ⇒ auto 档只在**实测到**底色半透明且当前有壁纸时把外框底色透掉(可逐字还原,见 __mpwThemeAssist)。
22046
22447
  toggleRow(t("themeAssist"), t("themeAssist.desc"), "themeAssist", DEFAULT_THEME_ASSIST),
22047
- toggleRow(t("composerBlur"), t("composerBlur.desc"), "composerBlur", DEFAULT_COMPOSER_BLUR),
22448
+ toggleRow(t("composerBlur"), t("composerBlur.desc"), "composerBlur", DEFAULT_COMPOSER_BLUR, false, false, composerBlurShown),
22048
22449
  (section.unifyTint !== void 0 ? !!section.unifyTint : DEFAULT_UNIFY_TINT)
22049
22450
  ? h("p", { className: "mpw_hint" }, t("sidebar.unifyHint"))
22050
22451
  : null,
22051
22452
 
22052
- // ①(新) 侧边栏磨砂(Aqua 方案):与标题栏磨砂同类(区域自身玻璃化),
22053
- // 弹窗打开时自动摘除(防弹窗被模糊层困住);统一虚化开启时被整屏虚化接管
22054
- // ①(修正) 受「侧边栏透出(sidebar)」控制:侧边栏未透出(或不透明)时禁用变灰,
22055
- // 并注明受侧边栏透出控制(与标题栏透出控制标题栏磨砂同理)。
22056
- // ①(修正 2026-09-13 第5项) 侧边栏磨砂与标题栏磨砂同款:**「侧边栏透出壁纸」关闭时整段收起**
22057
- // (此前只是禁用变灰 + 提示"需先开启侧边栏透出",用户要求直接隐藏,与标题栏一致)。
22058
- // 统一虚化开启时:开关+程度条被接管(禁用变灰),并在本项下方就地给接管说明(第6项)。
22059
- h("div", { style: { display: (section.sidebar !== void 0 ? !!section.sidebar : DEFAULT_SIDEBAR) ? "" : "none" } }, [
22060
- toggleRow(t("sidebarBlur"), t("sidebarBlur.desc"), "sidebarBlur", DEFAULT_SIDEBAR_BLUR,
22061
- followUnifyNow, followUnifyNow),
22062
- h("div", { style: {
22063
- display: (section.sidebarBlur !== void 0 ? !!section.sidebarBlur : DEFAULT_SIDEBAR_BLUR) ? "" : "none",
22064
- opacity: followUnifyNow ? 0.45 : 1,
22065
- pointerEvents: followUnifyNow ? "none" : "auto"
22066
- } },
22067
- sliderRow(t("sidebarBlurAmount"), "sidebarBlurAmount", 0, 40, "px", 1, DEFAULT_SIDEBAR_BLUR_AMOUNT)),
22068
- followUnifyNow
22069
- ? h("p", { className: "mpw_hint" }, t("sidebarBlur.overridden"))
22070
- : null,
22071
- ]),
22072
-
22073
22453
  // ⑤ 标题栏透出壁纸(②:与侧边栏透出归一类)
22074
22454
  toggleRow(t("headerBg"), t("headerBg.desc"), "headerBg", DEFAULT_HEADER_BG),
22075
22455
  (section.unifyTint !== void 0 ? !!section.unifyTint : DEFAULT_UNIFY_TINT)
22076
22456
  ? h("p", { className: "mpw_hint" }, t("headerBg.unifyHint"))
22077
22457
  : null,
22078
22458
 
22079
- // ①(重做) 标题栏磨砂:归到「标题栏透出壁纸」下面,透出关闭时隐藏。
22080
- // ①(修正) 统一虚化开启时**被整屏虚化接管**(与侧边栏磨砂一致):标题栏
22081
- // 磨砂开关禁用、程度条禁用,磨砂/白雾由 unifyAmount/sidebarAlpha 控制;
22082
- // 关掉统一虚化才恢复自由调节(用户要求:统一虚化开时不该被此开关自由关掉)。
22083
- // 开关(headerBg/headerBlur)仍独立:透出关 = 纯白背景。
22084
- h("div", { style: { display: (section.headerBg !== void 0 ? !!section.headerBg : DEFAULT_HEADER_BG) ? "" : "none" } },
22085
- toggleRow(t("headerBlur"), t("headerBlur.desc"), "headerBlur", DEFAULT_HEADER,
22086
- followUnifyNow, followUnifyNow)),
22087
- h("div", { style: {
22088
- display: (section.headerBg !== void 0 ? !!section.headerBg : DEFAULT_HEADER_BG) && (section.headerBlur !== void 0 ? !!section.headerBlur : DEFAULT_HEADER) ? "" : "none",
22089
- opacity: followUnifyNow ? 0.45 : 1,
22090
- pointerEvents: followUnifyNow ? "none" : "auto"
22091
- } },
22092
- sliderRow(t("headerBlurAmount"), "headerBlurAmount", 0, 100, "%", 1, DEFAULT_HEADER_BLUR_AMOUNT)),
22093
- followUnifyNow
22094
- ? h("p", { className: "mpw_hint" }, t("headerBlur.overridden"))
22095
- : null,
22096
- // ①(新 2026-09-13 用户要求) 「标题栏磨砂强度」独立滑条:
22097
- // 默认关闭 = 跟随整屏虚化;开启后 **只** 改标题栏的模糊半径(不影响侧边栏/右栏)。
22098
- h("div", { style: { display: (section.headerBg !== void 0 ? !!section.headerBg : DEFAULT_HEADER_BG) ? "" : "none" } },
22099
- toggleRow(t("headerFrostOwn"), t("headerFrostOwn.desc"), "headerFrostOwn", DEFAULT_HEADER_FROST_OWN)),
22100
- h("div", { style: {
22101
- display: (section.headerBg !== void 0 ? !!section.headerBg : DEFAULT_HEADER_BG) && (section.headerFrostOwn !== void 0 ? !!section.headerFrostOwn : DEFAULT_HEADER_FROST_OWN) ? "" : "none"
22102
- } },
22103
- sliderRow(t("headerFrostAmount"), "headerFrostAmount", 0, 60, "px", 1, DEFAULT_HEADER_FROST_AMOUNT)),
22104
22459
 
22105
22460
  // ①(2026-10-02 第4项) 「右侧边栏/dock 虚化」整组**迁到「界面虚化」页**:
22106
22461
  // 它是"某个区域的背景虚化",与该页的对话框/弹层/遮罩同类;放在"外观"页时
@@ -22192,6 +22547,62 @@ body[data-mpw-modal] [role="dialog"] {
22192
22547
  h("div", { style: { display: (section.maskBlur !== void 0 ? !!section.maskBlur : DEFAULT_MASK_BLUR) ? "" : "none" } },
22193
22548
  sliderRow(t("maskBlurAmount"), "maskAmount", 0, 40, "px", 1, DEFAULT_MASK_AMOUNT)),
22194
22549
 
22550
+ // ── 左侧边栏 / 标题栏(①(2026-10-02 第4项同批迁来:它们也是"某个区域的虚化")──
22551
+ h("div", { className: "mpw_section", style: { marginTop: 18 } }, t("sec.blurLeftTop")),
22552
+ h("p", { className: "mpw_hint" }, t("sec.blurLeftTop.desc")),
22553
+ // ①(新) 侧边栏磨砂(Aqua 方案):与标题栏磨砂同类(区域自身玻璃化),
22554
+ // 弹窗打开时自动摘除(防弹窗被模糊层困住);统一虚化开启时被整屏虚化接管
22555
+ // ①(修正) 受「侧边栏透出(sidebar)」控制:侧边栏未透出(或不透明)时禁用变灰,
22556
+ // 并注明受侧边栏透出控制(与标题栏透出控制标题栏磨砂同理)。
22557
+ // ①(修正 2026-09-13 第5项) 侧边栏磨砂与标题栏磨砂同款:**「侧边栏透出壁纸」关闭时整段收起**
22558
+ // (此前只是禁用变灰 + 提示"需先开启侧边栏透出",用户要求直接隐藏,与标题栏一致)。
22559
+ // 统一虚化开启时:开关+程度条被接管(禁用变灰),并在本项下方就地给接管说明(第6项)。
22560
+ h("div", { style: { display: (section.sidebar !== void 0 ? !!section.sidebar : DEFAULT_SIDEBAR) ? "" : "none" } }, [
22561
+ toggleRow(t("sidebarBlur"), t("sidebarBlur.desc"), "sidebarBlur", DEFAULT_SIDEBAR_BLUR,
22562
+ followUnifyNow, followUnifyNow),
22563
+ h("div", { style: {
22564
+ display: (section.sidebarBlur !== void 0 ? !!section.sidebarBlur : DEFAULT_SIDEBAR_BLUR) ? "" : "none"
22565
+ } },
22566
+ sliderRow(t("sidebarBlurAmount"), "sidebarBlurAmount", 0, 40, "px", 1, DEFAULT_SIDEBAR_BLUR_AMOUNT)),
22567
+ followUnifyNow
22568
+ ? h("div", { className: "mpw_inline" }, [
22569
+ h("span", { className: "mpw_hint" }, t(section.sidebarBlurUserSet ? "overridden.indep" : "sidebarBlur.overridden")),
22570
+ section.sidebarBlurUserSet
22571
+ ? h("button", { className: "mpw_miniBtn", type: "button", onClick: () => commit({ sidebarBlurUserSet: false }, true) }, t("follow.reset"))
22572
+ : null
22573
+ ])
22574
+ : null,
22575
+ ]),
22576
+
22577
+ // ①(重做) 标题栏磨砂:归到「标题栏透出壁纸」下面,透出关闭时隐藏。
22578
+ // ①(修正) 统一虚化开启时**被整屏虚化接管**(与侧边栏磨砂一致):标题栏
22579
+ // 磨砂开关禁用、程度条禁用,磨砂/白雾由 unifyAmount/sidebarAlpha 控制;
22580
+ // 关掉统一虚化才恢复自由调节(用户要求:统一虚化开时不该被此开关自由关掉)。
22581
+ // 开关(headerBg/headerBlur)仍独立:透出关 = 纯白背景。
22582
+ h("div", { style: { display: (section.headerBg !== void 0 ? !!section.headerBg : DEFAULT_HEADER_BG) ? "" : "none" } },
22583
+ toggleRow(t("headerBlur"), t("headerBlur.desc"), "headerBlur", DEFAULT_HEADER,
22584
+ followUnifyNow, followUnifyNow)),
22585
+ h("div", { style: {
22586
+ display: (section.headerBg !== void 0 ? !!section.headerBg : DEFAULT_HEADER_BG) && (section.headerBlur !== void 0 ? !!section.headerBlur : DEFAULT_HEADER) ? "" : "none"
22587
+ } },
22588
+ sliderRow(t("headerBlurAmount"), "headerBlurAmount", 0, 100, "%", 1, DEFAULT_HEADER_BLUR_AMOUNT)),
22589
+ followUnifyNow
22590
+ ? h("div", { className: "mpw_inline" }, [
22591
+ h("span", { className: "mpw_hint" }, t(section.headerFrostUserSet ? "overridden.indep" : "headerBlur.overridden")),
22592
+ section.headerFrostUserSet
22593
+ ? h("button", { className: "mpw_miniBtn", type: "button", onClick: () => commit({ headerFrostUserSet: false }, true) }, t("follow.reset"))
22594
+ : null
22595
+ ])
22596
+ : null,
22597
+ // ①(新 2026-09-13 用户要求) 「标题栏磨砂强度」独立滑条:
22598
+ // 默认关闭 = 跟随整屏虚化;开启后 **只** 改标题栏的模糊半径(不影响侧边栏/右栏)。
22599
+ h("div", { style: { display: (section.headerBg !== void 0 ? !!section.headerBg : DEFAULT_HEADER_BG) ? "" : "none" } },
22600
+ toggleRow(t("headerFrostOwn"), t("headerFrostOwn.desc"), "headerFrostOwn", DEFAULT_HEADER_FROST_OWN)),
22601
+ h("div", { style: {
22602
+ display: (section.headerBg !== void 0 ? !!section.headerBg : DEFAULT_HEADER_BG) && (section.headerFrostOwn !== void 0 ? !!section.headerFrostOwn : DEFAULT_HEADER_FROST_OWN) ? "" : "none"
22603
+ } },
22604
+ sliderRow(t("headerFrostAmount"), "headerFrostAmount", 0, 60, "px", 1, DEFAULT_HEADER_FROST_AMOUNT)),
22605
+
22195
22606
  // ── 右侧边栏 / Dock(①(2026-10-02 第4项) 从「外观」页迁来:它属于本页)──
22196
22607
  // 统一虚化开启且「跟随」开着时整组被接管(开关强制开+变灰,滑条变灰),
22197
22608
  // 并在下方就地给出接管说明;关掉上面的跟随开关即可恢复本组可调。
@@ -22200,16 +22611,19 @@ body[data-mpw-modal] [role="dialog"] {
22200
22611
  toggleRow(t("rightSidebarBlur"), t("rightSidebarBlur.desc"), "rightSidebarBlur", DEFAULT_RIGHT_SIDEBAR_BLUR,
22201
22612
  followUnifyNow, followUnifyNow),
22202
22613
  h("div", { style: {
22203
- display: (section.rightSidebarBlur !== void 0 ? !!section.rightSidebarBlur : DEFAULT_RIGHT_SIDEBAR_BLUR) ? "" : "none",
22204
- opacity: followUnifyNow ? 0.45 : 1,
22205
- pointerEvents: followUnifyNow ? "none" : "auto"
22614
+ display: (section.rightSidebarBlur !== void 0 ? !!section.rightSidebarBlur : DEFAULT_RIGHT_SIDEBAR_BLUR) ? "" : "none"
22206
22615
  } },
22207
22616
  sliderRow(t("rightSidebarBlurAmount"), "rightSidebarBlurAmount", 0, 40, "px", 1, DEFAULT_RIGHT_SIDEBAR_AMOUNT),
22208
22617
  h("p", { className: "mpw_hint", style: { marginTop: -4 } }, t("rightSidebarBlurAmount.desc")),
22209
22618
  sliderRow(t("rightSidebarAlpha"), "rightSidebarAlpha", 0, 100, "%", 1, DEFAULT_RIGHT_SIDEBAR_ALPHA),
22210
22619
  h("p", { className: "mpw_hint", style: { marginTop: -4 } }, t("rightSidebarAlpha.desc"))),
22211
22620
  followUnifyNow
22212
- ? h("p", { className: "mpw_hint" }, t("rightSidebarBlur.overridden"))
22621
+ ? h("div", { className: "mpw_inline" }, [
22622
+ h("span", { className: "mpw_hint" }, t(section.rightSidebarBlurUserSet ? "overridden.indep" : "rightSidebarBlur.overridden")),
22623
+ section.rightSidebarBlurUserSet
22624
+ ? h("button", { className: "mpw_miniBtn", type: "button", onClick: () => commit({ rightSidebarBlurUserSet: false }, true) }, t("follow.reset"))
22625
+ : null
22626
+ ])
22213
22627
  : null,
22214
22628
 
22215
22629
  ]),
@@ -22803,11 +23217,15 @@ body[data-mpw-modal] [role="dialog"] {
22803
23217
  "sec.appearance.desc": "面板不透明度=所有区域白雾厚度;磨砂模糊=整张壁纸的模糊程度(0=清晰);镜头缩放/位置=背景画面的放大与平移(缩小可看到画面边缘的组件)",
22804
23218
  "sec.blur": "界面虚化",
22805
23219
  "sec.blur.desc": "各区域的背景虚化:对话框(通用居中窗口+聊天输入框)、设置面板、下载确认弹窗各自独立;弹层=菜单/下拉/提示;遮罩=弹层背后的全屏背景;右侧边栏/dock=DSH 自带右栏与底部浮层。默认「跟随统一虚化」:统一虚化开着时,被接管的项(左侧边栏磨砂/标题栏磨砂/右侧边栏·dock)由「整屏虚化程度 + 左侧边栏/标题栏透明度」统一驱动。",
22806
- "blurFollowUnify": "界面虚化跟随统一虚化",
22807
- "blurFollowUnify.desc": "开(默认)=本页里被接管的项(左侧边栏磨砂、标题栏磨砂、右侧边栏/dock 虚化)交给统一虚化统一驱动,整屏一致;关=这些项各自独立可调,统一虚化只管壁纸模糊与各表面白雾,不再改它们的半径。",
23220
+ "blurFollowUnify": "跟随统一虚化",
23221
+ "blurFollowUnify.desc": "开(默认)=本页被接管的项(左侧边栏磨砂 / 标题栏磨砂 / 右侧边栏·dock)与「界面透明度」「整屏虚化程度」保持一致;其中任何一项只要**你自己单独调过**,它就从那一刻起独立(统一虚化不再改它)。关=全部独立。重新打开本开关 = 清掉独立标记、回到四表面完全一致。",
23222
+ "overridden.indep": "已改为独立:统一虚化不再改这一项(点右边按钮可恢复跟随)",
23223
+ "follow.reset": "恢复跟随",
22808
23224
  "blur.follow.hint": "下面这组「界面虚化」的虚化程度默认由「界面统一」页的滑条统一接管(统一虚化开启时);想让它们各自独立调节,请关掉下面这个开关。未被接管的项(对话框/设置面板/下载确认/弹层/遮罩)始终独立生效。",
23225
+ "sec.blurLeftTop": "左侧边栏 / 标题栏",
23226
+ "sec.blurLeftTop.desc": "这两个表面自己的虚化:左侧边栏磨砂=左栏自身玻璃化(需先开「左侧边栏透出壁纸」);标题栏磨砂=聊天区顶部那条的磨砂。跟随统一虚化时它们与「界面透明度」「整屏虚化程度」保持一致;单独调过即独立。",
22809
23227
  "sec.blurRight": "右侧边栏 / Dock",
22810
- "sec.blurRight.desc": "DSH 自带的右侧边栏与底部 dock 浮层(含 better-sidebar 的面板与底部工作台)。跟随统一虚化时本组由统一虚化接管,且与左侧边栏使用同一套表面颜色与厚度。",
23228
+ "sec.blurRight.desc": "DSH 自带的右侧边栏与底部 dock 浮层(含 better-sidebar 的面板与底部工作台)。跟随统一虚化时本组与左侧边栏**完全一致**(同一个界面透明度、同一个模糊半径、同一个宿主基色)。",
22811
23229
  "sec.unify": "界面统一",
22812
23230
  "sec.unify.desc": "整屏界面外观统一管理,两类效果互不干扰:①统一虚化=壁纸模糊度+白雾厚度(左侧边栏/标题栏/右侧边栏/dock 等整个界面外壳一起毛玻璃);②统一雾=全屏色调雾罩(所有表面共享一种雾色/浓度)。统一虚化开启时,「界面虚化」页里被点名的项由这里的两个条接管(可在那一页关掉「跟随统一虚化」让它们独立)。",
22813
23231
  "sec.unifyFog": "统一雾(色调)",
@@ -23138,7 +23556,10 @@ body[data-mpw-modal] [role="dialog"] {
23138
23556
  "themeAssist": "半透明主题适配(应用外框)",
23139
23557
  "themeAssist.desc": "宿主把底色改成半透明后,应用外框仍会不透明地捂住壁纸:开着时只在实测到底色半透明且当前有壁纸的情况下透掉外框底色(默认灰态零行为、可还原)",
23140
23558
  "composerBlur": "输入框磨砂",
23141
- "composerBlur.desc": "去掉输入框上方那道渐变色带、改为输入框卡片自身磨砂:底色半透明时正文会糊进输入框,开它可解决(默认主题下那道色带是有用的,故默认关)",
23559
+ "composerBlur.desc": "去掉输入框上方那道渐变色带、改为输入框卡片自身磨砂:底色半透明时正文会糊进输入框,开它可解决(默认主题下那道色带是有用的,故默认关)。宿主底色实测半透明时**自动开启**;在这里关掉 = 永不自动开",
23560
+ /* ①(2026-10-02 用户第 1 项) ≥4K 自动降档的两条提示文案(zh/en 必须成对;A 组判据钉住)。 */
23561
+ "perfNote4kAuto": "检测到 ≥4K 视频源:已自动降到 1080p(可在设置里改回)",
23562
+ "perfNote4kAdvisory": "检测到高分辨率视频源,同时开着壁纸层虚化:桌面端可能明显卡顿。建议在设置里把「分辨率上限」设为 1080p(走宿主 ffmpeg 转码),或把虚化调 0。",
23142
23563
  "headerBlur": "标题栏磨砂",
23143
23564
  "headerBg": "标题栏透出壁纸",
23144
23565
  "headerBg.unifyHint": "统一虚化开启中:标题栏的透出程度由「透明度」条控制(关掉本开关 = 完全不透明)",
@@ -23153,8 +23574,8 @@ body[data-mpw-modal] [role="dialog"] {
23153
23574
  "unifyTint.desc": "开启后,整个界面外壳(左侧边栏/标题栏/右侧边栏/dock)一起变成毛玻璃:模糊半径由「整屏虚化程度」控制,表面白雾厚度由「左侧边栏/标题栏透明度」控制;「界面虚化」页里被点名的项默认跟随这两个条(可在那一页关掉「跟随统一虚化」让它们独立)。关闭后各区域单独调节",
23154
23575
  "unifyAmount": "整屏虚化程度",
23155
23576
  "unifyAmount.desc": "整屏的模糊半径(px):0 = 不虚化(壁纸完全清晰,标题栏/侧边栏/右栏/dock 也回到实心,不会再把原始壁纸透出来);40 = 强毛玻璃。统一虚化开启时这是整个界面外壳唯一的模糊来源。",
23156
- "sidebarAlpha": "左侧边栏/标题栏透明度",
23157
- "sidebarAlpha.desc": "各表面白雾的厚度(%):0 = 完全透明(只看到**模糊**壁纸,不留雾、不留取色);100 = 完全不透明。统一虚化开启时,左侧边栏/标题栏/右侧边栏/dock 的表面厚度都由它决定。⚠ 它只在「整屏虚化程度 > 0」时决定观感:程度 = 0(不虚化)时外壳一律实心,避免把未模糊的原始壁纸透出来。",
23577
+ "sidebarAlpha": "界面透明度",
23578
+ "sidebarAlpha.desc": "整个界面外壳(左侧边栏 / 标题栏 / 右侧边栏·dock)的透明程度:0% = 完全不透明(正常界面,用宿主自己的表面色);100% = 完全透明(直接看到壁纸)。与模糊**互不影响**:模糊为 0 时照样按这个值透出,只是背后的壁纸是清晰的。",
23158
23579
  "chatFollow": "聊天区跟随整屏虚化",
23159
23580
  "chatFollow.desc": "统一虚化开启时:开 = 聊天区壁纸模糊也随「整屏虚化程度」(磨砂条被接管禁用);关 = 磨砂条恢复可调,聊天区壁纸由磨砂条控制(统一虚化仍负责界面外壳:左栏/标题栏/右栏/dock)",
23160
23581
  "sessionFollow": "新会话按钮跟随面板不透明度",
@@ -23181,7 +23602,7 @@ body[data-mpw-modal] [role="dialog"] {
23181
23602
  "rightSidebarBlurAmount": "虚化程度",
23182
23603
  "rightSidebarBlurAmount.desc": "右侧边栏/dock 背后壁纸的模糊半径(px)。「界面虚化跟随统一虚化」开着且统一虚化开启时,本项由「整屏虚化程度」接管。",
23183
23604
  "rightSidebarAlpha": "表面透明度",
23184
- "rightSidebarAlpha.desc": "右侧边栏/dock 表面白雾的浓淡(%):0 = 完全透明(只看到壁纸);100 = 完全不透明。跟随统一虚化时由「左侧边栏/标题栏透明度」接管。",
23605
+ "rightSidebarAlpha.desc": "右侧边栏/dock 自己的透明度(%):0 = 完全不透明、100 = 完全透明。跟随统一虚化时由「界面透明度」接管;单独调过本项即改为独立。",
23185
23606
  "rightSidebarBlur.desc": "适配 DSH 自带的右侧边栏与底部 dock 浮层(补打旧类名使其命中同一套虚化规则)。跟随统一虚化开启时,本组整体由统一虚化的两个条接管。",
23186
23607
  "sidebarBlur.desc": "左侧边栏自身玻璃化(backdrop-filter 模糊其背后的壁纸),开启后左侧边栏有类似 Aqua 的磨砂玻璃质感;打开弹窗时自动摘除以防弹窗被左侧边栏模糊层困住",
23187
23608
  "sidebarBlurAmount": "左侧边栏磨砂程度",
@@ -23385,9 +23806,13 @@ body[data-mpw-modal] [role="dialog"] {
23385
23806
  "sec.appearance.desc": "Panel opacity = frosted tint thickness of all areas; Frosted blur = how blurred the wallpaper itself is (0 = sharp); Lens zoom/position = zoom and pan of the background image (zoom out to see components at the picture edges)",
23386
23807
  "sec.blur": "UI blur",
23387
23808
  "sec.blur.desc": "Backdrop blur per area: dialogs (generic center windows + chat input), the settings panel and download/confirm popups each have their own switch; popovers = menus/dropdowns/tooltips; mask = the full-screen dim behind popups; right sidebar/dock = the built-in right panel and bottom dock. By default this page follows Unified blur: the taken-over rows (left sidebar frost, title-bar frost, right sidebar/dock) are driven by the Full-screen blur degree + Left sidebar / title-bar opacity.",
23388
- "blurFollowUnify": "UI blur follows Unified blur",
23389
- "blurFollowUnify.desc": "On (default) = the taken-over rows on this page (left sidebar frost, title-bar frost, right sidebar/dock blur) are driven by Unified blur so the whole shell matches; Off = those rows become independently adjustable, and Unified blur only owns the wallpaper blur and the surface fog.",
23809
+ "blurFollowUnify": "Follow Unified blur",
23810
+ "blurFollowUnify.desc": "On (default) = the taken-over rows here (left sidebar frost / title-bar frost / right sidebar · dock) stay identical to “UI transparency” + “Full-screen blur degree”. Any row you adjust yourself becomes independent from that moment on. Off = all of them independent. Turning this switch back on clears the independent markers and makes all four surfaces identical again.",
23811
+ "overridden.indep": "Independent now: Unified blur no longer changes this row (use the button to follow again)",
23812
+ "follow.reset": "Follow again",
23390
23813
  "blur.follow.hint": "The frost amounts below are taken over by the two sliders on the “Surface unify” page while Unified blur is on. Turn the switch below off to make them independent. Rows that are not taken over (dialogs / settings panel / download-confirm / popovers / mask) always stay independent.",
23814
+ "sec.blurLeftTop": "Left sidebar / Title bar",
23815
+ "sec.blurLeftTop.desc": "Frost for these two surfaces: left-sidebar frost = the sidebar itself becomes glass (needs “Show wallpaper behind the sidebar” first); title-bar frost = the bar at the top of the chat column. While following Unified blur they stay identical to “UI transparency” + “Full-screen blur degree”; adjust one yourself to make it independent.",
23391
23816
  "sec.blurRight": "Right sidebar / Dock",
23392
23817
  "sec.blurRight.desc": "The built-in right sidebar and bottom dock overlays (including better-sidebar panels and its bottom workbench). While following Unified blur this group is taken over and shares one surface color and thickness with the left sidebar.",
23393
23818
  "sec.unify": "Surface unify",
@@ -23590,7 +24015,9 @@ body[data-mpw-modal] [role="dialog"] {
23590
24015
  "themeAssist": "Translucent-theme assist (app frame)",
23591
24016
  "themeAssist.desc": "When the host base colour is made translucent, the app frame still paints opaque over the wallpaper: this only clears the frame fill after detecting a translucent base AND an active wallpaper (no-op on default themes, fully restorable)",
23592
24017
  "composerBlur": "Frost the composer",
23593
- "composerBlur.desc": "Drops the gradient slab above the composer and frosts the composer card itself: fixes transcript text bleeding through when the base colour is translucent (off by default, the slab is useful on default themes)",
24018
+ "composerBlur.desc": "Drops the gradient slab above the composer and frosts the composer card itself: fixes transcript text bleeding through when the base colour is translucent (off by default, the slab is useful on default themes). Turns on automatically when the host base colour is measured translucent; switching it off here means never auto-enabling it",
24019
+ "perfNote4kAuto": "≥4K video source detected: automatically lowered to 1080p (change it back in Settings)",
24020
+ "perfNote4kAdvisory": "High-resolution video source detected while wallpaper frosting is on: desktop may stutter noticeably. Set Resolution cap to 1080p in Settings (uses the host ffmpeg transcode), or set frosting to 0.",
23594
24021
  "headerBlur": "Frost the title bar",
23595
24022
  "headerBg": "Show wallpaper behind the title bar",
23596
24023
  "headerBg.unifyHint": "Unified blur is on: title bar translucency follows the Surface opacity slider (turn this switch off for a fully opaque title bar)",
@@ -23605,8 +24032,8 @@ body[data-mpw-modal] [role="dialog"] {
23605
24032
  "unifyTint.desc": "On: the whole UI shell (left sidebar / title bar / right sidebar / dock) becomes one seamless frosted glass; blur radius by the Full-screen blur degree, surface fog thickness by the Left sidebar / title-bar opacity slider. The rows taken over on the UI-blur page follow these two sliders by default (turn “UI blur follows Unified blur” off there to decouple them). Off = per-area control",
23606
24033
  "unifyAmount": "Full-screen blur degree",
23607
24034
  "unifyAmount.desc": "Blur radius (px) for the whole screen: 0 = no blur at all (the wallpaper is fully sharp and the title bar / sidebar / right sidebar / dock return to a solid surface — no raw wallpaper showing through); 40 = heavy frosted glass. While Unified blur is on this is the only blur source for the UI shell.",
23608
- "sidebarAlpha": "Left sidebar / title-bar opacity",
23609
- "sidebarAlpha.desc": "Fog thickness (%) on every chrome surface: 0 = fully transparent (the *blurred* wallpaper only — no fog and no wallpaper-sampled tint); 100 = fully opaque. While Unified blur is on it drives the left sidebar, title bar, right sidebar and dock alike. ⚠ It only matters while *Full-screen blur degree* > 0: with the degree at 0 (no blur) the shell stays solid, so the un-blurred wallpaper is never shown through.",
24035
+ "sidebarAlpha": "UI transparency",
24036
+ "sidebarAlpha.desc": "Transparency of the whole UI shell (left sidebar / title bar / right sidebar · dock): 0% = fully opaque (the normal UI, painted with the host's own surface colour); 100% = fully transparent (wallpaper visible). Independent of the blur: with the blur at 0 it still applies, only the wallpaper behind is sharp.",
23610
24037
  "chatFollow": "Chat follows full-screen blur",
23611
24038
  "chatFollow.desc": "When unified blur is on: On = the chat area wallpaper blur also follows the Full-screen blur degree (frosted slider taken over & disabled); Off = the frosted slider becomes adjustable again and the chat-area wallpaper follows it (unified blur still owns the UI shell: sidebar / title bar / right sidebar / dock)",
23612
24039
  "sessionFollow": "New-chat button follows panel opacity",
@@ -24068,6 +24495,10 @@ body[data-mpw-modal] [role="dialog"] {
24068
24495
  // 在模块作用域**不存在** ⇒ Now playing 的挂载路径(applyFromStorageInner 调用链)
24069
24496
  // 看不见它。这里存一份 `ctx.locale.bind(NS)`,与面板/词典注册同一个命名空间。
24070
24497
  try { mpwT = ctx.locale.bind(NS); } catch (e) { mpwT = (k) => k; }
24498
+ /* ①(2026-10-02 用户第 1 项) 预热宿主转码可用性读数:≥4K 自动降档的判据之一是"宿主 ffmpeg
24499
+ 可用",而它必须**在视频 metadata 到达之前**就有读数(否则一律"拿不到⇒不做")。
24500
+ 只读探针、一次一飞、失败不重试(mpwFfmpegProbeKick 内部去重)。 */
24501
+ try { mpwFfmpegProbeKick("apply"); } catch (e) {}
24071
24502
  /* ①(2026-09-19 设置持久化轮) **boot 收尾闸门 + 启动合成**(顺序就是根因链的顺序):
24072
24503
  ① 先开闸门:这之后到宿主设置合并完之间的任何写都只置脏、不落盘 —— 否则 boot 那次
24073
24504
  applyFromStorage() 会把"宿主字段还没合并进来"的残缺档同时写进两处存储;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-mpkg-wallpaper",
3
- "version": "3.14.0",
3
+ "version": "3.15.0",
4
4
  "description": "DSH Web 壁纸引擎 Wallpaper Engine mpkg 背景插件:浏览器内直接解析 .mpkg(preview.gif 动态背景/内嵌 mp4 视频/多时段切换),整屏统一虚化/对话框/弹层/遮罩独立虚化、镜头缩放平移、时钟、冲突检测。",
5
5
  "private": false,
6
6
  "type": "module",