@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/CHANGELOG.md +7 -0
- package/lib/client.js +161 -31
- package/package.json +1 -1
- package/src/client/composer-glass-adapter.js +26 -9
- package/src/client/index.js +18 -4
- package/src/client/operations-shell-v2.js +4 -1
- package/src/client/particle-field-adapter.js +23 -11
- package/src/client/preferences.js +1 -1
- package/src/client/startup-sequence.js +102 -9
- package/src/styles/stabilization.css +34 -0
- package/src/styles/startup-sequence.css +13 -6
package/package.json
CHANGED
|
@@ -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 (
|
|
109
|
-
|
|
110
|
-
|
|
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)
|
package/src/client/index.js
CHANGED
|
@@ -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 (
|
|
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="
|
|
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
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
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: '
|
|
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. 启动序列"
|
|
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
|
-
|
|
149
|
-
const
|
|
150
|
-
if (
|
|
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 (
|
|
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 =
|
|
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 =
|
|
280
|
-
|
|
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
|
-
|
|
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.
|
|
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:
|
|
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:
|
|
22
|
+
background: var(--prts-startup-bg);
|
|
24
23
|
color: var(--prts-startup-ink);
|
|
25
24
|
font-family: var(--prts-body, sans-serif);
|
|
26
|
-
opacity:
|
|
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-
|
|
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
|
|