Files
anouma/app/(frontend)/angebote/page.tsx
T
maro 526a908c80
CI / test (push) Successful in 3m42s
Release / test (push) Successful in 3m44s
Release / build-and-deploy (push) Successful in 8m59s
Redesign: warmer, more editorial visual pass; remove stray em dashes
Replaces em dashes with en dashes across all hardcoded copy, emails, and
seed content, then reworks the site's visual language so it reads as
deliberately designed rather than assembled from generic components:

- New ArrowLink component replaces ad hoc "Mehr erfahren ->" links and
  ghost buttons; one shared FormField (TextField/TextAreaField) replaces
  7 near-duplicate field styles across every form
- Navbar loses its glassmorphism blur for a solid warm background, gets a
  subtle active-link underline and a softer, warmer mega-menu
- PageHeader supports per-page blob tone/position and a compact mode
  instead of an identical band on every interior page
- Homepage: the core offer is featured large instead of four equal cards,
  the philosophy section gets a single calmer accent blob, blog teasers
  now show their cover image like the real /aktuelles page does
- Booking calendar and widget: 44px touch targets throughout (month nav,
  day cells, time slots, cancel action), a visible "today" ring, and
  responsive padding so the calendar isn't cramped on small screens
- Quieter, warmer empty states on /termine and /aktuelles

No functional changes: all interaction logic (nav menus, forms, booking,
CMS data fetching) is untouched, only markup and styling.
2026-09-06 22:00:28 +02:00

178 lines
6.9 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 { PageHeader } from "@/components/PageHeader";
import { Section, SectionHeading } from "@/components/Section";
import { ImagePlaceholder } from "@/components/ImagePlaceholder";
import { AngebotCard } from "@/components/AngebotCard";
import { Button } from "@/components/Button";
import { CTA } from "@/components/CTA";
import { Reveal } from "@/components/Reveal";
import { getOffers } from "@/lib/payload/content";
import { getAngeboteIntroGlobal, getSEOSettingsGlobal } from "@/lib/payload/globals";
import { groupOffersByCategory, moodForOffer } from "@/lib/angebote";
import { mediaAlt, mediaUrl } from "@/lib/payload/media";
import { resolveSeo } from "@/lib/seo/resolve";
import { buildMetadata } from "@/lib/seo/metadata";
import { getSiteUrl } from "@/lib/seo/config";
import { JsonLd, webPageJsonLd } from "@/lib/seo/jsonld";
import { siteConfig } from "@/lib/site";
export const dynamic = "force-dynamic";
const FALLBACK_DESCRIPTION =
"Prozessbegleitung, Doula-Begleitung, Kindergruppen und Singkreise – die Angebote von Anouma im Überblick.";
export async function generateMetadata(): Promise<Metadata> {
const [intro, seoSettings] = await Promise.all([getAngeboteIntroGlobal(), getSEOSettingsGlobal()]);
const resolved = resolveSeo({
seo: intro.seo,
fallbackTitle: `Begleitung für dich und deine Familie – ${siteConfig.name}`,
fallbackDescription: () => intro.lead || seoSettings.defaultDescription || FALLBACK_DESCRIPTION,
defaultOgImage: seoSettings.defaultOgImage,
});
return buildMetadata({
title: resolved.title,
description: resolved.description,
path: "/angebote",
ogImageUrl: resolved.ogImageUrl,
keywords: resolved.keywords,
});
}
export default async function AngebotePage() {
const [intro, offers] = await Promise.all([getAngeboteIntroGlobal(), getOffers()]);
const groups = groupOffersByCategory(offers);
const siteUrl = await getSiteUrl();
const prozessbegleitung = groups.find((g) => g.category === "prozessbegleitung")?.offers[0];
const doula = groups.find((g) => g.category === "doula-begleitung")?.offers[0];
const kindergruppen = groups.find((g) => g.category === "kindergruppen");
const singkreise = groups.find((g) => g.category === "singkreise");
return (
<>
<JsonLd
data={webPageJsonLd({
name: intro.title || "Angebote",
description: intro.lead || FALLBACK_DESCRIPTION,
url: `${siteUrl}/angebote`,
})}
/>
<PageHeader
title={intro.title || "Angebote"}
lead={intro.lead}
crumbs={[{ title: "Angebote" }]}
/>
{prozessbegleitung && (
<Section tone="plain">
<div className="grid items-center gap-14 lg:grid-cols-2">
<Reveal>
<div className="relative aspect-[4/5] w-full max-w-md lg:max-w-none">
<ImagePlaceholder
mood={moodForOffer(prozessbegleitung)}
label={prozessbegleitung.title}
src={mediaUrl(prozessbegleitung.image, "hero")}
alt={mediaAlt(prozessbegleitung.image)}
className="h-full w-full"
/>
</div>
</Reveal>
<Reveal delay={0.1}>
<p className="mb-3 text-xs font-medium uppercase tracking-[0.22em] text-anouma-plum">
Für Erwachsene
</p>
<h2 className="font-serif text-4xl font-medium leading-tight text-anouma-plum">
{prozessbegleitung.title}
</h2>
<p className="mt-5 text-lg leading-relaxed text-anouma-plum">
{prozessbegleitung.shortDescription}
</p>
<div className="mt-8">
<Button href={`/angebote/${prozessbegleitung.slug}`}>
{prozessbegleitung.title} entdecken
</Button>
</div>
</Reveal>
</div>
</Section>
)}
{doula && (
<Section tone="cream">
<div className="grid items-center gap-14 lg:grid-cols-2">
<Reveal className="order-2 lg:order-1">
<p className="mb-3 text-xs font-medium uppercase tracking-[0.22em] text-anouma-plum">
Kinderwunsch · Schwangerschaft · Geburt · Wochenbett
</p>
<h2 className="font-serif text-4xl font-medium leading-tight text-anouma-plum">
{doula.title}
</h2>
<p className="mt-5 text-lg leading-relaxed text-anouma-plum">
{doula.shortDescription}
</p>
<div className="mt-8">
<Button href={`/angebote/${doula.slug}`}>{doula.title} entdecken</Button>
</div>
</Reveal>
<Reveal delay={0.1} className="order-1 lg:order-2">
<div className="relative aspect-[4/5] w-full max-w-md lg:max-w-none lg:ml-auto">
<ImagePlaceholder
mood={moodForOffer(doula)}
label={doula.title}
src={mediaUrl(doula.image, "hero")}
alt={mediaAlt(doula.image)}
className="h-full w-full"
/>
</div>
</Reveal>
</div>
</Section>
)}
{kindergruppen && kindergruppen.offers.length > 0 && (
<Section id="kindergruppen" tone="plain">
<SectionHeading eyebrow="Für Kinder" title="Kindergruppen" lead={kindergruppen.label} />
<div className="mt-14 grid gap-6 sm:grid-cols-2">
{kindergruppen.offers.map((offer, i) => (
<Reveal key={offer.slug} delay={i * 0.08}>
<AngebotCard
href={`/angebote/${offer.slug}`}
title={offer.title}
tagline={offer.shortDescription}
mood={moodForOffer(offer)}
imageSrc={mediaUrl(offer.image, "card")}
size="large"
/>
</Reveal>
))}
</div>
</Section>
)}
{singkreise && singkreise.offers.length > 0 && (
<Section tone="warm">
<SectionHeading eyebrow="Für Herz und Seele" title="Singkreise" />
<div className="mt-14 grid gap-6 sm:grid-cols-3">
{singkreise.offers.map((offer, i) => (
<Reveal key={offer.slug} delay={i * 0.08}>
<AngebotCard
href={`/angebote/${offer.slug}`}
title={offer.title}
tagline={offer.shortDescription}
mood={moodForOffer(offer)}
imageSrc={mediaUrl(offer.image, "card")}
/>
</Reveal>
))}
</div>
</Section>
)}
<CTA
title="Unsicher, welcher Raum passt?"
lead="Melde dich gerne für ein unverbindliches Kennenlerngespräch – gemeinsam schauen wir, was dein Anliegen gerade braucht."
/>
</>
);
}