feat: add idle yawn animation when user is inactive (F-4)
After 30s without user input on Elements tab, producer sprites cycle a subtle yawn (translateY + scale) every 8s. Resets immediately on any pointer/touch/key/scroll event. Implemented via wrapper div so CharacterSprite internals are untouched. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
39
src/lib/useIdleMood.ts
Normal file
39
src/lib/useIdleMood.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
const IDLE_AFTER_MS = 30_000;
|
||||
const TICK_MS = 2_000;
|
||||
|
||||
export type Mood = 'awake' | 'idle';
|
||||
|
||||
const ACTIVITY_EVENTS: Array<keyof WindowEventMap> = [
|
||||
'pointerdown',
|
||||
'touchstart',
|
||||
'keydown',
|
||||
'scroll',
|
||||
];
|
||||
|
||||
export function useIdleMood(idleAfterMs: number = IDLE_AFTER_MS): Mood {
|
||||
const [mood, setMood] = useState<Mood>('awake');
|
||||
|
||||
useEffect(() => {
|
||||
let lastActivity = Date.now();
|
||||
const onActivity = () => {
|
||||
lastActivity = Date.now();
|
||||
setMood((prev) => (prev === 'awake' ? prev : 'awake'));
|
||||
};
|
||||
ACTIVITY_EVENTS.forEach((evt) =>
|
||||
window.addEventListener(evt, onActivity, { passive: true })
|
||||
);
|
||||
const tick = window.setInterval(() => {
|
||||
if (Date.now() - lastActivity >= idleAfterMs) {
|
||||
setMood((prev) => (prev === 'idle' ? prev : 'idle'));
|
||||
}
|
||||
}, TICK_MS);
|
||||
return () => {
|
||||
ACTIVITY_EVENTS.forEach((evt) => window.removeEventListener(evt, onActivity));
|
||||
window.clearInterval(tick);
|
||||
};
|
||||
}, [idleAfterMs]);
|
||||
|
||||
return mood;
|
||||
}
|
||||
Reference in New Issue
Block a user