diff --git a/src/app/components/Home.jsx b/src/app/components/Home.jsx index 9ccde9d..a32af80 100644 --- a/src/app/components/Home.jsx +++ b/src/app/components/Home.jsx @@ -7,6 +7,7 @@ import { EffectComposer, Bloom } from "@react-three/postprocessing"; import { motion } from "framer-motion"; import Navbar from "./Navbar"; import { ChevronRight } from "lucide-react"; +import * as THREE from "three"; const Glow = ({ className = "" }) => (
( /> ); -function AnimatedNeuralNode({ position, color, size = 0.08, delay = 0 }) { +// Optimized animation progress hook +function useAnimationProgress(duration = 2000, delay = 500) { + const [progress, setProgress] = useState(0); + const [isComplete, setIsComplete] = useState(false); + const rafRef = useRef(); + + useEffect(() => { + let startTime = null; + let animationFrameId = null; + + const animate = (timestamp) => { + if (!startTime) startTime = timestamp; + const elapsed = timestamp - startTime; + const rawProgress = Math.min(elapsed / duration, 1); + + // Smooth easing function + const easedProgress = 1 - Math.pow(1 - rawProgress, 3); + setProgress(easedProgress); + + if (rawProgress < 1) { + animationFrameId = requestAnimationFrame(animate); + } else { + setIsComplete(true); + } + }; + + const startAnimation = () => { + startTime = null; + animationFrameId = requestAnimationFrame(animate); + }; + + const timeoutId = setTimeout(startAnimation, delay); + + return () => { + clearTimeout(timeoutId); + if (animationFrameId) cancelAnimationFrame(animationFrameId); + }; + }, [duration, delay]); + + return { progress, isComplete }; +} + +function AnimatedNeuralNode({ position, color, size = 0.08, delay = 0, animationProgress = 1 }) { const ref = useRef(); + const initialPos = useMemo(() => new THREE.Vector3(0, 0, 0), []); + const targetPos = useMemo(() => new THREE.Vector3(...position), [position]); useFrame((state) => { if (ref.current) { - const time = state.clock.elapsedTime + delay; - ref.current.scale.x = 1 + Math.sin(time * 2) * 0.15; - ref.current.scale.y = 1 + Math.sin(time * 2) * 0.15; - ref.current.scale.z = 1 + Math.sin(time * 2) * 0.15; + // Position animation + ref.current.position.lerpVectors(initialPos, targetPos, animationProgress); + + // Scale animation only when animation is mostly complete + if (animationProgress > 0.8) { + const time = state.clock.elapsedTime + delay; + const scale = 1 + Math.sin(time * 2) * 0.15; + ref.current.scale.setScalar(scale); + } else { + ref.current.scale.setScalar(1); + } } }); return ( - + new THREE.Vector3(0, 0, 0), []); + const targetPos = useMemo(() => new THREE.Vector3(...position), [position]); + const nodes = useMemo(() => { const nodePositions = []; - const nodeCount = 12; + const nodeCount = 8; // Reduced for performance const radius = 1.2; for (let i = 0; i < nodeCount; i++) { @@ -66,50 +123,58 @@ function NeuralStructure({ const conns = []; const nodeCount = nodes.length; + // Simplified connection logic for (let i = 0; i < nodeCount; i++) { - const distances = nodes.map((node, j) => ({ - index: j, - distance: Math.hypot( - node[0] - nodes[i][0], - node[1] - nodes[i][1], - node[2] - nodes[i][2] - ), - })); - - distances.sort((a, b) => a.distance - b.distance); - - for (let j = 1; j <= 3 && j < distances.length; j++) { - const targetIndex = distances[j].index; - if (i < targetIndex) { - conns.push([i, targetIndex]); - } + for (let j = i + 1; j < Math.min(i + 3, nodeCount); j++) { + conns.push([i, j]); } } return conns; }, [nodes]); + useFrame(() => { + if (groupRef.current) { + groupRef.current.position.lerpVectors(initialPos, targetPos, animationProgress); + } + }); + return ( - + {nodes.map((pos, i) => ( ))} - {connections.map(([start, end], i) => ( - - ))} + {connections.map(([start, end], i) => { + // Calculate positions based on animation progress + const startPos = [ + nodes[start][0] * animationProgress, + nodes[start][1] * animationProgress, + nodes[start][2] * animationProgress + ]; + const endPos = [ + nodes[end][0] * animationProgress, + nodes[end][1] * animationProgress, + nodes[end][2] * animationProgress + ]; + + return ( + + ); + })} ); } @@ -181,6 +246,11 @@ export default function Home() { ]; const [isMobile, setIsMobile] = useState(false); + const [showText, setShowText] = useState(false); + const [showButton, setShowButton] = useState(false); + + // Use optimized animation hook + const { progress: animationProgress, isComplete } = useAnimationProgress(2000, 300); useEffect(() => { const checkMobile = () => { @@ -189,8 +259,21 @@ export default function Home() { checkMobile(); window.addEventListener('resize', checkMobile); + + // Show text when animation is complete + if (isComplete) { + const textTimeout = setTimeout(() => { + setShowText(true); + const buttonTimeout = setTimeout(() => { + setShowButton(true); + }, 800); + return () => clearTimeout(buttonTimeout); + }, 200); + return () => clearTimeout(textTimeout); + } + return () => window.removeEventListener('resize', checkMobile); - }, []); + }, [isComplete]); const structures = useMemo(() => { const positions = [ @@ -227,20 +310,21 @@ export default function Home() { [0, -5, -4], ]; - return positions.map((position, i) => ({ + // Reduce structures on mobile for performance + const filteredPositions = isMobile ? positions.slice(0, 15) : positions; + + return filteredPositions.map((position, i) => ({ position, color: colors[i % colors.length], structureIndex: i, })); - }, [colors]); + }, [colors, isMobile]); const handleExploreClick = () => { - // Dispatch custom event to open navbar if (typeof window !== "undefined") { window.dispatchEvent(new CustomEvent("open-navigation")); } - // Also scroll to next section if needed window.scrollTo({ top: window.innerHeight, behavior: 'smooth' @@ -256,66 +340,49 @@ export default function Home() {
- {/* Main Content Container */} - - - - MACHINE LEARNING FOR EVERYONE - - - - (ML4E) - + {/* Main Content Container - Animation controlled by showText state */} +
+ {/* Text appears only after neural networks spread */} + {showText && ( +
+ + MACHINE LEARNING FOR EVERYONE + + + + (ML4E) + - - THE OFFICIAL MACHINE LEARNING CLUB OF NIT ROURKELA - + + THE OFFICIAL MACHINE LEARNING CLUB OF NIT ROURKELA + +
+ )} - {/* Eye-catching Learn More Button */} + {/* Button appears after text animation */} + {showButton && ( @@ -340,32 +407,35 @@ export default function Home() {
- - + )} +
- {/* 3D Canvas with touch event handling */} + {/* 3D Canvas */}
- - - - + + + + {structures.map((props, i) => ( - + ))} - {!isMobile && ( + {!isMobile && animationProgress > 0.8 && (