parfum_agsd/parfum-shop/src/pages/LandingPage.jsx

278 lines
7.4 KiB
JavaScript

import { useEffect, useRef } from "react";
import { Link } from "react-router";
import { gsap } from "gsap";
import perfumes from "../data/perfumes";
import "../pages/LandingPage.css";
import "../style/navbar.css";
function LandingPage() {
const cardRefs = useRef([]);
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 (
<div className="page">
<header className="hero">
<nav className="navbar navbar--hero">
<div className="nav-pill">
<a href="#home" className="nav-link active">
Name
</a>
<a href="#dufte" className="nav-link">
Düfte
</a>
<a href="#testen" className="nav-link">
Testen
</a>
<a href="#cart" className="nav-link">
Cart
</a>
</div>
</nav>
<div className="hero-overlay" />
<div className="hero-content">
<p className="eyebrow">NISCHENDÜFTE</p>
<h1>
DÜFTE ALS
<br />
AUSDRUCK
<br />
VON KONZEPT
</h1>
<p className="hero-text">
Konzeptuelle Düfte zwischen Materialität, Raum und Charakter.
</p>
<div className="hero-actions">
<button className="btn btn-primary">Aktuelle Düfte</button>
<button className="btn btn-secondary">Discovery Set</button>
</div>
</div>
</header>
<main>
<section className="section" id="dufte">
<div className="section-heading">
<h2>
WÄHLE EINE
<br />
ATMOSPHÄRE
</h2>
</div>
<div className="product-grid">
{perfumes.map((item, index) => (
<Link
to={`/duft/${item.slug}`}
className="product-card"
key={item.id}
ref={(el) => {
cardRefs.current[index] = el;
}}
>
<div className="product-hover-fill" aria-hidden="true">
{item.fillVideo ? (
<video
className="product-hover-video"
src={item.fillVideo}
muted
loop
playsInline
preload="metadata"
/>
) : (
<div
className="product-hover-image"
style={{
backgroundImage: `url(${item.fillImage || item.image})`,
}}
/>
)}
</div>
<div className="product-top">
<span className="product-id">{item.id}</span>
<h3>{item.name}</h3>
</div>
<div className="product-image-wrap">
<img src={item.image} alt={item.name} className="product-image" />
</div>
<div className="product-bottom">
<p>{item.text}</p>
<span className="arrow"></span>
</div>
</Link>
))}
</div>
</section>
<section className="discovery-section" id="testen">
<div className="discovery-copy">
<h2>
DER SICHERE EINSTIEG
<br />
DISCOVERY SET
</h2>
<p>
Alle 6 Düfte als 2ml Samples 2ml.
<br />
Jeden Duft eine Woche tragen.
<br />
Verstehen, was funktioniert.
</p>
<button className="discovery-btn">Discovery Set bestellen</button>
</div>
<div className="discovery-banner">
<img src="/DISCOVERYSET.png" alt="Discovery Set" />
</div>
</section>
</main>
</div>
);
}
export default LandingPage;