@ash-qw/dsh-theme-prts 0.1.91 → 0.1.93
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 +31 -0
- package/README.en.md +23 -35
- package/README.md +23 -37
- package/lib/client.js +1018 -50
- package/package.json +15 -3
- package/src/client/conversation-scale-adapter.js +77 -0
- package/src/client/index.js +18 -3
- package/src/client/operations-shell-v2.js +214 -16
- package/src/client/particle-field-adapter.js +89 -9
- package/src/client/preferences.js +24 -5
- package/src/client/theme-controller-v2.js +609 -16
- package/src/client/theme-settings-workbench.js +69 -2
- package/src/styles/prts.css +362 -5
- package/src/styles/theme-settings-workbench.css +17 -0
|
@@ -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() {}, inspect: () => empty, dispose() {} }
|
|
329
|
+
if (!document || !window) return { update() {}, setSchemeTransitionActive() {}, inspect: () => empty, dispose() {} }
|
|
330
330
|
|
|
331
331
|
let preferences = {}
|
|
332
332
|
let operation
|
|
@@ -378,6 +378,7 @@ export function createParticleFieldAdapter({ document, window, emblem = '', embl
|
|
|
378
378
|
let side = -1
|
|
379
379
|
let segment = 0
|
|
380
380
|
let transition
|
|
381
|
+
let traversalOffset = 0
|
|
381
382
|
let pendingSegment
|
|
382
383
|
let transitionStartedAt
|
|
383
384
|
let densityTransition
|
|
@@ -394,6 +395,7 @@ export function createParticleFieldAdapter({ document, window, emblem = '', embl
|
|
|
394
395
|
let targetPrewarmQueue = []
|
|
395
396
|
let pauseStartedAt
|
|
396
397
|
let hydrationPaused = false
|
|
398
|
+
let schemeTransitionPaused = false
|
|
397
399
|
let hydrationTimer
|
|
398
400
|
let hydrationFrame
|
|
399
401
|
let hydrationStableFrames = 0
|
|
@@ -427,7 +429,7 @@ export function createParticleFieldAdapter({ document, window, emblem = '', embl
|
|
|
427
429
|
: preferences.conversationParticleDensity
|
|
428
430
|
const densityProfile = density => PARTICLE_DENSITY_PROFILES[density] ?? PARTICLE_DENSITY_PROFILES.standard
|
|
429
431
|
const particleRadius = density => densityProfile(density ?? activeDensity()).radius
|
|
430
|
-
const canRenderFrame = () => documentVisible && canvasVisible && !hydrationPaused && !resizing
|
|
432
|
+
const canRenderFrame = () => !disposed && documentVisible && canvasVisible && !hydrationPaused && !schemeTransitionPaused && !resizing
|
|
431
433
|
const layoutWidth = () => width
|
|
432
434
|
const layoutHeight = () => height
|
|
433
435
|
const layoutShiftX = () => 0
|
|
@@ -554,11 +556,50 @@ export function createParticleFieldAdapter({ document, window, emblem = '', embl
|
|
|
554
556
|
}
|
|
555
557
|
|
|
556
558
|
|
|
559
|
+
function particleTraversalSpeed(value = preferences) {
|
|
560
|
+
const numeric = Number(value?.particleTraversalSpeed)
|
|
561
|
+
return Number.isFinite(numeric) ? clamp(numeric, 0, 2) : 1
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
function currentScrollTop() {
|
|
565
|
+
return Number(scroller?.scrollTop) || 0
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
function traversalViewport() {
|
|
569
|
+
return Math.max(1, Number(scroller?.clientHeight) || height)
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
function traversalCoordinate(value = preferences) {
|
|
573
|
+
return traversalOffset + currentScrollTop() * particleTraversalSpeed(value) / traversalViewport()
|
|
574
|
+
}
|
|
575
|
+
|
|
557
576
|
function scrollProgress() {
|
|
558
577
|
if (!scroller) return 0
|
|
559
|
-
const
|
|
560
|
-
|
|
561
|
-
|
|
578
|
+
const coordinate = traversalCoordinate()
|
|
579
|
+
return coordinate - Math.floor(coordinate)
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
function restoreTraversalFromScroll() {
|
|
583
|
+
traversalOffset = 0
|
|
584
|
+
const coordinate = traversalCoordinate()
|
|
585
|
+
segment = Math.floor(coordinate)
|
|
586
|
+
side = sideForSegment(segment)
|
|
587
|
+
pendingSegment = undefined
|
|
588
|
+
transition = undefined
|
|
589
|
+
transitionStartedAt = undefined
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
function reanchorTraversal(coordinate = traversalCoordinate()) {
|
|
593
|
+
if (transition) {
|
|
594
|
+
const targetSegment = transition.targetSegment
|
|
595
|
+
side = sideForSegment(targetSegment)
|
|
596
|
+
transition = undefined
|
|
597
|
+
transitionStartedAt = undefined
|
|
598
|
+
commitTransitionTargets(targetSegment, true)
|
|
599
|
+
}
|
|
600
|
+
pendingSegment = undefined
|
|
601
|
+
traversalOffset = coordinate - currentScrollTop() * particleTraversalSpeed() / traversalViewport()
|
|
602
|
+
if (particles.length) snapParticlesToCurrentLayout()
|
|
562
603
|
}
|
|
563
604
|
|
|
564
605
|
function anchorFor(sideValue, verticalProgress) {
|
|
@@ -1373,6 +1414,7 @@ export function createParticleFieldAdapter({ document, window, emblem = '', embl
|
|
|
1373
1414
|
return
|
|
1374
1415
|
}
|
|
1375
1416
|
const targetSide = sideForSegment(targetSegment)
|
|
1417
|
+
const direction = targetSegment > segment ? 1 : -1
|
|
1376
1418
|
if (targetSide === side || motionReduced() || phoneStatic()) {
|
|
1377
1419
|
side = targetSide
|
|
1378
1420
|
transition = undefined
|
|
@@ -1389,7 +1431,7 @@ export function createParticleFieldAdapter({ document, window, emblem = '', embl
|
|
|
1389
1431
|
...population,
|
|
1390
1432
|
fromSide: side,
|
|
1391
1433
|
toSide: targetSide,
|
|
1392
|
-
direction
|
|
1434
|
+
direction,
|
|
1393
1435
|
targetSegment,
|
|
1394
1436
|
progress: 0,
|
|
1395
1437
|
}
|
|
@@ -1398,9 +1440,27 @@ export function createParticleFieldAdapter({ document, window, emblem = '', embl
|
|
|
1398
1440
|
scheduleFrame()
|
|
1399
1441
|
}
|
|
1400
1442
|
|
|
1443
|
+
function retargetTransition(targetSegment) {
|
|
1444
|
+
if (!transition || targetSegment === transition.targetSegment) return
|
|
1445
|
+
const population = prepareTransitionTargets(targetSegment)
|
|
1446
|
+
if (!population.toCount) {
|
|
1447
|
+
reportState({ phase: 'error', error: '徽记资源异常' })
|
|
1448
|
+
return
|
|
1449
|
+
}
|
|
1450
|
+
transition = {
|
|
1451
|
+
...transition,
|
|
1452
|
+
...population,
|
|
1453
|
+
toSide: sideForSegment(targetSegment),
|
|
1454
|
+
direction: targetSegment >= segment ? 1 : -1,
|
|
1455
|
+
targetSegment,
|
|
1456
|
+
}
|
|
1457
|
+
pendingSegment = undefined
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1401
1460
|
function resetTraversal() {
|
|
1402
1461
|
side = -1
|
|
1403
1462
|
segment = 0
|
|
1463
|
+
traversalOffset = -currentScrollTop() * particleTraversalSpeed() / traversalViewport()
|
|
1404
1464
|
pendingSegment = undefined
|
|
1405
1465
|
queuedDensityCount = undefined
|
|
1406
1466
|
queuedTargetRefresh = false
|
|
@@ -1430,6 +1490,9 @@ export function createParticleFieldAdapter({ document, window, emblem = '', embl
|
|
|
1430
1490
|
heroMarker = undefined
|
|
1431
1491
|
heroActive = false
|
|
1432
1492
|
scroller?.removeAttribute('data-prts-hero-active')
|
|
1493
|
+
side = -1
|
|
1494
|
+
segment = 0
|
|
1495
|
+
traversalOffset = -currentScrollTop() * particleTraversalSpeed() / traversalViewport()
|
|
1433
1496
|
pendingSegment = undefined
|
|
1434
1497
|
queuedDensityCount = undefined
|
|
1435
1498
|
queuedTargetRefresh = false
|
|
@@ -1474,9 +1537,8 @@ export function createParticleFieldAdapter({ document, window, emblem = '', embl
|
|
|
1474
1537
|
scheduleFrame()
|
|
1475
1538
|
return
|
|
1476
1539
|
}
|
|
1477
|
-
const
|
|
1478
|
-
|
|
1479
|
-
if (transition) pendingSegment = desiredSegment
|
|
1540
|
+
const desiredSegment = Math.floor(traversalCoordinate())
|
|
1541
|
+
if (transition) retargetTransition(desiredSegment)
|
|
1480
1542
|
else if (desiredSegment !== segment) startTransition(desiredSegment)
|
|
1481
1543
|
scheduleFrame()
|
|
1482
1544
|
}
|
|
@@ -1649,6 +1711,7 @@ export function createParticleFieldAdapter({ document, window, emblem = '', embl
|
|
|
1649
1711
|
targetCache.clear()
|
|
1650
1712
|
initializeFieldSize()
|
|
1651
1713
|
syncHeroState()
|
|
1714
|
+
if (!heroActive) restoreTraversalFromScroll()
|
|
1652
1715
|
if (!particles.length && maskRecords.length) buildParticles()
|
|
1653
1716
|
if (context && particles.length) {
|
|
1654
1717
|
syncCanvasBackingStore()
|
|
@@ -1714,8 +1777,19 @@ export function createParticleFieldAdapter({ document, window, emblem = '', embl
|
|
|
1714
1777
|
if (!mediaQuery?.addEventListener) mediaQuery?.addListener?.(mediaListener)
|
|
1715
1778
|
document.addEventListener?.('visibilitychange', onVisibilityChange)
|
|
1716
1779
|
return {
|
|
1780
|
+
setSchemeTransitionActive(active) {
|
|
1781
|
+
const next = Boolean(active)
|
|
1782
|
+
if (schemeTransitionPaused === next) return
|
|
1783
|
+
schemeTransitionPaused = next
|
|
1784
|
+
updateFrameGate()
|
|
1785
|
+
},
|
|
1717
1786
|
update(nextPreferences = {}) {
|
|
1718
1787
|
const previousPreferences = preferences
|
|
1788
|
+
const traversalSpeedChanged = previousPreferences.particleTraversalSpeed !== undefined
|
|
1789
|
+
&& particleTraversalSpeed(previousPreferences) !== particleTraversalSpeed(nextPreferences)
|
|
1790
|
+
const preservedTraversalCoordinate = traversalSpeedChanged && scroller && !heroActive
|
|
1791
|
+
? traversalCoordinate(previousPreferences)
|
|
1792
|
+
: undefined
|
|
1719
1793
|
preferences = nextPreferences
|
|
1720
1794
|
disposed = false
|
|
1721
1795
|
if (!preferences.enabled) {
|
|
@@ -1734,6 +1808,7 @@ export function createParticleFieldAdapter({ document, window, emblem = '', embl
|
|
|
1734
1808
|
const activeDensityChanged = previousActiveDensity !== undefined && previousActiveDensity !== activeDensity()
|
|
1735
1809
|
reportState({ requestedPattern: 'orthogonal' })
|
|
1736
1810
|
if (particles.length && activeDensityChanged) scheduleDensityReassembly(targetParticleCount(), true)
|
|
1811
|
+
if (preservedTraversalCoordinate !== undefined && scroller && !heroActive) reanchorTraversal(preservedTraversalCoordinate)
|
|
1737
1812
|
if (!mutationObserver && window.MutationObserver && document.body) {
|
|
1738
1813
|
const observationRoot = document.querySelector('[data-slot="root"]')
|
|
1739
1814
|
?? document.querySelector('[data-prts-region="frame"]')
|
|
@@ -1771,6 +1846,10 @@ export function createParticleFieldAdapter({ document, window, emblem = '', embl
|
|
|
1771
1846
|
emblemKey: (heroActive || transition?.fromHero ? heroEmblem() : emblemForSegment(segment))?.key,
|
|
1772
1847
|
nextEmblemKey: transition ? emblemForSegment(transition.targetSegment)?.key : null,
|
|
1773
1848
|
segment,
|
|
1849
|
+
traversalSpeed: particleTraversalSpeed(),
|
|
1850
|
+
traversalOffset,
|
|
1851
|
+
traversalCoordinate: traversalCoordinate(),
|
|
1852
|
+
traversalProgress: scrollProgress(),
|
|
1774
1853
|
transition: transition ? {
|
|
1775
1854
|
fromSide: transition.fromSide,
|
|
1776
1855
|
toSide: transition.toSide,
|
|
@@ -1787,6 +1866,7 @@ export function createParticleFieldAdapter({ document, window, emblem = '', embl
|
|
|
1787
1866
|
firstFrameReady: canvas?.hasAttribute('data-prts-particle-ready') === true,
|
|
1788
1867
|
hydrationPaused,
|
|
1789
1868
|
sleeping: Boolean(context && particles.length && frame === undefined && canRenderFrame() && !motionReduced() && !phoneStatic()),
|
|
1869
|
+
schemeTransitionPaused,
|
|
1790
1870
|
pointer: {
|
|
1791
1871
|
active: pointerActive,
|
|
1792
1872
|
clientX: pointerClientX,
|
|
@@ -10,8 +10,13 @@ export const CONVERSATION_SCALE_FOCUS_CONTRAST_MAX = 100
|
|
|
10
10
|
export const CONVERSATION_SCALE_FOCUS_CONTRAST_STEP = 10
|
|
11
11
|
export const CONVERSATION_SCALE_FOCUS_CONTRAST_DEFAULT = 70
|
|
12
12
|
|
|
13
|
+
export const PARTICLE_TRAVERSAL_SPEED_MIN = 0
|
|
14
|
+
export const PARTICLE_TRAVERSAL_SPEED_MAX = 2
|
|
15
|
+
export const PARTICLE_TRAVERSAL_SPEED_STEP = 0.25
|
|
16
|
+
export const PARTICLE_TRAVERSAL_SPEED_DEFAULT = 1
|
|
17
|
+
|
|
13
18
|
export const DEFAULT_PREFERENCES = Object.freeze({
|
|
14
|
-
version:
|
|
19
|
+
version: 8,
|
|
15
20
|
enabled: false,
|
|
16
21
|
preset: 'standard-tactical',
|
|
17
22
|
texture: 'full',
|
|
@@ -20,7 +25,9 @@ export const DEFAULT_PREFERENCES = Object.freeze({
|
|
|
20
25
|
bootAnimation: true,
|
|
21
26
|
conversationParticleDensity: 'sparse',
|
|
22
27
|
heroParticleDensity: 'light',
|
|
28
|
+
particleTraversalSpeed: PARTICLE_TRAVERSAL_SPEED_DEFAULT,
|
|
23
29
|
particlePattern: 'orthogonal',
|
|
30
|
+
railDefaultHidden: false,
|
|
24
31
|
conversationScaleMaxDistance: CONVERSATION_SCALE_DISTANCE_DEFAULT,
|
|
25
32
|
conversationScaleFocusContrast: CONVERSATION_SCALE_FOCUS_CONTRAST_DEFAULT,
|
|
26
33
|
})
|
|
@@ -60,10 +67,10 @@ export const PARTICLE_DETAIL_LEVELS = Object.freeze({
|
|
|
60
67
|
})
|
|
61
68
|
export const PREFERENCE_GROUPS = Object.freeze({
|
|
62
69
|
background: Object.freeze(['texture']),
|
|
63
|
-
particles: Object.freeze(['conversationParticleDensity', 'heroParticleDensity']),
|
|
70
|
+
particles: Object.freeze(['conversationParticleDensity', 'heroParticleDensity', 'particleTraversalSpeed']),
|
|
64
71
|
material: Object.freeze(['glass']),
|
|
65
72
|
accessibility: Object.freeze(['motion', 'bootAnimation']),
|
|
66
|
-
navigation: Object.freeze(['conversationScaleMaxDistance', 'conversationScaleFocusContrast']),
|
|
73
|
+
navigation: Object.freeze(['railDefaultHidden', 'conversationScaleMaxDistance', 'conversationScaleFocusContrast']),
|
|
67
74
|
})
|
|
68
75
|
|
|
69
76
|
const TEXTURES = new Set(['off', 'restrained', 'full'])
|
|
@@ -114,6 +121,13 @@ function scaleFocusContrastOr(value, fallback) {
|
|
|
114
121
|
return Math.round(clamped / CONVERSATION_SCALE_FOCUS_CONTRAST_STEP) * CONVERSATION_SCALE_FOCUS_CONTRAST_STEP
|
|
115
122
|
}
|
|
116
123
|
|
|
124
|
+
function particleTraversalSpeedOr(value, fallback) {
|
|
125
|
+
const numeric = Number(value)
|
|
126
|
+
if (!Number.isFinite(numeric)) return fallback
|
|
127
|
+
const clamped = Math.min(PARTICLE_TRAVERSAL_SPEED_MAX, Math.max(PARTICLE_TRAVERSAL_SPEED_MIN, numeric))
|
|
128
|
+
return Math.round(clamped / PARTICLE_TRAVERSAL_SPEED_STEP) * PARTICLE_TRAVERSAL_SPEED_STEP
|
|
129
|
+
}
|
|
130
|
+
|
|
117
131
|
function normalizeGlass(input) {
|
|
118
132
|
if (input.glassEnabled === false || input.glass === 'off') return 'off'
|
|
119
133
|
if (input.glass === 'liquid') return 'clear'
|
|
@@ -141,7 +155,7 @@ export function normalizePreferences(value) {
|
|
|
141
155
|
const particlePreset = VISUAL_PRESETS[input.preset] ?? DEFAULT_PREFERENCES
|
|
142
156
|
const legacyDensities = legacyParticleDensities(input)
|
|
143
157
|
const normalized = {
|
|
144
|
-
version:
|
|
158
|
+
version: 8,
|
|
145
159
|
enabled: booleanOr(input.enabled, DEFAULT_PREFERENCES.enabled),
|
|
146
160
|
preset: enumOr(migratedPreset, PRESETS, DEFAULT_PREFERENCES.preset),
|
|
147
161
|
texture: enumOr(input.texture, TEXTURES, DEFAULT_PREFERENCES.texture),
|
|
@@ -150,7 +164,12 @@ export function normalizePreferences(value) {
|
|
|
150
164
|
bootAnimation: booleanOr(input.bootAnimation, DEFAULT_PREFERENCES.bootAnimation),
|
|
151
165
|
conversationParticleDensity: enumOr(legacyDensities.conversation, PARTICLE_DENSITIES, particlePreset.conversationParticleDensity),
|
|
152
166
|
heroParticleDensity: enumOr(legacyDensities.hero, PARTICLE_DENSITIES, particlePreset.heroParticleDensity),
|
|
167
|
+
particleTraversalSpeed: particleTraversalSpeedOr(
|
|
168
|
+
input.particleTraversalSpeed,
|
|
169
|
+
DEFAULT_PREFERENCES.particleTraversalSpeed,
|
|
170
|
+
),
|
|
153
171
|
particlePattern: 'orthogonal',
|
|
172
|
+
railDefaultHidden: booleanOr(input.railDefaultHidden, DEFAULT_PREFERENCES.railDefaultHidden),
|
|
154
173
|
conversationScaleMaxDistance: scaleDistanceOr(
|
|
155
174
|
input.conversationScaleMaxDistance,
|
|
156
175
|
DEFAULT_PREFERENCES.conversationScaleMaxDistance,
|
|
@@ -205,7 +224,7 @@ export function loadPreferences(storage) {
|
|
|
205
224
|
const raw = storage?.getItem(PRTS_STORAGE_KEY)
|
|
206
225
|
if (raw === null || raw === undefined) return { ...DEFAULT_PREFERENCES }
|
|
207
226
|
const parsed = JSON.parse(raw)
|
|
208
|
-
if (![1, 2, 3, 4, 5, 6].includes(parsed?.version)) return { ...DEFAULT_PREFERENCES }
|
|
227
|
+
if (![1, 2, 3, 4, 5, 6, 7, 8].includes(parsed?.version)) return { ...DEFAULT_PREFERENCES }
|
|
209
228
|
return normalizePreferences(parsed)
|
|
210
229
|
} catch {
|
|
211
230
|
return { ...DEFAULT_PREFERENCES }
|