fix(ui): restore talkie idle vs active animation
🔍 Lint / 🪄 Check lint (pull_request) Has been cancelled
🔍 Lint / 🎨 Check format (pull_request) Has been cancelled
🔍 Lint / 🔎 Typecheck (pull_request) Has been cancelled
📊 Quality / 🔒 Security Audit (pull_request) Has been cancelled
📊 Quality / 📋 Dependency Freshness (pull_request) Has been cancelled
📊 Quality / 📦 Bundle Size (pull_request) Has been cancelled
🔍 Lint / 🏗 Build (pull_request) Has been cancelled
🔍 Lint / 🪄 Check lint (pull_request) Has been cancelled
🔍 Lint / 🎨 Check format (pull_request) Has been cancelled
🔍 Lint / 🔎 Typecheck (pull_request) Has been cancelled
📊 Quality / 🔒 Security Audit (pull_request) Has been cancelled
📊 Quality / 📋 Dependency Freshness (pull_request) Has been cancelled
📊 Quality / 📦 Bundle Size (pull_request) Has been cancelled
🔍 Lint / 🏗 Build (pull_request) Has been cancelled
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).
This commit is contained in:
@@ -14,7 +14,16 @@ const TALKIE_REVEAL_STEPS = new Set([
|
|||||||
"completed",
|
"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 { scene } = useGLTF(TALKIE_MODEL_PATH);
|
||||||
const model = useMemo(() => scene.clone(true), [scene]);
|
const model = useMemo(() => scene.clone(true), [scene]);
|
||||||
const groupRef = useRef<THREE.Group>(null);
|
const groupRef = useRef<THREE.Group>(null);
|
||||||
@@ -33,15 +42,26 @@ function TalkieModel(): React.JSX.Element {
|
|||||||
if (!groupRef.current) return;
|
if (!groupRef.current) return;
|
||||||
|
|
||||||
const t = clock.getElapsedTime();
|
const t = clock.getElapsedTime();
|
||||||
groupRef.current.rotation.z = Math.sin(t * 22) * 0.025;
|
const floatY = Math.sin(t * 1.4) * TALKIE_FLOAT_Y_AMPLITUDE;
|
||||||
groupRef.current.position.y = Math.sin(t * 6) * 0.012;
|
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 (
|
return (
|
||||||
<group ref={groupRef}>
|
<group ref={groupRef} position={[0, TALKIE_REST_Y, 0]}>
|
||||||
<primitive
|
<primitive
|
||||||
object={model}
|
object={model}
|
||||||
position={[0, -0.18, 0]}
|
|
||||||
rotation={[0.18, Math.PI, -0.08]}
|
rotation={[0.18, Math.PI, -0.08]}
|
||||||
scale={1.45}
|
scale={1.45}
|
||||||
/>
|
/>
|
||||||
@@ -73,11 +93,12 @@ export function TalkieDialogueOverlay(): React.JSX.Element | null {
|
|||||||
|
|
||||||
if (!isAfterReveal) return null;
|
if (!isAfterReveal) return null;
|
||||||
|
|
||||||
|
const overlayClassName = isNarratorDialogue
|
||||||
|
? "talkie-dialogue-overlay talkie-dialogue-overlay--active talkie-dialogue-overlay--raised"
|
||||||
|
: "talkie-dialogue-overlay";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<aside
|
<aside className={overlayClassName} aria-hidden="true">
|
||||||
className={`talkie-dialogue-overlay${isNarratorDialogue ? " talkie-dialogue-overlay--raised" : ""}`}
|
|
||||||
aria-hidden="true"
|
|
||||||
>
|
|
||||||
{isNarratorDialogue ? <TalkieSignalLines /> : null}
|
{isNarratorDialogue ? <TalkieSignalLines /> : null}
|
||||||
<div className="talkie-dialogue-overlay__model-frame">
|
<div className="talkie-dialogue-overlay__model-frame">
|
||||||
<Canvas
|
<Canvas
|
||||||
@@ -89,7 +110,7 @@ export function TalkieDialogueOverlay(): React.JSX.Element | null {
|
|||||||
<ambientLight intensity={2.5} />
|
<ambientLight intensity={2.5} />
|
||||||
<directionalLight position={[2, 3, 4]} intensity={2.8} />
|
<directionalLight position={[2, 3, 4]} intensity={2.8} />
|
||||||
<Suspense fallback={null}>
|
<Suspense fallback={null}>
|
||||||
<TalkieModel />
|
<TalkieModel active={isNarratorDialogue} />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</Canvas>
|
</Canvas>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+4
-1
@@ -1257,10 +1257,13 @@ canvas {
|
|||||||
.talkie-dialogue-overlay__model-frame {
|
.talkie-dialogue-overlay__model-frame {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
inset: 0;
|
inset: 0;
|
||||||
animation: talkie-radio-shake 1s ease-in-out infinite;
|
|
||||||
filter: drop-shadow(0 16px 22px rgba(0, 0, 0, 0.55));
|
filter: drop-shadow(0 16px 22px rgba(0, 0, 0, 0.55));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.talkie-dialogue-overlay--active .talkie-dialogue-overlay__model-frame {
|
||||||
|
animation: talkie-radio-shake 1s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
.talkie-dialogue-overlay__model-frame canvas {
|
.talkie-dialogue-overlay__model-frame canvas {
|
||||||
width: 100% !important;
|
width: 100% !important;
|
||||||
height: 100% !important;
|
height: 100% !important;
|
||||||
|
|||||||
Reference in New Issue
Block a user