chore:animation

This commit is contained in:
Ashwani Senapati
2026-01-20 07:51:41 +05:30
parent fb46bcab1f
commit 7e766a029d

View File

@@ -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 = "" }) => (
<div
@@ -15,20 +16,71 @@ 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 (
<Sphere ref={ref} args={[size, 32, 32]} position={position}>
<Sphere ref={ref} args={[size, 16, 16]}>
<meshStandardMaterial
color={color}
emissive={color}
@@ -43,10 +95,15 @@ function NeuralStructure({
position = [0, 0, 0],
color = "#00faff",
structureIndex = 0,
animationProgress = 1,
}) {
const groupRef = useRef();
const initialPos = useMemo(() => 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 (
<group position={position}>
<group ref={groupRef}>
{nodes.map((pos, i) => (
<AnimatedNeuralNode
key={i}
position={pos}
color={color}
delay={i * 0.1}
delay={i * 0.05} // Reduced delay
animationProgress={animationProgress}
/>
))}
{connections.map(([start, end], i) => (
<Line
key={i}
points={[nodes[start], nodes[end]]}
color={color}
lineWidth={1.5}
transparent
opacity={0.4}
toneMapped={false}
/>
))}
{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 (
<Line
key={i}
points={[startPos, endPos]}
color={color}
lineWidth={1}
transparent
opacity={0.3 * animationProgress}
toneMapped={false}
/>
);
})}
</group>
);
}
@@ -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() {
<div className={`absolute inset-0 flex ${isMobile ? 'items-start pt-32' : 'items-center justify-center'} z-10 pointer-events-none`}>
<Glow />
{/* Main Content Container */}
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 1.5, ease: "easeOut", delay: 0.5 }}
className={`flex flex-col items-center justify-center px-8 py-6 rounded-2xl bg-slate-900/40 backdrop-blur-xl border border-cyan-500/30 shadow-2xl text-center pointer-events-auto mx-4 ${isMobile ? 'mt-8 max-w-[90%]' : 'max-w-4xl'}`}
>
<motion.div
initial="hidden"
animate="visible"
variants={{
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
staggerChildren: 0.3,
delayChildren: 0.2
}
}
}}
className="flex flex-col items-center"
>
<motion.h1
variants={{
hidden: { opacity: 0, y: -20 },
visible: { opacity: 1, y: 0, transition: { duration: 0.8, ease: "easeOut" } }
}}
className={`${isMobile ? 'text-4xl' : 'text-3xl md:text-7xl'} font-bold text-cyan-200 drop-shadow-[0_0_20px_#00faff] mb-4 special-font`}
style={{ fontFamily: "Orbitron, sans-serif" }}
>
<b>MACHINE LEARNING FOR EVERYONE</b>
</motion.h1>
<motion.div
variants={{
hidden: { opacity: 0, scale: 0.9 },
visible: { opacity: 1, scale: 1, transition: { duration: 0.8, ease: "easeOut" } }
}}
className={`${isMobile ? 'text-xl' : 'text-2xl md:text-5xl'} font-bold text-cyan-300 drop-shadow-[0_0_15px_#00d9ff] mb-4`}
style={{ fontFamily: "Orbitron, sans-serif" }}
>
(ML4E)
</motion.div>
{/* Main Content Container - Animation controlled by showText state */}
<div className={`flex flex-col items-center justify-center px-8 py-6 rounded-2xl bg-slate-900/40 backdrop-blur-xl border border-cyan-500/30 shadow-2xl text-center pointer-events-auto mx-4 ${isMobile ? 'mt-8 max-w-[90%]' : 'max-w-4xl'}`}>
{/* Text appears only after neural networks spread */}
{showText && (
<div className="flex flex-col items-center">
<motion.h1
initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8, ease: "easeOut" }}
className={`${isMobile ? 'text-4xl' : 'text-3xl md:text-7xl'} font-bold text-cyan-200 drop-shadow-[0_0_20px_#00faff] mb-4 special-font`}
style={{ fontFamily: "Orbitron, sans-serif" }}
>
<b>MACHINE LEARNING FOR EVERYONE</b>
</motion.h1>
<motion.div
initial={{ opacity: 0, scale: 0.9 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ duration: 0.8, ease: "easeOut", delay: 0.2 }}
className={`${isMobile ? 'text-xl' : 'text-2xl md:text-5xl'} font-bold text-cyan-300 drop-shadow-[0_0_15px_#00d9ff] mb-4`}
style={{ fontFamily: "Orbitron, sans-serif" }}
>
(ML4E)
</motion.div>
<motion.p
variants={{
hidden: { opacity: 0, y: 20 },
visible: { opacity: 1, y: 0, transition: { duration: 0.8, ease: "easeOut" } }
}}
className={`mt-2 ${isMobile ? 'text-sm' : 'text-base md:text-xl'} text-blue-300 drop-shadow-[0_0_10px_#00d9ff] mb-8 md:mb-12`}
style={{ fontFamily: "Roboto, sans-serif" }}
>
THE OFFICIAL MACHINE LEARNING CLUB OF NIT ROURKELA
</motion.p>
<motion.p
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8, ease: "easeOut", delay: 0.4 }}
className={`mt-2 ${isMobile ? 'text-sm' : 'text-base md:text-xl'} text-blue-300 drop-shadow-[0_0_10px_#00d9ff] mb-8 md:mb-12`}
style={{ fontFamily: "Roboto, sans-serif" }}
>
THE OFFICIAL MACHINE LEARNING CLUB OF NIT ROURKELA
</motion.p>
</div>
)}
{/* Eye-catching Learn More Button */}
{/* Button appears after text animation */}
{showButton && (
<motion.button
initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8, delay: 1.5, ease: "easeOut" }}
transition={{ duration: 0.8, ease: "easeOut" }}
onClick={handleExploreClick}
className="group relative overflow-hidden px-8 md:px-12 py-3 md:py-4 rounded-full bg-gradient-to-r from-cyan-500/20 via-cyan-600/30 to-blue-500/20 border border-cyan-400/50 hover:border-cyan-300 transition-all duration-300 cursor-pointer shadow-[0_0_30px_rgba(0,245,255,0.3)] hover:shadow-[0_0_40px_rgba(0,245,255,0.5)]"
>
@@ -340,32 +407,35 @@ export default function Home() {
<div className="absolute -top-1 -left-1 w-2 h-2 rounded-full bg-cyan-400 opacity-70 group-hover:opacity-100 group-hover:animate-ping" />
<div className="absolute -bottom-1 -right-1 w-2 h-2 rounded-full bg-blue-400 opacity-70 group-hover:opacity-100 group-hover:animate-ping delay-75" />
</motion.button>
</motion.div>
</motion.div>
)}
</div>
</div>
{/* 3D Canvas with touch event handling */}
{/* 3D Canvas */}
<div className="fixed inset-0 z-0">
<Canvas
camera={{ position: [0, 0, 15], fov: 60 }}
style={{ touchAction: isMobile ? 'none' : 'auto' }}
dpr={[1, isMobile ? 1 : 1.5]}
performance={{ min: 0.8 }}
gl={{ antialias: false }}
>
<color attach="background" args={["#000000"]} />
<CyberGridBackground />
<ambientLight intensity={0.5} />
<pointLight position={[10, 10, 10]} intensity={1.5} />
<pointLight position={[-10, -10, -10]} intensity={0.8} color="#00faff" />
<CyberGridBackground animationProgress={animationProgress} />
<ambientLight intensity={0.5 * animationProgress} />
<pointLight position={[10, 10, 10]} intensity={1.5 * animationProgress} />
<pointLight position={[-10, -10, -10]} intensity={0.8 * animationProgress} color="#00faff" />
{structures.map((props, i) => (
<NeuralStructure key={i} {...props} />
<NeuralStructure key={i} {...props} animationProgress={animationProgress} />
))}
{!isMobile && (
{!isMobile && animationProgress > 0.8 && (
<OrbitControls
enableZoom={false}
autoRotate={false}
enablePan={false}
enableRotate={false}
enableRotate={true}
rotateSpeed={0.3}
minPolarAngle={Math.PI / 2}
maxPolarAngle={Math.PI / 2}
minDistance={10}
@@ -375,10 +445,10 @@ export default function Home() {
<EffectComposer>
<Bloom
intensity={2}
kernelSize={3}
luminanceThreshold={0.1}
luminanceSmoothing={0.9}
intensity={2 * animationProgress}
kernelSize={2}
luminanceThreshold={0.2}
luminanceSmoothing={0.8}
/>
</EffectComposer>
</Canvas>