dsh-skin-galactic-opera 0.1.4 → 0.1.6

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.
Files changed (3) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/lib/client.js +217 -131
  3. package/package.json +12 -3
package/CHANGELOG.md CHANGED
@@ -2,6 +2,39 @@
2
2
 
3
3
  All notable changes to this package will be documented in this file.
4
4
 
5
+ ## 0.1.6 - 2026-08-28
6
+
7
+ ### Changed
8
+
9
+ - Further reduces telemetry overhead by using cached candidate geometry, adding a bounded full-rescan TTL, increasing update debounce, and avoiding observer array allocations.
10
+ - Refines telemetry text extraction so hot-path probing uses lightweight text reads while rendered output preserves native visual spacing.
11
+ - Uses pointer movement distance thresholds for parallax updates instead of fixed time throttling.
12
+ - Reuses fixed telemetry row nodes and disables the telemetry backdrop blur while typing.
13
+
14
+ ### Fixed
15
+
16
+ - Prevents occasional telemetry panel disappearance during full rescans by temporarily making hidden native telemetry sources discoverable to `elementsFromPoint`.
17
+
18
+ ## 0.1.5 - 2026-08-28
19
+
20
+ ### Added
21
+
22
+ - Scanner target now double-flashes at the clicked page position projected into the bottom-left scanner disk.
23
+ - Package metadata for repository, homepage, and issue reporting.
24
+
25
+ ### Changed
26
+
27
+ - Optimizes telemetry rescan scheduling by ignoring skin-owned DOM mutations and reusing valid cached telemetry sources.
28
+ - Replaces the scanner target's continuous sweep polling with click-triggered pings.
29
+ - Caches the reduced-motion media query and tightens plugin-specific global state class names.
30
+ - Expands JavaScript syntax checks to include both client and host entries and runs them before publishing.
31
+
32
+ ### Fixed
33
+
34
+ - Improves contenteditable detection, including empty and plaintext-only contenteditable controls.
35
+ - Prevents hidden telemetry source elements from intercepting pointer events.
36
+ - Narrows scrollbar styling to avoid a global universal selector.
37
+
5
38
  ## 0.1.4 - 2026-08-28
6
39
 
7
40
  ### Fixed
package/lib/client.js CHANGED
@@ -79,6 +79,7 @@ body.dsh-skin-galactic-opera {
79
79
  radial-gradient(circle at 62% 83%, rgba(255, 71, 120, 0.20), transparent 34rem),
80
80
  radial-gradient(circle at 32% 74%, rgba(102, 255, 199, 0.10), transparent 28rem),
81
81
  linear-gradient(180deg, #02030a 0%, #03040b 44%, #080b1c 100%) !important;
82
+ scrollbar-color: rgba(125, 227, 255, 0.42) rgba(5, 7, 17, 0.40);
82
83
  }
83
84
 
84
85
  body.dsh-skin-galactic-opera::before,
@@ -139,35 +140,36 @@ body.dsh-skin-galactic-opera #root::before {
139
140
  will-change: transform;
140
141
  }
141
142
 
142
- body.dsh-skin-galactic-opera.dsh-skin-input-active::before,
143
- body.dsh-skin-galactic-opera.dsh-skin-input-active::after,
144
- body.dsh-skin-galactic-opera.dsh-skin-input-active #root::before {
143
+ body.dsh-skin-galactic-opera.dsh-skin-galactic-opera-input-active::before,
144
+ body.dsh-skin-galactic-opera.dsh-skin-galactic-opera-input-active::after,
145
+ body.dsh-skin-galactic-opera.dsh-skin-galactic-opera-input-active #root::before {
145
146
  animation-play-state: paused !important;
146
147
  }
147
148
 
148
- body.dsh-skin-galactic-opera.dsh-skin-input-active::before {
149
+ body.dsh-skin-galactic-opera.dsh-skin-galactic-opera-input-active::before {
149
150
  opacity: 0.28;
150
151
  }
151
152
 
152
- body.dsh-skin-galactic-opera.dsh-skin-input-active::after {
153
+ body.dsh-skin-galactic-opera.dsh-skin-galactic-opera-input-active::after {
153
154
  opacity: 0.12;
154
155
  }
155
156
 
156
- body.dsh-skin-galactic-opera.dsh-skin-input-active #root::before {
157
+ body.dsh-skin-galactic-opera.dsh-skin-galactic-opera-input-active #root::before {
157
158
  opacity: 0.10;
158
159
  transform: none;
159
160
  will-change: auto;
160
161
  }
161
162
 
162
- body.dsh-skin-galactic-opera.dsh-skin-input-active .dsh-skin-galactic-opera-decor {
163
+ body.dsh-skin-galactic-opera.dsh-skin-galactic-opera-input-active .dsh-skin-galactic-opera-decor {
163
164
  opacity: 0.28;
164
165
  }
165
166
 
166
- body.dsh-skin-galactic-opera.dsh-skin-input-active :is(.dsh-skin-nebula-core, .dsh-skin-energy, .dsh-skin-comet, .dsh-skin-starfighter, .dsh-skin-mission-crawl, .dsh-skin-scanner, .dsh-skin-scanner::after, .dsh-skin-route-map) {
167
+ body.dsh-skin-galactic-opera.dsh-skin-galactic-opera-input-active :is(.dsh-skin-nebula-core, .dsh-skin-energy, .dsh-skin-comet, .dsh-skin-starfighter, .dsh-skin-mission-crawl, .dsh-skin-scanner, .dsh-skin-route-map),
168
+ body.dsh-skin-galactic-opera.dsh-skin-galactic-opera-input-active .dsh-skin-scanner::after {
167
169
  animation-play-state: paused !important;
168
170
  }
169
171
 
170
- body.dsh-skin-galactic-opera.dsh-skin-input-active :is(.dsh-skin-nebula-core, .dsh-skin-energy, .dsh-skin-comet, .dsh-skin-starfighter, .dsh-skin-mission-crawl) {
172
+ body.dsh-skin-galactic-opera.dsh-skin-galactic-opera-input-active :is(.dsh-skin-nebula-core, .dsh-skin-energy, .dsh-skin-comet, .dsh-skin-starfighter, .dsh-skin-mission-crawl) {
171
173
  opacity: 0.08 !important;
172
174
  }
173
175
 
@@ -217,8 +219,8 @@ body.dsh-skin-galactic-opera .dsh-skin-galactic-opera-decor::before {
217
219
  filter: blur(0.2px);
218
220
  }
219
221
 
220
- body.dsh-skin-galactic-opera.dsh-skin-hyperspace .dsh-skin-galactic-opera-decor::before {
221
- animation: dsh-skin-hyperspace-burst 880ms cubic-bezier(.16,.8,.25,1) both;
222
+ body.dsh-skin-galactic-opera.dsh-skin-galactic-opera-hyperspace .dsh-skin-galactic-opera-decor::before {
223
+ animation: dsh-skin-galactic-opera-hyperspace-burst 880ms cubic-bezier(.16,.8,.25,1) both;
222
224
  }
223
225
 
224
226
  body.dsh-skin-galactic-opera .dsh-skin-galactic-opera-decor span {
@@ -446,7 +448,7 @@ body.dsh-skin-galactic-opera .dsh-skin-scanner-target {
446
448
  transition: opacity 120ms ease-out, transform 120ms ease-out, box-shadow 120ms ease-out;
447
449
  }
448
450
 
449
- body.dsh-skin-galactic-opera .dsh-skin-scanner-target[data-lit="true"] {
451
+ body.dsh-skin-galactic-opera .dsh-skin-scanner-target[data-ping="true"] {
450
452
  opacity: 1;
451
453
  transform: translate(-50%, -50%) scale(1.18);
452
454
  box-shadow:
@@ -454,7 +456,7 @@ body.dsh-skin-galactic-opera .dsh-skin-scanner-target[data-lit="true"] {
454
456
  0 0 14px rgba(255,207,92,0.96),
455
457
  0 0 30px rgba(125,227,255,0.68),
456
458
  0 0 54px rgba(255,71,120,0.28);
457
- animation: dsh-skin-scanner-target-ping 260ms ease-out both;
459
+ animation: dsh-skin-scanner-target-double-ping 640ms ease-out both;
458
460
  }
459
461
 
460
462
  body.dsh-skin-galactic-opera .dsh-skin-route-map {
@@ -646,9 +648,9 @@ body.dsh-skin-galactic-opera :is(pre, code, textarea, input, select, [class*="me
646
648
  inset 0 0 20px rgba(255, 207, 92, 0.035) !important;
647
649
  }
648
650
 
649
- body.dsh-skin-galactic-opera.dsh-skin-input-active :is(textarea, input, [contenteditable="true"]):hover,
650
- body.dsh-skin-galactic-opera.dsh-skin-input-active :is(textarea, input, [contenteditable="true"]):focus,
651
- body.dsh-skin-galactic-opera.dsh-skin-input-active :is(textarea, input, [contenteditable="true"]):focus-within {
651
+ body.dsh-skin-galactic-opera.dsh-skin-galactic-opera-input-active :is(textarea, input, [contenteditable]:not([contenteditable="false"])):hover,
652
+ body.dsh-skin-galactic-opera.dsh-skin-galactic-opera-input-active :is(textarea, input, [contenteditable]:not([contenteditable="false"])):focus,
653
+ body.dsh-skin-galactic-opera.dsh-skin-galactic-opera-input-active :is(textarea, input, [contenteditable]:not([contenteditable="false"])):focus-within {
652
654
  outline: none !important;
653
655
  box-shadow: none !important;
654
656
  }
@@ -658,12 +660,9 @@ body.dsh-skin-galactic-opera ::selection {
658
660
  background: rgba(255, 207, 92, 0.86);
659
661
  }
660
662
 
661
- body.dsh-skin-galactic-opera * {
662
- scrollbar-color: rgba(125, 227, 255, 0.42) rgba(5, 7, 17, 0.40);
663
- }
664
-
665
663
  body.dsh-skin-galactic-opera .dsh-skin-telemetry-source-hidden {
666
664
  opacity: 0 !important;
665
+ pointer-events: none !important;
667
666
  }
668
667
 
669
668
  body.dsh-skin-galactic-opera .dsh-skin-telemetry-panel[hidden] {
@@ -701,6 +700,11 @@ body.dsh-skin-galactic-opera .dsh-skin-telemetry-panel {
701
700
  pointer-events: none;
702
701
  }
703
702
 
703
+ body.dsh-skin-galactic-opera.dsh-skin-galactic-opera-input-active .dsh-skin-telemetry-panel {
704
+ backdrop-filter: none;
705
+ -webkit-backdrop-filter: none;
706
+ }
707
+
704
708
  body.dsh-skin-galactic-opera .dsh-skin-telemetry-panel[data-collapsed="true"] {
705
709
  width: 24px;
706
710
  min-height: 24px;
@@ -907,9 +911,11 @@ body.dsh-skin-galactic-opera .dsh-skin-telemetry-value-full {
907
911
  50% { opacity: 1; transform: rotate(7deg) scaleX(1); }
908
912
  }
909
913
 
910
- @keyframes dsh-skin-scanner-target-ping {
911
- 0%, 100% { transform: translate(-50%, -50%) scale(0.86); opacity: 0.68; }
912
- 50% { transform: translate(-50%, -50%) scale(1.22); opacity: 1; }
914
+ @keyframes dsh-skin-scanner-target-double-ping {
915
+ 0%, 100% { transform: translate(-50%, -50%) scale(0.72); opacity: 0.04; }
916
+ 12%, 42% { transform: translate(-50%, -50%) scale(1.24); opacity: 1; }
917
+ 27%, 57% { transform: translate(-50%, -50%) scale(0.86); opacity: 0.34; }
918
+ 72% { transform: translate(-50%, -50%) scale(1.12); opacity: 0.82; }
913
919
  }
914
920
 
915
921
  @keyframes dsh-skin-click-pulse {
@@ -918,7 +924,7 @@ body.dsh-skin-galactic-opera .dsh-skin-telemetry-value-full {
918
924
  100% { opacity: 0; width: 132px; height: 132px; filter: brightness(0.8); }
919
925
  }
920
926
 
921
- @keyframes dsh-skin-hyperspace-burst {
927
+ @keyframes dsh-skin-galactic-opera-hyperspace-burst {
922
928
  0% { opacity: 0; transform: scale(0.32) rotate(0deg); }
923
929
  15% { opacity: 0.9; }
924
930
  100% { opacity: 0; transform: scale(1.45) rotate(10deg); }
@@ -1043,20 +1049,43 @@ body.dsh-skin-galactic-opera .dsh-skin-telemetry-value-full {
1043
1049
  return decor;
1044
1050
  }
1045
1051
 
1052
+ const EDITABLE_CONTENT_SELECTOR = `[contenteditable]:not([contenteditable="false"])`;
1053
+ let reducedMotionQuery;
1054
+ let reducedMotionMatches = false;
1055
+ let reducedMotionListener;
1056
+
1046
1057
  function isReducedMotion() {
1047
- return typeof matchMedia !== "undefined" && matchMedia("(prefers-reduced-motion: reduce)").matches;
1058
+ if (typeof matchMedia === "undefined") return false;
1059
+ if (reducedMotionQuery === undefined) {
1060
+ reducedMotionQuery = matchMedia("(prefers-reduced-motion: reduce)");
1061
+ reducedMotionMatches = reducedMotionQuery.matches;
1062
+ reducedMotionListener = (event) => { reducedMotionMatches = event.matches; };
1063
+ if (typeof reducedMotionQuery.addEventListener === "function") reducedMotionQuery.addEventListener("change", reducedMotionListener);
1064
+ else if (typeof reducedMotionQuery.addListener === "function") reducedMotionQuery.addListener(reducedMotionListener);
1065
+ }
1066
+ return reducedMotionMatches;
1067
+ }
1068
+
1069
+ function disposeReducedMotionCache() {
1070
+ if (reducedMotionQuery !== undefined && reducedMotionListener !== undefined) {
1071
+ if (typeof reducedMotionQuery.removeEventListener === "function") reducedMotionQuery.removeEventListener("change", reducedMotionListener);
1072
+ else if (typeof reducedMotionQuery.removeListener === "function") reducedMotionQuery.removeListener(reducedMotionListener);
1073
+ }
1074
+ reducedMotionQuery = undefined;
1075
+ reducedMotionMatches = false;
1076
+ reducedMotionListener = undefined;
1048
1077
  }
1049
1078
 
1050
1079
  function isEditableTarget(target) {
1051
1080
  if (!(target instanceof Element)) return false;
1052
1081
  const tag = target.tagName.toLowerCase();
1053
- return tag === "input" || tag === "textarea" || tag === "select" || target.closest("[contenteditable=true]") !== null;
1082
+ return tag === "input" || tag === "textarea" || tag === "select" || target.closest(EDITABLE_CONTENT_SELECTOR) !== null;
1054
1083
  }
1055
1084
 
1056
1085
  function isInstructionInputTarget(target) {
1057
1086
  if (!(target instanceof Element)) return false;
1058
1087
  const tag = target.tagName.toLowerCase();
1059
- return tag === "textarea" || target.closest("[contenteditable=true]") !== null;
1088
+ return tag === "textarea" || target.closest(EDITABLE_CONTENT_SELECTOR) !== null;
1060
1089
  }
1061
1090
 
1062
1091
  function isLikelySendButton(target) {
@@ -1064,7 +1093,7 @@ body.dsh-skin-galactic-opera .dsh-skin-telemetry-value-full {
1064
1093
  const button = target.closest("button");
1065
1094
  if (!(button instanceof HTMLButtonElement)) return false;
1066
1095
  const form = button.closest("form");
1067
- if (form !== null && form.querySelector("textarea, [contenteditable=true]") !== null && button.type === "submit") return true;
1096
+ if (form !== null && form.querySelector(`textarea, ${EDITABLE_CONTENT_SELECTOR}`) !== null && button.type === "submit") return true;
1068
1097
  const label = `${button.getAttribute("aria-label") ?? ""} ${button.getAttribute("title") ?? ""} ${button.textContent ?? ""}`.toLowerCase();
1069
1098
  return /send|submit|发送|提交|送出|传送/.test(label);
1070
1099
  }
@@ -1072,53 +1101,76 @@ body.dsh-skin-galactic-opera .dsh-skin-telemetry-value-full {
1072
1101
  function installInteractions(decor) {
1073
1102
  if (typeof document === "undefined" || typeof window === "undefined" || decor === undefined) return () => {};
1074
1103
  let frame = 0;
1075
- let sweepFrame = 0;
1076
- let sweepTimer = 0;
1077
1104
  let latestX = window.innerWidth / 2;
1078
1105
  let latestY = window.innerHeight / 2;
1079
- let targetAngle = 0;
1106
+ let lastParallaxX = "";
1107
+ let lastParallaxY = "";
1080
1108
  let pulseIndex = 0;
1081
1109
  let hyperspaceTimer;
1082
1110
  let inputFocusTimer;
1111
+ let scannerPingTimer;
1083
1112
  let lastHyperspaceAt = 0;
1113
+ const scanner = decor.querySelector(".dsh-skin-scanner");
1084
1114
  const scannerTarget = decor.querySelector(".dsh-skin-scanner-target");
1085
- const scannerStartedAt = performance.now();
1086
1115
  const pulseColors = ["rgba(125,227,255,0.94)", "rgba(255,207,92,0.92)", "rgba(255,71,120,0.88)"];
1087
1116
  const updatePointer = () => {
1088
1117
  frame = 0;
1089
1118
  const nx = (latestX / Math.max(1, window.innerWidth) - 0.5) * 2;
1090
1119
  const ny = (latestY / Math.max(1, window.innerHeight) - 0.5) * 2;
1091
- document.body.style.setProperty("--dsh-skin-parallax-x", `${Math.round(nx * -10)}px`);
1092
- document.body.style.setProperty("--dsh-skin-parallax-y", `${Math.round(ny * -8)}px`);
1093
- const scanner = decor.querySelector(".dsh-skin-scanner");
1094
- if (scanner instanceof HTMLElement) {
1095
- const rect = scanner.getBoundingClientRect();
1096
- const scannerCenterX = rect.left + rect.width / 2;
1097
- const scannerCenterY = rect.top + rect.height / 2;
1098
- // Navigation mode: direction is measured from the viewport center,
1099
- // then projected onto the scanner disk so it behaves like a HUD compass.
1100
- const viewportCenterX = window.innerWidth / 2;
1101
- const viewportCenterY = window.innerHeight / 2;
1102
- const angle = Math.atan2(latestY - viewportCenterY, latestX - viewportCenterX);
1103
- targetAngle = (angle * 180 / Math.PI + 360) % 360;
1104
- const radius = Math.max(10, Math.min(rect.width, rect.height) * 0.38);
1105
- document.body.style.setProperty("--dsh-skin-scanner-dot-x", `${Math.round(scannerCenterX + Math.cos(angle) * radius)}px`);
1106
- document.body.style.setProperty("--dsh-skin-scanner-dot-y", `${Math.round(scannerCenterY + Math.sin(angle) * radius)}px`);
1120
+ const nextParallaxX = `${Math.round(nx * -10)}px`;
1121
+ const nextParallaxY = `${Math.round(ny * -8)}px`;
1122
+ if (nextParallaxX !== lastParallaxX) {
1123
+ lastParallaxX = nextParallaxX;
1124
+ document.body.style.setProperty("--dsh-skin-parallax-x", nextParallaxX);
1125
+ }
1126
+ if (nextParallaxY !== lastParallaxY) {
1127
+ lastParallaxY = nextParallaxY;
1128
+ document.body.style.setProperty("--dsh-skin-parallax-y", nextParallaxY);
1107
1129
  }
1108
1130
  };
1131
+ const pingScannerAt = (clientX, clientY) => {
1132
+ if (!(scanner instanceof HTMLElement) || !(scannerTarget instanceof HTMLElement)) return;
1133
+ const rect = scanner.getBoundingClientRect();
1134
+ const scannerCenterX = rect.left + rect.width / 2;
1135
+ const scannerCenterY = rect.top + rect.height / 2;
1136
+ const viewportCenterX = window.innerWidth / 2;
1137
+ const viewportCenterY = window.innerHeight / 2;
1138
+ const dx = clientX - viewportCenterX;
1139
+ const dy = clientY - viewportCenterY;
1140
+ const distance = Math.hypot(dx, dy);
1141
+ const farthestX = dx >= 0 ? window.innerWidth - viewportCenterX : viewportCenterX;
1142
+ const farthestY = dy >= 0 ? window.innerHeight - viewportCenterY : viewportCenterY;
1143
+ const maxDistance = Math.max(1, Math.hypot(farthestX, farthestY));
1144
+ const scannerRadius = Math.max(10, Math.min(rect.width, rect.height) * 0.38);
1145
+ const projectedRadius = Math.min(1, distance / maxDistance) * scannerRadius;
1146
+ const angle = Math.atan2(dy, dx);
1147
+ document.body.style.setProperty("--dsh-skin-scanner-dot-x", `${Math.round(scannerCenterX + Math.cos(angle) * projectedRadius)}px`);
1148
+ document.body.style.setProperty("--dsh-skin-scanner-dot-y", `${Math.round(scannerCenterY + Math.sin(angle) * projectedRadius)}px`);
1149
+ scannerTarget.removeAttribute("data-ping");
1150
+ // Force the double-flash animation to restart on repeated clicks at the same point.
1151
+ void scannerTarget.offsetWidth;
1152
+ scannerTarget.dataset.ping = "true";
1153
+ window.clearTimeout(scannerPingTimer);
1154
+ scannerPingTimer = window.setTimeout(() => {
1155
+ scannerTarget.removeAttribute("data-ping");
1156
+ }, 700);
1157
+ };
1109
1158
  const isTypingActive = () => document.activeElement instanceof Element && isEditableTarget(document.activeElement);
1110
1159
  const setInputActive = (active) => {
1111
1160
  window.clearTimeout(inputFocusTimer);
1112
1161
  if (active) {
1113
- document.body.classList.add("dsh-skin-input-active");
1162
+ document.body.classList.add("dsh-skin-galactic-opera-input-active");
1114
1163
  } else {
1115
1164
  inputFocusTimer = window.setTimeout(() => {
1116
- document.body.classList.remove("dsh-skin-input-active");
1165
+ document.body.classList.remove("dsh-skin-galactic-opera-input-active");
1117
1166
  }, 160);
1118
1167
  }
1119
1168
  };
1120
1169
  const onPointerMove = (event) => {
1121
1170
  if (isReducedMotion() || isTypingActive()) return;
1171
+ const dx = event.clientX - latestX;
1172
+ const dy = event.clientY - latestY;
1173
+ if (frame === 0 && dx * dx + dy * dy < 9) return;
1122
1174
  latestX = event.clientX;
1123
1175
  latestY = event.clientY;
1124
1176
  if (frame === 0) frame = requestAnimationFrame(updatePointer);
@@ -1128,10 +1180,10 @@ body.dsh-skin-galactic-opera .dsh-skin-telemetry-value-full {
1128
1180
  const now = performance.now();
1129
1181
  if (now - lastHyperspaceAt < 1200) return;
1130
1182
  lastHyperspaceAt = now;
1131
- document.body.classList.add("dsh-skin-hyperspace");
1183
+ document.body.classList.add("dsh-skin-galactic-opera-hyperspace");
1132
1184
  window.clearTimeout(hyperspaceTimer);
1133
1185
  hyperspaceTimer = window.setTimeout(() => {
1134
- document.body.classList.remove("dsh-skin-hyperspace");
1186
+ document.body.classList.remove("dsh-skin-galactic-opera-hyperspace");
1135
1187
  }, 900);
1136
1188
  };
1137
1189
  const onPointerDown = (event) => {
@@ -1146,6 +1198,7 @@ body.dsh-skin-galactic-opera .dsh-skin-telemetry-value-full {
1146
1198
  const remove = () => { pulse.remove(); };
1147
1199
  pulse.addEventListener("animationend", remove, { once: true });
1148
1200
  window.setTimeout(remove, 900);
1201
+ pingScannerAt(event.clientX, event.clientY);
1149
1202
  if (isLikelySendButton(event.target)) window.setTimeout(triggerHyperspace, 40);
1150
1203
  };
1151
1204
  const onKeyDown = (event) => {
@@ -1160,26 +1213,7 @@ body.dsh-skin-galactic-opera .dsh-skin-telemetry-value-full {
1160
1213
  const onFocusOut = (event) => {
1161
1214
  if (isEditableTarget(event.target)) setInputActive(false);
1162
1215
  };
1163
- const sweepLoop = () => {
1164
- sweepFrame = 0;
1165
- sweepTimer = 0;
1166
- const typing = isTypingActive();
1167
- if (scannerTarget instanceof HTMLElement) {
1168
- if (isReducedMotion() || typing) {
1169
- scannerTarget.removeAttribute("data-lit");
1170
- } else {
1171
- // The scanner disk rotates once every 7s. Offset roughly matches the bright conic wedge.
1172
- const sweepAngle = (((performance.now() - scannerStartedAt) % 7000) / 7000 * 360 + 18) % 360;
1173
- const delta = Math.abs(((sweepAngle - targetAngle + 540) % 360) - 180);
1174
- if (delta < 10) scannerTarget.dataset.lit = "true";
1175
- else scannerTarget.removeAttribute("data-lit");
1176
- }
1177
- }
1178
- if (typing) sweepTimer = window.setTimeout(sweepLoop, 300);
1179
- else sweepFrame = requestAnimationFrame(sweepLoop);
1180
- };
1181
1216
  updatePointer();
1182
- sweepFrame = requestAnimationFrame(sweepLoop);
1183
1217
  document.addEventListener("pointermove", onPointerMove, { passive: true });
1184
1218
  document.addEventListener("pointerdown", onPointerDown, { passive: true });
1185
1219
  document.addEventListener("keydown", onKeyDown);
@@ -1193,13 +1227,12 @@ body.dsh-skin-galactic-opera .dsh-skin-telemetry-value-full {
1193
1227
  document.removeEventListener("focusin", onFocusIn);
1194
1228
  document.removeEventListener("focusout", onFocusOut);
1195
1229
  window.clearTimeout(inputFocusTimer);
1196
- document.body.classList.remove("dsh-skin-input-active");
1230
+ document.body.classList.remove("dsh-skin-galactic-opera-input-active");
1197
1231
  if (frame !== 0) cancelAnimationFrame(frame);
1198
- if (sweepFrame !== 0) cancelAnimationFrame(sweepFrame);
1199
- if (sweepTimer !== 0) window.clearTimeout(sweepTimer);
1200
- if (scannerTarget instanceof HTMLElement) scannerTarget.removeAttribute("data-lit");
1232
+ if (scannerTarget instanceof HTMLElement) scannerTarget.removeAttribute("data-ping");
1233
+ window.clearTimeout(scannerPingTimer);
1201
1234
  window.clearTimeout(hyperspaceTimer);
1202
- document.body.classList.remove("dsh-skin-hyperspace");
1235
+ document.body.classList.remove("dsh-skin-galactic-opera-hyperspace");
1203
1236
  document.body.style.removeProperty("--dsh-skin-parallax-x");
1204
1237
  document.body.style.removeProperty("--dsh-skin-parallax-y");
1205
1238
  document.body.style.removeProperty("--dsh-skin-scanner-dot-x");
@@ -1210,11 +1243,15 @@ body.dsh-skin-galactic-opera .dsh-skin-telemetry-value-full {
1210
1243
  function installTelemetryPanel() {
1211
1244
  if (typeof document === "undefined" || typeof window === "undefined") return () => {};
1212
1245
  const telemetryPattern = /(?:\d[\d.,KkMm%/]*\s*轮\s*[·•]?\s*\d[\d.,KkMm%/]*\s*步)|(?:\d[\d.,KkMm%/]*\s*(?:rounds?|turns?)\s*[·•]?\s*\d[\d.,KkMm%/]*\s*steps?)|LLM\b|工具调用|(?:首\s*)?(?:token|tokens|tok|toks)\s*平均|(?:token|tokens|tok|toks)\/s|缓存命中|缓存|cache\b|cached\b|输入|输出|prompt\b|completion\b/i;
1213
- const skipSelector = `#${DECOR_ID}, style, script, textarea, input, select, button, [role=button], [aria-haspopup], [contenteditable=true], [role=dialog], [role=menu], [role=listbox], [role=tooltip], [popover], .dsh-skin-pulse, .dsh-skin-telemetry-panel`;
1214
- const editableDescendantSelector = "textarea, input, select, [contenteditable=true]";
1246
+ const skipSelector = `#${DECOR_ID}, style, script, textarea, input, select, button, [role=button], [aria-haspopup], ${EDITABLE_CONTENT_SELECTOR}, [role=dialog], [role=menu], [role=listbox], [role=tooltip], [popover], .dsh-skin-pulse, .dsh-skin-telemetry-panel`;
1247
+ const editableDescendantSelector = `textarea, input, select, ${EDITABLE_CONTENT_SELECTOR}`;
1215
1248
  let currentSources = new Set();
1216
1249
  let lastText = "";
1217
1250
  let frame = 0;
1251
+ let forceTelemetryRescan = true;
1252
+ let telemetryRefreshesSinceFullScan = 0;
1253
+ const TELEMETRY_UPDATE_DELAY = 300;
1254
+ const TELEMETRY_FULL_RESCAN_UPDATES = 7;
1218
1255
  let collapsed = false;
1219
1256
  try {
1220
1257
  collapsed = window.localStorage?.getItem("dsh-skin-galactic-opera:telemetry-collapsed") === "true";
@@ -1227,24 +1264,52 @@ body.dsh-skin-galactic-opera .dsh-skin-telemetry-value-full {
1227
1264
  panel.hidden = true;
1228
1265
  const content = document.createElement("div");
1229
1266
  content.className = "dsh-skin-telemetry-content";
1267
+ const rowMetrics = Array.from({ length: 5 }, () => {
1268
+ const row = document.createElement("div");
1269
+ row.className = "dsh-skin-telemetry-row";
1270
+ row.hidden = true;
1271
+ const metric = document.createElement("span");
1272
+ metric.className = "dsh-skin-telemetry-value dsh-skin-telemetry-value-full";
1273
+ row.append(metric);
1274
+ content.append(row);
1275
+ return { row, metric };
1276
+ });
1230
1277
  panel.append(content);
1231
1278
  const toggle = document.createElement("button");
1232
1279
  toggle.type = "button";
1233
1280
  toggle.className = "dsh-skin-telemetry-toggle";
1234
1281
  toggle.hidden = true;
1235
1282
  document.body.append(panel, toggle);
1283
+ const skinMutationSelector = `#${DECOR_ID}, .dsh-skin-pulse, .dsh-skin-telemetry-panel, .dsh-skin-telemetry-toggle`;
1236
1284
  const isSkippable = (element) => element.closest(skipSelector) !== null;
1237
1285
  const hasEditableDescendant = (element) => element.querySelector(editableDescendantSelector) !== null;
1238
- const getTelemetryText = (element) => (element.innerText || element.textContent || "").replace(/\s+/g, " ").trim();
1239
- const scoreCandidate = (element) => {
1240
- if (!(element instanceof HTMLElement) || isSkippable(element) || hasEditableDescendant(element)) return 0;
1286
+ const normalizeTelemetryText = (text) => text.replace(/\s+/g, " ").trim();
1287
+ const getTelemetryProbeText = (element) => normalizeTelemetryText(element.textContent || "");
1288
+ const getTelemetryText = (element) => normalizeTelemetryText(element.innerText || element.textContent || "");
1289
+ const getMutationElement = (node) => node instanceof Element ? node : node.parentElement;
1290
+ const isSkinOwnedNode = (node) => getMutationElement(node)?.closest(skinMutationSelector) !== null;
1291
+ const isSkinOwnedMutation = (mutation) => {
1292
+ if (isSkinOwnedNode(mutation.target)) return true;
1293
+ let touched = false;
1294
+ for (const node of mutation.addedNodes) {
1295
+ touched = true;
1296
+ if (!isSkinOwnedNode(node)) return false;
1297
+ }
1298
+ for (const node of mutation.removedNodes) {
1299
+ touched = true;
1300
+ if (!isSkinOwnedNode(node)) return false;
1301
+ }
1302
+ return touched;
1303
+ };
1304
+ const getCandidateInfo = (element) => {
1305
+ if (!(element instanceof HTMLElement) || isSkippable(element) || hasEditableDescendant(element)) return undefined;
1241
1306
  const rect = element.getBoundingClientRect();
1242
1307
  const compactBottomBar = rect.height <= 46 && rect.width <= window.innerWidth && rect.bottom > window.innerHeight - 150;
1243
- if (rect.width < 12 || rect.height < 8 || (!compactBottomBar && rect.width > Math.min(620, window.innerWidth * 0.72)) || rect.height > 86) return 0;
1244
- if (rect.bottom < window.innerHeight - 170) return 0;
1245
- const text = getTelemetryText(element);
1246
- if (text.length < 2 || text.length > 360 || !telemetryPattern.test(text) || !/\d/.test(text)) return 0;
1247
- if (/设置\s+设置\s+通用设置|Cmd\/Ctrl\+Enter|使用另一行|排队发送|Agent\s+预设/i.test(text)) return 0;
1308
+ if (rect.width < 12 || rect.height < 8 || (!compactBottomBar && rect.width > Math.min(620, window.innerWidth * 0.72)) || rect.height > 86) return undefined;
1309
+ if (rect.bottom < window.innerHeight - 170) return undefined;
1310
+ const text = getTelemetryProbeText(element);
1311
+ if (text.length < 2 || text.length > 360 || !telemetryPattern.test(text) || !/\d/.test(text)) return undefined;
1312
+ if (/设置\s+设置\s+通用设置|Cmd\/Ctrl\+Enter|使用另一行|排队发送|Agent\s+预设/i.test(text)) return undefined;
1248
1313
  let score = compactBottomBar ? 4 : 1;
1249
1314
  if (rect.bottom > window.innerHeight - 72) score += 3;
1250
1315
  if (rect.right > window.innerWidth * 0.45) score += 1;
@@ -1252,40 +1317,41 @@ body.dsh-skin-galactic-opera .dsh-skin-telemetry-value-full {
1252
1317
  if (/调用|消耗|上下文|usage|context|缓存|命中|输入|输出/i.test(text)) score += 2;
1253
1318
  if (/轮次|轮|步骤|步数|步|steps?|rounds?|turns?/i.test(text)) score += 2;
1254
1319
  if (/[·•|;;]/.test(text)) score += 2;
1255
- if (/button|input|textarea|select/i.test(element.tagName)) score -= 4;
1256
- return score;
1320
+ return { element, rect, score, text };
1257
1321
  };
1258
1322
  const chooseTelemetryElements = () => {
1259
1323
  const xs = [0.10, 0.22, 0.34, 0.46, 0.58, 0.70, 0.82, 0.94];
1260
1324
  const ys = [18, 38, 62, 90, 122, 150];
1261
1325
  const candidates = new Map();
1262
- for (const xRatio of xs) for (const yOffset of ys) {
1263
- const x = window.innerWidth * xRatio;
1264
- const y = window.innerHeight - yOffset;
1265
- if (y < 0) continue;
1266
- for (const base of document.elementsFromPoint(x, y)) {
1267
- let element = base;
1268
- for (let depth = 0; element instanceof HTMLElement && depth < 8; depth += 1, element = element.parentElement) {
1269
- const score = scoreCandidate(element);
1270
- if (score > 0) candidates.set(element, Math.max(candidates.get(element) ?? 0, score));
1326
+ const temporarilyRestoredSources = [...currentSources].filter((source) => source.classList.contains("dsh-skin-telemetry-source-hidden"));
1327
+ // elementsFromPoint ignores pointer-events:none. Temporarily restore the previous source
1328
+ // during the same JS task so full rescans do not miss the hidden native bottom bar.
1329
+ for (const source of temporarilyRestoredSources) source.classList.remove("dsh-skin-telemetry-source-hidden");
1330
+ try {
1331
+ for (const xRatio of xs) for (const yOffset of ys) {
1332
+ const x = window.innerWidth * xRatio;
1333
+ const y = window.innerHeight - yOffset;
1334
+ if (y < 0) continue;
1335
+ for (const base of document.elementsFromPoint(x, y)) {
1336
+ let element = base;
1337
+ for (let depth = 0; element instanceof HTMLElement && depth < 8; depth += 1, element = element.parentElement) {
1338
+ const candidate = getCandidateInfo(element);
1339
+ if (candidate !== undefined) {
1340
+ const previous = candidates.get(element);
1341
+ if (previous === undefined || candidate.score > previous.score) candidates.set(element, candidate);
1342
+ }
1343
+ }
1271
1344
  }
1272
1345
  }
1346
+ } finally {
1347
+ for (const source of temporarilyRestoredSources) if (source.isConnected) source.classList.add("dsh-skin-telemetry-source-hidden");
1273
1348
  }
1274
- return [...candidates]
1275
- .sort(([a, aScore], [b, bScore]) => {
1276
- if (bScore !== aScore) return bScore - aScore;
1277
- const ar = a.getBoundingClientRect();
1278
- const br = b.getBoundingClientRect();
1279
- return ar.top - br.top || ar.left - br.left;
1280
- })
1281
- .filter(([element], index, entries) => !entries.slice(0, index).some(([selected]) => selected.contains(element) || element.contains(selected)))
1349
+ return [...candidates.values()]
1350
+ .sort((a, b) => b.score - a.score || a.rect.top - b.rect.top || a.rect.left - b.rect.left)
1351
+ .filter((candidate, index, entries) => !entries.slice(0, index).some(({ element: selected }) => selected.contains(candidate.element) || candidate.element.contains(selected)))
1282
1352
  .slice(0, 10)
1283
- .map(([element]) => element)
1284
- .sort((a, b) => {
1285
- const ar = a.getBoundingClientRect();
1286
- const br = b.getBoundingClientRect();
1287
- return ar.top - br.top || ar.left - br.left;
1288
- });
1353
+ .sort((a, b) => a.rect.top - b.rect.top || a.rect.left - b.rect.left)
1354
+ .map(({ element }) => element);
1289
1355
  };
1290
1356
  const splitTelemetryRows = (text) => {
1291
1357
  const normalized = text
@@ -1380,36 +1446,48 @@ body.dsh-skin-galactic-opera .dsh-skin-telemetry-value-full {
1380
1446
  event.stopPropagation();
1381
1447
  setCollapsed(!collapsed);
1382
1448
  });
1383
- const renderPanel = (text) => {
1384
- const rows = splitTelemetryRows(text);
1449
+ const renderPanel = (telemetryText) => {
1450
+ const rows = splitTelemetryRows(telemetryText);
1385
1451
  panel.hidden = rows.length === 0;
1386
1452
  toggle.hidden = rows.length === 0;
1387
- content.replaceChildren(...rows.map((text) => {
1388
- const row = document.createElement("div");
1389
- row.className = "dsh-skin-telemetry-row";
1390
- const metric = document.createElement("span");
1391
- metric.className = "dsh-skin-telemetry-value dsh-skin-telemetry-value-full";
1392
- metric.textContent = text;
1393
- row.append(metric);
1394
- return row;
1395
- }));
1453
+ rowMetrics.forEach(({ row, metric }, index) => {
1454
+ const rowText = rows[index] ?? "";
1455
+ row.hidden = rowText === "";
1456
+ if (metric.textContent !== rowText) metric.textContent = rowText;
1457
+ });
1396
1458
  };
1397
1459
  const isTelemetryPaused = () => document.activeElement instanceof Element && isInstructionInputTarget(document.activeElement);
1460
+ const getSourcesText = (sources) => [...sources].map(getTelemetryText).filter(Boolean).join(" | ");
1461
+ const areCachedSourcesUsable = () => {
1462
+ if (currentSources.size === 0) return false;
1463
+ const sources = [...currentSources];
1464
+ if (!sources.every((source) => source.isConnected && getCandidateInfo(source) !== undefined)) return false;
1465
+ return splitTelemetryRows(getSourcesText(currentSources)).length > 0;
1466
+ };
1398
1467
  const update = () => {
1399
1468
  frame = 0;
1400
1469
  if (isTelemetryPaused()) return;
1401
- const nextSources = new Set(chooseTelemetryElements());
1402
- const text = [...nextSources].map(getTelemetryText).filter(Boolean).join(" | ");
1403
- for (const source of currentSources) if (!nextSources.has(source)) source.classList.remove("dsh-skin-telemetry-source-hidden");
1404
- for (const source of nextSources) if (!currentSources.has(source)) source.classList.add("dsh-skin-telemetry-source-hidden");
1405
- currentSources = nextSources;
1470
+ let nextSources = currentSources;
1471
+ let text = getSourcesText(nextSources);
1472
+ const needsFullScan = forceTelemetryRescan || telemetryRefreshesSinceFullScan >= TELEMETRY_FULL_RESCAN_UPDATES || !areCachedSourcesUsable();
1473
+ if (needsFullScan) {
1474
+ nextSources = new Set(chooseTelemetryElements());
1475
+ text = getSourcesText(nextSources);
1476
+ for (const source of currentSources) if (!nextSources.has(source)) source.classList.remove("dsh-skin-telemetry-source-hidden");
1477
+ for (const source of nextSources) if (!currentSources.has(source)) source.classList.add("dsh-skin-telemetry-source-hidden");
1478
+ currentSources = nextSources;
1479
+ forceTelemetryRescan = false;
1480
+ telemetryRefreshesSinceFullScan = 0;
1481
+ } else {
1482
+ telemetryRefreshesSinceFullScan += 1;
1483
+ }
1406
1484
  if (text === lastText) return;
1407
1485
  lastText = text;
1408
1486
  renderPanel(text);
1409
1487
  };
1410
1488
  const schedule = () => {
1411
1489
  if (isTelemetryPaused()) return;
1412
- if (frame === 0) frame = window.setTimeout(update, 180);
1490
+ if (frame === 0) frame = window.setTimeout(update, TELEMETRY_UPDATE_DELAY);
1413
1491
  };
1414
1492
  const onTelemetryFocusIn = (event) => {
1415
1493
  if (!isInstructionInputTarget(event.target)) return;
@@ -1421,15 +1499,22 @@ body.dsh-skin-galactic-opera .dsh-skin-telemetry-value-full {
1421
1499
  const onTelemetryFocusOut = (event) => {
1422
1500
  if (isInstructionInputTarget(event.target)) window.setTimeout(schedule, 240);
1423
1501
  };
1424
- const observer = typeof MutationObserver === "undefined" ? undefined : new MutationObserver(schedule);
1502
+ const onTelemetryResize = () => {
1503
+ forceTelemetryRescan = true;
1504
+ schedule();
1505
+ };
1506
+ const observer = typeof MutationObserver === "undefined" ? undefined : new MutationObserver((mutations) => {
1507
+ if (mutations.every(isSkinOwnedMutation)) return;
1508
+ schedule();
1509
+ });
1425
1510
  schedule();
1426
1511
  observer?.observe(document.body, { childList: true, subtree: true, characterData: true });
1427
- window.addEventListener("resize", schedule, { passive: true });
1512
+ window.addEventListener("resize", onTelemetryResize, { passive: true });
1428
1513
  document.addEventListener("focusin", onTelemetryFocusIn);
1429
1514
  document.addEventListener("focusout", onTelemetryFocusOut);
1430
1515
  return () => {
1431
1516
  observer?.disconnect();
1432
- window.removeEventListener("resize", schedule);
1517
+ window.removeEventListener("resize", onTelemetryResize);
1433
1518
  document.removeEventListener("focusin", onTelemetryFocusIn);
1434
1519
  document.removeEventListener("focusout", onTelemetryFocusOut);
1435
1520
  if (frame !== 0) window.clearTimeout(frame);
@@ -1451,6 +1536,7 @@ body.dsh-skin-galactic-opera .dsh-skin-telemetry-value-full {
1451
1536
  return () => {
1452
1537
  disposeTelemetryPanel();
1453
1538
  disposeInteractions();
1539
+ disposeReducedMotionCache();
1454
1540
  if (typeof document !== "undefined") document.body.classList.remove("dsh-skin-galactic-opera");
1455
1541
  decor?.remove();
1456
1542
  style?.remove();
package/package.json CHANGED
@@ -1,7 +1,15 @@
1
1
  {
2
2
  "name": "dsh-skin-galactic-opera",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "Unofficial cinematic space-opera dark skin bundle for the DSH Web GUI.",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/xmnathan/dsh-skin-galactic-opera.git"
8
+ },
9
+ "homepage": "https://github.com/xmnathan/dsh-skin-galactic-opera#readme",
10
+ "bugs": {
11
+ "url": "https://github.com/xmnathan/dsh-skin-galactic-opera/issues"
12
+ },
5
13
  "type": "module",
6
14
  "main": "lib/index.js",
7
15
  "exports": {
@@ -19,8 +27,9 @@
19
27
  "CHANGELOG.md"
20
28
  ],
21
29
  "scripts": {
22
- "check": "node --check lib/client.js",
23
- "pack:dry": "npm pack --dry-run"
30
+ "check": "node --check lib/client.js && node --check lib/index.js",
31
+ "pack:dry": "npm pack --dry-run",
32
+ "prepublishOnly": "npm run check"
24
33
  },
25
34
  "keywords": [
26
35
  "dsh",