portfolio_v2/app/intro.tsx

71 lines
1.9 KiB
TypeScript
Raw Normal View History

"use client";
import { useEffect } from "react";
import { usePathname } from "next/navigation";
import gsap from "gsap";
// Pages qui ont l'écran de chargement (avec/sans slash final selon la config).
const WITH_INTRO = new Set(["/", "/cv", "/cv/"]);
export default function Intro() {
const pathname = usePathname();
useEffect(() => {
const overlay = "[data-intro-overlay]";
const reveal = gsap.utils.toArray<HTMLElement>("[data-reveal]");
const reduce = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
if (reduce) {
gsap.set(overlay, { display: "none" });
return;
}
if (reveal.length) gsap.set(reveal, { autoAlpha: 0, y: 12 });
const tl = gsap.timeline({
defaults: { ease: "power3.out" },
onComplete: () => {
if (reveal.length) gsap.set(reveal, { clearProps: "all" });
},
});
tl.to("[data-intro-mark]", { autoAlpha: 1, scale: 1, duration: 0.45, ease: "power2.out" }, 0)
.fromTo(
"[data-intro-bar]",
{ scaleX: 0 },
{ scaleX: 1, duration: 0.85, ease: "power2.inOut" },
0.1,
)
.to(overlay, { yPercent: -100, duration: 0.5, ease: "power3.inOut" }, ">-0.1");
if (reveal.length) {
tl.to(reveal, { autoAlpha: 1, y: 0, duration: 0.5, stagger: 0.07 }, "<0.2");
}
tl.set(overlay, { display: "none" });
}, []);
if (!WITH_INTRO.has(pathname)) return null;
return (
<div
data-intro-overlay
className="fixed inset-0 z-50 flex items-center justify-center bg-canvas"
aria-hidden="true"
>
<div
data-intro-bar
className="absolute inset-x-0 top-0 h-[3px] origin-left bg-accent"
style={{ transform: "scaleX(0)" }}
/>
<div
data-intro-mark
className="grid size-12 place-items-center rounded-xl bg-surface text-lg font-bold"
style={{ opacity: 0, transform: "scale(0.94)" }}
>
F
</div>
</div>
);
}