@ash-qw/dsh-theme-prts 0.1.94 → 0.1.96

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ash-qw/dsh-theme-prts",
3
- "version": "0.1.94",
3
+ "version": "0.1.96",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Unofficial Arknights P.R.T.S.-inspired UI theme for DeepSeek Harness Web",
@@ -67,10 +67,11 @@
67
67
  "prepublishOnly": "npm run check"
68
68
  },
69
69
  "engines": {
70
- "node": ">=18"
70
+ "node": ">=18",
71
+ "dsh": ">=0.1.2-rc.1 <0.1.3-0"
71
72
  },
72
73
  "devDependencies": {
73
- "@playwright/test": "1.55.0",
74
+ "@playwright/test": "1.55.1",
74
75
  "jsdom": "24.1.3"
75
76
  }
76
77
  }
@@ -9,6 +9,7 @@ const COMPOSER_RELEVANCE_SELECTOR = [
9
9
  'button[aria-haspopup="listbox"]',
10
10
  '[role="menu"]',
11
11
  '[role="listbox"]',
12
+ '[data-trigger-menu]',
12
13
  '[data-slot="conversation.input.overlay"]',
13
14
  ].join(', ')
14
15
  const PRESET_TRIGGER_SELECTOR = [
@@ -27,7 +28,7 @@ function controlledPopup(document, trigger) {
27
28
  }
28
29
 
29
30
  function localPopup(trigger) {
30
- const selector = '[role="menu"], [role="listbox"], [class*="mufS8W_card"]'
31
+ const selector = '[role="menu"], [role="listbox"], [data-trigger-menu], [class*="mufS8W_card"]'
31
32
  const sibling = trigger.nextElementSibling
32
33
  if (sibling?.matches(selector)) return sibling
33
34
  return trigger.parentElement?.querySelector(`:scope > ${selector}`) ?? null
@@ -36,7 +37,7 @@ function localPopup(trigger) {
36
37
  function overlayPopup(document) {
37
38
  const overlay = document.querySelector('[data-slot="conversation.input.overlay"]')
38
39
  if (!overlay) return null
39
- return overlay.querySelector('[role="listbox"], [role="menu"], [class*="mufS8W_card"]')
40
+ return overlay.querySelector('[data-trigger-menu], [role="listbox"], [role="menu"], [class*="mufS8W_card"]')
40
41
  }
41
42
 
42
43
  function linkedMenu(document, trigger) {
@@ -48,9 +49,9 @@ function linkedMenu(document, trigger) {
48
49
  function markPopup(popup, kind) {
49
50
  if (!popup) return
50
51
  mark(popup, MENU_ATTRIBUTE, kind)
51
- const card = popup.closest?.('[class*="mufS8W_card"]')
52
+ const card = popup.closest?.('[data-trigger-menu], [class*="mufS8W_card"]')
52
53
  mark(card, MENU_ATTRIBUTE, kind)
53
- for (const nested of popup.querySelectorAll?.('[role="menu"], [role="listbox"], [class*="mufS8W_card"]') ?? []) {
54
+ for (const nested of popup.querySelectorAll?.('[role="menu"], [role="listbox"], [data-trigger-menu], [class*="mufS8W_card"]') ?? []) {
54
55
  mark(nested, MENU_ATTRIBUTE, kind)
55
56
  }
56
57
  }
@@ -162,7 +163,7 @@ export function createComposerGlassAdapter({ document, window }) {
162
163
 
163
164
  const overlay = document?.querySelector?.('[data-slot="conversation.input.overlay"]')
164
165
  if (overlay) {
165
- for (const popup of overlay.querySelectorAll('[role="menu"], [role="listbox"], [class*="mufS8W_card"]')) {
166
+ for (const popup of overlay.querySelectorAll('[data-trigger-menu], [role="menu"], [role="listbox"], [class*="mufS8W_card"]')) {
166
167
  markPopup(popup, popup.hasAttribute('aria-busy') ? 'model' : 'action')
167
168
  }
168
169
  }
@@ -12,7 +12,6 @@ import { createThemeController } from './theme-controller-v2.js'
12
12
  import { createParticleFieldAdapter } from './particle-field-adapter.js'
13
13
  import { createToBottomAdapter } from './to-bottom-adapter.js'
14
14
  import { createConversationControlAdapter } from './conversation-control-adapter.js'
15
- import { createConversationScaleAdapter } from './conversation-scale-adapter.js'
16
15
  import { createLayoutStabilityDiagnostics } from './layout-stability-diagnostics.js'
17
16
  import { createSidebarControlAdapter } from './sidebar-control-adapter.js'
18
17
  import { createFloatingGlassAdapter } from './floating-glass-adapter.js'
@@ -68,10 +67,8 @@ export function applyPrtsPlugin(ctx, environment) {
68
67
  particleField.setSchemeTransitionActive(active)
69
68
  },
70
69
  })
71
- const sessions = contextService(ctx, 'sessions')
72
70
  const toBottom = createToBottomAdapter({ document, window })
73
71
  const conversationControls = createConversationControlAdapter({ document, window })
74
- const conversationScale = createConversationScaleAdapter({ document, window, sessions })
75
72
  const layoutDiagnostics = createLayoutStabilityDiagnostics({ document, window })
76
73
  const sidebarControls = createSidebarControlAdapter({ document, window })
77
74
  const floatingGlass = createFloatingGlassAdapter({ document, window })
@@ -153,7 +150,6 @@ export function applyPrtsPlugin(ctx, environment) {
153
150
  particleField.update({ ...preferences, enabled: false })
154
151
  toBottom.dispose()
155
152
  conversationControls.dispose()
156
- conversationScale.dispose()
157
153
  layoutDiagnostics.dispose()
158
154
  sidebarControls.dispose()
159
155
  floatingGlass.dispose()
@@ -194,9 +190,6 @@ export function applyPrtsPlugin(ctx, environment) {
194
190
  onRetrySave() {
195
191
  retryPersistence()
196
192
  },
197
- getConversationScalePreview(value) {
198
- return conversationScale.getCalibrationState(value)
199
- },
200
193
  })
201
194
  if (!operations.update(preferences, status, settingsPersistence)) {
202
195
  if (!mountWarningIssued) {
@@ -216,8 +209,6 @@ export function applyPrtsPlugin(ctx, environment) {
216
209
  assistantGlass.start()
217
210
  toBottom.start()
218
211
  conversationControls.start()
219
- conversationScale.update(preferences)
220
- conversationScale.start()
221
212
  layoutDiagnostics.start()
222
213
  sidebarControls.start()
223
214
  floatingGlass.start()
@@ -315,7 +306,6 @@ export function applyPrtsPlugin(ctx, environment) {
315
306
  particleField.dispose()
316
307
  toBottom.dispose()
317
308
  conversationControls.dispose()
318
- conversationScale.dispose()
319
309
  layoutDiagnostics.dispose()
320
310
  sidebarControls.dispose()
321
311
  floatingGlass.dispose()
@@ -361,7 +351,7 @@ export function applyPrtsPlugin(ctx, environment) {
361
351
 
362
352
  export function apply(ctx) {
363
353
  const React = require('react')
364
- const { defineStore } = require('@deepseek-ai/dsh-client-runtime/client')
354
+ const { defineStore } = require('@deepseek-ai/dsh-client-store')
365
355
  applyPrtsPlugin(ctx, {
366
356
  document,
367
357
  window,
@@ -46,7 +46,6 @@ export function createOperationsShell({
46
46
  onPreferenceChange = () => {},
47
47
  onPreferencePreview = () => {},
48
48
  onVisualReset = () => {},
49
- getConversationScalePreview = () => ({ visible: false, reason: 'unmeasured', mode: 'hidden' }),
50
49
  onRetrySave = () => {},
51
50
  }) {
52
51
  const root = document.documentElement
@@ -222,7 +221,6 @@ export function createOperationsShell({
222
221
  )
223
222
  const active = Boolean(candidate)
224
223
  root.dataset.prtsConversationState = active ? 'active' : 'idle'
225
- settingsOverlay?.refreshScalePreview()
226
224
  }
227
225
 
228
226
  function toggleScheme(event) {
@@ -418,7 +416,6 @@ export function createOperationsShell({
418
416
  clearRailGesture()
419
417
  setRailViewport(window.innerWidth)
420
418
  syncRailState()
421
- settingsOverlay?.scheduleScalePreviewRefresh?.()
422
419
  }
423
420
 
424
421
  function bindResponsiveQueries() {
@@ -457,7 +454,6 @@ export function createOperationsShell({
457
454
  onPreferencePreview,
458
455
  onResetVisual: onVisualReset,
459
456
  onRetrySave,
460
- getConversationScalePreview,
461
457
  })
462
458
  resizeShield.start([
463
459
  operationRegion,
@@ -1,15 +1,5 @@
1
1
  export const PRTS_STORAGE_KEY = 'dsh.ui.prts.v1'
2
2
 
3
- export const CONVERSATION_SCALE_DISTANCE_MIN = 16
4
- export const CONVERSATION_SCALE_DISTANCE_MAX = 240
5
- export const CONVERSATION_SCALE_DISTANCE_STEP = 8
6
- export const CONVERSATION_SCALE_DISTANCE_DEFAULT = 96
7
-
8
- export const CONVERSATION_SCALE_FOCUS_CONTRAST_MIN = 0
9
- export const CONVERSATION_SCALE_FOCUS_CONTRAST_MAX = 100
10
- export const CONVERSATION_SCALE_FOCUS_CONTRAST_STEP = 10
11
- export const CONVERSATION_SCALE_FOCUS_CONTRAST_DEFAULT = 70
12
-
13
3
  export const PARTICLE_TRAVERSAL_SPEED_MIN = 0
14
4
  export const PARTICLE_TRAVERSAL_SPEED_MAX = 2
15
5
  export const PARTICLE_TRAVERSAL_SPEED_STEP = 0.25
@@ -28,8 +18,6 @@ export const DEFAULT_PREFERENCES = Object.freeze({
28
18
  particleTraversalSpeed: PARTICLE_TRAVERSAL_SPEED_DEFAULT,
29
19
  particlePattern: 'orthogonal',
30
20
  railDefaultHidden: false,
31
- conversationScaleMaxDistance: CONVERSATION_SCALE_DISTANCE_DEFAULT,
32
- conversationScaleFocusContrast: CONVERSATION_SCALE_FOCUS_CONTRAST_DEFAULT,
33
21
  })
34
22
 
35
23
  export const VISUAL_PRESETS = Object.freeze({
@@ -70,7 +58,7 @@ export const PREFERENCE_GROUPS = Object.freeze({
70
58
  particles: Object.freeze(['conversationParticleDensity', 'heroParticleDensity', 'particleTraversalSpeed']),
71
59
  material: Object.freeze(['glass']),
72
60
  accessibility: Object.freeze(['motion', 'bootAnimation']),
73
- navigation: Object.freeze(['railDefaultHidden', 'conversationScaleMaxDistance', 'conversationScaleFocusContrast']),
61
+ navigation: Object.freeze(['railDefaultHidden']),
74
62
  })
75
63
 
76
64
  const TEXTURES = new Set(['off', 'restrained', 'full'])
@@ -107,20 +95,6 @@ function enumOr(value, allowed, fallback) {
107
95
  return allowed.has(value) ? value : fallback
108
96
  }
109
97
 
110
- function scaleDistanceOr(value, fallback) {
111
- const numeric = Number(value)
112
- if (!Number.isFinite(numeric)) return fallback
113
- const clamped = Math.min(CONVERSATION_SCALE_DISTANCE_MAX, Math.max(CONVERSATION_SCALE_DISTANCE_MIN, numeric))
114
- return Math.round(clamped / CONVERSATION_SCALE_DISTANCE_STEP) * CONVERSATION_SCALE_DISTANCE_STEP
115
- }
116
-
117
- function scaleFocusContrastOr(value, fallback) {
118
- const numeric = Number(value)
119
- if (!Number.isFinite(numeric)) return fallback
120
- const clamped = Math.min(CONVERSATION_SCALE_FOCUS_CONTRAST_MAX, Math.max(CONVERSATION_SCALE_FOCUS_CONTRAST_MIN, numeric))
121
- return Math.round(clamped / CONVERSATION_SCALE_FOCUS_CONTRAST_STEP) * CONVERSATION_SCALE_FOCUS_CONTRAST_STEP
122
- }
123
-
124
98
  function particleTraversalSpeedOr(value, fallback) {
125
99
  const numeric = Number(value)
126
100
  if (!Number.isFinite(numeric)) return fallback
@@ -170,14 +144,6 @@ export function normalizePreferences(value) {
170
144
  ),
171
145
  particlePattern: 'orthogonal',
172
146
  railDefaultHidden: booleanOr(input.railDefaultHidden, DEFAULT_PREFERENCES.railDefaultHidden),
173
- conversationScaleMaxDistance: scaleDistanceOr(
174
- input.conversationScaleMaxDistance,
175
- DEFAULT_PREFERENCES.conversationScaleMaxDistance,
176
- ),
177
- conversationScaleFocusContrast: scaleFocusContrastOr(
178
- input.conversationScaleFocusContrast,
179
- DEFAULT_PREFERENCES.conversationScaleFocusContrast,
180
- ),
181
147
  }
182
148
  normalized.preset = matchedVisualPreset(normalized)
183
149
  return normalized
@@ -1,5 +1,4 @@
1
- import { CONVERSATION_SCALE_FOCUS_CONTRAST_DEFAULT, CONVERSATION_SCALE_FOCUS_CONTRAST_MAX, CONVERSATION_SCALE_FOCUS_CONTRAST_MIN, CONVERSATION_SCALE_FOCUS_CONTRAST_STEP, PARTICLE_TRAVERSAL_SPEED_DEFAULT, PARTICLE_TRAVERSAL_SPEED_MAX, PARTICLE_TRAVERSAL_SPEED_MIN, PARTICLE_TRAVERSAL_SPEED_STEP, resolveParticleDetail } from './preferences.js'
2
- import { resolveConversationScaleLengths } from './conversation-scale-adapter.js'
1
+ import { PARTICLE_TRAVERSAL_SPEED_DEFAULT, PARTICLE_TRAVERSAL_SPEED_MAX, PARTICLE_TRAVERSAL_SPEED_MIN, PARTICLE_TRAVERSAL_SPEED_STEP, resolveParticleDetail } from './preferences.js'
3
2
 
4
3
  const SETTINGS_FOCUSABLE = [
5
4
  'button:not([disabled])',
@@ -57,24 +56,6 @@ function particleMarkup() {
57
56
  </button>`).join('')
58
57
  }
59
58
 
60
- function calibrationMarkup() {
61
- return `<div data-prts-scale-calibration data-prts-calibration-mode="hidden" data-prts-calibration-reason="unmeasured">
62
- <div data-prts-scale-calibration-viewport>
63
- <div data-prts-scale-calibration-canvas aria-hidden="true">
64
- <i data-prts-scale-calibration-sidebar></i>
65
- <span data-prts-scale-calibration-measure><i></i><output data-prts-scale-calibration-value>96 px</output></span>
66
- <span data-prts-scale-calibration-rail>${Array.from({ length: 9 }, (_, index) => `<i style="--prts-calibration-tick:${index}"></i>`).join('')}</span>
67
- <i data-prts-scale-calibration-gap></i>
68
- <span data-prts-scale-calibration-content><i></i><small>会话内容区</small></span>
69
- </div>
70
- </div>
71
- <div data-prts-scale-calibration-meta>
72
- <span data-prts-scale-calibration-scale hidden>缩放预览</span>
73
- <output data-prts-scale-calibration-status aria-live="polite">当前视图:几何测量中</output>
74
- </div>
75
- </div>`
76
- }
77
-
78
59
  export function themeSettingsMarkup() {
79
60
  return `<div data-prts-settings-backdrop hidden aria-hidden="true"></div>
80
61
  <dialog id="prts-theme-settings" data-prts-theme-settings aria-label="P.R.T.S. 主题设置" aria-hidden="true">
@@ -100,7 +81,7 @@ export function themeSettingsMarkup() {
100
81
  </section>
101
82
 
102
83
  <details data-prts-settings-advanced>
103
- <summary><span><small>精细调整</small><strong>粒子与会话刻度</strong></span><output data-prts-advanced-summary>标准 / 96 px</output></summary>
84
+ <summary><span><small>精细调整</small><strong>粒子效果</strong></span><output data-prts-advanced-summary>标准 / 1×</output></summary>
104
85
  <div data-prts-settings-advanced-content>
105
86
  <section data-prts-particle-detail>
106
87
  <header><strong>粒子精度</strong><span>同时调整会话与新会话徽记</span></header>
@@ -111,22 +92,6 @@ export function themeSettingsMarkup() {
111
92
  </label>
112
93
  <span data-prts-particle-error role="alert" hidden>粒子资源加载异常,主题已使用静态后备效果</span>
113
94
  </section>
114
- <section data-prts-scale-adjustment>
115
- <header><strong>会话刻度</strong><span>调整位置距离与悬停焦点的视觉区分</span></header>
116
- ${calibrationMarkup()}
117
- <label data-prts-setting-row="conversationScaleMaxDistance" data-prts-setting-range-row>
118
- <span>最大距离</span>
119
- <div><input type="range" min="16" max="240" step="8" value="96" data-prts-setting-range data-prts-setting-key="conversationScaleMaxDistance" aria-label="会话刻度最大距离"><output data-prts-scale-distance-output>96 px</output></div>
120
- </label>
121
- <label data-prts-setting-row="conversationScaleFocusContrast" data-prts-setting-range-row>
122
- <span>聚焦区分度</span>
123
- <div data-prts-scale-focus-control>
124
- <span data-prts-scale-focus-preview aria-hidden="true">${Array.from({ length: 5 }, (_, index) => `<i data-prts-scale-focus-preview-tick="${index}"></i>`).join('')}</span>
125
- <input type="range" min="0" max="100" step="10" value="70" data-prts-setting-range data-prts-setting-key="conversationScaleFocusContrast" aria-label="会话刻度聚焦区分度">
126
- <output data-prts-scale-focus-output>70</output>
127
- </div>
128
- </label>
129
- </section>
130
95
  </div>
131
96
  </details>
132
97
 
@@ -160,7 +125,6 @@ export function createThemeSettingsOverlay({
160
125
  onPreferencePreview = () => {},
161
126
  onResetVisual = () => {},
162
127
  onRetrySave = () => {},
163
- getConversationScalePreview = () => ({ visible: false, reason: 'unmeasured', mode: 'hidden' }),
164
128
  }) {
165
129
  const window = document?.defaultView
166
130
  const transparencyMedia = window?.matchMedia?.('(prefers-reduced-transparency: reduce)')
@@ -171,17 +135,8 @@ export function createThemeSettingsOverlay({
171
135
  let currentPreferences
172
136
  let currentStatus
173
137
  let currentPersistence = { phase: 'idle', revision: 0 }
174
- let scaleDistanceDraft
175
- let scaleDistanceDirty = false
176
- let scaleFocusContrastDraft
177
- let scaleFocusContrastDirty = false
178
138
  let particleTraversalSpeedDraft
179
139
  let particleTraversalSpeedDirty = false
180
- let calibrationResizeObserver
181
- let calibrationSettleTimer
182
- let calibrationFrame
183
- let calibrationViewportWidth
184
- let calibrationFactor
185
140
  let savedTimer
186
141
  let feedbackTimer
187
142
  let feedbackTarget
@@ -230,8 +185,6 @@ export function createThemeSettingsOverlay({
230
185
  const value = Boolean(next)
231
186
  if (value === isOpen) return isOpen
232
187
  if (!value) {
233
- commitScaleDistance()
234
- commitScaleFocusContrast()
235
188
  commitParticleTraversalSpeed()
236
189
  }
237
190
  isOpen = value
@@ -243,7 +196,6 @@ export function createThemeSettingsOverlay({
243
196
  onOpen()
244
197
  showPanel()
245
198
  panel.setAttribute('data-prts-settings-visible', '')
246
- renderScaleCalibration()
247
199
  ;(panel.querySelector('[data-prts-theme-settings-close]') ?? focusable()[0] ?? panel).focus?.()
248
200
  } else {
249
201
  panel.removeAttribute('data-prts-settings-visible')
@@ -254,27 +206,6 @@ export function createThemeSettingsOverlay({
254
206
  return isOpen
255
207
  }
256
208
 
257
- function normalizeScaleDistance(value) {
258
- const numeric = Number(value)
259
- if (!Number.isFinite(numeric)) return 96
260
- return Math.min(240, Math.max(16, Math.round(numeric / 8) * 8))
261
- }
262
-
263
- function currentScaleDistance() {
264
- return normalizeScaleDistance(scaleDistanceDirty ? scaleDistanceDraft : currentPreferences?.conversationScaleMaxDistance)
265
- }
266
-
267
- function normalizeScaleFocusContrast(value) {
268
- const numeric = Number(value)
269
- if (!Number.isFinite(numeric)) return CONVERSATION_SCALE_FOCUS_CONTRAST_DEFAULT
270
- const clamped = Math.min(CONVERSATION_SCALE_FOCUS_CONTRAST_MAX, Math.max(CONVERSATION_SCALE_FOCUS_CONTRAST_MIN, numeric))
271
- return Math.round(clamped / CONVERSATION_SCALE_FOCUS_CONTRAST_STEP) * CONVERSATION_SCALE_FOCUS_CONTRAST_STEP
272
- }
273
-
274
- function currentScaleFocusContrast() {
275
- return normalizeScaleFocusContrast(scaleFocusContrastDirty ? scaleFocusContrastDraft : currentPreferences?.conversationScaleFocusContrast)
276
- }
277
-
278
209
  function normalizeParticleTraversalSpeed(value) {
279
210
  const numeric = Number(value)
280
211
  if (!Number.isFinite(numeric)) return PARTICLE_TRAVERSAL_SPEED_DEFAULT
@@ -306,142 +237,10 @@ export function createThemeSettingsOverlay({
306
237
  setText(panel.querySelector('[data-prts-particle-traversal-output]'), particleTraversalLabel(value))
307
238
  }
308
239
 
309
- function calibrationStatus(state, value) {
310
- if (state?.mode === 'configured') return `当前视图:使用配置上限 / ${Math.round(Number(state.left) || value)} px`
311
- if (state?.simulated) return `模拟布局:使用配置上限 / ${value} px`
312
- if (state?.mode === 'centered') return `当前视图:自动居中 / 距侧栏 ${Math.round(Number(state.left) || 0)} px`
313
- const reasons = {
314
- drawer: '抽屉布局', phone: '手机布局', turns: '消息不足', corridor: '会话走廊不足',
315
- preview: '会话预览空间不足', unmeasured: '模拟布局',
316
- }
317
- return `当前视图:隐藏 / ${reasons[state?.reason] || '空间不足'}`
318
- }
319
-
320
240
  function setText(node, value) {
321
241
  if (node && node.textContent !== value) node.textContent = value
322
242
  }
323
243
 
324
- function setCalibrationResizeState(active) {
325
- for (const node of [panel, backdrop]) {
326
- if (!node || node.hasAttribute('data-prts-resizing') === active) continue
327
- node.toggleAttribute('data-prts-resizing', active)
328
- }
329
- }
330
-
331
- function cancelCalibrationFrame() {
332
- if (calibrationFrame === undefined) return
333
- window?.cancelAnimationFrame?.(calibrationFrame)
334
- window?.clearTimeout?.(calibrationFrame)
335
- calibrationFrame = undefined
336
- }
337
-
338
- function measureScaleCalibration() {
339
- calibrationFrame = undefined
340
- if (!isOpen) return
341
- const viewport = panel?.querySelector?.('[data-prts-scale-calibration-viewport]')
342
- const canvas = panel?.querySelector?.('[data-prts-scale-calibration-canvas]')
343
- const calibration = panel?.querySelector?.('[data-prts-scale-calibration]')
344
- const scaleNote = panel?.querySelector?.('[data-prts-scale-calibration-scale]')
345
- if (!viewport || !canvas || !calibration) return
346
- const available = calibrationViewportWidth || viewport.clientWidth
347
- const factor = available > 0 ? Math.min(1, available / 360) : 1
348
- const roundedFactor = Number(factor.toFixed(4))
349
- const value = currentScaleDistance()
350
- let state
351
- try { state = getConversationScalePreview(value) } catch { state = { visible: false, reason: 'unmeasured', mode: 'hidden' } }
352
-
353
- if (roundedFactor !== calibrationFactor) {
354
- calibrationFactor = roundedFactor
355
- canvas.style.transform = `scale(${roundedFactor})`
356
- viewport.style.height = `${Math.ceil(94 * roundedFactor)}px`
357
- }
358
- const scaled = factor < .999
359
- if (calibration.hasAttribute('data-prts-calibration-scaled') !== scaled) calibration.toggleAttribute('data-prts-calibration-scaled', scaled)
360
- if (scaleNote && scaleNote.hidden !== !scaled) scaleNote.hidden = !scaled
361
- const mode = state?.simulated ? 'simulated' : state?.mode || 'hidden'
362
- const reason = state?.reason || 'unmeasured'
363
- if (calibration.dataset.prtsCalibrationMode !== mode) calibration.dataset.prtsCalibrationMode = mode
364
- if (calibration.dataset.prtsCalibrationReason !== reason) calibration.dataset.prtsCalibrationReason = reason
365
- setText(panel.querySelector('[data-prts-scale-calibration-status]'), calibrationStatus(state, value))
366
- }
367
-
368
- function scheduleCalibrationMeasurement() {
369
- if (!isOpen) return
370
- cancelCalibrationFrame()
371
- calibrationFrame = window?.requestAnimationFrame?.(measureScaleCalibration)
372
- ?? window?.setTimeout?.(measureScaleCalibration, 16)
373
- }
374
-
375
- function scheduleScalePreviewRefresh(entries = []) {
376
- const viewportEntry = entries.find?.(entry => entry.target?.matches?.('[data-prts-scale-calibration-viewport]'))
377
- const observedWidth = Number(viewportEntry?.contentRect?.width)
378
- if (Number.isFinite(observedWidth) && observedWidth > 0) {
379
- calibrationViewportWidth = observedWidth
380
- }
381
- if (!isOpen) return
382
- setCalibrationResizeState(true)
383
- if (calibrationSettleTimer !== undefined) window?.clearTimeout?.(calibrationSettleTimer)
384
- calibrationSettleTimer = window?.setTimeout?.(() => {
385
- calibrationSettleTimer = undefined
386
- setCalibrationResizeState(false)
387
- renderScaleCalibration()
388
- }, 160)
389
- }
390
-
391
- function renderScaleCalibration() {
392
- if (!isOpen) return
393
- if (!panel || !currentPreferences) return
394
- const value = currentScaleDistance()
395
- const contrast = currentScaleFocusContrast()
396
- const lengths = resolveConversationScaleLengths(contrast)
397
- const calibration = panel.querySelector('[data-prts-scale-calibration]')
398
- const distanceRange = panel.querySelector('input[data-prts-setting-range][data-prts-setting-key="conversationScaleMaxDistance"]')
399
- const contrastRange = panel.querySelector('input[data-prts-setting-range][data-prts-setting-key="conversationScaleFocusContrast"]')
400
- const distanceOutput = panel.querySelector('[data-prts-scale-distance-output]')
401
- const contrastOutput = panel.querySelector('[data-prts-scale-focus-output]')
402
- const calibrationValue = panel.querySelector('[data-prts-scale-calibration-value]')
403
- if (distanceRange && distanceRange.value !== String(value)) distanceRange.value = String(value)
404
- if (contrastRange && contrastRange.value !== String(contrast)) contrastRange.value = String(contrast)
405
- setText(distanceOutput, `${value} px`)
406
- setText(contrastOutput, String(contrast))
407
- setText(calibrationValue, `${value} px`)
408
- for (const tick of panel.querySelectorAll('[data-prts-scale-focus-preview-tick]')) {
409
- const length = lengths[Number(tick.dataset.prtsScaleFocusPreviewTick)] ?? lengths.at(-1)
410
- const width = `${length}px`
411
- if (tick.style.width !== width) tick.style.width = width
412
- }
413
- const detail = resolveParticleDetail(currentPreferences)
414
- const detailLabels = { compact: '精简', standard: '标准', precise: '精细' }
415
- const advanced = panel.querySelector('[data-prts-advanced-summary]')
416
- setText(advanced, `${detailLabels[detail]} / ${conciseNumber(currentParticleTraversalSpeed())}× / 区分度 ${contrast} / ${value} px`)
417
- if (!calibration) return
418
- const distance = `${value}px`
419
- if (calibration.style.getPropertyValue('--prts-calibration-distance') !== distance) {
420
- calibration.style.setProperty('--prts-calibration-distance', distance)
421
- }
422
- scheduleCalibrationMeasurement()
423
- }
424
-
425
- function commitScaleDistance() {
426
- if (!scaleDistanceDirty || !currentPreferences) return false
427
- const value = currentScaleDistance()
428
- scaleDistanceDirty = false
429
- scaleDistanceDraft = value
430
- if (value !== normalizeScaleDistance(currentPreferences.conversationScaleMaxDistance)) onPreferenceChange('conversationScaleMaxDistance', value)
431
- else renderScaleCalibration()
432
- return true
433
- }
434
-
435
- function commitScaleFocusContrast() {
436
- if (!scaleFocusContrastDirty || !currentPreferences) return false
437
- const value = currentScaleFocusContrast()
438
- scaleFocusContrastDirty = false
439
- scaleFocusContrastDraft = value
440
- if (value !== normalizeScaleFocusContrast(currentPreferences.conversationScaleFocusContrast)) onPreferenceChange('conversationScaleFocusContrast', value)
441
- else renderScaleCalibration()
442
- return true
443
- }
444
-
445
244
  function commitParticleTraversalSpeed() {
446
245
  if (!particleTraversalSpeedDirty || !currentPreferences) return false
447
246
  const value = currentParticleTraversalSpeed()
@@ -495,11 +294,8 @@ export function createThemeSettingsOverlay({
495
294
  if (transparency) transparency.hidden = !(currentPreferences.glass !== 'off' && transparencyMedia?.matches)
496
295
  const particleError = panel.querySelector('[data-prts-particle-error]')
497
296
  if (particleError) particleError.hidden = currentStatus?.particle?.phase !== 'error'
498
- if (!scaleDistanceDirty) scaleDistanceDraft = normalizeScaleDistance(currentPreferences.conversationScaleMaxDistance)
499
- if (!scaleFocusContrastDirty) scaleFocusContrastDraft = normalizeScaleFocusContrast(currentPreferences.conversationScaleFocusContrast)
500
297
  if (!particleTraversalSpeedDirty) particleTraversalSpeedDraft = normalizeParticleTraversalSpeed(currentPreferences.particleTraversalSpeed)
501
298
  renderParticleTraversal()
502
- renderScaleCalibration()
503
299
  renderPersistence()
504
300
  }
505
301
 
@@ -531,10 +327,6 @@ export function createThemeSettingsOverlay({
531
327
  if (target.hasAttribute('data-prts-reset-visual')) { setResetConfirmation(true); return }
532
328
  if (target.hasAttribute('data-prts-reset-cancel')) { setResetConfirmation(false); return }
533
329
  if (target.hasAttribute('data-prts-reset-confirm-action')) {
534
- scaleDistanceDirty = false
535
- scaleDistanceDraft = undefined
536
- scaleFocusContrastDirty = false
537
- scaleFocusContrastDraft = undefined
538
330
  particleTraversalSpeedDirty = false
539
331
  particleTraversalSpeedDraft = undefined
540
332
  setResetConfirmation(false)
@@ -547,13 +339,7 @@ export function createThemeSettingsOverlay({
547
339
  if (!target?.matches?.('input[data-prts-setting-range]')) return
548
340
  const value = Number(target.value)
549
341
  if (!Number.isFinite(value)) return
550
- if (target.dataset.prtsSettingKey === 'conversationScaleMaxDistance') {
551
- scaleDistanceDraft = value
552
- scaleDistanceDirty = true
553
- } else if (target.dataset.prtsSettingKey === 'conversationScaleFocusContrast') {
554
- scaleFocusContrastDraft = value
555
- scaleFocusContrastDirty = true
556
- } else if (target.dataset.prtsSettingKey === 'particleTraversalSpeed') {
342
+ if (target.dataset.prtsSettingKey === 'particleTraversalSpeed') {
557
343
  particleTraversalSpeedDraft = normalizeParticleTraversalSpeed(value)
558
344
  particleTraversalSpeedDirty = true
559
345
  renderParticleTraversal()
@@ -562,7 +348,6 @@ export function createThemeSettingsOverlay({
562
348
  } else {
563
349
  return
564
350
  }
565
- renderScaleCalibration()
566
351
  }
567
352
 
568
353
  function onKeydown(event) {
@@ -584,8 +369,6 @@ export function createThemeSettingsOverlay({
584
369
  const commitFromControl = event => {
585
370
  const target = event.target
586
371
  if (!target?.matches?.('input[data-prts-setting-range]')) return
587
- if (target.dataset.prtsSettingKey === 'conversationScaleMaxDistance') commitScaleDistance()
588
- if (target.dataset.prtsSettingKey === 'conversationScaleFocusContrast') commitScaleFocusContrast()
589
372
  if (target.dataset.prtsSettingKey === 'particleTraversalSpeed') commitParticleTraversalSpeed()
590
373
  }
591
374
 
@@ -600,24 +383,15 @@ export function createThemeSettingsOverlay({
600
383
  document.addEventListener('keydown', onKeydown)
601
384
  transparencyMedia?.addEventListener?.('change', renderMediaState)
602
385
  motionMedia?.addEventListener?.('change', renderMediaState)
603
- if (typeof window?.ResizeObserver === 'function') {
604
- calibrationResizeObserver = new window.ResizeObserver(scheduleScalePreviewRefresh)
605
- const viewport = panel?.querySelector?.('[data-prts-scale-calibration-viewport]')
606
- if (viewport) calibrationResizeObserver.observe(viewport)
607
- }
608
386
 
609
387
  return {
610
388
  update(preferences, status, persistenceState = currentPersistence) {
611
389
  currentPreferences = preferences
612
390
  currentStatus = status
613
391
  currentPersistence = persistenceState ?? { phase: 'idle', revision: 0 }
614
- if (!scaleDistanceDirty) scaleDistanceDraft = normalizeScaleDistance(preferences?.conversationScaleMaxDistance)
615
- if (!scaleFocusContrastDirty) scaleFocusContrastDraft = normalizeScaleFocusContrast(preferences?.conversationScaleFocusContrast)
616
392
  if (!particleTraversalSpeedDirty) particleTraversalSpeedDraft = normalizeParticleTraversalSpeed(preferences?.particleTraversalSpeed)
617
393
  renderState()
618
394
  },
619
- refreshScalePreview: renderScaleCalibration,
620
- scheduleScalePreviewRefresh,
621
395
  open: () => setOpen(true),
622
396
  close: () => setOpen(false),
623
397
  toggle,
@@ -626,10 +400,6 @@ export function createThemeSettingsOverlay({
626
400
  hidePanel()
627
401
  if (savedTimer !== undefined) window?.clearTimeout?.(savedTimer)
628
402
  if (feedbackTimer !== undefined) window?.clearTimeout?.(feedbackTimer)
629
- if (calibrationSettleTimer !== undefined) window?.clearTimeout?.(calibrationSettleTimer)
630
- calibrationSettleTimer = undefined
631
- cancelCalibrationFrame()
632
- setCalibrationResizeState(false)
633
403
  feedbackTarget?.removeAttribute?.('data-prts-setting-feedback')
634
404
  trigger?.setAttribute('aria-expanded', 'false')
635
405
  trigger?.removeEventListener('click', toggle)
@@ -643,7 +413,6 @@ export function createThemeSettingsOverlay({
643
413
  document.removeEventListener('keydown', onKeydown)
644
414
  transparencyMedia?.removeEventListener?.('change', renderMediaState)
645
415
  motionMedia?.removeEventListener?.('change', renderMediaState)
646
- calibrationResizeObserver?.disconnect()
647
416
  document.documentElement.removeAttribute('data-prts-settings-open')
648
417
  },
649
418
  }