import { useCallback, useEffect, useLayoutEffect, useRef, useState, } from "react"; import { Link } from "react-router"; import { gsap } from "gsap"; import { ScrollTrigger } from "gsap/ScrollTrigger"; import HeroSection from "../components/landing/HeroSection"; import SharedNavbar from "../components/SharedNavbar"; import { useProductTransition } from "../transitions/ProductTransitionContext"; import perfumes from "../data/perfumes"; import "../pages/LandingPage.css"; import "../style/navbar.css"; const INTRO_SESSION_KEY = "atmos-landing-intro-played"; gsap.registerPlugin(ScrollTrigger); function LandingPage() { const pageRef = useRef(null); const overlayRef = useRef(null); const overlayTextRef = useRef(null); const heroImageWrapRef = useRef(null); const heroImageRef = useRef(null); const discoveryImageRef = useRef(null); const headlineLineRefs = useRef([]); const heroMetaRefs = useRef([]); const cardRefs = useRef([]); const { startProductTransition } = useProductTransition(); const [introSettings] = useState(() => { if (typeof window === "undefined") { return { shouldPlayIntro: true }; } const prefersReducedMotion = window.matchMedia( "(prefers-reduced-motion: reduce)" ).matches; const introAlreadyPlayed = window.sessionStorage.getItem(INTRO_SESSION_KEY) === "true"; return { shouldPlayIntro: !prefersReducedMotion && !introAlreadyPlayed, }; }); const { shouldPlayIntro } = introSettings; const setHeadlinePrimaryRef = useCallback((element) => { headlineLineRefs.current[0] = element; }, []); const setHeadlineSecondaryRef = useCallback((element) => { headlineLineRefs.current[1] = element; }, []); const setDescriptionRef = useCallback((element) => { heroMetaRefs.current[0] = element; }, []); const setActionsRef = useCallback((element) => { heroMetaRefs.current[1] = element; }, []); useLayoutEffect(() => { const overlay = overlayRef.current; const overlayText = overlayTextRef.current; const heroImageWrap = heroImageWrapRef.current; const headlineLines = headlineLineRefs.current.filter(Boolean); const heroMeta = heroMetaRefs.current.filter(Boolean); if (!overlay || !overlayText || !heroImageWrap || headlineLines.length === 0) { return undefined; } const ctx = gsap.context(() => { gsap.set(heroImageWrap, { scale: 1.22, transformOrigin: "center center", }); gsap.set(headlineLines, { yPercent: 115, rotate: 2.2, transformOrigin: "0% 100%", force3D: true, }); gsap.set(heroMeta, { y: 36, autoAlpha: 0, }); if (!shouldPlayIntro) { gsap.set(heroImageWrap, { scale: 1 }); gsap.set(headlineLines, { yPercent: 0, rotate: 0 }); gsap.set(heroMeta, { y: 0, autoAlpha: 1 }); gsap.set(overlay, { yPercent: -100, autoAlpha: 0, display: "none", }); return; } gsap.set(overlay, { yPercent: 0, autoAlpha: 1, display: "block" }); gsap.set(overlayText, { xPercent: 28, yPercent: 140, autoAlpha: 0 }); const introTimeline = gsap.timeline({ defaults: { ease: "power3.out" }, onComplete: () => { window.sessionStorage.setItem(INTRO_SESSION_KEY, "true"); }, }); introTimeline .to(overlayText, { xPercent: 0, yPercent: 0, autoAlpha: 1, duration: 1.28, ease: "expo.out", }) .to({}, { duration: 0.34 }) .to( overlay, { yPercent: -100, duration: 1.46, ease: "power4.out", }, ">" ) .to( heroImageWrap, { scale: 1, duration: 1.6, ease: "power2.out", }, "<0.03" ) .to( headlineLines, { yPercent: 0, rotate: 0, duration: 1.18, stagger: 0.1, ease: "power4.out", }, "<0.27" ) .to( heroMeta, { y: 0, autoAlpha: 1, duration: 1.02, stagger: 0.12, ease: "power4.out", }, "<0.16" ) .set(overlay, { display: "none" }); }, pageRef); return () => { ctx.revert(); }; }, [shouldPlayIntro]); useLayoutEffect(() => { const heroImageWrap = heroImageWrapRef.current; const heroImage = heroImageRef.current; const discoveryImage = discoveryImageRef.current; if (!heroImageWrap || !heroImage || !discoveryImage || typeof window === "undefined") { return undefined; } if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) { return undefined; } const heroSection = heroImageWrap.closest(".hero"); const discoveryBanner = discoveryImage.closest(".discovery-banner"); if (!heroSection || !discoveryBanner) { return undefined; } const ctx = gsap.context(() => { gsap.set(heroImage, { scale: 1.14, yPercent: -4, transformOrigin: "center center", force3D: true, }); gsap.to(heroImage, { yPercent: 8, ease: "none", scrollTrigger: { trigger: heroSection, start: "top top", end: "bottom top", scrub: 1.15, }, }); gsap.set(discoveryImage, { scale: 1.16, yPercent: -8, transformOrigin: "center center", force3D: true, }); gsap.to(discoveryImage, { yPercent: 10, ease: "none", scrollTrigger: { trigger: discoveryBanner, start: "top bottom", end: "bottom top", scrub: 1.2, }, }); }, pageRef); return () => { ctx.revert(); }; }, []); useEffect(() => { const cards = cardRefs.current.filter(Boolean); const cardStates = cards .map((card) => { const hoverFill = card.querySelector(".product-hover-fill"); const hoverVideo = card.querySelector(".product-hover-video"); const productImage = card.querySelector(".product-image"); const arrow = card.querySelector(".arrow"); if (!hoverFill || !productImage || !arrow) { return null; } gsap.set(hoverFill, { autoAlpha: 0, scale: 1.08 }); gsap.set(productImage, { autoAlpha: 1, scale: 1 }); gsap.set(arrow, { x: 0 }); return { card, hoverFill, hoverVideo, productImage, arrow }; }) .filter(Boolean); const stopVideo = (video) => { if (!video) return; video.pause(); try { video.currentTime = 0; } catch { // Ignore errors when setting currentTime. } }; const playVideo = (video) => { if (!video) return; try { video.currentTime = 0; } catch { // Ignore errors when setting currentTime. } const playAttempt = video.play(); if (playAttempt && typeof playAttempt.catch === "function") { playAttempt.catch(() => {}); } }; const deactivate = (state) => { gsap.killTweensOf([state.hoverFill, state.productImage, state.arrow]); stopVideo(state.hoverVideo); gsap.to(state.hoverFill, { autoAlpha: 0, scale: 1.08, duration: 0.35, ease: "power2.out", overwrite: "auto", }); gsap.to(state.productImage, { scale: 1, autoAlpha: 1, duration: 0.35, ease: "power2.out", overwrite: "auto", }); gsap.to(state.arrow, { x: 0, duration: 0.35, ease: "power2.out", overwrite: "auto", }); }; const activate = (state) => { cardStates.forEach((item) => { if (item !== state) { deactivate(item); } }); gsap.killTweensOf([state.hoverFill, state.productImage, state.arrow]); playVideo(state.hoverVideo); gsap.to(state.hoverFill, { autoAlpha: 1, scale: 1, duration: 0.45, ease: "power2.out", overwrite: "auto", }); gsap.to(state.productImage, { scale: 0.92, autoAlpha: 0.35, duration: 0.45, ease: "power2.out", overwrite: "auto", }); gsap.to(state.arrow, { x: 8, duration: 0.45, ease: "power2.out", overwrite: "auto", }); }; const cardCleanups = cardStates.map((state) => { const onEnter = () => activate(state); const onLeave = () => deactivate(state); state.card.addEventListener("pointerenter", onEnter); state.card.addEventListener("pointerleave", onLeave); return () => { state.card.removeEventListener("pointerenter", onEnter); state.card.removeEventListener("pointerleave", onLeave); }; }); const resetAll = () => { cardStates.forEach((state) => deactivate(state)); }; const onMouseOutWindow = (event) => { if (!event.relatedTarget) { resetAll(); } }; window.addEventListener("blur", resetAll); document.addEventListener("mouseout", onMouseOutWindow); return () => { window.removeEventListener("blur", resetAll); document.removeEventListener("mouseout", onMouseOutWindow); cardCleanups.forEach((cleanup) => cleanup()); }; }, []); return (

{"W\u00C4HLE EINE"}
{"ATMOSPH\u00C4RE"}

{perfumes.map((item, index) => ( startProductTransition(event, item)} ref={(element) => { cardRefs.current[index] = element; }} >

DER SICHERE EINSTIEG
DISCOVERY SET

{"Alle 6 D\u00FCfte als 2ml Samples."}
Jeden Duft eine Woche tragen.
Verstehen, was funktioniert.

Discovery Set bestellen
Discovery Set
); } export default LandingPage;