Add perfume detail routing, global navbar styles and chage radius on elements

This commit is contained in:
Salih Hasicic 2026-04-03 14:17:31 +02:00
parent a524a5d36a
commit 8140efb951
10 changed files with 523 additions and 492 deletions

View File

@ -10,7 +10,8 @@
"dependencies": { "dependencies": {
"gsap": "^3.14.2", "gsap": "^3.14.2",
"react": "^19.2.4", "react": "^19.2.4",
"react-dom": "^19.2.4" "react-dom": "^19.2.4",
"react-router": "^7.14.0"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.29.0", "@babel/core": "^7.29.0",
@ -1050,9 +1051,9 @@
} }
}, },
"node_modules/brace-expansion": { "node_modules/brace-expansion": {
"version": "1.1.12", "version": "1.1.13",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz",
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@ -1177,6 +1178,19 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/cookie": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz",
"integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==",
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/cross-spawn": { "node_modules/cross-spawn": {
"version": "7.0.6", "version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
@ -2219,9 +2233,9 @@
"license": "ISC" "license": "ISC"
}, },
"node_modules/picomatch": { "node_modules/picomatch": {
"version": "4.0.3", "version": "4.0.4",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
@ -2295,6 +2309,7 @@
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz",
"integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"scheduler": "^0.27.0" "scheduler": "^0.27.0"
}, },
@ -2302,6 +2317,28 @@
"react": "^19.2.4" "react": "^19.2.4"
} }
}, },
"node_modules/react-router": {
"version": "7.14.0",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.14.0.tgz",
"integrity": "sha512-m/xR9N4LQLmAS0ZhkY2nkPA1N7gQ5TUVa5n8TgANuDTARbn1gt+zLPXEm7W0XDTbrQ2AJSJKhoa6yx1D8BcpxQ==",
"license": "MIT",
"dependencies": {
"cookie": "^1.0.1",
"set-cookie-parser": "^2.6.0"
},
"engines": {
"node": ">=20.0.0"
},
"peerDependencies": {
"react": ">=18",
"react-dom": ">=18"
},
"peerDependenciesMeta": {
"react-dom": {
"optional": true
}
}
},
"node_modules/resolve-from": { "node_modules/resolve-from": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
@ -2370,6 +2407,12 @@
"semver": "bin/semver.js" "semver": "bin/semver.js"
} }
}, },
"node_modules/set-cookie-parser": {
"version": "2.7.2",
"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz",
"integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==",
"license": "MIT"
},
"node_modules/shebang-command": { "node_modules/shebang-command": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",

View File

@ -12,7 +12,8 @@
"dependencies": { "dependencies": {
"gsap": "^3.14.2", "gsap": "^3.14.2",
"react": "^19.2.4", "react": "^19.2.4",
"react-dom": "^19.2.4" "react-dom": "^19.2.4",
"react-router": "^7.14.0"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.29.0", "@babel/core": "^7.29.0",

View File

@ -1,427 +1,15 @@
.page { * {
min-height: 100vh; box-sizing: border-box;
}
html,
body,
#root {
margin: 0;
min-height: 100%;
font-family: Arial, Helvetica, sans-serif;
}
body {
background: #efefef; background: #efefef;
color: #1f1f1f;
}
/*
Hallo im CSS,
damit ihr euch hier nicht durch einen wilden Haufen aus Klassen kämpfen müsst,
versuche ich die Bereiche sauber zu kommentieren. So ist schneller sichtbar,
welche Styles wohin gehören und wo welche Section anfängt.
Euer Freund und Helfer Salih
Bei Bugs, Verzweiflung oder akuten CSS-Zusammenbrüchen lesen Sie https://stackoverflow.com/questions
oder fragen Sie Salih oder eine KI Ihres Vertrauens.
Erreichbar unter salih.hasicic@stud.fhgr.ch oder telefonisch, fall Sie die Nummer haben*/
/* HERO */
.hero {
position: relative;
min-height: 720px;
margin-left: 20px;
margin-right: 20px;
margin-top: 0px;
border-radius: 0 0 18px 18px;
overflow: hidden;
background-image: url("/HERO.jpeg");
background-size: cover;
background-position: center;
}
.hero-overlay {
position: absolute;
inset: 0;
background:
linear-gradient(to right, rgba(0, 0, 0, 0.45), rgba(0, 0, 0, 0.1)),
linear-gradient(to bottom, rgba(0, 0, 0, 0.2), rgba(0, 0, 0, 0.45));
}
.hero-content {
position: relative;
z-index: 2;
max-width: 460px;
padding: 120px 0 0 38px;
color: white;
}
.eyebrow {
margin-bottom: 16px;
font-size: 12px;
letter-spacing: 0.18em;
opacity: 0.85;
}
.hero h1 {
margin: 0 0 18px;
font-size: 62px;
line-height: 0.95;
font-weight: 300;
letter-spacing: -0.04em;
color: white;
}
.hero-text {
max-width: 320px;
font-size: 15px;
line-height: 1.5;
color: rgba(255, 255, 255, 0.85);
}
.hero-actions {
display: flex;
gap: 12px;
margin-top: 28px;
}
.btn {
border: none;
border-radius: 999px;
padding: 12px 18px;
font-size: 14px;
cursor: pointer;
transition: transform 0.2s ease, opacity 0.2s ease;
}
.btn:hover {
transform: translateY(-1px);
}
.btn-primary {
background: #ff6a00;
color: #fff;
}
.btn-secondary {
background: rgba(255, 255, 255, 0.15);
color: #fff;
backdrop-filter: blur(8px);
}
/* --------------------------------------------------- */
/* SECTIONS */
.section {
padding: 28px 20px 10px;
}
.section-heading {
margin-bottom: 28px;
}
.section-heading h2,
.discovery-copy h2 {
margin: 0;
font-size: 52px;
line-height: 0.95;
font-weight: 300;
letter-spacing: -0.04em;
color: #1f1f1f;
}
/* --------------------------------------------------- */
/* GRID */
.product-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 18px;
}
.product-card {
position: relative;
isolation: isolate;
overflow: hidden;
background: #f5f5f5;
border: 1px solid #d9d9d9;
border-radius: 18px;
padding: 18px;
min-height: 360px;
display: flex;
flex-direction: column;
justify-content: space-between;
cursor: pointer;
transition: transform 0.15s ease, border-color 0.15s ease;
}
.product-card:focus-visible {
outline: 2px solid #ff6a00;
outline-offset: 3px;
}
.product-hover-fill {
position: absolute;
inset: 0;
z-index: 2;
pointer-events: none;
}
.product-hover-image,
.product-hover-video {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
z-index: 0;
}
.product-hover-image {
background-size: cover;
background-position: center;
}
.product-hover-video {
display: block;
object-fit: cover;
}
.product-hover-fill::after {
content: "";
position: absolute;
inset: 0;
z-index: 1;
background: linear-gradient(
to bottom,
rgba(255, 255, 255, 0.08),
rgba(0, 0, 0, 0.18)
);
}
.product-card:active {
transform: scale(0.97);
border-color: #ff6a00;
}
.product-top {
position: relative;
z-index: 4;
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: 12px;
}
.product-id {
font-size: 18px;
color: #5f5f5f;
}
.product-top h3 {
margin: 0;
font-size: 18px;
font-weight: 400;
text-align: right;
letter-spacing: 0.02em;
}
.product-image-wrap {
position: relative;
z-index: 1;
display: flex;
justify-content: center;
align-items: center;
min-height: 180px;
padding: 20px 0;
width: 100%;
overflow: hidden;
}
.product-image {
position: relative;
z-index: 1;
width: 100%;
max-width: 600px;
height: auto;
object-fit: contain;
border-radius: 10px;
transition: transform 0.4s ease;
}
.product-card:hover .product-image {
transform: scale(1.05);
}
.product-bottom {
position: relative;
z-index: 4;
display: flex;
justify-content: space-between;
align-items: flex-end;
gap: 12px;
}
.product-bottom p {
margin: 0;
max-width: 170px;
font-size: 15px;
line-height: 1.35;
color: #5f5f5f;
}
.arrow {
font-size: 26px;
color: #ff6a00;
line-height: 1;
}
.product-id,
.product-top h3,
.product-bottom p,
.arrow {
transition: color 0.25s ease;
}
.product-card:hover .product-id,
.product-card:hover .product-top h3,
.product-card:hover .product-bottom p,
.product-card:hover .arrow,
.product-card:focus-within .product-id,
.product-card:focus-within .product-top h3,
.product-card:focus-within .product-bottom p,
.product-card:focus-within .arrow {
color: #fff;
mix-blend-mode: difference;
}
.product-card:active .product-id,
.product-card:active .product-top h3,
.product-card:active .product-bottom p,
.product-card:active .arrow {
color: #ff6a00;
mix-blend-mode: normal;
transform: scale(1.02);
transition: all 0.1s ease;
}
/* DISCOVERY */
.discovery-section {
display: grid;
grid-template-columns: 600px 1fr;
gap: 28px;
align-items: center;
background: #ff6a00;
margin: 20px;
border-radius: 24px;
padding: 40px 38px;
}
.discovery-copy h2 {
margin: 0;
font-size: 42px;
line-height: 0.95;
font-weight: 300;
letter-spacing: -0.04em;
color: #fff;
}
.discovery-copy p {
margin-top: 18px;
font-size: 15px;
line-height: 1.5;
color: #fff;
}
.discovery-btn {
border: none;
border-radius: 999px;
padding: 12px 18px;
font-size: 14px;
cursor: pointer;
transition: transform 0.2s ease, opacity 0.2s ease;
background: #fff;
color: #ff6a00;
}
.discovery-btn:hover {
transform: translateY(-1px);
box-shadow: 0 12px 24px rgba(0, 0, 0, 0.2);
}
.discovery-btn:active {
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15);
background: rgba(255, 255, 255, 0.8);
}
.discovery-banner {
position: relative;
width: 100%;
max-width: 1300px;
height: 340px;
border-radius: 20px;
overflow: hidden;
}
.discovery-banner img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
/* --------------------------------------------------- */
/* RESPONSIVE */
@media (max-width: 900px) {
.hero {
min-height: 620px;
}
.hero-content {
padding: 90px 24px 40px;
}
.hero h1,
.section-heading h2,
.discovery-copy h2 {
font-size: 42px;
}
.product-grid {
grid-template-columns: repeat(2, 1fr);
}
.discovery-section {
grid-template-columns: 1fr;
}
}
@media (max-width: 640px) {
.hero {
margin: 12px;
min-height: 540px;
}
.nav-pill {
gap: 4px;
padding: 6px;
}
.nav-link {
padding: 8px 10px;
font-size: 12px;
}
.hero h1,
.section-heading h2,
.discovery-copy h2 {
font-size: 34px;
}
.hero-actions {
flex-direction: column;
align-items: flex-start;
}
.product-grid {
grid-template-columns: 1fr;
}
.product-card {
min-height: 320px;
}
} }

View File

@ -1,14 +1,14 @@
import { Routes, Route } from "react-router";
import LandingPage from "./pages/LandingPage"; import LandingPage from "./pages/LandingPage";
import ProductDetailPage from "./components/ProductDetailPage"; import ProductDetailPage from "./components/ProductDetailPage";
function App() { function App() {
const showDetailPage = true; return (
<Routes>
if (showDetailPage) { <Route path="/" element={<LandingPage />} />
return <ProductDetailPage perfumeSlug="kalter-beton" />; <Route path="/duft/:perfumeSlug" element={<ProductDetailPage />} />
} </Routes>
);
return <LandingPage />;
} }
export default App; export default App;

View File

@ -1,13 +1,3 @@
/*
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 --- */ /* --- Product Detail Page Wrapper Start --- */
.detail-page { .detail-page {
@ -22,7 +12,7 @@
.detail-shell { .detail-shell {
background: #f5f5f5; background: #f5f5f5;
border: 1px solid #d9d9d9; border: 1px solid #d9d9d9;
border-radius: 18px; border-radius: 0px;
padding: 38px; padding: 38px;
} }
@ -64,7 +54,7 @@
background: #d9d9d9; background: #d9d9d9;
aspect-ratio: 1 / 1; aspect-ratio: 1 / 1;
overflow: hidden; overflow: hidden;
border-radius: 18px; border-radius: 0px;
} }
.detail-main-image img { .detail-main-image img {
@ -84,7 +74,7 @@
width: 88px; width: 88px;
height: 88px; height: 88px;
border: none; border: none;
border-radius: 18px; border-radius: 0px;
background: #fff; background: #fff;
padding: 0; padding: 0;
cursor: pointer; cursor: pointer;
@ -155,7 +145,7 @@
margin-top: 18px; margin-top: 18px;
background: #dfdfdf; background: #dfdfdf;
padding: 16px; padding: 16px;
border-radius: 18px; border-radius: 0px;
} }
.mood-label { .mood-label {
@ -251,7 +241,7 @@
min-height: 34px; min-height: 34px;
padding: 8px 14px; padding: 8px 14px;
border: 1px solid #d2d2d2; border: 1px solid #d2d2d2;
border-radius: 999px; border-radius: 0px;
background: transparent; background: transparent;
font-size: 12px; font-size: 12px;
user-select: none; user-select: none;
@ -269,7 +259,7 @@
.size-card { .size-card {
border: 1px solid #d0d0d0; border: 1px solid #d0d0d0;
border-radius: 18px; border-radius: 0px;
background: #fff; background: #fff;
padding: 18px; padding: 18px;
text-align: center; text-align: center;
@ -306,7 +296,7 @@
background: #1f1f1f; background: #1f1f1f;
color: #fff; color: #fff;
padding: 14px 16px; padding: 14px 16px;
border-radius: 18px; border-radius: 0px;
justify-content: space-between; justify-content: space-between;
display: flex; display: flex;
gap: 20px; gap: 20px;
@ -326,7 +316,7 @@
.discovery-note-btn { .discovery-note-btn {
border: none; border: none;
border-radius: 999px; border-radius: 18px;
background: #ff6a00; background: #ff6a00;
color: #fff; color: #fff;
padding: 12px 18px; padding: 12px 18px;
@ -350,7 +340,7 @@
.buy-button { .buy-button {
width: 100%; width: 100%;
border: none; border: none;
border-radius: 999px; border-radius: 0px;
background: #ff6a00; background: #ff6a00;
color: #fff; color: #fff;
padding: 18px; padding: 18px;
@ -393,7 +383,7 @@
margin-top: 40px; margin-top: 40px;
padding: 40px 38px; padding: 40px 38px;
background: #ff6a00; background: #ff6a00;
border-radius: 24px; border-radius: 0px;
} }
.detail-bottom-cta h2 { .detail-bottom-cta h2 {

View File

@ -1,9 +1,12 @@
import { useMemo, useState } from "react"; import { useMemo, useState } from "react";
import { Link, useNavigate, useParams } from "react-router";
import perfumes from "../data/perfumes"; import perfumes from "../data/perfumes";
import "../navbar.css"; import "../style/navbar.css";
import "./ProductDetailPage.css"; import "./ProductDetailPage.css";
function ProductDetailPage({ perfumeSlug = "kalter-beton" }) { function ProductDetailContent({ perfumeSlug }) {
const navigate = useNavigate();
const perfume = useMemo( const perfume = useMemo(
() => perfumes.find((item) => item.slug === perfumeSlug) || perfumes[0], () => perfumes.find((item) => item.slug === perfumeSlug) || perfumes[0],
[perfumeSlug] [perfumeSlug]
@ -31,33 +34,29 @@ function ProductDetailPage({ perfumeSlug = "kalter-beton" }) {
return ( return (
<div className="detail-page"> <div className="detail-page">
{/* Navbar */}
<nav className="navbar navbar--light"> <nav className="navbar navbar--light">
<div className="nav-pill"> <div className="nav-pill">
<a href="#home" className="nav-link active"> <Link to="/" className="nav-link">
Name Name
</a> </Link>
<a href="#dufte" className="nav-link"> <Link to="/#dufte" className="nav-link active">
Düfte Düfte
</a> </Link>
<a href="#testen" className="nav-link"> <Link to="/#testen" className="nav-link">
Testen Testen
</a> </Link>
<a href="#cart" className="nav-link"> <a href="#cart" className="nav-link">
Cart Cart
</a> </a>
</div> </div>
</nav> </nav>
{/* --- Navbar End --- */}
{/* Product detail content */}
<main className="detail-shell"> <main className="detail-shell">
<button className="back-link" type="button"> <button className="back-link" type="button" onClick={() => navigate("/")}>
Zurück zur Startseite Zurück zur Startseite
</button> </button>
<section className="detail-layout"> <section className="detail-layout">
{/* Left column */}
<div className="detail-gallery"> <div className="detail-gallery">
<div className="detail-main-image"> <div className="detail-main-image">
<img src={selectedImage} alt={perfume.name} /> <img src={selectedImage} alt={perfume.name} />
@ -132,7 +131,6 @@ function ProductDetailPage({ perfumeSlug = "kalter-beton" }) {
</div> </div>
</div> </div>
{/* Right column */}
<div className="detail-info"> <div className="detail-info">
<div className="detail-heading"> <div className="detail-heading">
<h1>{perfume.name}</h1> <h1>{perfume.name}</h1>
@ -174,10 +172,10 @@ function ProductDetailPage({ perfumeSlug = "kalter-beton" }) {
automatisch abgezogen. automatisch abgezogen.
</p> </p>
</div> </div>
<button className="discovery-note-btn" type="button"> <button className="discovery-note-btn" type="button">
Zum Set Zum Set
</button> </button>
</div> </div>
<button className="buy-button" type="button"> <button className="buy-button" type="button">
@ -208,7 +206,6 @@ function ProductDetailPage({ perfumeSlug = "kalter-beton" }) {
</div> </div>
</section> </section>
{/* Bottom CTA */}
<section className="detail-bottom-cta"> <section className="detail-bottom-cta">
<h2>Lieber erst testen?</h2> <h2>Lieber erst testen?</h2>
<p> <p>
@ -227,4 +224,10 @@ function ProductDetailPage({ perfumeSlug = "kalter-beton" }) {
); );
} }
function ProductDetailPage() {
const { perfumeSlug = "kalter-beton" } = useParams();
return <ProductDetailContent key={perfumeSlug} perfumeSlug={perfumeSlug} />;
}
export default ProductDetailPage; export default ProductDetailPage;

View File

@ -1,10 +1,13 @@
import { StrictMode } from 'react' import React from "react";
import { createRoot } from 'react-dom/client' import ReactDOM from "react-dom/client";
import './index.css' import { BrowserRouter } from "react-router";
import App from './App.jsx' import App from "./App";
import "./App.css";
createRoot(document.getElementById('root')).render( ReactDOM.createRoot(document.getElementById("root")).render(
<StrictMode> <React.StrictMode>
<BrowserRouter>
<App /> <App />
</StrictMode>, </BrowserRouter>
) </React.StrictMode>
);

View File

@ -0,0 +1,397 @@
.page {
min-height: 100vh;
background: #efefef;
color: #1f1f1f;
}
/* HERO */
.hero {
position: relative;
min-height: 720px;
margin-left: 20px;
margin-right: 20px;
margin-top: 0px;
border-radius: 0;
overflow: hidden;
background-image: url("/HERO.jpeg");
background-size: cover;
background-position: center;
}
.hero-overlay {
position: absolute;
inset: 0;
background:
linear-gradient(to right, rgba(0, 0, 0, 0.45), rgba(0, 0, 0, 0.1)),
linear-gradient(to bottom, rgba(0, 0, 0, 0.2), rgba(0, 0, 0, 0.45));
}
.hero-content {
position: relative;
z-index: 2;
max-width: 460px;
padding: 120px 0 0 38px;
color: white;
}
.eyebrow {
margin-bottom: 16px;
font-size: 12px;
letter-spacing: 0.18em;
opacity: 0.85;
}
.hero h1 {
margin: 0 0 18px;
font-size: 62px;
line-height: 0.95;
font-weight: 300;
letter-spacing: -0.04em;
color: white;
}
.hero-text {
max-width: 320px;
font-size: 15px;
line-height: 1.5;
color: rgba(255, 255, 255, 0.85);
}
.hero-actions {
display: flex;
gap: 12px;
margin-top: 28px;
}
.btn {
border: none;
border-radius: 999px;
padding: 12px 18px;
font-size: 14px;
cursor: pointer;
transition: transform 0.2s ease, opacity 0.2s ease;
}
.btn:hover {
transform: translateY(-1px);
}
.btn-primary {
background: #ff6a00;
color: #fff;
}
.btn-secondary {
background: rgba(255, 255, 255, 0.15);
color: #fff;
backdrop-filter: blur(8px);
}
/* SECTIONS */
.section {
padding: 28px 20px 10px;
}
.section-heading {
margin-bottom: 28px;
}
.section-heading h2,
.discovery-copy h2 {
margin: 0;
font-size: 52px;
line-height: 0.95;
font-weight: 300;
letter-spacing: -0.04em;
color: #1f1f1f;
}
/* GRID */
.product-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 18px;
}
.product-card {
position: relative;
isolation: isolate;
overflow: hidden;
background: #f5f5f5;
border: 1px solid #d9d9d9;
border-radius: 0px;
padding: 18px;
min-height: 360px;
display: flex;
flex-direction: column;
justify-content: space-between;
cursor: pointer;
transition: transform 0.15s ease, border-color 0.15s ease;
text-decoration: none;
color: inherit;
}
.product-card:focus-visible {
outline: 2px solid #ff6a00;
outline-offset: 3px;
}
.product-hover-fill {
position: absolute;
inset: 0;
z-index: 2;
pointer-events: none;
}
.product-hover-image,
.product-hover-video {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
z-index: 0;
}
.product-hover-image {
background-size: cover;
background-position: center;
}
.product-hover-video {
display: block;
object-fit: cover;
}
.product-hover-fill::after {
content: "";
position: absolute;
inset: 0;
z-index: 1;
background: linear-gradient(
to bottom,
rgba(255, 255, 255, 0.08),
rgba(0, 0, 0, 0.18)
);
}
.product-card:active {
transform: scale(0.97);
border-color: #ff6a00;
}
.product-top {
position: relative;
z-index: 4;
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: 12px;
}
.product-id {
font-size: 18px;
color: #5f5f5f;
}
.product-top h3 {
margin: 0;
font-size: 18px;
font-weight: 400;
text-align: right;
letter-spacing: 0.02em;
}
.product-image-wrap {
position: relative;
z-index: 1;
display: flex;
justify-content: center;
align-items: center;
min-height: 180px;
padding: 20px 0;
width: 100%;
overflow: hidden;
}
.product-image {
position: relative;
z-index: 1;
width: 100%;
max-width: 600px;
height: auto;
object-fit: contain;
border-radius: 0px;
transition: transform 0.4s ease;
}
.product-card:hover .product-image {
transform: scale(1.05);
}
.product-bottom {
position: relative;
z-index: 4;
display: flex;
justify-content: space-between;
align-items: flex-end;
gap: 12px;
}
.product-bottom p {
margin: 0;
max-width: 170px;
font-size: 15px;
line-height: 1.35;
color: #5f5f5f;
}
.arrow {
font-size: 26px;
color: #ff6a00;
line-height: 1;
}
.product-id,
.product-top h3,
.product-bottom p,
.arrow {
transition: color 0.25s ease;
}
.product-card:hover .product-id,
.product-card:hover .product-top h3,
.product-card:hover .product-bottom p,
.product-card:hover .arrow,
.product-card:focus-within .product-id,
.product-card:focus-within .product-top h3,
.product-card:focus-within .product-bottom p,
.product-card:focus-within .arrow {
color: #fff;
mix-blend-mode: difference;
}
.product-card:active .product-id,
.product-card:active .product-top h3,
.product-card:active .product-bottom p,
.product-card:active .arrow {
color: #ff6a00;
mix-blend-mode: normal;
transform: scale(1.02);
transition: all 0.1s ease;
}
/* DISCOVERY */
.discovery-section {
display: grid;
grid-template-columns: 600px 1fr;
gap: 28px;
align-items: center;
background: #ff6a00;
margin: 20px;
border-radius: 0px;
padding: 40px 38px;
}
.discovery-copy h2 {
margin: 0;
font-size: 42px;
line-height: 0.95;
font-weight: 300;
letter-spacing: -0.04em;
color: #fff;
}
.discovery-copy p {
margin-top: 18px;
font-size: 15px;
line-height: 1.5;
color: #fff;
}
.discovery-btn {
border: none;
border-radius: 999px;
padding: 12px 18px;
font-size: 14px;
cursor: pointer;
transition: transform 0.2s ease, opacity 0.2s ease;
background: #fff;
color: #ff6a00;
}
.discovery-btn:hover {
transform: translateY(-1px);
box-shadow: 0 12px 24px rgba(0, 0, 0, 0.2);
}
.discovery-btn:active {
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15);
background: rgba(255, 255, 255, 0.8);
}
.discovery-banner {
position: relative;
width: 100%;
max-width: 1300px;
height: 340px;
border-radius: 20px;
overflow: hidden;
}
.discovery-banner img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
/* RESPONSIVE */
@media (max-width: 900px) {
.hero {
min-height: 620px;
}
.hero-content {
padding: 90px 24px 40px;
}
.hero h1,
.section-heading h2,
.discovery-copy h2 {
font-size: 42px;
}
.product-grid {
grid-template-columns: repeat(2, 1fr);
}
.discovery-section {
grid-template-columns: 1fr;
}
}
@media (max-width: 640px) {
.hero {
margin: 12px;
min-height: 540px;
}
.hero h1,
.section-heading h2,
.discovery-copy h2 {
font-size: 34px;
}
.hero-actions {
flex-direction: column;
align-items: flex-start;
}
.product-grid {
grid-template-columns: 1fr;
}
.product-card {
min-height: 320px;
}
}

View File

@ -1,8 +1,9 @@
import { useEffect, useRef } from "react"; import { useEffect, useRef } from "react";
import { Link } from "react-router";
import { gsap } from "gsap"; import { gsap } from "gsap";
import perfumes from "../data/perfumes"; import perfumes from "../data/perfumes";
import "../App.css"; import "../pages/LandingPage.css";
import "../navbar.css"; import "../style/navbar.css";
function LandingPage() { function LandingPage() {
const cardRefs = useRef([]); const cardRefs = useRef([]);
@ -33,7 +34,9 @@ function LandingPage() {
video.pause(); video.pause();
try { try {
video.currentTime = 0; video.currentTime = 0;
} catch {} } catch {
// Ignore errors when setting currentTime
}
}; };
const playVideo = (video) => { const playVideo = (video) => {
@ -41,7 +44,9 @@ function LandingPage() {
try { try {
video.currentTime = 0; video.currentTime = 0;
} catch {} } catch {
// Ignore errors when setting currentTime
}
const playAttempt = video.play(); const playAttempt = video.play();
if (playAttempt && typeof playAttempt.catch === "function") { if (playAttempt && typeof playAttempt.catch === "function") {
@ -198,7 +203,8 @@ function LandingPage() {
<div className="product-grid"> <div className="product-grid">
{perfumes.map((item, index) => ( {perfumes.map((item, index) => (
<article <Link
to={`/duft/${item.slug}`}
className="product-card" className="product-card"
key={item.id} key={item.id}
ref={(el) => { ref={(el) => {
@ -238,7 +244,7 @@ function LandingPage() {
<p>{item.text}</p> <p>{item.text}</p>
<span className="arrow"></span> <span className="arrow"></span>
</div> </div>
</article> </Link>
))} ))}
</div> </div>
</section> </section>