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

View File

@ -247,11 +247,13 @@ function ProductDetailContent({ perfumeSlug }) {
</div>
<div className="detail-info">
<div className="detail-heading">
<div className="detail-heading" data-reveal-group>
<div className="detail-heading-copy">
<span className="detail-kicker">Edition 04</span>
<h1>{perfume.name}</h1>
<p>{perfume.shortText}</p>
<span className="detail-kicker" data-reveal="fade">
Edition 04
</span>
<h1 data-reveal="fade">{perfume.name}</h1>
<p data-reveal="fade">{perfume.shortText}</p>
</div>
</div>
@ -282,8 +284,8 @@ function ProductDetailContent({ perfumeSlug }) {
</div>
</div>
<div className="discovery-note">
<div className="discovery-note-text">
<div className="discovery-note" data-reveal-group data-reveal-start="top 88%">
<div className="discovery-note-text" data-reveal="fade">
<strong>Discovery Set wird einmalig angerechnet</strong>
<p>
Nur das erste Discovery Set erzeugt CHF 48 Guthaben. Es wird
@ -297,7 +299,7 @@ function ProductDetailContent({ perfumeSlug }) {
)}
</div>
<Link to="/discovery-set" className="discovery-note-btn">
<Link to="/discovery-set" className="discovery-note-btn" data-reveal="fade">
Zum Set
</Link>
</div>
@ -468,15 +470,15 @@ function ProductDetailContent({ perfumeSlug }) {
</div>
</section>
<section className="detail-bottom-cta">
<h2>Lieber erst testen?</h2>
<p>
<section className="detail-bottom-cta" data-reveal-group>
<h2 data-reveal="fade">Lieber erst testen?</h2>
<p data-reveal="fade">
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.
</p>
<div className="detail-bottom-actions">
<div className="detail-bottom-actions" data-reveal="fade">
<button
type="button"
onClick={() =>

View File

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

View File

@ -4,6 +4,7 @@ import SharedNavbar from "../SharedNavbar";
function HeroSection({
heroImageWrapRef,
heroImageRef,
setHeadlinePrimaryRef,
setHeadlineSecondaryRef,
setDescriptionRef,
@ -18,31 +19,26 @@ function HeroSection({
src="/atmos-hero-image.png"
alt="Atmos Hero"
className="hero-media__image"
ref={heroImageRef}
loading="eager"
fetchPriority="high"
/>
</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" />
<div className="hero-content">
<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"}
</span>
<span className="hero-title-line" ref={setHeadlineSecondaryRef}>
</span>
<span className="hero-title-line">
<span className="reveal-line" ref={setHeadlineSecondaryRef}>
AUSDRUCK
</span>
</span>
</h1>
<p className="hero-text" ref={setDescriptionRef}>

View File

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

View File

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

View File

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

View File

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

View File

@ -43,6 +43,7 @@
display: block;
object-fit: cover;
object-position: center;
will-change: transform;
}
.hero .navbar--hero {
@ -54,21 +55,6 @@
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 {
position: relative;
z-index: 6;
@ -92,7 +78,13 @@
.hero-title-line {
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 {
@ -446,6 +438,7 @@
height: 100%;
object-fit: cover;
display: block;
will-change: transform;
}
/* RESPONSIVE */
@ -475,10 +468,6 @@
}
@media (max-width: 640px) {
.hero-brand {
top: 14px;
}
.hero .navbar--hero {
top: 14px;
}

View File

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

View File

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

View File

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

View File

@ -27,6 +27,16 @@
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 {
border: none;
font-family: inherit;
@ -90,4 +100,12 @@
padding: 8px 10px;
font-size: 12px;
}
.nav-link--brand {
padding: 8px 10px;
}
.nav-brand-logo {
width: 54px;
}
}