@ash-qw/dsh-theme-prts 0.1.99 → 0.1.101
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 +12 -0
- package/lib/client.js +553 -35
- package/package.json +2 -2
- package/src/client/composer-glass-adapter.js +13 -3
- package/src/client/facility-geometry.js +12 -6
- package/src/client/facility-vector.js +44 -7
- package/src/client/index.js +24 -3
- package/src/client/preferences.js +68 -4
- package/src/client/session-lifeline.js +19 -4
- package/src/client/sidebar-control-adapter.js +201 -4
- package/src/client/theme-controller-v2.js +13 -0
- package/src/client/theme-controller.js +13 -0
- package/src/client/theme-settings-workbench.js +193 -6
- package/src/styles/facility-vector.css +320 -18
- package/src/styles/theme-settings-workbench.css +83 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ash-qw/dsh-theme-prts",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.101",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Unofficial Arknights P.R.T.S.-inspired UI theme for DeepSeek Harness Web",
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
],
|
|
60
60
|
"scripts": {
|
|
61
61
|
"build": "node scripts/build.mjs",
|
|
62
|
-
"test": "node --test tests/*.test.mjs",
|
|
62
|
+
"test": "node --test --test-concurrency=1 tests/*.test.mjs",
|
|
63
63
|
"test:e2e": "playwright test",
|
|
64
64
|
"check": "npm run build && npm test",
|
|
65
65
|
"pack:plugin": "npm run check && npm pack",
|
|
@@ -61,6 +61,14 @@ function markPopup(popup, kind) {
|
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
+
function hasVisibleText(element) {
|
|
65
|
+
if (typeof element?.innerText === 'string') return element.innerText.trim().length > 0
|
|
66
|
+
const clone = element?.cloneNode?.(true)
|
|
67
|
+
if (!clone) return false
|
|
68
|
+
for (const hidden of clone.querySelectorAll?.('[hidden], [aria-hidden="true"]') ?? []) hidden.remove()
|
|
69
|
+
return clone.textContent?.trim().length > 0
|
|
70
|
+
}
|
|
71
|
+
|
|
64
72
|
function fallbackComposerForms(document) {
|
|
65
73
|
const forms = new Set()
|
|
66
74
|
for (const root of document?.querySelectorAll?.(FALLBACK_COMPOSER_ROOT_SELECTOR) ?? []) {
|
|
@@ -147,12 +155,14 @@ export function createComposerGlassAdapter({ document, window }) {
|
|
|
147
155
|
for (const trigger of composer.querySelectorAll('button[aria-haspopup="menu"], button[aria-haspopup="listbox"]')) {
|
|
148
156
|
const menu = linkedMenu(document, trigger)
|
|
149
157
|
?? trigger.parentElement?.querySelector('[role="menu"][aria-busy], [role="listbox"][aria-busy]')
|
|
150
|
-
const isModel = trigger.matches('[class*="_7KE1Ra_"]')
|
|
151
|
-
|| menu?.hasAttribute('aria-busy')
|
|
152
158
|
const isCommands = trigger.getAttribute('aria-haspopup') === 'listbox'
|
|
153
159
|
|| trigger.getAttribute('aria-label') === 'Commands'
|
|
154
160
|
|| trigger.matches('[class*="uV2eYG_add"]')
|
|
155
|
-
const
|
|
161
|
+
const isModel = !isCommands && (
|
|
162
|
+
menu?.hasAttribute('aria-busy')
|
|
163
|
+
|| (trigger.getAttribute('aria-haspopup') === 'menu' && hasVisibleText(trigger))
|
|
164
|
+
)
|
|
165
|
+
const kind = isModel ? 'model' : 'action'
|
|
156
166
|
mark(trigger, CONTROL_ATTRIBUTE, kind)
|
|
157
167
|
markPopup(menu, kind)
|
|
158
168
|
}
|
|
@@ -7,6 +7,9 @@ export const FACILITY_GEOMETRY = Object.freeze({
|
|
|
7
7
|
topOpening: 50,
|
|
8
8
|
topInner: 40,
|
|
9
9
|
topDepth: 10 / 3,
|
|
10
|
+
sessionTopOpening: 50,
|
|
11
|
+
sessionTopInner: 36.5,
|
|
12
|
+
sessionTopDepth: 4.5,
|
|
10
13
|
})
|
|
11
14
|
|
|
12
15
|
function number(value) {
|
|
@@ -17,7 +20,7 @@ function point(x, y) {
|
|
|
17
20
|
return `${number(x)} ${number(y)}`
|
|
18
21
|
}
|
|
19
22
|
|
|
20
|
-
export function createFacilityPath({ width, height, topNotch = false } = {}) {
|
|
23
|
+
export function createFacilityPath({ width, height, topNotch = false, sessionNotch = false } = {}) {
|
|
21
24
|
if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 2 || height <= FACILITY_GEOMETRY.sideOpening + 2) return ''
|
|
22
25
|
|
|
23
26
|
const halfStroke = FACILITY_GEOMETRY.stroke / 2
|
|
@@ -29,12 +32,15 @@ export function createFacilityPath({ width, height, topNotch = false } = {}) {
|
|
|
29
32
|
const centerY = height / 2
|
|
30
33
|
const points = [point(left + FACILITY_GEOMETRY.corner, top)]
|
|
31
34
|
|
|
32
|
-
|
|
35
|
+
const topOpening = sessionNotch ? FACILITY_GEOMETRY.sessionTopOpening : FACILITY_GEOMETRY.topOpening
|
|
36
|
+
const topInner = sessionNotch ? FACILITY_GEOMETRY.sessionTopInner : FACILITY_GEOMETRY.topInner
|
|
37
|
+
const topDepth = sessionNotch ? FACILITY_GEOMETRY.sessionTopDepth : FACILITY_GEOMETRY.topDepth
|
|
38
|
+
if (topNotch && width >= topOpening + FACILITY_GEOMETRY.corner * 2 + FACILITY_GEOMETRY.stroke) {
|
|
33
39
|
points.push(
|
|
34
|
-
point(centerX -
|
|
35
|
-
point(centerX -
|
|
36
|
-
point(centerX +
|
|
37
|
-
point(centerX +
|
|
40
|
+
point(centerX - topOpening / 2, top),
|
|
41
|
+
point(centerX - topInner / 2, top + topDepth),
|
|
42
|
+
point(centerX + topInner / 2, top + topDepth),
|
|
43
|
+
point(centerX + topOpening / 2, top),
|
|
38
44
|
)
|
|
39
45
|
}
|
|
40
46
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createFacilityPath } from './facility-geometry.js'
|
|
1
|
+
import { createFacilityPath, FACILITY_GEOMETRY } from './facility-geometry.js'
|
|
2
2
|
import { configureSessionLifeline } from './session-lifeline.js'
|
|
3
3
|
|
|
4
4
|
const SVG_NS = 'http://www.w3.org/2000/svg'
|
|
@@ -123,9 +123,15 @@ function appendSessionPickup(document, graphic) {
|
|
|
123
123
|
'clip-path': `url(#${clipId})`,
|
|
124
124
|
})
|
|
125
125
|
for (const strand of ['back', 'core', 'front']) {
|
|
126
|
-
|
|
126
|
+
const filament = svgNode(document, 'g', {
|
|
127
127
|
'data-prts-session-lifeline-filament': strand,
|
|
128
|
-
})
|
|
128
|
+
})
|
|
129
|
+
for (const layer of ['halo', 'body', 'core']) {
|
|
130
|
+
filament.append(svgNode(document, 'path', {
|
|
131
|
+
'data-prts-session-lifeline-layer': layer,
|
|
132
|
+
}))
|
|
133
|
+
}
|
|
134
|
+
lifeline.append(filament)
|
|
129
135
|
}
|
|
130
136
|
|
|
131
137
|
const pickup = svgNode(document, 'g', {
|
|
@@ -161,6 +167,28 @@ function appendSessionPickup(document, graphic) {
|
|
|
161
167
|
graphic.insertBefore(pickup, graphic.querySelector('[data-prts-facility-layer="outline"]'))
|
|
162
168
|
}
|
|
163
169
|
|
|
170
|
+
function appendSessionStatusUnderlay(document, graphic) {
|
|
171
|
+
const surface = graphic.querySelector('[data-prts-facility-layer="surface"]')
|
|
172
|
+
if (!surface) return
|
|
173
|
+
if (!graphic.querySelector('[data-prts-session-status-underlay]')) {
|
|
174
|
+
graphic.insertBefore(svgNode(document, 'path', {
|
|
175
|
+
'data-prts-session-status-underlay': '',
|
|
176
|
+
'aria-hidden': 'true',
|
|
177
|
+
transform: 'translate(3 2)',
|
|
178
|
+
'shape-rendering': 'geometricPrecision',
|
|
179
|
+
}), surface)
|
|
180
|
+
}
|
|
181
|
+
if (!graphic.querySelector('[data-prts-session-idle-indicator]')) {
|
|
182
|
+
graphic.insertBefore(svgNode(document, 'rect', {
|
|
183
|
+
'data-prts-session-idle-indicator': '',
|
|
184
|
+
'aria-hidden': 'true',
|
|
185
|
+
y: '2.5',
|
|
186
|
+
width: '50',
|
|
187
|
+
height: '2.5',
|
|
188
|
+
}), surface)
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
164
192
|
function clamp(value, min, max) {
|
|
165
193
|
return Math.min(max, Math.max(min, value))
|
|
166
194
|
}
|
|
@@ -173,7 +201,7 @@ function sessionLifelineGeometry(width, height) {
|
|
|
173
201
|
return { start, end, baseline, lift }
|
|
174
202
|
}
|
|
175
203
|
|
|
176
|
-
export function ensureFacilityGraphic(document, owner, { kind, topNotch = false, texture = 'none' } = {}) {
|
|
204
|
+
export function ensureFacilityGraphic(document, owner, { kind, topNotch = false, sessionNotch = false, texture = 'none' } = {}) {
|
|
177
205
|
if (!owner) return null
|
|
178
206
|
let graphic = owner.querySelector?.(':scope > svg[data-prts-facility-svg]')
|
|
179
207
|
if (!graphic) {
|
|
@@ -199,9 +227,13 @@ export function ensureFacilityGraphic(document, owner, { kind, topNotch = false,
|
|
|
199
227
|
owner.insertBefore(graphic, owner.firstChild)
|
|
200
228
|
}
|
|
201
229
|
if (texture === 'silhouette' && !graphic.querySelector('[data-prts-facility-silhouette]')) appendSilhouetteScene(document, graphic)
|
|
202
|
-
if (texture === 'pickup'
|
|
230
|
+
if (texture === 'pickup') {
|
|
231
|
+
appendSessionStatusUnderlay(document, graphic)
|
|
232
|
+
if (!graphic.querySelector('[data-prts-session-pickup]')) appendSessionPickup(document, graphic)
|
|
233
|
+
}
|
|
203
234
|
graphic.setAttribute('data-prts-facility-svg', kind)
|
|
204
235
|
graphic.toggleAttribute('data-prts-top-notch', Boolean(topNotch))
|
|
236
|
+
graphic.toggleAttribute('data-prts-session-notch', Boolean(sessionNotch))
|
|
205
237
|
if (!owner.hasAttribute('data-prts-facility-texture')) owner.setAttribute('data-prts-facility-texture', texture)
|
|
206
238
|
return graphic
|
|
207
239
|
}
|
|
@@ -216,19 +248,24 @@ function setReady(owner, ready) {
|
|
|
216
248
|
export function renderFacilityGraphic(owner, { width, height } = {}) {
|
|
217
249
|
const graphic = owner?.querySelector?.(':scope > svg[data-prts-facility-svg]')
|
|
218
250
|
const topNotch = graphic?.hasAttribute('data-prts-top-notch')
|
|
219
|
-
const
|
|
251
|
+
const sessionNotch = graphic?.hasAttribute('data-prts-session-notch')
|
|
252
|
+
const path = createFacilityPath({ width, height, topNotch, sessionNotch })
|
|
220
253
|
if (!graphic || !path) {
|
|
221
254
|
owner?.removeAttribute?.('data-prts-facility-size')
|
|
222
255
|
setReady(owner, false)
|
|
223
256
|
return false
|
|
224
257
|
}
|
|
225
258
|
|
|
226
|
-
const size = `${Number(width.toFixed(3))}x${Number(height.toFixed(3))}`
|
|
259
|
+
const size = `${Number(width.toFixed(3))}x${Number(height.toFixed(3))}:${topNotch ? 't' : '-'}${sessionNotch ? 's' : '-'}`
|
|
227
260
|
const lifeline = graphic.querySelector('[data-prts-session-lifeline]')
|
|
228
261
|
const lifelineGeometry = lifeline ? sessionLifelineGeometry(width, height) : undefined
|
|
229
262
|
if (owner.getAttribute('data-prts-facility-size') !== size) {
|
|
230
263
|
graphic.setAttribute('viewBox', `0 0 ${Number(width.toFixed(3))} ${Number(height.toFixed(3))}`)
|
|
231
264
|
for (const layer of graphic.querySelectorAll('[data-prts-facility-layer], [data-prts-facility-clip]')) layer.setAttribute('d', path)
|
|
265
|
+
const statusUnderlay = graphic.querySelector('[data-prts-session-status-underlay]')
|
|
266
|
+
statusUnderlay?.setAttribute('d', createFacilityPath({ width, height }))
|
|
267
|
+
const idleIndicator = graphic.querySelector('[data-prts-session-idle-indicator]')
|
|
268
|
+
idleIndicator?.setAttribute('x', String(Number((width / 2 - 25).toFixed(3))))
|
|
232
269
|
const silhouette = graphic.querySelector('[data-prts-facility-silhouette]')
|
|
233
270
|
if (silhouette) {
|
|
234
271
|
const availableHeight = Math.max(1, height - 2)
|
package/src/client/index.js
CHANGED
|
@@ -86,7 +86,12 @@ export function applyPrtsPlugin(ctx, environment) {
|
|
|
86
86
|
const toBottom = createToBottomAdapter({ document, window })
|
|
87
87
|
const conversationControls = createConversationControlAdapter({ document, window })
|
|
88
88
|
const layoutDiagnostics = createLayoutStabilityDiagnostics({ document, window })
|
|
89
|
-
const sidebarControls = createSidebarControlAdapter({
|
|
89
|
+
const sidebarControls = createSidebarControlAdapter({
|
|
90
|
+
document,
|
|
91
|
+
window,
|
|
92
|
+
sessions: contextService(ctx, 'sessions'),
|
|
93
|
+
workspaces: contextService(ctx, 'workspaces'),
|
|
94
|
+
})
|
|
90
95
|
const floatingGlass = createFloatingGlassAdapter({ document, window })
|
|
91
96
|
const adapter = createRc7Adapter({ document })
|
|
92
97
|
const hostGeometry = createHostGeometryAdapter({ document, window })
|
|
@@ -193,8 +198,24 @@ export function applyPrtsPlugin(ctx, environment) {
|
|
|
193
198
|
persistAndApply(updatePreferenceValue(preferences, key, value))
|
|
194
199
|
},
|
|
195
200
|
onPreferencePreview(key, value) {
|
|
196
|
-
if (key
|
|
197
|
-
|
|
201
|
+
if (key === 'particleTraversalSpeed') {
|
|
202
|
+
particleField.update(updatePreferenceValue(preferences, key, value))
|
|
203
|
+
return
|
|
204
|
+
}
|
|
205
|
+
if (key === 'sessionFlowSpeed') {
|
|
206
|
+
const preview = updatePreferenceValue(preferences, key, value)
|
|
207
|
+
document.documentElement.dataset.prtsSessionFlowSpeed = String(preview.sessionFlowSpeed)
|
|
208
|
+
return
|
|
209
|
+
}
|
|
210
|
+
const sessionFlowColorProperties = {
|
|
211
|
+
sessionFlowColorBack: '--prts-session-flow-back',
|
|
212
|
+
sessionFlowColorCore: '--prts-session-flow-core',
|
|
213
|
+
sessionFlowColorFront: '--prts-session-flow-front',
|
|
214
|
+
}
|
|
215
|
+
if (sessionFlowColorProperties[key]) {
|
|
216
|
+
document.documentElement.dataset.prtsSessionFlowPalette = 'custom'
|
|
217
|
+
document.documentElement.style.setProperty(sessionFlowColorProperties[key], String(value))
|
|
218
|
+
}
|
|
198
219
|
},
|
|
199
220
|
onVisualReset() {
|
|
200
221
|
persistAndApply({
|
|
@@ -4,9 +4,20 @@ export const PARTICLE_TRAVERSAL_SPEED_MIN = 0
|
|
|
4
4
|
export const PARTICLE_TRAVERSAL_SPEED_MAX = 2
|
|
5
5
|
export const PARTICLE_TRAVERSAL_SPEED_STEP = 0.25
|
|
6
6
|
export const PARTICLE_TRAVERSAL_SPEED_DEFAULT = 1
|
|
7
|
+
export const SESSION_FLOW_SPEED_MIN = 0.25
|
|
8
|
+
export const SESSION_FLOW_SPEED_MAX = 2
|
|
9
|
+
export const SESSION_FLOW_SPEED_STEP = 0.25
|
|
10
|
+
export const SESSION_FLOW_SPEED_DEFAULT = 1
|
|
11
|
+
|
|
12
|
+
export const SESSION_FLOW_PALETTES = Object.freeze({
|
|
13
|
+
triad: Object.freeze({ back: '#f7ddeb', core: '#65ddb0', front: '#6eb7f3' }),
|
|
14
|
+
rhodes: Object.freeze({ back: '#b9edf4', core: '#5ccddb', front: '#5a9ee6' }),
|
|
15
|
+
amber: Object.freeze({ back: '#ffe6a3', core: '#f0c800', front: '#ee8f42' }),
|
|
16
|
+
alert: Object.freeze({ back: '#ffc0b8', core: '#ee625a', front: '#db5b8c' }),
|
|
17
|
+
})
|
|
7
18
|
|
|
8
19
|
export const DEFAULT_PREFERENCES = Object.freeze({
|
|
9
|
-
version:
|
|
20
|
+
version: 12,
|
|
10
21
|
enabled: false,
|
|
11
22
|
preset: 'standard-tactical',
|
|
12
23
|
texture: 'full',
|
|
@@ -19,6 +30,12 @@ export const DEFAULT_PREFERENCES = Object.freeze({
|
|
|
19
30
|
particlePattern: 'orthogonal',
|
|
20
31
|
railDefaultHidden: false,
|
|
21
32
|
sessionFlow: true,
|
|
33
|
+
sessionGlow: true,
|
|
34
|
+
sessionFlowPalette: 'triad',
|
|
35
|
+
sessionFlowColorBack: SESSION_FLOW_PALETTES.triad.back,
|
|
36
|
+
sessionFlowColorCore: SESSION_FLOW_PALETTES.triad.core,
|
|
37
|
+
sessionFlowColorFront: SESSION_FLOW_PALETTES.triad.front,
|
|
38
|
+
sessionFlowSpeed: SESSION_FLOW_SPEED_DEFAULT,
|
|
22
39
|
conversationStyle: 'native',
|
|
23
40
|
})
|
|
24
41
|
|
|
@@ -61,7 +78,7 @@ export const PREFERENCE_GROUPS = Object.freeze({
|
|
|
61
78
|
material: Object.freeze(['glass']),
|
|
62
79
|
accessibility: Object.freeze(['motion', 'bootAnimation']),
|
|
63
80
|
navigation: Object.freeze(['railDefaultHidden']),
|
|
64
|
-
conversation: Object.freeze(['conversationStyle']),
|
|
81
|
+
conversation: Object.freeze(['sessionFlow', 'sessionGlow', 'sessionFlowPalette', 'sessionFlowColorBack', 'sessionFlowColorCore', 'sessionFlowColorFront', 'sessionFlowSpeed', 'conversationStyle']),
|
|
65
82
|
})
|
|
66
83
|
|
|
67
84
|
const TEXTURES = new Set(['off', 'restrained', 'full'])
|
|
@@ -70,6 +87,8 @@ const MOTIONS = new Set(['system', 'reduced'])
|
|
|
70
87
|
const PARTICLE_DENSITIES = new Set(['sparse', 'light', 'standard', 'dense', 'ultra'])
|
|
71
88
|
const PRESETS = new Set([...Object.keys(VISUAL_PRESETS), 'custom'])
|
|
72
89
|
const CONVERSATION_STYLES = new Set(['native', 'deck-chat'])
|
|
90
|
+
const SESSION_FLOW_PALETTE_IDS = new Set([...Object.keys(SESSION_FLOW_PALETTES), 'custom'])
|
|
91
|
+
const SESSION_FLOW_COLOR_KEYS = new Set(['sessionFlowColorBack', 'sessionFlowColorCore', 'sessionFlowColorFront'])
|
|
73
92
|
const PRESET_LINKED_KEYS = new Set(Object.keys(VISUAL_PRESETS['standard-tactical']))
|
|
74
93
|
|
|
75
94
|
|
|
@@ -99,6 +118,10 @@ function enumOr(value, allowed, fallback) {
|
|
|
99
118
|
return allowed.has(value) ? value : fallback
|
|
100
119
|
}
|
|
101
120
|
|
|
121
|
+
function colorOr(value, fallback) {
|
|
122
|
+
return typeof value === 'string' && /^#[\da-f]{6}$/i.test(value) ? value.toLowerCase() : fallback
|
|
123
|
+
}
|
|
124
|
+
|
|
102
125
|
function particleTraversalSpeedOr(value, fallback) {
|
|
103
126
|
const numeric = Number(value)
|
|
104
127
|
if (!Number.isFinite(numeric)) return fallback
|
|
@@ -106,6 +129,13 @@ function particleTraversalSpeedOr(value, fallback) {
|
|
|
106
129
|
return Math.round(clamped / PARTICLE_TRAVERSAL_SPEED_STEP) * PARTICLE_TRAVERSAL_SPEED_STEP
|
|
107
130
|
}
|
|
108
131
|
|
|
132
|
+
function sessionFlowSpeedOr(value, fallback) {
|
|
133
|
+
const numeric = Number(value)
|
|
134
|
+
if (!Number.isFinite(numeric)) return fallback
|
|
135
|
+
const clamped = Math.min(SESSION_FLOW_SPEED_MAX, Math.max(SESSION_FLOW_SPEED_MIN, numeric))
|
|
136
|
+
return Math.round(clamped / SESSION_FLOW_SPEED_STEP) * SESSION_FLOW_SPEED_STEP
|
|
137
|
+
}
|
|
138
|
+
|
|
109
139
|
function normalizeGlass(input) {
|
|
110
140
|
if (input.glassEnabled === false || input.glass === 'off') return 'off'
|
|
111
141
|
if (input.glass === 'liquid') return 'clear'
|
|
@@ -132,8 +162,16 @@ export function normalizePreferences(value) {
|
|
|
132
162
|
: input.preset
|
|
133
163
|
const particlePreset = VISUAL_PRESETS[input.preset] ?? DEFAULT_PREFERENCES
|
|
134
164
|
const legacyDensities = legacyParticleDensities(input)
|
|
165
|
+
const sessionFlowPalette = enumOr(input.sessionFlowPalette, SESSION_FLOW_PALETTE_IDS, DEFAULT_PREFERENCES.sessionFlowPalette)
|
|
166
|
+
const paletteColors = SESSION_FLOW_PALETTES[sessionFlowPalette]
|
|
167
|
+
const customColors = {
|
|
168
|
+
back: colorOr(input.sessionFlowColorBack, DEFAULT_PREFERENCES.sessionFlowColorBack),
|
|
169
|
+
core: colorOr(input.sessionFlowColorCore, DEFAULT_PREFERENCES.sessionFlowColorCore),
|
|
170
|
+
front: colorOr(input.sessionFlowColorFront, DEFAULT_PREFERENCES.sessionFlowColorFront),
|
|
171
|
+
}
|
|
172
|
+
const sessionFlowColors = paletteColors ?? customColors
|
|
135
173
|
const normalized = {
|
|
136
|
-
version:
|
|
174
|
+
version: 12,
|
|
137
175
|
enabled: booleanOr(input.enabled, DEFAULT_PREFERENCES.enabled),
|
|
138
176
|
preset: enumOr(migratedPreset, PRESETS, DEFAULT_PREFERENCES.preset),
|
|
139
177
|
texture: enumOr(input.texture, TEXTURES, DEFAULT_PREFERENCES.texture),
|
|
@@ -149,6 +187,12 @@ export function normalizePreferences(value) {
|
|
|
149
187
|
particlePattern: 'orthogonal',
|
|
150
188
|
railDefaultHidden: booleanOr(input.railDefaultHidden, DEFAULT_PREFERENCES.railDefaultHidden),
|
|
151
189
|
sessionFlow: booleanOr(input.sessionFlow, DEFAULT_PREFERENCES.sessionFlow),
|
|
190
|
+
sessionGlow: booleanOr(input.sessionGlow, DEFAULT_PREFERENCES.sessionGlow),
|
|
191
|
+
sessionFlowPalette,
|
|
192
|
+
sessionFlowColorBack: sessionFlowColors.back,
|
|
193
|
+
sessionFlowColorCore: sessionFlowColors.core,
|
|
194
|
+
sessionFlowColorFront: sessionFlowColors.front,
|
|
195
|
+
sessionFlowSpeed: sessionFlowSpeedOr(input.sessionFlowSpeed, DEFAULT_PREFERENCES.sessionFlowSpeed),
|
|
152
196
|
conversationStyle: enumOr(input.conversationStyle, CONVERSATION_STYLES, DEFAULT_PREFERENCES.conversationStyle),
|
|
153
197
|
}
|
|
154
198
|
normalized.preset = matchedVisualPreset(normalized)
|
|
@@ -175,6 +219,26 @@ export function updatePreferenceValue(value, key, nextValue) {
|
|
|
175
219
|
if (key === 'preset') return applyVisualPreset(value, nextValue)
|
|
176
220
|
if (key === 'particleDetail') return applyParticleDetail(value, nextValue)
|
|
177
221
|
const current = normalizePreferences(value)
|
|
222
|
+
if (key === 'sessionFlowPalette') {
|
|
223
|
+
const palette = enumOr(nextValue, SESSION_FLOW_PALETTE_IDS, current.sessionFlowPalette)
|
|
224
|
+
const colors = SESSION_FLOW_PALETTES[palette]
|
|
225
|
+
return normalizePreferences({
|
|
226
|
+
...current,
|
|
227
|
+
sessionFlowPalette: palette,
|
|
228
|
+
...(colors ? {
|
|
229
|
+
sessionFlowColorBack: colors.back,
|
|
230
|
+
sessionFlowColorCore: colors.core,
|
|
231
|
+
sessionFlowColorFront: colors.front,
|
|
232
|
+
} : {}),
|
|
233
|
+
})
|
|
234
|
+
}
|
|
235
|
+
if (SESSION_FLOW_COLOR_KEYS.has(key)) {
|
|
236
|
+
return normalizePreferences({
|
|
237
|
+
...current,
|
|
238
|
+
sessionFlowPalette: 'custom',
|
|
239
|
+
[key]: colorOr(nextValue, current[key]),
|
|
240
|
+
})
|
|
241
|
+
}
|
|
178
242
|
if (key === 'conversationParticleDensity' || key === 'heroParticleDensity') {
|
|
179
243
|
const density = enumOr(nextValue, PARTICLE_DENSITIES, current[key])
|
|
180
244
|
return normalizePreferences({ ...current, [key]: density })
|
|
@@ -196,7 +260,7 @@ export function loadPreferences(storage) {
|
|
|
196
260
|
const raw = storage?.getItem(PRTS_STORAGE_KEY)
|
|
197
261
|
if (raw === null || raw === undefined) return { ...DEFAULT_PREFERENCES }
|
|
198
262
|
const parsed = JSON.parse(raw)
|
|
199
|
-
if (![1, 2, 3, 4, 5, 6, 7, 8, 9].includes(parsed?.version)) return { ...DEFAULT_PREFERENCES }
|
|
263
|
+
if (![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12].includes(parsed?.version)) return { ...DEFAULT_PREFERENCES }
|
|
200
264
|
return normalizePreferences(parsed)
|
|
201
265
|
} catch {
|
|
202
266
|
return { ...DEFAULT_PREFERENCES }
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const HOVER_SETTLE = 180
|
|
2
2
|
const STATIC_TIME = 2.4
|
|
3
3
|
const POINT_COUNT = 9
|
|
4
|
+
const DEFAULT_FLOW_SPEED = 1
|
|
4
5
|
|
|
5
6
|
function clamp(value, min, max) {
|
|
6
7
|
return Math.min(max, Math.max(min, value))
|
|
@@ -25,6 +26,15 @@ function filamentNodes(lifeline) {
|
|
|
25
26
|
return Array.from(lifeline.querySelectorAll?.('[data-prts-session-lifeline-filament]') ?? [])
|
|
26
27
|
}
|
|
27
28
|
|
|
29
|
+
function setFilamentPath(filament, path) {
|
|
30
|
+
const layers = Array.from(filament?.querySelectorAll?.('[data-prts-session-lifeline-layer]') ?? [])
|
|
31
|
+
if (layers.length === 0) {
|
|
32
|
+
filament?.setAttribute?.('d', path)
|
|
33
|
+
return
|
|
34
|
+
}
|
|
35
|
+
layers.forEach(layer => layer.setAttribute('d', path))
|
|
36
|
+
}
|
|
37
|
+
|
|
28
38
|
function createSmoothPath(points) {
|
|
29
39
|
if (points.length < 2) return ''
|
|
30
40
|
const commands = [`M ${number(points[0].x)} ${number(points[0].y)}`]
|
|
@@ -91,7 +101,7 @@ export function configureSessionLifeline(lifeline, { start, end, baseline, lift
|
|
|
91
101
|
lifeline.setAttribute('data-prts-lifeline-lift', String(number(lift)))
|
|
92
102
|
filamentNodes(lifeline).forEach((filament, index, filaments) => {
|
|
93
103
|
const strand = filaments.length === 1 ? 0 : index - (filaments.length - 1) / 2
|
|
94
|
-
filament
|
|
104
|
+
setFilamentPath(filament, createSessionLifelinePath(geometry, STATIC_TIME, 1, strand))
|
|
95
105
|
})
|
|
96
106
|
return true
|
|
97
107
|
}
|
|
@@ -119,7 +129,7 @@ export function createSessionLifelineAnimator({ document, window }) {
|
|
|
119
129
|
if (!geometry || filaments.length === 0) return false
|
|
120
130
|
filaments.forEach((filament, index) => {
|
|
121
131
|
const strand = filaments.length === 1 ? 0 : index - (filaments.length - 1) / 2
|
|
122
|
-
filament
|
|
132
|
+
setFilamentPath(filament, createSessionLifelinePath(geometry, flowTime, strength, strand))
|
|
123
133
|
})
|
|
124
134
|
return true
|
|
125
135
|
}
|
|
@@ -136,6 +146,11 @@ export function createSessionLifelineAnimator({ document, window }) {
|
|
|
136
146
|
)
|
|
137
147
|
}
|
|
138
148
|
|
|
149
|
+
function currentFlowSpeed() {
|
|
150
|
+
const value = Number(document?.documentElement?.dataset?.prtsSessionFlowSpeed)
|
|
151
|
+
return Number.isFinite(value) ? clamp(value, .25, 2) : DEFAULT_FLOW_SPEED
|
|
152
|
+
}
|
|
153
|
+
|
|
139
154
|
function cancel() {
|
|
140
155
|
if (frame !== undefined) cancelFrame?.(frame)
|
|
141
156
|
frame = undefined
|
|
@@ -161,7 +176,7 @@ export function createSessionLifelineAnimator({ document, window }) {
|
|
|
161
176
|
if (lastTime === undefined) lastTime = now
|
|
162
177
|
const elapsed = Math.min(100, Math.max(0, now - lastTime))
|
|
163
178
|
lastTime = now
|
|
164
|
-
flowTime += elapsed / 1000
|
|
179
|
+
flowTime += elapsed / 1000 * currentFlowSpeed()
|
|
165
180
|
const target = hovered ? 0 : 1
|
|
166
181
|
if (strength !== target) {
|
|
167
182
|
const step = elapsed / HOVER_SETTLE
|
|
@@ -212,7 +227,7 @@ export function createSessionLifelineAnimator({ document, window }) {
|
|
|
212
227
|
motionObserver = new MotionObserver(syncMotion)
|
|
213
228
|
motionObserver.observe(document.documentElement, {
|
|
214
229
|
attributes: true,
|
|
215
|
-
attributeFilter: ['data-prts-motion', 'data-prts-session-flow'],
|
|
230
|
+
attributeFilter: ['data-prts-motion', 'data-prts-session-flow', 'data-prts-session-flow-speed'],
|
|
216
231
|
})
|
|
217
232
|
}
|
|
218
233
|
}
|