@linxin666/dsh-client-ui-skin-center 0.1.18 → 0.1.20

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.i18n.yaml CHANGED
@@ -2,5 +2,5 @@
2
2
  # side as of the last confirmed-consistent state. Both languages carry equal authority;
3
3
  # after editing either side, bring the other along and re-record with:
4
4
  # node scripts/verify-docs.mjs --write <dir>
5
- README.md: 0f2055eff6d26f61407584e4e18f70e716a815c2
6
- README.zh.md: 4747255cf51b02f43009eb8723d230f8e75c4990
5
+ README.md: 96f08ee9ae1e8040fbb798f2cc77b5ca2315efcd
6
+ README.zh.md: 0e90bf2477bc2e474290fe3ba4b837fec06e3d73
package/README.md CHANGED
@@ -7,7 +7,7 @@ English | [中文](README.zh.md)
7
7
  - List: shows "官方默认" (official default) plus every skin in the repo (qq98 / ths / xp / blue-fantasy / dragon-heir / minecraft) with its name, tagline, and accent color; the currently active target carries the Active marker.
8
8
  - Try-on: clicking "Try on" loads that skin's client bundle on demand — the host route `/api/skin-center/bundle/<id>` serves `lib/client.js` as a same-origin script (the same mechanism the core uses to load plugins), the factory registers with the page's own `window.__ModuleLoader__`, and `window.__DSH_MODULES__.import` materializes it (a real loader, not a simulator and no eval); the chrome takes effect immediately; light/dark switching rides the official theme service; "Exit try-on" restores everything — the current skin's styles, DOM, favicon, title, and body inline styles are all restored. "官方默认" (official default) can also be tried on: one click immediately withdraws the skin and returns to the official look preview.
9
9
  - Mutual exclusion: during try-on, the active skin's visual writes (body attribute, background inline styles, chrome child nodes, xp's footer taskbar) are temporarily withdrawn per the prescription, and restored verbatim on exit; only one skin is on the page at a time.
10
- - Apply: the host half (`src/index.ts` + `src/routes.ts`) exposes `/api/skin-center/apply` and `/api/skin-center/bundle/<id>` (serves skin bundles on demand); clicking "Apply / 恢复默认" (Apply / Restore default) runs the embedded in-process `dsh-skin use` port (`src/skin-switch.ts`) server-side, writes `<harness-home>/cordis.patch.yml`, and the DSH config watcher hot-loads it within seconds and the page auto-refreshes — **no dsh web restart, no copying a command, no `dsh-skin` binary on PATH**. On failure the error message includes a terminal fallback command. Harness home follows the dsh launcher: an injected HOME maps to `<home>/.dsh`, else a trimmed non-empty `$DSH_HOME` is used directly, else `~/.dsh`. The target profile resolves as: explicit option, then `$DSH_SKIN_PROFILE`, then `$DSH_PROFILE`, then `process.cwd()` when it is a directory directly under `<harness-home>/profiles/<name>`, then `web`. Windows compatibility: the same resolution rules apply with no `$HOME` or fixed paths, and profile links fall back to directory junctions when symlink privileges are missing.
10
+ - Apply: the host half (`src/index.ts` + `src/routes.ts`) exposes `/api/skin-center/apply` and `/api/skin-center/bundle/<id>` (serves skin bundles on demand); clicking "Apply / 恢复默认" (Apply / Restore default) runs the embedded in-process `dsh-skin use` port (`src/skin-switch.ts`) server-side, writes `<harness-home>/profiles/<profile>/cordis.patch.yml`, and the DSH config watcher hot-loads it within seconds and the page auto-refreshes — **no dsh web restart, no copying a command, no `dsh-skin` binary on PATH**. On failure the error message includes a terminal fallback command. Harness home follows the dsh launcher: an injected HOME maps to `<home>/.dsh`, else a trimmed non-empty `$DSH_HOME` is used directly, else `~/.dsh`. The target profile resolves as: explicit option, then `$DSH_SKIN_PROFILE`, then `$DSH_PROFILE`, then `process.cwd()` when it is a directory directly under `<harness-home>/profiles/<name>`, then `web`. Windows compatibility: the same resolution rules apply with no `$HOME` or fixed paths, and profile links fall back to directory junctions when symlink privileges are missing.
11
11
 
12
12
  - Background controls: a background-occlusion slider (0–100%) veils the backdrop behind the panels for skins that paint one (blue-fantasy / whale-song), plus two per-state Gaussian-blur sliders (0–20 px) — 空对话 (blur when the conversation is empty) and 有对话 (blur once it has content). The active blur is applied through a fixed `backdrop-filter` element behind the shell; 0 disables it entirely (no element, no GPU cost). These apply only to skins that paint a backdrop; the official default has none.
13
13
 
@@ -56,7 +56,7 @@ skins/skin-center/
56
56
  - Failure semantics: when the bundle route 404s (skin not installed / `lib/client.js` not built) or the network fails, the script's error event fires, try-on reports a generic error and fully restores the active skin; no half skin is left between loading and restoring (the tryOn catch branch handles recovery).
57
57
  - Exit restore: first run the skin's disposer (withdraws attribute/chrome/favicon/title/background entirely), then `invalidate(package)` + remove the style tag, then restore the active skin's visual snapshot verbatim. Official-default try-on = the same withdrawal prescription but without mounting any skin; exit restores identically.
58
58
  - Active-skin detection: `window.__DSH_BOOT__.entries` only contains enabled entries, compared against the registry packages; no match means official default.
59
- - One-click apply: host `/api/skin-center/apply` runs the embedded port of `dsh-skin use <name>` / `use official` (the port is the sole authority for the managed section and the symlink). Paths are `<harness-home>/cordis.patch.yml` and `<harness-home>/profiles/<profile>/node_modules` with the home/profile resolution described above. When the active skin is itself installed as a bundle — listed in the profile manifest's `dsh.profile.bundles` or `dependencies` (the two channels the loader reconciles), or a registry `bundleWired` skin — the home layer writes only the mutual-exclusion `disabled: true` rows and leaves the insert to the bundle patch; anything else, including the skin-center's own resolvability symlinks, keeps the home insert row. The structural dir-probe fallback applies only when the profile manifest is missing/unreadable. The DSH daemon surface ships its own config watcher (`watchUserPatches` + config-only HMR); after the patch is written it hot-loads in seconds without a restart; a browser refresh picks up the new boot graph (client plugin-graph row changes are not within `dsh-client-hmr` semantics).
59
+ - One-click apply: host `/api/skin-center/apply` runs the embedded port of `dsh-skin use <name>` / `use official` (the port is the sole authority for the managed section and the symlink). Paths are `<harness-home>/profiles/<profile>/cordis.patch.yml` and `<harness-home>/profiles/<profile>/node_modules` with the home/profile resolution described above; legacy global managed rows are removed on the first switch so other profiles never load Web-only skins. When the active skin is itself installed as a bundle — listed in the profile manifest's `dsh.profile.bundles` or `dependencies` (the two channels the loader reconciles), or a registry `bundleWired` skin — the profile layer writes only the mutual-exclusion `disabled: true` rows and leaves the insert to the bundle patch; anything else, including the skin-center's own resolvability symlinks, keeps the profile insert row. The structural dir-probe fallback applies only when the profile manifest is missing/unreadable. The DSH daemon surface ships its own config watcher (`watchUserPatches` + config-only HMR); after the patch is written it hot-loads in seconds without a restart; a browser refresh picks up the new boot graph (client plugin-graph row changes are not within `dsh-client-hmr` semantics).
60
60
 
61
61
  ## Build (in-repo tsdown, no DSH checkout)
62
62
 
@@ -79,7 +79,7 @@ pnpm --filter @linxin666/dsh-client-ui-skin-center run bundle
79
79
  ln -sfn ~/code/dsh-web-ui/packages/skins/skin-center \
80
80
  ~/.dsh/profiles/node_modules/@linxin666/dsh-client-ui-skin-center
81
81
 
82
- # 2. add to ~/.dsh/cordis.patch.yml (outside the dsh-skin managed section, do not touch that section):
82
+ # 2. add to ~/.dsh/profiles/web/cordis.patch.yml (outside the dsh-skin managed section, do not touch that section):
83
83
  # - insert:
84
84
  # - id: ui-skin-center
85
85
  # name: '@linxin666/dsh-client-ui-skin-center'
package/README.zh.md CHANGED
@@ -7,7 +7,7 @@
7
7
  - 列表:展示「官方默认」+ 仓库里全部皮肤(qq98 / ths / xp / blue-fantasy / dragon-heir / minecraft)的名称、tagline、强调色;当前激活的目标带 Active 标记。
8
8
  - 试穿:点击「Try on」后按需加载该皮肤的 client bundle——host 路由 `/api/skin-center/bundle/<id>` 以同源 script 提供 `lib/client.js`(内核加载插件的同一机制),factory 注册到页面自己的 `window.__ModuleLoader__`,`window.__DSH_MODULES__.import` 物化(不是模拟器、不用 eval),chrome 立即生效;亮/暗切换走官方 theme 服务;「Exit try-on」完全还原——当前皮肤的样式、DOM、favicon、标题、body 内联样式全部恢复。「官方默认」也可试穿:点一下皮肤立即收回、回到官方外观预览。
9
9
  - 互斥:试穿期间会按配方暂时收回当前激活皮肤的视觉写面(body 属性、背景内联样式、chrome 子节点、xp 的 footer taskbar),退出后原样恢复;同一时刻页面上只有一套皮肤。
10
- - 应用:host 半区(`src/index.ts` + `src/routes.ts`)暴露 `/api/skin-center/apply` 与 `/api/skin-center/bundle/<id>`(按需提供皮肤 bundle),点击「Apply / 恢复默认」即在服务端执行内嵌的 `dsh-skin use` 进程内移植版(`src/skin-switch.ts`),写入 `<harness-home>/cordis.patch.yml` 后由 DSH 配置 watcher 秒级热载入,页面自动刷新生效——**无需重启 dsh web,无需复制命令,也不要求 PATH 上有 `dsh-skin` 二进制**。应用失败时错误提示里附带终端兜底命令。harness home 与 dsh 启动器一致:注入的 HOME 映射为 `<home>/.dsh`,否则优先使用去除首尾空白后非空的 `$DSH_HOME`(直接使用,不再追加后缀),最后回退到 `~/.dsh`。目标 profile 依次取:显式选项、`$DSH_SKIN_PROFILE`、`$DSH_PROFILE`、`process.cwd()` 直接位于 `<harness-home>/profiles/<name>` 下时的 `<name>`,最后 `web`。Windows 兼容性:同一套解析规则不依赖 `$HOME` 与固定路径,符号链接权限不足时 profile 链接回退为目录 junction。
10
+ - 应用:host 半区(`src/index.ts` + `src/routes.ts`)暴露 `/api/skin-center/apply` 与 `/api/skin-center/bundle/<id>`(按需提供皮肤 bundle),点击「Apply / 恢复默认」即在服务端执行内嵌的 `dsh-skin use` 进程内移植版(`src/skin-switch.ts`),写入 `<harness-home>/profiles/<profile>/cordis.patch.yml` 后由 DSH 配置 watcher 秒级热载入,页面自动刷新生效——**无需重启 dsh web,无需复制命令,也不要求 PATH 上有 `dsh-skin` 二进制**。应用失败时错误提示里附带终端兜底命令。harness home 与 dsh 启动器一致:注入的 HOME 映射为 `<home>/.dsh`,否则优先使用去除首尾空白后非空的 `$DSH_HOME`(直接使用,不再追加后缀),最后回退到 `~/.dsh`。目标 profile 依次取:显式选项、`$DSH_SKIN_PROFILE`、`$DSH_PROFILE`、`process.cwd()` 直接位于 `<harness-home>/profiles/<name>` 下时的 `<name>`,最后 `web`。Windows 兼容性:同一套解析规则不依赖 `$HOME` 与固定路径,符号链接权限不足时 profile 链接回退为目录 junction。
11
11
 
12
12
  - 背景控制:背景遮挡滑块(0–100%)为带背景插画的皮肤(blue-fantasy / whale-song)背后的背景加遮罩;另有两条按对话状态区分的背景高斯模糊滑块(0–20 px)——「空对话」在对话为空时生效、「有对话」在出现内容后生效。生效的模糊通过 shell 背后的一个固定 `backdrop-filter` 元素施加;设为 0 即完全关闭(无元素、无 GPU 开销)。这些控件仅对带背景插画的皮肤可见有效;官方默认无背景图。
13
13
 
@@ -56,7 +56,7 @@ skins/skin-center/
56
56
  - 失败语义:bundle 路由 404(皮肤未安装 / `lib/client.js` 未构建)或网络失败时,script 的 error 事件触发,试穿报通用错误并完整还原激活皮肤;加载与还原之间不会留下半套皮肤(tryOn 的 catch 分支负责恢复)。
57
57
  - 退出还原:先跑皮肤的 disposer(属性/chrome/favicon/标题/背景全撤回),再 `invalidate(package)` + 删 style 标签,最后把激活皮肤的视觉快照原样恢复。官方默认试穿 = 同一套收回配方但不挂载任何皮肤,退出同样原样恢复。
58
58
  - 激活皮肤检测:`window.__DSH_BOOT__.entries` 只含启用条目,与注册表 package 比对;无匹配即官方默认。
59
- - 一键应用:host `/api/skin-center/apply` 执行内嵌的 `dsh-skin use <name>` / `use official` 移植版(该移植版是 managed 区段与 symlink 的唯一权威)。路径为 `<harness-home>/cordis.patch.yml` 与 `<harness-home>/profiles/<profile>/node_modules`,home/profile 按上文规则解析。当激活皮肤自身已作为 bundle 安装——出现在 profile manifest 的 `dsh.profile.bundles` 或 `dependencies` 中(loader 仅对这两条通道做 patch 行归并),或注册表标记 `bundleWired`——home 层只写互斥的 `disabled: true` 行,insert 留给 bundle patch;其余情况(包括 skin-center 自建的可解析 symlink)都保留 home 层 insert 行。结构目录探测仅在 profile manifest 缺失/不可读时兜底。DSH 长驻表面自带配置 watcher(`watchUserPatches` + config-only HMR),patch 写入后数秒热载入、无需重启;浏览器刷新页面取新 boot 图即生效(client 插件图行增删不在 `dsh-client-hmr` 语义内)。
59
+ - 一键应用:host `/api/skin-center/apply` 执行内嵌的 `dsh-skin use <name>` / `use official` 移植版(该移植版是 managed 区段与 symlink 的唯一权威)。路径为 `<harness-home>/profiles/<profile>/cordis.patch.yml` 与 `<harness-home>/profiles/<profile>/node_modules`,home/profile 按上文规则解析;首次切换会清理旧的全局 managed 行,避免其他 profile 加载 Web 专用皮肤。当激活皮肤自身已作为 bundle 安装——出现在 profile manifest 的 `dsh.profile.bundles` 或 `dependencies` 中(loader 仅对这两条通道做 patch 行归并),或注册表标记 `bundleWired`——profile 层只写互斥的 `disabled: true` 行,insert 留给 bundle patch;其余情况(包括 skin-center 自建的可解析 symlink)都保留 profile 层 insert 行。结构目录探测仅在 profile manifest 缺失/不可读时兜底。DSH 长驻表面自带配置 watcher(`watchUserPatches` + config-only HMR),patch 写入后数秒热载入、无需重启;浏览器刷新页面取新 boot 图即生效(client 插件图行增删不在 `dsh-client-hmr` 语义内)。
60
60
 
61
61
  ## 构建(仓库内 tsdown,无需 DSH checkout)
62
62
 
@@ -79,7 +79,7 @@ pnpm --filter @linxin666/dsh-client-ui-skin-center run bundle
79
79
  ln -sfn ~/code/dsh-web-ui/packages/skins/skin-center \
80
80
  ~/.dsh/profiles/node_modules/@linxin666/dsh-client-ui-skin-center
81
81
 
82
- # 2. ~/.dsh/cordis.patch.yml 增加(放在 dsh-skin managed 段之外,勿动该段):
82
+ # 2. ~/.dsh/profiles/web/cordis.patch.yml 增加(放在 dsh-skin managed 段之外,勿动该段):
83
83
  # - insert:
84
84
  # - id: ui-skin-center
85
85
  # name: '@linxin666/dsh-client-ui-skin-center'
package/lib/client.js CHANGED
@@ -204,6 +204,45 @@ window.__ModuleLoader__.load({
204
204
  "bodyAttr": "data-dsh-miku",
205
205
  "package": "@linxin666/dsh-client-ui-skin-miku",
206
206
  "order": 10
207
+ },
208
+ {
209
+ "id": "whale-mom",
210
+ "name": "鲸鱼妈妈",
211
+ "nameEn": "Whale Mom",
212
+ "author": "dsh-web-ui",
213
+ "tagline": "深海鲸鱼妈妈与幼崽的画作 · 全透面板由背景遮挡驱动 · 金线点缀",
214
+ "description": "dsh web ui 的鲸鱼妈妈主题皮肤:深海鲸鱼妈妈与幼崽们的氛围画作铺满视口,面板全透(透明度由设置-皮肤中心的背景遮挡滑杆驱动,侧边栏基准透明度可用 --dsw-skin-sidebar-alpha 微调),深蓝 / 奶油 / 金色配色贯穿全局,暗色是深海夜航。",
215
+ "tags": [
216
+ "ocean",
217
+ "whale",
218
+ "mother",
219
+ "translucent",
220
+ "gold"
221
+ ],
222
+ "accent": "#d9a53c",
223
+ "bodyAttr": "data-dsh-whale-mom",
224
+ "package": "@linxin666/dsh-client-ui-skin-whale-mom",
225
+ "order": 11
226
+ },
227
+ {
228
+ "id": "matrix",
229
+ "name": "Matrix 骇客帝国",
230
+ "nameEn": "Matrix",
231
+ "author": "contributed (seanchen)",
232
+ "tagline": "深夜护眼 · 墨绿暗色 · 数字雨",
233
+ "description": "深夜卧室场景的护眼暗色皮肤:近黑墨绿背景、墨绿等宽字体、低透明度数字雨。强制暗色(不跟随系统主题),整体低亮度,长时间使用不刺眼、不打扰家人睡觉。",
234
+ "tags": [
235
+ "matrix",
236
+ "dark",
237
+ "green",
238
+ "hacker",
239
+ "night",
240
+ "eye-care"
241
+ ],
242
+ "accent": "#00e676",
243
+ "bodyAttr": "data-dsh-matrix",
244
+ "package": "@linxin666/dsh-client-ui-skin-matrix",
245
+ "order": 12
207
246
  }
208
247
  ];
209
248
  //#endregion
@@ -260,9 +299,11 @@ window.__ModuleLoader__.load({
260
299
  * known skin chrome body children (title/status bars marked `data-skin-chrome`
261
300
  * or carrying the skin's body attribute, leaving other plugins' portals and
262
301
  * toasts in place), and neutralize known global-rule leaks (xp's sidebar
263
- * taskbar/start). Everything is snapshotted and restored on exit in original
264
- * order. The active skin's own fiber is never touched, so exiting try-on
265
- * returns the page to exactly the pre-try-on state.
302
+ * taskbar/start; matrix's full-viewport rain canvas, which has no scoping
303
+ * attribute — matrix's forced-dark observer goes inert on its own once the
304
+ * body attribute is retracted). Everything is snapshotted and restored on
305
+ * exit in original order. The active skin's own fiber is never touched, so
306
+ * exiting try-on returns the page to exactly the pre-try-on state.
266
307
  *
267
308
  * A ghost MutationObserver may survive retraction (blue-fantasy re-writes
268
309
  * its backdrop on theme flips), so during try-on a neutralizing observer
@@ -282,7 +323,10 @@ window.__ModuleLoader__.load({
282
323
  * the skin touches, so detaching chrome cannot remove them). Matched by
283
324
  * css-module class substring, which is stable across rebuilds.
284
325
  */
285
- const NEUTRALIZE_CSS = { xp: [`[data-pane='sidebar'] [class*='xpTaskbar']{background:transparent!important;border-top:none!important;box-shadow:none!important}`, `[data-pane='sidebar'] [class*='xpStart']{display:none!important}`].join("") };
326
+ const NEUTRALIZE_CSS = {
327
+ xp: [`[data-pane='sidebar'] [class*='xpTaskbar']{background:transparent!important;border-top:none!important;box-shadow:none!important}`, `[data-pane='sidebar'] [class*='xpStart']{display:none!important}`].join(""),
328
+ matrix: [`[data-plugin='dsh-matrix-skin']{display:none!important}`].join("")
329
+ };
286
330
  /** Host base path of the skin bundle route (registered by src/routes.ts). */
287
331
  const BUNDLE_ROUTE = "/api/skin-center/bundle";
288
332
  /**
@@ -583,7 +627,7 @@ window.__ModuleLoader__.load({
583
627
  };
584
628
  //#endregion
585
629
  //#region \0dsh-css:packages/skins/skin-center/src/client/skin-center.module.css.mjs
586
- const css = "body[data-dsh-skin-center] .eDzMgW_sectionList{margin:0;padding:0;list-style:none}body[data-dsh-skin-center] .eDzMgW_pluginCard{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;list-style:none;transition:border-color .16s,background .16s}body[data-dsh-skin-center] .eDzMgW_pluginCard:hover{border-color:var(--dsw-alias-label-dimmed)}body[data-dsh-skin-center] .eDzMgW_cardHeaderStatic{align-items:center;gap:12px;width:100%;padding:14px 16px;display:flex}body[data-dsh-skin-center] .eDzMgW_headText{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}body[data-dsh-skin-center] .eDzMgW_pluginName{color:var(--dsw-alias-label-primary);align-items:baseline;gap:8px;font-size:15px;font-weight:600;line-height:1.4;display:flex}body[data-dsh-skin-center] .eDzMgW_cardDescription{color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.5}body[data-dsh-skin-center] .eDzMgW_cardBody{border-top:1px solid var(--dsw-alias-border-l2);flex-direction:column;gap:12px;margin:0 16px;padding:12px 0 8px;display:flex}body[data-dsh-skin-center] .eDzMgW_head{flex-direction:column;gap:6px;display:flex}body[data-dsh-skin-center] .eDzMgW_titleBadge{color:var(--dsw-alias-label-secondary,#6b7280);font-size:11px;font-weight:500}body[data-dsh-skin-center] .eDzMgW_intro{color:var(--dsw-alias-label-secondary,#6b7280);font-size:12.5px;line-height:1.55}body[data-dsh-skin-center] .eDzMgW_themeRow{align-items:center;gap:8px;margin-top:2px;display:flex}body[data-dsh-skin-center] .eDzMgW_themeLabel{color:var(--dsw-alias-label-secondary,#6b7280);margin-right:2px;font-size:12px}body[data-dsh-skin-center] .eDzMgW_themeButton{border:1px solid var(--dsw-alias-border-l3,#cbd5e1);background:var(--dsw-alias-bg-layer-2,#fff);color:var(--dsw-alias-label-primary,#172a45);cursor:pointer;border-radius:6px;padding:5px 10px;font-size:12px;line-height:1;transition:background .12s,border-color .12s,color .12s}body[data-dsh-skin-center] .eDzMgW_themeButton:hover{border-color:var(--dsw-alias-border-l4,#94a3b8)}body[data-dsh-skin-center] .eDzMgW_themeButton:active{border-color:var(--dsw-alias-brand-primary,#2b7cd9);background:var(--dsw-alias-button-primary-dimmed,#e8f1fc);color:var(--dsw-alias-brand-primary,#1e63b8)}body[data-dsh-skin-center] .eDzMgW_themeButton:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#2b7cd9);outline-offset:2px}body[data-dsh-skin-center] .eDzMgW_themeButtonActive{border-color:var(--dsw-alias-brand-primary,#2b7cd9);background:var(--dsw-alias-button-primary-dimmed,#e8f1fc);color:var(--dsw-alias-brand-primary,#1e63b8)}body[data-dsh-skin-center] .eDzMgW_list{flex-direction:column;gap:10px;display:flex}body[data-dsh-skin-center] .eDzMgW_card{border:1px solid var(--dsw-alias-border-l1,#e2e8f0);background:var(--dsw-alias-bg-layer-2,#fff);border-radius:10px;flex-direction:column;gap:8px;padding:12px 14px;display:flex}body[data-dsh-skin-center] .eDzMgW_cardHead{align-items:center;gap:10px;min-width:0;display:flex}body[data-dsh-skin-center] .eDzMgW_swatch{width:14px;height:14px;box-shadow:inset 0 0 0 1px var(--dsw-alias-border-l4,#0f172a1f);border-radius:50%;flex:none}body[data-dsh-skin-center] .eDzMgW_cardName{text-overflow:ellipsis;white-space:nowrap;min-width:0;font-size:13.5px;font-weight:600;overflow:hidden}body[data-dsh-skin-center] .eDzMgW_cardTagline{color:var(--dsw-alias-label-secondary,#6b7280);font-size:12px;line-height:1.45}body[data-dsh-skin-center] .eDzMgW_badge{letter-spacing:.02em;border-radius:999px;flex:none;min-width:0;margin-left:auto;padding:2px 8px;font-size:11px;font-weight:600}body[data-dsh-skin-center] .eDzMgW_badgeActive{color:var(--dsw-alias-state-success-primary,#0f6b3a);background:var(--dsw-alias-state-success-tertiary,#dcf3e5)}body[data-dsh-skin-center] .eDzMgW_badgeTrying{color:var(--dsw-alias-brand-primary,#1e63b8);background:var(--dsw-alias-button-primary-dimmed,#e2edfc)}body[data-dsh-skin-center] .eDzMgW_actions{flex-wrap:wrap;align-items:center;gap:8px;display:flex}body[data-dsh-skin-center] .eDzMgW_button{border:1px solid var(--dsw-alias-border-l3,#cbd5e1);background:var(--dsw-alias-bg-layer-2,#fff);color:var(--dsw-alias-label-primary,#172a45);cursor:pointer;border-radius:7px;padding:6px 12px;font-size:12px;line-height:1;transition:background .12s,border-color .12s,color .12s}body[data-dsh-skin-center] .eDzMgW_button:hover:not(:disabled){border-color:var(--dsw-alias-brand-primary,#2b7cd9);color:var(--dsw-alias-brand-primary,#1e63b8)}body[data-dsh-skin-center] .eDzMgW_button:active:not(:disabled){border-color:var(--dsw-alias-button-primary-hover,#1e63b8);background:var(--dsw-alias-button-primary-dimmed,#e8f1fc);color:var(--dsw-alias-brand-primary,#1e63b8)}body[data-dsh-skin-center] .eDzMgW_button:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#2b7cd9);outline-offset:2px}body[data-dsh-skin-center] .eDzMgW_buttonPrimary{border-color:var(--dsw-alias-brand-primary,#2b7cd9);background:var(--dsw-alias-button-primary-fill,#2b7cd9);color:var(--dsw-alias-label-primary-foreground,#fff)}body[data-dsh-skin-center] .eDzMgW_buttonPrimary:hover:not(:disabled){border-color:var(--dsw-alias-button-primary-hover,#1e63b8);background:var(--dsw-alias-button-primary-hover,#1e63b8);color:var(--dsw-alias-label-primary-foreground,#fff)}body[data-dsh-skin-center] .eDzMgW_buttonPrimary:active:not(:disabled),body[data-dsh-skin-center] .eDzMgW_buttonPrimary:focus-visible:not(:disabled){border-color:var(--dsw-alias-button-primary-hover,#1e63b8);background:var(--dsw-alias-button-primary-hover,#1e63b8)}body[data-dsh-skin-center] .eDzMgW_buttonGhost{background:0 0;border-color:#0000}body[data-dsh-skin-center] .eDzMgW_button:disabled{opacity:.55;cursor:default}body[data-dsh-skin-center] .eDzMgW_error{color:var(--dsw-alias-state-error-primary,#b42318);font-size:12px}body[data-dsh-skin-center] .eDzMgW_enableRow{flex-wrap:wrap;align-items:center;gap:8px;padding:8px 0;display:flex}body[data-dsh-skin-center] .eDzMgW_enableLabel{color:var(--dsw-alias-label-primary,#172a45);font-size:12.5px;font-weight:600}body[data-dsh-skin-center] .eDzMgW_enableHint{min-width:100%;color:var(--dsw-alias-label-secondary,#6b7280);flex:1;margin:0;font-size:12px;line-height:1.5}body[data-dsh-skin-center] .eDzMgW_switch{border:1px solid var(--dsw-alias-border-l3,#cbd5e1);background:var(--dsw-alias-bg-layer-3,#e2e8f0);cursor:pointer;border-radius:999px;flex:none;align-items:center;width:40px;height:22px;padding:2px;transition:background .12s,border-color .12s;display:inline-flex;position:relative}body[data-dsh-skin-center] .eDzMgW_switchOn{border-color:var(--dsw-alias-brand-primary,#2b7cd9);background:var(--dsw-alias-brand-primary,#2b7cd9)}body[data-dsh-skin-center] .eDzMgW_switch:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#2b7cd9);outline-offset:2px}body[data-dsh-skin-center] .eDzMgW_switchThumb{background:var(--dsw-alias-label-primary-foreground,#fff);width:18px;height:18px;box-shadow:0 0 0 1px var(--dsw-alias-border-l4,#0f172a1f);border-radius:50%;transition:transform .12s;display:block;transform:translate(0)}body[data-dsh-skin-center] .eDzMgW_switchOn .eDzMgW_switchThumb{transform:translate(18px)}body[data-dsh-skin-center] .eDzMgW_offNote{color:var(--dsw-alias-label-secondary,#6b7280);margin:0;font-size:12.5px;line-height:1.5}body[data-dsh-skin-center] .eDzMgW_backgroundRow{flex-direction:column;gap:6px;padding:8px 0;display:flex}body[data-dsh-skin-center] .eDzMgW_backgroundHead{align-items:center;gap:8px;display:flex}body[data-dsh-skin-center] .eDzMgW_backgroundLabel{color:var(--dsw-alias-label-primary,#172a45);font-size:12.5px;font-weight:600}body[data-dsh-skin-center] .eDzMgW_backgroundValue{font-variant-numeric:tabular-nums;color:var(--dsw-alias-brand-primary,#2b7cd9);flex:none;margin-left:auto;font-size:12px}body[data-dsh-skin-center] .eDzMgW_backgroundRange{background:var(--dsw-alias-bg-layer-3,#e2e8f0);-webkit-appearance:none;appearance:none;cursor:pointer;border-radius:999px;width:100%;height:4px;margin:0}body[data-dsh-skin-center] .eDzMgW_backgroundRange::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;border:2px solid var(--dsw-alias-label-primary-foreground,#fff);background:var(--dsw-alias-brand-primary,#2b7cd9);width:14px;height:14px;box-shadow:0 0 0 1px var(--dsw-alias-border-l4,#0f172a1f);cursor:pointer;border-radius:50%}body[data-dsh-skin-center] .eDzMgW_backgroundRange::-moz-range-thumb{border:2px solid var(--dsw-alias-label-primary-foreground,#fff);background:var(--dsw-alias-brand-primary,#2b7cd9);width:12px;height:12px;box-shadow:0 0 0 1px var(--dsw-alias-border-l4,#0f172a1f);cursor:pointer;border-radius:50%}body[data-dsh-skin-center] .eDzMgW_backgroundRange:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#2b7cd9);outline-offset:2px}body[data-dsh-skin-center] .eDzMgW_backgroundHint{color:var(--dsw-alias-label-secondary,#6b7280);font-size:12px;line-height:1.5}body[data-dsh-skin-center] .eDzMgW_backgroundHintMuted{color:var(--dsw-alias-label-tertiary,#9aa4b5);font-size:12px;line-height:1.5}@media (prefers-reduced-motion:reduce){body[data-dsh-skin-center] .eDzMgW_pluginCard,body[data-dsh-skin-center] .eDzMgW_themeButton,body[data-dsh-skin-center] .eDzMgW_button,body[data-dsh-skin-center] .eDzMgW_switch,body[data-dsh-skin-center] .eDzMgW_switchThumb{transition:none}}";
630
+ const css = "body[data-dsh-skin-center] .eDzMgW_sectionList{margin:0;padding:0;list-style:none}body[data-dsh-skin-center] .eDzMgW_pluginCard{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;list-style:none;transition:border-color .16s,background .16s}body[data-dsh-skin-center] .eDzMgW_pluginCard:hover{border-color:var(--dsw-alias-label-dimmed)}body[data-dsh-skin-center] .eDzMgW_cardHeaderStatic{align-items:center;gap:12px;width:100%;padding:14px 16px;display:flex}body[data-dsh-skin-center] .eDzMgW_headText{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}body[data-dsh-skin-center] .eDzMgW_pluginName{color:var(--dsw-alias-label-primary);align-items:baseline;gap:8px;font-size:15px;font-weight:600;line-height:1.4;display:flex}body[data-dsh-skin-center] .eDzMgW_cardDescription{color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.5}body[data-dsh-skin-center] .eDzMgW_cardBody{border-top:1px solid var(--dsw-alias-border-l2);flex-direction:column;gap:12px;margin:0 16px;padding:12px 0 8px;display:flex}body[data-dsh-skin-center] .eDzMgW_head{flex-direction:column;gap:6px;display:flex}body[data-dsh-skin-center] .eDzMgW_titleBadge{color:var(--dsw-alias-label-secondary,#6b7280);font-size:11px;font-weight:500}body[data-dsh-skin-center] .eDzMgW_intro{color:var(--dsw-alias-label-secondary,#6b7280);font-size:12.5px;line-height:1.55}body[data-dsh-skin-center] .eDzMgW_themeRow{align-items:center;gap:8px;margin-top:2px;display:flex}body[data-dsh-skin-center] .eDzMgW_themeLabel{color:var(--dsw-alias-label-secondary,#6b7280);margin-right:2px;font-size:12px}body[data-dsh-skin-center] .eDzMgW_themeButton{border:1px solid var(--dsw-alias-border-l3,#cbd5e1);background:var(--dsw-alias-bg-layer-2,#fff);color:var(--dsw-alias-label-primary,#172a45);cursor:pointer;border-radius:6px;padding:5px 10px;font-size:12px;line-height:1;transition:background .12s,border-color .12s,color .12s}body[data-dsh-skin-center] .eDzMgW_themeButton:hover{border-color:var(--dsw-alias-border-l4,#94a3b8)}body[data-dsh-skin-center] .eDzMgW_themeButton:active{border-color:var(--dsw-alias-brand-primary,#2b7cd9);background:var(--dsw-alias-button-primary-dimmed,#e8f1fc);color:var(--dsw-alias-brand-primary,#1e63b8)}body[data-dsh-skin-center] .eDzMgW_themeButton:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#2b7cd9);outline-offset:2px}body[data-dsh-skin-center] .eDzMgW_themeButtonActive{border-color:var(--dsw-alias-brand-primary,#2b7cd9);background:var(--dsw-alias-button-primary-dimmed,#e8f1fc);color:var(--dsw-alias-brand-primary,#1e63b8)}body[data-dsh-skin-center] .eDzMgW_list{flex-direction:column;gap:10px;display:flex}body[data-dsh-skin-center] .eDzMgW_card{border:1px solid var(--dsw-alias-border-l1,#e2e8f0);background:var(--dsw-alias-bg-layer-2,#fff);border-radius:10px;flex-direction:column;gap:8px;padding:12px 14px;display:flex}body[data-dsh-skin-center] .eDzMgW_cardHead{align-items:center;gap:10px;min-width:0;display:flex}body[data-dsh-skin-center] .eDzMgW_swatch{width:14px;height:14px;box-shadow:inset 0 0 0 1px var(--dsw-alias-border-l4,#0f172a1f);border-radius:50%;flex:none}body[data-dsh-skin-center] .eDzMgW_cardName{text-overflow:ellipsis;white-space:nowrap;min-width:0;font-size:13.5px;font-weight:600;overflow:hidden}body[data-dsh-skin-center] .eDzMgW_cardTagline{color:var(--dsw-alias-label-secondary,#6b7280);font-size:12px;line-height:1.45}body[data-dsh-skin-center] .eDzMgW_badge{letter-spacing:.02em;border-radius:999px;flex:none;min-width:0;margin-left:auto;padding:2px 8px;font-size:11px;font-weight:600}body[data-dsh-skin-center] .eDzMgW_badgeActive{color:var(--dsw-alias-state-success-primary,#0f6b3a);background:var(--dsw-alias-state-success-tertiary,#dcf3e5)}body[data-dsh-skin-center] .eDzMgW_badgeTrying{color:var(--dsw-alias-brand-primary,#1e63b8);background:var(--dsw-alias-button-primary-dimmed,#e2edfc)}body[data-dsh-skin-center] .eDzMgW_actions{flex-wrap:wrap;align-items:center;gap:8px;display:flex}body[data-dsh-skin-center] .eDzMgW_button{border:1px solid var(--dsw-alias-border-l3,#cbd5e1);background:var(--dsw-alias-bg-layer-2,#fff);color:var(--dsw-alias-label-primary,#172a45);cursor:pointer;border-radius:7px;padding:6px 12px;font-size:12px;line-height:1;transition:background .12s,border-color .12s,color .12s}body[data-dsh-skin-center] .eDzMgW_button:hover:not(:disabled){border-color:var(--dsw-alias-brand-primary,#2b7cd9);color:var(--dsw-alias-brand-primary,#1e63b8)}body[data-dsh-skin-center] .eDzMgW_button:active:not(:disabled){border-color:var(--dsw-alias-button-primary-hover,#1e63b8);background:var(--dsw-alias-button-primary-dimmed,#e8f1fc);color:var(--dsw-alias-brand-primary,#1e63b8)}body[data-dsh-skin-center] .eDzMgW_button:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#2b7cd9);outline-offset:2px}body[data-dsh-skin-center] .eDzMgW_buttonPrimary{border-color:var(--dsw-alias-brand-primary,#2b7cd9);background:var(--dsw-alias-button-primary-fill,#2b7cd9);color:var(--dsw-alias-label-primary-foreground,#fff)}body[data-dsh-skin-center] .eDzMgW_buttonPrimary:hover:not(:disabled){border-color:var(--dsw-alias-button-primary-hover,#1e63b8);background:var(--dsw-alias-button-primary-hover,#1e63b8);color:var(--dsw-alias-label-primary-foreground,#fff)}body[data-dsh-skin-center] .eDzMgW_buttonPrimary:active:not(:disabled),body[data-dsh-skin-center] .eDzMgW_buttonPrimary:focus-visible:not(:disabled){border-color:var(--dsw-alias-button-primary-hover,#1e63b8);background:var(--dsw-alias-button-primary-hover,#1e63b8)}body[data-dsh-skin-center] .eDzMgW_buttonGhost{background:0 0;border-color:#0000}body[data-dsh-skin-center] .eDzMgW_button:disabled{opacity:.55;cursor:default}body[data-dsh-skin-center] .eDzMgW_error{color:var(--dsw-alias-state-error-primary,#b42318);font-size:12px}body[data-dsh-skin-center] .eDzMgW_enableRow{flex-wrap:wrap;align-items:center;gap:8px;padding:8px 0;display:flex}body[data-dsh-skin-center] .eDzMgW_enableLabel{color:var(--dsw-alias-label-primary,#172a45);font-size:12.5px;font-weight:600}body[data-dsh-skin-center] .eDzMgW_enableHint{min-width:100%;color:var(--dsw-alias-label-secondary,#6b7280);flex:1;margin:0;font-size:12px;line-height:1.5}body[data-dsh-skin-center] .eDzMgW_switch{border:1px solid var(--dsw-alias-border-l3,#cbd5e1);background:var(--dsw-alias-bg-layer-3,#e2e8f0);cursor:pointer;border-radius:999px;flex:none;align-items:center;width:40px;height:22px;padding:2px;transition:background .12s,border-color .12s;display:inline-flex;position:relative}body[data-dsh-skin-center] .eDzMgW_switchOn{border-color:var(--dsw-alias-brand-primary,#2b7cd9);background:var(--dsw-alias-brand-primary,#2b7cd9)}body[data-dsh-skin-center] .eDzMgW_switch:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#2b7cd9);outline-offset:2px}body[data-dsh-skin-center] .eDzMgW_switchThumb{background:var(--dsw-alias-label-primary-foreground,#fff);width:18px;height:18px;box-shadow:0 0 0 1px var(--dsw-alias-border-l4,#0f172a1f);border-radius:50%;transition:transform .12s;display:block;transform:translate(0)}body[data-dsh-skin-center] .eDzMgW_switchOn .eDzMgW_switchThumb{transform:translate(18px)}body[data-dsh-skin-center] .eDzMgW_offNote{color:var(--dsw-alias-label-secondary,#6b7280);margin:0;font-size:12.5px;line-height:1.5}body[data-dsh-skin-center] .eDzMgW_backgroundRow{flex-direction:column;gap:6px;padding:8px 0;display:flex}body[data-dsh-skin-center] .eDzMgW_backgroundHead{align-items:center;gap:8px;display:flex}body[data-dsh-skin-center] .eDzMgW_backgroundLabel{color:var(--dsw-alias-label-primary,#172a45);font-size:12.5px;font-weight:600}body[data-dsh-skin-center] .eDzMgW_backgroundValue{font-variant-numeric:tabular-nums;color:var(--dsw-alias-brand-primary,#2b7cd9);flex:none;margin-left:auto;font-size:12px}body[data-dsh-skin-center] .eDzMgW_backgroundRange{background:var(--dsw-alias-label-tertiary,#9aa4b5);background:color-mix(in srgb, var(--dsw-alias-label-tertiary,#9aa4b5) 45%, transparent);width:100%;height:4px;box-shadow:0 0 0 1px var(--dsw-alias-border-l3,#cbd5e1);-webkit-appearance:none;appearance:none;cursor:pointer;border-radius:999px;margin:0}body[data-dsh-skin-center] .eDzMgW_backgroundRange::-webkit-slider-runnable-track{background:var(--dsw-alias-bg-layer-3,#e2e8f0);border-radius:999px;height:4px}body[data-dsh-skin-center] .eDzMgW_backgroundRange::-moz-range-track{background:var(--dsw-alias-bg-layer-3,#e2e8f0);border-radius:999px;height:4px}body[data-dsh-skin-center] .eDzMgW_backgroundRange::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;border:2px solid var(--dsw-alias-label-primary-foreground,#fff);background:var(--dsw-alias-brand-primary,#2b7cd9);width:14px;height:14px;box-shadow:0 0 0 1px var(--dsw-alias-border-l4,#0f172a1f);cursor:pointer;border-radius:50%}body[data-dsh-skin-center] .eDzMgW_backgroundRange::-moz-range-thumb{border:2px solid var(--dsw-alias-label-primary-foreground,#fff);background:var(--dsw-alias-brand-primary,#2b7cd9);width:12px;height:12px;box-shadow:0 0 0 1px var(--dsw-alias-border-l4,#0f172a1f);cursor:pointer;border-radius:50%}body[data-dsh-skin-center] .eDzMgW_backgroundRange:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#2b7cd9);outline-offset:2px}body[data-dsh-skin-center] .eDzMgW_backgroundHint{color:var(--dsw-alias-label-secondary,#6b7280);font-size:12px;line-height:1.5}body[data-dsh-skin-center] .eDzMgW_backgroundHintMuted{color:var(--dsw-alias-label-tertiary,#9aa4b5);font-size:12px;line-height:1.5}@media (prefers-reduced-motion:reduce){body[data-dsh-skin-center] .eDzMgW_pluginCard,body[data-dsh-skin-center] .eDzMgW_themeButton,body[data-dsh-skin-center] .eDzMgW_button,body[data-dsh-skin-center] .eDzMgW_switch,body[data-dsh-skin-center] .eDzMgW_switchThumb{transition:none}}";
587
631
  const tagId = "@linxin666/dsh-client-ui-skin-center/skin-center.module.css";
588
632
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
589
633
  const tag = document.createElement("style");
@@ -651,7 +695,11 @@ window.__ModuleLoader__.load({
651
695
  /** The apply target of the official stock-look card. */
652
696
  const OFFICIAL = "official";
653
697
  /** Skin ids that read the background-scrim variable and paint a backdrop. */
654
- const BACKDROP_SKIN_IDS = /* @__PURE__ */ new Set(["blue-fantasy", "whale-song"]);
698
+ const BACKDROP_SKIN_IDS = /* @__PURE__ */ new Set([
699
+ "blue-fantasy",
700
+ "whale-song",
701
+ "whale-mom"
702
+ ]);
655
703
  /**
656
704
  * Render the skin-center card: a static header naming the plugin, with the
657
705
  * always-visible skin list (official default + every installed skin; try-on /
@@ -1381,7 +1429,7 @@ window.__ModuleLoader__.load({
1381
1429
  applying: "Applying…",
1382
1430
  restore: "Restore",
1383
1431
  applyFailed: "Apply failed",
1384
- appliedUnconfirmed: "Applied, but the change has not been confirmed — refresh the page if the skin did not switch",
1432
+ appliedUnconfirmed: "Applied, but the change has not been confirmed — refresh the page in dev mode; packaged installs (DSH Desktop) need an app restart",
1385
1433
  theme: "Theme preview",
1386
1434
  themeLight: "Light",
1387
1435
  themeDark: "Dark",
@@ -1412,7 +1460,7 @@ window.__ModuleLoader__.load({
1412
1460
  applying: "应用中…",
1413
1461
  restore: "恢复默认",
1414
1462
  applyFailed: "应用失败",
1415
- appliedUnconfirmed: "已写入配置但尚未确认生效——若皮肤未切换请手动刷新页面",
1463
+ appliedUnconfirmed: "已写入配置但尚未确认生效——开发模式请刷新页面;打包版(DSH Desktop)需重启应用后生效",
1416
1464
  theme: "主题预览",
1417
1465
  themeLight: "亮色",
1418
1466
  themeDark: "暗色",
package/lib/client.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","names":["css"],"sources":["../../../packages/skins/skin-center/src/client/generated/skins.ts","../../../packages/skins/skin-center/src/client/manifest.ts","../../../packages/skins/skin-center/src/client/try-on.ts","../../../packages/skins/skin-center/src/client/SkinCenter.tsx","../../../packages/skins/skin-center/src/client/background.ts","../../../packages/skins/skin-center/src/client/locales.ts","../../../packages/skins/skin-center/src/client/index.ts"],"sourcesContent":["/**\n * AUTO-GENERATED by scripts/skin-center-bundles — do not edit by hand.\n * Skin registry for the GUI skin center: gallery metadata only. Client\n * bundle text is NOT embedded here — try-on fetches each skin's\n * prebuilt lib/client.js on demand from the host route\n * /api/skin-center/bundle/<id> (a same-origin script executed by the\n * page's own module loader; no eval, and the GUI never parses the\n * embedded art base64 at startup).\n * Regenerate with `node scripts/skin-center-bundles`.\n */\n\n/** One registry entry: gallery metadata for the skin center card. */\nexport interface SkinCenterEntry {\n /** Skin id (packages/skins/<id>/). */\n id: string\n /** Display name (zh). */\n name: string\n /** Display name (en). */\n nameEn: string\n /** One-line tagline. */\n tagline: string\n /** Accent color for the swatch. */\n accent: string\n /** Author credit. */\n author?: string\n /** Longer description. */\n description?: string\n /** Tag list. */\n tags?: string[]\n /** Body attribute that scopes the skin styles. */\n bodyAttr: string\n /** Plugin package name (the boot-graph entry id when active). */\n package: string\n /** Gallery sort order (packages/skins/<name>/skin.json). */\n order?: number\n}\n\n/** Every skin, ordered by packages/skins/<name>/skin.json `order`. */\nexport const SKIN_CENTER_ENTRIES: readonly SkinCenterEntry[] = [\n {\"id\":\"blue-fantasy\",\"name\":\"蓝色幻想\",\"nameEn\":\"Blue Fantasy\",\"author\":\"powerdog996(DreamSkin 社区)· dsh-web-ui 适配\",\"tagline\":\"鲸鱼插画背景 · periwinkle 靛蓝调色板 · 半透明面板\",\"description\":\"DreamSkin「DeepSeek-鲸鱼娘」Codex 桌面主题的 dsh 适配:鲸鱼插画背景垫在半透明面板之下,遮罩随亮/暗主题实时切换,periwinkle 靛蓝色调重映射到全部 dsh token。\",\"tags\":[\"dreamskin\",\"whale\",\"indigo\",\"art\",\"translucent\"],\"accent\":\"#4a5fa8\",\"bodyAttr\":\"data-dsh-blue-fantasy\",\"package\":\"@linxin666/dsh-client-ui-skin-blue-fantasy\",\"order\":1},\n\n {\"id\":\"whale-song\",\"name\":\"鲸吟\",\"nameEn\":\"Whale Song\",\"author\":\"dsh-web-ui\",\"tagline\":\"深海鲸语女神背景 · 冰蓝海洋调色板 · 金色细线点缀\",\"description\":\"《鲸吟》— 深海鲸语女神主题:无文字纯氛围背景画(蓝发女神与鲸群居左、冰蓝星座网格与金线点缀、右侧大量留白)垫在半透明面板之下,遮罩随亮/暗主题实时切换,冰蓝/浅青/深海军蓝/钴蓝冷色体系重映射到全部 dsh token,暗色变体为深海夜航调。\",\"tags\":[\"whale\",\"ocean\",\"ice-blue\",\"goddess\",\"art\",\"translucent\"],\"accent\":\"#4d8fd4\",\"bodyAttr\":\"data-dsh-whale-song\",\"package\":\"@linxin666/dsh-client-ui-skin-whale-song\",\"order\":2},\n\n {\"id\":\"harbor\",\"name\":\"夕港\",\"nameEn\":\"Harbor\",\"author\":\"moeblack\",\"tagline\":\"暮光蓝港 · 日落橙辉 · 半透明夜色面板\",\"description\":\"《夕港》黄昏港口主题:动漫少女黄昏港口背景(暮光蓝天空渐入日落橙)垫在半透明面板之下,遮罩随亮/暗主题实时切换,深暮蓝 #141a2e 底与日落橙 #ff9d5c 主色重映射到 dsh token,亮色是薄暮纱、暗色是深海夜航纱,同一幅画两种读法。\",\"tags\":[\"harbor\",\"dusk\",\"twilight\",\"sunset\",\"amber\",\"art\",\"translucent\"],\"accent\":\"#ff9d5c\",\"bodyAttr\":\"data-dsh-harbor\",\"package\":\"@linxin666/dsh-client-ui-skin-harbor\",\"order\":3},\n\n {\"id\":\"qq98\",\"name\":\"QQ2008 怀旧版\",\"nameEn\":\"QQ2008 Retro\",\"author\":\"dsh-web-ui\",\"tagline\":\"水晶蓝桌面 · 玻璃深蓝标题栏 · 戴围巾企鹅\",\"description\":\"dsh web ui 家族收录的第一个皮肤:QQ2008 水晶蓝年代。深蓝渐变桌面、玻璃质感标题栏、浅蓝状态栏和圆角高光控件,配一只戴围巾的企鹅。\",\"tags\":[\"retro\",\"qq\",\"2008\",\"crystal-blue\",\"nostalgia\"],\"accent\":\"#2b7cd9\",\"bodyAttr\":\"data-dsh-retro\",\"package\":\"@linxin666/dsh-client-ui-skin-qq98\",\"order\":4},\n\n {\"id\":\"ths\",\"name\":\"同花顺风格\",\"nameEn\":\"Tonghuashun Trading\",\"author\":\"dsh-web-ui\",\"tagline\":\"品牌红标题栏 · 实时行情状态栏 · 灰蓝数据终端\",\"description\":\"同花顺风格炒股主题:品牌红标题栏带上证指数行情签,状态栏红涨绿跌,自选股风格的侧边栏和交易终端面板,写代码也像盯盘。\",\"tags\":[\"stock\",\"trading\",\"terminal\",\"red\"],\"accent\":\"#e60012\",\"bodyAttr\":\"data-dsh-ths\",\"package\":\"@linxin666/dsh-client-ui-skin-ths\",\"order\":5},\n\n {\"id\":\"xp\",\"name\":\"Windows XP (Luna)\",\"nameEn\":\"Windows XP Luna\",\"author\":\"dsh-web-ui\",\"tagline\":\"Luna 蓝窗口条 · 绿色开始按钮 · Bliss 蓝天桌面\",\"description\":\"Windows XP (Luna) 复古主题:蓝色渐变窗口条带窗口按钮、米色状态栏(大写/数字/滚动指示灯)、侧边栏任务栏上的绿色「开始」按钮、资源管理器风格树行和 Bliss 蓝天桌面,全局直角。\",\"tags\":[\"retro\",\"xp\",\"luna\",\"windows\",\"start-button\"],\"accent\":\"#316ac5\",\"bodyAttr\":\"data-dsh-xp\",\"package\":\"@linxin666/dsh-client-ui-skin-xp\",\"order\":6},\n\n {\"id\":\"dragon-heir\",\"name\":\"龙的传人\",\"nameEn\":\"Dragon Heir\",\"author\":\"dsh-web-ui\",\"tagline\":\"不屈龙魂 · 万里长城双主题 · 朱砂龙印\",\"description\":\"龙的传人 — 一面是不屈龙魂(墨龙穿云、朱砂印章、不屈锋芒),一面是万里长城(青黛山色、金晖镀墙、苍茫暮色)。亮暗主题各自配一幅画与一枚龙印 favicon,面板半透明磨砂,让画透出来。\",\"tags\":[\"dragon\",\"loong\",\"chinese\",\"ink-wash\",\"great-wall\",\"dual-theme\"],\"accent\":\"#c3272b\",\"bodyAttr\":\"data-dsh-dragon-heir\",\"package\":\"@linxin666/dsh-client-ui-skin-dragon-heir\",\"order\":7},\n\n {\"id\":\"minecraft\",\"name\":\"Minecraft 方块世界\",\"nameEn\":\"Minecraft Voxel\",\"author\":\"dsh-web-ui\",\"tagline\":\"动态全景天空盒 · 方块按钮 · 告示牌输入框\",\"description\":\"复刻《我的世界》主界面氛围的方块皮肤:程序化绘制的像素全景天空盒(方块山、像素云、方块树、草方块地面)在身后缓慢旋转,界面浮在石板上;按钮还原 MC 菜单按钮(灰石板、悬停变黄、按下下沉),输入框做成带钉子的木告示牌。\",\"tags\":[\"minecraft\",\"voxel\",\"pixel\",\"game\",\"panorama\",\"skybox\"],\"accent\":\"#7cbd4b\",\"bodyAttr\":\"data-dsh-minecraft\",\"package\":\"@linxin666/dsh-client-ui-skin-minecraft\",\"order\":8},\n\n {\"id\":\"trading\",\"name\":\"交易终端\",\"nameEn\":\"Trading Terminal\",\"author\":\"dsh-web-ui\",\"tagline\":\"实时行情跑马灯 · 长桥港美股行情 · 红涨绿跌交易终端\",\"description\":\"结合 dsh-fun-ticker 行情跑马灯与 dsh-longbridge 港美股行情的炒股皮肤:顶栏滚动 A股/港股/美股/指数/加密/外汇报价(装 fun-ticker 后跟随你的自选列表),状态栏展示长桥行情快照与 A股/港股/美股交易时段,写代码也像盯盘。\",\"tags\":[\"stock\",\"trading\",\"ticker\",\"live\",\"terminal\",\"longbridge\"],\"accent\":\"#f23645\",\"bodyAttr\":\"data-dsh-trading\",\"package\":\"@linxin666/dsh-client-ui-skin-trading\",\"order\":9},\n\n {\"id\":\"miku\",\"name\":\"初音未来 · 电子歌姬\",\"nameEn\":\"Hatsune Miku\",\"author\":\"涂山苏苏\",\"tagline\":\"蓝紫双马尾 · 01 编号 · 音符波形 · 电子歌姬主题\",\"description\":\"以世界第一的虚拟歌姬初音未来为灵感的主题皮肤:蓝紫洋红渐变贯穿全局,音符与声波曲线点缀在半透明面板之间,标题栏与状态栏带有 01 编号徽标与音乐波形,半透明毛玻璃面板透出背景图——沉浸式电子歌姬氛围。\",\"tags\":[\"miku\",\"vocaloid\",\"blue\",\"music\",\"idol\",\"waveform\"],\"accent\":\"#2e9bff\",\"bodyAttr\":\"data-dsh-miku\",\"package\":\"@linxin666/dsh-client-ui-skin-miku\",\"order\":10},\n\n]\n","/**\n * Boot-manifest readiness checks for the one-click apply flow.\n *\n * The host half writes the skin patch synchronously, but the web app's boot\n * graph (the `window.__DSH_BOOT__` JSON inside the served HTML) is\n * regenerated asynchronously by the config watcher. A page reloaded right\n * after the patch write can therefore boot into the previous skin. These\n * helpers let the frontend poll the served document until the manifest\n * actually reflects the target before reloading.\n * @module @linxin666/dsh-client-ui-skin-center/manifest\n */\n\n/** Bundle URL pattern of any skin entry in the boot manifest. */\nconst SKIN_BUNDLE_URL = /\\/plugins\\/@linxin666\\/dsh-client-ui-skin-(?!center)[a-z0-9-]+\\/client\\.js/\n\n/**\n * Whether a served GUI document's boot manifest enables the given skin.\n * A `null` target means the stock look: no skin bundle URL may be present\n * (the skin-center plugin's own bundle always loads and is excluded).\n * @param documentHtml - the served GUI document (contains the boot JSON).\n * @param target - skin id, or `null` for the stock look.\n * @returns whether the manifest already enables the target.\n */\nexport function manifestHasSkin(documentHtml: string, target: string | null): boolean {\n if (target === null) return !SKIN_BUNDLE_URL.test(documentHtml)\n return documentHtml.includes(`/plugins/@linxin666/dsh-client-ui-skin-${target}/client.js`)\n}\n","/**\n * Try-on engine for the in-GUI skin center.\n *\n * A skin's client bundle is executed through the REAL module system, not a\n * shim and not eval: the host route `/api/skin-center/bundle/<id>` serves\n * the skin's prebuilt `lib/client.js` as a same-origin script (mirroring\n * the kernel's own defaultLoadBundle — see dsh-client-modules), and its\n * body calls `window.__ModuleLoader__.load({id, factory})`, which only\n * REGISTERS the factory. `window.__DSH_MODULES__.import(package)` (the\n * kernel's ClientModuleSystem, contract C5/C6) then materializes it — which\n * auto-injects the skin's CSS `<style data-plugin>` tag — and\n * `surface.apply(miniCtx)` mounts the skin exactly as the fiber system\n * would, returning a full disposer. That makes try-on and its teardown the\n * real code paths, with no CSP `unsafe-eval` dependence and no startup\n * cost: the ~700KB of embedded art base64 is only parsed when a skin is\n * actually tried on.\n *\n * Mutual exclusion: the GUI never hosts two skins at once. The currently\n * ACTIVE skin is owned by its own cordis fiber (its disposer is not\n * reachable), so try-on retracts the active skin's visual writes by recipe:\n * remove its body attribute (its stylesheet goes inert), clear the\n * body-level backdrop inline styles (blue-fantasy's whale art), detach only\n * known skin chrome body children (title/status bars marked `data-skin-chrome`\n * or carrying the skin's body attribute, leaving other plugins' portals and\n * toasts in place), and neutralize known global-rule leaks (xp's sidebar\n * taskbar/start). Everything is snapshotted and restored on exit in original\n * order. The active skin's own fiber is never touched, so exiting try-on\n * returns the page to exactly the pre-try-on state.\n *\n * A ghost MutationObserver may survive retraction (blue-fantasy re-writes\n * its backdrop on theme flips), so during try-on a neutralizing observer\n * re-clears the backdrop props whenever `data-ds-dark-theme` changes.\n */\n\nimport { SKIN_CENTER_ENTRIES, type SkinCenterEntry } from './generated/skins.ts'\n\n/** Body-level backdrop properties skins may write inline (blue-fantasy). */\nconst BACKDROP_PROPS = [\n 'background-image',\n 'background-position',\n 'background-size',\n 'background-attachment',\n 'background-repeat',\n] as const\n\n/**\n * Per-skin neutralization CSS: rules that hide visual leaks whose styles\n * are NOT scoped under the skin's body attribute (they live on app elements\n * the skin touches, so detaching chrome cannot remove them). Matched by\n * css-module class substring, which is stable across rebuilds.\n */\nconst NEUTRALIZE_CSS: Record<string, string> = {\n // xp styles its start button + taskbar strip in the sidebar footer with\n // top-level (unscoped) rules; its apply() removes them on dispose, but\n // during try-on its ghost observer would re-add them, so hide instead.\n xp: [\n `[data-pane='sidebar'] [class*='xpTaskbar']{background:transparent!important;border-top:none!important;box-shadow:none!important}`,\n `[data-pane='sidebar'] [class*='xpStart']{display:none!important}`,\n ].join(''),\n}\n\n/** The window surfaces the boot protocol installs (manifest.ts contract). */\ninterface SkinCenterWindow {\n __DSH_BOOT__?: { entries?: Array<{ id: string }> }\n __DSH_MODULES__?: {\n import(specifier: string): Promise<unknown>\n invalidate(id: string): void\n }\n}\n\n/** Host base path of the skin bundle route (registered by src/routes.ts). */\nconst BUNDLE_ROUTE = '/api/skin-center/bundle'\n\n/**\n * Execute one skin's client bundle as a real same-origin script, mirroring\n * the kernel's own defaultLoadBundle (dsh-client-modules): the script body\n * calls `window.__ModuleLoader__.load({id, factory})`, which only registers\n * the factory — materialization is the caller's separate `import` step. No\n * eval: try-on works under any CSP that allows same-origin scripts (the\n * shell itself loads plugin bundles this way), and a failed fetch rejects\n * so the caller can restore the active skin instead of leaving it retracted.\n * @param url - same-origin bundle URL.\n * @returns a promise resolving once the script executed.\n */\nfunction loadBundleScript(url: string): Promise<void> {\n return new Promise((resolve, reject) => {\n const el = document.createElement('script')\n el.async = true\n el.src = url\n el.addEventListener('load', () => {\n el.remove()\n resolve()\n }, { once: true })\n el.addEventListener('error', () => {\n el.remove()\n reject(new Error(`skin-center: bundle script ${url} failed to load`))\n }, { once: true })\n document.head.append(el)\n })\n}\n\n/** Read the page's composed boot-graph entry ids (only enabled plugins appear). */\nfunction bootEntryIds(): string[] {\n const boot = (window as SkinCenterWindow).__DSH_BOOT__\n return boot?.entries?.map(entry => entry.id) ?? []\n}\n\n/** The skin package currently ACTIVE in the boot graph, if it is one of ours. */\nexport function activeSkinEntry(): SkinCenterEntry | undefined {\n const ids = new Set(bootEntryIds())\n return SKIN_CENTER_ENTRIES.find(entry => ids.has(entry.package))\n}\n\n/**\n * Whether a direct body child is skin chrome owned by `skin`: marked with the\n * `data-skin-chrome` marker (minecraft/dragon-heir) or carrying the skin's\n * scoping body attribute. Everything else — other plugins' portals, toasts and\n * overlays appended to body — is left alone.\n */\nfunction isSkinChrome(el: Element, skin: SkinCenterEntry | null): boolean {\n if (el.hasAttribute('data-skin-chrome')) return true\n return skin !== null && el.hasAttribute(skin.bodyAttr)\n}\n\n/** Minimal ctx the skins' apply() needs: cordis effect lifecycle only. */\ninterface MiniCtx {\n effect(callback: () => () => void, label?: string): () => void\n /**\n * Service reads are answered with undefined: try-on is a pure-DOM stage,\n * so optional service access (e.g. ths reading the connection handle)\n * degrades to its fallback instead of throwing mid-apply.\n */\n get(key: string): unknown\n /** Hidden handle for the controller: run the disposers in reverse order. */\n __disposeAll(): void\n}\n\nfunction miniCtx(): MiniCtx {\n const disposers: Array<() => void> = []\n return {\n effect(callback) {\n disposers.push(callback())\n return () => {}\n },\n get() {\n return undefined\n },\n __disposeAll(): void {\n for (const dispose of disposers.reverse()) dispose()\n },\n }\n}\n\n/** Snapshot of the active skin's visual writes, restored on try-on exit. */\ninterface ActiveVisuals {\n skin: SkinCenterEntry | null\n /** Attribute value before retraction (null = attribute absent). */\n bodyAttr: string | null\n /** body inline style before retraction (null = none). */\n bodyStyle: string | null\n /** Skin chrome elements detached from body, re-inserted at their anchors. */\n detached: Array<{ el: HTMLElement; anchor: Node | null }>\n /** Neutralizes ghost backdrop writes while a try-on is live. */\n clearObserver: MutationObserver | null\n /** Hides global-rule leaks of the active skin (xp taskbar). */\n neutralizeStyle: HTMLStyleElement | null\n}\n\n/**\n * One live try-on session: owns the tried-on skin's disposer plus the\n * captured active-skin visuals, and restores everything on exit.\n */\nexport class TryOnController {\n private session: {\n /** The tried-on skin, or null when trying on the official stock look. */\n entry: SkinCenterEntry | null\n dispose: () => void\n active: ActiveVisuals\n } | null = null\n\n /**\n * Generation counter. A newer try-on or exit increments it, so an in-flight\n * `tryOn` (awaiting the real bundle load) can detect it was superseded and\n * drop only what it mounted instead of clobbering the newer session.\n */\n private epoch = 0\n\n /**\n * One package can be requested again before its first script load finishes\n * (for example A -> B -> A). Share that materialization so two script tags\n * never race to register the same module factory.\n */\n private readonly pendingModules = new Map<string, Promise<(ctx: unknown) => unknown>>()\n\n /**\n * Package selected by the newest async request. A superseded request for the\n * same package must not invalidate the module/style now owned by that newer\n * request when their shared load settles.\n */\n private requestedPackage: string | null = null\n\n /**\n * Loads one skin's client bundle so its factory registers on the page's\n * `__ModuleLoader__`. Defaults to a same-origin script tag from the host\n * route `/api/skin-center/bundle/<id>`; tests inject a stub.\n */\n private readonly loadBundle: (entry: SkinCenterEntry) => Promise<void>\n\n constructor(options: { loadBundle?: (entry: SkinCenterEntry) => Promise<void> } = {}) {\n this.loadBundle = options.loadBundle ?? (entry => loadBundleScript(`${BUNDLE_ROUTE}/${encodeURIComponent(entry.id)}`))\n }\n /** The skin currently being tried on, if any. */\n get trying(): SkinCenterEntry | null {\n return this.session?.entry ?? null\n }\n\n /** Whether the official stock look (no skin) is being tried on. */\n get tryingOfficial(): boolean {\n return this.session !== null && this.session.entry === null\n }\n\n /**\n * Start trying on `entry` (replaces any live session).\n *\n * When another skin is already being tried on, keep it mounted while the\n * next bundle loads. Once the target is ready, tear down the old preview and\n * mount the new one against the SAME captured active-skin snapshot. This\n * avoids the expensive preview -> active -> preview round trip and prevents\n * a flash of the active skin between consecutive try-ons.\n * @returns whether this request mounted the target (false when superseded).\n */\n async tryOn(entry: SkinCenterEntry): Promise<boolean> {\n if (entry.package === activeSkinEntry()?.package) return false\n const epoch = ++this.epoch\n this.requestedPackage = entry.package\n\n let apply: (ctx: unknown) => unknown\n try {\n // Keep either the active skin or the current preview visible while the\n // target script downloads, parses and materializes its module.\n apply = await this.loadModuleOnce(entry)\n } catch (error) {\n if (this.shouldCleanupRequest(entry, epoch)) this.cleanupModule(entry)\n throw error\n }\n if (epoch !== this.epoch) {\n if (this.shouldCleanupRequest(entry, epoch)) this.cleanupModule(entry)\n return false\n }\n\n const previous = this.session\n let active: ActiveVisuals\n if (previous === null) {\n active = this.captureAndRetractActive()\n } else {\n // Transfer ownership of the original active-skin snapshot directly to\n // the next preview; restoring and immediately retracting it is wasted\n // DOM work and is the visible pause reported during chained try-ons.\n this.session = null\n previous.dispose()\n if (previous.entry !== null) this.cleanupModule(previous.entry)\n active = previous.active\n }\n\n let dispose: () => void\n try {\n dispose = this.applyLoaded(entry, apply)\n } catch (error) {\n if (epoch === this.epoch) this.restoreActive(active)\n throw error\n }\n this.session = { entry, dispose, active }\n return true\n }\n\n /**\n * Try on the official stock look: retract the active skin's visual writes\n * (same recipe as a skin try-on) and mount nothing. Exiting restores the\n * active skin exactly like any other try-on session.\n */\n tryOnOfficial(): void {\n if (activeSkinEntry() === null) return\n this.epoch += 1\n this.requestedPackage = null\n const previous = this.session\n if (previous !== null) {\n // A live preview already owns the captured active visuals. Dispose only\n // that preview and reuse the snapshot instead of restoring and retracting\n // the active skin in two consecutive layout-heavy operations.\n this.session = null\n previous.dispose()\n if (previous.entry !== null) this.cleanupModule(previous.entry)\n this.session = { entry: null, dispose: () => {}, active: previous.active }\n return\n }\n const active: ActiveVisuals = this.captureAndRetractActive()\n this.session = { entry: null, dispose: () => {}, active }\n }\n\n /** Exit the live session: dispose the tried-on skin, then restore the active skin. */\n exit(): void {\n // Also invalidate an in-flight initial/chained load. The visible session\n // may already be null by the time a user exits, but its late completion\n // must never remount a skin after cancellation.\n this.epoch += 1\n this.requestedPackage = null\n const session = this.session\n if (session === null) return\n this.session = null\n session.dispose()\n if (session.entry !== null) this.cleanupModule(session.entry)\n this.restoreActive(session.active)\n }\n\n /** Share one materialization while repeated requests for a package overlap. */\n private loadModuleOnce(entry: SkinCenterEntry): Promise<(ctx: unknown) => unknown> {\n const existing = this.pendingModules.get(entry.package)\n if (existing !== undefined) return existing\n\n const pending = this.loadModule(entry)\n this.pendingModules.set(entry.package, pending)\n void pending.then(\n () => {\n if (this.pendingModules.get(entry.package) === pending) this.pendingModules.delete(entry.package)\n },\n () => {\n if (this.pendingModules.get(entry.package) === pending) this.pendingModules.delete(entry.package)\n },\n )\n return pending\n }\n\n /** Whether this request still owns cleanup of the package module/style. */\n private shouldCleanupRequest(entry: SkinCenterEntry, epoch: number): boolean {\n return epoch === this.epoch || this.requestedPackage !== entry.package\n }\n\n /** Execute + materialize the target skin through the real loader. */\n private async loadModule(entry: SkinCenterEntry): Promise<(ctx: unknown) => unknown> {\n const modules = (window as SkinCenterWindow).__DSH_MODULES__\n if (modules === undefined) throw new Error('skin-center: window.__DSH_MODULES__ missing')\n // This try-on session owns the module record for the package for its\n // whole life (a try-on of the ACTIVE skin is rejected above, and the GUI\n // never hosts two skins at once). Drop any factory a crashed earlier\n // session left behind so the script below registers a fresh one — the\n // duplicate-registration retry dance is unnecessary with the real\n // loader, whose load() throws on a duplicate id.\n modules.invalidate(entry.package)\n await this.loadBundle(entry)\n const surface = await modules.import(entry.package)\n const apply = (surface as { apply?: (ctx: unknown) => unknown }).apply\n if (typeof apply !== 'function') {\n throw new Error(`skin-center: \"${entry.package}\" client bundle exports no apply`)\n }\n return apply\n }\n\n /** Apply a module that has already been loaded while the active skin was visible. */\n private applyLoaded(entry: SkinCenterEntry, apply: (ctx: unknown) => unknown): () => void {\n const ctx = miniCtx()\n try {\n apply(ctx)\n } catch (error) {\n // A skin that throws mid-apply leaves partial writes behind (body\n // attribute, chrome, style tag) and never registers its disposer —\n // the tryOn catch only restores the ACTIVE skin. Roll the residue back\n // here so a crashed try-on can never bleed into the next one.\n this.cleanupModule(entry)\n document.body.removeAttribute(entry.bodyAttr)\n for (const el of [...document.body.children] as HTMLElement[]) {\n if (isSkinChrome(el, entry)) el.remove()\n }\n throw error\n }\n return ctx.__disposeAll\n }\n\n /** Drop the tried-on module record + its injected style tag. */\n private cleanupModule(entry: SkinCenterEntry): void {\n const modules = (window as SkinCenterWindow).__DSH_MODULES__\n modules?.invalidate(entry.package)\n for (const el of document.querySelectorAll(`style[data-plugin=${JSON.stringify(entry.package)}]`)) {\n el.remove()\n }\n }\n\n /**\n * Snapshot the active skin's visual writes and retract them so the tried-on\n * skin can take over the whole surface.\n */\n private captureAndRetractActive(): ActiveVisuals {\n const skin = activeSkinEntry() ?? null\n const body = document.body\n const bodyAttr = skin === null ? null : body.getAttribute(skin.bodyAttr)\n if (skin !== null && bodyAttr !== null) body.removeAttribute(skin.bodyAttr)\n\n const bodyStyle = body.getAttribute('style')\n for (const prop of BACKDROP_PROPS) body.style.removeProperty(prop)\n\n // Detach only known skin chrome (marker/bodyAttr), leaving other\n // plugins' portals/toasts in place; remember each element's next sibling\n // so restore re-inserts in the original order.\n const children = [...body.children] as HTMLElement[]\n const chrome = new Set<HTMLElement>()\n for (const el of children) {\n if (el.id !== 'root' && isSkinChrome(el, skin)) chrome.add(el)\n }\n const detached: Array<{ el: HTMLElement; anchor: Node | null }> = []\n for (let i = 0; i < children.length; i++) {\n const el = children[i]\n if (!chrome.has(el)) continue\n let anchor: Node | null = null\n for (let j = i + 1; j < children.length; j++) {\n if (!chrome.has(children[j])) { anchor = children[j]; break }\n }\n detached.push({ el, anchor })\n }\n for (const { el } of detached) el.remove()\n\n // Neutralize a surviving ghost observer (blue-fantasy's backdrop\n // re-writer) across theme flips during the try-on.\n const clearObserver = new MutationObserver(() => {\n for (const prop of BACKDROP_PROPS) body.style.removeProperty(prop)\n })\n clearObserver.observe(body, { attributes: true, attributeFilter: ['data-ds-dark-theme'] })\n\n const neutralizeCss = skin === null ? undefined : NEUTRALIZE_CSS[skin.id]\n const neutralizeStyle = neutralizeCss === undefined ? null : this.injectStyle(neutralizeCss)\n\n return { skin, bodyAttr, bodyStyle, detached, clearObserver, neutralizeStyle }\n }\n\n /** Restore the active skin's captured visual state. */\n private restoreActive(active: ActiveVisuals): void {\n const body = document.body\n if (active.skin !== null && active.bodyAttr !== null) {\n body.setAttribute(active.skin.bodyAttr, active.bodyAttr)\n }\n if (active.bodyStyle !== null) {\n body.setAttribute('style', active.bodyStyle)\n } else {\n body.removeAttribute('style')\n }\n for (const { el, anchor } of active.detached) {\n body.insertBefore(el, anchor !== null && anchor.parentNode === body ? anchor : null)\n }\n active.clearObserver?.disconnect()\n active.neutralizeStyle?.remove()\n }\n\n private injectStyle(css: string): HTMLStyleElement {\n const tag = document.createElement('style')\n tag.dataset.skinCenterNeutralize = ''\n tag.textContent = css\n document.head.append(tag)\n return tag\n }\n}\n","/**\n * The skin-center card: rendered as the content of a first-level settings\n * section, listing every installed skin plus the official stock look. Live\n * try-on executes the real bundle inside the GUI (light/dark preview, full\n * restore on exit); Apply is one click — the host half runs `dsh-skin use`\n * through /api/skin-center/apply, the config watcher hot-reloads the patch,\n * and the page reloads into the new skin. Copy rides the standard `t` seat;\n * the theme preview control drives the official theme service (persisted,\n * same as the Appearance row).\n */\nimport { useEffect, useRef, useState, useSyncExternalStore, type ReactNode } from 'react'\nimport type { PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots'\nimport type { ThemeSnapshot } from '@deepseek-ai/dsh-client-ui-theme/client'\nimport { SKIN_CENTER_ENTRIES, type SkinCenterEntry } from './generated/skins.ts'\nimport { manifestHasSkin } from './manifest.ts'\nimport type { SkinBackgroundHandle } from './background.ts'\nimport { activeSkinEntry, TryOnController } from './try-on.ts'\nimport css from './skin-center.module.css'\n\n/** Business face the skin-center apply() injects into the card. */\nexport interface SkinCenterInjected {\n controller: TryOnController\n theme: {\n getTheme(): ThemeSnapshot\n subscribe(listener: () => void): () => void\n setTheme(id: 'light' | 'dark'): void\n }\n /** Background occluder over the shared skin-background namespace. */\n background: SkinBackgroundHandle\n}\n\n/** Plugin-card component props: locale seat + injected face. */\nexport type SkinCenterComponentProps =\n PropsLocale<'skinCenter'> & SkinCenterInjected\n\n/** The apply target of the official stock-look card. */\nconst OFFICIAL = 'official'\n\n/** Skin ids that read the background-scrim variable and paint a backdrop. */\nconst BACKDROP_SKIN_IDS = new Set(['blue-fantasy', 'whale-song'])\n\n/**\n * Render the skin-center card: a static header naming the plugin, with the\n * always-visible skin list (official default + every installed skin; try-on /\n * theme preview / one-click apply) rendered below it.\n * @param props - card props.\n * @returns the plugin card.\n */\nexport function SkinCenter({ t, controller, theme, background }: SkinCenterComponentProps) {\n const snapshot = useSyncExternalStore(theme.subscribe, theme.getTheme)\n const enabled = useSyncExternalStore(background.subscribe, background.enabled)\n const opacity = useSyncExternalStore(background.subscribe, background.opacity)\n const blurEmpty = useSyncExternalStore(background.subscribe, background.blurEmpty)\n const blurContent = useSyncExternalStore(background.subscribe, background.blurContent)\n const activePackage = activeSkinEntry()?.package\n const activeId = activeSkinEntry()?.id\n const backdropActive = activeId !== undefined && BACKDROP_SKIN_IDS.has(activeId)\n const [tryingId, setTryingId] = useState<string | null>(null)\n const [tryingOfficial, setTryingOfficial] = useState(false)\n const [loadingId, setLoadingId] = useState<string | null>(null)\n const [applying, setApplying] = useState<string | null>(null)\n const [error, setError] = useState<string | null>(null)\n // Unmount guard for the confirmation poll: once the card is gone, the\n // pending timers must stop and no reload / setState may fire.\n const mounted = useRef(false)\n // Latest-click-wins token for async bundle loads. A chained try-on or exit\n // invalidates older completions so a slow bundle can never overwrite the\n // UI state chosen by a newer click.\n const tryOnRequest = useRef(0)\n useEffect(() => {\n mounted.current = true\n return () => { mounted.current = false }\n }, [])\n\n const tryOn = (entry: SkinCenterEntry): void => {\n if (loadingId === entry.id) return\n const request = ++tryOnRequest.current\n setError(null)\n setLoadingId(entry.id)\n void controller.tryOn(entry)\n .then(mountedTarget => {\n if (!mounted.current || request !== tryOnRequest.current || !mountedTarget) return\n setLoadingId(null)\n setTryingId(entry.id)\n setTryingOfficial(false)\n })\n .catch(() => {\n if (!mounted.current || request !== tryOnRequest.current) return\n // A load failure keeps the previous preview mounted; a mount failure\n // restores the original active skin. Mirror the controller's actual\n // session instead of blindly clearing a preview that may still exist.\n setLoadingId(null)\n setError(t('tryOnError'))\n setTryingId(controller.trying?.id ?? null)\n setTryingOfficial(controller.tryingOfficial)\n })\n }\n\n const tryOnOfficial = (): void => {\n ++tryOnRequest.current\n setError(null)\n setLoadingId(null)\n try {\n controller.tryOnOfficial()\n } catch {\n setError(t('tryOnError'))\n setTryingOfficial(false)\n return\n }\n setTryingId(null)\n setTryingOfficial(true)\n }\n\n const exitTryOn = (): void => {\n ++tryOnRequest.current\n controller.exit()\n setLoadingId(null)\n setTryingId(null)\n setTryingOfficial(false)\n }\n\n /**\n * Poll the host state until the config watcher reports the target active\n * (the patch write lands before the watcher re-applies it), or time out.\n * @param target - skin id, or `official` for the stock look.\n * @returns whether the target became active within the poll budget.\n */\n const confirmActive = (target: string): Promise<boolean> =>\n new Promise(resolve => {\n const expected = target === OFFICIAL ? 'none' : target\n let tries = 0\n const tick = (): void => {\n if (!mounted.current) {\n resolve(false)\n return\n }\n tries += 1\n void fetch('/api/skin-center/state')\n .then(async response => {\n const payload = await response.json().catch(() => null) as { ok?: boolean; active?: string } | null\n if (response.ok && payload?.ok === true && payload.active === expected) {\n resolve(true)\n return\n }\n if (tries >= 20 || !mounted.current) resolve(false)\n else window.setTimeout(tick, 250)\n })\n .catch(() => {\n if (tries >= 20 || !mounted.current) resolve(false)\n else window.setTimeout(tick, 250)\n })\n }\n tick()\n })\n\n /**\n * Poll the served GUI document until the boot manifest actually enables\n * the target (the config watcher regenerates it asynchronously after the\n * patch write — reloading earlier boots the page into the previous skin),\n * or time out.\n * @param target - skin id, or `official` for the stock look.\n * @returns whether the manifest caught up within the poll budget.\n */\n const manifestReady = (target: string): Promise<boolean> =>\n new Promise(resolve => {\n const expected = target === OFFICIAL ? null : target\n let tries = 0\n const tick = (): void => {\n if (!mounted.current) {\n resolve(false)\n return\n }\n tries += 1\n void fetch(window.location.href, { cache: 'no-store' })\n .then(async response => {\n const html = await response.text().catch(() => null)\n if (html !== null && manifestHasSkin(html, expected)) {\n resolve(true)\n return\n }\n if (tries >= 40 || !mounted.current) resolve(false)\n else window.setTimeout(tick, 500)\n })\n .catch(() => {\n if (tries >= 40 || !mounted.current) resolve(false)\n else window.setTimeout(tick, 500)\n })\n }\n tick()\n })\n\n /**\n * One-click apply: the host half runs `dsh-skin use <target>` (or\n * `use official`), the config watcher hot-reloads the patch within\n * seconds, then this page reloads to pick up the new boot graph. The\n * reload waits for both the patch (state poll) and the regenerated boot\n * manifest (manifest poll) so the page never boots into the old skin.\n * @param target - skin id, or `official` for the stock look.\n */\n const applySkin = (target: string): void => {\n setError(null)\n setApplying(target)\n const body = target === OFFICIAL ? { official: true } : { skin: target }\n void fetch('/api/skin-center/apply', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(body),\n })\n .then(async response => {\n const payload = await response.json().catch(() => null) as { ok?: boolean; error?: string } | null\n if (!response.ok || payload?.ok !== true) {\n throw new Error(payload?.error ?? `HTTP ${response.status}`)\n }\n setApplying(null)\n // Patch written; reload only once the watcher reports the target\n // active AND the boot manifest caught up, so the page never boots\n // into the old skin.\n void confirmActive(target).then(confirmed => {\n if (!mounted.current) return\n if (!confirmed) {\n const command = target === OFFICIAL ? 'dsh-skin use official' : `dsh-skin use ${target}`\n setError(`${t('appliedUnconfirmed')} — ${command}`)\n return\n }\n void manifestReady(target).then(ready => {\n if (!mounted.current) return\n if (ready) {\n window.location.reload()\n } else {\n const command = target === OFFICIAL ? 'dsh-skin use official' : `dsh-skin use ${target}`\n setError(`${t('appliedUnconfirmed')} — ${command}`)\n }\n })\n })\n })\n .catch((cause: unknown) => {\n setApplying(null)\n const detail = cause instanceof Error ? cause.message : String(cause)\n const command = target === OFFICIAL ? 'dsh-skin use official' : `dsh-skin use ${target}`\n setError(`${t('applyFailed')} (${detail}) — ${command}`)\n })\n }\n\n const dark = snapshot.active.colorScheme === 'dark'\n\n /** One row: try-on control + apply button. Shared by the official card and every skin card. */\n const actionButtons = (opts: {\n key: string\n isActive: boolean\n isTrying: boolean\n onTryOn: () => void\n applyLabel: string\n }): ReactNode => (\n <div className={css.actions}>\n {opts.isActive ? (\n <button type=\"button\" className={`${css.button} ${css.buttonGhost}`} disabled>\n {t('tryOn')}\n </button>\n ) : opts.isTrying ? (\n <button type=\"button\" className={`${css.button} ${css.buttonPrimary}`} onClick={exitTryOn}>\n {t('exitTryOn')}\n </button>\n ) : (\n <button\n type=\"button\"\n className={`${css.button} ${css.buttonPrimary}`}\n disabled={loadingId === opts.key}\n onClick={opts.onTryOn}\n >\n {loadingId === opts.key ? t('loading') : t('tryOn')}\n </button>\n )}\n <button\n type=\"button\"\n className={css.button}\n disabled={applying !== null || loadingId !== null}\n onClick={() => { applySkin(opts.key) }}\n >\n {applying === opts.key ? t('applying') : opts.applyLabel}\n </button>\n </div>\n )\n\n return (\n <li className={css.pluginCard}>\n <div className={css.cardHeaderStatic}>\n <span className={css.headText}>\n <span className={css.pluginName}>\n {t('title')}\n <span className={css.titleBadge}>{String(SKIN_CENTER_ENTRIES.length)}</span>\n </span>\n <span className={css.cardDescription} title={t('cardDescription')}>{t('cardDescription')}</span>\n </span>\n </div>\n\n <div className={css.cardBody}>\n <div className={css.enableRow}>\n <span className={css.enableLabel} title={t('enabled')}>{t('enabled')}</span>\n <button\n type=\"button\"\n role=\"switch\"\n aria-checked={enabled}\n aria-label={t('enabled')}\n className={enabled ? css.switch + ' ' + css.switchOn : css.switch}\n onClick={() => { background.setEnabled(!enabled) }}\n >\n <span className={css.switchThumb} />\n </button>\n <p className={css.enableHint}>{t('enabledHint')}</p>\n </div>\n {enabled\n ? (\n <>\n <div className={css.head}>\n <div className={css.intro} title={t('intro')}>{t('intro')}</div>\n <div className={css.themeRow}>\n <span className={css.themeLabel}>{t('theme')}</span>\n <button\n type=\"button\"\n className={`${css.themeButton} ${dark ? '' : css.themeButtonActive}`}\n onClick={() => { theme.setTheme('light') }}\n >\n {t('themeLight')}\n </button>\n <button\n type=\"button\"\n className={`${css.themeButton} ${dark ? css.themeButtonActive : ''}`}\n onClick={() => { theme.setTheme('dark') }}\n >\n {t('themeDark')}\n </button>\n </div>\n </div>\n\n <div className={css.backgroundRow}>\n <div className={css.backgroundHead}>\n <span className={css.backgroundLabel}>{t('backgroundOpacity')}</span>\n <span className={css.backgroundValue} aria-hidden=\"true\">{opacity}%</span>\n </div>\n <input\n id=\"skin-center-background-opacity\"\n className={css.backgroundRange}\n type=\"range\"\n min=\"0\"\n max=\"100\"\n step=\"5\"\n value={opacity}\n aria-valuetext={`${opacity}%`}\n aria-label={t('backgroundOpacity')}\n onChange={(event) => { background.set(Number(event.target.value)) }}\n />\n <p className={backdropActive ? css.backgroundHint : css.backgroundHintMuted}>\n {backdropActive ? t('backgroundHint') : t('backgroundHintInert')}\n </p>\n </div>\n <div className={css.backgroundRow}>\n <div className={css.backgroundHead}>\n <span className={css.backgroundLabel}>{t('backgroundBlurEmpty')}</span>\n <span className={css.backgroundValue} aria-hidden=\"true\">{blurEmpty}px</span>\n </div>\n <input\n id=\"skin-center-background-blur-empty\"\n className={css.backgroundRange}\n type=\"range\"\n min=\"0\"\n max=\"20\"\n step=\"1\"\n value={blurEmpty}\n aria-valuetext={`${blurEmpty}px`}\n aria-label={t('backgroundBlurEmpty')}\n onChange={(event) => { background.setBlurEmpty(Number(event.target.value)) }}\n />\n <div className={css.backgroundHead}>\n <span className={css.backgroundLabel}>{t('backgroundBlurContent')}</span>\n <span className={css.backgroundValue} aria-hidden=\"true\">{blurContent}px</span>\n </div>\n <input\n id=\"skin-center-background-blur-content\"\n className={css.backgroundRange}\n type=\"range\"\n min=\"0\"\n max=\"20\"\n step=\"1\"\n value={blurContent}\n aria-valuetext={`${blurContent}px`}\n aria-label={t('backgroundBlurContent')}\n onChange={(event) => { background.setBlurContent(Number(event.target.value)) }}\n />\n <p className={backdropActive ? css.backgroundHint : css.backgroundHintMuted}>\n {backdropActive ? t('backgroundBlurHint') : t('backgroundBlurInert')}\n </p>\n </div>\n\n\n {error !== null && <div className={css.error}>{error}</div>}\n\n <div className={css.list}>\n {(() => {\n const isActive = activePackage === undefined\n const isTrying = tryingOfficial\n const badge = isActive ? t('active') : isTrying ? t('tryingOn') : null\n return (\n <div className={css.card} key={OFFICIAL}>\n <div className={css.cardHead}>\n <span className={css.swatch} style={{ background: '#98a1ab' }} aria-hidden=\"true\" />\n <span className={css.cardName} title={t('official')}>{t('official')}</span>\n {badge !== null && (\n <span className={`${css.badge} ${isActive ? css.badgeActive : css.badgeTrying}`}>\n {badge}\n </span>\n )}\n </div>\n <div className={css.cardTagline} title={t('officialTagline')}>{t('officialTagline')}</div>\n {actionButtons({\n key: OFFICIAL,\n isActive,\n isTrying,\n onTryOn: tryOnOfficial,\n applyLabel: t('restore'),\n })}\n </div>\n )\n })()}\n\n {SKIN_CENTER_ENTRIES.map(entry => {\n const isActive = entry.package === activePackage\n const isTrying = entry.id === tryingId\n const badge = isActive ? t('active') : isTrying ? t('tryingOn') : null\n return (\n <div className={css.card} key={entry.id}>\n <div className={css.cardHead}>\n <span className={css.swatch} style={{ background: entry.accent }} aria-hidden=\"true\" />\n <span className={css.cardName} title={entry.nameEn}>{entry.nameEn}</span>\n {badge !== null && (\n <span className={`${css.badge} ${isActive ? css.badgeActive : css.badgeTrying}`}>\n {badge}\n </span>\n )}\n </div>\n <div className={css.cardTagline} title={entry.tagline}>{entry.tagline}</div>\n {actionButtons({\n key: entry.id,\n isActive,\n isTrying,\n onTryOn: () => { tryOn(entry) },\n applyLabel: t('apply'),\n })}\n </div>\n )\n })}\n </div>\n </>\n )\n : (\n <p className={css.offNote} role=\"status\">{t('offNote')}</p>\n )}\n </div>\n </li>\n )\n}\n\n/** Props the settings section binds for the skin-center card page. */\nexport type SkinCenterSectionProps =\n PropsRuntime<'settings.section'>\n & PropsLocale<'skinCenter'>\n & SkinCenterInjected\n\n/** Render the skin-center card as a first-level settings page. */\nexport function SkinCenterSection(props: SkinCenterSectionProps): ReactNode {\n const { t, controller, theme, background } = props\n return (\n <ul className={css.sectionList}>\n <SkinCenter t={t} controller={controller} theme={theme} background={background} />\n </ul>\n )\n}\n","/**\n * Background-scrim handle for the skin center: binds the `skin-background`\n * settings namespace and applies the chosen occlusion to the page's backdrop,\n * plus an optional per-state Gaussian blur of that backdrop.\n *\n * Occlusion is a CSS variable on `document.body` (--dsw-skin-scrim), which\n * backdrop-painting skins (blue-fantasy / whale-song) read inside their\n * setBackdrop() so the veil stays in sync across theme flips and try-on\n * restores. The official stock look paints no backdrop, so the variable is\n * inert there — the value still persists so it is ready for the next backdrop\n * skin.\n *\n * The Gaussian blur targets the same painted backdrop through a fixed child\n * of `document.body` using backdrop-filter: it samples the body's own\n * background painted behind it. Separate strengths apply to the empty\n * conversation and the conversation-with-content states, detected from the\n * shell's stable message-row class suffixes (hash prefix varies, suffix is\n * stable). When the active blur is 0 no element exists, so there is no GPU\n * cost. Blur never changes the occlusion behavior above.\n *\n * Occlusion values are 0-100 (0 = no extra veil, 100 = fully obscured); they\n * are written through as a 0..1 alpha for the CSS variable. Blur values are\n * 0-20 px. Dragging the controls applies instantly (live) and persists\n * through the settings scope.\n */\nimport type { SettingsScope, SettingsScopeSnapshot } from '@deepseek-ai/dsh-client-runtime/client'\n\n/** The namespace string the Host registers (mirrors src/index.ts). */\nexport const SKIN_BACKGROUND_NS = 'skin-background'\n\n/** Field of the background value inside the namespace section. */\nexport const OPACITY_FIELD = 'backgroundOpacity'\n\n/** Field of the empty-conversation backdrop blur inside the namespace section. */\nexport const BLUR_EMPTY_FIELD = 'backgroundBlurEmpty'\n\n/** Field of the with-content backdrop blur inside the namespace section. */\nexport const BLUR_CONTENT_FIELD = 'backgroundBlurContent'\n\n/** CSS custom property written to document.body and read by backdrop skins. */\nexport const SCRIM_VAR = '--dsw-skin-scrim'\n\n/** Default occlusion (0 = no extra veil) when the section carries none. */\nexport const DEFAULT_OPACITY = 0\n\n/** Default blur (0 = disabled) when the section carries none. */\nexport const DEFAULT_BLUR = 0\n\n/** The face the skin-center card injects for the background control. */\nexport interface SkinBackgroundHandle {\n /** Current master switch (true when the plugin is on). */\n enabled(): boolean\n /** Toggle + persist the master switch. */\n setEnabled(value: boolean): void\n /** Current occlusion 0-100 (also the getSnapshot seat for useSyncExternalStore). */\n opacity(): number\n /** Current empty-conversation backdrop blur 0-20 px. */\n blurEmpty(): number\n /** Current with-content backdrop blur 0-20 px. */\n blurContent(): number\n /** Observe a change in the applied values. */\n subscribe(listener: () => void): () => void\n /** Apply + persist a new occlusion. */\n set(opacity: number): void\n /** Apply + persist a new empty-conversation backdrop blur (0-20 px). */\n setBlurEmpty(value: number): void\n /** Apply + persist a new with-content backdrop blur (0-20 px). */\n setBlurContent(value: number): void\n /** Tear down the blur element and MutationObserver. */\n dispose(): void\n}\n\n/**\n * Selector for a conversation message row inside the shell's center column.\n * The `data-pane=\"conversation\"` attribute is stamped by the dsh-web-ui-all\n * compat shim on the center column; the _userRow / _compactionRow /\n * _contextRow / _turnErrorRow suffixes are the official shell's CSS-module\n * hashed message-row classes (hash prefix varies, suffix is stable). Stable\n * like the repo's compat shim, not hash-dependent.\n */\nconst CONVERSATION_CONTENT_SELECTOR = [\n '[data-pane=\"conversation\"] [class*=\"_userRow\"]',\n '[data-pane=\"conversation\"] [class*=\"_compactionRow\"]',\n '[data-pane=\"conversation\"] [class*=\"_contextRow\"]',\n '[data-pane=\"conversation\"] [class*=\"_turnErrorRow\"]',\n].join(', ')\n\n/**\n * Own the skin-background scope: read the latest occlusion + blur strengths,\n * apply them to the body instantly, and persist changes through the settings\n * scope.\n */\nexport class BackgroundController implements SkinBackgroundHandle {\n private enabledValue = true\n private opacityValue = DEFAULT_OPACITY\n private blurEmptyValue = DEFAULT_BLUR\n private blurContentValue = DEFAULT_BLUR\n private readonly listeners = new Set<() => void>()\n private readonly scope: SettingsScope<{\n enabled?: boolean\n backgroundOpacity?: number\n backgroundBlurEmpty?: number\n backgroundBlurContent?: number\n }>\n /** The fixed backdrop-filter element, present only while active blur > 0. */\n private blurElement: HTMLDivElement | null = null\n /** The body MutationObserver, installed lazily once a blur is active. */\n private observer: MutationObserver | null = null\n /** Pending requestAnimationFrame id for a coalesced recheck. */\n private rafId: number | null = null\n /** Guard: after dispose no scheduled work may reinstall anything. */\n private disposed = false\n\n /**\n * @param scope - the bound skin-background settings scope.\n */\n constructor(scope: SettingsScope<{\n enabled?: boolean\n backgroundOpacity?: number\n backgroundBlurEmpty?: number\n backgroundBlurContent?: number\n }>) {\n this.scope = scope\n this.enabledValue = this.readEnabled()\n this.opacityValue = this.readOpacity()\n this.blurEmptyValue = this.readBlur(BLUR_EMPTY_FIELD)\n this.blurContentValue = this.readBlur(BLUR_CONTENT_FIELD)\n this.applyOcclusion()\n this.syncBlur()\n scope.subscribe(() => {\n this.enabledValue = this.readEnabled()\n this.opacityValue = this.readOpacity()\n this.blurEmptyValue = this.readBlur(BLUR_EMPTY_FIELD)\n this.blurContentValue = this.readBlur(BLUR_CONTENT_FIELD)\n this.applyOcclusion()\n this.syncBlur()\n this.publish()\n })\n }\n\n enabled(): boolean { return this.enabledValue }\n\n setEnabled(value: boolean): void {\n this.enabledValue = value\n this.applyOcclusion()\n this.syncBlur()\n this.publish()\n void this.scope.set('enabled', value)\n }\n\n opacity(): number { return this.opacityValue }\n\n blurEmpty(): number { return this.blurEmptyValue }\n\n blurContent(): number { return this.blurContentValue }\n\n subscribe(listener: () => void): () => void {\n this.listeners.add(listener)\n return () => { this.listeners.delete(listener) }\n }\n\n set(opacity: number): void {\n const clamped = Math.max(0, Math.min(100, Math.round(opacity)))\n this.opacityValue = clamped\n this.applyOcclusion()\n this.publish()\n // Persist: queue the write on the settings scope. Failures are silent —\n // the live value is already applied, and the write drains on reconnect.\n void this.scope.set(OPACITY_FIELD, clamped)\n }\n\n setBlurEmpty(value: number): void {\n const clamped = this.clampBlur(value)\n this.blurEmptyValue = clamped\n this.ensureObserver()\n this.syncBlur()\n this.publish()\n void this.scope.set(BLUR_EMPTY_FIELD, clamped)\n }\n\n setBlurContent(value: number): void {\n const clamped = this.clampBlur(value)\n this.blurContentValue = clamped\n this.ensureObserver()\n this.syncBlur()\n this.publish()\n void this.scope.set(BLUR_CONTENT_FIELD, clamped)\n }\n\n dispose(): void {\n this.disposed = true\n if (this.rafId !== null) {\n cancelAnimationFrame(this.rafId)\n this.rafId = null\n }\n this.removeBlurElement()\n if (this.observer !== null) {\n this.observer.disconnect()\n this.observer = null\n }\n }\n\n /** The effective master-switch section value, defaulting to true when absent. */\n private readEnabled(): boolean {\n const snapshot: SettingsScopeSnapshot<{ enabled?: boolean }> = this.scope.getSnapshot()\n const raw = snapshot.value?.enabled\n return typeof raw !== 'boolean' ? true : raw\n }\n\n /** The effective occlusion section value, clamped 0-100, defaulting to 0. */\n private readOpacity(): number {\n const snapshot: SettingsScopeSnapshot<{ backgroundOpacity?: number }> = this.scope.getSnapshot()\n const raw = snapshot.value?.backgroundOpacity\n if (typeof raw !== 'number' || !Number.isFinite(raw)) return DEFAULT_OPACITY\n return Math.max(0, Math.min(100, raw))\n }\n\n /** The effective blur section value for one field, clamped 0-20, defaulting to 0. */\n private readBlur(field: 'backgroundBlurEmpty' | 'backgroundBlurContent'): number {\n const snapshot: SettingsScopeSnapshot<{\n backgroundBlurEmpty?: number\n backgroundBlurContent?: number\n }> = this.scope.getSnapshot()\n const raw = snapshot.value?.[field]\n if (typeof raw !== 'number' || !Number.isFinite(raw)) return DEFAULT_BLUR\n return this.clampBlur(raw)\n }\n\n private clampBlur(value: number): number {\n return Math.max(0, Math.min(20, Math.round(value)))\n }\n\n /** Write the current occlusion onto the body CSS variable (0..1 alpha). */\n private applyOcclusion(): void {\n if (!this.enabledValue) {\n document.body.style.removeProperty(SCRIM_VAR)\n return\n }\n document.body.style.setProperty(SCRIM_VAR, String(this.opacityValue / 100))\n }\n\n /**\n * Apply the active blur: empty or with-content strength depending on the\n * conversation state. A value > 0 ensures the fixed blur element exists\n * with the matching backdrop-filter; 0 removes it.\n */\n private syncBlur(): void {\n if (this.disposed) return\n if (!this.enabledValue) {\n this.removeBlurElement()\n return\n }\n this.ensureObserver()\n const active = this.hasConversationContent() ? this.blurContentValue : this.blurEmptyValue\n if (active > 0) this.ensureBlurElement(active)\n else this.removeBlurElement()\n }\n\n /** True when the conversation pane hosts at least one message row. */\n private hasConversationContent(): boolean {\n return document.querySelector(CONVERSATION_CONTENT_SELECTOR) !== null\n }\n\n /** Create (if needed) and size the fixed backdrop-filter element. */\n private ensureBlurElement(active: number): void {\n if (this.blurElement === null) {\n const element = document.createElement('div')\n element.style.position = 'fixed'\n element.style.inset = '0'\n element.style.zIndex = '-1'\n element.style.pointerEvents = 'none'\n element.setAttribute('aria-hidden', 'true')\n this.blurElement = element\n document.body.appendChild(element)\n }\n const blur = 'blur(' + active + 'px)'\n this.blurElement.style.backdropFilter = blur\n // Safari: the vendor-prefixed form is only reachable via setProperty.\n this.blurElement.style.setProperty('-webkit-backdrop-filter', blur)\n }\n\n /** Remove the fixed blur element, if present. */\n private removeBlurElement(): void {\n if (this.blurElement === null) return\n this.blurElement.remove()\n this.blurElement = null\n }\n\n /**\n * Install the MutationObserver on document.body only when either blur\n * field is active, so a fully-disabled blur never pays the observation\n * cost. Runs lazily on the first non-zero set.\n */\n private ensureObserver(): void {\n if (this.disposed || this.observer !== null) return\n if (this.blurEmptyValue <= 0 && this.blurContentValue <= 0) return\n this.observer = new MutationObserver(() => this.scheduleRecheck())\n this.observer.observe(document.body, {\n childList: true,\n subtree: true,\n attributes: true,\n attributeFilter: ['class'],\n })\n }\n\n /** Coalesce burst mutations into one rAF-delayed recheck. */\n private scheduleRecheck(): void {\n if (this.disposed || this.rafId !== null) return\n this.rafId = requestAnimationFrame(() => {\n this.rafId = null\n if (this.disposed) return\n this.syncBlur()\n })\n }\n\n private publish(): void {\n for (const listener of this.listeners) listener()\n }\n}\n","/**\n * Skin-center locale dictionaries. The plugin-card name, its description,\n * and every control of the in-GUI skin center is localized through the\n * standard `t` seat.\n */\n\n/** Copy keys owned by this plugin. */\nexport type SkinCenterKey =\n | 'title'\n | 'cardDescription'\n | 'enabled'\n | 'enabledHint'\n | 'offNote'\n | 'intro'\n | 'official'\n | 'officialTagline'\n | 'active'\n | 'tryingOn'\n | 'tryOn'\n | 'loading'\n | 'exitTryOn'\n | 'apply'\n | 'applying'\n | 'restore'\n | 'applyFailed'\n | 'appliedUnconfirmed'\n | 'theme'\n | 'themeLight'\n | 'themeDark'\n | 'tryOnError'\n | 'backgroundOpacity'\n | 'backgroundBlurEmpty'\n | 'backgroundBlurContent'\n | 'backgroundBlurHint'\n | 'backgroundBlurInert'\n | 'backgroundHint'\n | 'backgroundHintInert'\n\nexport const en: Record<SkinCenterKey, string> = {\n title: 'Skin Center',\n cardDescription: 'Try on any installed skin live in the GUI — exit restores instantly, applying persists in one click.',\n enabled: 'Enable skin center',\n enabledHint: 'When off, try-on, apply and background controls are disabled; turn it back on to resume.',\n offNote: 'The skin center is turned off.',\n intro: 'Try on any skin live — it takes effect instantly, exit restores the current look. Apply persists it across restarts.',\n official: 'Official default',\n officialTagline: 'The stock DSH look with no skin applied.',\n active: 'Active',\n tryingOn: 'Trying on',\n tryOn: 'Try on',\n loading: 'Loading…',\n exitTryOn: 'Exit try-on',\n apply: 'Apply',\n applying: 'Applying…',\n restore: 'Restore',\n applyFailed: 'Apply failed',\n appliedUnconfirmed: 'Applied, but the change has not been confirmed — refresh the page if the skin did not switch',\n theme: 'Theme preview',\n themeLight: 'Light',\n themeDark: 'Dark',\n tryOnError: 'Try-on failed — see console',\n backgroundOpacity: 'Background occlusion',\n backgroundBlurEmpty: 'Blur when empty',\n backgroundBlurContent: 'Blur with content',\n backgroundBlurHint: 'Applies a separate Gaussian blur to the backdrop for the empty conversation and the conversation with content; 0 disables.',\n backgroundBlurInert: 'Visible only with skins that paint a backdrop; the official default has none.',\n backgroundHint: 'Instantly veils the backdrop behind the panels — higher values obscure the art to help you focus.',\n backgroundHintInert: 'Only applies to skins that paint a backdrop (Blue Fantasy / Whale Song). Applies to the official default automatically once such a skin is active.',\n}\n\nexport const zh: Record<SkinCenterKey, string> = {\n title: '皮肤中心',\n cardDescription: '在 GUI 内即时试穿任意皮肤,退出即完全还原;应用一键完成并自动刷新。',\n enabled: '启用皮肤中心',\n enabledHint: '关闭后停用试穿、应用与背景控件,重新打开即恢复。',\n offNote: '皮肤中心已关闭。',\n intro: '任意皮肤可即时试穿,退出即完全还原;「应用」一键持久化,页面自动刷新生效。',\n official: '官方默认',\n officialTagline: '还原 DSH 官方默认外观,不应用任何皮肤。',\n active: '当前激活',\n tryingOn: '试穿中',\n tryOn: '试穿',\n loading: '加载中…',\n exitTryOn: '退出试穿',\n apply: '应用',\n applying: '应用中…',\n restore: '恢复默认',\n applyFailed: '应用失败',\n appliedUnconfirmed: '已写入配置但尚未确认生效——若皮肤未切换请手动刷新页面',\n theme: '主题预览',\n themeLight: '亮色',\n themeDark: '暗色',\n tryOnError: '试穿失败,详见控制台',\n backgroundOpacity: '背景遮挡',\n backgroundBlurEmpty: '空对话背景模糊',\n backgroundBlurContent: '有对话背景模糊',\n backgroundBlurHint: '对话为空与有内容时分别应用不同的背景高斯模糊强度,0 为关闭。',\n backgroundBlurInert: '仅对带背景图插画的皮肤可见;官方默认无背景图。',\n backgroundHint: '即时为面板背后的背景加遮罩——数值越高越能弱化插画,帮你集中注意力。',\n backgroundHintInert: '仅对带背景图插画的皮肤(蓝色幻想 / 鲸吟)生效;官方默认无背景图,该滑块对这些皮肤自动生效。',\n}\n","/**\n * In-GUI skin center, browser half: registers the Skin Center as a first-level\n * settings section (`settings.section`) and provides the try-on controller +\n * official theme handle to it. The section lists every installed skin\n * (embedded registry), tries it on live inside the GUI, exits with a full\n * restore, and applies the chosen skin in one click. The plugin writes only\n * DOM and the settings ledger — no services, no events, no model access.\n */\nimport type { ClientContext, SettingsScope, SettingsScopeSpec } from '@deepseek-ai/dsh-client-runtime/client'\nimport type { ThemeRuntime } from '@deepseek-ai/dsh-client-ui-theme/client'\n// Type-only: pulls the locale plugin's Context merge (ctx.locale).\nimport type {} from '@deepseek-ai/dsh-client-locale/client'\n// Type-only: pulls the settings-surface Context merge (ctx.settingsScope).\nimport type {} from '@deepseek-ai/dsh-client-ui-settings/client'\nimport { SkinCenterSection, type SkinCenterInjected } from './SkinCenter.tsx'\nimport { BackgroundController, SKIN_BACKGROUND_NS } from './background.ts'\nimport { en, zh, type SkinCenterKey } from './locales.ts'\nimport { TryOnController } from './try-on.ts'\n\nexport type { SkinCenterComponentProps, SkinCenterInjected } from './SkinCenter.tsx'\nexport { TryOnController } from './try-on.ts'\n\n/** Locale namespace owned by this plugin. */\nexport const NS = 'skinCenter'\n\ndeclare module '@deepseek-ai/dsh-client-ui-slots' {\n interface LocaleNamespaceMap {\n /** The skin-center card's copy. */\n skinCenter: SkinCenterKey\n }\n}\n\ndeclare module '@deepseek-ai/cordis' {\n interface Context {\n /**\n * Optional rc.6 compatibility binder provided by dsh-web-ui-settings;\n * absent when that group plugin is not installed, so callers fall back to\n * the official settings scope.\n */\n webUiSettings?: { bind<S>(spec: SettingsScopeSpec<S>): SettingsScope<S> }\n }\n}\n\n\n/** Required services: slots + locale (plugin card), theme (preview toggle), and settingsScope + its transport (background scrim). */\nexport const inject = ['slots', 'locale', 'theme', 'settingsScope', 'connection', 'remote']\n\n/**\n * Register the skin-center dictionaries, the body scope attribute, and the\n * Skin Center as a first-level settings section.\n * @param ctx - client root context.\n */\nexport function apply(ctx: ClientContext): void {\n ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'ui-skin-center: dictionaries')\n\n // The card's own styles scope under this attribute so they keep applying\n // during try-on (when the active skin's attribute is retracted).\n ctx.effect(() => {\n document.body.dataset.dshSkinCenter = ''\n return () => { delete document.body.dataset.dshSkinCenter }\n }, 'ui-skin-center: body scope')\n\n const theme = ctx.get('theme') as ThemeRuntime\n const controller = new TryOnController()\n // Background occluder over the shared skin-background namespace. The scope\n // is bound to this plugin's fiber, so it is torn down with the card.\n const binder = ctx.get('webUiSettings') ?? ctx.settingsScope\n const backgroundScope = binder.bind<{\n enabled?: boolean\n backgroundOpacity?: number\n backgroundBlurEmpty?: number\n backgroundBlurContent?: number\n }>({ namespace: SKIN_BACKGROUND_NS })\n const background = new BackgroundController(backgroundScope)\n // Tear the blur element + observer down when this plugin's fiber goes away.\n ctx.effect(() => () => background.dispose(), 'ui-skin-center: background dispose')\n const injected = (): SkinCenterInjected => ({\n controller,\n theme: {\n getTheme: () => theme.getTheme(),\n subscribe: listener => ctx.on('theme/change', listener),\n setTheme: id => theme.setTheme(id),\n },\n background: {\n enabled: () => background.enabled(),\n setEnabled: value => background.setEnabled(value),\n opacity: () => background.opacity(),\n blurEmpty: () => background.blurEmpty(),\n blurContent: () => background.blurContent(),\n subscribe: listener => background.subscribe(listener),\n set: opacity => background.set(opacity),\n setBlurEmpty: value => background.setBlurEmpty(value),\n setBlurContent: value => background.setBlurContent(value),\n dispose: () => background.dispose(),\n },\n })\n\n ctx.slots.inject('settings.section', () => ctx.slots.register({\n name: 'settings.section',\n id: 'skin-center',\n order: 120,\n label: () => ctx.locale.bind('skinCenter')('title'),\n locale: 'skinCenter',\n inject: injected,\n }, SkinCenterSection))\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"client.js","names":["css"],"sources":["../../../packages/skins/skin-center/src/client/generated/skins.ts","../../../packages/skins/skin-center/src/client/manifest.ts","../../../packages/skins/skin-center/src/client/try-on.ts","../../../packages/skins/skin-center/src/client/SkinCenter.tsx","../../../packages/skins/skin-center/src/client/background.ts","../../../packages/skins/skin-center/src/client/locales.ts","../../../packages/skins/skin-center/src/client/index.ts"],"sourcesContent":["/**\n * AUTO-GENERATED by scripts/skin-center-bundles — do not edit by hand.\n * Skin registry for the GUI skin center: gallery metadata only. Client\n * bundle text is NOT embedded here — try-on fetches each skin's\n * prebuilt lib/client.js on demand from the host route\n * /api/skin-center/bundle/<id> (a same-origin script executed by the\n * page's own module loader; no eval, and the GUI never parses the\n * embedded art base64 at startup).\n * Regenerate with `node scripts/skin-center-bundles`.\n */\n\n/** One registry entry: gallery metadata for the skin center card. */\nexport interface SkinCenterEntry {\n /** Skin id (packages/skins/<id>/). */\n id: string\n /** Display name (zh). */\n name: string\n /** Display name (en). */\n nameEn: string\n /** One-line tagline. */\n tagline: string\n /** Accent color for the swatch. */\n accent: string\n /** Author credit. */\n author?: string\n /** Longer description. */\n description?: string\n /** Tag list. */\n tags?: string[]\n /** Body attribute that scopes the skin styles. */\n bodyAttr: string\n /** Plugin package name (the boot-graph entry id when active). */\n package: string\n /** Gallery sort order (packages/skins/<name>/skin.json). */\n order?: number\n}\n\n/** Every skin, ordered by packages/skins/<name>/skin.json `order`. */\nexport const SKIN_CENTER_ENTRIES: readonly SkinCenterEntry[] = [\n {\"id\":\"blue-fantasy\",\"name\":\"蓝色幻想\",\"nameEn\":\"Blue Fantasy\",\"author\":\"powerdog996(DreamSkin 社区)· dsh-web-ui 适配\",\"tagline\":\"鲸鱼插画背景 · periwinkle 靛蓝调色板 · 半透明面板\",\"description\":\"DreamSkin「DeepSeek-鲸鱼娘」Codex 桌面主题的 dsh 适配:鲸鱼插画背景垫在半透明面板之下,遮罩随亮/暗主题实时切换,periwinkle 靛蓝色调重映射到全部 dsh token。\",\"tags\":[\"dreamskin\",\"whale\",\"indigo\",\"art\",\"translucent\"],\"accent\":\"#4a5fa8\",\"bodyAttr\":\"data-dsh-blue-fantasy\",\"package\":\"@linxin666/dsh-client-ui-skin-blue-fantasy\",\"order\":1},\n\n {\"id\":\"whale-song\",\"name\":\"鲸吟\",\"nameEn\":\"Whale Song\",\"author\":\"dsh-web-ui\",\"tagline\":\"深海鲸语女神背景 · 冰蓝海洋调色板 · 金色细线点缀\",\"description\":\"《鲸吟》— 深海鲸语女神主题:无文字纯氛围背景画(蓝发女神与鲸群居左、冰蓝星座网格与金线点缀、右侧大量留白)垫在半透明面板之下,遮罩随亮/暗主题实时切换,冰蓝/浅青/深海军蓝/钴蓝冷色体系重映射到全部 dsh token,暗色变体为深海夜航调。\",\"tags\":[\"whale\",\"ocean\",\"ice-blue\",\"goddess\",\"art\",\"translucent\"],\"accent\":\"#4d8fd4\",\"bodyAttr\":\"data-dsh-whale-song\",\"package\":\"@linxin666/dsh-client-ui-skin-whale-song\",\"order\":2},\n\n {\"id\":\"harbor\",\"name\":\"夕港\",\"nameEn\":\"Harbor\",\"author\":\"moeblack\",\"tagline\":\"暮光蓝港 · 日落橙辉 · 半透明夜色面板\",\"description\":\"《夕港》黄昏港口主题:动漫少女黄昏港口背景(暮光蓝天空渐入日落橙)垫在半透明面板之下,遮罩随亮/暗主题实时切换,深暮蓝 #141a2e 底与日落橙 #ff9d5c 主色重映射到 dsh token,亮色是薄暮纱、暗色是深海夜航纱,同一幅画两种读法。\",\"tags\":[\"harbor\",\"dusk\",\"twilight\",\"sunset\",\"amber\",\"art\",\"translucent\"],\"accent\":\"#ff9d5c\",\"bodyAttr\":\"data-dsh-harbor\",\"package\":\"@linxin666/dsh-client-ui-skin-harbor\",\"order\":3},\n\n {\"id\":\"qq98\",\"name\":\"QQ2008 怀旧版\",\"nameEn\":\"QQ2008 Retro\",\"author\":\"dsh-web-ui\",\"tagline\":\"水晶蓝桌面 · 玻璃深蓝标题栏 · 戴围巾企鹅\",\"description\":\"dsh web ui 家族收录的第一个皮肤:QQ2008 水晶蓝年代。深蓝渐变桌面、玻璃质感标题栏、浅蓝状态栏和圆角高光控件,配一只戴围巾的企鹅。\",\"tags\":[\"retro\",\"qq\",\"2008\",\"crystal-blue\",\"nostalgia\"],\"accent\":\"#2b7cd9\",\"bodyAttr\":\"data-dsh-retro\",\"package\":\"@linxin666/dsh-client-ui-skin-qq98\",\"order\":4},\n\n {\"id\":\"ths\",\"name\":\"同花顺风格\",\"nameEn\":\"Tonghuashun Trading\",\"author\":\"dsh-web-ui\",\"tagline\":\"品牌红标题栏 · 实时行情状态栏 · 灰蓝数据终端\",\"description\":\"同花顺风格炒股主题:品牌红标题栏带上证指数行情签,状态栏红涨绿跌,自选股风格的侧边栏和交易终端面板,写代码也像盯盘。\",\"tags\":[\"stock\",\"trading\",\"terminal\",\"red\"],\"accent\":\"#e60012\",\"bodyAttr\":\"data-dsh-ths\",\"package\":\"@linxin666/dsh-client-ui-skin-ths\",\"order\":5},\n\n {\"id\":\"xp\",\"name\":\"Windows XP (Luna)\",\"nameEn\":\"Windows XP Luna\",\"author\":\"dsh-web-ui\",\"tagline\":\"Luna 蓝窗口条 · 绿色开始按钮 · Bliss 蓝天桌面\",\"description\":\"Windows XP (Luna) 复古主题:蓝色渐变窗口条带窗口按钮、米色状态栏(大写/数字/滚动指示灯)、侧边栏任务栏上的绿色「开始」按钮、资源管理器风格树行和 Bliss 蓝天桌面,全局直角。\",\"tags\":[\"retro\",\"xp\",\"luna\",\"windows\",\"start-button\"],\"accent\":\"#316ac5\",\"bodyAttr\":\"data-dsh-xp\",\"package\":\"@linxin666/dsh-client-ui-skin-xp\",\"order\":6},\n\n {\"id\":\"dragon-heir\",\"name\":\"龙的传人\",\"nameEn\":\"Dragon Heir\",\"author\":\"dsh-web-ui\",\"tagline\":\"不屈龙魂 · 万里长城双主题 · 朱砂龙印\",\"description\":\"龙的传人 — 一面是不屈龙魂(墨龙穿云、朱砂印章、不屈锋芒),一面是万里长城(青黛山色、金晖镀墙、苍茫暮色)。亮暗主题各自配一幅画与一枚龙印 favicon,面板半透明磨砂,让画透出来。\",\"tags\":[\"dragon\",\"loong\",\"chinese\",\"ink-wash\",\"great-wall\",\"dual-theme\"],\"accent\":\"#c3272b\",\"bodyAttr\":\"data-dsh-dragon-heir\",\"package\":\"@linxin666/dsh-client-ui-skin-dragon-heir\",\"order\":7},\n\n {\"id\":\"minecraft\",\"name\":\"Minecraft 方块世界\",\"nameEn\":\"Minecraft Voxel\",\"author\":\"dsh-web-ui\",\"tagline\":\"动态全景天空盒 · 方块按钮 · 告示牌输入框\",\"description\":\"复刻《我的世界》主界面氛围的方块皮肤:程序化绘制的像素全景天空盒(方块山、像素云、方块树、草方块地面)在身后缓慢旋转,界面浮在石板上;按钮还原 MC 菜单按钮(灰石板、悬停变黄、按下下沉),输入框做成带钉子的木告示牌。\",\"tags\":[\"minecraft\",\"voxel\",\"pixel\",\"game\",\"panorama\",\"skybox\"],\"accent\":\"#7cbd4b\",\"bodyAttr\":\"data-dsh-minecraft\",\"package\":\"@linxin666/dsh-client-ui-skin-minecraft\",\"order\":8},\n\n {\"id\":\"trading\",\"name\":\"交易终端\",\"nameEn\":\"Trading Terminal\",\"author\":\"dsh-web-ui\",\"tagline\":\"实时行情跑马灯 · 长桥港美股行情 · 红涨绿跌交易终端\",\"description\":\"结合 dsh-fun-ticker 行情跑马灯与 dsh-longbridge 港美股行情的炒股皮肤:顶栏滚动 A股/港股/美股/指数/加密/外汇报价(装 fun-ticker 后跟随你的自选列表),状态栏展示长桥行情快照与 A股/港股/美股交易时段,写代码也像盯盘。\",\"tags\":[\"stock\",\"trading\",\"ticker\",\"live\",\"terminal\",\"longbridge\"],\"accent\":\"#f23645\",\"bodyAttr\":\"data-dsh-trading\",\"package\":\"@linxin666/dsh-client-ui-skin-trading\",\"order\":9},\n\n {\"id\":\"miku\",\"name\":\"初音未来 · 电子歌姬\",\"nameEn\":\"Hatsune Miku\",\"author\":\"涂山苏苏\",\"tagline\":\"蓝紫双马尾 · 01 编号 · 音符波形 · 电子歌姬主题\",\"description\":\"以世界第一的虚拟歌姬初音未来为灵感的主题皮肤:蓝紫洋红渐变贯穿全局,音符与声波曲线点缀在半透明面板之间,标题栏与状态栏带有 01 编号徽标与音乐波形,半透明毛玻璃面板透出背景图——沉浸式电子歌姬氛围。\",\"tags\":[\"miku\",\"vocaloid\",\"blue\",\"music\",\"idol\",\"waveform\"],\"accent\":\"#2e9bff\",\"bodyAttr\":\"data-dsh-miku\",\"package\":\"@linxin666/dsh-client-ui-skin-miku\",\"order\":10},\n\n {\"id\":\"whale-mom\",\"name\":\"鲸鱼妈妈\",\"nameEn\":\"Whale Mom\",\"author\":\"dsh-web-ui\",\"tagline\":\"深海鲸鱼妈妈与幼崽的画作 · 全透面板由背景遮挡驱动 · 金线点缀\",\"description\":\"dsh web ui 的鲸鱼妈妈主题皮肤:深海鲸鱼妈妈与幼崽们的氛围画作铺满视口,面板全透(透明度由设置-皮肤中心的背景遮挡滑杆驱动,侧边栏基准透明度可用 --dsw-skin-sidebar-alpha 微调),深蓝 / 奶油 / 金色配色贯穿全局,暗色是深海夜航。\",\"tags\":[\"ocean\",\"whale\",\"mother\",\"translucent\",\"gold\"],\"accent\":\"#d9a53c\",\"bodyAttr\":\"data-dsh-whale-mom\",\"package\":\"@linxin666/dsh-client-ui-skin-whale-mom\",\"order\":11},\n\n {\"id\":\"matrix\",\"name\":\"Matrix 骇客帝国\",\"nameEn\":\"Matrix\",\"author\":\"contributed (seanchen)\",\"tagline\":\"深夜护眼 · 墨绿暗色 · 数字雨\",\"description\":\"深夜卧室场景的护眼暗色皮肤:近黑墨绿背景、墨绿等宽字体、低透明度数字雨。强制暗色(不跟随系统主题),整体低亮度,长时间使用不刺眼、不打扰家人睡觉。\",\"tags\":[\"matrix\",\"dark\",\"green\",\"hacker\",\"night\",\"eye-care\"],\"accent\":\"#00e676\",\"bodyAttr\":\"data-dsh-matrix\",\"package\":\"@linxin666/dsh-client-ui-skin-matrix\",\"order\":12},\n\n]\n","/**\n * Boot-manifest readiness checks for the one-click apply flow.\n *\n * The host half writes the skin patch synchronously, but the web app's boot\n * graph (the `window.__DSH_BOOT__` JSON inside the served HTML) is\n * regenerated asynchronously by the config watcher. A page reloaded right\n * after the patch write can therefore boot into the previous skin. These\n * helpers let the frontend poll the served document until the manifest\n * actually reflects the target before reloading.\n * @module @linxin666/dsh-client-ui-skin-center/manifest\n */\n\n/** Bundle URL pattern of any skin entry in the boot manifest. */\nconst SKIN_BUNDLE_URL = /\\/plugins\\/@linxin666\\/dsh-client-ui-skin-(?!center)[a-z0-9-]+\\/client\\.js/\n\n/**\n * Whether a served GUI document's boot manifest enables the given skin.\n * A `null` target means the stock look: no skin bundle URL may be present\n * (the skin-center plugin's own bundle always loads and is excluded).\n * @param documentHtml - the served GUI document (contains the boot JSON).\n * @param target - skin id, or `null` for the stock look.\n * @returns whether the manifest already enables the target.\n */\nexport function manifestHasSkin(documentHtml: string, target: string | null): boolean {\n if (target === null) return !SKIN_BUNDLE_URL.test(documentHtml)\n return documentHtml.includes(`/plugins/@linxin666/dsh-client-ui-skin-${target}/client.js`)\n}\n","/**\n * Try-on engine for the in-GUI skin center.\n *\n * A skin's client bundle is executed through the REAL module system, not a\n * shim and not eval: the host route `/api/skin-center/bundle/<id>` serves\n * the skin's prebuilt `lib/client.js` as a same-origin script (mirroring\n * the kernel's own defaultLoadBundle — see dsh-client-modules), and its\n * body calls `window.__ModuleLoader__.load({id, factory})`, which only\n * REGISTERS the factory. `window.__DSH_MODULES__.import(package)` (the\n * kernel's ClientModuleSystem, contract C5/C6) then materializes it — which\n * auto-injects the skin's CSS `<style data-plugin>` tag — and\n * `surface.apply(miniCtx)` mounts the skin exactly as the fiber system\n * would, returning a full disposer. That makes try-on and its teardown the\n * real code paths, with no CSP `unsafe-eval` dependence and no startup\n * cost: the ~700KB of embedded art base64 is only parsed when a skin is\n * actually tried on.\n *\n * Mutual exclusion: the GUI never hosts two skins at once. The currently\n * ACTIVE skin is owned by its own cordis fiber (its disposer is not\n * reachable), so try-on retracts the active skin's visual writes by recipe:\n * remove its body attribute (its stylesheet goes inert), clear the\n * body-level backdrop inline styles (blue-fantasy's whale art), detach only\n * known skin chrome body children (title/status bars marked `data-skin-chrome`\n * or carrying the skin's body attribute, leaving other plugins' portals and\n * toasts in place), and neutralize known global-rule leaks (xp's sidebar\n * taskbar/start; matrix's full-viewport rain canvas, which has no scoping\n * attribute — matrix's forced-dark observer goes inert on its own once the\n * body attribute is retracted). Everything is snapshotted and restored on\n * exit in original order. The active skin's own fiber is never touched, so\n * exiting try-on returns the page to exactly the pre-try-on state.\n *\n * A ghost MutationObserver may survive retraction (blue-fantasy re-writes\n * its backdrop on theme flips), so during try-on a neutralizing observer\n * re-clears the backdrop props whenever `data-ds-dark-theme` changes.\n */\n\nimport { SKIN_CENTER_ENTRIES, type SkinCenterEntry } from './generated/skins.ts'\n\n/** Body-level backdrop properties skins may write inline (blue-fantasy). */\nconst BACKDROP_PROPS = [\n 'background-image',\n 'background-position',\n 'background-size',\n 'background-attachment',\n 'background-repeat',\n] as const\n\n/**\n * Per-skin neutralization CSS: rules that hide visual leaks whose styles\n * are NOT scoped under the skin's body attribute (they live on app elements\n * the skin touches, so detaching chrome cannot remove them). Matched by\n * css-module class substring, which is stable across rebuilds.\n */\nconst NEUTRALIZE_CSS: Record<string, string> = {\n // xp styles its start button + taskbar strip in the sidebar footer with\n // top-level (unscoped) rules; its apply() removes them on dispose, but\n // during try-on its ghost observer would re-add them, so hide instead.\n xp: [\n `[data-pane='sidebar'] [class*='xpTaskbar']{background:transparent!important;border-top:none!important;box-shadow:none!important}`,\n `[data-pane='sidebar'] [class*='xpStart']{display:none!important}`,\n ].join(''),\n // matrix mounts a fixed full-viewport rain canvas with an extreme z-index\n // and no scoping attribute of its own, so detaching chrome cannot reach\n // it; hide it for the try-on session. Its forced-dark observer goes inert\n // by itself once `data-dsh-matrix` is retracted (the skin's callback\n // refuses to act without its own marker).\n matrix: [\n `[data-plugin='dsh-matrix-skin']{display:none!important}`,\n ].join(''),\n}\n\n/** The window surfaces the boot protocol installs (manifest.ts contract). */\ninterface SkinCenterWindow {\n __DSH_BOOT__?: { entries?: Array<{ id: string }> }\n __DSH_MODULES__?: {\n import(specifier: string): Promise<unknown>\n invalidate(id: string): void\n }\n}\n\n/** Host base path of the skin bundle route (registered by src/routes.ts). */\nconst BUNDLE_ROUTE = '/api/skin-center/bundle'\n\n/**\n * Execute one skin's client bundle as a real same-origin script, mirroring\n * the kernel's own defaultLoadBundle (dsh-client-modules): the script body\n * calls `window.__ModuleLoader__.load({id, factory})`, which only registers\n * the factory — materialization is the caller's separate `import` step. No\n * eval: try-on works under any CSP that allows same-origin scripts (the\n * shell itself loads plugin bundles this way), and a failed fetch rejects\n * so the caller can restore the active skin instead of leaving it retracted.\n * @param url - same-origin bundle URL.\n * @returns a promise resolving once the script executed.\n */\nfunction loadBundleScript(url: string): Promise<void> {\n return new Promise((resolve, reject) => {\n const el = document.createElement('script')\n el.async = true\n el.src = url\n el.addEventListener('load', () => {\n el.remove()\n resolve()\n }, { once: true })\n el.addEventListener('error', () => {\n el.remove()\n reject(new Error(`skin-center: bundle script ${url} failed to load`))\n }, { once: true })\n document.head.append(el)\n })\n}\n\n/** Read the page's composed boot-graph entry ids (only enabled plugins appear). */\nfunction bootEntryIds(): string[] {\n const boot = (window as SkinCenterWindow).__DSH_BOOT__\n return boot?.entries?.map(entry => entry.id) ?? []\n}\n\n/** The skin package currently ACTIVE in the boot graph, if it is one of ours. */\nexport function activeSkinEntry(): SkinCenterEntry | undefined {\n const ids = new Set(bootEntryIds())\n return SKIN_CENTER_ENTRIES.find(entry => ids.has(entry.package))\n}\n\n/**\n * Whether a direct body child is skin chrome owned by `skin`: marked with the\n * `data-skin-chrome` marker (minecraft/dragon-heir) or carrying the skin's\n * scoping body attribute. Everything else — other plugins' portals, toasts and\n * overlays appended to body — is left alone.\n */\nfunction isSkinChrome(el: Element, skin: SkinCenterEntry | null): boolean {\n if (el.hasAttribute('data-skin-chrome')) return true\n return skin !== null && el.hasAttribute(skin.bodyAttr)\n}\n\n/** Minimal ctx the skins' apply() needs: cordis effect lifecycle only. */\ninterface MiniCtx {\n effect(callback: () => () => void, label?: string): () => void\n /**\n * Service reads are answered with undefined: try-on is a pure-DOM stage,\n * so optional service access (e.g. ths reading the connection handle)\n * degrades to its fallback instead of throwing mid-apply.\n */\n get(key: string): unknown\n /** Hidden handle for the controller: run the disposers in reverse order. */\n __disposeAll(): void\n}\n\nfunction miniCtx(): MiniCtx {\n const disposers: Array<() => void> = []\n return {\n effect(callback) {\n disposers.push(callback())\n return () => {}\n },\n get() {\n return undefined\n },\n __disposeAll(): void {\n for (const dispose of disposers.reverse()) dispose()\n },\n }\n}\n\n/** Snapshot of the active skin's visual writes, restored on try-on exit. */\ninterface ActiveVisuals {\n skin: SkinCenterEntry | null\n /** Attribute value before retraction (null = attribute absent). */\n bodyAttr: string | null\n /** body inline style before retraction (null = none). */\n bodyStyle: string | null\n /** Skin chrome elements detached from body, re-inserted at their anchors. */\n detached: Array<{ el: HTMLElement; anchor: Node | null }>\n /** Neutralizes ghost backdrop writes while a try-on is live. */\n clearObserver: MutationObserver | null\n /** Hides global-rule leaks of the active skin (xp taskbar). */\n neutralizeStyle: HTMLStyleElement | null\n}\n\n/**\n * One live try-on session: owns the tried-on skin's disposer plus the\n * captured active-skin visuals, and restores everything on exit.\n */\nexport class TryOnController {\n private session: {\n /** The tried-on skin, or null when trying on the official stock look. */\n entry: SkinCenterEntry | null\n dispose: () => void\n active: ActiveVisuals\n } | null = null\n\n /**\n * Generation counter. A newer try-on or exit increments it, so an in-flight\n * `tryOn` (awaiting the real bundle load) can detect it was superseded and\n * drop only what it mounted instead of clobbering the newer session.\n */\n private epoch = 0\n\n /**\n * One package can be requested again before its first script load finishes\n * (for example A -> B -> A). Share that materialization so two script tags\n * never race to register the same module factory.\n */\n private readonly pendingModules = new Map<string, Promise<(ctx: unknown) => unknown>>()\n\n /**\n * Package selected by the newest async request. A superseded request for the\n * same package must not invalidate the module/style now owned by that newer\n * request when their shared load settles.\n */\n private requestedPackage: string | null = null\n\n /**\n * Loads one skin's client bundle so its factory registers on the page's\n * `__ModuleLoader__`. Defaults to a same-origin script tag from the host\n * route `/api/skin-center/bundle/<id>`; tests inject a stub.\n */\n private readonly loadBundle: (entry: SkinCenterEntry) => Promise<void>\n\n constructor(options: { loadBundle?: (entry: SkinCenterEntry) => Promise<void> } = {}) {\n this.loadBundle = options.loadBundle ?? (entry => loadBundleScript(`${BUNDLE_ROUTE}/${encodeURIComponent(entry.id)}`))\n }\n /** The skin currently being tried on, if any. */\n get trying(): SkinCenterEntry | null {\n return this.session?.entry ?? null\n }\n\n /** Whether the official stock look (no skin) is being tried on. */\n get tryingOfficial(): boolean {\n return this.session !== null && this.session.entry === null\n }\n\n /**\n * Start trying on `entry` (replaces any live session).\n *\n * When another skin is already being tried on, keep it mounted while the\n * next bundle loads. Once the target is ready, tear down the old preview and\n * mount the new one against the SAME captured active-skin snapshot. This\n * avoids the expensive preview -> active -> preview round trip and prevents\n * a flash of the active skin between consecutive try-ons.\n * @returns whether this request mounted the target (false when superseded).\n */\n async tryOn(entry: SkinCenterEntry): Promise<boolean> {\n if (entry.package === activeSkinEntry()?.package) return false\n const epoch = ++this.epoch\n this.requestedPackage = entry.package\n\n let apply: (ctx: unknown) => unknown\n try {\n // Keep either the active skin or the current preview visible while the\n // target script downloads, parses and materializes its module.\n apply = await this.loadModuleOnce(entry)\n } catch (error) {\n if (this.shouldCleanupRequest(entry, epoch)) this.cleanupModule(entry)\n throw error\n }\n if (epoch !== this.epoch) {\n if (this.shouldCleanupRequest(entry, epoch)) this.cleanupModule(entry)\n return false\n }\n\n const previous = this.session\n let active: ActiveVisuals\n if (previous === null) {\n active = this.captureAndRetractActive()\n } else {\n // Transfer ownership of the original active-skin snapshot directly to\n // the next preview; restoring and immediately retracting it is wasted\n // DOM work and is the visible pause reported during chained try-ons.\n this.session = null\n previous.dispose()\n if (previous.entry !== null) this.cleanupModule(previous.entry)\n active = previous.active\n }\n\n let dispose: () => void\n try {\n dispose = this.applyLoaded(entry, apply)\n } catch (error) {\n if (epoch === this.epoch) this.restoreActive(active)\n throw error\n }\n this.session = { entry, dispose, active }\n return true\n }\n\n /**\n * Try on the official stock look: retract the active skin's visual writes\n * (same recipe as a skin try-on) and mount nothing. Exiting restores the\n * active skin exactly like any other try-on session.\n */\n tryOnOfficial(): void {\n if (activeSkinEntry() === null) return\n this.epoch += 1\n this.requestedPackage = null\n const previous = this.session\n if (previous !== null) {\n // A live preview already owns the captured active visuals. Dispose only\n // that preview and reuse the snapshot instead of restoring and retracting\n // the active skin in two consecutive layout-heavy operations.\n this.session = null\n previous.dispose()\n if (previous.entry !== null) this.cleanupModule(previous.entry)\n this.session = { entry: null, dispose: () => {}, active: previous.active }\n return\n }\n const active: ActiveVisuals = this.captureAndRetractActive()\n this.session = { entry: null, dispose: () => {}, active }\n }\n\n /** Exit the live session: dispose the tried-on skin, then restore the active skin. */\n exit(): void {\n // Also invalidate an in-flight initial/chained load. The visible session\n // may already be null by the time a user exits, but its late completion\n // must never remount a skin after cancellation.\n this.epoch += 1\n this.requestedPackage = null\n const session = this.session\n if (session === null) return\n this.session = null\n session.dispose()\n if (session.entry !== null) this.cleanupModule(session.entry)\n this.restoreActive(session.active)\n }\n\n /** Share one materialization while repeated requests for a package overlap. */\n private loadModuleOnce(entry: SkinCenterEntry): Promise<(ctx: unknown) => unknown> {\n const existing = this.pendingModules.get(entry.package)\n if (existing !== undefined) return existing\n\n const pending = this.loadModule(entry)\n this.pendingModules.set(entry.package, pending)\n void pending.then(\n () => {\n if (this.pendingModules.get(entry.package) === pending) this.pendingModules.delete(entry.package)\n },\n () => {\n if (this.pendingModules.get(entry.package) === pending) this.pendingModules.delete(entry.package)\n },\n )\n return pending\n }\n\n /** Whether this request still owns cleanup of the package module/style. */\n private shouldCleanupRequest(entry: SkinCenterEntry, epoch: number): boolean {\n return epoch === this.epoch || this.requestedPackage !== entry.package\n }\n\n /** Execute + materialize the target skin through the real loader. */\n private async loadModule(entry: SkinCenterEntry): Promise<(ctx: unknown) => unknown> {\n const modules = (window as SkinCenterWindow).__DSH_MODULES__\n if (modules === undefined) throw new Error('skin-center: window.__DSH_MODULES__ missing')\n // This try-on session owns the module record for the package for its\n // whole life (a try-on of the ACTIVE skin is rejected above, and the GUI\n // never hosts two skins at once). Drop any factory a crashed earlier\n // session left behind so the script below registers a fresh one — the\n // duplicate-registration retry dance is unnecessary with the real\n // loader, whose load() throws on a duplicate id.\n modules.invalidate(entry.package)\n await this.loadBundle(entry)\n const surface = await modules.import(entry.package)\n const apply = (surface as { apply?: (ctx: unknown) => unknown }).apply\n if (typeof apply !== 'function') {\n throw new Error(`skin-center: \"${entry.package}\" client bundle exports no apply`)\n }\n return apply\n }\n\n /** Apply a module that has already been loaded while the active skin was visible. */\n private applyLoaded(entry: SkinCenterEntry, apply: (ctx: unknown) => unknown): () => void {\n const ctx = miniCtx()\n try {\n apply(ctx)\n } catch (error) {\n // A skin that throws mid-apply leaves partial writes behind (body\n // attribute, chrome, style tag) and never registers its disposer —\n // the tryOn catch only restores the ACTIVE skin. Roll the residue back\n // here so a crashed try-on can never bleed into the next one.\n this.cleanupModule(entry)\n document.body.removeAttribute(entry.bodyAttr)\n for (const el of [...document.body.children] as HTMLElement[]) {\n if (isSkinChrome(el, entry)) el.remove()\n }\n throw error\n }\n return ctx.__disposeAll\n }\n\n /** Drop the tried-on module record + its injected style tag. */\n private cleanupModule(entry: SkinCenterEntry): void {\n const modules = (window as SkinCenterWindow).__DSH_MODULES__\n modules?.invalidate(entry.package)\n for (const el of document.querySelectorAll(`style[data-plugin=${JSON.stringify(entry.package)}]`)) {\n el.remove()\n }\n }\n\n /**\n * Snapshot the active skin's visual writes and retract them so the tried-on\n * skin can take over the whole surface.\n */\n private captureAndRetractActive(): ActiveVisuals {\n const skin = activeSkinEntry() ?? null\n const body = document.body\n const bodyAttr = skin === null ? null : body.getAttribute(skin.bodyAttr)\n if (skin !== null && bodyAttr !== null) body.removeAttribute(skin.bodyAttr)\n\n const bodyStyle = body.getAttribute('style')\n for (const prop of BACKDROP_PROPS) body.style.removeProperty(prop)\n\n // Detach only known skin chrome (marker/bodyAttr), leaving other\n // plugins' portals/toasts in place; remember each element's next sibling\n // so restore re-inserts in the original order.\n const children = [...body.children] as HTMLElement[]\n const chrome = new Set<HTMLElement>()\n for (const el of children) {\n if (el.id !== 'root' && isSkinChrome(el, skin)) chrome.add(el)\n }\n const detached: Array<{ el: HTMLElement; anchor: Node | null }> = []\n for (let i = 0; i < children.length; i++) {\n const el = children[i]\n if (!chrome.has(el)) continue\n let anchor: Node | null = null\n for (let j = i + 1; j < children.length; j++) {\n if (!chrome.has(children[j])) { anchor = children[j]; break }\n }\n detached.push({ el, anchor })\n }\n for (const { el } of detached) el.remove()\n\n // Neutralize a surviving ghost observer (blue-fantasy's backdrop\n // re-writer) across theme flips during the try-on.\n const clearObserver = new MutationObserver(() => {\n for (const prop of BACKDROP_PROPS) body.style.removeProperty(prop)\n })\n clearObserver.observe(body, { attributes: true, attributeFilter: ['data-ds-dark-theme'] })\n\n const neutralizeCss = skin === null ? undefined : NEUTRALIZE_CSS[skin.id]\n const neutralizeStyle = neutralizeCss === undefined ? null : this.injectStyle(neutralizeCss)\n\n return { skin, bodyAttr, bodyStyle, detached, clearObserver, neutralizeStyle }\n }\n\n /** Restore the active skin's captured visual state. */\n private restoreActive(active: ActiveVisuals): void {\n const body = document.body\n if (active.skin !== null && active.bodyAttr !== null) {\n body.setAttribute(active.skin.bodyAttr, active.bodyAttr)\n }\n if (active.bodyStyle !== null) {\n body.setAttribute('style', active.bodyStyle)\n } else {\n body.removeAttribute('style')\n }\n for (const { el, anchor } of active.detached) {\n body.insertBefore(el, anchor !== null && anchor.parentNode === body ? anchor : null)\n }\n active.clearObserver?.disconnect()\n active.neutralizeStyle?.remove()\n }\n\n private injectStyle(css: string): HTMLStyleElement {\n const tag = document.createElement('style')\n tag.dataset.skinCenterNeutralize = ''\n tag.textContent = css\n document.head.append(tag)\n return tag\n }\n}\n","/**\n * The skin-center card: rendered as the content of a first-level settings\n * section, listing every installed skin plus the official stock look. Live\n * try-on executes the real bundle inside the GUI (light/dark preview, full\n * restore on exit); Apply is one click — the host half runs `dsh-skin use`\n * through /api/skin-center/apply, the config watcher hot-reloads the patch,\n * and the page reloads into the new skin. Copy rides the standard `t` seat;\n * the theme preview control drives the official theme service (persisted,\n * same as the Appearance row).\n */\nimport { useEffect, useRef, useState, useSyncExternalStore, type ReactNode } from 'react'\nimport type { PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots'\nimport type { ThemeSnapshot } from '@deepseek-ai/dsh-client-ui-theme/client'\nimport { SKIN_CENTER_ENTRIES, type SkinCenterEntry } from './generated/skins.ts'\nimport { manifestHasSkin } from './manifest.ts'\nimport type { SkinBackgroundHandle } from './background.ts'\nimport { activeSkinEntry, TryOnController } from './try-on.ts'\nimport css from './skin-center.module.css'\n\n/** Business face the skin-center apply() injects into the card. */\nexport interface SkinCenterInjected {\n controller: TryOnController\n theme: {\n getTheme(): ThemeSnapshot\n subscribe(listener: () => void): () => void\n setTheme(id: 'light' | 'dark'): void\n }\n /** Background occluder over the shared skin-background namespace. */\n background: SkinBackgroundHandle\n}\n\n/** Plugin-card component props: locale seat + injected face. */\nexport type SkinCenterComponentProps =\n PropsLocale<'skinCenter'> & SkinCenterInjected\n\n/** The apply target of the official stock-look card. */\nconst OFFICIAL = 'official'\n\n/** Skin ids that read the background-scrim variable and paint a backdrop. */\nconst BACKDROP_SKIN_IDS = new Set(['blue-fantasy', 'whale-song', 'whale-mom'])\n\n/**\n * Render the skin-center card: a static header naming the plugin, with the\n * always-visible skin list (official default + every installed skin; try-on /\n * theme preview / one-click apply) rendered below it.\n * @param props - card props.\n * @returns the plugin card.\n */\nexport function SkinCenter({ t, controller, theme, background }: SkinCenterComponentProps) {\n const snapshot = useSyncExternalStore(theme.subscribe, theme.getTheme)\n const enabled = useSyncExternalStore(background.subscribe, background.enabled)\n const opacity = useSyncExternalStore(background.subscribe, background.opacity)\n const blurEmpty = useSyncExternalStore(background.subscribe, background.blurEmpty)\n const blurContent = useSyncExternalStore(background.subscribe, background.blurContent)\n const activePackage = activeSkinEntry()?.package\n const activeId = activeSkinEntry()?.id\n const backdropActive = activeId !== undefined && BACKDROP_SKIN_IDS.has(activeId)\n const [tryingId, setTryingId] = useState<string | null>(null)\n const [tryingOfficial, setTryingOfficial] = useState(false)\n const [loadingId, setLoadingId] = useState<string | null>(null)\n const [applying, setApplying] = useState<string | null>(null)\n const [error, setError] = useState<string | null>(null)\n // Unmount guard for the confirmation poll: once the card is gone, the\n // pending timers must stop and no reload / setState may fire.\n const mounted = useRef(false)\n // Latest-click-wins token for async bundle loads. A chained try-on or exit\n // invalidates older completions so a slow bundle can never overwrite the\n // UI state chosen by a newer click.\n const tryOnRequest = useRef(0)\n useEffect(() => {\n mounted.current = true\n return () => { mounted.current = false }\n }, [])\n\n const tryOn = (entry: SkinCenterEntry): void => {\n if (loadingId === entry.id) return\n const request = ++tryOnRequest.current\n setError(null)\n setLoadingId(entry.id)\n void controller.tryOn(entry)\n .then(mountedTarget => {\n if (!mounted.current || request !== tryOnRequest.current || !mountedTarget) return\n setLoadingId(null)\n setTryingId(entry.id)\n setTryingOfficial(false)\n })\n .catch(() => {\n if (!mounted.current || request !== tryOnRequest.current) return\n // A load failure keeps the previous preview mounted; a mount failure\n // restores the original active skin. Mirror the controller's actual\n // session instead of blindly clearing a preview that may still exist.\n setLoadingId(null)\n setError(t('tryOnError'))\n setTryingId(controller.trying?.id ?? null)\n setTryingOfficial(controller.tryingOfficial)\n })\n }\n\n const tryOnOfficial = (): void => {\n ++tryOnRequest.current\n setError(null)\n setLoadingId(null)\n try {\n controller.tryOnOfficial()\n } catch {\n setError(t('tryOnError'))\n setTryingOfficial(false)\n return\n }\n setTryingId(null)\n setTryingOfficial(true)\n }\n\n const exitTryOn = (): void => {\n ++tryOnRequest.current\n controller.exit()\n setLoadingId(null)\n setTryingId(null)\n setTryingOfficial(false)\n }\n\n /**\n * Poll the host state until the config watcher reports the target active\n * (the patch write lands before the watcher re-applies it), or time out.\n * @param target - skin id, or `official` for the stock look.\n * @returns whether the target became active within the poll budget.\n */\n const confirmActive = (target: string): Promise<boolean> =>\n new Promise(resolve => {\n const expected = target === OFFICIAL ? 'none' : target\n let tries = 0\n const tick = (): void => {\n if (!mounted.current) {\n resolve(false)\n return\n }\n tries += 1\n void fetch('/api/skin-center/state')\n .then(async response => {\n const payload = await response.json().catch(() => null) as { ok?: boolean; active?: string } | null\n if (response.ok && payload?.ok === true && payload.active === expected) {\n resolve(true)\n return\n }\n if (tries >= 20 || !mounted.current) resolve(false)\n else window.setTimeout(tick, 250)\n })\n .catch(() => {\n if (tries >= 20 || !mounted.current) resolve(false)\n else window.setTimeout(tick, 250)\n })\n }\n tick()\n })\n\n /**\n * Poll the served GUI document until the boot manifest actually enables\n * the target (the config watcher regenerates it asynchronously after the\n * patch write — reloading earlier boots the page into the previous skin),\n * or time out.\n * @param target - skin id, or `official` for the stock look.\n * @returns whether the manifest caught up within the poll budget.\n */\n const manifestReady = (target: string): Promise<boolean> =>\n new Promise(resolve => {\n const expected = target === OFFICIAL ? null : target\n let tries = 0\n const tick = (): void => {\n if (!mounted.current) {\n resolve(false)\n return\n }\n tries += 1\n void fetch(window.location.href, { cache: 'no-store' })\n .then(async response => {\n const html = await response.text().catch(() => null)\n if (html !== null && manifestHasSkin(html, expected)) {\n resolve(true)\n return\n }\n if (tries >= 40 || !mounted.current) resolve(false)\n else window.setTimeout(tick, 500)\n })\n .catch(() => {\n if (tries >= 40 || !mounted.current) resolve(false)\n else window.setTimeout(tick, 500)\n })\n }\n tick()\n })\n\n /**\n * One-click apply: the host half runs `dsh-skin use <target>` (or\n * `use official`), the config watcher hot-reloads the patch within\n * seconds, then this page reloads to pick up the new boot graph. The\n * reload waits for both the patch (state poll) and the regenerated boot\n * manifest (manifest poll) so the page never boots into the old skin.\n * @param target - skin id, or `official` for the stock look.\n */\n const applySkin = (target: string): void => {\n setError(null)\n setApplying(target)\n const body = target === OFFICIAL ? { official: true } : { skin: target }\n void fetch('/api/skin-center/apply', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(body),\n })\n .then(async response => {\n const payload = await response.json().catch(() => null) as { ok?: boolean; error?: string } | null\n if (!response.ok || payload?.ok !== true) {\n throw new Error(payload?.error ?? `HTTP ${response.status}`)\n }\n setApplying(null)\n // Patch written; reload only once the watcher reports the target\n // active AND the boot manifest caught up, so the page never boots\n // into the old skin.\n void confirmActive(target).then(confirmed => {\n if (!mounted.current) return\n if (!confirmed) {\n const command = target === OFFICIAL ? 'dsh-skin use official' : `dsh-skin use ${target}`\n setError(`${t('appliedUnconfirmed')} — ${command}`)\n return\n }\n void manifestReady(target).then(ready => {\n if (!mounted.current) return\n if (ready) {\n window.location.reload()\n } else {\n const command = target === OFFICIAL ? 'dsh-skin use official' : `dsh-skin use ${target}`\n setError(`${t('appliedUnconfirmed')} — ${command}`)\n }\n })\n })\n })\n .catch((cause: unknown) => {\n setApplying(null)\n const detail = cause instanceof Error ? cause.message : String(cause)\n const command = target === OFFICIAL ? 'dsh-skin use official' : `dsh-skin use ${target}`\n setError(`${t('applyFailed')} (${detail}) — ${command}`)\n })\n }\n\n const dark = snapshot.active.colorScheme === 'dark'\n\n /** One row: try-on control + apply button. Shared by the official card and every skin card. */\n const actionButtons = (opts: {\n key: string\n isActive: boolean\n isTrying: boolean\n onTryOn: () => void\n applyLabel: string\n }): ReactNode => (\n <div className={css.actions}>\n {opts.isActive ? (\n <button type=\"button\" className={`${css.button} ${css.buttonGhost}`} disabled>\n {t('tryOn')}\n </button>\n ) : opts.isTrying ? (\n <button type=\"button\" className={`${css.button} ${css.buttonPrimary}`} onClick={exitTryOn}>\n {t('exitTryOn')}\n </button>\n ) : (\n <button\n type=\"button\"\n className={`${css.button} ${css.buttonPrimary}`}\n disabled={loadingId === opts.key}\n onClick={opts.onTryOn}\n >\n {loadingId === opts.key ? t('loading') : t('tryOn')}\n </button>\n )}\n <button\n type=\"button\"\n className={css.button}\n disabled={applying !== null || loadingId !== null}\n onClick={() => { applySkin(opts.key) }}\n >\n {applying === opts.key ? t('applying') : opts.applyLabel}\n </button>\n </div>\n )\n\n return (\n <li className={css.pluginCard}>\n <div className={css.cardHeaderStatic}>\n <span className={css.headText}>\n <span className={css.pluginName}>\n {t('title')}\n <span className={css.titleBadge}>{String(SKIN_CENTER_ENTRIES.length)}</span>\n </span>\n <span className={css.cardDescription} title={t('cardDescription')}>{t('cardDescription')}</span>\n </span>\n </div>\n\n <div className={css.cardBody}>\n <div className={css.enableRow}>\n <span className={css.enableLabel} title={t('enabled')}>{t('enabled')}</span>\n <button\n type=\"button\"\n role=\"switch\"\n aria-checked={enabled}\n aria-label={t('enabled')}\n className={enabled ? css.switch + ' ' + css.switchOn : css.switch}\n onClick={() => { background.setEnabled(!enabled) }}\n >\n <span className={css.switchThumb} />\n </button>\n <p className={css.enableHint}>{t('enabledHint')}</p>\n </div>\n {enabled\n ? (\n <>\n <div className={css.head}>\n <div className={css.intro} title={t('intro')}>{t('intro')}</div>\n <div className={css.themeRow}>\n <span className={css.themeLabel}>{t('theme')}</span>\n <button\n type=\"button\"\n className={`${css.themeButton} ${dark ? '' : css.themeButtonActive}`}\n onClick={() => { theme.setTheme('light') }}\n >\n {t('themeLight')}\n </button>\n <button\n type=\"button\"\n className={`${css.themeButton} ${dark ? css.themeButtonActive : ''}`}\n onClick={() => { theme.setTheme('dark') }}\n >\n {t('themeDark')}\n </button>\n </div>\n </div>\n\n <div className={css.backgroundRow}>\n <div className={css.backgroundHead}>\n <span className={css.backgroundLabel}>{t('backgroundOpacity')}</span>\n <span className={css.backgroundValue} aria-hidden=\"true\">{opacity}%</span>\n </div>\n <input\n id=\"skin-center-background-opacity\"\n className={css.backgroundRange}\n type=\"range\"\n min=\"0\"\n max=\"100\"\n step=\"5\"\n value={opacity}\n aria-valuetext={`${opacity}%`}\n aria-label={t('backgroundOpacity')}\n onChange={(event) => { background.set(Number(event.target.value)) }}\n />\n <p className={backdropActive ? css.backgroundHint : css.backgroundHintMuted}>\n {backdropActive ? t('backgroundHint') : t('backgroundHintInert')}\n </p>\n </div>\n <div className={css.backgroundRow}>\n <div className={css.backgroundHead}>\n <span className={css.backgroundLabel}>{t('backgroundBlurEmpty')}</span>\n <span className={css.backgroundValue} aria-hidden=\"true\">{blurEmpty}px</span>\n </div>\n <input\n id=\"skin-center-background-blur-empty\"\n className={css.backgroundRange}\n type=\"range\"\n min=\"0\"\n max=\"20\"\n step=\"1\"\n value={blurEmpty}\n aria-valuetext={`${blurEmpty}px`}\n aria-label={t('backgroundBlurEmpty')}\n onChange={(event) => { background.setBlurEmpty(Number(event.target.value)) }}\n />\n <div className={css.backgroundHead}>\n <span className={css.backgroundLabel}>{t('backgroundBlurContent')}</span>\n <span className={css.backgroundValue} aria-hidden=\"true\">{blurContent}px</span>\n </div>\n <input\n id=\"skin-center-background-blur-content\"\n className={css.backgroundRange}\n type=\"range\"\n min=\"0\"\n max=\"20\"\n step=\"1\"\n value={blurContent}\n aria-valuetext={`${blurContent}px`}\n aria-label={t('backgroundBlurContent')}\n onChange={(event) => { background.setBlurContent(Number(event.target.value)) }}\n />\n <p className={backdropActive ? css.backgroundHint : css.backgroundHintMuted}>\n {backdropActive ? t('backgroundBlurHint') : t('backgroundBlurInert')}\n </p>\n </div>\n\n\n {error !== null && <div className={css.error}>{error}</div>}\n\n <div className={css.list}>\n {(() => {\n const isActive = activePackage === undefined\n const isTrying = tryingOfficial\n const badge = isActive ? t('active') : isTrying ? t('tryingOn') : null\n return (\n <div className={css.card} key={OFFICIAL}>\n <div className={css.cardHead}>\n <span className={css.swatch} style={{ background: '#98a1ab' }} aria-hidden=\"true\" />\n <span className={css.cardName} title={t('official')}>{t('official')}</span>\n {badge !== null && (\n <span className={`${css.badge} ${isActive ? css.badgeActive : css.badgeTrying}`}>\n {badge}\n </span>\n )}\n </div>\n <div className={css.cardTagline} title={t('officialTagline')}>{t('officialTagline')}</div>\n {actionButtons({\n key: OFFICIAL,\n isActive,\n isTrying,\n onTryOn: tryOnOfficial,\n applyLabel: t('restore'),\n })}\n </div>\n )\n })()}\n\n {SKIN_CENTER_ENTRIES.map(entry => {\n const isActive = entry.package === activePackage\n const isTrying = entry.id === tryingId\n const badge = isActive ? t('active') : isTrying ? t('tryingOn') : null\n return (\n <div className={css.card} key={entry.id}>\n <div className={css.cardHead}>\n <span className={css.swatch} style={{ background: entry.accent }} aria-hidden=\"true\" />\n <span className={css.cardName} title={entry.nameEn}>{entry.nameEn}</span>\n {badge !== null && (\n <span className={`${css.badge} ${isActive ? css.badgeActive : css.badgeTrying}`}>\n {badge}\n </span>\n )}\n </div>\n <div className={css.cardTagline} title={entry.tagline}>{entry.tagline}</div>\n {actionButtons({\n key: entry.id,\n isActive,\n isTrying,\n onTryOn: () => { tryOn(entry) },\n applyLabel: t('apply'),\n })}\n </div>\n )\n })}\n </div>\n </>\n )\n : (\n <p className={css.offNote} role=\"status\">{t('offNote')}</p>\n )}\n </div>\n </li>\n )\n}\n\n/** Props the settings section binds for the skin-center card page. */\nexport type SkinCenterSectionProps =\n PropsRuntime<'settings.section'>\n & PropsLocale<'skinCenter'>\n & SkinCenterInjected\n\n/** Render the skin-center card as a first-level settings page. */\nexport function SkinCenterSection(props: SkinCenterSectionProps): ReactNode {\n const { t, controller, theme, background } = props\n return (\n <ul className={css.sectionList}>\n <SkinCenter t={t} controller={controller} theme={theme} background={background} />\n </ul>\n )\n}\n","/**\n * Background-scrim handle for the skin center: binds the `skin-background`\n * settings namespace and applies the chosen occlusion to the page's backdrop,\n * plus an optional per-state Gaussian blur of that backdrop.\n *\n * Occlusion is a CSS variable on `document.body` (--dsw-skin-scrim), which\n * backdrop-painting skins (blue-fantasy / whale-song) read inside their\n * setBackdrop() so the veil stays in sync across theme flips and try-on\n * restores. The official stock look paints no backdrop, so the variable is\n * inert there — the value still persists so it is ready for the next backdrop\n * skin.\n *\n * The Gaussian blur targets the same painted backdrop through a fixed child\n * of `document.body` using backdrop-filter: it samples the body's own\n * background painted behind it. Separate strengths apply to the empty\n * conversation and the conversation-with-content states, detected from the\n * shell's stable message-row class suffixes (hash prefix varies, suffix is\n * stable). When the active blur is 0 no element exists, so there is no GPU\n * cost. Blur never changes the occlusion behavior above.\n *\n * Occlusion values are 0-100 (0 = no extra veil, 100 = fully obscured); they\n * are written through as a 0..1 alpha for the CSS variable. Blur values are\n * 0-20 px. Dragging the controls applies instantly (live) and persists\n * through the settings scope.\n */\nimport type { SettingsScope, SettingsScopeSnapshot } from '@deepseek-ai/dsh-client-runtime/client'\n\n/** The namespace string the Host registers (mirrors src/index.ts). */\nexport const SKIN_BACKGROUND_NS = 'skin-background'\n\n/** Field of the background value inside the namespace section. */\nexport const OPACITY_FIELD = 'backgroundOpacity'\n\n/** Field of the empty-conversation backdrop blur inside the namespace section. */\nexport const BLUR_EMPTY_FIELD = 'backgroundBlurEmpty'\n\n/** Field of the with-content backdrop blur inside the namespace section. */\nexport const BLUR_CONTENT_FIELD = 'backgroundBlurContent'\n\n/** CSS custom property written to document.body and read by backdrop skins. */\nexport const SCRIM_VAR = '--dsw-skin-scrim'\n\n/** Default occlusion (0 = no extra veil) when the section carries none. */\nexport const DEFAULT_OPACITY = 0\n\n/** Default blur (0 = disabled) when the section carries none. */\nexport const DEFAULT_BLUR = 0\n\n/** The face the skin-center card injects for the background control. */\nexport interface SkinBackgroundHandle {\n /** Current master switch (true when the plugin is on). */\n enabled(): boolean\n /** Toggle + persist the master switch. */\n setEnabled(value: boolean): void\n /** Current occlusion 0-100 (also the getSnapshot seat for useSyncExternalStore). */\n opacity(): number\n /** Current empty-conversation backdrop blur 0-20 px. */\n blurEmpty(): number\n /** Current with-content backdrop blur 0-20 px. */\n blurContent(): number\n /** Observe a change in the applied values. */\n subscribe(listener: () => void): () => void\n /** Apply + persist a new occlusion. */\n set(opacity: number): void\n /** Apply + persist a new empty-conversation backdrop blur (0-20 px). */\n setBlurEmpty(value: number): void\n /** Apply + persist a new with-content backdrop blur (0-20 px). */\n setBlurContent(value: number): void\n /** Tear down the blur element and MutationObserver. */\n dispose(): void\n}\n\n/**\n * Selector for a conversation message row inside the shell's center column.\n * The `data-pane=\"conversation\"` attribute is stamped by the dsh-web-ui-all\n * compat shim on the center column; the _userRow / _compactionRow /\n * _contextRow / _turnErrorRow suffixes are the official shell's CSS-module\n * hashed message-row classes (hash prefix varies, suffix is stable). Stable\n * like the repo's compat shim, not hash-dependent.\n */\nconst CONVERSATION_CONTENT_SELECTOR = [\n '[data-pane=\"conversation\"] [class*=\"_userRow\"]',\n '[data-pane=\"conversation\"] [class*=\"_compactionRow\"]',\n '[data-pane=\"conversation\"] [class*=\"_contextRow\"]',\n '[data-pane=\"conversation\"] [class*=\"_turnErrorRow\"]',\n].join(', ')\n\n/**\n * Own the skin-background scope: read the latest occlusion + blur strengths,\n * apply them to the body instantly, and persist changes through the settings\n * scope.\n */\nexport class BackgroundController implements SkinBackgroundHandle {\n private enabledValue = true\n private opacityValue = DEFAULT_OPACITY\n private blurEmptyValue = DEFAULT_BLUR\n private blurContentValue = DEFAULT_BLUR\n private readonly listeners = new Set<() => void>()\n private readonly scope: SettingsScope<{\n enabled?: boolean\n backgroundOpacity?: number\n backgroundBlurEmpty?: number\n backgroundBlurContent?: number\n }>\n /** The fixed backdrop-filter element, present only while active blur > 0. */\n private blurElement: HTMLDivElement | null = null\n /** The body MutationObserver, installed lazily once a blur is active. */\n private observer: MutationObserver | null = null\n /** Pending requestAnimationFrame id for a coalesced recheck. */\n private rafId: number | null = null\n /** Guard: after dispose no scheduled work may reinstall anything. */\n private disposed = false\n\n /**\n * @param scope - the bound skin-background settings scope.\n */\n constructor(scope: SettingsScope<{\n enabled?: boolean\n backgroundOpacity?: number\n backgroundBlurEmpty?: number\n backgroundBlurContent?: number\n }>) {\n this.scope = scope\n this.enabledValue = this.readEnabled()\n this.opacityValue = this.readOpacity()\n this.blurEmptyValue = this.readBlur(BLUR_EMPTY_FIELD)\n this.blurContentValue = this.readBlur(BLUR_CONTENT_FIELD)\n this.applyOcclusion()\n this.syncBlur()\n scope.subscribe(() => {\n this.enabledValue = this.readEnabled()\n this.opacityValue = this.readOpacity()\n this.blurEmptyValue = this.readBlur(BLUR_EMPTY_FIELD)\n this.blurContentValue = this.readBlur(BLUR_CONTENT_FIELD)\n this.applyOcclusion()\n this.syncBlur()\n this.publish()\n })\n }\n\n enabled(): boolean { return this.enabledValue }\n\n setEnabled(value: boolean): void {\n this.enabledValue = value\n this.applyOcclusion()\n this.syncBlur()\n this.publish()\n void this.scope.set('enabled', value)\n }\n\n opacity(): number { return this.opacityValue }\n\n blurEmpty(): number { return this.blurEmptyValue }\n\n blurContent(): number { return this.blurContentValue }\n\n subscribe(listener: () => void): () => void {\n this.listeners.add(listener)\n return () => { this.listeners.delete(listener) }\n }\n\n set(opacity: number): void {\n const clamped = Math.max(0, Math.min(100, Math.round(opacity)))\n this.opacityValue = clamped\n this.applyOcclusion()\n this.publish()\n // Persist: queue the write on the settings scope. Failures are silent —\n // the live value is already applied, and the write drains on reconnect.\n void this.scope.set(OPACITY_FIELD, clamped)\n }\n\n setBlurEmpty(value: number): void {\n const clamped = this.clampBlur(value)\n this.blurEmptyValue = clamped\n this.ensureObserver()\n this.syncBlur()\n this.publish()\n void this.scope.set(BLUR_EMPTY_FIELD, clamped)\n }\n\n setBlurContent(value: number): void {\n const clamped = this.clampBlur(value)\n this.blurContentValue = clamped\n this.ensureObserver()\n this.syncBlur()\n this.publish()\n void this.scope.set(BLUR_CONTENT_FIELD, clamped)\n }\n\n dispose(): void {\n this.disposed = true\n if (this.rafId !== null) {\n cancelAnimationFrame(this.rafId)\n this.rafId = null\n }\n this.removeBlurElement()\n if (this.observer !== null) {\n this.observer.disconnect()\n this.observer = null\n }\n }\n\n /** The effective master-switch section value, defaulting to true when absent. */\n private readEnabled(): boolean {\n const snapshot: SettingsScopeSnapshot<{ enabled?: boolean }> = this.scope.getSnapshot()\n const raw = snapshot.value?.enabled\n return typeof raw !== 'boolean' ? true : raw\n }\n\n /** The effective occlusion section value, clamped 0-100, defaulting to 0. */\n private readOpacity(): number {\n const snapshot: SettingsScopeSnapshot<{ backgroundOpacity?: number }> = this.scope.getSnapshot()\n const raw = snapshot.value?.backgroundOpacity\n if (typeof raw !== 'number' || !Number.isFinite(raw)) return DEFAULT_OPACITY\n return Math.max(0, Math.min(100, raw))\n }\n\n /** The effective blur section value for one field, clamped 0-20, defaulting to 0. */\n private readBlur(field: 'backgroundBlurEmpty' | 'backgroundBlurContent'): number {\n const snapshot: SettingsScopeSnapshot<{\n backgroundBlurEmpty?: number\n backgroundBlurContent?: number\n }> = this.scope.getSnapshot()\n const raw = snapshot.value?.[field]\n if (typeof raw !== 'number' || !Number.isFinite(raw)) return DEFAULT_BLUR\n return this.clampBlur(raw)\n }\n\n private clampBlur(value: number): number {\n return Math.max(0, Math.min(20, Math.round(value)))\n }\n\n /** Write the current occlusion onto the body CSS variable (0..1 alpha). */\n private applyOcclusion(): void {\n if (!this.enabledValue) {\n document.body.style.removeProperty(SCRIM_VAR)\n return\n }\n document.body.style.setProperty(SCRIM_VAR, String(this.opacityValue / 100))\n }\n\n /**\n * Apply the active blur: empty or with-content strength depending on the\n * conversation state. A value > 0 ensures the fixed blur element exists\n * with the matching backdrop-filter; 0 removes it.\n */\n private syncBlur(): void {\n if (this.disposed) return\n if (!this.enabledValue) {\n this.removeBlurElement()\n return\n }\n this.ensureObserver()\n const active = this.hasConversationContent() ? this.blurContentValue : this.blurEmptyValue\n if (active > 0) this.ensureBlurElement(active)\n else this.removeBlurElement()\n }\n\n /** True when the conversation pane hosts at least one message row. */\n private hasConversationContent(): boolean {\n return document.querySelector(CONVERSATION_CONTENT_SELECTOR) !== null\n }\n\n /** Create (if needed) and size the fixed backdrop-filter element. */\n private ensureBlurElement(active: number): void {\n if (this.blurElement === null) {\n const element = document.createElement('div')\n element.style.position = 'fixed'\n element.style.inset = '0'\n element.style.zIndex = '-1'\n element.style.pointerEvents = 'none'\n element.setAttribute('aria-hidden', 'true')\n this.blurElement = element\n document.body.appendChild(element)\n }\n const blur = 'blur(' + active + 'px)'\n this.blurElement.style.backdropFilter = blur\n // Safari: the vendor-prefixed form is only reachable via setProperty.\n this.blurElement.style.setProperty('-webkit-backdrop-filter', blur)\n }\n\n /** Remove the fixed blur element, if present. */\n private removeBlurElement(): void {\n if (this.blurElement === null) return\n this.blurElement.remove()\n this.blurElement = null\n }\n\n /**\n * Install the MutationObserver on document.body only when either blur\n * field is active, so a fully-disabled blur never pays the observation\n * cost. Runs lazily on the first non-zero set.\n */\n private ensureObserver(): void {\n if (this.disposed || this.observer !== null) return\n if (this.blurEmptyValue <= 0 && this.blurContentValue <= 0) return\n this.observer = new MutationObserver(() => this.scheduleRecheck())\n this.observer.observe(document.body, {\n childList: true,\n subtree: true,\n attributes: true,\n attributeFilter: ['class'],\n })\n }\n\n /** Coalesce burst mutations into one rAF-delayed recheck. */\n private scheduleRecheck(): void {\n if (this.disposed || this.rafId !== null) return\n this.rafId = requestAnimationFrame(() => {\n this.rafId = null\n if (this.disposed) return\n this.syncBlur()\n })\n }\n\n private publish(): void {\n for (const listener of this.listeners) listener()\n }\n}\n","/**\n * Skin-center locale dictionaries. The plugin-card name, its description,\n * and every control of the in-GUI skin center is localized through the\n * standard `t` seat.\n */\n\n/** Copy keys owned by this plugin. */\nexport type SkinCenterKey =\n | 'title'\n | 'cardDescription'\n | 'enabled'\n | 'enabledHint'\n | 'offNote'\n | 'intro'\n | 'official'\n | 'officialTagline'\n | 'active'\n | 'tryingOn'\n | 'tryOn'\n | 'loading'\n | 'exitTryOn'\n | 'apply'\n | 'applying'\n | 'restore'\n | 'applyFailed'\n | 'appliedUnconfirmed'\n | 'theme'\n | 'themeLight'\n | 'themeDark'\n | 'tryOnError'\n | 'backgroundOpacity'\n | 'backgroundBlurEmpty'\n | 'backgroundBlurContent'\n | 'backgroundBlurHint'\n | 'backgroundBlurInert'\n | 'backgroundHint'\n | 'backgroundHintInert'\n\nexport const en: Record<SkinCenterKey, string> = {\n title: 'Skin Center',\n cardDescription: 'Try on any installed skin live in the GUI — exit restores instantly, applying persists in one click.',\n enabled: 'Enable skin center',\n enabledHint: 'When off, try-on, apply and background controls are disabled; turn it back on to resume.',\n offNote: 'The skin center is turned off.',\n intro: 'Try on any skin live — it takes effect instantly, exit restores the current look. Apply persists it across restarts.',\n official: 'Official default',\n officialTagline: 'The stock DSH look with no skin applied.',\n active: 'Active',\n tryingOn: 'Trying on',\n tryOn: 'Try on',\n loading: 'Loading…',\n exitTryOn: 'Exit try-on',\n apply: 'Apply',\n applying: 'Applying…',\n restore: 'Restore',\n applyFailed: 'Apply failed',\n appliedUnconfirmed: 'Applied, but the change has not been confirmed — refresh the page in dev mode; packaged installs (DSH Desktop) need an app restart',\n theme: 'Theme preview',\n themeLight: 'Light',\n themeDark: 'Dark',\n tryOnError: 'Try-on failed — see console',\n backgroundOpacity: 'Background occlusion',\n backgroundBlurEmpty: 'Blur when empty',\n backgroundBlurContent: 'Blur with content',\n backgroundBlurHint: 'Applies a separate Gaussian blur to the backdrop for the empty conversation and the conversation with content; 0 disables.',\n backgroundBlurInert: 'Visible only with skins that paint a backdrop; the official default has none.',\n backgroundHint: 'Instantly veils the backdrop behind the panels — higher values obscure the art to help you focus.',\n backgroundHintInert: 'Only applies to skins that paint a backdrop (Blue Fantasy / Whale Song). Applies to the official default automatically once such a skin is active.',\n}\n\nexport const zh: Record<SkinCenterKey, string> = {\n title: '皮肤中心',\n cardDescription: '在 GUI 内即时试穿任意皮肤,退出即完全还原;应用一键完成并自动刷新。',\n enabled: '启用皮肤中心',\n enabledHint: '关闭后停用试穿、应用与背景控件,重新打开即恢复。',\n offNote: '皮肤中心已关闭。',\n intro: '任意皮肤可即时试穿,退出即完全还原;「应用」一键持久化,页面自动刷新生效。',\n official: '官方默认',\n officialTagline: '还原 DSH 官方默认外观,不应用任何皮肤。',\n active: '当前激活',\n tryingOn: '试穿中',\n tryOn: '试穿',\n loading: '加载中…',\n exitTryOn: '退出试穿',\n apply: '应用',\n applying: '应用中…',\n restore: '恢复默认',\n applyFailed: '应用失败',\n appliedUnconfirmed: '已写入配置但尚未确认生效——开发模式请刷新页面;打包版(DSH Desktop)需重启应用后生效',\n theme: '主题预览',\n themeLight: '亮色',\n themeDark: '暗色',\n tryOnError: '试穿失败,详见控制台',\n backgroundOpacity: '背景遮挡',\n backgroundBlurEmpty: '空对话背景模糊',\n backgroundBlurContent: '有对话背景模糊',\n backgroundBlurHint: '对话为空与有内容时分别应用不同的背景高斯模糊强度,0 为关闭。',\n backgroundBlurInert: '仅对带背景图插画的皮肤可见;官方默认无背景图。',\n backgroundHint: '即时为面板背后的背景加遮罩——数值越高越能弱化插画,帮你集中注意力。',\n backgroundHintInert: '仅对带背景图插画的皮肤(蓝色幻想 / 鲸吟)生效;官方默认无背景图,该滑块对这些皮肤自动生效。',\n}\n","/**\n * In-GUI skin center, browser half: registers the Skin Center as a first-level\n * settings section (`settings.section`) and provides the try-on controller +\n * official theme handle to it. The section lists every installed skin\n * (embedded registry), tries it on live inside the GUI, exits with a full\n * restore, and applies the chosen skin in one click. The plugin writes only\n * DOM and the settings ledger — no services, no events, no model access.\n */\nimport type { ClientContext, SettingsScope, SettingsScopeSpec } from '@deepseek-ai/dsh-client-runtime/client'\nimport type { ThemeRuntime } from '@deepseek-ai/dsh-client-ui-theme/client'\n// Type-only: pulls the locale plugin's Context merge (ctx.locale).\nimport type {} from '@deepseek-ai/dsh-client-locale/client'\n// Type-only: pulls the settings-surface Context merge (ctx.settingsScope).\nimport type {} from '@deepseek-ai/dsh-client-ui-settings/client'\nimport { SkinCenterSection, type SkinCenterInjected } from './SkinCenter.tsx'\nimport { BackgroundController, SKIN_BACKGROUND_NS } from './background.ts'\nimport { en, zh, type SkinCenterKey } from './locales.ts'\nimport { TryOnController } from './try-on.ts'\n\nexport type { SkinCenterComponentProps, SkinCenterInjected } from './SkinCenter.tsx'\nexport { TryOnController } from './try-on.ts'\n\n/** Locale namespace owned by this plugin. */\nexport const NS = 'skinCenter'\n\ndeclare module '@deepseek-ai/dsh-client-ui-slots' {\n interface LocaleNamespaceMap {\n /** The skin-center card's copy. */\n skinCenter: SkinCenterKey\n }\n}\n\ndeclare module '@deepseek-ai/cordis' {\n interface Context {\n /**\n * Optional rc.6 compatibility binder provided by dsh-web-ui-settings;\n * absent when that group plugin is not installed, so callers fall back to\n * the official settings scope.\n */\n webUiSettings?: { bind<S>(spec: SettingsScopeSpec<S>): SettingsScope<S> }\n }\n}\n\n\n/** Required services: slots + locale (plugin card), theme (preview toggle), and settingsScope + its transport (background scrim). */\nexport const inject = ['slots', 'locale', 'theme', 'settingsScope', 'connection', 'remote']\n\n/**\n * Register the skin-center dictionaries, the body scope attribute, and the\n * Skin Center as a first-level settings section.\n * @param ctx - client root context.\n */\nexport function apply(ctx: ClientContext): void {\n ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'ui-skin-center: dictionaries')\n\n // The card's own styles scope under this attribute so they keep applying\n // during try-on (when the active skin's attribute is retracted).\n ctx.effect(() => {\n document.body.dataset.dshSkinCenter = ''\n return () => { delete document.body.dataset.dshSkinCenter }\n }, 'ui-skin-center: body scope')\n\n const theme = ctx.get('theme') as ThemeRuntime\n const controller = new TryOnController()\n // Background occluder over the shared skin-background namespace. The scope\n // is bound to this plugin's fiber, so it is torn down with the card.\n const binder = ctx.get('webUiSettings') ?? ctx.settingsScope\n const backgroundScope = binder.bind<{\n enabled?: boolean\n backgroundOpacity?: number\n backgroundBlurEmpty?: number\n backgroundBlurContent?: number\n }>({ namespace: SKIN_BACKGROUND_NS })\n const background = new BackgroundController(backgroundScope)\n // Tear the blur element + observer down when this plugin's fiber goes away.\n ctx.effect(() => () => background.dispose(), 'ui-skin-center: background dispose')\n const injected = (): SkinCenterInjected => ({\n controller,\n theme: {\n getTheme: () => theme.getTheme(),\n subscribe: listener => ctx.on('theme/change', listener),\n setTheme: id => theme.setTheme(id),\n },\n background: {\n enabled: () => background.enabled(),\n setEnabled: value => background.setEnabled(value),\n opacity: () => background.opacity(),\n blurEmpty: () => background.blurEmpty(),\n blurContent: () => background.blurContent(),\n subscribe: listener => background.subscribe(listener),\n set: opacity => background.set(opacity),\n setBlurEmpty: value => background.setBlurEmpty(value),\n setBlurContent: value => background.setBlurContent(value),\n dispose: () => background.dispose(),\n },\n })\n\n ctx.slots.inject('settings.section', () => ctx.slots.register({\n name: 'settings.section',\n id: 'skin-center',\n order: 120,\n label: () => ctx.locale.bind('skinCenter')('title'),\n locale: 'skinCenter',\n inject: injected,\n }, SkinCenterSection))\n}\n"],"mappings":"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"}
package/lib/index.js CHANGED
@@ -11,11 +11,12 @@ import { fileURLToPath } from "node:url";
11
11
  * `dsh-skin` binary on PATH (the bug zhu1090093659/dsh-web-ui#5: "dsh-skin
12
12
  * CLI not found on PATH").
13
13
  *
14
- * `use` owns the `dsh-skin managed` section of the harness-home
14
+ * `use` owns the `dsh-skin managed` section of the active profile's
15
15
  * `cordis.patch.yml` (atomic rewrite, hot-reloaded by the DSH config watcher
16
16
  * within seconds, no restart) and the profile node_modules symlink that makes
17
17
  * the selected skin resolvable from the running profile. `current` reads the
18
- * active back.
18
+ * active state back. Keeping the patch profile-scoped prevents non-Web
19
+ * profiles such as dsh-tui from trying to resolve browser-only skin packages.
19
20
  *
20
21
  * The behaviour/text is a 1:1 port of scripts/dsh-skin (`use`/`current`;
21
22
  * workspace assets live in packages/skins/<id>). The skin registry is
@@ -604,7 +605,8 @@ function resolvePaths(home, profile, fromUrl = import.meta.url) {
604
605
  const profilesRoot = join(harnessHome, "profiles");
605
606
  const activeProfile = firstNonBlank(profile, process.env.DSH_SKIN_PROFILE, process.env.DSH_PROFILE) ?? profileFromCwd(process.cwd(), profilesRoot) ?? install?.profile ?? "web";
606
607
  return {
607
- patchPath: join(harnessHome, "cordis.patch.yml"),
608
+ patchPath: join(harnessHome, "profiles", activeProfile, "cordis.patch.yml"),
609
+ legacyPatchPath: join(harnessHome, "cordis.patch.yml"),
608
610
  profileModulesDir: join(harnessHome, "profiles", activeProfile, "node_modules"),
609
611
  profileManifestPath: join(harnessHome, "profiles", activeProfile, "package.json")
610
612
  };
@@ -810,6 +812,9 @@ function useSkin(name, opts = {}) {
810
812
  if (problem !== null) throw new Error(problem);
811
813
  renderRegistry = registryWithProfileWiring(registry, paths.profileModulesDir, paths.profileManifestPath);
812
814
  }
815
+ const legacyPatch = readPatch(paths.legacyPatchPath);
816
+ const migratedLegacyPatch = stripLegacySkinRows(stripManaged(legacyPatch));
817
+ if (migratedLegacyPatch !== legacyPatch) writePatchAtomic(paths.legacyPatchPath, migratedLegacyPatch);
813
818
  const patch = stripLegacySkinRows(stripManaged(readPatch(paths.patchPath)));
814
819
  let next = `${patch.replace(/\s+$/, "")}\n\n${renderManaged(official ? null : name, renderRegistry)}\n`;
815
820
  let skippedInsert = false;
@@ -838,7 +843,9 @@ function useSkin(name, opts = {}) {
838
843
  function currentSkin(patch, opts = {}) {
839
844
  const paths = resolvePaths(opts.home, opts.profile);
840
845
  const registry = opts.registry ?? loadRegistry();
841
- return currentActive(patch ?? readPatch(paths.patchPath), registryWithProfileWiring(registry, paths.profileModulesDir, paths.profileManifestPath)) ?? "none";
846
+ let activePatch = patch ?? readPatch(paths.patchPath);
847
+ if (patch === void 0 && !activePatch.includes("# --- dsh-skin managed (auto-generated; do not edit) ---")) activePatch = readPatch(paths.legacyPatchPath);
848
+ return currentActive(activePatch, registryWithProfileWiring(registry, paths.profileModulesDir, paths.profileManifestPath)) ?? "none";
842
849
  }
843
850
  //#endregion
844
851
  //#region src/routes.ts
@@ -849,7 +856,7 @@ function currentSkin(patch, opts = {}) {
849
856
  * try-on (the GUI never embeds the ~700KB of art base64 in its own bundle).
850
857
  * The host half switches skins in-process (src/skin-switch.ts) — an ESM port
851
858
  * of the `dsh-skin` CLI that owns the `dsh-skin managed` section of
852
- * `~/.dsh/cordis.patch.yml` and the profile symlink, exactly like
859
+ * the active profile's `cordis.patch.yml` and the profile symlink, exactly like
853
860
  * `dsh-skin use <name>` — so no `dsh-skin` binary is required on PATH
854
861
  * (the bug zhu1090093659/dsh-web-ui#5). The config watcher hot-reloads the
855
862
  * patch within seconds and the frontend reloads the page to pick up the new
@@ -1138,6 +1145,47 @@ function makeSkinCenterRoutes(deps = {}) {
1138
1145
  ];
1139
1146
  }
1140
1147
  //#endregion
1148
+ //#region src/mount-once.ts
1149
+ /**
1150
+ * Host single-instance guard shared by the plugin family. The family bundle
1151
+ * (dsh-web-ui-all / dsh-skins) namespaces every child row id (web-ui-*), so
1152
+ * the loader accepts a standalone install of the same package side by side;
1153
+ * without this guard the second instance would still re-register the same
1154
+ * webserver routes, tools, settings namespaces, and system-prompt sections
1155
+ * and fail the boot. mountOnce makes the second host apply a no-op for the
1156
+ * lifetime of the first instance (the browser half is already deduped by
1157
+ * package name in the client module host).
1158
+ *
1159
+ * The registry rides a global symbol so two module instances of the same
1160
+ * package (npm copy vs repository link) still share one verdict. cordis
1161
+ * `ctx.effect` runs its callback immediately and treats the callback's
1162
+ * return value as the fiber disposer, so the unmarker is returned, not run.
1163
+ */
1164
+ const MOUNTED = Symbol.for("dsh-web-ui.mounted-plugins");
1165
+ function mountedSet() {
1166
+ const registry = globalThis;
1167
+ return registry[MOUNTED] ??= /* @__PURE__ */ new Set();
1168
+ }
1169
+ /**
1170
+ * Wrap a cordis plugin apply so the package runs at most once per process.
1171
+ * The first mount registers normally and unmarks when its fiber disposes;
1172
+ * any later mount of the same package name is a no-op.
1173
+ * @param packageName - npm package identity shared by every install source.
1174
+ * @param fn - the original plugin apply.
1175
+ * @returns an apply of the same shape.
1176
+ */
1177
+ function mountOnce(packageName, fn) {
1178
+ return ((...args) => {
1179
+ const mounted = mountedSet();
1180
+ if (mounted.has(packageName)) return;
1181
+ mounted.add(packageName);
1182
+ args[0]?.effect?.(() => () => {
1183
+ mounted.delete(packageName);
1184
+ });
1185
+ return fn(...args);
1186
+ });
1187
+ }
1188
+ //#endregion
1141
1189
  //#region src/index.ts
1142
1190
  /** Stable cordis plugin name (matches cordis.patch.yml insert id). */
1143
1191
  const name = "ui-skin-center";
@@ -1167,7 +1215,8 @@ const SkinBackgroundConfigSchema = z.object({
1167
1215
  * must not take the GUI down.
1168
1216
  * @param ctx - cordis context.
1169
1217
  */
1170
- function apply(ctx) {
1218
+ const apply = mountOnce("@linxin666/dsh-client-ui-skin-center", applyImpl);
1219
+ function applyImpl(ctx) {
1171
1220
  installSettingsSection(ctx, SKIN_BACKGROUND_NAMESPACE, SkinBackgroundConfigSchema, {}, {
1172
1221
  setSource: () => {},
1173
1222
  onChange: () => {}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@linxin666/dsh-client-ui-skin-center",
3
3
  "description": "In-GUI skin center for the dsh web GUI: lists the official default plus every installed skin (qq98 / ths / xp / blue-fantasy / dragon-heir / minecraft), tries it on live inside the real GUI (executes the actual client bundle, light/dark preview), exits with a full restore, and applies in one click through the host /api/skin-center API (dsh-skin use, hot-reloaded, no restart)",
4
- "version": "0.1.18",
4
+ "version": "0.1.20",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
7
7
  "exports": {