@linxin666/dsh-client-ui-skin-center 0.3.20 → 0.3.22

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.
@@ -45,7 +45,7 @@
45
45
  | `data-dsh-wallpaper-active` | html + body(body/html 级,另行管理) | WE 壁纸挂载期间置 `true`,卸载 / 禁用清除;供皮肤 CSS 与壁纸中和规则锚定(#734) |
46
46
  | `data-dsh-wallpaper-surface` | 官方 shell 全视口背景元素 + 侧栏工作区淡化条(元素级) | `WallpaperController.markWallpaperSurfaces()` 在 WE 壁纸挂载期间打标(全视口 bg-base 背景 + `data-slot="sidebar.workspaces"` 内渐变淡化条),命中 `html[data-dsh-wallpaper-active] [data-dsh-wallpaper-surface]` 中和;卸载清除,不含哈希类依赖(#734) |
47
47
 
48
- ## part 组(71 行,含各 owner 行)
48
+ ## part 组(79 行,含各 owner 行)
49
49
 
50
50
  shell 区域(owner: shell):
51
51
 
@@ -70,6 +70,9 @@ family / 插件区域:
70
70
  | `column` | task-board | 状态列;`section[data-status]` |
71
71
  | `card` | task-board | 任务卡;列内 `[data-status]` 条目 |
72
72
  | `detail` | task-board | 任务详情面板 |
73
+ | `tag-filter` | task-board | 标签筛选条;`[data-dsh-taskboard-board] [data-dsh-part="tag-filter"]` |
74
+ | `tag-chip` | task-board | 筛选条内的标签胶囊;`[data-dsh-part="tag-chip"]`,`data-tag-tone` 为 0-5 调色板槽 |
75
+ | `tag-badge` | task-board | 卡片上的标签徽章;`[data-dsh-part="tag-badge"]`,`data-tag-tone` 同上 |
73
76
  | `tab-bar` / `tab` | ssh | 页签条/页签;`[role="tablist"]` / `[role="tab"]` |
74
77
  | `host-table` / `host-row` | ssh | 主机表/行;`[data-dsh-ssh-view]` 内 table/tr |
75
78
  | `terminal` | ssh | xterm 终端;面板内 termContainer(.xterm 辅锚) |
@@ -113,13 +116,15 @@ family / 插件区域:
113
116
  | `bubble` | miku-pet | 对话气泡;`[data-dsh-part="bubble"]` |
114
117
  | `float` | miku-pet | 互动飘字;`[data-dsh-part="float"]` |
115
118
  | `header` | usage | 使用统计分区头部(当前提供方 + 刷新钮);`[data-dsh-plugin="usage"] [data-dsh-part="header"]` |
116
- | `tabs` / `tab` | usage | 用量/个人套餐页签;`[role="tablist"]` / `[role="tab"]` |
119
+ | `tabs` / `tab` | usage | 用量/个人套餐/Token 银行页签;`[role="tablist"]` / `[role="tab"]` |
117
120
  | `today-card` | usage | 今日用量统计卡;`[data-dsh-part="today-card"]` |
118
121
  | `provider-list` / `provider-row` | usage | 今日分 provider 用量列表 / 余额行;卡片内行容器 |
119
122
  | `balance-card` | usage | 各 provider 余额卡;`[data-dsh-part="balance-card"]` |
120
123
  | `trend-card` / `usage-chart` | usage | 近 30 天卡 / 其中的提供方-模型条形图;`[data-dsh-part="trend-card"]` 内 `[data-dsh-part="usage-chart"]` |
121
124
  | `settings-row` | usage | 插件设置行;`[data-dsh-part="settings-row"]` |
122
125
  | `plan-card` / `plan-window` | usage | 套餐卡 / 套餐窗口行(个人套餐页签);`[data-dsh-part="plan-card"]` 内窗口行 |
126
+ | `bank-card` | usage | Token 银行卡(鲸元券,无官方用量时为空状态);`[data-dsh-part="bank-card"]` |
127
+ | `voucher-preview` | usage | 票面 canvas 容器;bank-card 内 `[data-dsh-part="voucher-preview"]` |
123
128
  | `panel` | model-capabilities | 提供方卡片能力扩展区根;`[data-dsh-plugin="model-capabilities"][data-dsh-part="panel"]` |
124
129
  | `toggle` | model-capabilities | 扩展区折叠头按钮;panel 内 `button[data-dsh-part="toggle"]` |
125
130
  | `model-row` / `model-toggle` | model-capabilities | 逐模型能力行与其展开按钮;panel 内 `li[data-dsh-part="model-row"]` 及其中 `button[data-dsh-part="model-toggle"]` |
@@ -128,8 +133,13 @@ family / 插件区域:
128
133
  | `save` / `reset` / `disable` / `enable` / `reload` | model-capabilities | 扩展区动作按钮(保存 / 重置 / 禁用此提供方 / 启用 / 重新读取);panel 内裸值按钮 |
129
134
  | `disabled-state` | model-capabilities | 已禁用提供方的卡片提示区(含启用按钮);panel 内 `[data-dsh-part="disabled-state"]` |
130
135
  | `disabled-footer` / `disabled-row` | model-capabilities | Models 页底部存档区与其行;`settings.models.footer` 槽内 `section[data-dsh-part="disabled-footer"]` 与其中 `li[data-dsh-part="disabled-row"]` |
136
+ | `lever` | liangshen | 首页输入框内的梁神模式拨杆根(模型选择器左侧);`[data-dsh-plugin="liangshen"][data-dsh-part="lever"]`,状态由 `data-state`(`on` / `off` / `locked` / `missing`)锚定 |
137
+ | `lever-track` | liangshen | 拨杆底座槽(臂的转动轨道);lever 内 `[data-dsh-part="lever-track"]` |
138
+ | `lever-arm` | liangshen | 拨杆臂(拉杆 + 球头,随状态转动);lever 内 `[data-dsh-part="lever-arm"]` |
139
+ | `lever-burst` | liangshen | 拨下命中后的中奖特效浮层(闪光 / 冲击环 / 火花 / 横幅);`[data-dsh-part="lever-burst"]`,`prefers-reduced-motion` 下退化为淡出 |
140
+ | `lever-banner` | liangshen | 特效中的梁神横幅(模式名 + 文言文/二进制/摩斯三行);burst 内 `[data-dsh-part="lever-banner"]` |
131
141
 
132
- ## plugin 组(14 个)
142
+ ## plugin 组(15 个)
133
143
 
134
144
  | data-dsh-plugin | owner | 锚定方式 |
135
145
  | --- | --- | --- |
@@ -147,6 +157,7 @@ family / 插件区域:
147
157
  | `usage` | dsh-usage | 一级设置分区 settings.section id `dsh-usage`(创意工坊下方);`[data-dsh-plugin="usage"]` |
148
158
  | `model-capabilities` | dsh-model-capabilities | Models 页 `settings.models.provider-card` keyed 槽 key `llm-pi-ai`(提供方卡片扩展区)+ `settings.models.footer` 槽 entry id `ui-model-capabilities`(存档区);`[data-dsh-plugin="model-capabilities"]` |
149
159
  | `preset-center` | dsh-preset-center | 创意工坊卡片的「预设」标签页面板(`dsh-workshop.panel` keyed 槽 key `preset`);`[data-dsh-plugin="preset-center"]` |
160
+ | `liangshen` | dsh-liangshen | 首页输入框内的梁神模式拨杆;`[data-dsh-plugin="liangshen"][data-dsh-part="lever"]`,slot entry id `liangshen-lever` |
150
161
 
151
162
  ## 已知脆弱点(上游主题缝 PR 诉求)
152
163
 
package/lib/client.js CHANGED
@@ -401,6 +401,16 @@ window.__ModuleLoader__.load({
401
401
  ].join(", ");
402
402
  const sourceSets = /* @__PURE__ */ new WeakMap();
403
403
  const contentObservers = /* @__PURE__ */ new WeakMap();
404
+ const contentFrames = /* @__PURE__ */ new WeakMap();
405
+ /** Write a marker attribute only when the desired state is not applied yet. */
406
+ function applyMarker(el, attr, active) {
407
+ if (el === null) return;
408
+ if (active) {
409
+ if (el.getAttribute(attr) !== "true") el.setAttribute(attr, "true");
410
+ return;
411
+ }
412
+ if (el.hasAttribute(attr)) el.removeAttribute(attr);
413
+ }
404
414
  /**
405
415
  * Report one source's backdrop-art presence. The marker stays on while any
406
416
  * source is active, so the skin and wallpaper controllers never clobber each
@@ -418,16 +428,13 @@ window.__ModuleLoader__.load({
418
428
  }
419
429
  /** Reflect the source set onto html/body and ensure the neutralizer on use. */
420
430
  function syncMarker(doc, sources) {
421
- if (sources.size > 0) {
422
- doc.body?.setAttribute(BACKDROP_ACTIVE_ATTR, "true");
423
- doc.documentElement?.setAttribute(BACKDROP_ACTIVE_ATTR, "true");
431
+ const active = sources.size > 0;
432
+ applyMarker(doc.body, BACKDROP_ACTIVE_ATTR, active);
433
+ applyMarker(doc.documentElement, BACKDROP_ACTIVE_ATTR, active);
434
+ if (active) {
424
435
  ensureSceneNeutralizer(doc);
425
436
  startContentObserver(doc);
426
- } else {
427
- doc.body?.removeAttribute(BACKDROP_ACTIVE_ATTR);
428
- doc.documentElement?.removeAttribute(BACKDROP_ACTIVE_ATTR);
429
- stopContentObserver(doc);
430
- }
437
+ } else stopContentObserver(doc);
431
438
  }
432
439
  /**
433
440
  * Track whether the active conversation scrollport has message rows for the
@@ -436,13 +443,27 @@ window.__ModuleLoader__.load({
436
443
  * those stale rows and flash the composer frost over the new empty topic.
437
444
  */
438
445
  function updateConversationContent(doc) {
439
- if (doc.body !== null && doc.body.querySelector(ACTIVE_CONVERSATION_CONTENT_SELECTOR) !== null) {
440
- doc.body?.setAttribute(CONVERSATION_CONTENT_ATTR, "true");
441
- doc.documentElement?.setAttribute(CONVERSATION_CONTENT_ATTR, "true");
442
- } else {
443
- doc.body?.removeAttribute(CONVERSATION_CONTENT_ATTR);
444
- doc.documentElement?.removeAttribute(CONVERSATION_CONTENT_ATTR);
446
+ const has = doc.body !== null && doc.body.querySelector(ACTIVE_CONVERSATION_CONTENT_SELECTOR) !== null;
447
+ applyMarker(doc.body, CONVERSATION_CONTENT_ATTR, has);
448
+ applyMarker(doc.documentElement, CONVERSATION_CONTENT_ATTR, has);
449
+ }
450
+ /**
451
+ * Coalesce the mutation bursts of a streaming conversation into one content
452
+ * check per frame; a check scheduled for a document that stopped observing is
453
+ * dropped so a late frame can never re-add the marker after teardown.
454
+ */
455
+ function scheduleConversationContent(doc) {
456
+ if (contentFrames.has(doc)) return;
457
+ const win = doc.defaultView;
458
+ if (win === null || typeof win.requestAnimationFrame !== "function") {
459
+ updateConversationContent(doc);
460
+ return;
445
461
  }
462
+ contentFrames.set(doc, win.requestAnimationFrame(() => {
463
+ contentFrames.delete(doc);
464
+ if (!contentObservers.has(doc)) return;
465
+ updateConversationContent(doc);
466
+ }));
446
467
  }
447
468
  /** Observe the conversation tree while a backdrop is visible. */
448
469
  function startContentObserver(doc) {
@@ -450,22 +471,28 @@ window.__ModuleLoader__.load({
450
471
  updateConversationContent(doc);
451
472
  const win = doc.defaultView;
452
473
  if (win === null || typeof win.MutationObserver !== "function") return;
453
- const observer = new win.MutationObserver(() => updateConversationContent(doc));
474
+ const observer = new win.MutationObserver(() => scheduleConversationContent(doc));
454
475
  observer.observe(doc.body ?? doc.documentElement, {
455
476
  childList: true,
456
477
  subtree: true
457
478
  });
458
479
  contentObservers.set(doc, observer);
459
480
  }
460
- /** Stop the content observer and drop the content marker. */
481
+ /** Stop the content observer, cancel pending work and drop the marker. */
461
482
  function stopContentObserver(doc) {
483
+ const frame = contentFrames.get(doc);
484
+ if (frame !== void 0) {
485
+ const win = doc.defaultView;
486
+ if (win !== null && typeof win.cancelAnimationFrame === "function") win.cancelAnimationFrame(frame);
487
+ contentFrames.delete(doc);
488
+ }
462
489
  const observer = contentObservers.get(doc);
463
490
  if (observer !== void 0) {
464
491
  observer.disconnect();
465
492
  contentObservers.delete(doc);
466
493
  }
467
- doc.body?.removeAttribute(CONVERSATION_CONTENT_ATTR);
468
- doc.documentElement?.removeAttribute(CONVERSATION_CONTENT_ATTR);
494
+ applyMarker(doc.body, CONVERSATION_CONTENT_ATTR, false);
495
+ applyMarker(doc.documentElement, CONVERSATION_CONTENT_ATTR, false);
469
496
  }
470
497
  /**
471
498
  * Install the shared composer-seat neutralizer, keyed by head presence so a
@@ -2320,11 +2347,13 @@ window.__ModuleLoader__.load({
2320
2347
  const blurContent = (0, react.useSyncExternalStore)(background.subscribe, background.blurContent);
2321
2348
  const inputCardBlur = (0, react.useSyncExternalStore)(background.subscribe, background.inputCardBlur);
2322
2349
  const bubbleOpacity = (0, react.useSyncExternalStore)(background.subscribe, background.bubbleOpacity);
2350
+ const bubbleBlur = (0, react.useSyncExternalStore)(background.subscribe, background.bubbleBlur);
2323
2351
  const [shownOpacity, setShownOpacity] = useLiveValue(opacity);
2324
2352
  const [shownBlurEmpty, setShownBlurEmpty] = useLiveValue(blurEmpty);
2325
2353
  const [shownBlurContent, setShownBlurContent] = useLiveValue(blurContent);
2326
2354
  const [shownInputCardBlur, setShownInputCardBlur] = useLiveValue(inputCardBlur);
2327
2355
  const [shownBubbleOpacity, setShownBubbleOpacity] = useLiveValue(bubbleOpacity);
2356
+ const [shownBubbleBlur, setShownBubbleBlur] = useLiveValue(bubbleBlur);
2328
2357
  const catalog = (0, react.useSyncExternalStore)(runtime.subscribe, runtime.catalog);
2329
2358
  const state = (0, react.useSyncExternalStore)(runtime.subscribe, runtime.controller.getState);
2330
2359
  const customThemeState = (0, react.useSyncExternalStore)(customTheme.subscribe, customTheme.getState);
@@ -2816,6 +2845,40 @@ window.__ModuleLoader__.load({
2816
2845
  })
2817
2846
  ]
2818
2847
  }),
2848
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2849
+ className: skin_center_module_css_default.backgroundRow,
2850
+ children: [
2851
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2852
+ className: skin_center_module_css_default.backgroundHead,
2853
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2854
+ className: skin_center_module_css_default.backgroundLabel,
2855
+ children: t("bubbleBlur")
2856
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2857
+ className: skin_center_module_css_default.backgroundValue,
2858
+ "aria-hidden": "true",
2859
+ children: [shownBubbleBlur, "px"]
2860
+ })]
2861
+ }),
2862
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SliderControl, {
2863
+ id: "skin-center-bubble-blur",
2864
+ className: skin_center_module_css_default.backgroundRange,
2865
+ min: 0,
2866
+ max: 20,
2867
+ step: 1,
2868
+ value: bubbleBlur,
2869
+ ariaValuetext: shownBubbleBlur + "px",
2870
+ ariaLabel: t("bubbleBlur"),
2871
+ onChanging: setShownBubbleBlur,
2872
+ onChange: (value) => {
2873
+ background.setBubbleBlur(value);
2874
+ }
2875
+ }),
2876
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2877
+ className: skin_center_module_css_default.backgroundHint,
2878
+ children: t("bubbleBlurHint")
2879
+ })
2880
+ ]
2881
+ }),
2819
2882
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WallpaperPanel, {
2820
2883
  t,
2821
2884
  wallpaper
@@ -2973,7 +3036,8 @@ window.__ModuleLoader__.load({
2973
3036
  backgroundBlurEmpty: 0,
2974
3037
  backgroundBlurContent: 0,
2975
3038
  inputCardBlur: 10,
2976
- bubbleOpacity: 50
3039
+ bubbleOpacity: 50,
3040
+ bubbleBlur: 10
2977
3041
  };
2978
3042
  /** The fields normalize/sanitize know about; unknown keys are dropped. */
2979
3043
  const SKIN_BACKGROUND_FIELDS = Object.keys(SKIN_BACKGROUND_DEFAULTS);
@@ -2985,7 +3049,8 @@ window.__ModuleLoader__.load({
2985
3049
  backgroundBlurEmpty: [0, 20],
2986
3050
  backgroundBlurContent: [0, 20],
2987
3051
  inputCardBlur: [0, 20],
2988
- bubbleOpacity: [0, 100]
3052
+ bubbleOpacity: [0, 100],
3053
+ bubbleBlur: [0, 20]
2989
3054
  };
2990
3055
  function isRecord$1(value) {
2991
3056
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -3053,10 +3118,13 @@ window.__ModuleLoader__.load({
3053
3118
  const SCRIM_VAR = "--dsw-skin-scrim";
3054
3119
  /** CSS custom property consumed by skins that expose translucent bubbles. */
3055
3120
  const BUBBLE_ALPHA_VAR = "--dsh-skin-bubble-alpha";
3121
+ /** CSS custom property consumed by skins that expose a bubble backdrop blur. */
3122
+ const BUBBLE_BLUR_VAR = "--dsh-skin-bubble-blur";
3056
3123
  /** CSS custom property consumed by the shared composer neutralizer. */
3057
3124
  const INPUT_CARD_BLUR_VAR = "--dsh-input-card-blur";
3058
3125
  SKIN_BACKGROUND_DEFAULTS.backgroundOpacity;
3059
3126
  SKIN_BACKGROUND_DEFAULTS.bubbleOpacity;
3127
+ SKIN_BACKGROUND_DEFAULTS.bubbleBlur;
3060
3128
  SKIN_BACKGROUND_DEFAULTS.backgroundBlurEmpty;
3061
3129
  /**
3062
3130
  * Selector for a conversation message row inside the shell's center column.
@@ -3084,6 +3152,7 @@ window.__ModuleLoader__.load({
3084
3152
  blurContentValue = SKIN_BACKGROUND_DEFAULTS.backgroundBlurContent;
3085
3153
  inputCardBlurValue = SKIN_BACKGROUND_DEFAULTS.inputCardBlur;
3086
3154
  bubbleOpacityValue = SKIN_BACKGROUND_DEFAULTS.bubbleOpacity;
3155
+ bubbleBlurValue = SKIN_BACKGROUND_DEFAULTS.bubbleBlur;
3087
3156
  listeners = /* @__PURE__ */ new Set();
3088
3157
  persist;
3089
3158
  /** The fixed backdrop-filter element, present only while active blur > 0. */
@@ -3105,6 +3174,7 @@ window.__ModuleLoader__.load({
3105
3174
  this.applyOcclusion();
3106
3175
  this.applyInputCardBlur();
3107
3176
  this.applyBubbleOpacity();
3177
+ this.applyBubbleBlur();
3108
3178
  this.syncBlur();
3109
3179
  }
3110
3180
  /**
@@ -3119,6 +3189,7 @@ window.__ModuleLoader__.load({
3119
3189
  this.applyOcclusion();
3120
3190
  this.applyInputCardBlur();
3121
3191
  this.applyBubbleOpacity();
3192
+ this.applyBubbleBlur();
3122
3193
  this.syncBlur();
3123
3194
  this.publish();
3124
3195
  }
@@ -3130,7 +3201,8 @@ window.__ModuleLoader__.load({
3130
3201
  backgroundBlurEmpty: this.blurEmptyValue,
3131
3202
  backgroundBlurContent: this.blurContentValue,
3132
3203
  inputCardBlur: this.inputCardBlurValue,
3133
- bubbleOpacity: this.bubbleOpacityValue
3204
+ bubbleOpacity: this.bubbleOpacityValue,
3205
+ bubbleBlur: this.bubbleBlurValue
3134
3206
  };
3135
3207
  }
3136
3208
  enabled = () => this.enabledValue;
@@ -3139,6 +3211,7 @@ window.__ModuleLoader__.load({
3139
3211
  this.applyOcclusion();
3140
3212
  this.applyInputCardBlur();
3141
3213
  this.applyBubbleOpacity();
3214
+ this.applyBubbleBlur();
3142
3215
  this.syncBlur();
3143
3216
  this.publish();
3144
3217
  this.persist(this.snapshot());
@@ -3148,6 +3221,7 @@ window.__ModuleLoader__.load({
3148
3221
  blurContent = () => this.blurContentValue;
3149
3222
  inputCardBlur = () => this.inputCardBlurValue;
3150
3223
  bubbleOpacity = () => this.bubbleOpacityValue;
3224
+ bubbleBlur = () => this.bubbleBlurValue;
3151
3225
  subscribe = (listener) => {
3152
3226
  this.listeners.add(listener);
3153
3227
  return () => {
@@ -3186,6 +3260,12 @@ window.__ModuleLoader__.load({
3186
3260
  this.publish();
3187
3261
  this.persist(this.snapshot());
3188
3262
  }
3263
+ setBubbleBlur(value) {
3264
+ this.bubbleBlurValue = this.clampBlur(value);
3265
+ this.applyBubbleBlur();
3266
+ this.publish();
3267
+ this.persist(this.snapshot());
3268
+ }
3189
3269
  dispose() {
3190
3270
  this.disposed = true;
3191
3271
  if (this.rafId !== null) {
@@ -3195,6 +3275,7 @@ window.__ModuleLoader__.load({
3195
3275
  this.removeBlurElement();
3196
3276
  document.body.style.removeProperty(INPUT_CARD_BLUR_VAR);
3197
3277
  document.body.style.removeProperty(BUBBLE_ALPHA_VAR);
3278
+ document.body.style.removeProperty(BUBBLE_BLUR_VAR);
3198
3279
  if (this.observer !== null) {
3199
3280
  this.observer.disconnect();
3200
3281
  this.observer = null;
@@ -3209,6 +3290,7 @@ window.__ModuleLoader__.load({
3209
3290
  this.blurContentValue = resolved.backgroundBlurContent;
3210
3291
  this.inputCardBlurValue = resolved.inputCardBlur;
3211
3292
  this.bubbleOpacityValue = resolved.bubbleOpacity;
3293
+ this.bubbleBlurValue = resolved.bubbleBlur;
3212
3294
  }
3213
3295
  clampBlur(value) {
3214
3296
  return Math.max(0, Math.min(20, Math.round(value)));
@@ -3230,6 +3312,13 @@ window.__ModuleLoader__.load({
3230
3312
  }
3231
3313
  document.body.style.setProperty(BUBBLE_ALPHA_VAR, String(this.bubbleOpacityValue / 100));
3232
3314
  }
3315
+ applyBubbleBlur() {
3316
+ if (!this.enabledValue) {
3317
+ document.body.style.removeProperty(BUBBLE_BLUR_VAR);
3318
+ return;
3319
+ }
3320
+ document.body.style.setProperty(BUBBLE_BLUR_VAR, this.bubbleBlurValue + "px");
3321
+ }
3233
3322
  /** Write the current occlusion onto the body CSS variable (0..1 alpha). */
3234
3323
  applyOcclusion() {
3235
3324
  if (!this.enabledValue) {
@@ -3462,6 +3551,8 @@ window.__ModuleLoader__.load({
3462
3551
  inputCardBlurHint: "Blurs only the area behind the input card while backdrop art is visible; it does not blur the entire wallpaper.",
3463
3552
  bubbleOpacity: "Bubble opacity",
3464
3553
  bubbleOpacityHint: "Controls translucent message bubbles for skins that expose bubble alpha, such as Whale Mom.",
3554
+ bubbleBlur: "Bubble blur",
3555
+ bubbleBlurHint: "Blurs the backdrop behind translucent message bubbles; independent of bubble opacity, 0 disables.",
3465
3556
  backgroundBlurHint: "Applies a separate Gaussian blur to the backdrop for the empty conversation and the conversation with content; 0 disables.",
3466
3557
  backgroundBlurInert: "Visible only with skins that paint a backdrop; the official default has none.",
3467
3558
  backgroundHint: "Instantly veils the backdrop behind the panels — higher values obscure the art to help you focus.",
@@ -3582,6 +3673,8 @@ window.__ModuleLoader__.load({
3582
3673
  inputCardBlurHint: "仅模糊输入卡背后的区域,不会让整张壁纸变糊。",
3583
3674
  bubbleOpacity: "气泡不透明度",
3584
3675
  bubbleOpacityHint: "调节支持气泡 alpha 的皮肤消息气泡,例如鲸鱼妈妈。",
3676
+ bubbleBlur: "气泡模糊程度",
3677
+ bubbleBlurHint: "模糊半透明消息气泡背后的区域,与「气泡不透明度」相互独立,0 为关闭。",
3585
3678
  backgroundBlurHint: "对话为空与有内容时分别应用不同的背景高斯模糊强度,0 为关闭。",
3586
3679
  backgroundBlurInert: "仅对带背景图插画的皮肤可见;官方默认无背景图。",
3587
3680
  backgroundHint: "即时为面板背后的背景加遮罩——数值越高越能弱化插画,帮你集中注意力。",
@@ -4056,9 +4149,12 @@ window.__ModuleLoader__.load({
4056
4149
  /* #1117: The upstream recommended badge pairs two background-fill tokens
4057
4150
  as bg + text — in dark mode, skins like Blue Fantasy collapse them to
4058
4151
  near-identical dark navy values (contrast ~1:1). Override the text
4059
- color to a readable foreground and tweak the background for contrast. */
4060
- body[data-ds-dark-theme] ${scoped("[data-question-key] [class*=\"_badge\"]")},
4061
- body[data-ds-dark-theme] ${scoped("[data-question-scroll] [class*=\"_badge\"]")} {
4152
+ color to a readable foreground and tweak the background for contrast.
4153
+ The dark-theme attribute lives on <body>, so it belongs inside the
4154
+ scoped selector: prefixing the already-scoped list produced
4155
+ "body ... html ...", a descendant chain that can never match (#1490). */
4156
+ ${scoped("body[data-ds-dark-theme] [data-question-key] [class*=\"_badge\"]")},
4157
+ ${scoped("body[data-ds-dark-theme] [data-question-scroll] [class*=\"_badge\"]")} {
4062
4158
  color: var(--dsw-alias-label-primary, #ffffff) !important;
4063
4159
  background: var(--dsw-alias-interactive-bg-active, color-mix(in srgb, var(--dsw-alias-button-info-fill, #4a5fa8) 50%, transparent)) !important;
4064
4160
  }
@@ -4076,35 +4172,62 @@ window.__ModuleLoader__.load({
4076
4172
  try {
4077
4173
  win?.scrollTo?.(0, 0);
4078
4174
  } catch {}
4175
+ const composerSelector = COMPOSER_SEAT_SELECTORS.join(", ");
4079
4176
  let resizeObserver = null;
4080
4177
  let mutationObserver = null;
4081
4178
  let observedComposer = null;
4179
+ let appliedHeight = "";
4180
+ let scheduledFrame = null;
4181
+ let disposed = false;
4182
+ const resolveComposer = () => {
4183
+ if (observedComposer !== null && observedComposer.isConnected) return observedComposer;
4184
+ return doc.body === null ? null : doc.body.querySelector(composerSelector);
4185
+ };
4082
4186
  const syncHeight = () => {
4083
4187
  if (doc.body === null) return;
4084
- const composer = doc.body.querySelector(COMPOSER_SEAT_SELECTORS.join(", "));
4085
- if (composer !== null) {
4086
- if (observedComposer !== composer) {
4087
- if (observedComposer !== null && resizeObserver !== null) resizeObserver.unobserve(observedComposer);
4088
- observedComposer = composer;
4089
- if (resizeObserver !== null) resizeObserver.observe(composer);
4090
- }
4091
- const rect = composer.getBoundingClientRect();
4092
- if (rect.height > 0) doc.documentElement?.style.setProperty("--dsh-composer-height", `${Math.ceil(rect.height)}px`);
4188
+ const composer = resolveComposer();
4189
+ if (composer === null) return;
4190
+ if (observedComposer !== composer) {
4191
+ if (observedComposer !== null && resizeObserver !== null) resizeObserver.unobserve(observedComposer);
4192
+ observedComposer = composer;
4193
+ if (resizeObserver !== null) resizeObserver.observe(composer);
4093
4194
  }
4195
+ const rect = composer.getBoundingClientRect();
4196
+ if (rect.height <= 0) return;
4197
+ const root = doc.documentElement;
4198
+ const next = `${Math.ceil(rect.height)}px`;
4199
+ if (next === appliedHeight || root === null) return;
4200
+ appliedHeight = next;
4201
+ root.style.setProperty("--dsh-composer-height", next);
4202
+ };
4203
+ const scheduleSync = () => {
4204
+ if (scheduledFrame !== null || disposed) return;
4205
+ if (win === null || typeof win.requestAnimationFrame !== "function") {
4206
+ syncHeight();
4207
+ return;
4208
+ }
4209
+ scheduledFrame = win.requestAnimationFrame(() => {
4210
+ scheduledFrame = null;
4211
+ if (disposed) return;
4212
+ syncHeight();
4213
+ });
4094
4214
  };
4095
4215
  if (win !== null && typeof win.ResizeObserver === "function") resizeObserver = new win.ResizeObserver(() => syncHeight());
4096
4216
  if (win !== null && typeof win.MutationObserver === "function" && doc.body !== null) {
4097
- mutationObserver = new win.MutationObserver(() => syncHeight());
4217
+ mutationObserver = new win.MutationObserver(() => scheduleSync());
4098
4218
  mutationObserver.observe(doc.body, {
4099
4219
  childList: true,
4100
4220
  subtree: true
4101
4221
  });
4102
4222
  }
4103
4223
  syncHeight();
4104
- let disposed = false;
4105
4224
  return () => {
4106
4225
  if (disposed) return;
4107
4226
  disposed = true;
4227
+ if (scheduledFrame !== null) {
4228
+ if (win !== null && typeof win.cancelAnimationFrame === "function") win.cancelAnimationFrame(scheduledFrame);
4229
+ scheduledFrame = null;
4230
+ }
4108
4231
  if (resizeObserver !== null) {
4109
4232
  resizeObserver.disconnect();
4110
4233
  resizeObserver = null;
@@ -4114,6 +4237,7 @@ window.__ModuleLoader__.load({
4114
4237
  mutationObserver = null;
4115
4238
  }
4116
4239
  observedComposer = null;
4240
+ appliedHeight = "";
4117
4241
  doc.documentElement?.style.removeProperty("--dsh-composer-height");
4118
4242
  style.remove();
4119
4243
  };
@@ -4322,22 +4446,28 @@ window.__ModuleLoader__.load({
4322
4446
  function setBackgroundLayer(activation, nodes) {
4323
4447
  const style = doc.body.style;
4324
4448
  const previousBackgroundColor = style.getPropertyValue("background-color");
4449
+ const previousBackgroundImage = style.getPropertyValue("background-image");
4325
4450
  const restore = () => {
4326
4451
  if (currentActivation !== activation) return;
4327
4452
  clearLayer(layers.background);
4328
4453
  setSceneBackdropActive(doc, "skin", false);
4329
4454
  if (previousBackgroundColor === "") style.removeProperty("background-color");
4330
4455
  else style.setProperty("background-color", previousBackgroundColor);
4456
+ if (previousBackgroundImage === "") style.removeProperty("background-image");
4457
+ else style.setProperty("background-image", previousBackgroundImage);
4331
4458
  };
4332
4459
  clearLayer(layers.background);
4333
4460
  if (nodes.length > 0) {
4334
4461
  for (const node of nodes) layers.background.appendChild(node);
4335
4462
  style.setProperty("background-color", "transparent");
4463
+ style.setProperty("background-image", "none");
4336
4464
  setSceneBackdropActive(doc, "skin", true);
4337
4465
  } else {
4338
4466
  setSceneBackdropActive(doc, "skin", false);
4339
4467
  if (previousBackgroundColor === "") style.removeProperty("background-color");
4340
4468
  else style.setProperty("background-color", previousBackgroundColor);
4469
+ if (previousBackgroundImage === "") style.removeProperty("background-image");
4470
+ else style.setProperty("background-image", previousBackgroundImage);
4341
4471
  }
4342
4472
  ledger.record(activation, "background:layer", restore);
4343
4473
  }
@@ -5011,7 +5141,7 @@ window.__ModuleLoader__.load({
5011
5141
  /** The building package's version, when the bundle carries it. */
5012
5142
  function bakedVersion() {
5013
5143
  try {
5014
- return "0.3.20";
5144
+ return "0.3.22";
5015
5145
  } catch {
5016
5146
  return;
5017
5147
  }
@@ -5224,12 +5354,14 @@ window.__ModuleLoader__.load({
5224
5354
  blurContent: () => background.blurContent(),
5225
5355
  inputCardBlur: () => background.inputCardBlur(),
5226
5356
  bubbleOpacity: () => background.bubbleOpacity(),
5357
+ bubbleBlur: () => background.bubbleBlur(),
5227
5358
  subscribe: (listener) => background.subscribe(listener),
5228
5359
  set: (opacity) => background.set(opacity),
5229
5360
  setBlurEmpty: (value) => background.setBlurEmpty(value),
5230
5361
  setBlurContent: (value) => background.setBlurContent(value),
5231
5362
  setInputCardBlur: (value) => background.setInputCardBlur(value),
5232
5363
  setBubbleOpacity: (value) => background.setBubbleOpacity(value),
5364
+ setBubbleBlur: (value) => background.setBubbleBlur(value),
5233
5365
  dispose: () => background.dispose()
5234
5366
  },
5235
5367
  wallpaper: {