diff --git a/node_modules/.package-lock.json b/node_modules/.package-lock.json
index 0a01d47..73720ef 100644
--- a/node_modules/.package-lock.json
+++ b/node_modules/.package-lock.json
@@ -1,5 +1,5 @@
{
- "name": ".parfum_agsd-1",
+ "name": "parfum_agsd",
"lockfileVersion": 3,
"requires": true,
"packages": {
diff --git a/package-lock.json b/package-lock.json
index de4c189..fa8582c 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,5 +1,5 @@
{
- "name": ".parfum_agsd-1",
+ "name": "parfum_agsd",
"lockfileVersion": 3,
"requires": true,
"packages": {
diff --git a/parfum-shop/src/App.css b/parfum-shop/src/App.css
index 5d8f657..34dff87 100644
--- a/parfum-shop/src/App.css
+++ b/parfum-shop/src/App.css
@@ -40,41 +40,6 @@
linear-gradient(to bottom, rgba(0, 0, 0, 0.2), rgba(0, 0, 0, 0.45));
}
-/* NAVBAR */
-
-.navbar {
- position: relative;
- z-index: 2;
- display: flex;
- justify-content: center;
- padding-top: 22px;
-}
-
-.nav-pill {
- display: flex;
- gap: 10px;
- padding: 8px 10px;
- border-radius: 999px;
- background: rgba(255, 255, 255, 0.15);
- backdrop-filter: blur(10px);
-}
-
-.nav-link {
- text-decoration: none;
- color: rgba(255, 255, 255, 0.88);
- font-size: 13px;
- padding: 8px 14px;
- border-radius: 999px;
- transition: 0.2s ease;
-}
-
-.nav-link:hover,
-.nav-link.active {
- background: rgba(255, 255, 255, 0.22);
-}
-
-/* --------------------------------------------------- */
-
.hero-content {
position: relative;
z-index: 2;
diff --git a/parfum-shop/src/App.jsx b/parfum-shop/src/App.jsx
index c5572bf..cacd0fa 100644
--- a/parfum-shop/src/App.jsx
+++ b/parfum-shop/src/App.jsx
@@ -1,355 +1,14 @@
-import { useEffect, useRef } from "react";
-import { gsap } from "gsap";
-import "./App.css";
-
-// Hallo im Code,
-// ich streue hier bewusst Kommentare ein, damit ihr euch nicht auf eine jahrlange
-// archäologische Expedition begeben müsst. So bleibt besser sichtbar, wo
-// einzelne Sections anfangen und aufhören und wo wichtige Elemente oder
-// Funktionen liegen.
-
-// Euer Freund und Helfer Salih
-
-// Bei Bugs, kleinen Krisen oder emotionalem Kontrollverlust bitte
-// https://stackoverflow.com/questions konsultieren
-// oder fragen Sie Salih oder eine KI Ihres Vertrauens.
-
-//Erreichbar unter salih.hasicic@stud.fhgr.ch oder telefonisch, falls ihr die Nummer habt*/
-
-//Elements and Images for the grid
-const perfumes = [
- {
- id: "01",
- name: "KALTER BETON",
- image:
- "/kalter-beton-product.png",
- fillImage: "/platzhalter.png",
- fillVideo: "/kalter-beton-hover.webm",
- text: "Mineralisch. Roh. Unberührt.",
- },
- {
- id: "02",
- name: "NASSER MARMOR",
- image:
- "/NASSER MARMOR.png",
- fillImage: "/platzhalter.png",
- fillVideo: "/nasser-marmor-hover.webm",
- text: "Kühl. Glatt. Sinnlich.",
- },
- {
- id: "03",
- name: "BLASSE SEIDE",
- image:
- "/BLASSE SEIDE.png",
- fillImage: "/platzhalter.png",
- fillVideo: "/blasse-seide-hover.webm",
- text: "Blass. Sanft. Kostbar.",
- },
- {
- id: "04",
- name: "WEISSE ASCHE",
- image:
- "/WEISSE ASCHE.png",
- fillImage: "/platzhalter.png",
- fillVideo: "/weisse-asche-hover.webm",
- text: "Still. Staubig. Erhaben.",
- },
- {
- id: "05",
- name: "VERBRANNTES CHROM",
- image:
- "/VERBRANNTES CHROM.png",
- fillImage: "/platzhalter.png",
- fillVideo: "/verbranntes-chrom-hover.webm",
- text: "Metallisch. Verzehrt. Edel.",
- },
- {
- id: "06",
- name: "SCHWARZES BENZIN",
- image:
- "/SCHWARZES BENZIN.png",
- fillImage: "/platzhalter.png",
- fillVideo: "/schwarzes-benzin-hover.webm",
- text: "Dunkel. Glänzend. Verboten.",
- },
-];
+import LandingPage from "./pages/LandingPage";
+import ProductDetailPage from "./components/ProductDetailPage";
function App() {
- const cardRefs = useRef([]);
+ const showDetailPage = true;
- 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 (showDetailPage) {
+ return ;
+ }
- 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 if browser blocks random access while buffering.
- }
- };
-
- const playVideo = (video) => {
- if (!video) {
- return;
- }
-
- try {
- video.currentTime = 0;
- } catch {
- // Ignore if browser blocks random access while buffering.
- }
-
- 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 (
-
- {/* Hero */}
-
- {/* --- Hero End --- */}
-
-
- {/* Grid with Core Collection */}
-
-
-
- WÄHLE EINE
-
- ATMOSPHÄRE
-
-
-
-
- {perfumes.map((item, index) => (
-
{
- cardRefs.current[index] = el;
- }}
- >
-
- {item.fillVideo ? (
-
- ) : (
-
- )}
-
-
-
- {item.id}
-
{item.name}
-
-
-
-
-
-
-
-
- ))}
-
-
- {/* --- Grid End --- */}
-
- {/* Dicovery Set Section */}
-
-
-
- DER SICHERE EINSTIEG
-
- DISCOVERY SET
-
-
- 6 Samples × 2ml.
-
- Jeden Duft eine Woche tragen.
-
- Verstehen, was funktioniert.
-
-
-
-
-
-
Discovery Set bestellen
-
-
- {/* --- Dicovery Set Section End--- */}
-
-
- );
+ return ;
}
-export default App;
+export default App;
\ No newline at end of file
diff --git a/parfum-shop/src/components/ProductDetailPage.css b/parfum-shop/src/components/ProductDetailPage.css
new file mode 100644
index 0000000..b1fc4c1
--- /dev/null
+++ b/parfum-shop/src/components/ProductDetailPage.css
@@ -0,0 +1,432 @@
+/*
+ Hallo im CSS,
+ ich versuche auch hier die Struktur sauber zu kommentieren, damit ihr nicht
+ im Styling-Dschungel verloren geht und schneller versteht, was wohin gehört.
+
+ Bei Bugs, kleinen Krisen oder emotionalem Kontrollverlust bitte
+ https://stackoverflow.com/questions konsultieren
+ oder fragt Salih oder eine KI eures Vertrauens.
+*/
+
+/* --- Product Detail Page Wrapper Start --- */
+
+.detail-page {
+ min-height: 100vh;
+ background: #efefef;
+ color: #191919;
+ padding: 20px;
+}
+
+.detail-shell {
+ background: #f7f7f7;
+ border: 1px solid #d6d6d6;
+ padding: 22px;
+}
+
+/* --- Product Detail Page Wrapper End --- */
+
+/* --- Back Link Start --- */
+
+.back-link {
+ background: none;
+ border: none;
+ padding: 0;
+ margin-bottom: 18px;
+ font-size: 14px;
+ cursor: pointer;
+ color: #222;
+}
+
+/* --- Back Link End --- */
+
+/* --- Main Detail Layout Start --- */
+
+.detail-layout {
+ display: grid;
+ grid-template-columns: 1.02fr 1fr;
+ gap: 28px;
+ align-items: start;
+}
+
+/* --- Main Detail Layout End --- */
+
+/* --- Left Column / Gallery Start --- */
+
+.detail-gallery {
+ display: flex;
+ flex-direction: column;
+}
+
+.detail-main-image {
+ background: #ddd;
+ aspect-ratio: 1 / 1;
+ overflow: hidden;
+}
+
+.detail-main-image img {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ display: block;
+}
+
+.detail-thumbs {
+ display: flex;
+ gap: 14px;
+ margin-top: 14px;
+}
+
+.thumb-btn {
+ width: 88px;
+ height: 88px;
+ border: 1px solid #cfcfcf;
+ background: #fff;
+ padding: 0;
+ cursor: pointer;
+}
+
+.thumb-btn.active {
+ outline: 2px solid #4da3ff;
+ outline-offset: 1px;
+}
+
+.thumb-btn img {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+}
+
+/* --- Left Column / Gallery End --- */
+
+/* --- Duftstruktur Start --- */
+
+.detail-structure {
+ margin-top: 28px;
+}
+
+.detail-structure h3 {
+ font-size: 12px;
+ letter-spacing: 0.24em;
+ font-weight: 500;
+ margin: 0 0 14px;
+}
+
+.structure-block {
+ margin-bottom: 12px;
+}
+
+.structure-phase {
+ display: block;
+ margin-bottom: 6px;
+ font-size: 10px;
+ letter-spacing: 0.18em;
+ color: #555;
+}
+
+.structure-tags {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 6px;
+}
+
+.structure-tags span {
+ display: inline-flex;
+ align-items: center;
+ min-height: 34px;
+ padding: 8px 14px;
+ border: 1px solid #d2d2d2;
+ background: #ebebeb;
+ font-size: 12px;
+}
+
+.mood-box {
+ margin-top: 18px;
+ background: #dfdfdf;
+ padding: 16px;
+}
+
+.mood-label {
+ display: block;
+ margin-bottom: 8px;
+ font-size: 10px;
+ letter-spacing: 0.2em;
+ color: #555;
+}
+
+.mood-box p {
+ font-size: 15px;
+ line-height: 1.5;
+ margin: 0;
+}
+
+/* --- Duftstruktur End --- */
+
+/* --- Meta Infos unter Duftstruktur Start --- */
+
+.detail-meta-grid {
+ display: grid;
+ grid-template-columns: 1fr 1fr 1.5fr;
+ gap: 20px;
+ margin-top: 24px;
+}
+
+.detail-meta-grid span {
+ display: block;
+ margin-bottom: 8px;
+ font-size: 10px;
+ letter-spacing: 0.2em;
+ color: #666;
+}
+
+.detail-meta-grid p {
+ font-size: 13px;
+ line-height: 1.5;
+ margin: 0;
+}
+
+/* --- Meta Infos unter Duftstruktur End --- */
+
+/* --- Right Column Start --- */
+
+.detail-info {
+ display: flex;
+ flex-direction: column;
+ gap: 22px;
+}
+
+.detail-heading h1 {
+ margin: 0 0 6px;
+ font-size: 52px;
+ line-height: 0.95;
+ font-weight: 400;
+ letter-spacing: -0.04em;
+ color: #131313;
+}
+
+.detail-heading p {
+ font-size: 22px;
+ color: #3f3f3f;
+ margin: 0;
+}
+
+.detail-section-block {
+ display: flex;
+ flex-direction: column;
+}
+
+.label-title {
+ display: block;
+ margin-bottom: 10px;
+ font-size: 11px;
+ letter-spacing: 0.24em;
+ color: #666;
+}
+
+/* --- Right Column End --- */
+
+/* --- Material Tags Start --- */
+
+.material-tags {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 8px;
+}
+
+.material-tags span {
+ display: inline-flex;
+ align-items: center;
+ min-height: 34px;
+ padding: 8px 14px;
+ border: 1px solid #d2d2d2;
+ background: #ebebeb;
+ font-size: 12px;
+}
+
+/* --- Material Tags End --- */
+
+/* --- Size Selection Start --- */
+
+.size-grid {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 12px;
+}
+
+.size-card {
+ border: 1px solid #d0d0d0;
+ background: #fefefe;
+ padding: 18px;
+ text-align: center;
+ cursor: pointer;
+}
+
+.size-card.active {
+ border-color: #111;
+}
+
+.size-title {
+ display: block;
+ margin-bottom: 8px;
+ font-size: 13px;
+}
+
+.size-card strong {
+ display: block;
+ font-size: 28px;
+ font-weight: 400;
+ margin-bottom: 6px;
+}
+
+.size-card small {
+ font-size: 11px;
+ color: #666;
+}
+
+/* --- Size Selection End --- */
+
+/* --- Discovery Hinweis + Kaufen Button Start --- */
+
+.discovery-note {
+ background: #050505;
+ color: #fff;
+ padding: 14px 16px;
+}
+
+.discovery-note strong {
+ display: block;
+ margin-bottom: 6px;
+ font-size: 13px;
+}
+
+.discovery-note p {
+ font-size: 11px;
+ color: rgba(255, 255, 255, 0.78);
+ margin: 0;
+}
+
+.buy-button {
+ width: 100%;
+ border: none;
+ background: #050505;
+ color: #fff;
+ padding: 18px;
+ font-size: 16px;
+ letter-spacing: 0.08em;
+ cursor: pointer;
+}
+
+/* --- Discovery Hinweis + Kaufen Button End --- */
+
+/* --- Description Columns Start --- */
+
+.detail-columns {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 22px 30px;
+}
+
+.detail-copy-block p {
+ white-space: pre-line;
+ color: #2b2b2b;
+ line-height: 1.55;
+ margin: 0;
+}
+
+/* --- Description Columns End --- */
+
+/* --- Bottom Upsell Bar Start --- */
+
+.detail-upsell-bar {
+ margin-top: auto;
+ background: #060606;
+ color: #fff;
+ padding: 12px 16px;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.detail-upsell-bar strong {
+ display: block;
+ margin-bottom: 4px;
+ font-size: 13px;
+}
+
+.detail-upsell-bar p {
+ font-size: 11px;
+ color: rgba(255, 255, 255, 0.7);
+ margin: 0;
+}
+
+/* --- Bottom Upsell Bar End --- */
+
+/* --- Bottom CTA Start --- */
+
+.detail-bottom-cta {
+ margin-top: 42px;
+ padding: 42px 20px 16px;
+ border-top: 1px solid #d4d4d4;
+ text-align: center;
+}
+
+.detail-bottom-cta h2 {
+ margin: 0 0 14px;
+ font-size: 34px;
+ font-weight: 400;
+}
+
+.detail-bottom-cta p {
+ max-width: 880px;
+ margin: 0 auto;
+ color: #333;
+ line-height: 1.7;
+}
+
+.detail-bottom-actions {
+ display: flex;
+ justify-content: center;
+ gap: 16px;
+ margin-top: 26px;
+}
+
+.detail-bottom-actions button {
+ border: 1px solid #101010;
+ background: transparent;
+ padding: 16px 24px;
+ min-width: 250px;
+ cursor: pointer;
+}
+
+/* --- Bottom CTA End --- */
+
+/* --- Responsive Start --- */
+
+@media (max-width: 1100px) {
+ .detail-layout {
+ grid-template-columns: 1fr;
+ }
+
+ .detail-heading h1 {
+ font-size: 40px;
+ }
+}
+
+@media (max-width: 700px) {
+ .detail-columns,
+ .detail-meta-grid,
+ .size-grid,
+ .detail-bottom-actions {
+ grid-template-columns: 1fr;
+ display: grid;
+ }
+
+ .detail-bottom-actions button {
+ min-width: 100%;
+ }
+
+ .detail-thumbs {
+ flex-wrap: wrap;
+ }
+}
+
+/* --- Responsive End --- */
\ No newline at end of file
diff --git a/parfum-shop/src/components/ProductDetailPage.jsx b/parfum-shop/src/components/ProductDetailPage.jsx
new file mode 100644
index 0000000..6a3e418
--- /dev/null
+++ b/parfum-shop/src/components/ProductDetailPage.jsx
@@ -0,0 +1,232 @@
+import { useMemo, useState } from "react";
+import perfumes from "../data/perfumes";
+import "../navbar.css";
+import "./ProductDetailPage.css";
+
+function ProductDetailPage({ perfumeSlug = "kalter-beton" }) {
+ const perfume = useMemo(
+ () => perfumes.find((item) => item.slug === perfumeSlug) || perfumes[0],
+ [perfumeSlug]
+ );
+
+ const [selectedImage, setSelectedImage] = useState(
+ perfume.gallery?.[0] || perfume.image
+ );
+ const [selectedSize, setSelectedSize] = useState("sample");
+
+ const sizeOptions = [
+ {
+ key: "sample",
+ title: "Sample 2ml",
+ price: perfume.prices.sample,
+ note: "Zum Testen · ca. 20 Anwendungen",
+ },
+ {
+ key: "full",
+ title: "Full Size 50ml",
+ price: perfume.prices.full,
+ note: "Nachkauf · 500+ Anwendungen",
+ },
+ ];
+
+ return (
+
+ {/* Navbar */}
+
+
+
+ {/* --- Navbar End --- */}
+
+ {/* Product detail content */}
+
+
+ ← Zurück zur Startseite
+
+
+
+ {/* Left column */}
+
+
+
+
+
+
+ {[perfume.image, ...(perfume.gallery || [])]
+ .slice(0, 3)
+ .map((img, index) => (
+
setSelectedImage(img)}
+ >
+
+
+ ))}
+
+
+
+
DUFTSTRUKTUR
+
+
+
PHASE 1: TOP NOTES (0–1 H)
+
+ {perfume.phases.top.map((note) => (
+ {note}
+ ))}
+
+
+
+
+
PHASE 2: HEART NOTES (1–4 H)
+
+ {perfume.phases.heart.map((note) => (
+ {note}
+ ))}
+
+
+
+
+
PHASE 3: BASE NOTES (4 H+)
+
+ {perfume.phases.base.map((note) => (
+ {note}
+ ))}
+
+
+
+
+
MOODSETTING
+
{perfume.mood}
+
+
+
+
+
+
TRAGEHINWEIS
+
{perfume.dosage}
+
+
+
+
HALTBARKEIT
+
{perfume.longevity}
+
+
+
+
ANLASS
+
{perfume.occasion}
+
+
+
+
+ {/* Right column */}
+
+
+
{perfume.name}
+
{perfume.shortText}
+
+
+
+
MATERIAL-KOMPOSITION
+
+ {perfume.materialTags.map((tag) => (
+ {tag}
+ ))}
+
+
+
+
+
GRÖSSE WÄHLEN
+
+ {sizeOptions.map((option) => (
+ setSelectedSize(option.key)}
+ >
+ {option.title}
+ {option.price}
+ {option.note}
+
+ ))}
+
+
+
+
+
Discovery Set wird angerechnet
+
+ Hast du das Discovery Set gekauft, wird der volle Preis beim Kauf
+ automatisch abgezogen.
+
+
+
+
+ KAUFEN
+
+
+
+
+
BESCHREIBUNG
+
{perfume.description}
+
+
+
+
HERKUNFT
+
{perfume.origin}
+
+
+
+
KONZENTRATION
+
{perfume.concentration}
+
+
+
+
EDITION
+
{perfume.edition}
+
+
+
+
+
+
Noch unsicher?
+
Discovery Set bestellen und Düfte testen
+
+
—
+
+
+
+
+ {/* Bottom CTA */}
+
+ Lieber erst testen?
+
+ Bestelle ein 2ml Sample für CHF 12 oder das komplette Discovery Set
+ mit allen 6 Düften für CHF 48. Beide werden beim späteren Full-Size-Kauf
+ vollständig angerechnet.
+
+
+
+ SAMPLE BESTELLEN – CHF 12
+ DISCOVERY SET – CHF 48
+
+
+
+
+ );
+}
+
+export default ProductDetailPage;
\ No newline at end of file
diff --git a/parfum-shop/src/data/perfumes.js b/parfum-shop/src/data/perfumes.js
new file mode 100644
index 0000000..b14828f
--- /dev/null
+++ b/parfum-shop/src/data/perfumes.js
@@ -0,0 +1,196 @@
+const perfumes = [
+ {
+ id: "01",
+ slug: "kalter-beton",
+ name: "KALTER BETON",
+ image: "/kalter-beton-product.png",
+ fillImage: "/platzhalter.png",
+ fillVideo: "/kalter-beton-hover.webm",
+ text: "Mineralisch. Roh. Unberührt.",
+ prices: {
+ sample: "CHF 12.–",
+ full: "CHF 185.–",
+ },
+ materialTags: ["Graue Iris", "Stein Akkord", "Zedernholz", "Weihrauch"],
+ phases: {
+ top: ["Graue Iris", "Mineral Note", "Betonakkord"],
+ heart: ["Stein Akkord", "Salz", "Staubnote"],
+ base: ["Zedernholz", "Pfeffer", "Weihrauch"],
+ },
+ mood: "Unbewohnte Räume. Erste Kälte. Architektonische Stille.",
+ dosage: "2–3 Sprühstösse",
+ longevity: "8–12 Stunden",
+ occasion: "Studio, Konzentration, Arbeit, Beginn, Zurückhaltung.",
+ description: "Parfümerie / Studio\nAtelier LM / Luz",
+ origin: "Frankreich, 2024",
+ concentration: "Eau de Parfum (18%)",
+ edition: "Batch 04/24 – limitiert 500 Stück",
+ gallery: [
+ "/kalter-beton-product.png",
+ "/kalter-beton-product.png",
+ "/kalter-beton-product.png",
+ ],
+ },
+ {
+ id: "02",
+ slug: "nasser-marmor",
+ name: "NASSER MARMOR",
+ image: "/NASSER MARMOR.png",
+ fillImage: "/platzhalter.png",
+ fillVideo: "/nasser-marmor-hover.webm",
+ text: "Kühl. Glatt. Sinnlich.",
+ prices: {
+ sample: "CHF 12.–",
+ full: "CHF 185.–",
+ },
+ materialTags: ["Marmorakkord", "Aldehyde", "Moschus", "Vetiver"],
+ phases: {
+ top: ["Aldehyde", "Ozonic Note", "Kalter Stein"],
+ heart: ["Marmorakkord", "Iris", "Nebelnote"],
+ base: ["Moschus", "Vetiver", "Ambra"],
+ },
+ mood: "Feuchte Flächen. Kühle Eleganz. Polierte Ruhe.",
+ dosage: "2–3 Sprühstösse",
+ longevity: "7–11 Stunden",
+ occasion: "Galerie, Abend, Konzentration, Übergang.",
+ description: "Parfümerie / Studio\nAtelier LM / Luz",
+ origin: "Italien, 2024",
+ concentration: "Eau de Parfum (17%)",
+ edition: "Batch 02/24 – limitiert 450 Stück",
+ gallery: [
+ "/NASSER MARMOR.png",
+ "/NASSER MARMOR.png",
+ "/NASSER MARMOR.png",
+ ],
+ },
+ {
+ id: "03",
+ slug: "blasse-seide",
+ name: "BLASSE SEIDE",
+ image: "/BLASSE SEIDE.png",
+ fillImage: "/platzhalter.png",
+ fillVideo: "/blasse-seide-hover.webm",
+ text: "Blass. Sanft. Kostbar.",
+ prices: {
+ sample: "CHF 12.–",
+ full: "CHF 185.–",
+ },
+ materialTags: ["Weisser Moschus", "Reispuder", "Kaschmirholz", "Iris"],
+ phases: {
+ top: ["Reispuder", "Helle Blüte", "Luft"],
+ heart: ["Iris", "Seidenakkord", "Puder"],
+ base: ["Kaschmirholz", "Weisser Moschus", "Ambrette"],
+ },
+ mood: "Helles Gewebe. Stille Hautnähe. Gedämpfte Wärme.",
+ dosage: "3–4 Sprühstösse",
+ longevity: "6–10 Stunden",
+ occasion: "Alltag, Nähe, Morgen, feine Präsenz.",
+ description: "Parfümerie / Studio\nAtelier LM / Luz",
+ origin: "Schweiz, 2024",
+ concentration: "Eau de Parfum (16%)",
+ edition: "Batch 03/24 – limitiert 600 Stück",
+ gallery: [
+ "/BLASSE SEIDE.png",
+ "/BLASSE SEIDE.png",
+ "/BLASSE SEIDE.png",
+ ],
+ },
+ {
+ id: "04",
+ slug: "weisse-asche",
+ name: "WEISSE ASCHE",
+ image: "/WEISSE ASCHE.png",
+ fillImage: "/platzhalter.png",
+ fillVideo: "/weisse-asche-hover.webm",
+ text: "Still. Staubig. Erhaben.",
+ prices: {
+ sample: "CHF 12.–",
+ full: "CHF 185.–",
+ },
+ materialTags: ["Ascheakkord", "Papyrus", "Rauch", "Weisses Holz"],
+ phases: {
+ top: ["Staub", "Kalter Rauch", "Papier"],
+ heart: ["Ascheakkord", "Papyrus", "Kreide"],
+ base: ["Weisses Holz", "Rauch", "Harz"],
+ },
+ mood: "Stille Rückstände. Helle Spuren. Erhobene Leere.",
+ dosage: "2–3 Sprühstösse",
+ longevity: "8–12 Stunden",
+ occasion: "Abend, Winter, Alleinsein, Konzentration.",
+ description: "Parfümerie / Studio\nAtelier LM / Luz",
+ origin: "Deutschland, 2024",
+ concentration: "Eau de Parfum (19%)",
+ edition: "Batch 01/24 – limitiert 350 Stück",
+ gallery: [
+ "/WEISSE ASCHE.png",
+ "/WEISSE ASCHE.png",
+ "/WEISSE ASCHE.png",
+ ],
+ },
+ {
+ id: "05",
+ slug: "verbranntes-chrom",
+ name: "VERBRANNTES CHROM",
+ image: "/VERBRANNTES CHROM.png",
+ fillImage: "/platzhalter.png",
+ fillVideo: "/verbranntes-chrom-hover.webm",
+ text: "Metallisch. Verzehrt. Edel.",
+ prices: {
+ sample: "CHF 14.–",
+ full: "CHF 195.–",
+ },
+ materialTags: ["Metallakkord", "Oud", "Schwarzer Pfeffer", "Labdanum"],
+ phases: {
+ top: ["Schwarzer Pfeffer", "Funke", "Metall"],
+ heart: ["Metallakkord", "Rauch", "Harz"],
+ base: ["Oud", "Labdanum", "Dunkles Holz"],
+ },
+ mood: "Hitze auf Metall. Dunkler Glanz. Kontrollierte Zerstörung.",
+ dosage: "1–2 Sprühstösse",
+ longevity: "10–14 Stunden",
+ occasion: "Nacht, Statement, Kälte, Formalität.",
+ description: "Parfümerie / Studio\nAtelier LM / Luz",
+ origin: "Frankreich, 2024",
+ concentration: "Extrait de Parfum (24%)",
+ edition: "Batch 05/24 – limitiert 300 Stück",
+ gallery: [
+ "/VERBRANNTES CHROM.png",
+ "/VERBRANNTES CHROM.png",
+ "/VERBRANNTES CHROM.png",
+ ],
+ },
+ {
+ id: "06",
+ slug: "schwarzes-benzin",
+ name: "SCHWARZES BENZIN",
+ image: "/SCHWARZES BENZIN.png",
+ fillImage: "/platzhalter.png",
+ fillVideo: "/schwarzes-benzin-hover.webm",
+ text: "Dunkel. Glänzend. Verboten.",
+ prices: {
+ sample: "CHF 14.–",
+ full: "CHF 195.–",
+ },
+ materialTags: ["Petrol Akkord", "Leder", "Birke", "Patchouli"],
+ phases: {
+ top: ["Petrol Akkord", "Schwarzer Rauch", "Lack"],
+ heart: ["Leder", "Birke", "Harz"],
+ base: ["Patchouli", "Vetiver", "Dunkles Holz"],
+ },
+ mood: "Verbotene Oberfläche. Glanz im Schatten. Asphalt nach Regen.",
+ dosage: "1–2 Sprühstösse",
+ longevity: "9–13 Stunden",
+ occasion: "Abend, Urban, Nacht, Signaturduft.",
+ description: "Parfümerie / Studio\nAtelier LM / Luz",
+ origin: "Belgien, 2024",
+ concentration: "Eau de Parfum (20%)",
+ edition: "Batch 06/24 – limitiert 280 Stück",
+ gallery: [
+ "/SCHWARZES BENZIN.png",
+ "/SCHWARZES BENZIN.png",
+ "/SCHWARZES BENZIN.png",
+ ],
+ },
+];
+
+export default perfumes;
\ No newline at end of file
diff --git a/parfum-shop/src/navbar.css b/parfum-shop/src/navbar.css
new file mode 100644
index 0000000..ded9081
--- /dev/null
+++ b/parfum-shop/src/navbar.css
@@ -0,0 +1,76 @@
+/* --- Shared Navbar Start --- */
+
+.navbar {
+ position: relative;
+ z-index: 20;
+ display: flex;
+ justify-content: center;
+}
+
+.nav-pill {
+ display: flex;
+ gap: 10px;
+ padding: 8px 10px;
+ border-radius: 999px;
+ backdrop-filter: blur(10px);
+ -webkit-backdrop-filter: blur(10px);
+}
+
+.nav-link {
+ text-decoration: none;
+ font-size: 13px;
+ padding: 8px 14px;
+ border-radius: 999px;
+ transition: 0.2s ease;
+}
+
+/* Hero variant */
+.navbar--hero {
+ padding-top: 22px;
+}
+
+.navbar--hero .nav-pill {
+ background: rgba(255, 255, 255, 0.15);
+}
+
+.navbar--hero .nav-link {
+ color: rgba(255, 255, 255, 0.88);
+}
+
+.navbar--hero .nav-link:hover,
+.navbar--hero .nav-link.active {
+ background: rgba(255, 255, 255, 0.22);
+}
+
+/* Detail page variant */
+.navbar--light {
+ margin-bottom: 18px;
+}
+
+.navbar--light .nav-pill {
+ background: rgba(255, 255, 255, 0.88);
+ border: 1px solid #d6d6d6;
+}
+
+.navbar--light .nav-link {
+ color: #1d1d1d;
+}
+
+.navbar--light .nav-link:hover,
+.navbar--light .nav-link.active {
+ background: #ebebeb;
+}
+
+/* --- Shared Navbar End --- */
+
+@media (max-width: 640px) {
+ .nav-pill {
+ gap: 4px;
+ padding: 6px;
+ }
+
+ .nav-link {
+ padding: 8px 10px;
+ font-size: 12px;
+ }
+}
\ No newline at end of file
diff --git a/parfum-shop/src/pages/LandingPage.jsx b/parfum-shop/src/pages/LandingPage.jsx
new file mode 100644
index 0000000..53f50b9
--- /dev/null
+++ b/parfum-shop/src/pages/LandingPage.jsx
@@ -0,0 +1,272 @@
+import { useEffect, useRef } from "react";
+import { gsap } from "gsap";
+import perfumes from "../data/perfumes";
+import "../App.css";
+import "../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 {}
+ };
+
+ const playVideo = (video) => {
+ if (!video) return;
+
+ try {
+ video.currentTime = 0;
+ } catch {}
+
+ 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ÄHLE EINE
+
+ ATMOSPHÄRE
+
+
+
+
+ {perfumes.map((item, index) => (
+
{
+ cardRefs.current[index] = el;
+ }}
+ >
+
+ {item.fillVideo ? (
+
+ ) : (
+
+ )}
+
+
+
+ {item.id}
+
{item.name}
+
+
+
+
+
+
+
+
+ ))}
+
+
+
+
+
+
+ DER SICHERE EINSTIEG
+
+ DISCOVERY SET
+
+
+ 6 Samples × 2ml.
+
+ Jeden Duft eine Woche tragen.
+
+ Verstehen, was funktioniert.
+
+
+
+
+
+
Discovery Set bestellen
+
+
+
+
+ );
+}
+
+export default LandingPage;
\ No newline at end of file