import React, { useState, useEffect, useRef, useMemo, Suspense } from 'https://esm.sh/react'; import ReactDOM from 'https://esm.sh/react-dom/client'; import * as THREE from 'https://esm.sh/three'; import { Canvas, useFrame, useThree } from 'https://esm.sh/@react-three/fiber'; import { useGLTF, useAnimations, PerspectiveCamera, Environment, Stars, Float } from 'https://esm.sh/@react-three/drei'; const MODEL_URL = 'https://raw.githubusercontent.com/mrdoob/three.js/master/examples/models/gltf/Horse.glb'; function Animal({ velocity }) { const group = useRef(); const { scene, animations } = useGLTF(MODEL_URL); const { actions } = useAnimations(animations, group); useEffect(() => { // Horse model usually has an animation named 'run' or the first one is the run loop const animationName = animations[0]?.name; if (actions[animationName]) { actions[animationName].play(); } return () => actions[animationName]?.stop(); }, [actions, animations]); useFrame((state, delta) => { const animationName = animations[0]?.name; if (actions[animationName]) { const animScale = 0.2 + (velocity * 3.5); actions[animationName].setEffectiveTimeScale(animScale); } if (group.current) { group.current.rotation.z = THREE.MathUtils.lerp(group.current.rotation.z, (velocity * 0.1), 0.1); group.current.position.y = Math.sin(state.clock.elapsedTime * 15 * velocity) * 0.1 * velocity; } }); return ( ); } function InfiniteGround({ velocity }) { const meshRef = useRef(); const texture = useMemo(() => { const canvas = document.createElement('canvas'); canvas.width = 512; canvas.height = 512; const ctx = canvas.getContext('2d'); ctx.fillStyle = '#050505'; ctx.fillRect(0, 0, 512, 512); ctx.strokeStyle = '#39FF1444'; ctx.lineWidth = 1; ctx.strokeRect(0, 0, 512, 512); const tex = new THREE.CanvasTexture(canvas); tex.wrapS = tex.wrapT = THREE.RepeatWrapping; tex.repeat.set(100, 100); return tex; }, []); useFrame((state, delta) => { const speed = velocity * 25 * delta; meshRef.current.material.map.offset.y += speed; }); return ( ); } function Trees({ velocity }) { const count = 40; const trees = useMemo(() => { const arr = []; for (let i = 0; i < count; i++) { arr.push({ id: i, position: [ (Math.random() - 0.5) * 60 + (Math.random() > 0.5 ? 15 : -15), 0, -Math.random() * 200 ], scale: 0.5 + Math.random() * 2 }); } return arr; }, []); const groupRef = useRef(); useFrame((state, delta) => { const speed = velocity * 25 * delta; groupRef.current.children.forEach((child) => { child.position.z += speed; if (child.position.z > 20) { child.position.z = -180; } }); }); return ( {trees.map((tree) => ( ))} ); } function GameScene({ velocity }) { return ( <> ); } function GameBridge({ onUpdate, onStart }) { const targetVelocity = useRef(0); const velocity = useRef(0); useEffect(() => { const handleScroll = (e) => { onStart(); // Increased sensitivity for better feel const delta = Math.abs(e.deltaY) * 0.008; targetVelocity.current = Math.min(targetVelocity.current + delta, 1); }; window.addEventListener('wheel', handleScroll, { passive: true }); return () => window.removeEventListener('wheel', handleScroll); }, [onStart]); useFrame((state, delta) => { // Deceleration targetVelocity.current = Math.max(targetVelocity.current - (0.4 * delta), 0); // Smooth lerp velocity.current = THREE.MathUtils.lerp(velocity.current, targetVelocity.current, 0.1); onUpdate(velocity.current); // Speed-based effects state.camera.fov = 50 + (velocity.current * 25); state.camera.updateProjectionMatrix(); if (velocity.current > 0.4) { state.camera.position.x = (Math.random() - 0.5) * 0.08 * velocity.current; state.camera.position.y = 2 + (Math.random() - 0.5) * 0.08 * velocity.current; state.camera.position.z = 6 + (Math.random() - 0.5) * 0.04 * velocity.current; } else { state.camera.position.x = THREE.MathUtils.lerp(state.camera.position.x, 0, 0.1); state.camera.position.y = THREE.MathUtils.lerp(state.camera.position.y, 2, 0.1); state.camera.position.z = THREE.MathUtils.lerp(state.camera.position.z, 6, 0.1); } }); return null; } function App() { const [velocity, setVelocity] = useState(0); const [showInstructions, setShowInstructions] = useState(true); const onFrameUpdate = (v) => setVelocity(v); return (
setShowInstructions(false)} />
Velocity
SCROLL TO RUN
); } const root = ReactDOM.createRoot(document.getElementById('root')); root.render();