Files
einfach-produktiv/app/challenge/page.tsx
T
Marco 4f2f137b27 Fix RSC build break: keep next/headers out of lib/payload.ts
payload.ts's mapping functions/types are also imported by "use client"
components (LiveTestimonialsGrid, LivePostContent) — importing
next/headers anywhere in that module made it unbundlable for the client,
breaking the production build. draftMode() is now only ever called in the
Server Component pages themselves; they pass the resulting boolean into
getPostBySlug/getLegalPage/getTestimonials as a plain `draft` option.
2026-07-21 19:21:10 +00:00

418 lines
18 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type { Metadata } from "next";
import Link from "next/link";
import Image from "next/image";
import { draftMode } from "next/headers";
import { Footer } from "../components/Footer";
import { Reveal, RevealGroup, RevealItem } from "../components/Reveal";
import { TestimonialsGrid } from "../components/TestimonialsGrid";
import { LiveTestimonialsGrid } from "../components/LiveTestimonialsGrid";
import { getTestimonials } from "../lib/payload";
const title = "7-Tage-Challenge Mehr Klarheit in 7 Tagen";
const description =
"Die 7-Tage-Challenge für deinen klaren Kopf und mehr Struktur im Alltag: 7 kurze, praktische Impulse direkt ins Postfach für mehr Fokus, Ruhe und Klarheit.";
export const metadata: Metadata = {
title,
description,
alternates: {
canonical: "/challenge",
},
openGraph: {
title,
description,
url: "/challenge",
images: ["/blog-featured.jpg"],
},
twitter: {
title,
description,
images: ["/blog-featured.jpg"],
},
};
function IconEnvelope() {
return (
<svg width="52" height="44" viewBox="0 0 52 44" fill="none">
<rect x="2" y="2" width="48" height="40" rx="3" stroke="#f6a701" strokeWidth="2" />
<path d="M2 2l24 22L50 2" stroke="#f6a701" strokeWidth="2" />
</svg>
);
}
function IconMailLines() {
return (
<svg width="56" height="44" viewBox="0 0 56 44" fill="none">
<line x1="2" y1="12" x2="12" y2="12" stroke="#f6a701" strokeWidth="2" strokeLinecap="round" />
<line x1="2" y1="20" x2="9" y2="20" stroke="#f6a701" strokeWidth="2" strokeLinecap="round" />
<line x1="2" y1="28" x2="7" y2="28" stroke="#f6a701" strokeWidth="2" strokeLinecap="round" />
<rect x="13" y="2" width="41" height="40" rx="3" stroke="#f6a701" strokeWidth="2" />
<path d="M13 2l20.5 19L54 2" stroke="#f6a701" strokeWidth="2" />
</svg>
);
}
function IconNotepad() {
return (
<svg width="46" height="52" viewBox="0 0 46 52" fill="none">
<rect x="2" y="4" width="32" height="42" rx="2" stroke="#f6a701" strokeWidth="2" />
<line x1="9" y1="16" x2="27" y2="16" stroke="#f6a701" strokeWidth="2" strokeLinecap="round" />
<line x1="9" y1="23" x2="24" y2="23" stroke="#f6a701" strokeWidth="2" strokeLinecap="round" />
<line x1="9" y1="30" x2="20" y2="30" stroke="#f6a701" strokeWidth="2" strokeLinecap="round" />
<circle cx="37" cy="40" r="7" stroke="#f6a701" strokeWidth="2" />
<line x1="37" y1="27" x2="37" y2="33" stroke="#f6a701" strokeWidth="2" strokeLinecap="round" />
</svg>
);
}
function IconCheckCircle() {
return (
<svg width="48" height="48" viewBox="0 0 48 48" fill="none">
<circle cx="24" cy="24" r="21" stroke="#f6a701" strokeWidth="2" />
<path d="M14 24l7.5 7.5L34 16" stroke="#f6a701" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round" />
</svg>
);
}
function Check() {
return (
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" className="shrink-0 mt-0.5">
<path d="M3 9.5l4 4L15 4" stroke="#f6a701" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round" />
</svg>
);
}
function LockIcon() {
return (
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" className="shrink-0">
<rect x="2" y="6" width="10" height="7" rx="1.5" stroke="#888" strokeWidth="1.3" />
<path d="M4.5 6V4.5a2.5 2.5 0 0 1 5 0V6" stroke="#888" strokeWidth="1.3" />
</svg>
);
}
const steps = [
{
icon: <IconEnvelope />,
title: "1. Anmelden",
desc: "Trage dich mit deiner E-Mail-Adresse ein und starte sofort.",
},
{
icon: <IconMailLines />,
title: "2. E-Mail erhalten",
desc: "Du bekommst 7 Tage lang jeweils einen Impuls direkt in dein Postfach.",
},
{
icon: <IconNotepad />,
title: "3. Umsetzen",
desc: "Setze die einfache Aufgabe in wenigen Minuten um für mehr Klarheit und Fokus.",
},
{
icon: <IconCheckCircle />,
title: "4. Dranbleiben",
desc: "Kleine Schritte führen zu großen Veränderungen Tag für Tag.",
},
];
const benefits = [
{ title: "Klarheit gewinnen", desc: "Sortiere deine Gedanken und erkenne, was wirklich zählt." },
{ title: "Fokus stärken", desc: "Richte deine Aufmerksamkeit auf das Wesentliche." },
{ title: "Struktur schaffen", desc: "Einfache Methoden für mehr Ordnung im Alltag." },
{ title: "Gewohnheiten aufbauen", desc: "Kleine Schritte, die langfristig Wirkung zeigen." },
{ title: "Gelassener leben", desc: "Weniger Stress, mehr Zeit für die Dinge, die dir wichtig sind." },
];
function EmailCapture({ buttonLabel = "Challenge starten" }: { buttonLabel?: string }) {
return (
<div className="flex flex-col gap-2 w-full">
<div className="flex gap-3 w-full">
<input
type="email"
placeholder="Deine E-Mail-Adresse"
className="flex-1 min-w-0 bg-white border border-[#d9d9d9] rounded-lg px-4 py-3 text-[1rem] text-[#868686] outline-none focus:border-[#f6a701] transition-colors"
/>
<button
type="submit"
className="shrink-0 bg-[#f6a701] rounded-lg px-5 py-3 font-bold text-[1rem] text-[#222221] whitespace-nowrap hover:brightness-95 active:scale-[0.97] transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#f6a701] focus-visible:ring-offset-2"
>
{buttonLabel}
</button>
</div>
{/* Consent checkbox — this signup's legal basis is consent (email
marketing), same wording as the other newsletter forms; colors
match this page's own hardcoded palette instead of the shared
design tokens, consistent with the rest of the page. */}
<label className="flex gap-2 items-start cursor-pointer">
<input
type="checkbox"
className="size-4 shrink-0 mt-0.5 rounded-xs border border-[#d9d9d9] accent-[#f6a701]"
/>
<span className="text-[0.8rem] text-[#444] leading-normal">
Ich akzeptiere die{" "}
<Link
href="/datenschutz"
target="_blank"
rel="noopener noreferrer"
className="underline hover:text-[#f6a701]"
>
Datenschutzerklärung
</Link>
.
</span>
</label>
<p className="flex items-center gap-1.5 text-[0.8rem] text-[#888]">
<LockIcon />
Keine Werbung. Jederzeit abbestellbar.
</p>
</div>
);
}
export default async function ChallengePage() {
const { isEnabled: isPreview } = await draftMode();
const testimonials = await getTestimonials("challenge", { draft: isPreview });
return (
<>
<main className="flex flex-col flex-1 bg-[#f5f0e8]">
{/* ── Hero ── */}
<section className="w-full flex items-stretch overflow-hidden" style={{ minHeight: "32rem" }}>
{/* Left: text + form. justify-center removed from here — the
outer section's items-stretch already makes this column full
height, but centering its whole content (breadcrumb included)
made the breadcrumb's Y position drift depending on this
page's content length vs. /todo-cards's and /newsletter's own
(different) content lengths. Same fix as both of those. */}
{/* pt-10 md:pt-12 (not the original py-10 lg:py-14's top value) —
matches /todo-cards's and /newsletter's breadcrumb top offset
exactly; pb-10 lg:pb-14 keeps this page's original bottom
spacing, which was never the inconsistent part. */}
<Reveal className="flex flex-col gap-6 px-8 lg:px-[5rem] pt-10 md:pt-12 pb-10 lg:pb-14 w-full lg:w-[52%] lg:shrink-0">
{/* Breadcrumb — 3 levels (Startseite Werkzeuge
7-Tage-Challenge), matching /todo-cards's and
/newsletter's pattern; was missing "Werkzeuge" in the
middle. Also switched from this page's hardcoded hex
colors to the shared text-body-sm/text-text-muted/
text-text-primary tokens the other two breadcrumbs use —
text-body-sm is 0.875rem, identical to the old literal
value, so this is a pure consistency fix, not a visual
change. Deliberately NOT part of the centered block below. */}
<p className="text-body-sm text-text-muted flex items-center gap-2">
<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">7-Tage-Challenge</span>
</p>
{/* Everything else — centered in the remaining vertical space,
same fix as /todo-cards's and /newsletter's Hero sections. */}
<div className="flex flex-col gap-6 flex-1 justify-center">
{/* Headline */}
<h1
className="font-semibold text-[#222221] leading-[1.15]"
style={{ fontFamily: "var(--font-lora)", fontSize: "clamp(2.25rem, 5vw, 3.75rem)" }}
>
7 Tage.<br />
Mehr Klarheit.<br />
Weniger Stress<span className="text-brand">.</span>
</h1>
{/* Subtitle */}
<p className="text-[1rem] text-[#444] leading-[1.6] max-w-[30rem]">
Die 7-Tage-Challenge für deinen klaren Kopf und mehr Struktur im Alltag.
</p>
{/* Checklist */}
<ul className="flex flex-col gap-2">
{[
"7 kurze Impulse direkt in dein Postfach",
"Praktisch, umsetzbar und alltagstauglich",
"Für mehr Fokus, Ruhe und Klarheit",
].map((item) => (
<li key={item} className="flex items-start gap-2 text-[#222221] text-[0.95rem]">
<Check />
{item}
</li>
))}
</ul>
{/* Email form */}
<EmailCapture />
</div>
</Reveal>
{/* Right: hero image with badge — group + scale-105 on hover,
same restrained "photo feels alive" treatment as the Home/
todo-cards hero images; delay={0.15} mirrors their text→image
stagger. */}
<Reveal className="hidden lg:block group flex-1 relative overflow-hidden" delay={0.15}>
<Image
alt="7-Tage-Challenge"
src="/blog-featured.jpg"
fill
sizes="48vw"
className="object-cover object-center pointer-events-none transition-transform duration-500 group-hover:scale-105"
/>
{/* Badge */}
<div className="absolute top-8 right-8 w-[7.5rem] h-[7.5rem] rounded-full bg-white shadow-md flex flex-col items-center justify-center text-center p-3 gap-1">
<p className="text-[0.65rem] font-semibold text-[#222221] leading-tight">
Jeden Tag ein neuer Impuls in deinem Postfach.
</p>
<svg width="20" height="16" viewBox="0 0 20 16" fill="none">
<rect x="1" y="1" width="18" height="14" rx="2" stroke="#f6a701" strokeWidth="1.5" />
<path d="M1 1l9 8 9-8" stroke="#f6a701" strokeWidth="1.5" />
</svg>
</div>
</Reveal>
</section>
{/* ── So funktioniert die Challenge ── */}
<section className="bg-white w-full py-14 lg:py-20">
<div className="px-8 lg:px-[5rem] max-w-[1280px] mx-auto flex flex-col items-center gap-12">
<Reveal className="flex flex-col items-center gap-2 text-center">
<h2
className="font-semibold text-[#222221]"
style={{ fontFamily: "var(--font-lora)", fontSize: "clamp(1.5rem, 3vw, 2rem)" }}
>
So funktioniert die Challenge
</h2>
<p className="text-[1rem] text-[#666]">Jeden Tag ein Impuls. In nur wenigen Minuten.</p>
</Reveal>
<RevealGroup className="flex flex-col lg:flex-row items-start lg:items-start gap-8 lg:gap-2 w-full">
{steps.flatMap((step, i) => [
<RevealItem key={step.title} className="group flex lg:flex-col items-start lg:items-center gap-4 lg:gap-5 flex-1 min-w-0">
<div className="flex items-center justify-center w-16 h-14 shrink-0 transition-transform duration-300 group-hover:scale-110">
{step.icon}
</div>
<div className="flex flex-col gap-1 lg:text-center">
<p className="font-semibold text-[#222221] text-[1rem]">{step.title}</p>
<p className="text-[0.875rem] text-[#666] leading-[1.5]">{step.desc}</p>
</div>
</RevealItem>,
i < steps.length - 1 ? (
// Same /icon-arrow-connector.svg asset and rotate-on-stack
// pattern as todo-cards/newsletter's HowItWorks — this
// used to be its own hand-drawn SVG arrow, inconsistent
// with those two. Always visible (rotated 90° while
// stacked below lg, this page's own structural
// breakpoint) rather than hidden below lg like before.
<div key={`arrow-${i}`} className="flex items-center justify-center shrink-0 lg:mt-5">
<Image
alt=""
src="/icon-arrow-connector.svg"
width={24}
height={24}
className="w-6 h-6 rotate-90 lg:w-10 lg:h-3 lg:rotate-0"
/>
</div>
) : null,
])}
</RevealGroup>
</div>
</section>
{/* ── Das erwartet dich ── */}
<section className="w-full py-14 lg:py-20">
<div className="px-8 lg:px-[5rem] max-w-[1280px] mx-auto flex flex-col gap-10">
<Reveal className="flex flex-col items-center gap-2 text-center">
<h2
className="font-semibold text-[#222221]"
style={{ fontFamily: "var(--font-lora)", fontSize: "clamp(1.5rem, 3vw, 2rem)" }}
>
Das erwartet dich
</h2>
<p className="text-[1rem] text-[#666]">7 Tage. 7 Impulse. Für mehr Klarheit in deinem Alltag.</p>
</Reveal>
<div className="flex flex-col lg:flex-row gap-10 lg:gap-16 items-center">
{/* Image */}
<Reveal
className="group relative w-full lg:w-[44%] lg:shrink-0 rounded-xl overflow-hidden"
style={{ minHeight: "18rem" }}
>
<Image
alt="Notizbuch"
src="/challenge-content.jpg"
fill
sizes="(min-width: 1024px) 44vw, 100vw"
className="object-cover transition-transform duration-500 group-hover:scale-105"
/>
</Reveal>
{/* Checklist */}
<Reveal delay={0.1} className="flex-1 w-full">
<ul className="flex flex-col gap-5">
{benefits.map((b) => (
<li key={b.title} className="flex items-start gap-3">
<Check />
<div className="flex flex-col gap-0.5">
<p className="font-semibold text-[#222221] text-[1rem]">{b.title}</p>
<p className="text-[0.9rem] text-[#555] leading-[1.5]">{b.desc}</p>
</div>
</li>
))}
</ul>
</Reveal>
</div>
</div>
</section>
{/* ── Was andere sagen ── */}
{isPreview ? (
<LiveTestimonialsGrid testimonials={testimonials} />
) : (
<TestimonialsGrid testimonials={testimonials} />
)}
{/* ── Bottom CTA ── */}
<section className="w-full py-14 lg:py-16">
<div className="px-8 lg:px-[5rem] max-w-[1280px] mx-auto">
<Reveal className="bg-[#f8f3ec] rounded-xl flex flex-col lg:flex-row gap-8 lg:gap-[3.5rem] items-start lg:items-center px-6 lg:px-10 py-8">
{/* Left: icon + copy */}
<div className="flex gap-5 items-start flex-1 min-w-0">
<div className="shrink-0 -rotate-4">
<svg width="52" height="44" viewBox="0 0 52 44" fill="none">
<rect x="2" y="2" width="48" height="40" rx="3" stroke="#f6a701" strokeWidth="2" />
<path d="M2 2l24 22L50 2" stroke="#f6a701" strokeWidth="2" />
</svg>
</div>
<div className="flex flex-col gap-2">
<p
className="font-semibold text-[#222221] leading-normal"
style={{ fontFamily: "var(--font-lora)", fontSize: "clamp(1.125rem, 2vw, 1.375rem)" }}
>
Starte jetzt deine 7-Tage-Challenge
</p>
<p className="text-[0.9rem] text-[#555] leading-[1.5]">
Trage dich ein und erhalte ab sofort täglich einen Impuls für mehr Klarheit und Fokus.
</p>
</div>
</div>
{/* Right: form */}
<div className="w-full lg:w-[30rem] lg:shrink-0">
<EmailCapture />
</div>
</Reveal>
</div>
</section>
</main>
<Footer />
</>
);
}