feat: animated hero with parallax rocket + scroll reveal animations

- Add animated hero section with parallax rocket SVG that descends on scroll
- Add floating decorative particles and gradient layers in hero
- Add staggered text reveal animation on hero h1
- Create ScrollReveal component (IntersectionObserver-based fade/slide)
- Create AnimatedCounter component for stat numbers
- Add scroll animations to all sections (Problematique, System, Demos, AboutMe, FAQ, Contact, Footer)
- Add smooth FAQ accordion transitions
- Add extensive CSS keyframe animations (float, flame, particles, stat glow)

https://claude.ai/code/session_01V8YAjpqRQ3bfBYsABYsEgo
This commit is contained in:
Claude
2026-02-16 19:09:16 +00:00
parent e94a03f302
commit 6555969c30
13 changed files with 1143 additions and 444 deletions

View File

@@ -0,0 +1,61 @@
"use client";
import { useEffect, useRef, useState } from "react";
interface AnimatedCounterProps {
end: number;
duration?: number;
suffix?: string;
prefix?: string;
className?: string;
}
export default function AnimatedCounter({
end,
duration = 2000,
suffix = "",
prefix = "",
className = "",
}: AnimatedCounterProps) {
const [count, setCount] = useState(0);
const ref = useRef<HTMLSpanElement>(null);
const hasAnimated = useRef(false);
useEffect(() => {
const el = ref.current;
if (!el) return;
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting && !hasAnimated.current) {
hasAnimated.current = true;
const startTime = performance.now();
const animate = (currentTime: number) => {
const elapsed = currentTime - startTime;
const progress = Math.min(elapsed / duration, 1);
// Ease out cubic
const eased = 1 - Math.pow(1 - progress, 3);
setCount(Math.floor(eased * end));
if (progress < 1) {
requestAnimationFrame(animate);
}
};
requestAnimationFrame(animate);
}
},
{ threshold: 0.5 }
);
observer.observe(el);
return () => observer.disconnect();
}, [end, duration]);
return (
<span ref={ref} className={className}>
{prefix}{count}{suffix}
</span>
);
}