Initialisieren
This commit is contained in:
327
components/hero.tsx
Normal file
327
components/hero.tsx
Normal file
@@ -0,0 +1,327 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import {
|
||||
motion,
|
||||
useMotionValue,
|
||||
useScroll,
|
||||
useSpring,
|
||||
useTransform,
|
||||
} from "framer-motion";
|
||||
import { ArrowDown } from "lucide-react";
|
||||
import { gsap } from "@/lib/gsap";
|
||||
|
||||
export default function Hero() {
|
||||
const sectionRef = useRef<HTMLElement>(null);
|
||||
|
||||
const { scrollY } = useScroll();
|
||||
|
||||
// Scroll-Transformationen (Reversibel)
|
||||
const ringY = useTransform(scrollY, [0, 1000], [0, 180]);
|
||||
const ringScale = useTransform(scrollY, [0, 1000], [1, 1.18]);
|
||||
const ringOpacity = useTransform(scrollY, [0, 400, 1000, 1500], [1, 1, 0.5, 0]);
|
||||
const ringRotate = useTransform(scrollY, [0, 1000], [0, 10]);
|
||||
|
||||
// Interaktive Cursor-Bewegung (Subtile Spring-Physik für elegantes Hovering)
|
||||
const rawMouseX = useMotionValue(0);
|
||||
const rawMouseY = useMotionValue(0);
|
||||
|
||||
const smoothMouseX = useSpring(rawMouseX, { stiffness: 60, damping: 28 });
|
||||
const smoothMouseY = useSpring(rawMouseY, { stiffness: 60, damping: 28 });
|
||||
|
||||
// Subtile magnetische Linienverschiebung
|
||||
const magnetX = useTransform(smoothMouseX, [-0.5, 0.5], [-10, 10]);
|
||||
const magnetY = useTransform(smoothMouseY, [-0.5, 0.5], [-8, 8]);
|
||||
|
||||
// SVG-Glow-Position (in viewBox 1550 x 950 Koordinaten)
|
||||
const [svgGlow, setSvgGlow] = useState({ x: 775, y: 475 });
|
||||
|
||||
const handleMouseMove = (e: React.MouseEvent<HTMLElement>) => {
|
||||
const rect = sectionRef.current?.getBoundingClientRect();
|
||||
if (!rect) return;
|
||||
|
||||
// Normalisierte Mauskoordinaten (-0.5 bis 0.5)
|
||||
const normX = (e.clientX - rect.left) / rect.width - 0.5;
|
||||
const normY = (e.clientY - rect.top) / rect.height - 0.5;
|
||||
rawMouseX.set(normX);
|
||||
rawMouseY.set(normY);
|
||||
|
||||
// Umrechnung auf SVG-ViewBox-Koordinaten für das sanfte Aufleuchten
|
||||
const svgX = Math.round(((e.clientX - rect.left) / rect.width) * 1550);
|
||||
const svgY = Math.round(((e.clientY - rect.top) / rect.height) * 950);
|
||||
setSvgGlow({ x: svgX, y: svgY });
|
||||
};
|
||||
|
||||
// Text-Intro startet erst, wenn der Preloader das "app:reveal"-Event feuert
|
||||
useEffect(() => {
|
||||
// Der Hero ist das LCP-Element. Auf Mobilgeräten bleibt er deshalb sofort
|
||||
// sichtbar, statt seine endgültige Darstellung durch eine Timeline zu verzögern.
|
||||
if (window.matchMedia("(max-width: 767px)").matches) return;
|
||||
|
||||
let ctx: gsap.Context | undefined;
|
||||
let fallback: ReturnType<typeof setTimeout> | undefined;
|
||||
|
||||
const start = () => {
|
||||
if (ctx) return;
|
||||
ctx = gsap.context(() => {
|
||||
gsap.fromTo(
|
||||
".hero-el",
|
||||
{ y: 34, opacity: 0 },
|
||||
{
|
||||
y: 0,
|
||||
opacity: 1,
|
||||
duration: 1,
|
||||
stagger: 0.1,
|
||||
ease: "power3.out",
|
||||
},
|
||||
);
|
||||
}, sectionRef);
|
||||
};
|
||||
|
||||
const mm = gsap.matchMedia();
|
||||
mm.add("(prefers-reduced-motion: no-preference)", () => {
|
||||
window.addEventListener("app:reveal", start, { once: true });
|
||||
|
||||
// Beim gecachten bzw. mobilen Preloader ist die Seite schon sichtbar,
|
||||
// bevor dieser Effect registriert wird. Dann sofort animieren statt auf
|
||||
// den Fallback-Timer zu warten.
|
||||
if (document.documentElement.dataset.preloader !== "active") {
|
||||
start();
|
||||
} else {
|
||||
fallback = setTimeout(start, 1200);
|
||||
}
|
||||
});
|
||||
|
||||
return () => {
|
||||
mm.revert();
|
||||
window.removeEventListener("app:reveal", start);
|
||||
if (fallback) clearTimeout(fallback);
|
||||
ctx?.revert();
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<section
|
||||
id="top"
|
||||
ref={sectionRef}
|
||||
onMouseMove={handleMouseMove}
|
||||
className="relative flex min-h-[100svh] flex-col justify-center pt-16 cursor-default"
|
||||
>
|
||||
{/* Weicher Spot in der Mitte: Fadet am Rand und unten zu 100% transparent in den Seitenhintergrund aus */}
|
||||
<div
|
||||
aria-hidden
|
||||
className="absolute inset-0 bg-[radial-gradient(ellipse_75%_55%_at_50%_35%,#ffffff_0%,transparent_100%)]"
|
||||
/>
|
||||
|
||||
{/* Multischichtiges Ring-Design: Kreise fließen flüssig über die Sektionsgrenze nach unten */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ duration: 1.5, ease: "easeOut" }}
|
||||
style={{
|
||||
y: ringY,
|
||||
x: magnetX,
|
||||
scale: ringScale,
|
||||
opacity: ringOpacity,
|
||||
rotate: ringRotate,
|
||||
}}
|
||||
className="pointer-events-none absolute left-1/2 top-1/2 w-[min(100vw,1550px)] h-[min(115vh,1100px)] -translate-x-1/2 -translate-y-1/2"
|
||||
>
|
||||
<motion.div style={{ y: magnetY }} className="w-full h-full">
|
||||
<svg
|
||||
aria-hidden
|
||||
className="w-full h-full"
|
||||
viewBox="0 0 1550 1100"
|
||||
fill="none"
|
||||
>
|
||||
<defs>
|
||||
{/* Subtiler radialer Glow-Verlauf unter dem Cursor */}
|
||||
<radialGradient
|
||||
id="cursorLineGlow"
|
||||
cx={svgGlow.x}
|
||||
cy={svgGlow.y}
|
||||
r="280"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop offset="0%" stopColor="#0d7a57" stopOpacity="0.32" />
|
||||
<stop offset="50%" stopColor="#0d7a57" stopOpacity="0.12" />
|
||||
<stop offset="100%" stopColor="#0d7a57" stopOpacity="0" />
|
||||
</radialGradient>
|
||||
</defs>
|
||||
|
||||
{/* 1. Basis-Ellipsen mit dezentem Ruhe-Opacity */}
|
||||
<ellipse
|
||||
cx="775"
|
||||
cy="490"
|
||||
rx="290"
|
||||
ry="180"
|
||||
stroke="#111412"
|
||||
strokeOpacity="0.13"
|
||||
strokeWidth="1"
|
||||
/>
|
||||
<ellipse
|
||||
cx="775"
|
||||
cy="490"
|
||||
rx="440"
|
||||
ry="275"
|
||||
stroke="#111412"
|
||||
strokeOpacity="0.10"
|
||||
strokeWidth="1"
|
||||
/>
|
||||
<ellipse
|
||||
cx="775"
|
||||
cy="490"
|
||||
rx="610"
|
||||
ry="370"
|
||||
stroke="#111412"
|
||||
strokeOpacity="0.11"
|
||||
strokeWidth="1"
|
||||
/>
|
||||
<ellipse
|
||||
cx="775"
|
||||
cy="490"
|
||||
rx="770"
|
||||
ry="460"
|
||||
stroke="#111412"
|
||||
strokeOpacity="0.08"
|
||||
strokeWidth="1"
|
||||
strokeDasharray="5 5"
|
||||
/>
|
||||
<ellipse
|
||||
cx="775"
|
||||
cy="490"
|
||||
rx="920"
|
||||
ry="550"
|
||||
stroke="#111412"
|
||||
strokeOpacity="0.06"
|
||||
strokeWidth="1"
|
||||
/>
|
||||
<ellipse
|
||||
cx="775"
|
||||
cy="490"
|
||||
rx="1080"
|
||||
ry="640"
|
||||
stroke="#111412"
|
||||
strokeOpacity="0.05"
|
||||
strokeWidth="1"
|
||||
/>
|
||||
|
||||
{/* Dynamisch kreuzende Orbit-Bögen – reichen flüssig bis in die nächste Sektion */}
|
||||
<circle
|
||||
cx="690"
|
||||
cy="440"
|
||||
r="620"
|
||||
stroke="#111412"
|
||||
strokeOpacity="0.07"
|
||||
strokeWidth="1"
|
||||
/>
|
||||
<circle
|
||||
cx="860"
|
||||
cy="570"
|
||||
r="680"
|
||||
stroke="#111412"
|
||||
strokeOpacity="0.06"
|
||||
strokeWidth="1"
|
||||
/>
|
||||
<ellipse
|
||||
cx="775"
|
||||
cy="490"
|
||||
rx="1040"
|
||||
ry="440"
|
||||
transform="rotate(-11 775 490)"
|
||||
stroke="#111412"
|
||||
strokeOpacity="0.06"
|
||||
strokeWidth="1"
|
||||
/>
|
||||
|
||||
{/* Feine Horizon-Linie hinter den Buttons */}
|
||||
<line
|
||||
x1="390"
|
||||
y1="690"
|
||||
x2="1160"
|
||||
y2="690"
|
||||
stroke="#111412"
|
||||
strokeOpacity="0.09"
|
||||
strokeWidth="1"
|
||||
/>
|
||||
|
||||
{/* 2. Subtile Aufleuchten-Ebene unter dem Cursor */}
|
||||
<g stroke="url(#cursorLineGlow)" strokeWidth="1.3">
|
||||
<ellipse cx="775" cy="490" rx="290" ry="180" />
|
||||
<ellipse cx="775" cy="490" rx="440" ry="275" />
|
||||
<ellipse cx="775" cy="490" rx="610" ry="370" />
|
||||
<ellipse cx="775" cy="490" rx="770" ry="460" strokeDasharray="5 5" />
|
||||
<ellipse cx="775" cy="490" rx="920" ry="550" />
|
||||
<ellipse cx="775" cy="490" rx="1080" ry="640" />
|
||||
<circle cx="690" cy="440" r="620" />
|
||||
<circle cx="860" cy="570" r="680" />
|
||||
<ellipse
|
||||
cx="775"
|
||||
cy="490"
|
||||
rx="1040"
|
||||
ry="440"
|
||||
transform="rotate(-11 775 490)"
|
||||
/>
|
||||
<line x1="390" y1="690" x2="1160" y2="690" />
|
||||
</g>
|
||||
</svg>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
|
||||
<div className="bg-noise pointer-events-none absolute inset-0" aria-hidden />
|
||||
|
||||
<div className="relative mx-auto flex w-full max-w-5xl flex-col items-center px-5 py-16 text-center sm:px-8">
|
||||
<h1 className="font-sans text-[11vw] font-medium leading-[1.02] tracking-[-0.03em] sm:text-6xl lg:text-7xl">
|
||||
<span className="hero-el block">Kostenloser</span>
|
||||
<span className="hero-el block font-display text-[1.06em] font-normal italic tracking-normal text-[#0d7a57]">
|
||||
Zufallsgenerator
|
||||
</span>
|
||||
<span className="hero-el block">für schnelle Entscheidungen</span>
|
||||
</h1>
|
||||
|
||||
<p className="hero-el mt-8 max-w-md text-base leading-relaxed text-ink-soft sm:text-lg">
|
||||
Lass den Zufall entscheiden – mit Münze, Glücksrad, Würfel & mehr für
|
||||
alle, die sich mal wieder nicht entscheiden können.
|
||||
</p>
|
||||
|
||||
<div className="hero-el mt-10 flex flex-wrap items-center justify-center gap-3">
|
||||
<motion.a
|
||||
href="#tools"
|
||||
whileHover={{ y: -2, scale: 1.02 }}
|
||||
whileTap={{ scale: 0.97 }}
|
||||
className="inline-flex items-center rounded-full bg-[#0d7a57] px-8 py-3.5 text-sm font-semibold text-white shadow-[0_12px_28px_rgba(13,122,87,0.30)] transition-colors duration-200 hover:bg-[#095c41]"
|
||||
>
|
||||
Jetzt entscheiden
|
||||
</motion.a>
|
||||
<motion.a
|
||||
href="#features"
|
||||
whileHover={{ y: -2 }}
|
||||
whileTap={{ scale: 0.97 }}
|
||||
className="inline-flex items-center rounded-full border border-ink/20 bg-surface/80 px-8 py-3.5 text-sm font-semibold text-ink transition-colors duration-200 hover:border-[#0d7a57] hover:text-[#0d7a57]"
|
||||
>
|
||||
Mehr erfahren
|
||||
</motion.a>
|
||||
</div>
|
||||
|
||||
<p className="hero-el mt-8 inline-flex items-center rounded-full border border-[#0d7a57]/20 bg-[#e6f7f0] px-4 py-1.5 text-xs font-semibold uppercase tracking-[0.2em] text-[#0d7a57]">
|
||||
<span className="inline-block h-1.5 w-1.5 rounded-full bg-[#0d7a57] mr-2.5" />
|
||||
7 Tools · Kostenlos · Ohne Anmeldung
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Scroll-Hinweis */}
|
||||
<a
|
||||
href="#tools"
|
||||
aria-label="Zu den Tools scrollen"
|
||||
className="absolute bottom-6 left-1/2 z-10 hidden -translate-x-1/2 flex-col items-center gap-1 text-ink-soft transition-colors hover:text-ink md:flex"
|
||||
>
|
||||
<span className="text-[11px] font-semibold uppercase tracking-[0.2em]">
|
||||
Scroll
|
||||
</span>
|
||||
<ArrowDown className="animate-bob h-4 w-4" />
|
||||
</a>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user