@dotcraft/avatar 0.6.4 → 0.6.5

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.
Files changed (32) hide show
  1. package/dist/composer/ComposerMascot.d.ts.map +1 -1
  2. package/dist/composer/ComposerMascot.js +32 -106
  3. package/dist/composer/ComposerMascot.js.map +1 -1
  4. package/dist/composer/MascotIdleStage.d.ts +11 -0
  5. package/dist/composer/MascotIdleStage.d.ts.map +1 -0
  6. package/dist/composer/MascotIdleStage.js +9 -0
  7. package/dist/composer/MascotIdleStage.js.map +1 -0
  8. package/dist/composer/constants.d.ts +12 -1
  9. package/dist/composer/constants.d.ts.map +1 -1
  10. package/dist/composer/constants.js +9 -4
  11. package/dist/composer/constants.js.map +1 -1
  12. package/dist/composer/styles/idle.css +116 -105
  13. package/dist/composer/useComposerAvatarBehavior.d.ts +1 -0
  14. package/dist/composer/useComposerAvatarBehavior.d.ts.map +1 -1
  15. package/dist/composer/useComposerAvatarBehavior.js +2 -2
  16. package/dist/composer/useComposerAvatarBehavior.js.map +1 -1
  17. package/dist/composer/useMascotActiveIdle.d.ts +33 -0
  18. package/dist/composer/useMascotActiveIdle.d.ts.map +1 -0
  19. package/dist/composer/useMascotActiveIdle.js +128 -0
  20. package/dist/composer/useMascotActiveIdle.js.map +1 -0
  21. package/dist/react.d.ts +5 -0
  22. package/dist/react.d.ts.map +1 -1
  23. package/dist/react.js +4 -0
  24. package/dist/react.js.map +1 -1
  25. package/package.json +1 -1
  26. package/src/composer/ComposerMascot.tsx +36 -112
  27. package/src/composer/MascotIdleStage.tsx +23 -0
  28. package/src/composer/constants.ts +25 -4
  29. package/src/composer/styles/idle.css +116 -105
  30. package/src/composer/useComposerAvatarBehavior.ts +3 -2
  31. package/src/composer/useMascotActiveIdle.ts +170 -0
  32. package/src/react.ts +5 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useMascotActiveIdle.js","sourceRoot":"","sources":["../../src/composer/useMascotActiveIdle.ts"],"names":[],"mappings":"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"}
package/dist/react.d.ts CHANGED
@@ -3,4 +3,9 @@ export { DecorationSwatch, PrimaryDecoration, SecondaryDecoration } from './Deco
3
3
  export { ComposerMascot } from './composer/ComposerMascot.js';
4
4
  export type { ComposerMascotProps, ComposerMascotContext, MascotExpression, MascotLight } from './composer/types.js';
5
5
  export { ComposerMascotShadow } from './composer/ComposerMascotShadow.js';
6
+ export { useComposerAvatarBehavior } from './composer/useComposerAvatarBehavior.js';
7
+ export { useMascotActiveIdle, type MascotActiveIdleAttributes, type MascotActiveIdleHandle, type MascotActiveIdleOptions } from './composer/useMascotActiveIdle.js';
8
+ export { MascotIdleStage } from './composer/MascotIdleStage.js';
9
+ export { MASCOT_SLEEP_AFTER_MS } from './composer/constants.js';
10
+ export type { MascotActiveIdle, MascotActiveIdleMotion, MascotActiveIdlePhase, MascotActiveIdlePlan, MascotIdleDirection } from './composer/constants.js';
6
11
  //# sourceMappingURL=react.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../src/react.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,gBAAgB,EAAE,KAAK,WAAW,EAAE,KAAK,qBAAqB,EAAE,MAAM,aAAa,CAAA;AACpG,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAA;AAE3F,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAA;AAC7D,YAAY,EAAE,mBAAmB,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AACpH,OAAO,EAAE,oBAAoB,EAAE,MAAM,oCAAoC,CAAA"}
1
+ {"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../src/react.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,gBAAgB,EAAE,KAAK,WAAW,EAAE,KAAK,qBAAqB,EAAE,MAAM,aAAa,CAAA;AACpG,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAA;AAE3F,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAA;AAC7D,YAAY,EAAE,mBAAmB,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AACpH,OAAO,EAAE,oBAAoB,EAAE,MAAM,oCAAoC,CAAA;AACzE,OAAO,EAAE,yBAAyB,EAAE,MAAM,yCAAyC,CAAA;AACnF,OAAO,EAAE,mBAAmB,EAAE,KAAK,0BAA0B,EAAE,KAAK,sBAAsB,EAAE,KAAK,uBAAuB,EAAE,MAAM,mCAAmC,CAAA;AACnK,OAAO,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAA;AAC/D,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAA;AAC/D,YAAY,EAAE,gBAAgB,EAAE,sBAAsB,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAA"}
package/dist/react.js CHANGED
@@ -2,4 +2,8 @@ export { Avatar, AppearanceAvatar } from './Avatar.js';
2
2
  export { DecorationSwatch, PrimaryDecoration, SecondaryDecoration } from './Decorations.js';
3
3
  export { ComposerMascot } from './composer/ComposerMascot.js';
4
4
  export { ComposerMascotShadow } from './composer/ComposerMascotShadow.js';
5
+ export { useComposerAvatarBehavior } from './composer/useComposerAvatarBehavior.js';
6
+ export { useMascotActiveIdle } from './composer/useMascotActiveIdle.js';
7
+ export { MascotIdleStage } from './composer/MascotIdleStage.js';
8
+ export { MASCOT_SLEEP_AFTER_MS } from './composer/constants.js';
5
9
  //# sourceMappingURL=react.js.map
package/dist/react.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"react.js","sourceRoot":"","sources":["../src/react.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,gBAAgB,EAAgD,MAAM,aAAa,CAAA;AACpG,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAA;AAE3F,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAA;AAE7D,OAAO,EAAE,oBAAoB,EAAE,MAAM,oCAAoC,CAAA"}
1
+ {"version":3,"file":"react.js","sourceRoot":"","sources":["../src/react.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,gBAAgB,EAAgD,MAAM,aAAa,CAAA;AACpG,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAA;AAE3F,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAA;AAE7D,OAAO,EAAE,oBAAoB,EAAE,MAAM,oCAAoC,CAAA;AACzE,OAAO,EAAE,yBAAyB,EAAE,MAAM,yCAAyC,CAAA;AACnF,OAAO,EAAE,mBAAmB,EAA8F,MAAM,mCAAmC,CAAA;AACnK,OAAO,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAA;AAC/D,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dotcraft/avatar",
3
- "version": "0.6.4",
3
+ "version": "0.6.5",
4
4
  "description": "Name-derived DotCraft avatars and React SVG animation components",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -5,7 +5,8 @@ import { useComposerAvatarBehavior } from './useComposerAvatarBehavior.js'
5
5
  import { consumeMascotHandoff, recordMascotHandoff } from './mascotHandoff.js'
6
6
  import { useComposerProfile } from './useComposerProfile.js'
7
7
  import { useComposerMotion } from './useComposerMotion.js'
8
- import { MASCOT_SIZE, MASCOT_SCALE, MASCOT_HIDDEN_RATIO, MASCOT_RAISE, MASCOT_SLEEP_AFTER_MS, MASCOT_WAVE_DURATION_MS, MASCOT_ACTIVE_IDLE_MIN_MS, MASCOT_ACTIVE_IDLE_JITTER_MS, MASCOT_ACTIVE_IDLE_ACTIVITY_THROTTLE_MS, MASCOT_ACTIVE_IDLE_TRAVEL_MS, MASCOT_ACTIVE_IDLE_HOLD_MS, MASCOT_SPARKLES, pickMascotActiveIdle, type MascotActiveIdleState, type MascotActiveIdleMotion } from './constants.js'
8
+ import { MASCOT_SIZE, MASCOT_SCALE, MASCOT_HIDDEN_RATIO, MASCOT_RAISE, MASCOT_SLEEP_AFTER_MS, MASCOT_WAVE_DURATION_MS, MASCOT_SPARKLES } from './constants.js'
9
+ import { useMascotActiveIdle } from './useMascotActiveIdle.js'
9
10
  import type { ComposerMascotProps, ComposerMascotContext, MascotExpression, MascotLight } from './types.js'
10
11
  export function ComposerMascot({ name, motion = 'system', theme = 'dark', focused = false, dragOver = false, bounceSignal = 0, interaction, reasoningEffort = 'off', speed = 'standard', contextMax = false, anchorOffset = 0, anchorPushSignal = 0, handoff = false, renderCharacter, renderMenu, onNameRendered }: ComposerMascotProps) {
11
12
  const reduced = !useComposerMotion(motion)
@@ -24,11 +25,8 @@ export function ComposerMascot({ name, motion = 'system', theme = 'dark', focuse
24
25
  const [nodding, setNodding] = useState(false)
25
26
  const [landing, setLanding] = useState(false)
26
27
  const [pushLift, setPushLift] = useState(false)
27
- const [activeIdle, setActiveIdle] = useState<MascotActiveIdleState | null>(null)
28
- const [activityRevision, setActivityRevision] = useState(0)
29
28
  const rootRef = useRef<HTMLDivElement | null>(null)
30
- const lastActivityRef = useRef(0)
31
- const lastActiveIdleRef = useRef<MascotActiveIdleMotion | null>(null)
29
+ const clearGestureRef = useRef<() => void>(() => {})
32
30
 
33
31
  const baseExpression: MascotExpression =
34
32
  interaction?.expression ?? (dragOver ? 'operator' : focused ? 'happy' : 'neutral')
@@ -58,6 +56,24 @@ export function ComposerMascot({ name, motion = 'system', theme = 'dark', focuse
58
56
  : laptopActive
59
57
  ? 'working'
60
58
  : 'idle'
59
+ const ambient =
60
+ !focused &&
61
+ !dragOver &&
62
+ !bubble &&
63
+ !holdSign &&
64
+ menuPos == null &&
65
+ baseExpression === 'neutral' &&
66
+ light === 'default'
67
+ const idle = useMascotActiveIdle({
68
+ enabled: ambient && !sleeping && !reduced,
69
+ onStart: () => clearGestureRef.current(),
70
+ onActivity: () => {
71
+ setSleeping((current) => {
72
+ if (current && !reduced) setStartled(true)
73
+ return false
74
+ })
75
+ }
76
+ })
61
77
  const avatarBehavior = useComposerAvatarBehavior({
62
78
  semanticPose: semanticAvatarPose,
63
79
  baseExpression,
@@ -65,10 +81,11 @@ export function ComposerMascot({ name, motion = 'system', theme = 'dark', focuse
65
81
  dragOver,
66
82
  sleeping,
67
83
  waving,
68
- activeIdle: activeIdle != null,
84
+ activeIdle: idle.activeIdle != null,
69
85
  bounceSignal,
70
86
  reducedMotion: reduced
71
87
  })
88
+ clearGestureRef.current = avatarBehavior.clearGesture
72
89
  const mascotPalette = mascotPaletteOf(deriveAppearance(avatar ?? ''))
73
90
  const activity: ComposerMascotContext["activity"] = light === 'error'
74
91
  ? 'error'
@@ -96,87 +113,6 @@ export function ComposerMascot({ name, motion = 'system', theme = 'dark', focuse
96
113
  contextMax,
97
114
  reducedMotion: reduced
98
115
  }
99
- const ambient =
100
- !focused &&
101
- !dragOver &&
102
- !bubble &&
103
- !holdSign &&
104
- menuPos == null &&
105
- baseExpression === 'neutral' &&
106
- light === 'default'
107
-
108
- const markActivity = useCallback(() => {
109
- setActiveIdle(null)
110
- setSleeping((current) => {
111
- if (current && !reduced) setStartled(true)
112
- return false
113
- })
114
- const now = Date.now()
115
- if (now - lastActivityRef.current < MASCOT_ACTIVE_IDLE_ACTIVITY_THROTTLE_MS) return
116
- lastActivityRef.current = now
117
- setActivityRevision((value) => value + 1)
118
- }, [reduced])
119
-
120
- useEffect(() => {
121
- const markPointerMoveActivity = (): void => {
122
- if (Date.now() - lastActivityRef.current >= MASCOT_ACTIVE_IDLE_ACTIVITY_THROTTLE_MS) {
123
- markActivity()
124
- }
125
- }
126
- window.addEventListener('keydown', markActivity)
127
- window.addEventListener('pointerdown', markActivity)
128
- window.addEventListener('pointermove', markPointerMoveActivity, { passive: true })
129
- window.addEventListener('wheel', markActivity, { passive: true })
130
- window.addEventListener('focusin', markActivity)
131
- return () => {
132
- window.removeEventListener('keydown', markActivity)
133
- window.removeEventListener('pointerdown', markActivity)
134
- window.removeEventListener('pointermove', markPointerMoveActivity)
135
- window.removeEventListener('wheel', markActivity)
136
- window.removeEventListener('focusin', markActivity)
137
- }
138
- }, [markActivity])
139
-
140
- useEffect(() => {
141
- if (!ambient || sleeping || reduced) {
142
- setActiveIdle(null)
143
- return undefined
144
- }
145
-
146
- let timer = 0
147
- const start = (): void => {
148
- if (document.hidden) {
149
- timer = window.setTimeout(start, 5000)
150
- return
151
- }
152
- const motion = pickMascotActiveIdle(Math.random(), lastActiveIdleRef.current)
153
- lastActiveIdleRef.current = motion
154
- avatarBehavior.clearGesture()
155
- setActiveIdle({ motion, phase: 'outbound' })
156
- }
157
- timer = window.setTimeout(
158
- start,
159
- MASCOT_ACTIVE_IDLE_MIN_MS + Math.random() * MASCOT_ACTIVE_IDLE_JITTER_MS
160
- )
161
- return () => window.clearTimeout(timer)
162
- }, [ambient, activityRevision, sleeping, reduced, avatarBehavior.clearGesture])
163
-
164
- useEffect(() => {
165
- if (!activeIdle) return undefined
166
- const delay = activeIdle.phase === 'away'
167
- ? MASCOT_ACTIVE_IDLE_HOLD_MS[activeIdle.motion]
168
- : MASCOT_ACTIVE_IDLE_TRAVEL_MS[activeIdle.motion] + 160
169
- const timer = window.setTimeout(() => {
170
- setActiveIdle((current) => {
171
- if (!current) return null
172
- if (current.phase === 'outbound') return { ...current, phase: 'away' }
173
- if (current.phase === 'away') return { ...current, phase: 'inbound' }
174
- return null
175
- })
176
- }, delay)
177
- return () => window.clearTimeout(timer)
178
- }, [activeIdle])
179
-
180
116
  useLayoutEffect(() => {
181
117
  if (!handoff) return undefined
182
118
  const el = rootRef.current
@@ -284,10 +220,10 @@ export function ComposerMascot({ name, motion = 'system', theme = 'dark', focuse
284
220
  setSleeping(false)
285
221
  return
286
222
  }
287
- if (sleeping || activeIdle) return
223
+ if (sleeping || idle.activeIdle) return
288
224
  const timer = window.setTimeout(() => setSleeping(true), MASCOT_SLEEP_AFTER_MS)
289
225
  return () => window.clearTimeout(timer)
290
- }, [ambient, activityRevision, sleeping, activeIdle, reduced])
226
+ }, [ambient, idle.activityRevision, sleeping, idle.activeIdle, reduced])
291
227
 
292
228
  const wake = useCallback(() => {
293
229
  setSleeping(false)
@@ -311,21 +247,7 @@ export function ComposerMascot({ name, motion = 'system', theme = 'dark', focuse
311
247
  }, [focused, reduced])
312
248
 
313
249
  const onAnimationEnd = (event: AnimationEvent<HTMLDivElement>): void => {
314
- if (activeIdle) {
315
- const expected = activeIdle.motion === 'hop'
316
- ? 'composer-mascot-idle-hop-travel'
317
- : activeIdle.motion === 'rocket'
318
- ? 'composer-mascot-idle-rocket-flight-x'
319
- : activeIdle.phase === 'outbound'
320
- ? 'composer-mascot-idle-hover-launch-body'
321
- : 'composer-mascot-idle-hover-land-body'
322
- if (event.animationName === expected) {
323
- setActiveIdle((current) => {
324
- if (!current) return null
325
- return current.phase === 'outbound' ? { ...current, phase: 'away' } : null
326
- })
327
- }
328
- }
250
+ idle.onAnimationEnd(event)
329
251
  switch (event.animationName) {
330
252
  case 'composer-mascot-launch':
331
253
  setLaunching(false)
@@ -388,7 +310,7 @@ export function ComposerMascot({ name, motion = 'system', theme = 'dark', focuse
388
310
 
389
311
  const rootClassName =
390
312
  [
391
- activeIdle ? 'composer-mascot-active-idle' : null,
313
+ idle.className,
392
314
  sleeping ? 'composer-mascot-sleeping' : null,
393
315
  ]
394
316
  .filter(Boolean)
@@ -403,6 +325,7 @@ export function ComposerMascot({ name, motion = 'system', theme = 'dark', focuse
403
325
 
404
326
  aria-hidden={interaction ? undefined : true}
405
327
  data-composer-mascot-motion={reduced ? 'off' : 'on'}
328
+ data-composer-avatar-pose={semanticAvatarPose}
406
329
  ref={rootRef}
407
330
  className={rootClassName}
408
331
  data-mascot-name={avatar ?? ''}
@@ -411,8 +334,7 @@ export function ComposerMascot({ name, motion = 'system', theme = 'dark', focuse
411
334
  data-mascot-speed={speed}
412
335
  data-mascot-context={contextMax ? 'max' : 'default'}
413
336
  data-mascot-profile-transition={profileTransition ? 'active' : 'idle'}
414
- data-mascot-active-idle={activeIdle?.motion}
415
- data-mascot-idle-phase={activeIdle?.phase}
337
+ {...idle.attributes}
416
338
  data-mascot-anchor-offset={anchorOffset}
417
339
  onAnimationEnd={onAnimationEnd}
418
340
  style={{
@@ -446,25 +368,27 @@ export function ComposerMascot({ name, motion = 'system', theme = 'dark', focuse
446
368
  )}
447
369
  <div
448
370
  key={profileTransitionRevision}
449
- className="composer-mascot-motion"
371
+ className="composer-mascot-motion composer-mascot-stage"
450
372
  style={{
373
+ '--mascot-idle-stage-scale': MASCOT_SCALE,
451
374
  transformOrigin: 'bottom center',
452
375
  transform: `scale(${MASCOT_SCALE})`,
453
376
 
454
377
  filter: `drop-shadow(0 5.3px 7.3px color-mix(in srgb, ${mascotPalette.shadow} 20%, transparent))`
455
- }}
378
+ } as CSSProperties}
456
379
  >
457
380
  <div
381
+ className="composer-mascot-travel"
458
382
  style={{
459
383
  transformOrigin: 'bottom center',
460
384
  transition: 'transform 280ms cubic-bezier(0.34, 1.56, 0.64, 1)',
461
385
  transform: poseTransform
462
386
  }}
463
387
  >
464
- <div className={shotClass}>
465
- <div className={loopClass}>
388
+ <div className={shotClass ? `composer-mascot-lift ${shotClass}` : 'composer-mascot-lift'}>
389
+ <div className={`composer-mascot-body ${loopClass}`}>
466
390
  <div
467
- className="composer-mascot-jelly"
391
+ className="composer-mascot-jelly composer-mascot-trail"
468
392
  style={{ pointerEvents: 'auto', cursor: hasMenu ? 'context-menu' : undefined }}
469
393
  onMouseEnter={sleeping ? wake : undefined}
470
394
  onClick={() => {
@@ -0,0 +1,23 @@
1
+ import type { CSSProperties, ReactElement, ReactNode } from 'react'
2
+
3
+ /**
4
+ * The layer stack the active-idle styles animate: travel (x), lift (y), body (squash), trail (jets).
5
+ * ComposerMascot tags its own wrappers with these classes instead of rendering this stage.
6
+ */
7
+ export function MascotIdleStage({ className, style, children }: {
8
+ className?: string
9
+ style?: CSSProperties
10
+ children: ReactNode
11
+ }): ReactElement {
12
+ return (
13
+ <div className={className ? `composer-mascot-stage ${className}` : 'composer-mascot-stage'} style={style}>
14
+ <div className="composer-mascot-travel">
15
+ <div className="composer-mascot-lift">
16
+ <div className="composer-mascot-body">
17
+ <div className="composer-mascot-trail">{children}</div>
18
+ </div>
19
+ </div>
20
+ </div>
21
+ </div>
22
+ )
23
+ }
@@ -10,12 +10,25 @@ export const MASCOT_ACTIVE_IDLE_ACTIVITY_THROTTLE_MS = 500
10
10
 
11
11
  export type MascotActiveIdleMotion = 'hop' | 'rocket' | 'hover'
12
12
  export type MascotActiveIdlePhase = 'outbound' | 'away' | 'inbound'
13
+ export type MascotIdleDirection = 'left' | 'right'
14
+ export const MASCOT_ACTIVE_IDLE_MOTIONS: readonly MascotActiveIdleMotion[] = ['hop', 'rocket', 'hover']
13
15
 
14
16
  export interface MascotActiveIdleState {
15
17
  motion: MascotActiveIdleMotion
16
18
  phase: MascotActiveIdlePhase
17
19
  }
18
20
 
21
+ export interface MascotActiveIdle extends MascotActiveIdleState {
22
+ /** Where the trip heads; the inbound leg travels the other way. */
23
+ direction: MascotIdleDirection
24
+ travel: MascotIdleDirection
25
+ }
26
+
27
+ export interface MascotActiveIdlePlan {
28
+ direction?: MascotIdleDirection
29
+ motions?: readonly MascotActiveIdleMotion[]
30
+ }
31
+
19
32
  export interface MascotProfileTransition {
20
33
  fromAccent: string
21
34
  toAccent: string
@@ -36,10 +49,18 @@ export const MASCOT_ACTIVE_IDLE_HOLD_MS: Record<MascotActiveIdleMotion, number>
36
49
  hover: 2800
37
50
  }
38
51
 
39
- export function pickMascotActiveIdle(random: number, previous: MascotActiveIdleMotion | null): MascotActiveIdleMotion {
40
- const selected: MascotActiveIdleMotion = random < 0.65 ? 'hop' : random < 0.9 ? 'rocket' : 'hover'
41
- if (selected !== previous) return selected
42
- return selected === 'hop' ? 'rocket' : selected === 'rocket' ? 'hop' : 'rocket'
52
+ export function pickMascotActiveIdle(
53
+ random: number,
54
+ previous: MascotActiveIdleMotion | null,
55
+ allowed: readonly MascotActiveIdleMotion[] = MASCOT_ACTIVE_IDLE_MOTIONS
56
+ ): MascotActiveIdleMotion {
57
+ const preferred: MascotActiveIdleMotion = random < 0.65 ? 'hop' : random < 0.9 ? 'rocket' : 'hover'
58
+ const selected = allowed.includes(preferred)
59
+ ? preferred
60
+ : MASCOT_ACTIVE_IDLE_MOTIONS.find((motion) => allowed.includes(motion)) ?? preferred
61
+ if (selected !== previous || allowed.length < 2) return selected
62
+ const alternate: MascotActiveIdleMotion = selected === 'hop' ? 'rocket' : selected === 'rocket' ? 'hop' : 'rocket'
63
+ return allowed.includes(alternate) ? alternate : allowed.find((motion) => motion !== selected) ?? selected
43
64
  }
44
65
 
45
66
  export const MASCOT_SPARKLES = Array.from({ length: 7 }, (_, i) => {