@ash-qw/dsh-theme-prts 0.1.97 → 0.1.98

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.97",
3
+ "version": "0.1.98",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Unofficial Arknights P.R.T.S.-inspired UI theme for DeepSeek Harness Web",
@@ -2,8 +2,13 @@ const CONTROL_ATTRIBUTE = 'data-prts-glass-control'
2
2
  const MENU_ATTRIBUTE = 'data-prts-glass-menu'
3
3
  const FALLBACK_COMPOSER_ATTRIBUTE = 'data-prts-composer-fallback'
4
4
  const COMPOSER_SIGNAL_ATTRIBUTE = 'data-prts-composer-signal'
5
+ const FALLBACK_COMPOSER_ROOT_SELECTOR = [
6
+ '[data-slot="conversation.composer"]',
7
+ '[data-composer-seat]',
8
+ ].join(', ')
5
9
  const COMPOSER_RELEVANCE_SELECTOR = [
6
10
  '[data-composer-card]',
11
+ FALLBACK_COMPOSER_ROOT_SELECTOR,
7
12
  'form textarea',
8
13
  'button[aria-haspopup="menu"]',
9
14
  'button[aria-haspopup="listbox"]',
@@ -56,6 +61,18 @@ function markPopup(popup, kind) {
56
61
  }
57
62
  }
58
63
 
64
+ function fallbackComposerForms(document) {
65
+ const forms = new Set()
66
+ for (const root of document?.querySelectorAll?.(FALLBACK_COMPOSER_ROOT_SELECTOR) ?? []) {
67
+ if (root.querySelector('[data-composer-card]')) continue
68
+ for (const textarea of root.querySelectorAll('form textarea')) {
69
+ const form = textarea.closest('form')
70
+ if (form && root.contains(form)) forms.add(form)
71
+ }
72
+ }
73
+ return forms
74
+ }
75
+
59
76
  export function createComposerGlassAdapter({ document, window }) {
60
77
  let observer
61
78
  let scanFrame
@@ -104,17 +121,17 @@ export function createComposerGlassAdapter({ document, window }) {
104
121
  signal.remove?.()
105
122
  ownedSignals.delete(signal)
106
123
  }
124
+ const fallbackForms = fallbackComposerForms(document)
107
125
  for (const form of [...ownedFallbacks]) {
108
- if (!form.isConnected || form.hasAttribute('data-composer-card')) {
109
- form.removeAttribute?.(FALLBACK_COMPOSER_ATTRIBUTE)
110
- ownedFallbacks.delete(form)
111
- }
126
+ if (form.isConnected && fallbackForms.has(form) && !form.hasAttribute('data-composer-card')) continue
127
+ form.removeAttribute?.(FALLBACK_COMPOSER_ATTRIBUTE)
128
+ ownedFallbacks.delete(form)
129
+ if (form.hasAttribute('data-composer-card')) continue
130
+ const signal = form.querySelector?.(`:scope > [${COMPOSER_SIGNAL_ATTRIBUTE}]`)
131
+ if (!ownedSignals.has(signal)) continue
132
+ signal.remove?.()
133
+ ownedSignals.delete(signal)
112
134
  }
113
- const fallbackForms = new Set(
114
- [...(document?.querySelectorAll?.('form textarea') ?? [])]
115
- .map(textarea => textarea.closest('form'))
116
- .filter(form => form && !form.querySelector('[data-composer-card]')),
117
- )
118
135
  for (const form of fallbackForms) {
119
136
  form.setAttribute(FALLBACK_COMPOSER_ATTRIBUTE, '')
120
137
  ownedFallbacks.add(form)
@@ -44,7 +44,6 @@ export function applyPrtsPlugin(ctx, environment) {
44
44
  const safeMode = isSafeMode(window?.location?.search ?? '')
45
45
  const storage = window?.localStorage
46
46
  const themeService = contextService(ctx, 'theme')
47
- const startup = createPrtsStartupSequence({ document, window, prtsEmblem: assets.prtsEmblem, rhodesEmblem: assets.emblem, timings: startupTimings })
48
47
  const composerGlass = createComposerGlassAdapter({ document, window })
49
48
  const assistantGlass = createAssistantGlassAdapter({
50
49
  document,
@@ -62,6 +61,19 @@ export function applyPrtsPlugin(ctx, environment) {
62
61
  sync()
63
62
  },
64
63
  })
64
+ const startup = createPrtsStartupSequence({
65
+ document,
66
+ window,
67
+ prtsEmblem: assets.prtsEmblem,
68
+ rhodesEmblem: assets.emblem,
69
+ timings: startupTimings,
70
+ onActiveChange(active) {
71
+ particleField.setStartupActive(active)
72
+ },
73
+ onReady() {
74
+ particleField.setStartupActive(false)
75
+ },
76
+ })
65
77
  const theme = createThemeController({
66
78
  document,
67
79
  window,
@@ -144,6 +156,7 @@ export function applyPrtsPlugin(ctx, environment) {
144
156
  }
145
157
 
146
158
  const disposeVisualState = () => {
159
+ particleField.update({ ...preferences, enabled: false })
147
160
  startup.stop({ restoreFocus: false })
148
161
  stopStartupReadyWatch()
149
162
  stopMountWatch()
@@ -151,7 +164,6 @@ export function applyPrtsPlugin(ctx, environment) {
151
164
  disposeOperations()
152
165
  composerGlass.dispose()
153
166
  assistantGlass.dispose()
154
- particleField.update({ ...preferences, enabled: false })
155
167
  toBottom.dispose()
156
168
  conversationControls.dispose()
157
169
  layoutDiagnostics.dispose()
@@ -227,9 +239,11 @@ export function applyPrtsPlugin(ctx, environment) {
227
239
  phase: result.persisted ? 'saved' : 'error',
228
240
  revision: settingsPersistence.revision + 1,
229
241
  }
242
+ const shouldPlayStartup = !safeMode && !wasEnabled && preferences.enabled && preferences.bootAnimation
243
+ if (shouldPlayStartup) particleField.setStartupActive(true)
230
244
  applyVisualState()
231
245
  sync()
232
- if (!safeMode && !wasEnabled && preferences.enabled && preferences.bootAnimation) startup.play()
246
+ if (shouldPlayStartup && !startup.play()) particleField.setStartupActive(false)
233
247
  }
234
248
 
235
249
 
@@ -302,13 +316,13 @@ export function applyPrtsPlugin(ctx, environment) {
302
316
  }
303
317
  ctx.effect(() => () => {
304
318
  statusSource.dispose()
319
+ particleField.dispose()
305
320
  startup.dispose({ restoreFocus: false })
306
321
  stopStartupReadyWatch()
307
322
  stopMountWatch()
308
323
  disposeOperations()
309
324
  composerGlass.dispose()
310
325
  assistantGlass.dispose()
311
- particleField.dispose()
312
326
  toBottom.dispose()
313
327
  conversationControls.dispose()
314
328
  layoutDiagnostics.dispose()
@@ -27,7 +27,10 @@ function markup(emblem) {
27
27
  <svg data-prts-scheme-icon="dark" viewBox="0 0 24 24" aria-hidden="true"><path d="M20.2 15.4A8.4 8.4 0 0 1 8.6 3.8 8.5 8.5 0 1 0 20.2 15.4Z"></path></svg>
28
28
  <small>明暗</small>
29
29
  </button>
30
- <button type="button" data-prts-theme-disable aria-label="关闭 P.R.T.S. 主题"><b aria-hidden="true">×</b><small>关闭主题</small></button>
30
+ <button type="button" data-prts-theme-disable aria-label="退出 P.R.T.S.,返回 Harness 原生界面">
31
+ <svg data-prts-theme-disable-icon viewBox="0 0 24 24" aria-hidden="true"><path d="M12 3v8"></path><path d="M7.1 6.8a7 7 0 1 0 9.8 0"></path></svg>
32
+ <small>退出 P.R.T.S.</small>
33
+ </button>
31
34
  </div>
32
35
  </nav>
33
36
  ${themeSettingsMarkup()}
@@ -326,7 +326,7 @@ export function createParticleFieldAdapter({ document, window, emblem = '', embl
326
326
  const rhodesIndex = Math.max(0, emblemSequence.findIndex(entry => entry.key === 'rhodes-island'))
327
327
  const suppliedHeroMask = normalizeEmblemMasks(heroEmblemMask ? [heroEmblemMask] : [], window).get('rhodes-island-hero')
328
328
  const empty = { mounted: false, hero: false, side: -1, transition: null, anchor: { horizontalProgress: 0, verticalProgress: 0 }, phase: 'error', error: '徽记资源异常' }
329
- if (!document || !window) return { update() {}, setSchemeTransitionActive() {}, inspect: () => empty, dispose() {} }
329
+ if (!document || !window) return { update() {}, setStartupActive() {}, setSchemeTransitionActive() {}, inspect: () => empty, dispose() {} }
330
330
 
331
331
  let preferences = {}
332
332
  let operation
@@ -396,6 +396,7 @@ export function createParticleFieldAdapter({ document, window, emblem = '', embl
396
396
  let pauseStartedAt
397
397
  let hydrationPaused = false
398
398
  let schemeTransitionPaused = false
399
+ let startupPaused = false
399
400
  let hydrationTimer
400
401
  let hydrationFrame
401
402
  let hydrationStableFrames = 0
@@ -429,7 +430,7 @@ export function createParticleFieldAdapter({ document, window, emblem = '', embl
429
430
  : preferences.conversationParticleDensity
430
431
  const densityProfile = density => PARTICLE_DENSITY_PROFILES[density] ?? PARTICLE_DENSITY_PROFILES.standard
431
432
  const particleRadius = density => densityProfile(density ?? activeDensity()).radius
432
- const canRenderFrame = () => !disposed && documentVisible && canvasVisible && !hydrationPaused && !schemeTransitionPaused && !resizing
433
+ const canRenderFrame = () => !disposed && documentVisible && canvasVisible && !hydrationPaused && !startupPaused && !schemeTransitionPaused && !resizing
433
434
  const layoutWidth = () => width
434
435
  const layoutHeight = () => height
435
436
  const layoutShiftX = () => 0
@@ -707,6 +708,7 @@ export function createParticleFieldAdapter({ document, window, emblem = '', embl
707
708
  }
708
709
 
709
710
  function buildParticles() {
711
+ if (startupPaused) return false
710
712
  const selected = populationSource()
711
713
  if (!selected.length) {
712
714
  reportState({ phase: 'error', error: '徽记资源异常' })
@@ -1646,14 +1648,17 @@ export function createParticleFieldAdapter({ document, window, emblem = '', embl
1646
1648
  cancelFrame()
1647
1649
  return
1648
1650
  }
1649
- if (pauseStartedAt !== undefined) {
1650
- resumePausedTimelines(now)
1651
- if (canvas && particles.length) {
1652
- syncCanvasBackingStore()
1653
- cancelFrame()
1654
- draw(now)
1655
- return
1656
- }
1651
+ const resumedFromPause = pauseStartedAt !== undefined
1652
+ if (resumedFromPause) resumePausedTimelines(now)
1653
+ let particlesBuilt = false
1654
+ if (canvas && !particles.length && maskRecords.length) {
1655
+ particlesBuilt = buildParticles()
1656
+ }
1657
+ if (canvas && particles.length && (resumedFromPause || particlesBuilt)) {
1658
+ syncCanvasBackingStore()
1659
+ cancelFrame()
1660
+ draw(now)
1661
+ return
1657
1662
  }
1658
1663
  scheduleFrame()
1659
1664
  }
@@ -1819,6 +1824,12 @@ export function createParticleFieldAdapter({ document, window, emblem = '', embl
1819
1824
  if (!mediaQuery?.addEventListener) mediaQuery?.addListener?.(mediaListener)
1820
1825
  document.addEventListener?.('visibilitychange', onVisibilityChange)
1821
1826
  return {
1827
+ setStartupActive(active) {
1828
+ const next = Boolean(active)
1829
+ if (startupPaused === next) return
1830
+ startupPaused = next
1831
+ updateFrameGate()
1832
+ },
1822
1833
  setSchemeTransitionActive(active) {
1823
1834
  const next = Boolean(active)
1824
1835
  if (schemeTransitionPaused === next) return
@@ -1903,12 +1914,13 @@ export function createParticleFieldAdapter({ document, window, emblem = '', embl
1903
1914
  : transition?.toCount !== undefined
1904
1915
  ? Math.round(transition.fromCount + (transition.toCount - transition.fromCount) * easeInOut(transition.progress))
1905
1916
  : particles.length,
1906
- targetParticles: transition?.toCount ?? targetParticleCount(),
1917
+ targetParticles: startupPaused && !particles.length ? 0 : transition?.toCount ?? targetParticleCount(),
1907
1918
  density: activeDensity() || 'standard',
1908
1919
  firstFrameReady: canvas?.hasAttribute('data-prts-particle-ready') === true,
1909
1920
  hydrationPaused,
1910
1921
  sleeping: Boolean(context && particles.length && frame === undefined && canRenderFrame() && !motionReduced() && !phoneStatic()),
1911
1922
  schemeTransitionPaused,
1923
+ startupPaused,
1912
1924
  pointer: {
1913
1925
  active: pointerActive,
1914
1926
  clientX: pointerClientX,
@@ -18,7 +18,7 @@ export const DEFAULT_PREFERENCES = Object.freeze({
18
18
  particleTraversalSpeed: PARTICLE_TRAVERSAL_SPEED_DEFAULT,
19
19
  particlePattern: 'orthogonal',
20
20
  railDefaultHidden: false,
21
- conversationStyle: 'deck-chat',
21
+ conversationStyle: 'native',
22
22
  })
23
23
 
24
24
  export const VISUAL_PRESETS = Object.freeze({
@@ -24,7 +24,7 @@ const STARTUP_STAGES = Object.freeze({
24
24
 
25
25
  function startupMarkup(prtsEmblem = '', rhodesEmblem = '') {
26
26
  const ticks = Array.from({ length: 21 }, (_, index) => `<i${index % 5 === 0 ? ' data-major' : ''}></i>`).join('')
27
- return `<section data-prts-startup data-stage="boot" popover="manual" role="status" aria-live="assertive" aria-label="P.R.T.S. 启动序列" tabindex="-1">
27
+ return `<section data-prts-startup data-stage="boot" popover="manual" role="status" aria-live="assertive" aria-label="P.R.T.S. 启动序列">
28
28
  <div data-prts-startup-grid aria-hidden="true"></div>
29
29
  <div data-prts-startup-scan aria-hidden="true"></div>
30
30
  <div data-prts-startup-cut="left" aria-hidden="true"></div>
@@ -73,16 +73,60 @@ function easeOutCubic(value) {
73
73
  return 1 - Math.pow(1 - clamp01(value), 3)
74
74
  }
75
75
 
76
+ function approachProgressAt(elapsed, duration) {
77
+ if (!(duration > 0)) return 90
78
+ return 90 * easeOutCubic(elapsed / duration)
79
+ }
80
+
81
+ function approachVelocityAt(elapsed, duration) {
82
+ if (!(duration > 0)) return 0
83
+ const remaining = 1 - clamp01(elapsed / duration)
84
+ return 270 * remaining * remaining / duration
85
+ }
86
+
87
+ function finishProgressAt(startProgress, startVelocity, elapsed, duration) {
88
+ if (!(duration > 0)) return 100
89
+ const progressDelta = Math.max(0, 100 - startProgress)
90
+ if (progressDelta === 0) return 100
91
+ const time = clamp01(elapsed / duration)
92
+ const timeSquared = time * time
93
+ const timeCubed = timeSquared * time
94
+ const startWeight = 2 * timeCubed - 3 * timeSquared + 1
95
+ const velocityWeight = timeCubed - 2 * timeSquared + time
96
+ const endWeight = -2 * timeCubed + 3 * timeSquared
97
+ const startTangent = Math.min(progressDelta * 3, Math.max(0, startVelocity) * duration)
98
+ const progress = startWeight * startProgress
99
+ + velocityWeight * startTangent
100
+ + endWeight * 100
101
+ return Math.max(startProgress, Math.min(100, progress))
102
+ }
103
+
76
104
  export function findNativeHarnessLoader(document) {
77
105
  return document?.querySelector?.('[data-dsh-boot]') ?? null
78
106
  }
79
107
 
108
+ function matchesOpenTopLayer(node) {
109
+ for (const selector of [':modal', ':popover-open']) {
110
+ try {
111
+ if (node?.matches?.(selector)) return true
112
+ } catch {}
113
+ }
114
+ return false
115
+ }
116
+
117
+ function hostHasOpenTopLayer(document, overlay) {
118
+ const candidates = [...(document?.querySelectorAll?.('dialog[open], [popover]') ?? [])]
119
+ return candidates.some(node => node !== overlay && matchesOpenTopLayer(node))
120
+ }
121
+
80
122
  export function createPrtsStartupSequence({
81
123
  document,
82
124
  window,
83
125
  prtsEmblem = '',
84
126
  rhodesEmblem = '',
85
127
  timings = PRTS_STARTUP_TIMINGS,
128
+ onActiveChange = () => {},
129
+ onReady = () => {},
86
130
  } = {}) {
87
131
  const schedule = { ...PRTS_STARTUP_TIMINGS, ...timings }
88
132
  let overlay
@@ -95,6 +139,7 @@ export function createPrtsStartupSequence({
95
139
  let hostReady = false
96
140
  let finishStartedAt
97
141
  let finishStartedProgress = 0
142
+ let finishStartedVelocity = 0
98
143
  let readyStartedAt
99
144
  let exitStartedAt
100
145
  let timeoutStartedAt
@@ -102,7 +147,12 @@ export function createPrtsStartupSequence({
102
147
  let pendingStage
103
148
  let copyTransitionStartedAt
104
149
  let lastProgress = -1
150
+ let lastRoundedProgress = -1
105
151
  let useReducedMotion = false
152
+ let progressFill
153
+ let progressPercent
154
+ let activeReported = false
155
+ let readyReported = false
106
156
 
107
157
  const now = () => window?.performance?.now?.() ?? Date.now()
108
158
  const requestFrame = callback => window?.requestAnimationFrame?.(callback)
@@ -113,6 +163,21 @@ export function createPrtsStartupSequence({
113
163
  else window?.clearTimeout?.(id)
114
164
  }
115
165
 
166
+ function notify(callback, value) {
167
+ try {
168
+ callback(value)
169
+ } catch {
170
+ // Startup remains fail-open when an integration callback fails.
171
+ }
172
+ }
173
+
174
+ function reportActive(active) {
175
+ const next = Boolean(active)
176
+ if (activeReported === next) return
177
+ activeReported = next
178
+ notify(onActiveChange, next)
179
+ }
180
+
116
181
  function restoreInteractivity() {
117
182
  for (const record of inertRecords) {
118
183
  if (!record.node?.isConnected) continue
@@ -135,9 +200,12 @@ export function createPrtsStartupSequence({
135
200
  }
136
201
  overlay?.remove?.()
137
202
  overlay = undefined
203
+ progressFill = undefined
204
+ progressPercent = undefined
138
205
  restoreInteractivity()
139
206
  if (restoreFocus && previousFocus?.isConnected) previousFocus.focus?.()
140
207
  previousFocus = undefined
208
+ reportActive(false)
141
209
  }
142
210
 
143
211
  function updateProgress(progress) {
@@ -145,9 +213,12 @@ export function createPrtsStartupSequence({
145
213
  const safeProgress = Math.max(0, Math.min(100, progress))
146
214
  if (Math.abs(safeProgress - lastProgress) < .01) return
147
215
  lastProgress = safeProgress
148
- overlay.style.setProperty('--prts-startup-progress', (safeProgress / 100).toFixed(4))
149
- const percent = overlay.querySelector('[data-prts-startup-percent]')
150
- if (percent) percent.textContent = `${String(Math.round(safeProgress)).padStart(3, '0')}%`
216
+ if (progressFill) progressFill.style.transform = `scaleX(${(safeProgress / 100).toFixed(4)})`
217
+ const roundedProgress = Math.round(safeProgress)
218
+ if (progressPercent && roundedProgress !== lastRoundedProgress) {
219
+ lastRoundedProgress = roundedProgress
220
+ progressPercent.textContent = `${String(roundedProgress).padStart(3, '0')}%`
221
+ }
151
222
  }
152
223
 
153
224
  function applyStage(name) {
@@ -161,6 +232,10 @@ export function createPrtsStartupSequence({
161
232
  if (label) label.textContent = stage.label
162
233
  if (detail) detail.textContent = stage.detail
163
234
  overlay.setAttribute('aria-label', `P.R.T.S. 启动序列:${stage.detail}`)
235
+ if (name === 'ready' && !readyReported) {
236
+ readyReported = true
237
+ notify(onReady)
238
+ }
164
239
  }
165
240
 
166
241
  function requestStage(name, elapsed) {
@@ -218,7 +293,14 @@ export function createPrtsStartupSequence({
218
293
  }
219
294
 
220
295
  function promoteToTopLayer() {
221
- if (typeof overlay?.showPopover !== 'function') return
296
+ if (!hostHasOpenTopLayer(document, overlay)) {
297
+ overlay?.removeAttribute('popover')
298
+ return
299
+ }
300
+ if (typeof overlay?.showPopover !== 'function') {
301
+ overlay?.removeAttribute('popover')
302
+ return
303
+ }
222
304
  try {
223
305
  overlay.showPopover()
224
306
  } catch {
@@ -266,7 +348,7 @@ export function createPrtsStartupSequence({
266
348
  updateProgress(90)
267
349
  if (elapsed - timeoutStartedAt >= schedule.timeoutHold) beginExit(elapsed, { failOpen: true })
268
350
  } else {
269
- const approachProgress = 90 * easeOutCubic(elapsed / schedule.approach)
351
+ const approachProgress = approachProgressAt(elapsed, schedule.approach)
270
352
  let progress = Math.min(90, approachProgress)
271
353
 
272
354
  if (!hostReady && elapsed >= schedule.timeout) {
@@ -275,9 +357,14 @@ export function createPrtsStartupSequence({
275
357
  if (finishStartedAt === undefined) {
276
358
  finishStartedAt = elapsed
277
359
  finishStartedProgress = progress
360
+ finishStartedVelocity = approachVelocityAt(elapsed, schedule.approach)
278
361
  }
279
- progress = finishStartedProgress
280
- + (100 - finishStartedProgress) * easeOutCubic((elapsed - finishStartedAt) / schedule.finish)
362
+ progress = finishProgressAt(
363
+ finishStartedProgress,
364
+ finishStartedVelocity,
365
+ elapsed - finishStartedAt,
366
+ schedule.finish,
367
+ )
281
368
  if (elapsed - finishStartedAt >= schedule.finish) {
282
369
  progress = 100
283
370
  requestStage('ready', elapsed)
@@ -332,18 +419,24 @@ export function createPrtsStartupSequence({
332
419
  readyStartedAt = undefined
333
420
  exitStartedAt = undefined
334
421
  timeoutStartedAt = undefined
422
+ finishStartedVelocity = 0
335
423
  currentStage = 'boot'
336
424
  pendingStage = undefined
337
425
  copyTransitionStartedAt = undefined
338
426
  lastProgress = -1
427
+ activeReported = false
428
+ readyReported = false
339
429
  useReducedMotion = reducedMotionRequested(reduced)
340
430
 
341
431
  previousFocus = document.activeElement
432
+ lastRoundedProgress = -1
342
433
  document.body.appendChild(overlay)
434
+ progressFill = overlay.querySelector('[data-prts-startup-fill]')
435
+ progressPercent = overlay.querySelector('[data-prts-startup-percent]')
343
436
  promoteToTopLayer()
344
437
  document.documentElement.setAttribute('data-prts-startup-active', '')
345
438
  lockSiblings()
346
- overlay.focus?.({ preventScroll: true })
439
+ reportActive(true)
347
440
  document.addEventListener('visibilitychange', onVisibilityChange)
348
441
 
349
442
  if (useReducedMotion) {
@@ -49,6 +49,40 @@ html[data-dsh-prts] [data-prts-nav-bottom] > button:is(:hover, :focus-visible) {
49
49
  color: #f3f6f7 !important;
50
50
  }
51
51
 
52
+ html[data-dsh-prts] [data-prts-theme-disable]::after {
53
+ content: "";
54
+ position: absolute;
55
+ inset: 8px auto 8px 0;
56
+ width: 2px;
57
+ background: var(--prts-yellow);
58
+ transform: scaleY(0);
59
+ transition: transform 160ms var(--prts-ease-snap);
60
+ }
61
+
62
+ html[data-dsh-prts] [data-prts-theme-disable]:is(:hover, :focus-visible) {
63
+ color: var(--prts-yellow) !important;
64
+ }
65
+
66
+ html[data-dsh-prts] [data-prts-theme-disable]:is(:hover, :focus-visible)::after {
67
+ transform: scaleY(1);
68
+ }
69
+
70
+ html[data-dsh-prts] [data-prts-theme-disable-icon] {
71
+ width: 18px;
72
+ height: 18px;
73
+ fill: none;
74
+ stroke: currentColor;
75
+ stroke-width: 1.7;
76
+ stroke-linecap: round;
77
+ stroke-linejoin: round;
78
+ transition: transform 120ms var(--prts-ease-snap), opacity 120ms ease;
79
+ }
80
+
81
+ html[data-dsh-prts] [data-prts-theme-disable]:active [data-prts-theme-disable-icon] {
82
+ opacity: .72;
83
+ transform: scale(.9);
84
+ }
85
+
52
86
  html[data-dsh-prts] [data-prts-rail-brand][aria-expanded="true"]::after {
53
87
  content: "";
54
88
  position: absolute;
@@ -1,13 +1,12 @@
1
- /* P.R.T.S. startup overlay. Popover promotes it above host dialogs; z-index is the legacy fallback. */
1
+ /* P.R.T.S. startup overlay. Fixed composition is the fast path; Popover is reserved for an active Host top layer. */
2
2
  html[data-dsh-prts] [data-prts-startup] {
3
3
  --prts-startup-bg: #07090b;
4
4
  --prts-startup-ink: #e8edef;
5
5
  --prts-startup-muted: #7d898f;
6
6
  --prts-startup-line: #394349;
7
7
  --prts-startup-accent: #f0c800;
8
- --prts-startup-progress: 0;
9
8
  position: fixed;
10
- z-index: 2147480000;
9
+ z-index: 2147483647;
11
10
  inset: 0;
12
11
  width: 100vw;
13
12
  max-width: none;
@@ -20,10 +19,10 @@ html[data-dsh-prts] [data-prts-startup] {
20
19
  overflow: hidden;
21
20
  isolation: isolate;
22
21
  box-sizing: border-box;
23
- background: transparent;
22
+ background: var(--prts-startup-bg);
24
23
  color: var(--prts-startup-ink);
25
24
  font-family: var(--prts-body, sans-serif);
26
- opacity: 0;
25
+ opacity: 1;
27
26
  outline: 0;
28
27
  pointer-events: auto;
29
28
  transition: opacity 120ms ease;
@@ -44,7 +43,9 @@ html[data-dsh-prts] [data-prts-startup]::after {
44
43
  html[data-dsh-prts] [data-prts-startup]::before { left: 0; }
45
44
  html[data-dsh-prts] [data-prts-startup]::after { right: 0; }
46
45
 
47
- html[data-dsh-prts] [data-prts-startup][data-visible] { opacity: 1; }
46
+ html[data-dsh-prts] [data-prts-startup][data-exiting]:not([data-reduced-motion]) {
47
+ background: transparent;
48
+ }
48
49
 
49
50
  html[data-dsh-prts] [data-prts-startup][data-exiting]::before {
50
51
  transform: translate3d(-101%, 0, 0);
@@ -347,6 +348,7 @@ html[data-dsh-prts] [data-prts-startup-track] {
347
348
  }
348
349
 
349
350
  html[data-dsh-prts] [data-prts-startup-fill] {
351
+ --prts-startup-progress: 0;
350
352
  position: absolute;
351
353
  top: -2px;
352
354
  right: 0;
@@ -356,6 +358,7 @@ html[data-dsh-prts] [data-prts-startup-fill] {
356
358
  transform: scaleX(var(--prts-startup-progress));
357
359
  transform-origin: left center;
358
360
  will-change: transform;
361
+ transition: transform 80ms linear;
359
362
  }
360
363
 
361
364
  html[data-dsh-prts] [data-prts-startup-ticks] {
@@ -407,6 +410,10 @@ html[data-dsh-prts] [data-prts-startup][data-reduced-motion] [data-prts-startup-
407
410
  transition: none;
408
411
  }
409
412
 
413
+ html[data-dsh-prts] [data-prts-startup][data-reduced-motion] [data-prts-startup-fill] {
414
+ transition: none;
415
+ }
416
+
410
417
  html[data-dsh-prts] [data-prts-startup][data-reduced-motion] [data-prts-startup-frame-outer] { transform: rotate(45deg); }
411
418
  html[data-dsh-prts] [data-prts-startup][data-reduced-motion] [data-prts-startup-frame-inner] { transform: rotate(-45deg); }
412
419