add global text reveal hook and scroll animations

This commit is contained in:
Ermin Zoronjic 2026-04-23 22:56:47 +02:00
parent 9de9ce7f43
commit d0c7916386
13 changed files with 431 additions and 262 deletions

View File

@ -1,3 +1,4 @@
import { useRef } from "react";
import { Routes, Route, useLocation } from "react-router"; import { Routes, Route, useLocation } from "react-router";
import LandingPage from "./pages/LandingPage"; import LandingPage from "./pages/LandingPage";
import ProductDetailPage from "./components/ProductDetailPage"; import ProductDetailPage from "./components/ProductDetailPage";
@ -12,16 +13,22 @@ import SupportChatbot from "./components/SupportChatbot";
import ScrollToTop from "./components/ScrollToTop"; import ScrollToTop from "./components/ScrollToTop";
import ShopDrawer from "./components/ShopDrawer"; import ShopDrawer from "./components/ShopDrawer";
import CartToast from "./components/CartToast"; import CartToast from "./components/CartToast";
import useScrollTextReveal from "./hooks/useScrollTextReveal";
import "./style/textReveal.css";
function App() { function App() {
const location = useLocation(); const location = useLocation();
const routeContentRef = useRef(null);
const shouldFlushFooter = const shouldFlushFooter =
location.pathname === "/" || location.pathname.startsWith("/duft/"); location.pathname === "/" || location.pathname.startsWith("/duft/");
useScrollTextReveal(routeContentRef, [location.pathname]);
return ( return (
<> <>
<ScrollToTop /> <ScrollToTop />
<div ref={routeContentRef}>
<Routes> <Routes>
<Route path="/" element={<LandingPage />} /> <Route path="/" element={<LandingPage />} />
<Route path="/duft/:perfumeSlug" element={<ProductDetailPage />} /> <Route path="/duft/:perfumeSlug" element={<ProductDetailPage />} />
@ -32,6 +39,7 @@ function App() {
<Route path="/discovery-set" element={<DiscoverySetPage />} /> <Route path="/discovery-set" element={<DiscoverySetPage />} />
<Route path="/small-batch" element={<SmallBatchPage />} /> <Route path="/small-batch" element={<SmallBatchPage />} />
</Routes> </Routes>
</div>
<ShopDrawer /> <ShopDrawer />
<CartToast /> <CartToast />

View File

@ -247,11 +247,13 @@ function ProductDetailContent({ perfumeSlug }) {
</div> </div>
<div className="detail-info"> <div className="detail-info">
<div className="detail-heading"> <div className="detail-heading" data-reveal-group>
<div className="detail-heading-copy"> <div className="detail-heading-copy">
<span className="detail-kicker">Edition 04</span> <span className="detail-kicker" data-reveal="fade">
<h1>{perfume.name}</h1> Edition 04
<p>{perfume.shortText}</p> </span>
<h1 data-reveal="fade">{perfume.name}</h1>
<p data-reveal="fade">{perfume.shortText}</p>
</div> </div>
</div> </div>
@ -282,8 +284,8 @@ function ProductDetailContent({ perfumeSlug }) {
</div> </div>
</div> </div>
<div className="discovery-note"> <div className="discovery-note" data-reveal-group data-reveal-start="top 88%">
<div className="discovery-note-text"> <div className="discovery-note-text" data-reveal="fade">
<strong>Discovery Set wird einmalig angerechnet</strong> <strong>Discovery Set wird einmalig angerechnet</strong>
<p> <p>
Nur das erste Discovery Set erzeugt CHF 48 Guthaben. Es wird Nur das erste Discovery Set erzeugt CHF 48 Guthaben. Es wird
@ -297,7 +299,7 @@ function ProductDetailContent({ perfumeSlug }) {
)} )}
</div> </div>
<Link to="/discovery-set" className="discovery-note-btn"> <Link to="/discovery-set" className="discovery-note-btn" data-reveal="fade">
Zum Set Zum Set
</Link> </Link>
</div> </div>
@ -468,15 +470,15 @@ function ProductDetailContent({ perfumeSlug }) {
</div> </div>
</section> </section>
<section className="detail-bottom-cta"> <section className="detail-bottom-cta" data-reveal-group>
<h2>Lieber erst testen?</h2> <h2 data-reveal="fade">Lieber erst testen?</h2>
<p> <p data-reveal="fade">
Bestelle ein 2ml Sample für CHF 12 oder das komplette Discovery Set 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 mit allen 6 Düften für CHF 48. Beide werden beim späteren Full-Size-Kauf
vollständig angerechnet. vollständig angerechnet.
</p> </p>
<div className="detail-bottom-actions"> <div className="detail-bottom-actions" data-reveal="fade">
<button <button
type="button" type="button"
onClick={() => onClick={() =>

View File

@ -6,12 +6,18 @@ function SharedNavbar({ variant = "light", active = "" }) {
const { cart, openCart, openProfile, user } = useShop(); const { cart, openCart, openProfile, user } = useShop();
const cartLabel = const cartLabel =
cart.total_quantity > 0 ? `Cart ${cart.total_quantity}` : "Cart"; cart.total_quantity > 0 ? `Cart ${cart.total_quantity}` : "Cart";
const logoSrc =
variant === "hero" ? "/atmos-logo-light.svg" : "/atmos-logo-dark.svg";
return ( return (
<nav className={`navbar navbar--${variant}`} aria-label="Hauptnavigation"> <nav className={`navbar navbar--${variant}`} aria-label="Hauptnavigation">
<div className="nav-pill"> <div className="nav-pill">
<Link to="/" className={`nav-link ${active === "atmos" ? "active" : ""}`}> <Link
atmos to="/"
className={`nav-link nav-link--brand ${active === "atmos" ? "active" : ""}`}
aria-label="Atmos Startseite"
>
<img src={logoSrc} alt="" className="nav-brand-logo" />
</Link> </Link>
<Link <Link
to="/discovery-set" to="/discovery-set"

View File

@ -4,6 +4,7 @@ import SharedNavbar from "../SharedNavbar";
function HeroSection({ function HeroSection({
heroImageWrapRef, heroImageWrapRef,
heroImageRef,
setHeadlinePrimaryRef, setHeadlinePrimaryRef,
setHeadlineSecondaryRef, setHeadlineSecondaryRef,
setDescriptionRef, setDescriptionRef,
@ -18,31 +19,26 @@ function HeroSection({
src="/atmos-hero-image.png" src="/atmos-hero-image.png"
alt="Atmos Hero" alt="Atmos Hero"
className="hero-media__image" className="hero-media__image"
ref={heroImageRef}
loading="eager" loading="eager"
fetchPriority="high" fetchPriority="high"
/> />
</div> </div>
<Link to="/" className="hero-brand" aria-label="Atmos Startseite">
<img
src="/atmos-logo-light.svg"
alt="atmos"
className="hero-brand__logo"
loading="eager"
fetchPriority="high"
/>
</Link>
<SharedNavbar variant="hero" active="atmos" /> <SharedNavbar variant="hero" active="atmos" />
<div className="hero-content"> <div className="hero-content">
<h1 className="hero-title"> <h1 className="hero-title">
<span className="hero-title-line" ref={setHeadlinePrimaryRef}> <span className="hero-title-line">
<span className="reveal-line" ref={setHeadlinePrimaryRef}>
{"D\u00DCFTE ALS"} {"D\u00DCFTE ALS"}
</span> </span>
<span className="hero-title-line" ref={setHeadlineSecondaryRef}> </span>
<span className="hero-title-line">
<span className="reveal-line" ref={setHeadlineSecondaryRef}>
AUSDRUCK AUSDRUCK
</span> </span>
</span>
</h1> </h1>
<p className="hero-text" ref={setDescriptionRef}> <p className="hero-text" ref={setDescriptionRef}>

View File

@ -1,3 +1,4 @@
import { Link } from "react-router";
import SharedNavbar from "../components/SharedNavbar"; import SharedNavbar from "../components/SharedNavbar";
import "./AboutPage.css"; import "./AboutPage.css";
@ -7,10 +8,12 @@ function AboutPage() {
<SharedNavbar variant="light" /> <SharedNavbar variant="light" />
<main className="about-shell"> <main className="about-shell">
<section className="about-hero"> <section className="about-hero" data-reveal-group>
<div className="about-hero-copy"> <div className="about-hero-copy">
<span className="about-kicker">ABOUT atmos</span> <span className="about-kicker" data-reveal="fade">
<h1> ABOUT atmos
</span>
<h1 data-reveal="lines">
WIR DEKODIEREN WIR DEKODIEREN
<br /> <br />
ATMOSPHÄREN ATMOSPHÄREN
@ -19,7 +22,7 @@ function AboutPage() {
<br /> <br />
SIE IN DÜFTE SIE IN DÜFTE
</h1> </h1>
<p className="about-intro"> <p className="about-intro" data-reveal="fade">
atmos entwickelt Nischendüfte, die nicht bloss gut riechen sollen, atmos entwickelt Nischendüfte, die nicht bloss gut riechen sollen,
sondern eine Atmosphäre präzise übersetzen: Material, Raum, Kälte, sondern eine Atmosphäre präzise übersetzen: Material, Raum, Kälte,
Licht, Oberfläche, Spannung. Jeder Duft ist eine verdichtete Licht, Oberfläche, Spannung. Jeder Duft ist eine verdichtete
@ -27,7 +30,7 @@ function AboutPage() {
</p> </p>
</div> </div>
<div className="about-hero-panel"> <div className="about-hero-panel" data-reveal="fade">
<span className="about-panel-label">POSITIONIERUNG</span> <span className="about-panel-label">POSITIONIERUNG</span>
<p> <p>
Nischig. Hochwertig. Edel. Luxuriös. Reduziert in der Form, Nischig. Hochwertig. Edel. Luxuriös. Reduziert in der Form,
@ -51,13 +54,15 @@ function AboutPage() {
</div> </div>
</section> </section>
<section className="about-section about-section--split"> <section className="about-section about-section--split" data-reveal-group>
<div className="about-section-heading"> <div className="about-section-heading">
<span className="about-label">UNSER VERSTÄNDNIS</span> <span className="about-label" data-reveal="fade">
<h2>NICHT DEKORATION. SONDERN HALTUNG.</h2> UNSER VERSTÄNDNIS
</span>
<h2 data-reveal="lines">NICHT DEKORATION. SONDERN HALTUNG.</h2>
</div> </div>
<div className="about-section-copy"> <div className="about-section-copy" data-reveal="fade">
<p> <p>
atmos versteht Parfum nicht als beiläufiges Accessoire, sondern als atmos versteht Parfum nicht als beiläufiges Accessoire, sondern als
Form von Ausdruck. Unsere Düfte entstehen nicht aus Trends, sondern Form von Ausdruck. Unsere Düfte entstehen nicht aus Trends, sondern
@ -75,34 +80,34 @@ function AboutPage() {
</div> </div>
</section> </section>
<section className="about-proof-strip"> <section className="about-proof-strip" data-reveal-group data-reveal-start="top 90%">
<div className="about-proof-item"> <div className="about-proof-item" data-reveal="fade">
<span className="about-label">ATELIER</span> <span className="about-label">ATELIER</span>
<p>Entwicklung aus einem kuratierten Duft- und Materialkontext</p> <p>Entwicklung aus einem kuratierten Duft- und Materialkontext</p>
</div> </div>
<div className="about-proof-item"> <div className="about-proof-item" data-reveal="fade">
<span className="about-label">KLEINSERIEN</span> <span className="about-label">KLEINSERIEN</span>
<p>Chargenbasiert gedacht statt massenmarktfähig optimiert</p> <p>Chargenbasiert gedacht statt massenmarktfähig optimiert</p>
</div> </div>
<div className="about-proof-item"> <div className="about-proof-item" data-reveal="fade">
<span className="about-label">KOMPOSITION</span> <span className="about-label">KOMPOSITION</span>
<p>Materiallogik vor Trendformel und Lautstärke</p> <p>Materiallogik vor Trendformel und Lautstärke</p>
</div> </div>
<div className="about-proof-item"> <div className="about-proof-item" data-reveal="fade">
<span className="about-label">HERKUNFT</span> <span className="about-label">HERKUNFT</span>
<p>Creative Direction und Qualitätsanspruch aus der Schweiz</p> <p>Creative Direction und Qualitätsanspruch aus der Schweiz</p>
</div> </div>
</section> </section>
<section className="about-quote-block"> <section className="about-quote-block" data-reveal-group>
<p> <p data-reveal="fade">
Jeder Duft beginnt bei uns nicht mit einer Note, sondern mit einem Jeder Duft beginnt bei uns nicht mit einer Note, sondern mit einem
Raumgefühl. Raumgefühl.
</p> </p>
</section> </section>
<section className="about-grid-section"> <section className="about-grid-section" data-reveal-group data-reveal-start="top 84%">
<div className="about-card"> <div className="about-card" data-reveal="fade">
<span className="about-label">01 / DEKODIEREN</span> <span className="about-label">01 / DEKODIEREN</span>
<h3>Atmosphäre lesen</h3> <h3>Atmosphäre lesen</h3>
<p> <p>
@ -112,7 +117,7 @@ function AboutPage() {
</p> </p>
</div> </div>
<div className="about-card"> <div className="about-card" data-reveal="fade">
<span className="about-label">02 / VERDICHTEN</span> <span className="about-label">02 / VERDICHTEN</span>
<h3>In Duft übersetzen</h3> <h3>In Duft übersetzen</h3>
<p> <p>
@ -122,7 +127,7 @@ function AboutPage() {
</p> </p>
</div> </div>
<div className="about-card"> <div className="about-card" data-reveal="fade">
<span className="about-label">03 / REDUZIEREN</span> <span className="about-label">03 / REDUZIEREN</span>
<h3>Wirkung schärfen</h3> <h3>Wirkung schärfen</h3>
<p> <p>
@ -133,13 +138,20 @@ function AboutPage() {
</div> </div>
</section> </section>
<section className="about-section about-section--split about-process-section"> <section
className="about-section about-section--split about-process-section"
data-reveal-group
>
<div className="about-section-heading"> <div className="about-section-heading">
<span className="about-label">PROZESS & KOMPETENZ</span> <span className="about-label" data-reveal="fade">
<h2>WIE AUS EINER IDEE EINE BELASTBARE KOMPOSITION WIRD.</h2> PROZESS & KOMPETENZ
</span>
<h2 data-reveal="lines">
WIE AUS EINER IDEE EINE BELASTBARE KOMPOSITION WIRD.
</h2>
</div> </div>
<div className="about-section-copy"> <div className="about-section-copy" data-reveal="fade">
<p> <p>
atmos arbeitet nicht mit einem losgelösten Storytelling, das im atmos arbeitet nicht mit einem losgelösten Storytelling, das im
Nachhinein auf einen Duft gelegt wird. Jede Komposition beginnt mit Nachhinein auf einen Duft gelegt wird. Jede Komposition beginnt mit
@ -156,8 +168,12 @@ function AboutPage() {
</div> </div>
</section> </section>
<section className="about-credentials-grid"> <section
<article className="about-credential-card"> className="about-credentials-grid"
data-reveal-group
data-reveal-start="top 84%"
>
<article className="about-credential-card" data-reveal="fade">
<span className="about-label">ATELIER / ENTWICKLUNG</span> <span className="about-label">ATELIER / ENTWICKLUNG</span>
<h3>Komposition aus einem klaren Duftverständnis</h3> <h3>Komposition aus einem klaren Duftverständnis</h3>
<p> <p>
@ -167,7 +183,7 @@ function AboutPage() {
</p> </p>
</article> </article>
<article className="about-credential-card"> <article className="about-credential-card" data-reveal="fade">
<span className="about-label">MATERIALLOGIK</span> <span className="about-label">MATERIALLOGIK</span>
<h3>Noten folgen einer Idee, nicht bloss einer Wirkung</h3> <h3>Noten folgen einer Idee, nicht bloss einer Wirkung</h3>
<p> <p>
@ -177,7 +193,7 @@ function AboutPage() {
</p> </p>
</article> </article>
<article className="about-credential-card"> <article className="about-credential-card" data-reveal="fade">
<span className="about-label">CHARGENPRINZIP</span> <span className="about-label">CHARGENPRINZIP</span>
<h3>Kleinserie statt gesichtsloser Massenästhetik</h3> <h3>Kleinserie statt gesichtsloser Massenästhetik</h3>
<p> <p>
@ -187,7 +203,7 @@ function AboutPage() {
</p> </p>
</article> </article>
<article className="about-credential-card"> <article className="about-credential-card" data-reveal="fade">
<span className="about-label">QUALITÄTSPRÜFUNG</span> <span className="about-label">QUALITÄTSPRÜFUNG</span>
<h3>Komposition, Haltbarkeit und Verlauf werden bewusst geprüft</h3> <h3>Komposition, Haltbarkeit und Verlauf werden bewusst geprüft</h3>
<p> <p>
@ -198,11 +214,15 @@ function AboutPage() {
</article> </article>
</section> </section>
<section className="about-method-section"> <section className="about-method-section" data-reveal-group>
<div className="about-method-copy"> <div className="about-method-copy">
<span className="about-label">NACHWEISBARE DUFTLOGIK</span> <span className="about-label" data-reveal="fade">
<h2>FÜR MENSCHEN, DIE HYPE ERKENNEN UND SUBSTANZ SUCHEN.</h2> NACHWEISBARE DUFTLOGIK
<p> </span>
<h2 data-reveal="lines">
FÜR MENSCHEN, DIE HYPE ERKENNEN UND SUBSTANZ SUCHEN.
</h2>
<p data-reveal="fade">
Wer in der Szene unterwegs ist, erkennt schnell, wenn ein Duft vor Wer in der Szene unterwegs ist, erkennt schnell, wenn ein Duft vor
allem über Bildsprache verkauft wird. Genau deshalb machen wir unsere allem über Bildsprache verkauft wird. Genau deshalb machen wir unsere
Denkweise sichtbar: über Struktur, Materialbezug, Edition, Trageverlauf Denkweise sichtbar: über Struktur, Materialbezug, Edition, Trageverlauf
@ -210,7 +230,7 @@ function AboutPage() {
</p> </p>
</div> </div>
<div className="about-method-points"> <div className="about-method-points" data-reveal="fade">
<div> <div>
<span>DUFTSTRUKTUR</span> <span>DUFTSTRUKTUR</span>
<p>Top, Heart und Base werden als Verlauf und nicht nur als Liste gedacht.</p> <p>Top, Heart und Base werden als Verlauf und nicht nur als Liste gedacht.</p>
@ -230,23 +250,25 @@ function AboutPage() {
</div> </div>
</section> </section>
<section className="about-section about-origin-section"> <section className="about-section about-origin-section" data-reveal-group>
<div className="about-origin-copy"> <div className="about-origin-copy">
<span className="about-label">MADE IN SWITZERLAND</span> <span className="about-label" data-reveal="fade">
<h2>PRÄZISION IN FORM, DUFT UND AUFTRITT.</h2> MADE IN SWITZERLAND
<p> </span>
<h2 data-reveal="lines">PRÄZISION IN FORM, DUFT UND AUFTRITT.</h2>
<p data-reveal="fade">
atmos ist in der Schweiz verankert. Dieser Ursprung zeigt sich nicht atmos ist in der Schweiz verankert. Dieser Ursprung zeigt sich nicht
nur in der Herstellung, sondern auch in unserer Haltung zur Qualität: nur in der Herstellung, sondern auch in unserer Haltung zur Qualität:
kontrolliert, bewusst, präzise und kompromisslos in der Ausarbeitung. kontrolliert, bewusst, präzise und kompromisslos in der Ausarbeitung.
</p> </p>
<p> <p data-reveal="fade">
Unsere Düfte bewegen sich zwischen Luxus und Konzept. Sie sollen Unsere Düfte bewegen sich zwischen Luxus und Konzept. Sie sollen
hochwertig wirken, ohne laut zu werden. Edel, ohne klassisch zu sein. hochwertig wirken, ohne laut zu werden. Edel, ohne klassisch zu sein.
Und luxuriös, ohne sich über Überfluss zu definieren. Und luxuriös, ohne sich über Überfluss zu definieren.
</p> </p>
</div> </div>
<div className="about-origin-box"> <div className="about-origin-box" data-reveal="fade">
<div> <div>
<span>QUALITÄTSVERSPRECHEN</span> <span>QUALITÄTSVERSPRECHEN</span>
<p>Kuratiert, präzise und mit Fokus auf charakterstarke Kompositionen.</p> <p>Kuratiert, präzise und mit Fokus auf charakterstarke Kompositionen.</p>
@ -262,9 +284,11 @@ function AboutPage() {
</div> </div>
</section> </section>
<section className="about-trust-note"> <section className="about-trust-note" data-reveal-group>
<span className="about-label">QUALITÄTSVERSTÄNDNIS</span> <span className="about-label" data-reveal="fade">
<p> QUALITÄTSVERSTÄNDNIS
</span>
<p data-reveal="fade">
Bei atmos steht nicht die schnelle Aufmerksamkeit im Vordergrund, sondern die Bei atmos steht nicht die schnelle Aufmerksamkeit im Vordergrund, sondern die
Qualität der Komposition. Unsere Düfte entstehen aus klaren Materialideen, Qualität der Komposition. Unsere Düfte entstehen aus klaren Materialideen,
kontrollierter Entwicklung und einem kuratierten Anspruch an Verlauf, kontrollierter Entwicklung und einem kuratierten Anspruch an Verlauf,
@ -272,19 +296,23 @@ function AboutPage() {
sondern bestehen durch Präzision, Eigenständigkeit und eine spürbare sondern bestehen durch Präzision, Eigenständigkeit und eine spürbare
kompositorische Disziplin. kompositorische Disziplin.
</p> </p>
</section> </section>
<section className="about-bottom-cta"> <section className="about-bottom-cta" data-reveal-group>
<div className="about-bottom-copy"> <div className="about-bottom-copy">
<span className="about-label">atmos</span> <span className="about-label" data-reveal="fade">
<h2>FÜR MENSCHEN, DIE NICHT NUR EINEN DUFT SUCHEN, SONDERN EINE HALTUNG.</h2> atmos
<p> </span>
<h2 data-reveal="lines">
FÜR MENSCHEN, DIE NICHT NUR EINEN DUFT SUCHEN, SONDERN EINE HALTUNG.
</h2>
<p data-reveal="fade">
Entdecke Düfte, die Atmosphäre nicht illustrieren, sondern in eine Entdecke Düfte, die Atmosphäre nicht illustrieren, sondern in eine
tragbare Form übersetzen. tragbare Form übersetzen.
</p> </p>
</div> </div>
<div className="about-bottom-actions"> <div className="about-bottom-actions" data-reveal="fade">
<Link to="/" className="about-btn about-btn--primary"> <Link to="/" className="about-btn about-btn--primary">
Zur Startseite Zur Startseite
</Link> </Link>

View File

@ -7,10 +7,10 @@ function DatenschutzPage() {
<SharedNavbar variant="light" /> <SharedNavbar variant="light" />
<main className="datenschutz-shell"> <main className="datenschutz-shell">
<section className="datenschutz-hero"> <section className="datenschutz-hero" data-reveal-group>
<span className="datenschutz-kicker">RECHTLICHE ANGABEN</span> <span className="datenschutz-kicker" data-reveal="fade">RECHTLICHE ANGABEN</span>
<h1>DATENSCHUTZ</h1> <h1 data-reveal="lines">DATENSCHUTZ</h1>
<p className="datenschutz-intro"> <p className="datenschutz-intro" data-reveal="fade">
Der Schutz persönlicher Daten hat für atmos einen hohen Stellenwert. Der Schutz persönlicher Daten hat für atmos einen hohen Stellenwert.
Nachfolgend informieren wir darüber, welche personenbezogenen Daten bei Nachfolgend informieren wir darüber, welche personenbezogenen Daten bei
der Nutzung dieser Website erhoben, verarbeitet und gespeichert werden, der Nutzung dieser Website erhoben, verarbeitet und gespeichert werden,
@ -19,13 +19,13 @@ function DatenschutzPage() {
</p> </p>
</section> </section>
<section className="datenschutz-section"> <section className="datenschutz-section" data-reveal-group>
<div className="datenschutz-section-heading"> <div className="datenschutz-section-heading">
<span className="datenschutz-label">VERANTWORTLICHE STELLE</span> <span className="datenschutz-label" data-reveal="fade">VERANTWORTLICHE STELLE</span>
<h2>WER FÜR DIE DATENVERARBEITUNG VERANTWORTLICH IST</h2> <h2 data-reveal="lines">WER FÜR DIE DATENVERARBEITUNG VERANTWORTLICH IST</h2>
</div> </div>
<div className="datenschutz-section-copy"> <div className="datenschutz-section-copy" data-reveal="fade">
<p> <p>
Verantwortlich für die Datenverarbeitung im Zusammenhang mit dieser Verantwortlich für die Datenverarbeitung im Zusammenhang mit dieser
Website ist: Website ist:
@ -47,13 +47,13 @@ function DatenschutzPage() {
</div> </div>
</section> </section>
<section className="datenschutz-section"> <section className="datenschutz-section" data-reveal-group>
<div className="datenschutz-section-heading"> <div className="datenschutz-section-heading">
<span className="datenschutz-label">ALLGEMEINES</span> <span className="datenschutz-label" data-reveal="fade">ALLGEMEINES</span>
<h2>WELCHE DATEN ERHOBEN WERDEN</h2> <h2 data-reveal="lines">WELCHE DATEN ERHOBEN WERDEN</h2>
</div> </div>
<div className="datenschutz-section-copy"> <div className="datenschutz-section-copy" data-reveal="fade">
<p> <p>
Beim Besuch dieser Website können automatisch technische Daten Beim Besuch dieser Website können automatisch technische Daten
erfasst werden. Dazu gehören insbesondere IP-Adresse, Datum und erfasst werden. Dazu gehören insbesondere IP-Adresse, Datum und
@ -70,13 +70,13 @@ function DatenschutzPage() {
</div> </div>
</section> </section>
<section className="datenschutz-section"> <section className="datenschutz-section" data-reveal-group>
<div className="datenschutz-section-heading"> <div className="datenschutz-section-heading">
<span className="datenschutz-label">ZWECK</span> <span className="datenschutz-label" data-reveal="fade">ZWECK</span>
<h2>WOFÜR DIE DATEN VERWENDET WERDEN</h2> <h2 data-reveal="lines">WOFÜR DIE DATEN VERWENDET WERDEN</h2>
</div> </div>
<div className="datenschutz-section-copy"> <div className="datenschutz-section-copy" data-reveal="fade">
<p>Die Verarbeitung personenbezogener Daten erfolgt insbesondere:</p> <p>Die Verarbeitung personenbezogener Daten erfolgt insbesondere:</p>
<ul className="datenschutz-list"> <ul className="datenschutz-list">
<li>zur Bereitstellung und technischen Optimierung der Website,</li> <li>zur Bereitstellung und technischen Optimierung der Website,</li>
@ -89,13 +89,13 @@ function DatenschutzPage() {
</div> </div>
</section> </section>
<section className="datenschutz-section"> <section className="datenschutz-section" data-reveal-group>
<div className="datenschutz-section-heading"> <div className="datenschutz-section-heading">
<span className="datenschutz-label">COOKIES & TRACKING</span> <span className="datenschutz-label" data-reveal="fade">COOKIES & TRACKING</span>
<h2>COOKIES UND ÄHNLICHE TECHNOLOGIEN</h2> <h2 data-reveal="lines">COOKIES UND ÄHNLICHE TECHNOLOGIEN</h2>
</div> </div>
<div className="datenschutz-section-copy"> <div className="datenschutz-section-copy" data-reveal="fade">
<p> <p>
Diese Website kann Cookies oder ähnliche Technologien verwenden, um Diese Website kann Cookies oder ähnliche Technologien verwenden, um
Funktionen bereitzustellen, die Nutzung zu analysieren und das Funktionen bereitzustellen, die Nutzung zu analysieren und das
@ -112,13 +112,13 @@ function DatenschutzPage() {
</div> </div>
</section> </section>
<section className="datenschutz-section"> <section className="datenschutz-section" data-reveal-group>
<div className="datenschutz-section-heading"> <div className="datenschutz-section-heading">
<span className="datenschutz-label">WEITERGABE</span> <span className="datenschutz-label" data-reveal="fade">WEITERGABE</span>
<h2>WEITERGABE AN DRITTE</h2> <h2 data-reveal="lines">WEITERGABE AN DRITTE</h2>
</div> </div>
<div className="datenschutz-section-copy"> <div className="datenschutz-section-copy" data-reveal="fade">
<p> <p>
Eine Weitergabe personenbezogener Daten an Dritte erfolgt nur, soweit Eine Weitergabe personenbezogener Daten an Dritte erfolgt nur, soweit
dies zur Vertragserfüllung notwendig ist, eine gesetzliche dies zur Vertragserfüllung notwendig ist, eine gesetzliche
@ -134,13 +134,13 @@ function DatenschutzPage() {
</div> </div>
</section> </section>
<section className="datenschutz-section"> <section className="datenschutz-section" data-reveal-group>
<div className="datenschutz-section-heading"> <div className="datenschutz-section-heading">
<span className="datenschutz-label">SPEICHERDAUER</span> <span className="datenschutz-label" data-reveal="fade">SPEICHERDAUER</span>
<h2>WIE LANGE DATEN GESPEICHERT WERDEN</h2> <h2 data-reveal="lines">WIE LANGE DATEN GESPEICHERT WERDEN</h2>
</div> </div>
<div className="datenschutz-section-copy"> <div className="datenschutz-section-copy" data-reveal="fade">
<p> <p>
Personenbezogene Daten werden nur so lange gespeichert, wie dies für Personenbezogene Daten werden nur so lange gespeichert, wie dies für
die jeweiligen Zwecke erforderlich ist oder gesetzliche die jeweiligen Zwecke erforderlich ist oder gesetzliche
@ -151,13 +151,13 @@ function DatenschutzPage() {
</div> </div>
</section> </section>
<section className="datenschutz-section"> <section className="datenschutz-section" data-reveal-group>
<div className="datenschutz-section-heading"> <div className="datenschutz-section-heading">
<span className="datenschutz-label">RECHTE</span> <span className="datenschutz-label" data-reveal="fade">RECHTE</span>
<h2>RECHTE DER BETROFFENEN PERSONEN</h2> <h2 data-reveal="lines">RECHTE DER BETROFFENEN PERSONEN</h2>
</div> </div>
<div className="datenschutz-section-copy"> <div className="datenschutz-section-copy" data-reveal="fade">
<p> <p>
Betroffene Personen haben im Rahmen des anwendbaren Datenschutzrechts Betroffene Personen haben im Rahmen des anwendbaren Datenschutzrechts
insbesondere das Recht auf Auskunft, Berichtigung, Löschung, insbesondere das Recht auf Auskunft, Berichtigung, Löschung,
@ -171,13 +171,13 @@ function DatenschutzPage() {
</div> </div>
</section> </section>
<section className="datenschutz-section"> <section className="datenschutz-section" data-reveal-group>
<div className="datenschutz-section-heading"> <div className="datenschutz-section-heading">
<span className="datenschutz-label">SICHERHEIT</span> <span className="datenschutz-label" data-reveal="fade">SICHERHEIT</span>
<h2>TECHNISCHE UND ORGANISATORISCHE MASSNAHMEN</h2> <h2 data-reveal="lines">TECHNISCHE UND ORGANISATORISCHE MASSNAHMEN</h2>
</div> </div>
<div className="datenschutz-section-copy"> <div className="datenschutz-section-copy" data-reveal="fade">
<p> <p>
Wir treffen angemessene technische und organisatorische Wir treffen angemessene technische und organisatorische
Sicherheitsmassnahmen, um personenbezogene Daten vor unbefugtem Sicherheitsmassnahmen, um personenbezogene Daten vor unbefugtem
@ -190,13 +190,13 @@ function DatenschutzPage() {
</div> </div>
</section> </section>
<section className="datenschutz-section"> <section className="datenschutz-section" data-reveal-group>
<div className="datenschutz-section-heading"> <div className="datenschutz-section-heading">
<span className="datenschutz-label">AKTUALISIERUNG</span> <span className="datenschutz-label" data-reveal="fade">AKTUALISIERUNG</span>
<h2>ÄNDERUNGEN DIESER DATENSCHUTZERKLÄRUNG</h2> <h2 data-reveal="lines">ÄNDERUNGEN DIESER DATENSCHUTZERKLÄRUNG</h2>
</div> </div>
<div className="datenschutz-note-box"> <div className="datenschutz-note-box" data-reveal="fade">
<p> <p>
atmos behält sich vor, diese Datenschutzerklärung bei Bedarf atmos behält sich vor, diese Datenschutzerklärung bei Bedarf
anzupassen, insbesondere wenn sich rechtliche Vorgaben, technische anzupassen, insbesondere wenn sich rechtliche Vorgaben, technische

View File

@ -31,10 +31,12 @@ function DiscoverySetPage() {
</button> </button>
</div> </div>
<section className="discovery-hero"> <section className="discovery-hero" data-reveal-group>
<div className="discovery-hero-copy"> <div className="discovery-hero-copy">
<span className="discovery-kicker">DISCOVERY SET</span> <span className="discovery-kicker" data-reveal="fade">
<h1> DISCOVERY SET
</span>
<h1 data-reveal="lines">
DER SICHERSTE EINSTIEG DER SICHERSTE EINSTIEG
<br /> <br />
IN DIE WELT DER NISCHEN- IN DIE WELT DER NISCHEN-
@ -42,12 +44,12 @@ function DiscoverySetPage() {
DÜFTE DÜFTE
</h1> </h1>
<p className="discovery-intro"> <p className="discovery-intro" data-reveal="fade">
6 Düfte × 2ml. Jeden Duft eine Woche tragen. Verstehen, was 6 Düfte × 2ml. Jeden Duft eine Woche tragen. Verstehen, was
wirklich funktioniert. Ohne Risiko. wirklich funktioniert. Ohne Risiko.
</p> </p>
<div className="discovery-benefits"> <div className="discovery-benefits" data-reveal="fade">
<div className="discovery-benefit"> <div className="discovery-benefit">
<span className="discovery-benefit-icon"></span> <span className="discovery-benefit-icon"></span>
<div> <div>
@ -93,7 +95,7 @@ function DiscoverySetPage() {
</div> </div>
</div> </div>
<div className="discovery-hero-actions"> <div className="discovery-hero-actions" data-reveal="fade">
<button type="button" className="discovery-primary-btn" onClick={buyDiscoverySet}> <button type="button" className="discovery-primary-btn" onClick={buyDiscoverySet}>
DISCOVERY SET BESTELLEN CHF 48. DISCOVERY SET BESTELLEN CHF 48.
</button> </button>
@ -110,10 +112,12 @@ function DiscoverySetPage() {
</div> </div>
</section> </section>
<section className="discovery-included"> <section className="discovery-included" data-reveal-group>
<div className="discovery-section-heading"> <div className="discovery-section-heading">
<span className="discovery-label">IM SET ENTHALTEN</span> <span className="discovery-label" data-reveal="fade">
<h2>ALLE 6 SIGNATURE-DÜFTE ZUM TESTEN.</h2> IM SET ENTHALTEN
</span>
<h2 data-reveal="lines">ALLE 6 SIGNATURE-DÜFTE ZUM TESTEN.</h2>
</div> </div>
<div className="discovery-products-grid"> <div className="discovery-products-grid">
@ -139,11 +143,11 @@ function DiscoverySetPage() {
</section> </section>
<section className="discovery-steps-section"> <section className="discovery-steps-section">
<div className="discovery-steps-shell"> <div className="discovery-steps-shell" data-reveal-group>
<h2>So funktioniert&apos;s</h2> <h2 data-reveal="lines">So funktioniert&apos;s</h2>
<div className="discovery-steps-grid"> <div className="discovery-steps-grid">
<article className="discovery-step-card"> <article className="discovery-step-card" data-reveal="fade">
<div className="discovery-step-number">1</div> <div className="discovery-step-number">1</div>
<h3>Bestellen</h3> <h3>Bestellen</h3>
<p> <p>
@ -152,7 +156,7 @@ function DiscoverySetPage() {
</p> </p>
</article> </article>
<article className="discovery-step-card"> <article className="discovery-step-card" data-reveal="fade">
<div className="discovery-step-number">2</div> <div className="discovery-step-number">2</div>
<h3>Testen</h3> <h3>Testen</h3>
<p> <p>
@ -161,7 +165,7 @@ function DiscoverySetPage() {
</p> </p>
</article> </article>
<article className="discovery-step-card"> <article className="discovery-step-card" data-reveal="fade">
<div className="discovery-step-number">3</div> <div className="discovery-step-number">3</div>
<h3>Entscheiden</h3> <h3>Entscheiden</h3>
<p> <p>
@ -173,11 +177,13 @@ function DiscoverySetPage() {
</div> </div>
</section> </section>
<section className="discovery-comparison-section"> <section className="discovery-comparison-section" data-reveal-group>
<div className="discovery-section-heading discovery-section-heading--center"> <div className="discovery-section-heading discovery-section-heading--center">
<span className="discovery-label">WARUM DISCOVERY SET</span> <span className="discovery-label" data-reveal="fade">
<h2>DER KLÜGERE EINSTIEG IN NISCHENDÜFTE.</h2> WARUM DISCOVERY SET
<p> </span>
<h2 data-reveal="lines">DER KLÜGERE EINSTIEG IN NISCHENDÜFTE.</h2>
<p data-reveal="fade">
Nischen-Parfums sind keine Impulskäufe. Sie brauchen Zeit, um zu Nischen-Parfums sind keine Impulskäufe. Sie brauchen Zeit, um zu
verstehen, wie sie auf deiner Haut funktionieren, wie sie sich im verstehen, wie sie auf deiner Haut funktionieren, wie sie sich im
Alltag entwickeln und ob sie wirklich zu dir passen. Alltag entwickeln und ob sie wirklich zu dir passen.
@ -185,7 +191,7 @@ function DiscoverySetPage() {
</div> </div>
<div className="discovery-comparison-grid"> <div className="discovery-comparison-grid">
<div className="discovery-comparison-card"> <div className="discovery-comparison-card" data-reveal="fade">
<div className="discovery-comparison-head"> <div className="discovery-comparison-head">
<span className="discovery-comparison-icon">×</span> <span className="discovery-comparison-icon">×</span>
<h3>Traditioneller Weg</h3> <h3>Traditioneller Weg</h3>
@ -197,7 +203,10 @@ function DiscoverySetPage() {
</p> </p>
</div> </div>
<div className="discovery-comparison-card discovery-comparison-card--highlight"> <div
className="discovery-comparison-card discovery-comparison-card--highlight"
data-reveal="fade"
>
<div className="discovery-comparison-head"> <div className="discovery-comparison-head">
<span className="discovery-comparison-icon"></span> <span className="discovery-comparison-icon"></span>
<h3>Discovery Set Weg</h3> <h3>Discovery Set Weg</h3>
@ -210,7 +219,7 @@ function DiscoverySetPage() {
</div> </div>
</div> </div>
<div className="discovery-bottom-cta"> <div className="discovery-bottom-cta" data-reveal="fade">
<button type="button" className="discovery-primary-btn" onClick={buyDiscoverySet}> <button type="button" className="discovery-primary-btn" onClick={buyDiscoverySet}>
DISCOVERY SET BESTELLEN CHF 48. DISCOVERY SET BESTELLEN CHF 48.
</button> </button>

View File

@ -7,18 +7,20 @@ function ImpressumPage() {
<SharedNavbar variant="light" /> <SharedNavbar variant="light" />
<main className="impressum-shell"> <main className="impressum-shell">
<section className="impressum-hero"> <section className="impressum-hero" data-reveal-group>
<span className="impressum-kicker">RECHTLICHE ANGABEN</span> <span className="impressum-kicker" data-reveal="fade">
<h1>IMPRESSUM</h1> RECHTLICHE ANGABEN
<p className="impressum-intro"> </span>
<h1 data-reveal="lines">IMPRESSUM</h1>
<p className="impressum-intro" data-reveal="fade">
Dieses Impressum enthält die rechtlichen Angaben zu atmos sowie Dieses Impressum enthält die rechtlichen Angaben zu atmos sowie
Informationen zur Verantwortlichkeit, Erreichbarkeit und zu den Informationen zur Verantwortlichkeit, Erreichbarkeit und zu den
veröffentlichten Inhalten dieser Website. veröffentlichten Inhalten dieser Website.
</p> </p>
</section> </section>
<section className="impressum-grid"> <section className="impressum-grid" data-reveal-group data-reveal-start="top 90%">
<article className="impressum-card"> <article className="impressum-card" data-reveal="fade">
<span className="impressum-label">ANBIETER</span> <span className="impressum-label">ANBIETER</span>
<h2>Unternehmen</h2> <h2>Unternehmen</h2>
<p> <p>
@ -32,7 +34,7 @@ function ImpressumPage() {
</p> </p>
</article> </article>
<article className="impressum-card"> <article className="impressum-card" data-reveal="fade">
<span className="impressum-label">KONTAKT</span> <span className="impressum-label">KONTAKT</span>
<h2>Erreichbarkeit</h2> <h2>Erreichbarkeit</h2>
<p> <p>
@ -42,7 +44,7 @@ function ImpressumPage() {
</p> </p>
</article> </article>
<article className="impressum-card"> <article className="impressum-card" data-reveal="fade">
<span className="impressum-label">VERTRETUNGSBERECHTIGT</span> <span className="impressum-label">VERTRETUNGSBERECHTIGT</span>
<h2>Geschäftsführung</h2> <h2>Geschäftsführung</h2>
<p> <p>
@ -52,7 +54,7 @@ function ImpressumPage() {
</p> </p>
</article> </article>
<article className="impressum-card"> <article className="impressum-card" data-reveal="fade">
<span className="impressum-label">HANDELSREGISTER</span> <span className="impressum-label">HANDELSREGISTER</span>
<h2>Registereintrag</h2> <h2>Registereintrag</h2>
<p> <p>
@ -63,13 +65,15 @@ function ImpressumPage() {
</article> </article>
</section> </section>
<section className="impressum-section"> <section className="impressum-section" data-reveal-group>
<div className="impressum-section-heading"> <div className="impressum-section-heading">
<span className="impressum-label">INHALTLICHE VERANTWORTUNG</span> <span className="impressum-label" data-reveal="fade">
<h2>VERANTWORTLICH FÜR DEN INHALT</h2> INHALTLICHE VERANTWORTUNG
</span>
<h2 data-reveal="lines">VERANTWORTLICH FÜR DEN INHALT</h2>
</div> </div>
<div className="impressum-section-copy"> <div className="impressum-section-copy" data-reveal="fade">
<p> <p>
Verantwortlich für diese Website und die publizierten Inhalte: Verantwortlich für diese Website und die publizierten Inhalte:
</p> </p>
@ -85,13 +89,15 @@ function ImpressumPage() {
</div> </div>
</section> </section>
<section className="impressum-section"> <section className="impressum-section" data-reveal-group>
<div className="impressum-section-heading"> <div className="impressum-section-heading">
<span className="impressum-label">HAFTUNG</span> <span className="impressum-label" data-reveal="fade">
<h2>HAFTUNGSAUSSCHLUSS</h2> HAFTUNG
</span>
<h2 data-reveal="lines">HAFTUNGSAUSSCHLUSS</h2>
</div> </div>
<div className="impressum-section-copy"> <div className="impressum-section-copy" data-reveal="fade">
<p> <p>
Trotz sorgfältiger inhaltlicher Kontrolle übernimmt atmos keine Trotz sorgfältiger inhaltlicher Kontrolle übernimmt atmos keine
Gewähr für die Aktualität, Richtigkeit, Vollständigkeit oder Gewähr für die Aktualität, Richtigkeit, Vollständigkeit oder
@ -105,13 +111,15 @@ function ImpressumPage() {
</div> </div>
</section> </section>
<section className="impressum-section"> <section className="impressum-section" data-reveal-group>
<div className="impressum-section-heading"> <div className="impressum-section-heading">
<span className="impressum-label">URHEBERRECHT</span> <span className="impressum-label" data-reveal="fade">
<h2>URHEBER- UND MARKENRECHTE</h2> URHEBERRECHT
</span>
<h2 data-reveal="lines">URHEBER- UND MARKENRECHTE</h2>
</div> </div>
<div className="impressum-section-copy"> <div className="impressum-section-copy" data-reveal="fade">
<p> <p>
Sämtliche Inhalte dieser Website, einschliesslich Texte, Bilder, Sämtliche Inhalte dieser Website, einschliesslich Texte, Bilder,
Gestaltungselemente, Logos und Marken, sind urheberrechtlich Gestaltungselemente, Logos und Marken, sind urheberrechtlich
@ -123,13 +131,15 @@ function ImpressumPage() {
</div> </div>
</section> </section>
<section className="impressum-section"> <section className="impressum-section" data-reveal-group>
<div className="impressum-section-heading"> <div className="impressum-section-heading">
<span className="impressum-label">TRANSPARENZ</span> <span className="impressum-label" data-reveal="fade">
<h2>KLARE ANGABEN UND ERREICHBARKEIT</h2> TRANSPARENZ
</span>
<h2 data-reveal="lines">KLARE ANGABEN UND ERREICHBARKEIT</h2>
</div> </div>
<div className="impressum-note-box"> <div className="impressum-note-box" data-reveal="fade">
<p> <p>
atmos legt Wert auf eine klare, transparente und nachvollziehbare atmos legt Wert auf eine klare, transparente und nachvollziehbare
Kommunikation. Dieses Impressum dient der eindeutigen Kommunikation. Dieses Impressum dient der eindeutigen

View File

@ -43,6 +43,7 @@
display: block; display: block;
object-fit: cover; object-fit: cover;
object-position: center; object-position: center;
will-change: transform;
} }
.hero .navbar--hero { .hero .navbar--hero {
@ -54,21 +55,6 @@
padding-top: 0; padding-top: 0;
} }
.hero-brand {
position: absolute;
top: 22px;
left: clamp(1rem, 1.45vw, 20px);
z-index: 14;
display: inline-flex;
align-items: center;
}
.hero-brand__logo {
display: block;
width: clamp(74px, 8vw, 112px);
height: auto;
}
.hero-content { .hero-content {
position: relative; position: relative;
z-index: 6; z-index: 6;
@ -92,7 +78,13 @@
.hero-title-line { .hero-title-line {
display: block; display: block;
will-change: transform, opacity; overflow: hidden;
padding-bottom: 0.08em;
margin-bottom: -0.08em;
}
.hero-title-line .reveal-line {
will-change: transform;
} }
.hero-title-line + .hero-title-line { .hero-title-line + .hero-title-line {
@ -446,6 +438,7 @@
height: 100%; height: 100%;
object-fit: cover; object-fit: cover;
display: block; display: block;
will-change: transform;
} }
/* RESPONSIVE */ /* RESPONSIVE */
@ -475,10 +468,6 @@
} }
@media (max-width: 640px) { @media (max-width: 640px) {
.hero-brand {
top: 14px;
}
.hero .navbar--hero { .hero .navbar--hero {
top: 14px; top: 14px;
} }

View File

@ -7,6 +7,7 @@ import {
} from "react"; } from "react";
import { Link } from "react-router"; import { Link } from "react-router";
import { gsap } from "gsap"; import { gsap } from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";
import HeroSection from "../components/landing/HeroSection"; import HeroSection from "../components/landing/HeroSection";
import perfumes from "../data/perfumes"; import perfumes from "../data/perfumes";
import "../pages/LandingPage.css"; import "../pages/LandingPage.css";
@ -14,11 +15,15 @@ import "../style/navbar.css";
const INTRO_SESSION_KEY = "atmos-landing-intro-played"; const INTRO_SESSION_KEY = "atmos-landing-intro-played";
gsap.registerPlugin(ScrollTrigger);
function LandingPage() { function LandingPage() {
const pageRef = useRef(null); const pageRef = useRef(null);
const overlayRef = useRef(null); const overlayRef = useRef(null);
const overlayTextRef = useRef(null); const overlayTextRef = useRef(null);
const heroImageWrapRef = useRef(null); const heroImageWrapRef = useRef(null);
const heroImageRef = useRef(null);
const discoveryImageRef = useRef(null);
const headlineLineRefs = useRef([]); const headlineLineRefs = useRef([]);
const heroMetaRefs = useRef([]); const heroMetaRefs = useRef([]);
const cardRefs = useRef([]); const cardRefs = useRef([]);
@ -62,9 +67,8 @@ function LandingPage() {
const heroImageWrap = heroImageWrapRef.current; const heroImageWrap = heroImageWrapRef.current;
const headlineLines = headlineLineRefs.current.filter(Boolean); const headlineLines = headlineLineRefs.current.filter(Boolean);
const heroMeta = heroMetaRefs.current.filter(Boolean); const heroMeta = heroMetaRefs.current.filter(Boolean);
const revealTargets = [...headlineLines, ...heroMeta];
if (!overlay || !overlayText || !heroImageWrap || revealTargets.length === 0) { if (!overlay || !overlayText || !heroImageWrap || headlineLines.length === 0) {
return undefined; return undefined;
} }
@ -74,14 +78,22 @@ function LandingPage() {
transformOrigin: "center center", transformOrigin: "center center",
}); });
gsap.set(revealTargets, { gsap.set(headlineLines, {
y: 56, yPercent: 115,
rotate: 2.2,
transformOrigin: "0% 100%",
force3D: true,
});
gsap.set(heroMeta, {
y: 36,
autoAlpha: 0, autoAlpha: 0,
}); });
if (!shouldPlayIntro) { if (!shouldPlayIntro) {
gsap.set(heroImageWrap, { scale: 1 }); gsap.set(heroImageWrap, { scale: 1 });
gsap.set(revealTargets, { y: 0, autoAlpha: 1 }); gsap.set(headlineLines, { yPercent: 0, rotate: 0 });
gsap.set(heroMeta, { y: 0, autoAlpha: 1 });
gsap.set(overlay, { gsap.set(overlay, {
yPercent: -100, yPercent: -100,
autoAlpha: 0, autoAlpha: 0,
@ -130,11 +142,11 @@ function LandingPage() {
.to( .to(
headlineLines, headlineLines,
{ {
y: 0, yPercent: 0,
autoAlpha: 1, rotate: 0,
duration: 1.04, duration: 1.18,
stagger: 0.16, stagger: 0.1,
ease: "power3.out", ease: "power4.out",
}, },
"<0.27" "<0.27"
) )
@ -143,11 +155,11 @@ function LandingPage() {
{ {
y: 0, y: 0,
autoAlpha: 1, autoAlpha: 1,
duration: 0.98, duration: 1.02,
stagger: 0.14, stagger: 0.12,
ease: "power3.out", ease: "power4.out",
}, },
"<0.2" "<0.16"
) )
.set(overlay, { display: "none" }); .set(overlay, { display: "none" });
}, pageRef); }, pageRef);
@ -157,6 +169,69 @@ function LandingPage() {
}; };
}, [shouldPlayIntro]); }, [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(() => { useEffect(() => {
const cards = cardRefs.current.filter(Boolean); const cards = cardRefs.current.filter(Boolean);
const cardStates = cards const cardStates = cards
@ -302,6 +377,7 @@ function LandingPage() {
<div className="page" ref={pageRef}> <div className="page" ref={pageRef}>
<HeroSection <HeroSection
heroImageWrapRef={heroImageWrapRef} heroImageWrapRef={heroImageWrapRef}
heroImageRef={heroImageRef}
setHeadlinePrimaryRef={setHeadlinePrimaryRef} setHeadlinePrimaryRef={setHeadlinePrimaryRef}
setHeadlineSecondaryRef={setHeadlineSecondaryRef} setHeadlineSecondaryRef={setHeadlineSecondaryRef}
setDescriptionRef={setDescriptionRef} setDescriptionRef={setDescriptionRef}
@ -311,9 +387,9 @@ function LandingPage() {
/> />
<main> <main>
<section className="section" id="dufte"> <section className="section" id="dufte" data-reveal-group>
<div className="section-heading"> <div className="section-heading">
<h2> <h2 data-reveal="lines">
{"W\u00C4HLE EINE"} {"W\u00C4HLE EINE"}
<br /> <br />
{"ATMOSPH\u00C4RE"} {"ATMOSPH\u00C4RE"}
@ -368,27 +444,37 @@ function LandingPage() {
</div> </div>
</section> </section>
<section className="discovery-section" id="testen"> <section
className="discovery-section"
id="testen"
data-reveal-group
data-reveal-start="top 82%"
>
<div className="discovery-copy"> <div className="discovery-copy">
<h2> <h2 data-reveal="lines">
DER SICHERE EINSTIEG DER SICHERE EINSTIEG
<br /> <br />
DISCOVERY SET DISCOVERY SET
</h2> </h2>
<p> <p data-reveal="fade">
{"Alle 6 D\u00FCfte als 2ml Samples."} {"Alle 6 D\u00FCfte als 2ml Samples."}
<br /> <br />
Jeden Duft eine Woche tragen. Jeden Duft eine Woche tragen.
<br /> <br />
Verstehen, was funktioniert. Verstehen, was funktioniert.
</p> </p>
<Link to="/discovery-set" className="discovery-btn"> <Link to="/discovery-set" className="discovery-btn" data-reveal="fade">
Discovery Set bestellen Discovery Set bestellen
</Link> </Link>
</div> </div>
<div className="discovery-banner"> <div className="discovery-banner">
<img src="/atmos-discovery-set-thumbnail.png" alt="Discovery Set" loading="lazy" /> <img
src="/atmos-discovery-set-thumbnail.png"
alt="Discovery Set"
loading="lazy"
ref={discoveryImageRef}
/>
</div> </div>
</section> </section>
</main> </main>

View File

@ -63,30 +63,36 @@ function SmallBatchPage() {
<SharedNavbar variant="light" /> <SharedNavbar variant="light" />
<main className="small-shell"> <main className="small-shell">
<section className="small-hero"> <section className="small-hero" data-reveal-group>
<span className="small-kicker">SMALL BATCH / ARCHIVE / PROTOTYPE</span> <span className="small-kicker" data-reveal="fade">
<h1>EARLY ACCESS</h1> SMALL BATCH / ARCHIVE / PROTOTYPE
<p> </span>
<h1 data-reveal="fade">EARLY ACCESS</h1>
<p data-reveal="fade">
Limited releases are reserved for customers with enough purchase Limited releases are reserved for customers with enough purchase
history to understand the atmos material language. history to understand the atmos material language.
</p> </p>
</section> </section>
{!user ? ( {!user ? (
<section className="small-panel"> <section className="small-panel" data-reveal-group>
<span className="small-kicker">LOGIN REQUIRED</span> <span className="small-kicker" data-reveal="fade">
<h2>Sign in to check access.</h2> LOGIN REQUIRED
<p>Small Batch access is calculated from your completed orders.</p> </span>
<h2 data-reveal="fade">Sign in to check access.</h2>
<p data-reveal="fade">Small Batch access is calculated from your completed orders.</p>
<button type="button" onClick={openProfile}> <button type="button" onClick={openProfile}>
Login / Register Login / Register
</button> </button>
</section> </section>
) : ( ) : (
<> <>
<section className="small-panel"> <section className="small-panel" data-reveal-group>
<span className="small-kicker">ACCESS STATUS</span> <span className="small-kicker" data-reveal="fade">
<h2>{loyalty.unlocked ? "Unlocked" : "Locked"}</h2> ACCESS STATUS
<div className="small-requirements"> </span>
<h2 data-reveal="fade">{loyalty.unlocked ? "Unlocked" : "Locked"}</h2>
<div className="small-requirements" data-reveal="fade">
<Requirement label="Discovery Set" met={loyalty.hasDiscoverySet} /> <Requirement label="Discovery Set" met={loyalty.hasDiscoverySet} />
<Requirement label="Full Size" met={loyalty.hasFullSize} /> <Requirement label="Full Size" met={loyalty.hasFullSize} />
<Requirement label="Purchases" met={loyalty.purchases >= 3}> <Requirement label="Purchases" met={loyalty.purchases >= 3}>
@ -102,9 +108,9 @@ function SmallBatchPage() {
{state.loading && <p className="small-error">Loading access...</p>} {state.loading && <p className="small-error">Loading access...</p>}
{loyalty.unlocked && ( {loyalty.unlocked && (
<section className="release-grid"> <section className="release-grid" data-reveal-group data-reveal-start="top 88%">
{state.releases.map((release) => ( {state.releases.map((release) => (
<article className="release-card" key={release.name}> <article className="release-card" key={release.name} data-reveal="fade">
<span>{release.type}</span> <span>{release.type}</span>
<h3>{release.name}</h3> <h3>{release.name}</h3>
<p>{release.note}</p> <p>{release.note}</p>

View File

@ -1,3 +1,4 @@
import { Link } from "react-router";
import SharedNavbar from "../components/SharedNavbar"; import SharedNavbar from "../components/SharedNavbar";
import "./SupportPage.css"; import "./SupportPage.css";
@ -7,10 +8,12 @@ function SupportPage() {
<SharedNavbar variant="light" /> <SharedNavbar variant="light" />
<main className="support-shell"> <main className="support-shell">
<section className="support-hero"> <section className="support-hero" data-reveal-group>
<div className="support-hero-copy"> <div className="support-hero-copy">
<span className="support-kicker">SUPPORT</span> <span className="support-kicker" data-reveal="fade">
<h1> SUPPORT
</span>
<h1 data-reveal="lines">
WIR HELFEN WIR HELFEN
<br /> <br />
KLAR, DIREKT KLAR, DIREKT
@ -19,7 +22,7 @@ function SupportPage() {
<br /> <br />
UMWEGE UMWEGE
</h1> </h1>
<p className="support-intro"> <p className="support-intro" data-reveal="fade">
Wenn du Fragen zu Bestellung, Versand, Discovery Set, Produkten oder Wenn du Fragen zu Bestellung, Versand, Discovery Set, Produkten oder
deiner Auswahl hast, ist das Support-Team von atmos für dich da. deiner Auswahl hast, ist das Support-Team von atmos für dich da.
Präzise, persönlich und mit dem Anspruch, jedes Anliegen sorgfältig Präzise, persönlich und mit dem Anspruch, jedes Anliegen sorgfältig
@ -27,7 +30,7 @@ function SupportPage() {
</p> </p>
</div> </div>
<div className="support-hero-panel"> <div className="support-hero-panel" data-reveal="fade">
<span className="support-panel-label">KONTAKT</span> <span className="support-panel-label">KONTAKT</span>
<p> <p>
Für Anliegen rund um Bestellung, Produkte, Versand und allgemeine Für Anliegen rund um Bestellung, Produkte, Versand und allgemeine
@ -51,8 +54,8 @@ function SupportPage() {
</div> </div>
</section> </section>
<section className="support-quick-grid"> <section className="support-quick-grid" data-reveal-group data-reveal-start="top 90%">
<article className="support-quick-card"> <article className="support-quick-card" data-reveal="fade">
<span className="support-label">BESTELLUNG</span> <span className="support-label">BESTELLUNG</span>
<h3>Fragen zu einer laufenden Bestellung</h3> <h3>Fragen zu einer laufenden Bestellung</h3>
<p> <p>
@ -61,7 +64,7 @@ function SupportPage() {
</p> </p>
</article> </article>
<article className="support-quick-card"> <article className="support-quick-card" data-reveal="fade">
<span className="support-label">VERSAND</span> <span className="support-label">VERSAND</span>
<h3>Lieferung und Zustellung</h3> <h3>Lieferung und Zustellung</h3>
<p> <p>
@ -70,7 +73,7 @@ function SupportPage() {
</p> </p>
</article> </article>
<article className="support-quick-card"> <article className="support-quick-card" data-reveal="fade">
<span className="support-label">PRODUKTE</span> <span className="support-label">PRODUKTE</span>
<h3>Beratung zu Duft, Sample oder Discovery Set</h3> <h3>Beratung zu Duft, Sample oder Discovery Set</h3>
<p> <p>
@ -80,13 +83,15 @@ function SupportPage() {
</article> </article>
</section> </section>
<section className="support-section support-section--split"> <section className="support-section support-section--split" data-reveal-group>
<div className="support-section-heading"> <div className="support-section-heading">
<span className="support-label">SO ERREICHST DU UNS</span> <span className="support-label" data-reveal="fade">
<h2>EIN GUTER SUPPORT BEGINNT MIT EINER KLAREN ANFRAGE.</h2> SO ERREICHST DU UNS
</span>
<h2 data-reveal="lines">EIN GUTER SUPPORT BEGINNT MIT EINER KLAREN ANFRAGE.</h2>
</div> </div>
<div className="support-section-copy"> <div className="support-section-copy" data-reveal="fade">
<p> <p>
Damit wir dein Anliegen möglichst schnell bearbeiten können, hilft es, Damit wir dein Anliegen möglichst schnell bearbeiten können, hilft es,
wenn du uns in deiner Nachricht die wichtigsten Informationen direkt wenn du uns in deiner Nachricht die wichtigsten Informationen direkt
@ -102,8 +107,8 @@ function SupportPage() {
</div> </div>
</section> </section>
<section className="support-info-grid"> <section className="support-info-grid" data-reveal-group>
<div className="support-info-box"> <div className="support-info-box" data-reveal="fade">
<span className="support-label">AM BESTEN MITGEBEN</span> <span className="support-label">AM BESTEN MITGEBEN</span>
<ul className="support-list"> <ul className="support-list">
<li>Bestellnummer, falls bereits bestellt wurde</li> <li>Bestellnummer, falls bereits bestellt wurde</li>
@ -113,7 +118,7 @@ function SupportPage() {
</ul> </ul>
</div> </div>
<div className="support-info-box support-info-box--dark"> <div className="support-info-box support-info-box--dark" data-reveal="fade">
<span className="support-label">KONTAKT</span> <span className="support-label">KONTAKT</span>
<h3>support@atmos.ch</h3> <h3>support@atmos.ch</h3>
<p> <p>
@ -126,14 +131,16 @@ function SupportPage() {
</div> </div>
</section> </section>
<section className="support-faq-section"> <section className="support-faq-section" data-reveal-group>
<div className="support-section-heading"> <div className="support-section-heading">
<span className="support-label">HÄUFIGE FRAGEN</span> <span className="support-label" data-reveal="fade">
<h2>DIE WICHTIGSTEN THEMEN AUF EINEN BLICK.</h2> HÄUFIGE FRAGEN
</span>
<h2 data-reveal="lines">DIE WICHTIGSTEN THEMEN AUF EINEN BLICK.</h2>
</div> </div>
<div className="support-faq-grid"> <div className="support-faq-grid">
<article className="support-faq-card"> <article className="support-faq-card" data-reveal="fade">
<h3>Wie lange dauert der Versand?</h3> <h3>Wie lange dauert der Versand?</h3>
<p> <p>
Bestellungen werden in der Regel innerhalb von 12 Werktagen Bestellungen werden in der Regel innerhalb von 12 Werktagen
@ -142,7 +149,7 @@ function SupportPage() {
</p> </p>
</article> </article>
<article className="support-faq-card"> <article className="support-faq-card" data-reveal="fade">
<h3>Kann ich zuerst testen?</h3> <h3>Kann ich zuerst testen?</h3>
<p> <p>
Ja. Dafür ist das Discovery Set oder ein einzelnes Sample gedacht. Ja. Dafür ist das Discovery Set oder ein einzelnes Sample gedacht.
@ -151,7 +158,7 @@ function SupportPage() {
</p> </p>
</article> </article>
<article className="support-faq-card"> <article className="support-faq-card" data-reveal="fade">
<h3>Ich bin unsicher, welcher Duft zu mir passt.</h3> <h3>Ich bin unsicher, welcher Duft zu mir passt.</h3>
<p> <p>
Schreib uns kurz, welche Duftcharaktere, Materialien oder Stimmungen Schreib uns kurz, welche Duftcharaktere, Materialien oder Stimmungen
@ -159,7 +166,7 @@ function SupportPage() {
</p> </p>
</article> </article>
<article className="support-faq-card"> <article className="support-faq-card" data-reveal="fade">
<h3>Was tun bei einem Problem mit der Bestellung?</h3> <h3>Was tun bei einem Problem mit der Bestellung?</h3>
<p> <p>
Kontaktiere uns direkt mit deiner Bestellnummer und einer kurzen Kontaktiere uns direkt mit deiner Bestellnummer und einer kurzen
@ -170,17 +177,21 @@ function SupportPage() {
</div> </div>
</section> </section>
<section className="support-bottom-cta"> <section className="support-bottom-cta" data-reveal-group>
<div className="support-bottom-copy"> <div className="support-bottom-copy">
<span className="support-label">atmos SUPPORT</span> <span className="support-label" data-reveal="fade">
<h2>NICHT AUTOMATISIERT AUF DISTANZ. SONDERN PERSÖNLICH UND PRÄZISE.</h2> atmos SUPPORT
<p> </span>
<h2 data-reveal="lines">
NICHT AUTOMATISIERT AUF DISTANZ. SONDERN PERSÖNLICH UND PRÄZISE.
</h2>
<p data-reveal="fade">
Wir möchten, dass sich auch der Service so anfühlt wie die Marke Wir möchten, dass sich auch der Service so anfühlt wie die Marke
selbst: klar, hochwertig und sorgfältig. selbst: klar, hochwertig und sorgfältig.
</p> </p>
</div> </div>
<div className="support-bottom-actions"> <div className="support-bottom-actions" data-reveal="fade">
<a href="mailto:support@atmos.ch" className="support-btn support-btn--primary"> <a href="mailto:support@atmos.ch" className="support-btn support-btn--primary">
E-Mail senden E-Mail senden
</a> </a>

View File

@ -27,6 +27,16 @@
transition: 0.2s ease; transition: 0.2s ease;
} }
.nav-link--brand {
padding: 8px 12px;
}
.nav-brand-logo {
display: block;
width: clamp(56px, 5.4vw, 78px);
height: auto;
}
.nav-button { .nav-button {
border: none; border: none;
font-family: inherit; font-family: inherit;
@ -90,4 +100,12 @@
padding: 8px 10px; padding: 8px 10px;
font-size: 12px; font-size: 12px;
} }
.nav-link--brand {
padding: 8px 10px;
}
.nav-brand-logo {
width: 54px;
}
} }