74 lines
2.4 KiB
TypeScript
74 lines
2.4 KiB
TypeScript
"use client";
|
|
|
|
/**
|
|
* Cinematic section divider — a thin gradient rule that DRAWS itself across the
|
|
* viewport (GSAP DrawSVG) as it scrolls into view, with a travelling node that
|
|
* rides along it. Pure decoration; adds rhythm + "life" between sections so the
|
|
* page never reads as empty stacked blocks.
|
|
* Reduced-motion: renders fully drawn, no travel.
|
|
*/
|
|
import { useEffect, useRef } from "react";
|
|
import { gsap } from "./gsap";
|
|
|
|
export default function SectionDivider({ label }: { label?: string }) {
|
|
const ref = useRef<HTMLDivElement>(null);
|
|
|
|
useEffect(() => {
|
|
const el = ref.current;
|
|
if (!el) return;
|
|
const reduce = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
if (reduce) return;
|
|
|
|
const ctx = gsap.context(() => {
|
|
const tl = gsap.timeline({
|
|
scrollTrigger: { trigger: el, start: "top 88%", once: true },
|
|
});
|
|
tl.from(".divider__line", {
|
|
drawSVG: "50% 50%",
|
|
duration: 1.2,
|
|
ease: "power2.inOut",
|
|
})
|
|
.from(
|
|
".divider__node",
|
|
{ scale: 0, transformOrigin: "center", duration: 0.5, ease: "back.out(2.2)" },
|
|
"-=0.4"
|
|
)
|
|
.from(
|
|
".divider__label",
|
|
{ autoAlpha: 0, y: 8, filter: "blur(4px)", duration: 0.6, ease: "power2.out" },
|
|
"-=0.4"
|
|
);
|
|
|
|
// node drifts gently along the line on scroll => parallax life
|
|
gsap.to(".divider__node", {
|
|
x: 120,
|
|
ease: "none",
|
|
scrollTrigger: { trigger: el, start: "top bottom", end: "bottom top", scrub: 1 },
|
|
});
|
|
}, el);
|
|
|
|
return () => ctx.revert();
|
|
}, []);
|
|
|
|
return (
|
|
<div ref={ref} className="divider wrap" aria-hidden="true">
|
|
{label && <span className="divider__label">{label}</span>}
|
|
<svg className="divider__svg" viewBox="0 0 1000 12" preserveAspectRatio="none">
|
|
<defs>
|
|
<linearGradient id="dividerGrad" x1="0" y1="0" x2="1" y2="0">
|
|
<stop offset="0%" stopColor="#3b82f6" stopOpacity="0.1" />
|
|
<stop offset="50%" stopColor="#8b5cf6" stopOpacity="0.9" />
|
|
<stop offset="100%" stopColor="#10b981" stopOpacity="0.1" />
|
|
</linearGradient>
|
|
</defs>
|
|
<line
|
|
className="divider__line"
|
|
x1="0" y1="6" x2="1000" y2="6"
|
|
stroke="url(#dividerGrad)"
|
|
strokeWidth="1.5"
|
|
/>
|
|
</svg>
|
|
<span className="divider__node" />
|
|
</div>
|
|
);
|
|
}
|