Initialisieren

This commit is contained in:
2026-07-23 21:59:15 +02:00
commit 2d961adad4
76 changed files with 14318 additions and 0 deletions

327
components/hero.tsx Normal file
View 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 &amp; 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>
);
}