|
|
@@ -372,6 +372,7 @@ export interface EffectFrame {
|
|
|
paint: string
|
|
|
showEffects: boolean
|
|
|
showGlyphs: boolean
|
|
|
+ showTrails?: boolean
|
|
|
}
|
|
|
|
|
|
/** Called once per frame from the engine loop. */
|
|
|
@@ -381,7 +382,7 @@ export function updateEffects(frame: EffectFrame) {
|
|
|
const strength = frame.strength
|
|
|
|
|
|
if (frame.trails) {
|
|
|
- if (spec?.trails && frame.showEffects && strength > 0) {
|
|
|
+ if (spec?.trails && frame.showEffects && frame.showTrails !== false && strength > 0) {
|
|
|
drawTrails(frame.trails, spec.trails, frame.elapsed, strength, centre, centre)
|
|
|
} else if (frame.trails.childNodes.length) {
|
|
|
frame.trails.replaceChildren()
|
|
|
@@ -917,11 +918,13 @@ export interface CursorOptions {
|
|
|
gaze?: { x?: number; y?: number }
|
|
|
expression?: number
|
|
|
fixedTime?: number
|
|
|
+ /** Live activity rings. A saved "working" face does not wear them. */
|
|
|
+ ribbons?: boolean
|
|
|
}
|
|
|
type Engine = ReturnType<typeof newEngine>
|
|
|
function newEngine() {
|
|
|
return { current: clone(EXPRESSIONS[0]), target: EXPRESSIONS[0], expression: 0, morph: 1, velocity: 0, blinkStart: noTimestamp(), spinStart: noTimestamp(), spinDuration: 900, last: 0, stateStart: 0, lastState: 'idle' as CursorState, lastBodyTransform: '',
|
|
|
- props: {state: 'idle' as CursorState, expression: undefined as number | undefined, gaze: undefined as {x?:number;y?:number}|undefined, turn:0, spring:7, eyeScale:1, paused:true, motionStrength:0, effects:true, glyphs:true} }
|
|
|
+ props: {state: 'idle' as CursorState, expression: undefined as number | undefined, gaze: undefined as {x?:number;y?:number}|undefined, turn:0, spring:7, eyeScale:1, paused:true, motionStrength:0, effects:true, glyphs:true, ribbons:true} }
|
|
|
}
|
|
|
let nextId = 0
|
|
|
export function mountCursorAvatar(svg: SVGSVGElement, initial: CursorOptions) {
|
|
|
@@ -1001,6 +1004,7 @@ export function mountCursorAvatar(svg: SVGSVGElement, initial: CursorOptions) {
|
|
|
paint: paintRef.current,
|
|
|
showEffects: p.effects !== false,
|
|
|
showGlyphs: p.glyphs !== false,
|
|
|
+ showTrails: p.ribbons !== false,
|
|
|
})
|
|
|
}
|
|
|
|
|
|
@@ -1032,7 +1036,7 @@ export function mountCursorAvatar(svg: SVGSVGElement, initial: CursorOptions) {
|
|
|
options=next
|
|
|
svg.setAttribute('viewBox', next.silhouette.viewBox ?? VIEW_BOX)
|
|
|
const e=engine.current, now=performance.now();paintRef.current=next.color
|
|
|
- e.props={...e.props,state:next.state,expression:next.expression,gaze:next.gaze,paused:next.paused,motionStrength:next.paused?0:1}
|
|
|
+ e.props={...e.props,state:next.state,expression:next.expression,gaze:next.gaze,paused:next.paused,motionStrength:next.paused?0:1,ribbons:next.ribbons!==false}
|
|
|
const outline=node<SVGGElement>('outline').current, clip=node<SVGClipPathElement>('clip').current, face=node<SVGGElement>('face').current
|
|
|
const body=next.silhouette.body.replace(/\{\{GRADIENT\}\}/g,next.color)
|
|
|
if(lastBody!==body){outline.innerHTML=body;lastBody=body}
|