391 lines
12 KiB
TypeScript
391 lines
12 KiB
TypeScript
"use client";
|
||
|
||
import { useEffect, useRef } from "react";
|
||
import { motion } from "framer-motion";
|
||
import { gsap } from "@/lib/gsap";
|
||
|
||
/**
|
||
* Botanisch präzise verschnörkelte Ranken:
|
||
* Das Wachstum ist exakt auf den gesamten Scrollverlauf abgestimmt (kein zu schnelles Wachsen).
|
||
* Die Ranken entfalten sich genau synchron zur Scroll-Position und erreichen den
|
||
* untersten Rand erst, wenn man auch ganz unten angekommen ist.
|
||
*/
|
||
|
||
// Botanisch geformtes Blatt mit spitzer Silhouetten-Form, Blattader & dezentem Hover-Wiegeln
|
||
function Leaf({
|
||
x,
|
||
y,
|
||
rotate = 0,
|
||
scale = 1,
|
||
className = "",
|
||
}: {
|
||
x: number;
|
||
y: number;
|
||
rotate?: number;
|
||
scale?: number;
|
||
className?: string;
|
||
}) {
|
||
return (
|
||
<motion.g
|
||
transform={`translate(${x}, ${y}) rotate(${rotate}) scale(${scale})`}
|
||
whileHover={{ scale: scale * 1.18, rotate: rotate + 6 }}
|
||
transition={{ type: "spring", stiffness: 160, damping: 16 }}
|
||
className={`vine-leaf pointer-events-auto cursor-pointer ${className}`}
|
||
>
|
||
{/* Äußere Blattsilhouette – edles Gelb-Grün / Smaragd */}
|
||
<path
|
||
d="M 0 0 C 14 -20 36 -12 42 0 C 36 12 14 20 0 0 Z"
|
||
fill="#0d7a57"
|
||
fillOpacity="0.92"
|
||
className="transition-all duration-300 hover:fill-[#109b6f]"
|
||
/>
|
||
{/* Feine Blattader */}
|
||
<path
|
||
d="M 0 0 C 15 -3 28 -2 39 0"
|
||
stroke="#ffffff"
|
||
strokeOpacity="0.75"
|
||
strokeWidth="1.2"
|
||
strokeLinecap="round"
|
||
/>
|
||
</motion.g>
|
||
);
|
||
}
|
||
|
||
// Offene Spiral-Tendril (Ranken-Kringel)
|
||
function Tendril({ d, className = "" }: { d: string; className?: string }) {
|
||
return (
|
||
<path
|
||
className={`vine-tendril ${className}`}
|
||
d={d}
|
||
fill="none"
|
||
stroke="#161513"
|
||
strokeWidth="1.4"
|
||
strokeLinecap="round"
|
||
vectorEffect="non-scaling-stroke"
|
||
/>
|
||
);
|
||
}
|
||
|
||
// Ranke 1 (Links-Oben nach Mitte-Rechts - Hero Bereich)
|
||
const STEM_1 =
|
||
"M -40 20 C 120 70, 240 160, 320 300 C 400 440, 280 600, 420 740 C 520 860, 680 940, 820 1060";
|
||
|
||
const TENDRILS_1 = [
|
||
"M 240 160 C 275 135, 295 160, 275 185 C 255 195, 240 178, 252 165",
|
||
"M 420 740 C 465 710, 485 745, 460 770 C 435 785, 415 755, 428 738",
|
||
];
|
||
|
||
const LEAF_NODES_1 = [
|
||
{ x: 120, y: 70, rotate: -25, scale: 0.9 },
|
||
{ x: 240, y: 160, rotate: 42, scale: 1.1 },
|
||
{ x: 320, y: 300, rotate: -45, scale: 1.0 },
|
||
{ x: 400, y: 440, rotate: 32, scale: 0.95 },
|
||
{ x: 420, y: 740, rotate: -35, scale: 1.05 },
|
||
{ x: 680, y: 940, rotate: 50, scale: 0.9 },
|
||
];
|
||
|
||
// Ranke 2 (Rechts-Oben edel schwebend - Mittlerer Bereich)
|
||
const STEM_2 =
|
||
"M 920 30 C 760 150, 620 310, 710 490 C 800 680, 620 830, 740 1020 C 860 1180, 720 1340, 840 1500";
|
||
|
||
const TENDRILS_2 = [
|
||
"M 710 490 C 665 460, 640 495, 665 520 C 690 535, 710 505, 695 488",
|
||
"M 740 1020 C 695 990, 670 1025, 695 1050 C 720 1065, 740 1035, 725 1018",
|
||
];
|
||
|
||
const LEAF_NODES_2 = [
|
||
{ x: 760, y: 150, rotate: 155, scale: 1.0 },
|
||
{ x: 710, y: 490, rotate: -130, scale: 1.15 },
|
||
{ x: 620, y: 830, rotate: 140, scale: 0.9 },
|
||
{ x: 740, y: 1020, rotate: -150, scale: 1.05 },
|
||
{ x: 840, y: 1500, rotate: 135, scale: 1.1 },
|
||
];
|
||
|
||
// Ranke 3 (Untere Ranke Links – Wächst erst in der 2. Seitenhälfte bis in den Footer)
|
||
const STEM_3 =
|
||
"M -30 50 C 210 180, 440 240, 520 430 C 600 620, 420 810, 610 1000 C 780 1170, 690 1340, 840 1550 C 920 1720, 780 1880, 910 2050";
|
||
|
||
const TENDRILS_3 = [
|
||
"M 520 430 C 565 400, 590 435, 565 460 C 540 475, 518 445, 532 428",
|
||
"M 610 1000 C 655 970, 680 1005, 655 1030 C 630 1045, 608 1015, 622 998",
|
||
"M 840 1550 C 885 1520, 910 1555, 885 1580 C 860 1595, 838 1565, 852 1548",
|
||
];
|
||
|
||
const LEAF_NODES_3 = [
|
||
{ x: 210, y: 180, rotate: -30, scale: 1.05 },
|
||
{ x: 440, y: 240, rotate: 35, scale: 0.9 },
|
||
{ x: 520, y: 430, rotate: -40, scale: 1.1 },
|
||
{ x: 420, y: 810, rotate: 45, scale: 1.0 },
|
||
{ x: 610, y: 1000, rotate: -35, scale: 1.15 },
|
||
{ x: 780, y: 1170, rotate: 30, scale: 0.95 },
|
||
{ x: 840, y: 1550, rotate: -25, scale: 1.1 },
|
||
{ x: 880, y: 1860, rotate: 40, scale: 0.95 },
|
||
];
|
||
|
||
// Ranke 4 (Untere Ranke Rechts – Wächst erst in der 2. Seitenhälfte bis in den Footer)
|
||
const STEM_4 =
|
||
"M 900 40 C 740 220, 620 400, 700 600 C 780 800, 600 1000, 720 1220 C 840 1440, 680 1680, 780 1920 C 880 2080, 750 2200, 850 2320";
|
||
|
||
const TENDRILS_4 = [
|
||
"M 700 600 C 655 570, 630 605, 655 630 C 680 645, 700 615, 685 598",
|
||
"M 720 1220 C 675 1190, 650 1225, 675 1250 C 700 1265, 720 1235, 705 1218",
|
||
];
|
||
|
||
const LEAF_NODES_4 = [
|
||
{ x: 740, y: 220, rotate: 145, scale: 1.0 },
|
||
{ x: 700, y: 600, rotate: 125, scale: 0.95 },
|
||
{ x: 600, y: 1000, rotate: -140, scale: 1.1 },
|
||
{ x: 720, y: 1220, rotate: 135, scale: 1.05 },
|
||
{ x: 780, y: 1920, rotate: -130, scale: 0.95 },
|
||
];
|
||
|
||
export default function ScrollFlourishes() {
|
||
const rootRef = useRef<HTMLDivElement>(null);
|
||
|
||
useEffect(() => {
|
||
const root = rootRef.current;
|
||
if (!root) return;
|
||
|
||
const mm = gsap.matchMedia();
|
||
|
||
mm.add("(prefers-reduced-motion: no-preference)", () => {
|
||
// 1. Dasharray & Offset für alle Linienpfade vorbereiten
|
||
const strokePaths = root.querySelectorAll<SVGPathElement>(
|
||
".vine-stem, .vine-tendril",
|
||
);
|
||
strokePaths.forEach((p) => {
|
||
const len = p.getTotalLength();
|
||
p.style.strokeDasharray = `${len}`;
|
||
p.style.strokeDashoffset = `${len}`;
|
||
});
|
||
|
||
const leaves = root.querySelectorAll<SVGGElement>(".vine-leaf");
|
||
gsap.set(leaves, { opacity: 0, scale: 0, transformOrigin: "0px 0px" });
|
||
|
||
// 2. Perfekt getaktete ScrollTrigger Timeline (Spannweite von 0 bis 1.0 des gesamten Dokument-Scrolls)
|
||
const tl = gsap.timeline({
|
||
scrollTrigger: {
|
||
trigger: document.body,
|
||
start: "top top",
|
||
end: "bottom bottom",
|
||
scrub: 0.7,
|
||
},
|
||
});
|
||
|
||
// Ranke 1 (Hero): Wächst im oberen Drittel (0.0 bis 0.38)
|
||
tl.to(
|
||
root.querySelectorAll(".vine-stem-1"),
|
||
{ strokeDashoffset: 0, ease: "none", duration: 0.38 },
|
||
0,
|
||
);
|
||
tl.to(
|
||
root.querySelectorAll(".vine-tendril-1"),
|
||
{ strokeDashoffset: 0, ease: "none", duration: 0.08 },
|
||
0.25,
|
||
);
|
||
|
||
// Ranke 2 (Mitte-Rechts): Wächst im mittleren Bereich (0.22 bis 0.68)
|
||
tl.to(
|
||
root.querySelectorAll(".vine-stem-2"),
|
||
{ strokeDashoffset: 0, ease: "none", duration: 0.46 },
|
||
0.22,
|
||
);
|
||
tl.to(
|
||
root.querySelectorAll(".vine-tendril-2"),
|
||
{ strokeDashoffset: 0, ease: "none", duration: 0.08 },
|
||
0.48,
|
||
);
|
||
|
||
// Ranke 3 (Untere Ranke Links): Wächst in der 2. Hälfte bis ganz nach unten (0.45 bis 1.0)
|
||
tl.to(
|
||
root.querySelectorAll(".vine-stem-3"),
|
||
{ strokeDashoffset: 0, ease: "none", duration: 0.55 },
|
||
0.45,
|
||
);
|
||
tl.to(
|
||
root.querySelectorAll(".vine-tendril-3"),
|
||
{ strokeDashoffset: 0, ease: "none", duration: 0.08 },
|
||
0.72,
|
||
);
|
||
|
||
// Ranke 4 (Untere Ranke Rechts): Wächst in der 2. Hälfte bis ganz nach unten (0.50 bis 1.0)
|
||
tl.to(
|
||
root.querySelectorAll(".vine-stem-4"),
|
||
{ strokeDashoffset: 0, ease: "none", duration: 0.5 },
|
||
0.5,
|
||
);
|
||
tl.to(
|
||
root.querySelectorAll(".vine-tendril-4"),
|
||
{ strokeDashoffset: 0, ease: "none", duration: 0.08 },
|
||
0.78,
|
||
);
|
||
|
||
// Blätter entfalten sich organisch zur jeweiligen Wachstumszeit
|
||
tl.to(
|
||
leaves,
|
||
{
|
||
opacity: 1,
|
||
scale: 1,
|
||
ease: "back.out(1.5)",
|
||
duration: 0.1,
|
||
stagger: 0.02,
|
||
},
|
||
0.1,
|
||
);
|
||
|
||
// Subtiler, ruhiger Parallax-Versatz (Ohne hektische Maus-Swayings)
|
||
tl.to(
|
||
".vine-container-1",
|
||
{ yPercent: 6, scale: 1.06, ease: "none", duration: 1 },
|
||
0,
|
||
);
|
||
tl.to(
|
||
".vine-container-2",
|
||
{ yPercent: 8, scale: 0.95, ease: "none", duration: 1 },
|
||
0,
|
||
);
|
||
tl.to(
|
||
".vine-container-3",
|
||
{ yPercent: -4, scale: 1.05, ease: "none", duration: 1 },
|
||
0,
|
||
);
|
||
tl.to(
|
||
".vine-container-4",
|
||
{ yPercent: 5, scale: 1.04, ease: "none", duration: 1 },
|
||
0,
|
||
);
|
||
});
|
||
|
||
return () => mm.revert();
|
||
}, []);
|
||
|
||
return (
|
||
<div
|
||
ref={rootRef}
|
||
aria-hidden
|
||
className="pointer-events-none fixed inset-0 -z-10 overflow-hidden select-none"
|
||
>
|
||
{/* Ranke 1: Links-Oben nach Mitte-Rechts (Hero) */}
|
||
<div className="vine-container-1 absolute left-[-4vw] top-[-2vh] w-[50vw] max-w-[750px] min-w-[320px] opacity-35 transition-opacity duration-500">
|
||
<svg
|
||
viewBox="0 0 850 1100"
|
||
fill="none"
|
||
strokeLinecap="round"
|
||
className="w-full h-auto pointer-events-auto"
|
||
>
|
||
<path
|
||
className="vine-stem vine-stem-1"
|
||
d={STEM_1}
|
||
stroke="#161513"
|
||
strokeWidth="1.6"
|
||
vectorEffect="non-scaling-stroke"
|
||
/>
|
||
{TENDRILS_1.map((d, i) => (
|
||
<Tendril key={i} className="vine-tendril-1" d={d} />
|
||
))}
|
||
{LEAF_NODES_1.map((node, i) => (
|
||
<Leaf
|
||
key={i}
|
||
x={node.x}
|
||
y={node.y}
|
||
rotate={node.rotate}
|
||
scale={node.scale}
|
||
/>
|
||
))}
|
||
</svg>
|
||
</div>
|
||
|
||
{/* Ranke 2: Rechts-Oben (Mittlerer Bereich) */}
|
||
<div className="vine-container-2 absolute right-[-4vw] top-[18vh] w-[54vw] max-w-[820px] min-w-[340px] opacity-30 transition-opacity duration-500">
|
||
<svg
|
||
viewBox="0 0 950 1550"
|
||
fill="none"
|
||
strokeLinecap="round"
|
||
className="w-full h-auto pointer-events-auto"
|
||
>
|
||
<path
|
||
className="vine-stem vine-stem-2"
|
||
d={STEM_2}
|
||
stroke="#161513"
|
||
strokeWidth="1.6"
|
||
vectorEffect="non-scaling-stroke"
|
||
/>
|
||
{TENDRILS_2.map((d, i) => (
|
||
<Tendril key={i} className="vine-tendril-2" d={d} />
|
||
))}
|
||
{LEAF_NODES_2.map((node, i) => (
|
||
<Leaf
|
||
key={i}
|
||
x={node.x}
|
||
y={node.y}
|
||
rotate={node.rotate}
|
||
scale={node.scale}
|
||
/>
|
||
))}
|
||
</svg>
|
||
</div>
|
||
|
||
{/* Ranke 3: Untere Ranke Links – Wächst erst in der 2. Seitenhälfte bis in den Footer */}
|
||
<div className="vine-container-3 absolute left-[-3vw] top-[42vh] w-[58vw] max-w-[920px] min-w-[360px] opacity-35 transition-opacity duration-500">
|
||
<svg
|
||
viewBox="0 0 950 2100"
|
||
fill="none"
|
||
strokeLinecap="round"
|
||
className="w-full h-auto pointer-events-auto"
|
||
>
|
||
<path
|
||
className="vine-stem vine-stem-3"
|
||
d={STEM_3}
|
||
stroke="#161513"
|
||
strokeWidth="1.6"
|
||
vectorEffect="non-scaling-stroke"
|
||
/>
|
||
{TENDRILS_3.map((d, i) => (
|
||
<Tendril key={i} className="vine-tendril-3" d={d} />
|
||
))}
|
||
{LEAF_NODES_3.map((node, i) => (
|
||
<Leaf
|
||
key={i}
|
||
x={node.x}
|
||
y={node.y}
|
||
rotate={node.rotate}
|
||
scale={node.scale}
|
||
/>
|
||
))}
|
||
</svg>
|
||
</div>
|
||
|
||
{/* Ranke 4: Untere Ranke Rechts – Wächst erst in der 2. Seitenhälfte bis in den Footer */}
|
||
<div className="vine-container-4 absolute right-[-3vw] top-[48vh] w-[56vw] max-w-[900px] min-w-[350px] opacity-35 transition-opacity duration-500">
|
||
<svg
|
||
viewBox="0 0 950 2350"
|
||
fill="none"
|
||
strokeLinecap="round"
|
||
className="w-full h-auto pointer-events-auto"
|
||
>
|
||
<path
|
||
className="vine-stem vine-stem-4"
|
||
d={STEM_4}
|
||
stroke="#161513"
|
||
strokeWidth="1.6"
|
||
vectorEffect="non-scaling-stroke"
|
||
/>
|
||
{TENDRILS_4.map((d, i) => (
|
||
<Tendril key={i} className="vine-tendril-4" d={d} />
|
||
))}
|
||
{LEAF_NODES_4.map((node, i) => (
|
||
<Leaf
|
||
key={i}
|
||
x={node.x}
|
||
y={node.y}
|
||
rotate={node.rotate}
|
||
scale={node.scale}
|
||
/>
|
||
))}
|
||
</svg>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|