Skip to content

Commit 4789925

Browse files
committed
feat(home): add interactive camera and click-forge bursts
1 parent c32be8f commit 4789925

1 file changed

Lines changed: 30 additions & 2 deletions

File tree

frontend/src/components/home/ForgeVisualization.tsx

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import * as THREE from 'three';
33

44
export function ForgeVisualization() {
55
const mountRef = useRef<HTMLDivElement>(null);
6+
const pointerRef = useRef({ x: 0, y: 0 });
67

78
useEffect(() => {
89
const mount = mountRef.current;
@@ -91,6 +92,7 @@ export function ForgeVisualization() {
9192
let lastForge = 0;
9293
const forgeIntervalMs = 2200;
9394
const clock = new THREE.Clock();
95+
let userPulse = 0;
9496

9597
const triggerForge = () => {
9698
fireLight.intensity = 3.7;
@@ -108,6 +110,21 @@ export function ForgeVisualization() {
108110
sparkGeo.attributes.position.needsUpdate = true;
109111
};
110112

113+
const onPointerMove = (event: PointerEvent) => {
114+
if (!mount) return;
115+
const rect = mount.getBoundingClientRect();
116+
const x = ((event.clientX - rect.left) / rect.width) * 2 - 1;
117+
const y = -(((event.clientY - rect.top) / rect.height) * 2 - 1);
118+
pointerRef.current.x = x;
119+
pointerRef.current.y = y;
120+
userPulse = 0.7;
121+
};
122+
123+
const onPointerDown = () => {
124+
triggerForge();
125+
userPulse = 1;
126+
};
127+
111128
const animate = () => {
112129
const elapsed = clock.getElapsedTime();
113130
const delta = Math.min(clock.getDelta(), 0.03);
@@ -148,8 +165,15 @@ export function ForgeVisualization() {
148165
}
149166
sparkGeo.attributes.position.needsUpdate = true;
150167

151-
camera.position.x = Math.sin(elapsed * 0.24) * 0.85;
152-
camera.lookAt(0, 0.35, 0);
168+
const targetX = Math.sin(elapsed * 0.24) * 0.85 + pointerRef.current.x * 0.45;
169+
const targetY = 1.6 + pointerRef.current.y * 0.28;
170+
camera.position.x += (targetX - camera.position.x) * 0.06;
171+
camera.position.y += (targetY - camera.position.y) * 0.06;
172+
camera.lookAt(pointerRef.current.x * 0.2, 0.35 + pointerRef.current.y * 0.1, 0);
173+
174+
userPulse = Math.max(0, userPulse - delta * 1.8);
175+
sparks.material.opacity = 0.72 + userPulse * 0.2;
176+
fireLight.color.setHSL(0.06 + userPulse * 0.04, 1, 0.5);
153177

154178
renderer.render(scene, camera);
155179
frame = requestAnimationFrame(animate);
@@ -164,10 +188,14 @@ export function ForgeVisualization() {
164188
renderer.setSize(mount.clientWidth, mount.clientHeight);
165189
};
166190
window.addEventListener('resize', onResize);
191+
mount.addEventListener('pointermove', onPointerMove);
192+
mount.addEventListener('pointerdown', onPointerDown);
167193

168194
return () => {
169195
cancelAnimationFrame(frame);
170196
window.removeEventListener('resize', onResize);
197+
mount.removeEventListener('pointermove', onPointerMove);
198+
mount.removeEventListener('pointerdown', onPointerDown);
171199
renderer.dispose();
172200
sparkGeo.dispose();
173201
mount.removeChild(renderer.domElement);

0 commit comments

Comments
 (0)