From 3b07f40f2d64d6dbb0c42e7a984da5b249d0cd96 Mon Sep 17 00:00:00 2001 From: Tom Boullay Date: Mon, 1 Jun 2026 17:01:08 +0200 Subject: [PATCH] fix(ui): restore talkie idle vs active animation The previous talkie-overlay refactor lost the rest/active behaviour and left the radio-shake CSS animation running constantly. Restore the intended polish: - Position lerp between TALKIE_REST_Y (idle) and TALKIE_ACTIVE_Y (raised when narrator is speaking) with a subtle floating bob. - Subtle z-axis float at idle, faster shake when active. - Gate the CSS radio-shake on the new --active modifier so the talkie is calm when no narrator dialogue is playing. - Keep the face-camera rotation [0.18, PI, -0.08] from the original overlay version. Visibility still kicks in at the reveal step (no regression on the recent fix). --- src/components/ui/TalkieDialogueOverlay.tsx | 41 ++++++++++++++++----- src/index.css | 5 ++- 2 files changed, 35 insertions(+), 11 deletions(-) diff --git a/src/components/ui/TalkieDialogueOverlay.tsx b/src/components/ui/TalkieDialogueOverlay.tsx index bc1b445..ce7100b 100644 --- a/src/components/ui/TalkieDialogueOverlay.tsx +++ b/src/components/ui/TalkieDialogueOverlay.tsx @@ -14,7 +14,16 @@ const TALKIE_REVEAL_STEPS = new Set([ "completed", ]); -function TalkieModel(): React.JSX.Element { +const TALKIE_REST_Y = -0.55; +const TALKIE_ACTIVE_Y = -0.18; +const TALKIE_FLOAT_Y_AMPLITUDE = 0.025; +const TALKIE_FLOAT_ROTATION_AMPLITUDE = THREE.MathUtils.degToRad(1.6); + +interface TalkieModelProps { + active: boolean; +} + +function TalkieModel({ active }: TalkieModelProps): React.JSX.Element { const { scene } = useGLTF(TALKIE_MODEL_PATH); const model = useMemo(() => scene.clone(true), [scene]); const groupRef = useRef(null); @@ -33,15 +42,26 @@ function TalkieModel(): React.JSX.Element { if (!groupRef.current) return; const t = clock.getElapsedTime(); - groupRef.current.rotation.z = Math.sin(t * 22) * 0.025; - groupRef.current.position.y = Math.sin(t * 6) * 0.012; + const floatY = Math.sin(t * 1.4) * TALKIE_FLOAT_Y_AMPLITUDE; + const targetY = (active ? TALKIE_ACTIVE_Y : TALKIE_REST_Y) + floatY; + groupRef.current.position.y = THREE.MathUtils.lerp( + groupRef.current.position.y, + targetY, + 0.14, + ); + + if (active) { + groupRef.current.rotation.z = Math.sin(t * 22) * 0.025; + } else { + groupRef.current.rotation.z = + Math.sin(t * 0.8) * TALKIE_FLOAT_ROTATION_AMPLITUDE; + } }); return ( - + @@ -73,11 +93,12 @@ export function TalkieDialogueOverlay(): React.JSX.Element | null { if (!isAfterReveal) return null; + const overlayClassName = isNarratorDialogue + ? "talkie-dialogue-overlay talkie-dialogue-overlay--active talkie-dialogue-overlay--raised" + : "talkie-dialogue-overlay"; + return ( -