dsh-any-background 0.4.1 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js CHANGED
@@ -62,6 +62,13 @@ window.__ModuleLoader__.load({
62
62
  captionInkTheme: "跟随主题",
63
63
  captionInkWhite: "始终白色",
64
64
  captionInkBlack: "始终黑色",
65
+ tpTitle: "第三方插件兼容",
66
+ tpEnabled: "保护第三方表面",
67
+ tpHint: "本插件把宿主的一批底色令牌改成了半透明,好让壁纸透出来。其它插件如果用同样的令牌画表面,就会跟着一起变透明、和滚过的文字叠在一起。开启后认出这些插件,只把它们的那几块钉在可读的密度上,不随滑块调节。",
68
+ tpNone: "未检测到读取这些令牌的其它插件。",
69
+ tpPinned: "已钉住",
70
+ tpUnpinned: "未登记 —— 若它的表面变透明或看不清,请把开机日志这一行发给我",
71
+ tpReaderLanes: "吸顶总结栏",
65
72
  fontPageTitle: "字体与描边",
66
73
  fontTitle: "自定义字体",
67
74
  fontHint: "支持 ttf/otf/woff/woff2 字体文件(上限 100MB),将应用到整个界面的文字;代码块的等宽字体保持不变。中文字体通常有几十 MB,首次上传与刷新加载需要片刻",
@@ -261,6 +268,13 @@ window.__ModuleLoader__.load({
261
268
  captionInkTheme: "Follow theme",
262
269
  captionInkWhite: "Always white",
263
270
  captionInkBlack: "Always black",
271
+ tpTitle: "Third-party compatibility",
272
+ tpEnabled: "Protect third-party surfaces",
273
+ tpHint: "This plugin turns a batch of the host’s background tokens translucent so the wallpaper shows through. Another plugin that paints its surfaces with those same tokens goes translucent with it and collides with the text scrolling underneath. When on, those plugins are detected and only their surfaces are pinned to a legible density — outside the sliders.",
274
+ tpNone: "No other plugin was seen filling from these tokens.",
275
+ tpPinned: "pinned",
276
+ tpUnpinned: "not registered — if its surface fades or becomes unreadable, send me that boot-log line",
277
+ tpReaderLanes: "its sticky reading lanes",
264
278
  fontPageTitle: "Font & outline",
265
279
  fontTitle: "Custom font",
266
280
  fontHint: "Accepts ttf/otf/woff/woff2 files (up to 100MB) and applies them to the whole interface; code blocks keep their monospace stack. CJK fonts routinely span tens of MB, so the first upload and each reload take a moment",
@@ -533,6 +547,7 @@ window.__ModuleLoader__.load({
533
547
  headerOpacity: .5,
534
548
  headerBarOpacity: .5,
535
549
  captionInk: "auto",
550
+ protectThirdParty: true,
536
551
  profiles: [],
537
552
  rotation: {
538
553
  enabled: false,
@@ -762,6 +777,10 @@ window.__ModuleLoader__.load({
762
777
  function rCaptionInk() {
763
778
  return adoptCaptionInk(cfg.captionInk);
764
779
  }
780
+ /** Whether the third-party compatibility pins are active — see `third-party.ts`. */
781
+ function rProtectThirdParty() {
782
+ return typeof cfg.protectThirdParty === "boolean" ? cfg.protectThirdParty : DEFAULT_CONFIG.protectThirdParty;
783
+ }
765
784
  function rBgState() {
766
785
  return cfg.bgState;
767
786
  }
@@ -999,6 +1018,7 @@ window.__ModuleLoader__.load({
999
1018
  headerOpacity: clamp01(c.headerOpacity, DEFAULT_CONFIG.headerOpacity),
1000
1019
  headerBarOpacity: clamp01(c.headerBarOpacity, DEFAULT_CONFIG.headerBarOpacity),
1001
1020
  captionInk: adoptCaptionInk(c.captionInk),
1021
+ protectThirdParty: typeof c.protectThirdParty === "boolean" ? c.protectThirdParty : DEFAULT_CONFIG.protectThirdParty,
1002
1022
  profiles: adoptProfiles(c.profiles),
1003
1023
  rotation: adoptRotation(c.rotation),
1004
1024
  schedule: adoptSchedule(c.schedule),
@@ -1800,7 +1820,7 @@ window.__ModuleLoader__.load({
1800
1820
  /** False until the server verdict lands. Feature gates must not read "not asked
1801
1821
  * yet" as "an old host" — see `adapterResolved()`. */
1802
1822
  let resolved = false;
1803
- const subs$1 = /* @__PURE__ */ new Set();
1823
+ const subs$2 = /* @__PURE__ */ new Set();
1804
1824
  /** Cached adapter, keyed by the verdict it was built from. Held because React's
1805
1825
  * `useSyncExternalStore` requires a stable snapshot between notifications. */
1806
1826
  let adapterCache = null;
@@ -1823,14 +1843,14 @@ window.__ModuleLoader__.load({
1823
1843
  resolved = true;
1824
1844
  if (changed) {
1825
1845
  adapterCache = null;
1826
- subs$1.forEach((cb) => cb());
1846
+ subs$2.forEach((cb) => cb());
1827
1847
  }
1828
1848
  }
1829
1849
  /** Subscribe to the verdict landing (or changing). */
1830
1850
  function subscribeHostInfo(cb) {
1831
- subs$1.add(cb);
1851
+ subs$2.add(cb);
1832
1852
  return () => {
1833
- subs$1.delete(cb);
1853
+ subs$2.delete(cb);
1834
1854
  };
1835
1855
  }
1836
1856
  /** Whether the server verdict has landed at all. */
@@ -2243,14 +2263,21 @@ window.__ModuleLoader__.load({
2243
2263
  }
2244
2264
  //#endregion
2245
2265
  //#region src/client/dom-watch.ts
2246
- const listeners = /* @__PURE__ */ new Set();
2266
+ const listeners = {
2267
+ body: /* @__PURE__ */ new Set(),
2268
+ head: /* @__PURE__ */ new Set()
2269
+ };
2247
2270
  let observer = null;
2248
- /** Subscribe to body-subtree mutations. Returns an unsubscribe. */
2249
- function watchDom(cb) {
2250
- listeners.add(cb);
2271
+ /** Subscribe to mutations of one tree. Returns an unsubscribe. */
2272
+ function watchDom(cb, target = "body") {
2273
+ listeners[target].add(cb);
2251
2274
  if (observer === null && typeof MutationObserver !== "undefined" && typeof document !== "undefined") {
2252
2275
  observer = new MutationObserver((records) => {
2253
- for (const l of listeners) l(records);
2276
+ for (const l of listeners.body) l(records);
2277
+ if (listeners.head.size > 0) {
2278
+ const head = records.filter((r) => r.target === document.head);
2279
+ if (head.length > 0) for (const l of listeners.head) l(head);
2280
+ }
2254
2281
  });
2255
2282
  observer.observe(document.body, {
2256
2283
  childList: true,
@@ -2258,10 +2285,11 @@ window.__ModuleLoader__.load({
2258
2285
  attributes: true,
2259
2286
  attributeFilter: ["aria-expanded"]
2260
2287
  });
2288
+ observer.observe(document.head, { childList: true });
2261
2289
  }
2262
2290
  return () => {
2263
- listeners.delete(cb);
2264
- if (listeners.size === 0) {
2291
+ listeners[target].delete(cb);
2292
+ if (listeners.body.size + listeners.head.size === 0) {
2265
2293
  observer?.disconnect();
2266
2294
  observer = null;
2267
2295
  }
@@ -3741,6 +3769,12 @@ window.__ModuleLoader__.load({
3741
3769
  "--dsw-specific-input-major": "--dsh-any-op-input",
3742
3770
  "--dsw-specific-menu": "--dsh-any-op-menu"
3743
3771
  };
3772
+ /** Every host token this plugin re-emits on `body` at a slider's alpha. Exported
3773
+ * because it is also the answer to "what can another plugin collide with": anything
3774
+ * that fills from one of these names fades with our sliders whether or not its author
3775
+ * knew we existed — the whole of §2-V. `third-party.ts` scans installed plugin
3776
+ * stylesheets for exactly this list. */
3777
+ const OWNED_TOKENS = Object.keys(OPACITY_VARS);
3744
3778
  let baseTokenKey = "";
3745
3779
  let pendingOps = null;
3746
3780
  let tokensRaf = null;
@@ -3877,6 +3911,7 @@ window.__ModuleLoader__.load({
3877
3911
  applyPartOpacities(ops);
3878
3912
  }
3879
3913
  applyExemptDefaults();
3914
+ applyReaderLanes(surfaces);
3880
3915
  } catch {}
3881
3916
  }
3882
3917
  const SETTINGS_PANEL_SEL = "[role=\"dialog\"][aria-modal=\"true\"][aria-labelledby][class*=\"_panel\"]";
@@ -4428,6 +4463,89 @@ window.__ModuleLoader__.load({
4428
4463
  document.head.appendChild(exemptStyleEl);
4429
4464
  return exemptStyleEl;
4430
4465
  }
4466
+ const READER_ROOT = "[data-dsh-better-display]:not([data-reader-glass])";
4467
+ /** Plate density the lanes are pinned at: 「主界面」 at 20% — a band that reads as a
4468
+ * band while still letting the wallpaper through. */
4469
+ const READER_LANE_ALPHA = .2;
4470
+ /** Frost the lanes are pinned at (px) — the middle of the 0–60 blur range, i.e. the
4471
+ * plugin's own default, so the band looks like every other frosted surface here. */
4472
+ const READER_LANE_BLUR_PX = 30;
4473
+ /** The sticky wrapper holding the status lane and the closed-turn digest. It paints
4474
+ * NOTHING today (`Reader.module.css:27`), and `:556` forces both children
4475
+ * `position:static; z-index:auto` — so it is the one element whose box can simply BE
4476
+ * the band: one plate, one frost region, one pair of rounded corners. */
4477
+ const READER_WRAP = `${READER_ROOT} [class*="_turnStickyLanes"]`;
4478
+ /** The band's two height levers, both published by that plugin itself
4479
+ * (`StickyLane.tsx:11-13` writes the measured toolbar and status-lane heights onto
4480
+ * ancestors the wrapper inherits from), so the merged box tracks real text wrapping
4481
+ * instead of a hardcoded 40px. */
4482
+ const READER_TOOLBAR_H = "var(--reader-toolbar-height,40px)";
4483
+ const READER_BAND_TOP = `max(${READER_TOOLBAR_H},var(--reader-status-height,40px))`;
4484
+ /** The two surfaces that paint a band, spelled once so the static and dynamic halves
4485
+ * cannot drift: the merged wrapper, and the live-fold summary cell — which cannot be
4486
+ * merged into it, because it sticks at `z-index:5` BELOW the wrapper's 6, so growing
4487
+ * the wrapper over that row would paint the wrapper's plate straight across the row's
4488
+ * own text (tried, measured, reverted). */
4489
+ const READER_LANE_SEL = `${READER_WRAP},${READER_ROOT} [data-flow-summary]`;
4490
+ /** Everything that used to paint its own plate and must stop now that the wrapper
4491
+ * does. Their plates are what made the stack read as separate bars: the status lane is
4492
+ * `width:calc(100% - var(--reader-control-width))` (`:546`) so it notched short of the
4493
+ * toolbar's width, and each `box-shadow` gap plate (`:555/:564/:565`) lands on a
4494
+ * neighbour and doubles the alpha there — a 14–16px darker strip per seam.
4495
+ *
4496
+ * The toolbar lane hands its plate over only while a band exists to swallow it: a turn
4497
+ * with no process steps renders no wrapper at all, and there the strip would be left
4498
+ * with nothing behind its own button. It can be swallowed at all because its
4499
+ * `z-index:7` (`:538`) sits ABOVE the wrapper's 6 — its own text keeps painting over
4500
+ * the merged plate, which is exactly the arrangement the live-fold cell cannot use. */
4501
+ const READER_SILENT = `${READER_WRAP}>*,${READER_ROOT}:has([class*="_turnStickyLanes"]) [data-reader-lane="toolbar"]`;
4502
+ /** Static half — the merge, the frost, the shape. Every value is a literal or one of
4503
+ * that plugin's own published variables; nothing here reads a slider, which is what
4504
+ * makes the band immune to 调控.
4505
+ *
4506
+ * It is TEXT in the dynamic sheet rather than a rule in `buildStaticStyles()`, and
4507
+ * that is the point: the static sheet is this plugin's contract with the HOST, and a
4508
+ * third-party accommodation must be able to vanish completely — paint, frost and the
4509
+ * merged box's geometry alike — when 「保护第三方表面」 is switched off. One sheet, one
4510
+ * owner, one thing to remove.
4511
+ *
4512
+ * HOW THE BOX GROWS WITHOUT MOVING CONTENT: `padding-top` reaches up over the toolbar
4513
+ * strip and an equal negative `margin-top` cancels it, so the wrapper's margin box
4514
+ * keeps its height and every following sibling stays exactly where it was; `top` is
4515
+ * pulled up by the same amount so the STUCK position puts the wrapper's content back at
4516
+ * `max(toolbar, status)`. The lanes' text does not move a pixel — only the paint does.
4517
+ * Measured in the §4.2 mock with the pin on and off: the column's height and the first
4518
+ * paragraph's offset are identical, and the status row's `top` is unchanged.
4519
+ *
4520
+ * The rounded bottom goes on whichever band is actually LAST: the live-fold cell sticks
4521
+ * 40px below the wrapper (`:558`), so while the turn has one the wrapper is not the
4522
+ * bottom edge and rounding it would open a notch against the cell under it. A build
4523
+ * without `:has()` keeps both bands square; nothing else changes. */
4524
+ const READER_LANE_RULE = `${READER_LANE_SEL}{background:var(--dsw-alias-bg-base);box-shadow:none;${elementFrostDecl(`blur(${READER_LANE_BLUR_PX}px)`)}}${READER_WRAP}{border-radius:0 0 14px 14px;padding-top:${READER_TOOLBAR_H};margin-top:calc(0px - ${READER_TOOLBAR_H});top:calc(${READER_BAND_TOP} - ${READER_TOOLBAR_H})}${READER_ROOT} [data-flow-summary]{border-radius:0 0 14px 14px}${READER_ROOT} [data-reader-turn]:has([data-flow-summary]) [class*="_turnStickyLanes"]{border-radius:0}${READER_SILENT}{background:transparent;box-shadow:none}`;
4525
+ let readerStyleEl = null;
4526
+ /** Emit the whole pin — geometry, frost and the plate literal — into one sheet, or
4527
+ * remove that sheet entirely. Two things switch it off: 「保护第三方表面」 being turned
4528
+ * off, and no color resolving yet (the palette and the host's own tokens are both
4529
+ * empty pre-boot), because a `var()` naming an unwritten property is the §2-E
4530
+ * guaranteed-invalid trap and its symptom here is the very bug being pinned. */
4531
+ function applyReaderLanes(surfaces) {
4532
+ if (typeof document === "undefined") return;
4533
+ const base = rProtectThirdParty() ? (surfaces ?? paletteTokens() ?? readHostOpacityTokens())?.["--dsw-alias-bg-base"] : void 0;
4534
+ if (base === void 0 || base === "") {
4535
+ if (readerStyleEl !== null) {
4536
+ readerStyleEl.remove();
4537
+ readerStyleEl = null;
4538
+ }
4539
+ return;
4540
+ }
4541
+ if (readerStyleEl === null) {
4542
+ readerStyleEl = document.createElement("style");
4543
+ readerStyleEl.dataset.plugin = "dsh-any-background-reader";
4544
+ document.head.appendChild(readerStyleEl);
4545
+ }
4546
+ const css = `${READER_LANE_RULE}${READER_LANE_SEL}{--dsw-alias-bg-base:${toRgba(base, READER_LANE_ALPHA)}}`;
4547
+ if (readerStyleEl.textContent !== css) readerStyleEl.textContent = css;
4548
+ }
4431
4549
  const PRODUCED_RULE = `[data-code-block-content] pre,[data-code-block-banner],[data-composer-chip],[data-changed-files],[data-terminal],[data-read],[data-diff],[data-context-injection-body],[data-search],[data-web],[class*="_ioCard"],[data-inspect],[data-turn-trigger],[style*="--dsh-hover-preview-fade"],:not(pre)>code{-webkit-backdrop-filter:var(--dsh-any-blur-prod,none);backdrop-filter:var(--dsh-any-blur-prod,none)}[data-markdown-variant="compact"] :not(pre)>code{-webkit-backdrop-filter:none;backdrop-filter:none}${BETTER_SIDEBAR_PANEL}{-webkit-backdrop-filter:var(--dsh-any-blur-prod,none);backdrop-filter:var(--dsh-any-blur-prod,none)}${BETTER_SIDEBAR_PANEL}{--dsw-alias-bg-base:var(--dsh-any-prod-panel-bg-base);--dsw-alias-bg-layer-1:var(--dsh-any-prod-panel-layer-1);--dsw-alias-bg-layer-2:var(--dsh-any-prod-panel-layer-2);--dsw-alias-bg-layer-3:var(--dsh-any-prod-panel-layer-3)}@supports (background:color-mix(in srgb,red 50%,transparent)){.md-code-block{background:transparent!important}.md-code-block>div{background-color:transparent!important}.md-code-block [data-code-block-content] pre{background-color:color-mix(in srgb,var(--dsl-code-block-background,var(--dsw-alias-markdown-code-block,transparent)) var(--dsh-any-prod-pct,100%),transparent)!important}.md-code-block [data-code-block-banner]{background-color:color-mix(in srgb,var(--dsl-code-block-banner-background-color,var(--dsw-alias-markdown-code-block-banner,transparent)) var(--dsh-any-prod-pct,100%),transparent)!important}[data-code-block-banner]{background-color:color-mix(in srgb,var(--dsl-code-block-banner-background-color,var(--dsl-code-block-background,var(--dsw-alias-markdown-code-block,transparent))) var(--dsh-any-prod-pct,100%),transparent)!important}[data-code-block-content]{--shiki-background:color-mix(in srgb,var(--dsw-alias-markdown-code-block,var(--dsl-code-block-background,transparent)) var(--dsh-any-prod-pct,100%),transparent)}:not(pre)>code{background-color:color-mix(in srgb,var(--dsw-alias-markdown-inline-code,transparent) var(--dsh-any-prod-pct,100%),transparent)!important}[data-composer-chip]>*{background-color:color-mix(in srgb,var(--dsw-alias-interactive-bg-hover,transparent) var(--dsh-any-prod-pct,100%),transparent)!important}[data-terminal]{background-color:color-mix(in srgb,var(--dsw-alias-markdown-code-block,transparent) var(--dsh-any-prod-pct,100%),transparent)!important}[data-terminal] button{background-color:transparent!important}[data-read],[data-diff]{background-color:color-mix(in srgb,var(--dsw-alias-markdown-code-block,transparent) var(--dsh-any-prod-pct,100%),transparent)!important}[data-read]>div:first-child:not([data-code-block-banner]),[data-diff]>div:first-child:not([data-code-block-banner]){background-color:color-mix(in srgb,var(--dsw-alias-markdown-code-block-banner,transparent) var(--dsh-any-prod-pct,100%),transparent)!important}[data-context-injection-body]{background-color:color-mix(in srgb,var(--dsw-alias-markdown-code-block,transparent) var(--dsh-any-prod-pct,100%),transparent)!important}[data-inspect]{background-color:color-mix(in srgb,var(--dsw-alias-markdown-code-block,transparent) var(--dsh-any-prod-pct,100%),transparent)!important}[data-turn-trigger]{background-color:color-mix(in srgb,var(--dsw-alias-turn-trigger-bg,var(--dsw-alias-markdown-code-block,transparent)) var(--dsh-any-prod-pct,100%),transparent)!important}[data-turn-trigger]:hover{background-color:color-mix(in srgb,var(--dsw-alias-turn-trigger-bg-hover,var(--dsw-alias-interactive-bg-hover,transparent)) var(--dsh-any-prod-pct,100%),transparent)!important}[data-changed-files]{background-color:var(--dsh-any-prod-layer-1,var(--dsw-alias-bg-layer-1,transparent))!important}[data-changed-files]>button:first-child{background-color:color-mix(in srgb,var(--changes-fill,transparent) var(--dsh-any-prod-pct,100%),transparent)!important}[data-changed-files]>button:first-child:hover{background-color:color-mix(in srgb,var(--changes-hover,var(--changes-fill,transparent)) var(--dsh-any-prod-pct,100%),transparent)!important}[data-changed-files]>button:first-child>span:first-child{background-color:color-mix(in srgb,var(--changes-fill,transparent) var(--dsh-any-prod-pct,100%),transparent)!important}[style*="--dsh-hover-preview-fade"]{background-color:color-mix(in srgb,var(--dsw-hovercard-bg,#2c2c2e) var(--dsh-any-prod-pct,100%),transparent)!important}[style*="--dsh-hover-preview-fade"][class*="_preview"]{background-color:var(--dsh-any-prod-layer-1,var(--dsw-alias-bg-layer-1,transparent))!important}[data-search]{background-color:color-mix(in srgb,var(--dsw-alias-markdown-code-block,transparent) var(--dsh-any-prod-pct,100%),transparent)!important}[data-web]{background-color:color-mix(in srgb,var(--dsw-alias-markdown-code-block,transparent) var(--dsh-any-prod-pct,100%),transparent)!important}[class*="_ioCard"]{background-color:color-mix(in srgb,var(--dsw-alias-markdown-code-block,transparent) var(--dsh-any-prod-pct,100%),transparent)!important}}`;
4432
4550
  const STROKE_RULE = [
4433
4551
  `${SETTINGS_PANEL_SEL}{-webkit-text-stroke:var(--dsh-any-stroke-settings-w,0px) var(--dsh-any-stroke-settings-c,transparent);paint-order:stroke fill}`,
@@ -4790,6 +4908,45 @@ window.__ModuleLoader__.load({
4790
4908
  let rightEl = null;
4791
4909
  const PART_BLUR_CLASS = "dab-part-blur";
4792
4910
  const PART_UNDERLAY_CLASS = "dab-part-underlay";
4911
+ /** Paint-order lift for the frosted SIDEBAR column (issue #32).
4912
+ *
4913
+ * The host's `Tooltip` bubble is an IN-PLACE `span[role="tooltip"]`: its sheet
4914
+ * (`dsh-web-frontend` `Tooltip.module.css`, read on 0.2.1-rc.3) reads
4915
+ * `position:fixed;z-index:100`, going to `z-index:1100` only under
4916
+ * `[data-portal]` — and the `portal` prop defaults to FALSE, which the
4917
+ * workspace browser's 添加工作区 trigger (`ui-workspace` `headerActions`) does
4918
+ * not pass. `position:fixed` keeps the bubble anchored to the viewport, but
4919
+ * its z-index only competes INSIDE the nearest stacking-context ancestor. The
4920
+ * frosted sidebar column IS that ancestor — `PART_BLUR_RULE`'s
4921
+ * `isolation:isolate` is what keeps the underlay's `z-index:-1` inside — so
4922
+ * the bubble, hanging below the icon row and past the column's right edge, is
4923
+ * painted as part of the sidebar's subtree, and the center column (a later
4924
+ * sibling, itself an isolated stacking context once 「主界面」模糊 is up)
4925
+ * paints its translucent fill + frost OVER the overhang: the tooltip's right
4926
+ * half comes out blurred and tinted. The trap is not the tooltip's alone —
4927
+ * every in-place float the sidebar hangs (the workspace pick flow anchors
4928
+ * there too) sits in the same local context.
4929
+ *
4930
+ * Lifting the FROSTED sidebar column to `z-index:1` moves its whole paint
4931
+ * slot after the center column's. The two are grid siblings on separate
4932
+ * tracks — they never overlap geometrically, so the lift only matters to
4933
+ * floating layers that overhang a column edge, which is exactly the set that
4934
+ * must win. It stays below everything that has to beat it: the host's overlay
4935
+ * layer (`AppFrame.module.css` `.overlayLayer{z-index:20}`, byte-identical on
4936
+ * 0.2.0-rc.1 and 0.2.1-rc.3), the settings modal (it escapes the center
4937
+ * column through the `isolation:auto` arm below and carries z-index 1000+),
4938
+ * and body-level portaled menus/tooltips (z-index 100 / 1100). Grid items
4939
+ * honor `z-index` without `position`, so this lands even on a column the
4940
+ * host leaves `static`.
4941
+ *
4942
+ * The `:has([role="dialog"])` arm must pair `z-index:auto` with the
4943
+ * isolation arm: with the settings view open the sidebar column itself holds
4944
+ * a `[role="dialog"]` (counted live when the isolation arm was written), and
4945
+ * a column pinned at `z-index:1` would REMAIN a stacking context through that
4946
+ * arm — the dialog would keep its z-index local and paint under the overlay
4947
+ * layer instead of covering the page. Back at `auto` it escapes the column
4948
+ * exactly as it did before this class existed. */
4949
+ const PART_LIFT_CLASS = "dab-part-blur-side";
4793
4950
  /** The AppFrame's main-bg clear-out, as a class rather than an inline write.
4794
4951
  *
4795
4952
  * WHY NOT INLINE (measured on 0.1.7): the frame's own translucent
@@ -4842,7 +4999,7 @@ window.__ModuleLoader__.load({
4842
4999
  * `0.2.1-rc.3` (`AppFrame.module.css` byte-identical rc.2→rc.3). A build
4843
5000
  * that renames the track simply stops matching, which is the untouched host look. */
4844
5001
  const SIDEBAR_ONE_LAYER_RULE = "[class*=\"_sidebarCol\"] *{--dsw-specific-sidebar-fill:transparent}";
4845
- const PART_BLUR_RULE = `.${PART_BLUR_CLASS}{isolation:isolate}.${PART_BLUR_CLASS}:has([role="dialog"]){isolation:auto}.${PART_UNDERLAY_CLASS}{position:absolute;inset:0;z-index:-1;pointer-events:none;border-radius:inherit;backdrop-filter:var(--dsh-any-part-blur,none);-webkit-backdrop-filter:var(--dsh-any-part-blur,none)}`;
5002
+ const PART_BLUR_RULE = `.${PART_BLUR_CLASS}{isolation:isolate}.${PART_BLUR_CLASS}:has([role="dialog"]){isolation:auto}.${PART_LIFT_CLASS}{z-index:1}.${PART_LIFT_CLASS}:has([role="dialog"]){z-index:auto}.${PART_UNDERLAY_CLASS}{position:absolute;inset:0;z-index:-1;pointer-events:none;border-radius:inherit;backdrop-filter:var(--dsh-any-part-blur,none);-webkit-backdrop-filter:var(--dsh-any-part-blur,none)}`;
4846
5003
  let partBlurStyleEl = null;
4847
5004
  function ensurePartBlurStyle() {
4848
5005
  if (partBlurStyleEl?.isConnected) return;
@@ -4878,9 +5035,9 @@ window.__ModuleLoader__.load({
4878
5035
  centerEl = children[overlayIdx - 2] ?? null;
4879
5036
  }
4880
5037
  }
4881
- function setBlur(el, px) {
5038
+ function setBlur(el, px, lift = false) {
4882
5039
  if (el === null) return;
4883
- if (px === 0 && !el.classList.contains(PART_BLUR_CLASS) && el.getAttribute("data-dab-pos-patched") !== "1") return;
5040
+ if (px === 0 && !el.classList.contains(PART_BLUR_CLASS) && !el.classList.contains(PART_LIFT_CLASS) && el.getAttribute("data-dab-pos-patched") !== "1") return;
4884
5041
  const underlay = el.querySelector(`:scope > .${PART_UNDERLAY_CLASS}`);
4885
5042
  if (px > 0) {
4886
5043
  ensurePartBlurStyle();
@@ -4889,6 +5046,7 @@ window.__ModuleLoader__.load({
4889
5046
  el.setAttribute("data-dab-pos-patched", "1");
4890
5047
  }
4891
5048
  el.classList.add(PART_BLUR_CLASS);
5049
+ el.classList.toggle(PART_LIFT_CLASS, lift);
4892
5050
  if (underlay === null) {
4893
5051
  const node = document.createElement("div");
4894
5052
  node.className = PART_UNDERLAY_CLASS;
@@ -4897,6 +5055,7 @@ window.__ModuleLoader__.load({
4897
5055
  el.style.setProperty("--dsh-any-part-blur", `blur(${px}px)`);
4898
5056
  } else {
4899
5057
  el.classList.remove(PART_BLUR_CLASS);
5058
+ el.classList.remove(PART_LIFT_CLASS);
4900
5059
  el.style.removeProperty("--dsh-any-part-blur");
4901
5060
  underlay?.remove();
4902
5061
  if (el.getAttribute("data-dab-pos-patched") === "1") {
@@ -4943,7 +5102,7 @@ window.__ModuleLoader__.load({
4943
5102
  function applyPartBlurs(blurs) {
4944
5103
  discoverParts();
4945
5104
  setBlur(frameEl, 0);
4946
- setBlur(sidebarEl, blurs.sidebar);
5105
+ setBlur(sidebarEl, blurs.sidebar, true);
4947
5106
  setBlur(centerEl, blurs.bg);
4948
5107
  setBlur(rightEl, 0);
4949
5108
  applyBgBlurGlobal(blurs.bg);
@@ -5020,7 +5179,7 @@ window.__ModuleLoader__.load({
5020
5179
  applyBgBlurGlobal(v);
5021
5180
  return;
5022
5181
  }
5023
- setBlur(sidebarEl, v);
5182
+ setBlur(sidebarEl, v, true);
5024
5183
  applySidebarBandBlur(v);
5025
5184
  }
5026
5185
  const VIEW_CARDS = [{
@@ -5628,6 +5787,7 @@ window.__ModuleLoader__.load({
5628
5787
  }
5629
5788
  applyPanelOverrides(rPanelOpacity());
5630
5789
  applyExemptDefaults();
5790
+ applyReaderLanes();
5631
5791
  applyPartBlurs(rBlurs());
5632
5792
  applyStrokes();
5633
5793
  }
@@ -5687,6 +5847,8 @@ window.__ModuleLoader__.load({
5687
5847
  headerStyleEl = null;
5688
5848
  exemptStyleEl?.remove();
5689
5849
  exemptStyleEl = null;
5850
+ readerStyleEl?.remove();
5851
+ readerStyleEl = null;
5690
5852
  for (const v of Object.values(OPACITY_VARS)) document.documentElement.style.removeProperty(v);
5691
5853
  baseTokenKey = "";
5692
5854
  lastBgKey = "";
@@ -7428,6 +7590,161 @@ window.__ModuleLoader__.load({
7428
7590
  ] });
7429
7591
  }
7430
7592
  //#endregion
7593
+ //#region src/client/third-party.ts
7594
+ /**
7595
+ * Third-party plugin compatibility — the registry, and the boot self-check that names
7596
+ * collisions out loud.
7597
+ *
7598
+ * WHY THIS FILE EXISTS
7599
+ * This plugin does not restyle surfaces; it re-emits HOST TOKENS on `<body>` at a
7600
+ * slider's alpha (`OWNED_TOKENS`), and every element that fills from one of those names
7601
+ * fades with it — the host's own, and any other plugin's too. A third-party author has
7602
+ * no reason to expect that: a sticky bar painted with `background:var(--dsw-alias-bg-base)`
7603
+ * is correct on an unthemed host and invisible on ours, because our 「主界面」 slider
7604
+ * defaults to 0. That is issue-class §2-V, and it was found the slow way — a user
7605
+ * reported unreadable text, and someone had to grep another plugin's stylesheet to see
7606
+ * which token it drank.
7607
+ *
7608
+ * WHAT THE SELF-CHECK DOES
7609
+ * Third-party plugins tag their own stylesheets (`style[data-plugin="<package>"]` —
7610
+ * dsh-better-display mounts exactly that, at module load, before any of its views
7611
+ * exists). So the page can answer "who is installed and what of ours do they read"
7612
+ * without anyone reporting it: scan those sheets, look for `var(--<owned token>)`, and
7613
+ * name each hit once in the console. A registered pin says so and stays quiet; an
7614
+ * UNREGISTERED collision logs a warning, which turns "the user finds broken pixels and
7615
+ * files an issue" into "the boot log already knows which plugin and which token".
7616
+ *
7617
+ * WHAT IT DELIBERATELY DOES NOT DO
7618
+ * It never auto-pins an unknown surface. Guessing which of another plugin's elements are
7619
+ * "supposed to stay legible" would repaint someone else's design on a heuristic — the
7620
+ * §2-G lesson about heuristics with side effects. Detection is automatic; protection is
7621
+ * a reviewed table entry, and the settings card exposes it so a user can switch the
7622
+ * whole class off.
7623
+ */
7624
+ /** The table. Adding a plugin is one row here plus the CSS that honors it. */
7625
+ const THIRD_PARTY_PINS = [{
7626
+ plugin: "dsh-better-display",
7627
+ labelKey: "tpReaderLanes",
7628
+ probe: "[data-dsh-better-display]",
7629
+ tokens: ["--dsw-alias-bg-base"]
7630
+ }];
7631
+ /** This plugin's own sheets are tagged with its name (plus a suffix per table); they
7632
+ * consume the tokens by design, so they must never count as a collision. */
7633
+ const SELF = "dsh-any-background";
7634
+ /** The host's own client modules tag their stylesheets the same way — measured on
7635
+ * 0.2.1-rc.3: **157 of the 172 `style[data-plugin]` sheets on a real page belong to
7636
+ * `@deepseek-ai/…`**, and they read these tokens on purpose (fading them IS the
7637
+ * product). Treating them as collisions would concatenate ~540 KB of host CSS per
7638
+ * scan, warn about the host's own UI, and fill the settings card with a hundred rows.
7639
+ * So the host namespace is excluded by prefix. The cost of that rule is asymmetric on
7640
+ * purpose: a scoped third-party package gets missed (no log, no wrong pixel), while a
7641
+ * false positive would spam and mislead. */
7642
+ const HOST_NAMESPACE = "@deepseek-ai/";
7643
+ function isHost(name) {
7644
+ return name.startsWith(HOST_NAMESPACE);
7645
+ }
7646
+ function isSelf(name) {
7647
+ return name === SELF || name.startsWith("dsh-any-background-");
7648
+ }
7649
+ /** The package a sheet came from. Scoped names keep their scope (`@hytime/dsh-x` →
7650
+ * `@hytime/dsh-x`, not `@hytime`), and a `pkg/module.css`-style tag trims to its
7651
+ * package. Ordering matters: this runs AFTER the host/self tests, which must see the
7652
+ * raw attribute — truncating first turned `@deepseek-ai/dsh-client-ui-chat` into
7653
+ * `@deepseek-ai`, which no longer matches the `@deepseek-ai/` prefix and so let 157
7654
+ * host sheets through as one phantom "plugin" (found on the real host, not in the
7655
+ * mock: the mock only ever had one host-ish sheet to begin with). */
7656
+ function pluginKey(name) {
7657
+ if (name.startsWith("@")) {
7658
+ const i = name.indexOf("/", 1);
7659
+ return i > 0 ? name.slice(0, i) : name;
7660
+ }
7661
+ const i = name.indexOf("/");
7662
+ return i > 0 ? name.slice(0, i) : name;
7663
+ }
7664
+ let sightings = [];
7665
+ const subs$1 = /* @__PURE__ */ new Set();
7666
+ const logged = /* @__PURE__ */ new Set();
7667
+ /** Read every tagged third-party stylesheet in `<head>` and report the collisions.
7668
+ * Runs at boot and again whenever a sheet appears; per sheet, not concatenated — the
7669
+ * real page has 170+ sheets and the difference is measurable. */
7670
+ function scan() {
7671
+ if (typeof document === "undefined") return;
7672
+ const hits = /* @__PURE__ */ new Map();
7673
+ const seen = /* @__PURE__ */ new Set();
7674
+ const note = (plugin, tokens) => {
7675
+ seen.add(plugin);
7676
+ if (tokens.length === 0) return;
7677
+ const set = hits.get(plugin) ?? /* @__PURE__ */ new Set();
7678
+ for (const t of tokens) set.add(t);
7679
+ hits.set(plugin, set);
7680
+ };
7681
+ for (const el of Array.from(document.querySelectorAll("style[data-plugin]"))) {
7682
+ const raw = (el.getAttribute("data-plugin") ?? "").trim();
7683
+ if (raw === "" || isSelf(raw) || isHost(raw)) continue;
7684
+ const plugin = pluginKey(raw);
7685
+ const text = el.textContent ?? "";
7686
+ note(plugin, OWNED_TOKENS.filter((t) => text.includes(`var(${t})`) || text.includes(`var(${t},`) || text.includes(`var(${t} `)));
7687
+ }
7688
+ for (const pin of THIRD_PARTY_PINS) if (pin.probe !== void 0 && !seen.has(pin.plugin) && document.querySelector(pin.probe) !== null) note(pin.plugin, []);
7689
+ const next = [];
7690
+ for (const plugin of seen) {
7691
+ const read = [...hits.get(plugin) ?? []];
7692
+ const pin = THIRD_PARTY_PINS.find((p) => p.plugin === plugin);
7693
+ if (pin === void 0 && read.length === 0) continue;
7694
+ const s = {
7695
+ plugin,
7696
+ tokens: read.length > 0 ? read : [...pin?.tokens ?? []],
7697
+ pinned: pin !== void 0,
7698
+ labelKey: pin?.labelKey
7699
+ };
7700
+ next.push(s);
7701
+ report(plugin, s);
7702
+ }
7703
+ const changed = JSON.stringify(next) !== JSON.stringify(sightings);
7704
+ sightings = next;
7705
+ if (changed) subs$1.forEach((cb) => cb());
7706
+ }
7707
+ function report(plugin, s) {
7708
+ if (logged.has(plugin)) return;
7709
+ logged.add(plugin);
7710
+ if (s.pinned) {
7711
+ const state = rProtectThirdParty() ? "the registered compatibility pin keeps them legible" : "a pin exists for it but 「保护第三方表面」 is currently OFF";
7712
+ console.log(`dsh-any-background: ${plugin} detected — its surfaces fill from tokens this plugin fades, and ${state} (${s.tokens.join(", ")})`);
7713
+ return;
7714
+ }
7715
+ console.warn(`dsh-any-background: ${plugin} paints from ${s.tokens.join(", ")} — the same tokens the interface-opacity sliders own, so those surfaces fade with them. No compatibility pin is registered for ${plugin}; if one of its surfaces goes transparent or unreadable, report this line and a pin can be added.`);
7716
+ }
7717
+ /** Snapshot of what the scan found (for the settings card). */
7718
+ function thirdPartySightings() {
7719
+ return sightings;
7720
+ }
7721
+ /** Subscribe to sightings changing. */
7722
+ function subscribeThirdParty(cb) {
7723
+ subs$1.add(cb);
7724
+ return () => {
7725
+ subs$1.delete(cb);
7726
+ };
7727
+ }
7728
+ /** Run the scan at boot and keep it current. Returns an unsubscribe for teardown. */
7729
+ function startThirdPartyWatch() {
7730
+ scan();
7731
+ return watchDom((records) => {
7732
+ let relevant = false;
7733
+ for (const r of records) {
7734
+ for (const n of r.addedNodes) {
7735
+ if (!(n instanceof HTMLStyleElement)) continue;
7736
+ const name = n.getAttribute("data-plugin");
7737
+ if (name !== null && !isSelf(name) && !isHost(name)) {
7738
+ relevant = true;
7739
+ break;
7740
+ }
7741
+ }
7742
+ if (relevant) break;
7743
+ }
7744
+ if (relevant) scan();
7745
+ }, "head");
7746
+ }
7747
+ //#endregion
7431
7748
  //#region src/client/components/LiveSlider.tsx
7432
7749
  /**
7433
7750
  * Zero-lag slider: the thumb and value label update through DOM refs while
@@ -7564,186 +7881,244 @@ window.__ModuleLoader__.load({
7564
7881
  function InterfacePage({ p }) {
7565
7882
  const { t, setOps, setBlurs, setSop, setPanelOp, useStore } = p;
7566
7883
  useStore((s) => s.metaRev);
7567
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
7568
- className: "dab-head dab-rise",
7569
- style: { "--d": 0 },
7570
- children: [
7571
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7572
- className: "dab-overline",
7573
- children: "Surfaces"
7574
- }),
7575
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
7576
- className: "dab-h1",
7577
- children: t("uiTitle")
7578
- }),
7579
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
7580
- className: "dab-desc",
7581
- children: t("descInterface")
7582
- })
7583
- ]
7584
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7585
- className: "dab-grid-parts",
7586
- children: PARTS$1.map((part, i) => {
7587
- const { labelKey, Icon, isSettings, isChat, isTrajectory, isPanel, isProduced, isHeader, isHeaderBar } = part;
7588
- const opKey = part.opKey;
7589
- const label = t(labelKey);
7590
- const blurKey = isChat ? "chat" : isTrajectory ? "trajectory" : isSettings ? "settings" : isPanel ? "panel" : isProduced ? "produced" : isHeader ? "header" : isHeaderBar ? "headerBar" : opKey;
7591
- const opacity = isChat ? rChatTextOpacity() : isTrajectory ? rTrajectoryOpacity() : isSettings ? rSop() : isPanel ? rPanelOpacity() : isProduced ? rProducedOpacity() : isHeader ? rHeaderOpacity() : isHeaderBar ? rHeaderBarOpacity() : rOps()[opKey];
7592
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
7593
- className: "dab-card dab-card-hover dab-rise",
7594
- style: { "--d": i + 1 },
7595
- children: [
7596
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7597
- className: "dab-part-head",
7598
- children: [
7599
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7600
- className: "dab-part-ico",
7601
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, { size: 16 })
7602
- }),
7603
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7604
- className: "dab-part-name",
7605
- children: label
7606
- }),
7607
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
7608
- className: "dab-part-badge",
7609
- children: [Math.round(opacity * 100), "%"]
7610
- })
7611
- ]
7612
- }),
7613
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LiveSlider, {
7614
- label: t("uiOpacity"),
7615
- min: 0,
7616
- max: 100,
7617
- step: 1,
7618
- def: Math.round(opacity * 100),
7619
- fmt: (v) => `${v}%`,
7620
- onInput: (v) => {
7621
- const op = v / 100;
7622
- if (isChat) {
7623
- cfg.chatTextOpacity = op;
7624
- applyViewCards();
7625
- } else if (isTrajectory) {
7626
- cfg.trajectoryOpacity = op;
7627
- applyTrajectoryOverrides(op);
7628
- } else if (isSettings) {
7629
- cfg.settingsOpacity = op;
7630
- applySettingsOverrides(op);
7631
- } else if (isPanel) {
7632
- cfg.panelOpacity = op;
7633
- applyPanelOverrides(op);
7634
- } else if (isProduced) {
7635
- cfg.producedOpacity = op;
7636
- applyProduced();
7637
- } else if (isHeader) {
7638
- cfg.headerOpacity = op;
7639
- applyHeaderPopovers();
7640
- } else if (isHeaderBar) {
7641
- cfg.headerBarOpacity = op;
7642
- applyHeaderBar();
7643
- } else {
7644
- const ops = { ...rOps() };
7645
- ops[opKey] = op;
7646
- cfg.opacities = ops;
7647
- applyCustomTokens(ops);
7648
- }
7649
- saveConfig();
7650
- },
7651
- onChange: (v) => {
7652
- const op = v / 100;
7653
- if (isChat) {
7654
- cfg.chatTextOpacity = op;
7655
- applyViewCards();
7656
- saveConfig();
7657
- } else if (isTrajectory) {
7658
- cfg.trajectoryOpacity = op;
7659
- applyTrajectoryOverrides(op);
7660
- saveConfig();
7661
- } else if (isSettings) setSop(op);
7662
- else if (isPanel) setPanelOp(op);
7663
- else if (isProduced) {
7664
- cfg.producedOpacity = op;
7665
- applyProduced();
7666
- saveConfig();
7667
- } else if (isHeader) {
7668
- cfg.headerOpacity = op;
7669
- applyHeaderPopovers();
7670
- saveConfig();
7671
- } else if (isHeaderBar) {
7672
- cfg.headerBarOpacity = op;
7673
- applyHeaderBar();
7884
+ const [tpRev, setTpRev] = (0, react.useState)(0);
7885
+ (0, react.useEffect)(() => subscribeThirdParty(() => setTpRev((v) => v + 1)), []);
7886
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
7887
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
7888
+ className: "dab-head dab-rise",
7889
+ style: { "--d": 0 },
7890
+ children: [
7891
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7892
+ className: "dab-overline",
7893
+ children: "Surfaces"
7894
+ }),
7895
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
7896
+ className: "dab-h1",
7897
+ children: t("uiTitle")
7898
+ }),
7899
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
7900
+ className: "dab-desc",
7901
+ children: t("descInterface")
7902
+ })
7903
+ ]
7904
+ }),
7905
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7906
+ className: "dab-grid-parts",
7907
+ children: PARTS$1.map((part, i) => {
7908
+ const { labelKey, Icon, isSettings, isChat, isTrajectory, isPanel, isProduced, isHeader, isHeaderBar } = part;
7909
+ const opKey = part.opKey;
7910
+ const label = t(labelKey);
7911
+ const blurKey = isChat ? "chat" : isTrajectory ? "trajectory" : isSettings ? "settings" : isPanel ? "panel" : isProduced ? "produced" : isHeader ? "header" : isHeaderBar ? "headerBar" : opKey;
7912
+ const opacity = isChat ? rChatTextOpacity() : isTrajectory ? rTrajectoryOpacity() : isSettings ? rSop() : isPanel ? rPanelOpacity() : isProduced ? rProducedOpacity() : isHeader ? rHeaderOpacity() : isHeaderBar ? rHeaderBarOpacity() : rOps()[opKey];
7913
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
7914
+ className: "dab-card dab-card-hover dab-rise",
7915
+ style: { "--d": i + 1 },
7916
+ children: [
7917
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7918
+ className: "dab-part-head",
7919
+ children: [
7920
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7921
+ className: "dab-part-ico",
7922
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, { size: 16 })
7923
+ }),
7924
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7925
+ className: "dab-part-name",
7926
+ children: label
7927
+ }),
7928
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
7929
+ className: "dab-part-badge",
7930
+ children: [Math.round(opacity * 100), "%"]
7931
+ })
7932
+ ]
7933
+ }),
7934
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LiveSlider, {
7935
+ label: t("uiOpacity"),
7936
+ min: 0,
7937
+ max: 100,
7938
+ step: 1,
7939
+ def: Math.round(opacity * 100),
7940
+ fmt: (v) => `${v}%`,
7941
+ onInput: (v) => {
7942
+ const op = v / 100;
7943
+ if (isChat) {
7944
+ cfg.chatTextOpacity = op;
7945
+ applyViewCards();
7946
+ } else if (isTrajectory) {
7947
+ cfg.trajectoryOpacity = op;
7948
+ applyTrajectoryOverrides(op);
7949
+ } else if (isSettings) {
7950
+ cfg.settingsOpacity = op;
7951
+ applySettingsOverrides(op);
7952
+ } else if (isPanel) {
7953
+ cfg.panelOpacity = op;
7954
+ applyPanelOverrides(op);
7955
+ } else if (isProduced) {
7956
+ cfg.producedOpacity = op;
7957
+ applyProduced();
7958
+ } else if (isHeader) {
7959
+ cfg.headerOpacity = op;
7960
+ applyHeaderPopovers();
7961
+ } else if (isHeaderBar) {
7962
+ cfg.headerBarOpacity = op;
7963
+ applyHeaderBar();
7964
+ } else {
7965
+ const ops = { ...rOps() };
7966
+ ops[opKey] = op;
7967
+ cfg.opacities = ops;
7968
+ applyCustomTokens(ops);
7969
+ }
7674
7970
  saveConfig();
7675
- } else {
7676
- const ops = { ...rOps() };
7677
- ops[opKey] = op;
7678
- setOps(ops);
7679
- }
7680
- }
7681
- }),
7682
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LiveSlider, {
7683
- label: t("uiBlur"),
7684
- min: 0,
7685
- max: 60,
7686
- step: 1,
7687
- def: rBlurs()[blurKey],
7688
- fmt: (v) => `${v}px`,
7689
- onInput: (v) => {
7690
- const blurs = { ...rBlurs() };
7691
- blurs[blurKey] = v;
7692
- cfg.blurs = blurs;
7693
- setPartBlur(blurKey, v);
7694
- saveConfig();
7695
- },
7696
- onChange: (v) => {
7697
- const blurs = { ...rBlurs() };
7698
- blurs[blurKey] = v;
7699
- setBlurs(blurs);
7700
- }
7701
- }),
7702
- isHeaderBar && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7703
- className: "dab-slider-block",
7704
- title: t("captionInkHint"),
7705
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7706
- className: "dab-slider-label",
7707
- children: t("captionInkLabel")
7708
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7709
- style: {
7710
- display: "flex",
7711
- alignItems: "center",
7712
- gap: 10
7713
7971
  },
7714
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
7715
- className: "dab-select",
7716
- value: rCaptionInk(),
7717
- onChange: (e) => {
7718
- cfg.captionInk = e.target.value;
7972
+ onChange: (v) => {
7973
+ const op = v / 100;
7974
+ if (isChat) {
7975
+ cfg.chatTextOpacity = op;
7976
+ applyViewCards();
7977
+ saveConfig();
7978
+ } else if (isTrajectory) {
7979
+ cfg.trajectoryOpacity = op;
7980
+ applyTrajectoryOverrides(op);
7981
+ saveConfig();
7982
+ } else if (isSettings) setSop(op);
7983
+ else if (isPanel) setPanelOp(op);
7984
+ else if (isProduced) {
7985
+ cfg.producedOpacity = op;
7986
+ applyProduced();
7987
+ saveConfig();
7988
+ } else if (isHeader) {
7989
+ cfg.headerOpacity = op;
7990
+ applyHeaderPopovers();
7991
+ saveConfig();
7992
+ } else if (isHeaderBar) {
7993
+ cfg.headerBarOpacity = op;
7719
7994
  applyHeaderBar();
7720
7995
  saveConfig();
7996
+ } else {
7997
+ const ops = { ...rOps() };
7998
+ ops[opKey] = op;
7999
+ setOps(ops);
8000
+ }
8001
+ }
8002
+ }),
8003
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LiveSlider, {
8004
+ label: t("uiBlur"),
8005
+ min: 0,
8006
+ max: 60,
8007
+ step: 1,
8008
+ def: rBlurs()[blurKey],
8009
+ fmt: (v) => `${v}px`,
8010
+ onInput: (v) => {
8011
+ const blurs = { ...rBlurs() };
8012
+ blurs[blurKey] = v;
8013
+ cfg.blurs = blurs;
8014
+ setPartBlur(blurKey, v);
8015
+ saveConfig();
8016
+ },
8017
+ onChange: (v) => {
8018
+ const blurs = { ...rBlurs() };
8019
+ blurs[blurKey] = v;
8020
+ setBlurs(blurs);
8021
+ }
8022
+ }),
8023
+ isHeaderBar && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8024
+ className: "dab-slider-block",
8025
+ title: t("captionInkHint"),
8026
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8027
+ className: "dab-slider-label",
8028
+ children: t("captionInkLabel")
8029
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8030
+ style: {
8031
+ display: "flex",
8032
+ alignItems: "center",
8033
+ gap: 10
7721
8034
  },
7722
- children: [
7723
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
7724
- value: "auto",
7725
- children: t("captionInkAuto")
7726
- }),
7727
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
7728
- value: "theme",
7729
- children: t("captionInkTheme")
7730
- }),
7731
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
7732
- value: "white",
7733
- children: t("captionInkWhite")
7734
- }),
7735
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
7736
- value: "black",
7737
- children: t("captionInkBlack")
7738
- })
7739
- ]
7740
- })
7741
- })]
7742
- })
7743
- ]
7744
- }, blurKey);
8035
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
8036
+ className: "dab-select",
8037
+ value: rCaptionInk(),
8038
+ onChange: (e) => {
8039
+ cfg.captionInk = e.target.value;
8040
+ applyHeaderBar();
8041
+ saveConfig();
8042
+ },
8043
+ children: [
8044
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
8045
+ value: "auto",
8046
+ children: t("captionInkAuto")
8047
+ }),
8048
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
8049
+ value: "theme",
8050
+ children: t("captionInkTheme")
8051
+ }),
8052
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
8053
+ value: "white",
8054
+ children: t("captionInkWhite")
8055
+ }),
8056
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
8057
+ value: "black",
8058
+ children: t("captionInkBlack")
8059
+ })
8060
+ ]
8061
+ })
8062
+ })]
8063
+ })
8064
+ ]
8065
+ }, blurKey);
8066
+ })
8067
+ }),
8068
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
8069
+ className: "dab-card dab-rise",
8070
+ style: { "--d": PARTS$1.length + 1 },
8071
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8072
+ className: "dab-row-head",
8073
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8074
+ style: {
8075
+ display: "flex",
8076
+ alignItems: "center",
8077
+ gap: 10
8078
+ },
8079
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8080
+ className: "dab-part-ico",
8081
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PanelIcon, { size: 16 })
8082
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8083
+ className: "dab-part-name",
8084
+ children: t("tpTitle")
8085
+ })]
8086
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
8087
+ type: "button",
8088
+ className: `dab-toggle${rProtectThirdParty() ? " is-on" : ""}`,
8089
+ role: "switch",
8090
+ "aria-checked": rProtectThirdParty(),
8091
+ "aria-label": t("tpEnabled"),
8092
+ title: t("tpHint"),
8093
+ onClick: () => {
8094
+ cfg.protectThirdParty = !rProtectThirdParty();
8095
+ applyReaderLanes();
8096
+ saveConfig();
8097
+ setTpRev((v) => v + 1);
8098
+ },
8099
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "dab-toggle-knob" })
8100
+ })]
8101
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8102
+ className: "dab-slider-block",
8103
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8104
+ className: "dab-slider-label",
8105
+ title: t("tpHint"),
8106
+ children: t("tpEnabled")
8107
+ }), thirdPartySightings().length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8108
+ style: { fontSize: 12 },
8109
+ children: t("tpNone")
8110
+ }) : thirdPartySightings().map((s) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
8111
+ style: { fontSize: 12 },
8112
+ title: s.tokens.join(", "),
8113
+ children: [
8114
+ s.plugin,
8115
+ ":",
8116
+ s.pinned && s.labelKey ? `${t(s.labelKey)} ${t("tpPinned")}` : s.pinned ? t("tpPinned") : t("tpUnpinned")
8117
+ ]
8118
+ }, s.plugin))]
8119
+ })]
7745
8120
  })
7746
- })] });
8121
+ ] });
7747
8122
  }
7748
8123
  //#endregion
7749
8124
  //#region src/client/components/pages/FontPage.tsx
@@ -10697,6 +11072,7 @@ window.__ModuleLoader__.load({
10697
11072
  teardownWp();
10698
11073
  }, "dsh-any-background: wp cleanup");
10699
11074
  ctx.effect(() => startBetterSidebarWatch(), "dsh-any-background: better-sidebar watch");
11075
+ ctx.effect(() => startThirdPartyWatch(), "dsh-any-background: third-party scan");
10700
11076
  ctx.effect(() => startHeaderPopoverTagging(), "dsh-any-background: header popover tagging");
10701
11077
  ctx.effect(() => ctx.on("theme/change", () => {
10702
11078
  if (rHasColor() || rBgDark() !== null || rSchemeOverride() !== "auto") {