feat: Implement initial cinematic portfolio UI with animated components and page structure.
This commit is contained in:
1 parent
9d779390e5
commit
2ede7e98f9
18 files changed
+2107
-147
No files matched your search
@@ -0,0 +1,173 @@
|
||||
"use client";
|
||||
|
||||
import { useRef, useEffect, type ReactNode } from "react";
|
||||
|
||||
interface CinematicSectionProps {
|
||||
children: ReactNode;
|
||||
className?: string;
|
||||
effect?: "fade-scale" | "clip-reveal" | "parallax-depth" | "rotate-in";
|
||||
id?: string;
|
||||
}
|
||||
|
||||
export default function CinematicSection({
|
||||
children,
|
||||
className = "",
|
||||
effect = "fade-scale",
|
||||
id,
|
||||
}: CinematicSectionProps) {
|
||||
const sectionRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const el = sectionRef.current;
|
||||
if (!el) return;
|
||||
|
||||
// Check reduced motion preference
|
||||
if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return;
|
||||
|
||||
let cleanup: (() => void) | undefined;
|
||||
|
||||
const setup = async () => {
|
||||
const [{ gsap }, { ScrollTrigger }] = await Promise.all([
|
||||
import("gsap"),
|
||||
import("gsap/ScrollTrigger"),
|
||||
]);
|
||||
|
||||
gsap.registerPlugin(ScrollTrigger);
|
||||
|
||||
switch (effect) {
|
||||
case "fade-scale": {
|
||||
gsap.set(el, {
|
||||
opacity: 0,
|
||||
scale: 0.92,
|
||||
transformOrigin: "center center",
|
||||
});
|
||||
|
||||
const tl = gsap.timeline({
|
||||
scrollTrigger: {
|
||||
trigger: el,
|
||||
start: "top 85%",
|
||||
end: "top 20%",
|
||||
scrub: 0.8,
|
||||
},
|
||||
});
|
||||
|
||||
tl.to(el, {
|
||||
opacity: 1,
|
||||
scale: 1,
|
||||
ease: "power2.out",
|
||||
});
|
||||
|
||||
cleanup = () => {
|
||||
tl.scrollTrigger?.kill();
|
||||
tl.kill();
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
||||
case "clip-reveal": {
|
||||
gsap.set(el, {
|
||||
clipPath: "inset(15% 5% 15% 5%)",
|
||||
opacity: 0.3,
|
||||
});
|
||||
|
||||
const tl = gsap.timeline({
|
||||
scrollTrigger: {
|
||||
trigger: el,
|
||||
start: "top 80%",
|
||||
end: "top 15%",
|
||||
scrub: 1,
|
||||
},
|
||||
});
|
||||
|
||||
tl.to(el, {
|
||||
clipPath: "inset(0% 0% 0% 0%)",
|
||||
opacity: 1,
|
||||
ease: "power3.out",
|
||||
});
|
||||
|
||||
cleanup = () => {
|
||||
tl.scrollTrigger?.kill();
|
||||
tl.kill();
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
||||
case "parallax-depth": {
|
||||
gsap.set(el, {
|
||||
y: 80,
|
||||
opacity: 0,
|
||||
rotateX: 4,
|
||||
transformPerspective: 1200,
|
||||
transformOrigin: "center bottom",
|
||||
});
|
||||
|
||||
const tl = gsap.timeline({
|
||||
scrollTrigger: {
|
||||
trigger: el,
|
||||
start: "top 90%",
|
||||
end: "top 30%",
|
||||
scrub: 0.6,
|
||||
},
|
||||
});
|
||||
|
||||
tl.to(el, {
|
||||
y: 0,
|
||||
opacity: 1,
|
||||
rotateX: 0,
|
||||
ease: "power2.out",
|
||||
});
|
||||
|
||||
cleanup = () => {
|
||||
tl.scrollTrigger?.kill();
|
||||
tl.kill();
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
||||
case "rotate-in": {
|
||||
gsap.set(el, {
|
||||
opacity: 0,
|
||||
rotateY: -5,
|
||||
x: -60,
|
||||
transformPerspective: 1000,
|
||||
transformOrigin: "left center",
|
||||
});
|
||||
|
||||
const tl = gsap.timeline({
|
||||
scrollTrigger: {
|
||||
trigger: el,
|
||||
start: "top 80%",
|
||||
end: "top 25%",
|
||||
scrub: 0.8,
|
||||
},
|
||||
});
|
||||
|
||||
tl.to(el, {
|
||||
opacity: 1,
|
||||
rotateY: 0,
|
||||
x: 0,
|
||||
ease: "power3.out",
|
||||
});
|
||||
|
||||
cleanup = () => {
|
||||
tl.scrollTrigger?.kill();
|
||||
tl.kill();
|
||||
};
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
void setup();
|
||||
|
||||
return () => {
|
||||
cleanup?.();
|
||||
};
|
||||
}, [effect]);
|
||||
|
||||
return (
|
||||
<div ref={sectionRef} className={className} id={id}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user