Add shop, cart, versand pages and Impulse & Tipps detail page
- New /shop overview, /cart (real cart state via useSyncExternalStore), /versand (shipping policy page with TOC) and /newsletter detail page - Cart: quantity/removal, order summary, related-products cross-sell with randomized picks, VersandModal quick-reference instead of navigating away, MwSt. disclosure next to unit prices - Add-to-cart UX: inline success feedback (green state) plus a fly-to-navbar-cart-icon animation (CartFlyProvider) with a delayed badge count-up; AddToCartButton no longer navigates straight to /cart - Shared lib/products.ts catalog and lib/shipping.ts constants (cost, free-shipping threshold, handling/transit days) so cart, trust badges and the versand page can never drift apart - Fix Navbar smooth-scroll easing (ease-out instead of ease-in-out, no more perceived start delay); compress newsletter modal photo 2.4MB -> 85KB to fix first-open jank
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
import { Fragment } from "react";
|
||||
import { Reveal, RevealGroup, RevealItem } from "../../components/Reveal";
|
||||
|
||||
const steps = [
|
||||
{
|
||||
icon: "/icon-weekly-step-1.png",
|
||||
title: "1. Anmelden",
|
||||
desc: "Trage dich mit deiner E-Mail-Adresse ein und bestätige kurz.",
|
||||
},
|
||||
{
|
||||
icon: "/icon-weekly-step-2.png",
|
||||
title: "2. E-Mail erhalten",
|
||||
desc: "Jeden Mittwoch bekommst du deinen neuen Impuls direkt in dein Postfach.",
|
||||
},
|
||||
{
|
||||
icon: "/icon-weekly-step-3.png",
|
||||
title: "3. Lesen & umsetzen",
|
||||
desc: "Lies in wenigen Minuten und setze die Tipps direkt in deinem Alltag um.",
|
||||
},
|
||||
{
|
||||
icon: "/icon-weekly-step-4.png",
|
||||
title: "4. Dranbleiben",
|
||||
desc: "Kleine Schritte, jede Woche – für mehr Klarheit, Fokus und Leichtigkeit.",
|
||||
},
|
||||
];
|
||||
|
||||
export function HowItWorks() {
|
||||
return (
|
||||
<section className="w-full bg-bg-base flex flex-col gap-12 items-center py-12 md:py-16 px-[var(--layout-padding-x)]">
|
||||
<Reveal className="flex flex-col gap-2 items-center text-center">
|
||||
<p
|
||||
className="font-semibold text-h-emphasis text-text-primary"
|
||||
style={{ fontFamily: "var(--font-lora)" }}
|
||||
>
|
||||
So funktioniert's
|
||||
</p>
|
||||
<p className="text-body text-text-muted">
|
||||
Wöchentliche Inspiration. Kein Aufwand für dich.
|
||||
</p>
|
||||
</Reveal>
|
||||
|
||||
{/* Same pattern as /todo-cards's HowItWorks: arrows always visible
|
||||
(rotated to point down while stacked below md:), RevealGroup/
|
||||
RevealItem stagger the steps in. lg:px-[10rem] mirrors Figma's
|
||||
Desktop-only px-[160px] inset (no fluid token — pure aesthetic
|
||||
narrowing, fine to just drop below lg:). */}
|
||||
<RevealGroup className="flex flex-col md:flex-row gap-8 items-center md:items-start w-full lg:px-[10rem]">
|
||||
{steps.map((step, i) => (
|
||||
<Fragment key={step.title}>
|
||||
<RevealItem className="group flex flex-col gap-4 items-center text-center flex-1 max-w-xs md:max-w-none">
|
||||
<img
|
||||
src={step.icon}
|
||||
alt=""
|
||||
className="h-16 w-auto object-contain transition-transform duration-300 group-hover:scale-110"
|
||||
/>
|
||||
<p className="font-semibold text-body text-text-primary">{step.title}</p>
|
||||
<p className="text-body-sm text-text-primary text-center">{step.desc}</p>
|
||||
</RevealItem>
|
||||
{i < steps.length - 1 && (
|
||||
<div className="flex items-center justify-center shrink-0">
|
||||
<img
|
||||
alt=""
|
||||
src="/icon-arrow-connector.svg"
|
||||
className="w-6 h-6 rotate-90 md:w-10 md:h-3 md:rotate-0"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</Fragment>
|
||||
))}
|
||||
</RevealGroup>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
import Image from "next/image";
|
||||
import { Reveal, RevealGroup, RevealItem } from "../../components/Reveal";
|
||||
|
||||
const testimonials = [
|
||||
{
|
||||
quote:
|
||||
"„Die wöchentlichen Impulse sind mein fester Start in die Woche. Kurz, hilfreich und immer genau richtig.“",
|
||||
name: "Sarah M.",
|
||||
role: "Marketing Managerin",
|
||||
avatar: "/avatar-weekly-1.png",
|
||||
},
|
||||
{
|
||||
quote: "„Endlich mal Tipps, die man wirklich umsetzen kann. Jeden Mittwoch freue ich mich auf die Mail.“",
|
||||
name: "Thomas K.",
|
||||
role: "Selbstständiger Berater",
|
||||
avatar: "/avatar-weekly-2.png",
|
||||
},
|
||||
{
|
||||
quote: "„Die Impulse helfen mir, den Fokus zu behalten und das Wesentliche nicht aus den Augen zu verlieren.“",
|
||||
name: "Miriam L.",
|
||||
role: "Projektleiterin",
|
||||
avatar: "/avatar-weekly-3.png",
|
||||
},
|
||||
];
|
||||
|
||||
// Same card style as /challenge and /todo-cards's testimonials (bg-muted,
|
||||
// no border, decorative quote-mark, quote on top with flex-1,
|
||||
// avatar+name+role row pinned to the bottom, hover-lift + avatar scale) —
|
||||
// this page's own Figma spec actually used a different bordered pattern,
|
||||
// but site-wide consistency across all three testimonial sections was
|
||||
// explicitly requested over per-page Figma fidelity here. No pagination
|
||||
// dots either, for the same reason — neither of the other two pages has
|
||||
// them. max-w-[1600px] matches Challenge's testimonial container cap —
|
||||
// see the identical comment in /todo-cards's Testimonials.tsx.
|
||||
export function Testimonials() {
|
||||
return (
|
||||
<section className="w-full bg-bg-base flex flex-col gap-8 items-center py-12 md:py-16 px-[var(--layout-padding-x)]">
|
||||
<div className="max-w-[1600px] mx-auto w-full flex flex-col gap-8 items-center">
|
||||
<Reveal
|
||||
className="font-semibold text-h-emphasis text-text-primary text-center"
|
||||
style={{ fontFamily: "var(--font-lora)" }}
|
||||
>
|
||||
Was andere sagen
|
||||
</Reveal>
|
||||
|
||||
<RevealGroup className="grid grid-cols-1 md:grid-cols-12 gap-6 md:gap-[var(--layout-grid-gap)] w-full">
|
||||
{testimonials.map((t) => (
|
||||
<RevealItem
|
||||
key={t.name}
|
||||
className="group relative md:col-span-4 bg-bg-muted rounded-xl p-6 flex flex-col gap-4 transition-transform duration-300 hover:-translate-y-1"
|
||||
>
|
||||
<span
|
||||
aria-hidden
|
||||
className="absolute top-4 right-6 font-bold text-[2.5rem] text-[#ccc] leading-none select-none"
|
||||
>
|
||||
”
|
||||
</span>
|
||||
<p className="flex-1 text-body-sm text-text-primary leading-[1.6] pr-8">{t.quote}</p>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="relative size-10 shrink-0 rounded-full overflow-hidden transition-transform duration-300 group-hover:scale-110">
|
||||
<Image src={t.avatar} alt={t.name} fill sizes="40px" className="object-cover" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="font-semibold text-body-sm text-text-primary">{t.name}</p>
|
||||
<p className="text-body-sm text-text-muted">{t.role}</p>
|
||||
</div>
|
||||
</div>
|
||||
</RevealItem>
|
||||
))}
|
||||
</RevealGroup>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import Image from "next/image";
|
||||
import { Reveal } from "../../components/Reveal";
|
||||
|
||||
const benefits = [
|
||||
{ title: "Praktische Impulse", desc: "Konkrete Ideen, die du sofort umsetzen kannst." },
|
||||
{ title: "Neue Perspektiven", desc: "Gedankenanstöße, die dir helfen, klarer zu sehen." },
|
||||
{ title: "Bewährte Methoden", desc: "Einfache Strategien für mehr Fokus und Struktur." },
|
||||
{ title: "Motivation & Erinnerung", desc: "Ein freundlicher Schub in die richtige Richtung." },
|
||||
];
|
||||
|
||||
export function WeeklyBenefits() {
|
||||
return (
|
||||
<section className="w-full bg-bg-base flex flex-col lg:flex-row gap-10 lg:gap-16 items-center py-12 md:py-16 px-[var(--layout-padding-x)]">
|
||||
<Reveal className="group relative w-full lg:w-[42%] lg:shrink-0 aspect-[580/328] rounded-md overflow-hidden">
|
||||
<Image
|
||||
src="/weekly-photo.png"
|
||||
alt="Notizbuch mit wöchentlichem Impuls"
|
||||
fill
|
||||
sizes="(min-width: 1024px) 42vw, 100vw"
|
||||
className="object-cover transition-transform duration-500 group-hover:scale-105"
|
||||
/>
|
||||
</Reveal>
|
||||
|
||||
<Reveal delay={0.1} className="flex flex-col gap-6 items-start flex-1 min-w-0 w-full">
|
||||
<p
|
||||
className="font-semibold text-h-emphasis text-text-primary w-full"
|
||||
style={{ fontFamily: "var(--font-lora)" }}
|
||||
>
|
||||
Das bekommst du jede Woche
|
||||
</p>
|
||||
<ul className="flex flex-col gap-4 items-start w-full">
|
||||
{benefits.map((b) => (
|
||||
<li key={b.title} className="flex gap-[0.625rem] items-start w-full">
|
||||
<img alt="" src="/icon-check.svg" className="size-5 shrink-0 mt-0.5" />
|
||||
<div className="flex flex-col gap-0.5 items-start flex-1 min-w-0">
|
||||
<p className="font-semibold text-body text-text-primary">{b.title}</p>
|
||||
<p className="text-body-sm text-text-muted">{b.desc}</p>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</Reveal>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
import Link from "next/link";
|
||||
import Image from "next/image";
|
||||
import { Reveal } from "../../components/Reveal";
|
||||
|
||||
const checklist = [
|
||||
"Jeden Mittwoch neue Impulse & Tipps",
|
||||
"Kurz & knackig – in 5 Minuten gelesen",
|
||||
"Sofort umsetzbar für mehr Fokus und Struktur",
|
||||
"Kostenlos und jederzeit abbestellbar",
|
||||
];
|
||||
|
||||
export function WeeklyImpulsesHero() {
|
||||
return (
|
||||
<section className="bg-bg-base w-full overflow-hidden">
|
||||
{/* Same lg:-only structural exception as Home/todo-cards Hero (see
|
||||
figma-to-nextjs skill Gotcha #5) — a wide fixed-ratio photo next
|
||||
to a text column gets too cramped at Tablet widths under the
|
||||
site-wide md: convention. lg:items-center removed — see the
|
||||
breadcrumb/centering comment below, same fix as /todo-cards. */}
|
||||
{/* pt-10 md:pt-12, no lg override — same fix and same reasoning as
|
||||
/todo-cards's Hero: this is now the only thing positioning the
|
||||
breadcrumb, and it must match /todo-cards's and /challenge's
|
||||
value exactly for the breadcrumb to land at the same Y everywhere. */}
|
||||
<div className="flex flex-col lg:grid lg:grid-cols-12 gap-8 lg:gap-[var(--layout-grid-gap)] pt-10 md:pt-12">
|
||||
<Reveal className="order-1 lg:order-none lg:col-span-5 flex flex-col gap-6 items-start pl-[var(--layout-padding-x)] pr-10 lg:pr-0">
|
||||
{/* Breadcrumb — 3 levels here (Startseite › Werkzeuge › Impulse &
|
||||
Tipps), matching this page's actual Figma breadcrumb.
|
||||
Deliberately NOT part of the centered block below — see its
|
||||
comment. */}
|
||||
<p className="flex items-center gap-2 text-body-sm text-text-muted">
|
||||
<Link href="/" className="hover:text-brand transition-colors">
|
||||
Startseite
|
||||
</Link>
|
||||
<span>›</span>
|
||||
<Link href="/#werkzeuge" className="hover:text-brand transition-colors">
|
||||
Werkzeuge
|
||||
</Link>
|
||||
<span>›</span>
|
||||
<span className="text-text-primary">Impulse & Tipps</span>
|
||||
</p>
|
||||
|
||||
{/* Everything else — centered in the remaining vertical space at
|
||||
lg+, same fix as /todo-cards's Hero: keeps the breadcrumb
|
||||
above at a fixed, cross-page-consistent Y position instead of
|
||||
its offset drifting with this page's larger content (pill
|
||||
badge + 4 checklist items vs. /todo-cards's 3, no badge). */}
|
||||
<div className="flex flex-col gap-6 items-start w-full flex-1 lg:justify-center">
|
||||
{/* Pill badge */}
|
||||
<div className="flex gap-2 items-center border border-border rounded-full pl-3.5 pr-4 py-2">
|
||||
<img alt="" src="/icon-envelope-small.svg" className="w-4 h-3 shrink-0" />
|
||||
<span className="text-body-sm font-semibold text-text-primary whitespace-nowrap">
|
||||
Jeden Mittwoch in deinem Postfach
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Heading — 48px Desktop, same new --text-h-page token as
|
||||
/todo-cards's hero heading (see globals.css). */}
|
||||
<p
|
||||
className="font-semibold text-h-page text-text-primary leading-[1.1]"
|
||||
style={{ fontFamily: "var(--font-playfair)" }}
|
||||
>
|
||||
Impulse & Tipps.
|
||||
<br />
|
||||
Wöchentliche Klarheit
|
||||
<br />
|
||||
für deinen Alltag.
|
||||
</p>
|
||||
|
||||
<p className="text-body text-text-body">
|
||||
Einmal pro Woche bekommst du konkrete Impulse, die dich wirklich weiterbringen – kurz, praktisch und direkt umsetzbar.
|
||||
</p>
|
||||
|
||||
<ul className="flex flex-col gap-3 items-start w-full">
|
||||
{checklist.map((item) => (
|
||||
<li key={item} className="flex gap-[0.625rem] items-center w-full">
|
||||
<img alt="" src="/icon-check.svg" className="size-5 shrink-0" />
|
||||
<span className="flex-1 text-body text-text-primary">{item}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
{/* Inline email capture — page-specific, simpler than the shared
|
||||
Newsletter component's panel form (no button-adjacent styling
|
||||
needed here, just input + submit inline). */}
|
||||
<div className="flex gap-3 items-start w-full sm:w-auto">
|
||||
<input
|
||||
type="email"
|
||||
placeholder="Deine E-Mail-Adresse"
|
||||
className="w-full sm:w-[17.5rem] bg-bg-base border border-border rounded-sm px-4 py-[0.8125rem] text-body-sm text-text-muted font-normal outline-none focus:border-brand transition-colors"
|
||||
/>
|
||||
<button
|
||||
type="submit"
|
||||
className="shrink-0 bg-brand rounded-sm px-6 py-[0.8125rem] font-bold text-body text-text-primary whitespace-nowrap hover:bg-brand-hover active:scale-[0.97] transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand focus-visible:ring-offset-2 focus-visible:ring-offset-bg-base"
|
||||
>
|
||||
Jetzt anmelden
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-[0.375rem] items-center">
|
||||
<img alt="" src="/icon-lock.svg" className="size-3.5" />
|
||||
<span className="text-label text-text-muted">Keine Werbung. Jederzeit abbestellbar.</span>
|
||||
</div>
|
||||
</div>
|
||||
</Reveal>
|
||||
|
||||
<Reveal
|
||||
className="order-2 lg:order-none lg:col-span-7 group relative w-full aspect-[830/611] rounded-md overflow-hidden"
|
||||
delay={0.15}
|
||||
>
|
||||
<Image
|
||||
src="/hero-weekly-impulses.png"
|
||||
alt="Notizbuch mit wöchentlichem Impuls, Kaffee und Stift"
|
||||
fill
|
||||
priority
|
||||
sizes="(min-width: 1024px) 58vw, 100vw"
|
||||
className="object-cover transition-transform duration-500 group-hover:scale-105"
|
||||
/>
|
||||
</Reveal>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
import type { Metadata } from "next";
|
||||
import { WeeklyImpulsesHero } from "./components/WeeklyImpulsesHero";
|
||||
import { HowItWorks } from "./components/HowItWorks";
|
||||
import { WeeklyBenefits } from "./components/WeeklyBenefits";
|
||||
import { Testimonials } from "./components/Testimonials";
|
||||
import { Newsletter } from "../components/Newsletter";
|
||||
import { Footer } from "../components/Footer";
|
||||
|
||||
const title = "Impulse & Tipps – Wöchentliche Klarheit für deinen Alltag";
|
||||
const description =
|
||||
"Einmal pro Woche bekommst du konkrete Impulse, die dich wirklich weiterbringen – kurz, praktisch und direkt umsetzbar. Kostenlos, jeden Mittwoch, jederzeit abbestellbar.";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title,
|
||||
description,
|
||||
alternates: {
|
||||
canonical: "/newsletter",
|
||||
},
|
||||
openGraph: {
|
||||
title,
|
||||
description,
|
||||
url: "/newsletter",
|
||||
images: ["/hero-weekly-impulses.png"],
|
||||
},
|
||||
twitter: {
|
||||
title,
|
||||
description,
|
||||
images: ["/hero-weekly-impulses.png"],
|
||||
},
|
||||
};
|
||||
|
||||
// Route is "/newsletter" — not "/impulse-tipps" or similar — because that
|
||||
// path already existed as a dangling link from two places (Navbar's
|
||||
// "Newsletter" CTA button and Tools.tsx's "Impulse & Tipps" → "Anmelden"
|
||||
// card). Both point at the same underlying thing (signing up for the
|
||||
// weekly newsletter), so this page resolves both at once instead of
|
||||
// picking a new URL and leaving one of them still dangling.
|
||||
export default function NewsletterPage() {
|
||||
return (
|
||||
<>
|
||||
<main className="flex flex-col flex-1">
|
||||
<WeeklyImpulsesHero />
|
||||
<HowItWorks />
|
||||
<WeeklyBenefits />
|
||||
<Testimonials />
|
||||
<Newsletter
|
||||
title="Wöchentliche Impulse für mehr Klarheit"
|
||||
description="Melde dich jetzt an und erhalte jeden Mittwoch neue Impulse & Tipps direkt in dein Postfach."
|
||||
/>
|
||||
</main>
|
||||
<Footer />
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user