dsh-skin-galactic-opera 0.1.8 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,24 @@
2
2
 
3
3
  All notable changes to this package will be documented in this file.
4
4
 
5
+ ## 0.2.0 - 2026-09-04
6
+
7
+ ### Changed
8
+
9
+ - Pauses skin animation work while the page is hidden to reduce background CPU/GPU usage.
10
+ - Suspends telemetry scans while the page is hidden, then forces an immediate refresh plus a short follow-up when the page becomes visible again.
11
+
12
+ ## 0.1.9 - 2026-09-03
13
+
14
+ ### Changed
15
+
16
+ - Replaces the README with separate English and Simplified Chinese sections linked from a language switcher at the top.
17
+ - Refreshes the bottom-right telemetry HUD promptly when switching conversations or navigating between sessions.
18
+
19
+ ### Fixed
20
+
21
+ - Keeps navigation-triggered telemetry refreshes active even when the instruction input still has focus.
22
+
5
23
  ## 0.1.8 - 2026-09-03
6
24
 
7
25
  ### Added
package/README.md CHANGED
@@ -1,37 +1,51 @@
1
1
  # dsh-skin-galactic-opera
2
2
 
3
- Unofficial cinematic space-opera dark skin bundle for the DSH Web GUI.
3
+ <p align="center">DSH Web GUI 非官方 太空歌剧风 暗色 动态 皮肤插件</p>
4
+
5
+ <p align="center">
6
+ <a href="#english">English</a> · <a href="#简体中文">简体中文</a>
7
+ </p>
4
8
 
5
- 非官方 DSH Web GUI 太空歌剧暗色皮肤插件。它使用通用科幻视觉语言,包括深空背景、霓虹 HUD、黑洞吸积盘、行星与卫星轨道、流星、V 形战机剪影和提交消息时的超空间跃迁效果。
9
+ ---
6
10
 
7
- This package does not include or recreate any official franchise logos, characters, ship designs, quotes, or proprietary names.
11
+ ## English
12
+
13
+ Unofficial cinematic space-opera dark skin bundle for the DSH Web GUI.
8
14
 
9
- 本插件不包含也不复刻任何特定影视作品的官方 logo、角色、飞船设计、台词或专有名称。
15
+ It uses generic sci-fi visual language: deep-space backgrounds, neon HUD chrome, a central black-hole accretion disk, a planet with orbital elements, scanner UI, comet trails, energy beams, subtle starfighter silhouettes, telemetry chrome, and a hyperspace-style send effect.
10
16
 
11
- ## Features
17
+ > This package is not affiliated with, endorsed by, or derived from any specific franchise. It does **not** include or recreate official logos, characters, ship designs, quotes, proprietary names, or protected visual designs.
18
+
19
+ ### Features
12
20
 
13
21
  - Registers and enables the `galactic-opera` dark theme.
14
22
  - Overrides DSH Web GUI theme tokens for backgrounds, borders, text, brand colors, state colors, scrollbars, and sidebar surfaces.
15
- - Adds global visual styling for star fields, diagonal hyperspace lanes, hover glow, selection highlight, and code block glow.
16
- - Adds decorative space-opera elements:
17
- - Central black-hole accretion disk.
18
- - Top-right planet, one moon, and an elliptical satellite orbit.
19
- - Bottom-left rotating scanner HUD and holographic route map.
20
- - Red, cyan, and gold energy beams.
21
- - Sparse twinkling stars, comets, and generic V-shaped starfighter silhouettes.
22
- - Gold mission-briefing crawl panel.
23
+ - Adds global space-opera styling:
24
+ - deep-space background layers;
25
+ - diagonal hyperspace lanes;
26
+ - hover glow for buttons, inputs, messages, and code blocks;
27
+ - sci-fi text selection and code block glow.
28
+ - Adds decorative visual elements:
29
+ - central black-hole accretion disk with subtle infall particles;
30
+ - top-right planet, moon, and elliptical satellite orbit;
31
+ - bottom-left rotating scanner HUD and holographic route map;
32
+ - red, cyan, and gold energy beams;
33
+ - sparse twinkling stars, comets, and generic V-shaped starfighter silhouettes;
34
+ - gold mission-briefing crawl panel.
23
35
  - Adds interactions:
24
- - Subtle mouse parallax.
25
- - Click energy pulse.
26
- - Hover energy outlines on buttons, inputs, messages, and code blocks.
27
- - Bottom telemetry/status information is mirrored into a fixed bottom-right sci-fi panel.
28
- - Scanner target dot projected from the pointer direction relative to the viewport center; the target lights only when the scanner sweep passes it.
29
- - Automatic hyperspace burst after submitting a new instruction or message.
36
+ - subtle pointer parallax;
37
+ - click energy pulse;
38
+ - scanner target ping projected from the clicked position;
39
+ - automatic hyperspace burst after submitting a new instruction or message;
40
+ - fixed bottom-right telemetry/status HUD panel.
41
+ - Uses Shadow DOM for the decorative layer to reduce interference from host application styles.
30
42
  - Respects `prefers-reduced-motion: reduce` by disabling major animations.
31
43
 
32
- ## Telemetry panel
44
+ ### Telemetry panel
45
+
46
+ When DSH Web exposes bottom status/usage data, the skin keeps the original UI layout in place and mirrors the detected global telemetry into a non-interactive bottom-right HUD panel.
33
47
 
34
- When DSH Web exposes bottom status/usage data, the skin keeps the original UI layout in place and mirrors the detected global telemetry into a non-interactive bottom-right HUD panel. The panel is intended to show a consistent five-line status block:
48
+ The panel is intended to show a consistent five-line status block:
35
49
 
36
50
  ```text
37
51
  312 轮 · 8732 步
@@ -43,9 +57,9 @@ LLM 2253m42s · 工具调用 972m8s
43
57
 
44
58
  The skin treats each original pipe-separated bottom-bar segment as a complete information block, so combined items such as `首 token 平均 ... · ... tok/s` and `输入 ... · 输出 ...` remain intact. It also filters nearby message-level timing text and settings/menu text so the panel stays focused on global session telemetry.
45
59
 
46
- ## Installation
60
+ ### Installation
47
61
 
48
- ### From npm
62
+ #### From npm
49
63
 
50
64
  Recommended for most users:
51
65
 
@@ -59,13 +73,13 @@ npm package page:
59
73
  https://www.npmjs.com/package/dsh-skin-galactic-opera
60
74
  ```
61
75
 
62
- ### From GitHub
76
+ #### From GitHub
63
77
 
64
78
  ```powershell
65
79
  dsh plugin --profile web add https://github.com/xmnathan/dsh-skin-galactic-opera.git
66
80
  ```
67
81
 
68
- ### From a local checkout
82
+ #### From a local checkout
69
83
 
70
84
  From the parent directory of this package:
71
85
 
@@ -91,7 +105,7 @@ If the browser keeps an older client bundle, use a hard refresh:
91
105
  Ctrl + F5
92
106
  ```
93
107
 
94
- ## Uninstall
108
+ ### Uninstall
95
109
 
96
110
  ```powershell
97
111
  dsh plugin --profile web remove dsh-skin-galactic-opera
@@ -99,7 +113,7 @@ dsh plugin --profile web remove dsh-skin-galactic-opera
99
113
 
100
114
  Refresh the Web GUI after uninstalling.
101
115
 
102
- ## Development
116
+ ### Development
103
117
 
104
118
  Check browser-side JavaScript syntax:
105
119
 
@@ -119,7 +133,7 @@ Publish to npm:
119
133
  npm publish
120
134
  ```
121
135
 
122
- ## Package structure
136
+ ### Package structure
123
137
 
124
138
  ```text
125
139
  dsh-skin-galactic-opera/
@@ -138,6 +152,160 @@ dsh-skin-galactic-opera/
138
152
  - `lib/index.js`: host-side no-op entry.
139
153
  - `lib/client.js`: browser-side theme, decoration, and interaction implementation.
140
154
 
141
- ## License
155
+ ### License
156
+
157
+ MIT
158
+
159
+ <p align="right"><a href="#dsh-skin-galactic-opera">Back to top ↑</a></p>
160
+
161
+ ---
162
+
163
+ ## 简体中文
164
+
165
+ DSH Web GUI 的非官方电影感太空歌剧暗色皮肤插件。
166
+
167
+ 它使用通用科幻视觉语言:深空背景、霓虹 HUD、中央黑洞吸积盘、行星与轨道元素、扫描器界面、彗星轨迹、能量束、克制的战机剪影、遥测信息面板,以及发送消息时的超空间风格特效。
168
+
169
+ > 本插件不隶属于、不受认可于、也不派生自任何特定影视或游戏系列。它**不包含也不复刻**任何官方 logo、角色、飞船设计、台词、专有名称或受保护视觉设计。
170
+
171
+ ### 功能特性
172
+
173
+ - 注册并启用 `galactic-opera` 暗色主题。
174
+ - 覆盖 DSH Web GUI 的主题 token,包括背景、边框、文本、品牌色、状态色、滚动条和侧边栏表面。
175
+ - 添加全局太空歌剧风格:
176
+ - 深空背景层;
177
+ - 斜向超空间航道;
178
+ - 按钮、输入框、消息和代码块的悬停辉光;
179
+ - 科幻风格文本选中与代码块辉光。
180
+ - 添加装饰性视觉元素:
181
+ - 带轻微吸入粒子的中央黑洞吸积盘;
182
+ - 右上角行星、卫星和椭圆卫星轨道;
183
+ - 左下角旋转扫描盘 HUD 和全息路线图;
184
+ - 红、青、金三色能量束;
185
+ - 稀疏闪烁星点、彗星和通用 V 形战机剪影;
186
+ - 金色任务简报滚动面板。
187
+ - 添加交互效果:
188
+ - 轻微鼠标视差;
189
+ - 点击能量脉冲;
190
+ - 根据点击位置投影到扫描盘的目标 ping;
191
+ - 提交新指令或消息后的自动超空间爆发;
192
+ - 固定在右下角的 telemetry / 状态 HUD 面板。
193
+ - 装饰层使用 Shadow DOM,减少宿主应用样式对行星、扫描盘、点击脉冲等几何形态的污染。
194
+ - 遵循 `prefers-reduced-motion: reduce`,会关闭主要动画。
195
+
196
+ ### Telemetry 面板
197
+
198
+ 当 DSH Web 暴露底部状态/用量信息时,皮肤会保留原始 UI 布局,并将检测到的全局 telemetry 镜像到右下角的非交互 HUD 面板中。
199
+
200
+ 面板目标是稳定显示五行状态信息:
201
+
202
+ ```text
203
+ 312 轮 · 8732 步
204
+ LLM 2253m42s · 工具调用 972m8s
205
+ 首 token 平均 10.5s · 58 tok/s
206
+ 缓存命中 97%
207
+ 输入 1369M tok · 输出 2.5M tok
208
+ ```
209
+
210
+ 皮肤会把原始底栏中用竖线分隔的每一段都视为完整信息块,因此 `首 token 平均 ... · ... tok/s`、`输入 ... · 输出 ...` 这类组合信息不会被拆散。同时也会过滤附近的单条消息耗时、设置菜单文本等内容,让面板聚焦于全局会话 telemetry。
211
+
212
+ ### 安装
213
+
214
+ #### 通过 npm 安装
215
+
216
+ 推荐大多数用户使用:
217
+
218
+ ```powershell
219
+ dsh plugin --profile web add dsh-skin-galactic-opera
220
+ ```
221
+
222
+ npm 包页面:
223
+
224
+ ```text
225
+ https://www.npmjs.com/package/dsh-skin-galactic-opera
226
+ ```
227
+
228
+ #### 通过 GitHub 安装
229
+
230
+ ```powershell
231
+ dsh plugin --profile web add https://github.com/xmnathan/dsh-skin-galactic-opera.git
232
+ ```
233
+
234
+ #### 通过本地源码安装
235
+
236
+ 在本包的上级目录执行:
237
+
238
+ ```powershell
239
+ dsh plugin --profile web add ./dsh-skin-galactic-opera
240
+ ```
241
+
242
+ 也可以使用绝对路径:
243
+
244
+ ```powershell
245
+ dsh plugin --profile web add D:\path\to\dsh-skin-galactic-opera
246
+ ```
247
+
248
+ 然后刷新 DSH Web GUI,通常地址为:
249
+
250
+ ```text
251
+ http://127.0.0.1:3080
252
+ ```
253
+
254
+ 如果浏览器仍然缓存旧的客户端 bundle,请使用硬刷新:
255
+
256
+ ```text
257
+ Ctrl + F5
258
+ ```
259
+
260
+ ### 卸载
261
+
262
+ ```powershell
263
+ dsh plugin --profile web remove dsh-skin-galactic-opera
264
+ ```
265
+
266
+ 卸载后刷新 Web GUI。
267
+
268
+ ### 开发
269
+
270
+ 检查浏览器端 JavaScript 语法:
271
+
272
+ ```powershell
273
+ npm run check
274
+ ```
275
+
276
+ 发布前预览 npm 包内容:
277
+
278
+ ```powershell
279
+ npm run pack:dry
280
+ ```
281
+
282
+ 发布到 npm:
283
+
284
+ ```powershell
285
+ npm publish
286
+ ```
287
+
288
+ ### 包结构
289
+
290
+ ```text
291
+ dsh-skin-galactic-opera/
292
+ ├─ package.json
293
+ ├─ cordis.patch.yml
294
+ ├─ README.md
295
+ ├─ LICENSE
296
+ ├─ CHANGELOG.md
297
+ └─ lib/
298
+ ├─ index.js
299
+ └─ client.js
300
+ ```
301
+
302
+ - `package.json`:包元数据、npm 发布设置、DSH bundle 声明和 Web client 插件声明。
303
+ - `cordis.patch.yml`:将 `dsh-skin-galactic-opera` client 插件插入 Web profile。
304
+ - `lib/index.js`:宿主侧空实现入口。
305
+ - `lib/client.js`:浏览器侧主题、装饰层和交互实现。
306
+
307
+ ### 许可证
142
308
 
143
309
  MIT
310
+
311
+ <p align="right"><a href="#dsh-skin-galactic-opera">返回顶部 ↑</a></p>
package/lib/client.js CHANGED
@@ -11,6 +11,7 @@ window.__ModuleLoader__.load({
11
11
  const STYLE_ID = "dsh-skin-galactic-opera-style";
12
12
  const DECOR_ID = "dsh-skin-galactic-opera-decor";
13
13
  const DECOR_HOST_CLASS = "dsh-skin-galactic-opera-decor-host";
14
+ const PAGE_HIDDEN_CLASS = "dsh-skin-galactic-opera-page-hidden";
14
15
  const CSS_TAG_ID = `${PLUGIN_ID}/global.css`;
15
16
 
16
17
  const TOKENS = Object.freeze({
@@ -170,6 +171,16 @@ body.dsh-skin-galactic-opera.dsh-skin-galactic-opera-input-active :is(.dsh-skin-
170
171
  opacity: 0.16 !important;
171
172
  }
172
173
 
174
+ body.dsh-skin-galactic-opera.dsh-skin-galactic-opera-page-hidden::before,
175
+ body.dsh-skin-galactic-opera.dsh-skin-galactic-opera-page-hidden::after,
176
+ body.dsh-skin-galactic-opera.dsh-skin-galactic-opera-page-hidden #root::before,
177
+ body.dsh-skin-galactic-opera.dsh-skin-galactic-opera-page-hidden .dsh-skin-galactic-opera-decor::before,
178
+ body.dsh-skin-galactic-opera.dsh-skin-galactic-opera-page-hidden .dsh-skin-galactic-opera-decor *,
179
+ body.dsh-skin-galactic-opera.dsh-skin-galactic-opera-page-hidden .dsh-skin-galactic-opera-decor *::before,
180
+ body.dsh-skin-galactic-opera.dsh-skin-galactic-opera-page-hidden .dsh-skin-galactic-opera-decor *::after {
181
+ animation-play-state: paused !important;
182
+ }
183
+
173
184
  body.dsh-skin-galactic-opera #root > *,
174
185
  body.dsh-skin-galactic-opera main,
175
186
  body.dsh-skin-galactic-opera aside,
@@ -1149,6 +1160,7 @@ body.dsh-skin-galactic-opera .dsh-skin-telemetry-value-full {
1149
1160
  return `${CSS
1150
1161
  .replace(/body\.dsh-skin-galactic-opera\.dsh-skin-galactic-opera-input-active\s+/g, ":host-context(body.dsh-skin-galactic-opera.dsh-skin-galactic-opera-input-active) ")
1151
1162
  .replace(/body\.dsh-skin-galactic-opera\.dsh-skin-galactic-opera-hyperspace\s+/g, ":host-context(body.dsh-skin-galactic-opera.dsh-skin-galactic-opera-hyperspace) ")
1163
+ .replace(/body\.dsh-skin-galactic-opera\.dsh-skin-galactic-opera-page-hidden\s+/g, ":host-context(body.dsh-skin-galactic-opera.dsh-skin-galactic-opera-page-hidden) ")
1152
1164
  .replace(/body\.dsh-skin-galactic-opera\s+/g, "")}
1153
1165
 
1154
1166
  .dsh-skin-galactic-opera-decor {
@@ -1249,6 +1261,20 @@ body.dsh-skin-galactic-opera .dsh-skin-telemetry-value-full {
1249
1261
  }
1250
1262
 
1251
1263
  const EDITABLE_CONTENT_SELECTOR = `[contenteditable]:not([contenteditable="false"])`;
1264
+
1265
+ function installVisibilityState() {
1266
+ if (typeof document === "undefined" || typeof window === "undefined") return () => {};
1267
+ const syncVisibilityClass = () => {
1268
+ document.body.classList.toggle(PAGE_HIDDEN_CLASS, document.hidden);
1269
+ };
1270
+ syncVisibilityClass();
1271
+ document.addEventListener("visibilitychange", syncVisibilityClass);
1272
+ return () => {
1273
+ document.removeEventListener("visibilitychange", syncVisibilityClass);
1274
+ document.body.classList.remove(PAGE_HIDDEN_CLASS);
1275
+ };
1276
+ }
1277
+
1252
1278
  let reducedMotionQuery;
1253
1279
  let reducedMotionMatches = false;
1254
1280
  let reducedMotionListener;
@@ -1452,9 +1478,14 @@ body.dsh-skin-galactic-opera .dsh-skin-telemetry-value-full {
1452
1478
  let currentSources = new Set();
1453
1479
  let lastText = "";
1454
1480
  let frame = 0;
1481
+ let frameIgnoresTelemetryPause = false;
1455
1482
  let forceTelemetryRescan = true;
1456
1483
  let telemetryRefreshesSinceFullScan = 0;
1484
+ let lastTelemetryLocationKey = typeof window.location === "undefined" ? "" : `${window.location.pathname}${window.location.search}${window.location.hash}`;
1485
+ const telemetryNavigationTimers = new Set();
1457
1486
  const TELEMETRY_UPDATE_DELAY = 300;
1487
+ const TELEMETRY_NAV_UPDATE_DELAY = 48;
1488
+ const TELEMETRY_NAV_FOLLOWUP_DELAY = 220;
1458
1489
  const TELEMETRY_FULL_RESCAN_UPDATES = 7;
1459
1490
  let collapsed = false;
1460
1491
  try {
@@ -1668,9 +1699,10 @@ body.dsh-skin-galactic-opera .dsh-skin-telemetry-value-full {
1668
1699
  if (!sources.every((source) => source.isConnected && getCandidateInfo(source) !== undefined)) return false;
1669
1700
  return splitTelemetryRows(getSourcesText(currentSources)).length > 0;
1670
1701
  };
1671
- const update = () => {
1702
+ const update = ({ ignorePause = false } = {}) => {
1672
1703
  frame = 0;
1673
- if (isTelemetryPaused()) return;
1704
+ frameIgnoresTelemetryPause = false;
1705
+ if (document.hidden || (!ignorePause && isTelemetryPaused())) return;
1674
1706
  let nextSources = currentSources;
1675
1707
  let text = getSourcesText(nextSources);
1676
1708
  const needsFullScan = forceTelemetryRescan || telemetryRefreshesSinceFullScan >= TELEMETRY_FULL_RESCAN_UPDATES || !areCachedSourcesUsable();
@@ -1689,13 +1721,78 @@ body.dsh-skin-galactic-opera .dsh-skin-telemetry-value-full {
1689
1721
  lastText = text;
1690
1722
  renderPanel(text);
1691
1723
  };
1692
- const schedule = () => {
1693
- if (isTelemetryPaused()) return;
1694
- if (frame === 0) frame = window.setTimeout(update, TELEMETRY_UPDATE_DELAY);
1724
+ const schedule = ({ delay = TELEMETRY_UPDATE_DELAY, force = false, ignorePause = false } = {}) => {
1725
+ if (document.hidden || (!ignorePause && isTelemetryPaused())) return;
1726
+ if (force) forceTelemetryRescan = true;
1727
+ if (frame !== 0) {
1728
+ if (!force && delay >= TELEMETRY_UPDATE_DELAY) return;
1729
+ window.clearTimeout(frame);
1730
+ frameIgnoresTelemetryPause = false;
1731
+ }
1732
+ frameIgnoresTelemetryPause = ignorePause;
1733
+ frame = window.setTimeout(() => update({ ignorePause }), delay);
1734
+ };
1735
+ const getTelemetryLocationKey = () => `${window.location.pathname}${window.location.search}${window.location.hash}`;
1736
+ const setTelemetryNavigationTimer = (callback, delay) => {
1737
+ const timer = window.setTimeout(() => {
1738
+ telemetryNavigationTimers.delete(timer);
1739
+ callback();
1740
+ }, delay);
1741
+ telemetryNavigationTimers.add(timer);
1742
+ return timer;
1695
1743
  };
1744
+ const refreshTelemetryForNavigation = () => {
1745
+ lastTelemetryLocationKey = getTelemetryLocationKey();
1746
+ schedule({ delay: TELEMETRY_NAV_UPDATE_DELAY, force: true, ignorePause: true });
1747
+ setTelemetryNavigationTimer(() => schedule({ delay: 0, force: true, ignorePause: true }), TELEMETRY_NAV_FOLLOWUP_DELAY);
1748
+ };
1749
+ const checkTelemetryLocation = () => {
1750
+ const nextKey = getTelemetryLocationKey();
1751
+ if (nextKey !== lastTelemetryLocationKey) refreshTelemetryForNavigation();
1752
+ };
1753
+ const onTelemetryNavigationClick = (event) => {
1754
+ if (isSkinOwnedNode(event.target) || isEditableTarget(event.target)) return;
1755
+ const target = event.target instanceof Element ? event.target.closest("a[href], button, [role=button], [aria-current], [data-active]") : null;
1756
+ if (target === null) return;
1757
+ schedule({ delay: TELEMETRY_NAV_UPDATE_DELAY, force: true, ignorePause: true });
1758
+ setTelemetryNavigationTimer(checkTelemetryLocation, 0);
1759
+ setTelemetryNavigationTimer(() => schedule({ delay: 0, force: true, ignorePause: true }), TELEMETRY_NAV_FOLLOWUP_DELAY);
1760
+ };
1761
+ const isPotentialTelemetryNavigationMutation = (mutation) => {
1762
+ if (mutation.type !== "childList") return false;
1763
+ const nodes = [...mutation.addedNodes, ...mutation.removedNodes];
1764
+ return nodes.some((node) => {
1765
+ const element = getMutationElement(node);
1766
+ if (!(element instanceof Element) || isSkinOwnedNode(element) || isEditableTarget(element)) return false;
1767
+ const text = normalizeTelemetryText(element.textContent || "");
1768
+ return element.matches?.("main, section, article, aside, nav, [role=main], [role=navigation], [data-active], [aria-current]")
1769
+ || element.querySelector?.("main, section, article, aside, nav, [role=main], [role=navigation], [data-active], [aria-current]") !== null
1770
+ || telemetryPattern.test(text);
1771
+ });
1772
+ };
1773
+ const originalPushState = window.history?.pushState;
1774
+ const originalReplaceState = window.history?.replaceState;
1775
+ let telemetryPushState;
1776
+ let telemetryReplaceState;
1777
+ if (typeof originalPushState === "function") {
1778
+ telemetryPushState = function dshSkinTelemetryPushState(...args) {
1779
+ const result = originalPushState.apply(this, args);
1780
+ setTelemetryNavigationTimer(checkTelemetryLocation, 0);
1781
+ return result;
1782
+ };
1783
+ window.history.pushState = telemetryPushState;
1784
+ }
1785
+ if (typeof originalReplaceState === "function") {
1786
+ telemetryReplaceState = function dshSkinTelemetryReplaceState(...args) {
1787
+ const result = originalReplaceState.apply(this, args);
1788
+ setTelemetryNavigationTimer(checkTelemetryLocation, 0);
1789
+ return result;
1790
+ };
1791
+ window.history.replaceState = telemetryReplaceState;
1792
+ }
1696
1793
  const onTelemetryFocusIn = (event) => {
1697
1794
  if (!isInstructionInputTarget(event.target)) return;
1698
- if (frame !== 0) {
1795
+ if (frame !== 0 && !frameIgnoresTelemetryPause) {
1699
1796
  window.clearTimeout(frame);
1700
1797
  frame = 0;
1701
1798
  }
@@ -1707,21 +1804,53 @@ body.dsh-skin-galactic-opera .dsh-skin-telemetry-value-full {
1707
1804
  forceTelemetryRescan = true;
1708
1805
  schedule();
1709
1806
  };
1807
+ const onTelemetryVisibilityChange = () => {
1808
+ if (document.hidden) {
1809
+ if (frame !== 0) {
1810
+ window.clearTimeout(frame);
1811
+ frame = 0;
1812
+ }
1813
+ frameIgnoresTelemetryPause = false;
1814
+ for (const timer of telemetryNavigationTimers) window.clearTimeout(timer);
1815
+ telemetryNavigationTimers.clear();
1816
+ return;
1817
+ }
1818
+ schedule({ delay: 0, force: true, ignorePause: true });
1819
+ setTelemetryNavigationTimer(() => schedule({ delay: 0, force: true, ignorePause: true }), TELEMETRY_NAV_FOLLOWUP_DELAY);
1820
+ };
1710
1821
  const observer = typeof MutationObserver === "undefined" ? undefined : new MutationObserver((mutations) => {
1711
1822
  if (mutations.every(isSkinOwnedMutation)) return;
1823
+ if (isTelemetryPaused() && mutations.some(isPotentialTelemetryNavigationMutation)) {
1824
+ schedule({ delay: TELEMETRY_NAV_UPDATE_DELAY, force: true, ignorePause: true });
1825
+ setTelemetryNavigationTimer(() => schedule({ delay: 0, force: true, ignorePause: true }), TELEMETRY_NAV_FOLLOWUP_DELAY);
1826
+ return;
1827
+ }
1712
1828
  schedule();
1713
1829
  });
1714
1830
  schedule();
1715
1831
  observer?.observe(document.body, { childList: true, subtree: true, characterData: true });
1716
1832
  window.addEventListener("resize", onTelemetryResize, { passive: true });
1833
+ document.addEventListener("visibilitychange", onTelemetryVisibilityChange);
1834
+ window.addEventListener("popstate", checkTelemetryLocation, { passive: true });
1835
+ window.addEventListener("hashchange", checkTelemetryLocation, { passive: true });
1836
+ document.addEventListener("click", onTelemetryNavigationClick, true);
1717
1837
  document.addEventListener("focusin", onTelemetryFocusIn);
1718
1838
  document.addEventListener("focusout", onTelemetryFocusOut);
1719
1839
  return () => {
1720
1840
  observer?.disconnect();
1841
+ if (telemetryPushState !== undefined && window.history.pushState === telemetryPushState) window.history.pushState = originalPushState;
1842
+ if (telemetryReplaceState !== undefined && window.history.replaceState === telemetryReplaceState) window.history.replaceState = originalReplaceState;
1721
1843
  window.removeEventListener("resize", onTelemetryResize);
1844
+ document.removeEventListener("visibilitychange", onTelemetryVisibilityChange);
1845
+ window.removeEventListener("popstate", checkTelemetryLocation);
1846
+ window.removeEventListener("hashchange", checkTelemetryLocation);
1847
+ document.removeEventListener("click", onTelemetryNavigationClick, true);
1722
1848
  document.removeEventListener("focusin", onTelemetryFocusIn);
1723
1849
  document.removeEventListener("focusout", onTelemetryFocusOut);
1724
1850
  if (frame !== 0) window.clearTimeout(frame);
1851
+ frameIgnoresTelemetryPause = false;
1852
+ for (const timer of telemetryNavigationTimers) window.clearTimeout(timer);
1853
+ telemetryNavigationTimers.clear();
1725
1854
  for (const source of currentSources) source.classList.remove("dsh-skin-telemetry-source-hidden");
1726
1855
  toggle.remove();
1727
1856
  panel.remove();
@@ -1734,12 +1863,14 @@ body.dsh-skin-galactic-opera .dsh-skin-telemetry-value-full {
1734
1863
  ctx.effect(() => {
1735
1864
  const style = ensureStyle();
1736
1865
  if (typeof document !== "undefined") document.body.classList.add("dsh-skin-galactic-opera");
1866
+ const disposeVisibilityState = installVisibilityState();
1737
1867
  const decor = ensureDecor();
1738
1868
  const disposeInteractions = installInteractions(decor);
1739
1869
  const disposeTelemetryPanel = installTelemetryPanel();
1740
1870
  return () => {
1741
1871
  disposeTelemetryPanel();
1742
1872
  disposeInteractions();
1873
+ disposeVisibilityState();
1743
1874
  disposeReducedMotionCache();
1744
1875
  if (typeof document !== "undefined") document.body.classList.remove("dsh-skin-galactic-opera");
1745
1876
  decor?.remove();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-skin-galactic-opera",
3
- "version": "0.1.8",
3
+ "version": "0.2.0",
4
4
  "description": "Unofficial cinematic space-opera dark skin bundle for the DSH Web GUI.",
5
5
  "repository": {
6
6
  "type": "git",