feat(ui): Seite "Fuer Unternehmen" mit Leben - Glaeser, Biene, Zielgruppen
Docker Image bauen und veroeffentlichen / build-and-push (push) Successful in 2m30s

- Hero: Titel gross auf dem Bienenfoto, darunter "Fuer ..." mit
  wechselnder Zielgruppe (Unternehmen, Kanzleien, Praxen, Hotels,
  Agenturen, Handwerksbetriebe - die Woerter aus dem Untertitel), eine
  Biene fliegt durchs Bild, Knopf springt zur Anfrage.
- Die drei Pakete wieder als Karten, jetzt mit gezeichnetem Honigglas:
  Klassik mit Banderole, Geschenk im Karton mit Schleife, Individuell mit
  Stift und Funkeln. Beim Hover hebt sich das Glas, die Schleife wippt.
  Ein Klick springt zum Formular und waehlt das Paket dort vor
  (?paket=, nur bekannte Schluessel).
- Anfrage: links Ueberschrift und das Glaeser-Foto (mitlaufend, leichte
  Parallaxe), rechts das Formular.

Neu im Woerterbuch nur Ueberschrift, Knopftext und die Zielgruppenliste;
keine neuen Aussagen. Dunkelmodus und reduzierte Bewegung beruecksichtigt.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
Nick SzaunigandClaude Opus 5.5 committed 2026-09-26 06:35:29 +02:00
1 parent 1ff5f8c722
commit 86cc0ad310
7 files changed
+267 -32

No files matched your search

+107 -30
View File
@@ -1,12 +1,17 @@
import type { Metadata } from "next";
import { Container, SectionHeading } from "@/components/ui";
import Link from "next/link";
import { Container, SectionHeading, LinkButton, FramedMedia } from "@/components/ui";
import { HoneycombPattern } from "@/components/honeycomb";
import { ContentImage } from "@/components/content-image";
import { BusinessInquiryForm } from "@/components/business-inquiry-form";
import { ScrollReveal } from "@/components/scroll-reveal";
import { BeeFlight } from "@/components/bee";
import { ParallaxMedia } from "@/components/parallax-media";
import { AudienceCycle } from "@/components/audience-cycle";
import { PackageJar, type PackageKey } from "@/components/package-jar";
import { getLocale } from "@/i18n/locale";
import { getDictionary } from "@/i18n/get-dictionary";
import { getContentValue } from "@/lib/content";
import { getContentValues } from "@/lib/content";
export async function generateMetadata(): Promise<Metadata> {
const t = getDictionary(await getLocale());
@@ -17,47 +22,119 @@ export async function generateMetadata(): Promise<Metadata> {
};
}
export default async function UnternehmenPage() {
export default async function UnternehmenPage(props: PageProps<"/unternehmen">) {
const locale = await getLocale();
const t = getDictionary(locale);
const image = await getContentValue("unternehmen.image", locale);
const tu = t.unternehmenPage;
const { paket } = await props.searchParams;
// Nur bekannte Pakete uebernehmen — der Wert landet als Vorauswahl im Formular.
const selected = tu.packages.find((p) => p.key === paket)?.key;
const content = await getContentValues(["unternehmen.image", "home.businessTeaser.image"], locale);
return (
<div>
<div className="relative h-[50vh] min-h-[340px] w-full overflow-hidden">
<div className="motion-drift absolute inset-0 animate-[wh-drift_26s_ease-in-out_infinite_alternate]">
{/* Hero: Titel auf dem Foto, die Zielgruppen wechseln, eine Biene fliegt durchs Bild. */}
<section className="relative isolate flex min-h-[72vh] items-end overflow-hidden bg-forest text-cream">
<div className="motion-drift absolute inset-0 -z-10 animate-[wh-drift_26s_ease-in-out_infinite_alternate]">
<ContentImage
src={image || null}
alt={t.unternehmenPage.eyebrow}
tone="honey"
src={content["unternehmen.image"] || null}
alt={tu.eyebrow}
tone="forest"
className="h-full w-full"
label={t.unternehmenPage.eyebrow}
label={tu.eyebrow}
rounded={false}
priority
/>
</div>
<HoneycombPattern className="text-cream/[0.14]" size={30} fade="right" />
</div>
<Container className="py-20">
<ScrollReveal>
<SectionHeading
level="h1"
title={t.unternehmenPage.title}
subtitle={t.unternehmenPage.subtitle}
/>
</ScrollReveal>
<div aria-hidden="true" className="absolute inset-0 -z-10 bg-gradient-to-t from-forest via-forest/65 to-forest/10" />
<HoneycombPattern className="-z-10 text-cream/[0.1]" size={30} fade="right" />
<BeeFlight className="!absolute inset-x-0 top-[18%] h-32 md:h-44" />
<Container className="relative pb-16 pt-40 md:pb-20">
<ScrollReveal>
<h1 className="max-w-3xl font-serif text-5xl leading-[1.02] text-cream text-balance md:text-7xl">{tu.title}</h1>
<AudienceCycle className="mt-6 text-xl text-cream/85 md:text-2xl" prefix={tu.forLabel} items={tu.audiences} />
<LinkButton href="#anfrage" variant="accent" className="mt-10">
{tu.inquiryHeading}
</LinkButton>
</ScrollReveal>
</Container>
</section>
{/* Geteilt: die Ueberschrift laeuft neben dem langen Formular mit. */}
<div className="mt-24 grid gap-10 lg:grid-cols-12 lg:gap-16">
<ScrollReveal className="lg:col-span-4">
<h2 className="text-4xl leading-tight text-heading lg:sticky lg:top-32">
{t.unternehmenPage.inquiryHeading}
</h2>
{/* Pakete als gezeichnete Glaeser — ein Klick waehlt das Paket im Formular vor. */}
<section className="relative overflow-hidden py-24 md:py-28">
<HoneycombPattern className="text-honey/[0.07]" size={24} fade="radial" />
<Container className="relative">
<ScrollReveal>
<SectionHeading title={tu.packagesHeading} align="center" className="mx-auto" />
</ScrollReveal>
<ScrollReveal className="lg:col-span-8">
<BusinessInquiryForm turnstileSiteKey={process.env.NEXT_PUBLIC_TURNSTILE_SITE_KEY} />
<div className="mt-16 grid gap-8 md:grid-cols-3">
{tu.packages.map((pkg, i) => (
<ScrollReveal key={pkg.key} delay={i * 0.1}>
<Link
href={`/unternehmen?paket=${pkg.key}#anfrage`}
className={`group flex h-full flex-col items-center rounded-lg border bg-background px-8 pb-8 pt-10 text-center transition-[transform,border-color,box-shadow] duration-500 ease-[var(--ease-arrive)] hover:-translate-y-1 hover:border-honey/60 hover:shadow-[0_24px_44px_-28px_rgba(184,138,68,0.55)] motion-reduce:transition-none ${
selected === pkg.key ? "border-honey" : "border-ink/10"
}`}
>
<PackageJar variant={pkg.key as PackageKey} className="h-40 w-32 md:h-56 md:w-44" />
<h3 className="mt-6 font-serif text-3xl text-heading">{pkg.title}</h3>
<p className="mt-2 text-ink/70">{pkg.text}</p>
<span className="mt-auto inline-flex items-center gap-2 pt-6 text-sm tracking-wide-caps text-honey-dark">
{tu.packageCta}
<svg
viewBox="0 0 24 24"
className="h-4 w-4 transition-transform duration-300 group-hover:translate-x-1 motion-reduce:transition-none"
fill="none"
stroke="currentColor"
strokeWidth="1.6"
aria-hidden="true"
>
<path d="M4 12h15M13 6l6 6-6 6" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</span>
</Link>
</ScrollReveal>
))}
</div>
</Container>
</section>
{/* Anfrage: links Ueberschrift und Glaeser-Foto, die mitlaufen; rechts das Formular. */}
<section id="anfrage" className="scroll-mt-24 bg-surface/60 py-24">
<Container className="grid gap-12 lg:grid-cols-12 lg:gap-16">
<div className="lg:col-span-5">
<div className="lg:sticky lg:top-32">
<ScrollReveal>
<h2 className="text-4xl leading-tight text-heading md:text-5xl">{tu.inquiryHeading}</h2>
<p className="mt-4 max-w-sm text-ink/70">{tu.subtitle}</p>
</ScrollReveal>
<ScrollReveal delay={0.1} className="mt-10 hidden lg:block">
<FramedMedia>
<ParallaxMedia className="rounded-lg">
<ContentImage
src={content["home.businessTeaser.image"] || null}
alt=""
tone="honey"
className="aspect-[4/3] w-full"
label={tu.eyebrow}
sizes="(min-width: 1024px) 34vw, 100vw"
/>
</ParallaxMedia>
</FramedMedia>
</ScrollReveal>
</div>
</div>
<ScrollReveal className="lg:col-span-7">
{/* key: bei Wechsel der Vorauswahl (Klick auf eine Paketkarte) neu aufbauen,
sonst bliebe die zuvor gewaehlte Option aktiv. */}
<BusinessInquiryForm
key={selected ?? "default"}
defaultPackage={selected}
turnstileSiteKey={process.env.NEXT_PUBLIC_TURNSTILE_SITE_KEY}
/>
</ScrollReveal>
</div>
</Container>
</Container>
</section>
</div>
);
}