dsh-skin-galactic-opera 0.1.3 → 0.1.5

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 +26 -0
  2. package/lib/client.js +125 -88
  3. package/package.json +12 -3
package/CHANGELOG.md CHANGED
@@ -2,6 +2,32 @@
2
2
 
3
3
  All notable changes to this package will be documented in this file.
4
4
 
5
+ ## 0.1.5 - 2026-08-28
6
+
7
+ ### Added
8
+
9
+ - Scanner target now double-flashes at the clicked page position projected into the bottom-left scanner disk.
10
+ - Package metadata for repository, homepage, and issue reporting.
11
+
12
+ ### Changed
13
+
14
+ - Optimizes telemetry rescan scheduling by ignoring skin-owned DOM mutations and reusing valid cached telemetry sources.
15
+ - Replaces the scanner target's continuous sweep polling with click-triggered pings.
16
+ - Caches the reduced-motion media query and tightens plugin-specific global state class names.
17
+ - Expands JavaScript syntax checks to include both client and host entries and runs them before publishing.
18
+
19
+ ### Fixed
20
+
21
+ - Improves contenteditable detection, including empty and plaintext-only contenteditable controls.
22
+ - Prevents hidden telemetry source elements from intercepting pointer events.
23
+ - Narrows scrollbar styling to avoid a global universal selector.
24
+
25
+ ## 0.1.4 - 2026-08-28
26
+
27
+ ### Fixed
28
+
29
+ - Restores the top-right satellite's original 0.1.2 elliptical orbit behavior by excluding satellite and orbit animations from typing low-distraction pause rules.
30
+
5
31
  ## 0.1.3 - 2026-08-27
6
32
 
7
33
  ### Added
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, .dsh-skin-satellite, .dsh-skin-satellite-orbit, .dsh-skin-satellite-orbit-front) {
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] {
@@ -907,9 +906,11 @@ body.dsh-skin-galactic-opera .dsh-skin-telemetry-value-full {
907
906
  50% { opacity: 1; transform: rotate(7deg) scaleX(1); }
908
907
  }
909
908
 
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; }
909
+ @keyframes dsh-skin-scanner-target-double-ping {
910
+ 0%, 100% { transform: translate(-50%, -50%) scale(0.72); opacity: 0.04; }
911
+ 12%, 42% { transform: translate(-50%, -50%) scale(1.24); opacity: 1; }
912
+ 27%, 57% { transform: translate(-50%, -50%) scale(0.86); opacity: 0.34; }
913
+ 72% { transform: translate(-50%, -50%) scale(1.12); opacity: 0.82; }
913
914
  }
914
915
 
915
916
  @keyframes dsh-skin-click-pulse {
@@ -918,7 +919,7 @@ body.dsh-skin-galactic-opera .dsh-skin-telemetry-value-full {
918
919
  100% { opacity: 0; width: 132px; height: 132px; filter: brightness(0.8); }
919
920
  }
920
921
 
921
- @keyframes dsh-skin-hyperspace-burst {
922
+ @keyframes dsh-skin-galactic-opera-hyperspace-burst {
922
923
  0% { opacity: 0; transform: scale(0.32) rotate(0deg); }
923
924
  15% { opacity: 0.9; }
924
925
  100% { opacity: 0; transform: scale(1.45) rotate(10deg); }
@@ -1043,20 +1044,43 @@ body.dsh-skin-galactic-opera .dsh-skin-telemetry-value-full {
1043
1044
  return decor;
1044
1045
  }
1045
1046
 
1047
+ const EDITABLE_CONTENT_SELECTOR = `[contenteditable]:not([contenteditable="false"])`;
1048
+ let reducedMotionQuery;
1049
+ let reducedMotionMatches = false;
1050
+ let reducedMotionListener;
1051
+
1046
1052
  function isReducedMotion() {
1047
- return typeof matchMedia !== "undefined" && matchMedia("(prefers-reduced-motion: reduce)").matches;
1053
+ if (typeof matchMedia === "undefined") return false;
1054
+ if (reducedMotionQuery === undefined) {
1055
+ reducedMotionQuery = matchMedia("(prefers-reduced-motion: reduce)");
1056
+ reducedMotionMatches = reducedMotionQuery.matches;
1057
+ reducedMotionListener = (event) => { reducedMotionMatches = event.matches; };
1058
+ if (typeof reducedMotionQuery.addEventListener === "function") reducedMotionQuery.addEventListener("change", reducedMotionListener);
1059
+ else if (typeof reducedMotionQuery.addListener === "function") reducedMotionQuery.addListener(reducedMotionListener);
1060
+ }
1061
+ return reducedMotionMatches;
1062
+ }
1063
+
1064
+ function disposeReducedMotionCache() {
1065
+ if (reducedMotionQuery !== undefined && reducedMotionListener !== undefined) {
1066
+ if (typeof reducedMotionQuery.removeEventListener === "function") reducedMotionQuery.removeEventListener("change", reducedMotionListener);
1067
+ else if (typeof reducedMotionQuery.removeListener === "function") reducedMotionQuery.removeListener(reducedMotionListener);
1068
+ }
1069
+ reducedMotionQuery = undefined;
1070
+ reducedMotionMatches = false;
1071
+ reducedMotionListener = undefined;
1048
1072
  }
1049
1073
 
1050
1074
  function isEditableTarget(target) {
1051
1075
  if (!(target instanceof Element)) return false;
1052
1076
  const tag = target.tagName.toLowerCase();
1053
- return tag === "input" || tag === "textarea" || tag === "select" || target.closest("[contenteditable=true]") !== null;
1077
+ return tag === "input" || tag === "textarea" || tag === "select" || target.closest(EDITABLE_CONTENT_SELECTOR) !== null;
1054
1078
  }
1055
1079
 
1056
1080
  function isInstructionInputTarget(target) {
1057
1081
  if (!(target instanceof Element)) return false;
1058
1082
  const tag = target.tagName.toLowerCase();
1059
- return tag === "textarea" || target.closest("[contenteditable=true]") !== null;
1083
+ return tag === "textarea" || target.closest(EDITABLE_CONTENT_SELECTOR) !== null;
1060
1084
  }
1061
1085
 
1062
1086
  function isLikelySendButton(target) {
@@ -1064,7 +1088,7 @@ body.dsh-skin-galactic-opera .dsh-skin-telemetry-value-full {
1064
1088
  const button = target.closest("button");
1065
1089
  if (!(button instanceof HTMLButtonElement)) return false;
1066
1090
  const form = button.closest("form");
1067
- if (form !== null && form.querySelector("textarea, [contenteditable=true]") !== null && button.type === "submit") return true;
1091
+ if (form !== null && form.querySelector(`textarea, ${EDITABLE_CONTENT_SELECTOR}`) !== null && button.type === "submit") return true;
1068
1092
  const label = `${button.getAttribute("aria-label") ?? ""} ${button.getAttribute("title") ?? ""} ${button.textContent ?? ""}`.toLowerCase();
1069
1093
  return /send|submit|发送|提交|送出|传送/.test(label);
1070
1094
  }
@@ -1072,17 +1096,15 @@ body.dsh-skin-galactic-opera .dsh-skin-telemetry-value-full {
1072
1096
  function installInteractions(decor) {
1073
1097
  if (typeof document === "undefined" || typeof window === "undefined" || decor === undefined) return () => {};
1074
1098
  let frame = 0;
1075
- let sweepFrame = 0;
1076
- let sweepTimer = 0;
1077
1099
  let latestX = window.innerWidth / 2;
1078
1100
  let latestY = window.innerHeight / 2;
1079
- let targetAngle = 0;
1080
1101
  let pulseIndex = 0;
1081
1102
  let hyperspaceTimer;
1082
1103
  let inputFocusTimer;
1104
+ let scannerPingTimer;
1083
1105
  let lastHyperspaceAt = 0;
1106
+ const scanner = decor.querySelector(".dsh-skin-scanner");
1084
1107
  const scannerTarget = decor.querySelector(".dsh-skin-scanner-target");
1085
- const scannerStartedAt = performance.now();
1086
1108
  const pulseColors = ["rgba(125,227,255,0.94)", "rgba(255,207,92,0.92)", "rgba(255,71,120,0.88)"];
1087
1109
  const updatePointer = () => {
1088
1110
  frame = 0;
@@ -1090,30 +1112,42 @@ body.dsh-skin-galactic-opera .dsh-skin-telemetry-value-full {
1090
1112
  const ny = (latestY / Math.max(1, window.innerHeight) - 0.5) * 2;
1091
1113
  document.body.style.setProperty("--dsh-skin-parallax-x", `${Math.round(nx * -10)}px`);
1092
1114
  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`);
1107
- }
1115
+ };
1116
+ const pingScannerAt = (clientX, clientY) => {
1117
+ if (!(scanner instanceof HTMLElement) || !(scannerTarget instanceof HTMLElement)) return;
1118
+ const rect = scanner.getBoundingClientRect();
1119
+ const scannerCenterX = rect.left + rect.width / 2;
1120
+ const scannerCenterY = rect.top + rect.height / 2;
1121
+ const viewportCenterX = window.innerWidth / 2;
1122
+ const viewportCenterY = window.innerHeight / 2;
1123
+ const dx = clientX - viewportCenterX;
1124
+ const dy = clientY - viewportCenterY;
1125
+ const distance = Math.hypot(dx, dy);
1126
+ const farthestX = dx >= 0 ? window.innerWidth - viewportCenterX : viewportCenterX;
1127
+ const farthestY = dy >= 0 ? window.innerHeight - viewportCenterY : viewportCenterY;
1128
+ const maxDistance = Math.max(1, Math.hypot(farthestX, farthestY));
1129
+ const scannerRadius = Math.max(10, Math.min(rect.width, rect.height) * 0.38);
1130
+ const projectedRadius = Math.min(1, distance / maxDistance) * scannerRadius;
1131
+ const angle = Math.atan2(dy, dx);
1132
+ document.body.style.setProperty("--dsh-skin-scanner-dot-x", `${Math.round(scannerCenterX + Math.cos(angle) * projectedRadius)}px`);
1133
+ document.body.style.setProperty("--dsh-skin-scanner-dot-y", `${Math.round(scannerCenterY + Math.sin(angle) * projectedRadius)}px`);
1134
+ scannerTarget.removeAttribute("data-ping");
1135
+ // Force the double-flash animation to restart on repeated clicks at the same point.
1136
+ void scannerTarget.offsetWidth;
1137
+ scannerTarget.dataset.ping = "true";
1138
+ window.clearTimeout(scannerPingTimer);
1139
+ scannerPingTimer = window.setTimeout(() => {
1140
+ scannerTarget.removeAttribute("data-ping");
1141
+ }, 700);
1108
1142
  };
1109
1143
  const isTypingActive = () => document.activeElement instanceof Element && isEditableTarget(document.activeElement);
1110
1144
  const setInputActive = (active) => {
1111
1145
  window.clearTimeout(inputFocusTimer);
1112
1146
  if (active) {
1113
- document.body.classList.add("dsh-skin-input-active");
1147
+ document.body.classList.add("dsh-skin-galactic-opera-input-active");
1114
1148
  } else {
1115
1149
  inputFocusTimer = window.setTimeout(() => {
1116
- document.body.classList.remove("dsh-skin-input-active");
1150
+ document.body.classList.remove("dsh-skin-galactic-opera-input-active");
1117
1151
  }, 160);
1118
1152
  }
1119
1153
  };
@@ -1128,10 +1162,10 @@ body.dsh-skin-galactic-opera .dsh-skin-telemetry-value-full {
1128
1162
  const now = performance.now();
1129
1163
  if (now - lastHyperspaceAt < 1200) return;
1130
1164
  lastHyperspaceAt = now;
1131
- document.body.classList.add("dsh-skin-hyperspace");
1165
+ document.body.classList.add("dsh-skin-galactic-opera-hyperspace");
1132
1166
  window.clearTimeout(hyperspaceTimer);
1133
1167
  hyperspaceTimer = window.setTimeout(() => {
1134
- document.body.classList.remove("dsh-skin-hyperspace");
1168
+ document.body.classList.remove("dsh-skin-galactic-opera-hyperspace");
1135
1169
  }, 900);
1136
1170
  };
1137
1171
  const onPointerDown = (event) => {
@@ -1146,6 +1180,7 @@ body.dsh-skin-galactic-opera .dsh-skin-telemetry-value-full {
1146
1180
  const remove = () => { pulse.remove(); };
1147
1181
  pulse.addEventListener("animationend", remove, { once: true });
1148
1182
  window.setTimeout(remove, 900);
1183
+ pingScannerAt(event.clientX, event.clientY);
1149
1184
  if (isLikelySendButton(event.target)) window.setTimeout(triggerHyperspace, 40);
1150
1185
  };
1151
1186
  const onKeyDown = (event) => {
@@ -1160,26 +1195,7 @@ body.dsh-skin-galactic-opera .dsh-skin-telemetry-value-full {
1160
1195
  const onFocusOut = (event) => {
1161
1196
  if (isEditableTarget(event.target)) setInputActive(false);
1162
1197
  };
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
1198
  updatePointer();
1182
- sweepFrame = requestAnimationFrame(sweepLoop);
1183
1199
  document.addEventListener("pointermove", onPointerMove, { passive: true });
1184
1200
  document.addEventListener("pointerdown", onPointerDown, { passive: true });
1185
1201
  document.addEventListener("keydown", onKeyDown);
@@ -1193,13 +1209,12 @@ body.dsh-skin-galactic-opera .dsh-skin-telemetry-value-full {
1193
1209
  document.removeEventListener("focusin", onFocusIn);
1194
1210
  document.removeEventListener("focusout", onFocusOut);
1195
1211
  window.clearTimeout(inputFocusTimer);
1196
- document.body.classList.remove("dsh-skin-input-active");
1212
+ document.body.classList.remove("dsh-skin-galactic-opera-input-active");
1197
1213
  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");
1214
+ if (scannerTarget instanceof HTMLElement) scannerTarget.removeAttribute("data-ping");
1215
+ window.clearTimeout(scannerPingTimer);
1201
1216
  window.clearTimeout(hyperspaceTimer);
1202
- document.body.classList.remove("dsh-skin-hyperspace");
1217
+ document.body.classList.remove("dsh-skin-galactic-opera-hyperspace");
1203
1218
  document.body.style.removeProperty("--dsh-skin-parallax-x");
1204
1219
  document.body.style.removeProperty("--dsh-skin-parallax-y");
1205
1220
  document.body.style.removeProperty("--dsh-skin-scanner-dot-x");
@@ -1210,8 +1225,8 @@ body.dsh-skin-galactic-opera .dsh-skin-telemetry-value-full {
1210
1225
  function installTelemetryPanel() {
1211
1226
  if (typeof document === "undefined" || typeof window === "undefined") return () => {};
1212
1227
  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]";
1228
+ 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`;
1229
+ const editableDescendantSelector = `textarea, input, select, ${EDITABLE_CONTENT_SELECTOR}`;
1215
1230
  let currentSources = new Set();
1216
1231
  let lastText = "";
1217
1232
  let frame = 0;
@@ -1233,9 +1248,17 @@ body.dsh-skin-galactic-opera .dsh-skin-telemetry-value-full {
1233
1248
  toggle.className = "dsh-skin-telemetry-toggle";
1234
1249
  toggle.hidden = true;
1235
1250
  document.body.append(panel, toggle);
1251
+ const skinMutationSelector = `#${DECOR_ID}, .dsh-skin-pulse, .dsh-skin-telemetry-panel, .dsh-skin-telemetry-toggle`;
1236
1252
  const isSkippable = (element) => element.closest(skipSelector) !== null;
1237
1253
  const hasEditableDescendant = (element) => element.querySelector(editableDescendantSelector) !== null;
1238
1254
  const getTelemetryText = (element) => (element.innerText || element.textContent || "").replace(/\s+/g, " ").trim();
1255
+ const getMutationElement = (node) => node instanceof Element ? node : node.parentElement;
1256
+ const isSkinOwnedNode = (node) => getMutationElement(node)?.closest(skinMutationSelector) !== null;
1257
+ const isSkinOwnedMutation = (mutation) => {
1258
+ if (isSkinOwnedNode(mutation.target)) return true;
1259
+ const touched = [...mutation.addedNodes, ...mutation.removedNodes];
1260
+ return touched.length > 0 && touched.every(isSkinOwnedNode);
1261
+ };
1239
1262
  const scoreCandidate = (element) => {
1240
1263
  if (!(element instanceof HTMLElement) || isSkippable(element) || hasEditableDescendant(element)) return 0;
1241
1264
  const rect = element.getBoundingClientRect();
@@ -1252,7 +1275,6 @@ body.dsh-skin-galactic-opera .dsh-skin-telemetry-value-full {
1252
1275
  if (/调用|消耗|上下文|usage|context|缓存|命中|输入|输出/i.test(text)) score += 2;
1253
1276
  if (/轮次|轮|步骤|步数|步|steps?|rounds?|turns?/i.test(text)) score += 2;
1254
1277
  if (/[·•|;;]/.test(text)) score += 2;
1255
- if (/button|input|textarea|select/i.test(element.tagName)) score -= 4;
1256
1278
  return score;
1257
1279
  };
1258
1280
  const chooseTelemetryElements = () => {
@@ -1380,29 +1402,40 @@ body.dsh-skin-galactic-opera .dsh-skin-telemetry-value-full {
1380
1402
  event.stopPropagation();
1381
1403
  setCollapsed(!collapsed);
1382
1404
  });
1383
- const renderPanel = (text) => {
1384
- const rows = splitTelemetryRows(text);
1405
+ const renderPanel = (telemetryText) => {
1406
+ const rows = splitTelemetryRows(telemetryText);
1385
1407
  panel.hidden = rows.length === 0;
1386
1408
  toggle.hidden = rows.length === 0;
1387
- content.replaceChildren(...rows.map((text) => {
1409
+ content.replaceChildren(...rows.map((rowText) => {
1388
1410
  const row = document.createElement("div");
1389
1411
  row.className = "dsh-skin-telemetry-row";
1390
1412
  const metric = document.createElement("span");
1391
1413
  metric.className = "dsh-skin-telemetry-value dsh-skin-telemetry-value-full";
1392
- metric.textContent = text;
1414
+ metric.textContent = rowText;
1393
1415
  row.append(metric);
1394
1416
  return row;
1395
1417
  }));
1396
1418
  };
1397
1419
  const isTelemetryPaused = () => document.activeElement instanceof Element && isInstructionInputTarget(document.activeElement);
1420
+ const getSourcesText = (sources) => [...sources].map(getTelemetryText).filter(Boolean).join(" | ");
1421
+ const areCachedSourcesUsable = () => {
1422
+ if (currentSources.size === 0) return false;
1423
+ const sources = [...currentSources];
1424
+ if (!sources.every((source) => source.isConnected && scoreCandidate(source) > 0)) return false;
1425
+ return splitTelemetryRows(getSourcesText(currentSources)).length > 0;
1426
+ };
1398
1427
  const update = () => {
1399
1428
  frame = 0;
1400
1429
  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;
1430
+ let nextSources = currentSources;
1431
+ let text = getSourcesText(nextSources);
1432
+ if (!areCachedSourcesUsable()) {
1433
+ nextSources = new Set(chooseTelemetryElements());
1434
+ text = getSourcesText(nextSources);
1435
+ for (const source of currentSources) if (!nextSources.has(source)) source.classList.remove("dsh-skin-telemetry-source-hidden");
1436
+ for (const source of nextSources) if (!currentSources.has(source)) source.classList.add("dsh-skin-telemetry-source-hidden");
1437
+ currentSources = nextSources;
1438
+ }
1406
1439
  if (text === lastText) return;
1407
1440
  lastText = text;
1408
1441
  renderPanel(text);
@@ -1421,7 +1454,10 @@ body.dsh-skin-galactic-opera .dsh-skin-telemetry-value-full {
1421
1454
  const onTelemetryFocusOut = (event) => {
1422
1455
  if (isInstructionInputTarget(event.target)) window.setTimeout(schedule, 240);
1423
1456
  };
1424
- const observer = typeof MutationObserver === "undefined" ? undefined : new MutationObserver(schedule);
1457
+ const observer = typeof MutationObserver === "undefined" ? undefined : new MutationObserver((mutations) => {
1458
+ if (mutations.every(isSkinOwnedMutation)) return;
1459
+ schedule();
1460
+ });
1425
1461
  schedule();
1426
1462
  observer?.observe(document.body, { childList: true, subtree: true, characterData: true });
1427
1463
  window.addEventListener("resize", schedule, { passive: true });
@@ -1451,6 +1487,7 @@ body.dsh-skin-galactic-opera .dsh-skin-telemetry-value-full {
1451
1487
  return () => {
1452
1488
  disposeTelemetryPanel();
1453
1489
  disposeInteractions();
1490
+ disposeReducedMotionCache();
1454
1491
  if (typeof document !== "undefined") document.body.classList.remove("dsh-skin-galactic-opera");
1455
1492
  decor?.remove();
1456
1493
  style?.remove();
package/package.json CHANGED
@@ -1,7 +1,15 @@
1
1
  {
2
2
  "name": "dsh-skin-galactic-opera",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
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",