Files
Wabenhain/src/components/home-sections-b.tsx
T
Nick SzaunigandClaude Opus 5.5 7e467a9ddf feat(ui): Startseite mit eigenstaendigen Abschnitten statt Schablonen-Zickzack
- Aktuelle Ernte: bei ein, zwei Produkten steht die Ueberschrift als
  eigene Spalte daneben, statt ueber einem halb leeren Dreierraster.
- Herkunft: Foto randlos ueber die halbe Seitenbreite, Text daneben,
  ohne Rahmen.
- Philosophie: typografische Reihe mit Trennlinien und grossen
  Serif-Titeln statt vier Icon-Karten.
- Region: breites Bild, der Text liegt als Tafel ueberlappend darueber.
- Geschaeftskunden: kompakte Tafel mit randlosem Bild statt eines dritten
  grossen Bild/Text-Wechsels.

Inhalte, Texte und Content-Schluessel bleiben unveraendert.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-26 01:45:06 +02:00

234 lines
9.4 KiB
TypeScript

import { Container, SectionHeading, LinkButton } from "@/components/ui";
import { ProcessPath } from "@/components/process-path";
import { HoneycombPattern, HexMark } from "@/components/honeycomb";
import { ScrollReveal } from "@/components/scroll-reveal";
import { ContentImage } from "@/components/content-image";
import { StarRating } from "@/components/star-rating";
import { fetchGoogleReviews } from "@/lib/google-reviews";
import { getLocale } from "@/i18n/locale";
import { getDictionary } from "@/i18n/get-dictionary";
import { getContentValues, parseListTitled, parseListNumbered } from "@/lib/content";
export async function ProcessTimeline() {
const locale = await getLocale();
const t = getDictionary(locale);
const content = await getContentValues(["home.processTimeline.steps"], locale);
const steps = parseListNumbered(content["home.processTimeline.steps"]);
return (
<section className="py-24 bg-cream/60">
<Container>
<ScrollReveal>
<SectionHeading
title={t.home.processTimeline.title}
align="center"
className="mx-auto"
/>
</ScrollReveal>
<ProcessPath steps={steps} />
</Container>
</section>
);
}
export async function RegionalSection() {
const locale = await getLocale();
const t = getDictionary(locale);
const content = await getContentValues(
[
"home.regionalSection.paragraph1",
"home.regionalSection.paragraph2",
"home.regionalSection.image",
],
locale
);
return (
<section className="py-24">
<Container>
{/* Breites Bild, der Text legt sich als Tafel darueber — Tiefe statt
eines weiteren Bild-neben-Text-Wechsels. */}
<ScrollReveal>
<ContentImage
src={content["home.regionalSection.image"] || null}
alt=""
tone="forest"
className="aspect-[4/3] w-full sm:aspect-[16/9]"
label={t.home.regionalSection.eyebrow}
sizes="(min-width: 1280px) 1200px, 100vw"
/>
</ScrollReveal>
<ScrollReveal
delay={0.1}
className="relative mx-4 -mt-16 max-w-2xl bg-background p-8 shadow-[0_24px_50px_-34px_rgba(36,53,43,0.45)] sm:mx-10 sm:p-10 lg:-mt-40 lg:ml-16 lg:p-12"
>
<SectionHeading title={t.home.regionalSection.title} />
<p className="mt-6 text-ink/75 leading-relaxed">{content["home.regionalSection.paragraph1"]}</p>
<p className="mt-4 text-ink/75 leading-relaxed">{content["home.regionalSection.paragraph2"]}</p>
<div className="mt-8 flex flex-wrap gap-4">
<LinkButton href="/imkerei" variant="secondary">
{t.home.regionalSection.ctaAbout}
</LinkButton>
<LinkButton href="/kontakt" variant="ghost">
{t.home.regionalSection.ctaPickup}
</LinkButton>
</div>
</ScrollReveal>
</Container>
</section>
);
}
export async function Sustainability() {
const locale = await getLocale();
const t = getDictionary(locale);
const content = await getContentValues(["home.sustainability.items"], locale);
const items = parseListTitled(content["home.sustainability.items"]);
return (
<section className="py-28 bg-cream/60">
{/* Geteilt statt Viererreihe: die Ueberschrift laeuft mit, die Punkte
werden als ruhige Liste gelesen. */}
<Container className="grid gap-12 lg:grid-cols-12 lg:gap-16">
<ScrollReveal className="lg:col-span-5">
<div className="lg:sticky lg:top-32">
<SectionHeading title={t.home.sustainability.title} />
</div>
</ScrollReveal>
<ul className="divide-y divide-ink/10 border-y border-ink/10 lg:col-span-7">
{items.map((item, i) => (
<li key={item.title}>
<ScrollReveal delay={i * 0.08}>
<div className="group grid grid-cols-[auto_1fr] gap-x-5 py-7">
<HexMark className="mt-2 h-3 w-3 text-honey transition-transform duration-500 ease-[var(--ease-arrive)] group-hover:rotate-90" />
<div>
<h3 className="text-2xl text-forest">{item.title}</h3>
<p className="mt-2 max-w-[55ch] text-ink/70 leading-relaxed">{item.text}</p>
</div>
</div>
</ScrollReveal>
</li>
))}
</ul>
</Container>
</section>
);
}
export async function BusinessTeaser() {
const locale = await getLocale();
const t = getDictionary(locale);
const content = await getContentValues(
["home.businessTeaser.paragraph", "home.businessTeaser.image"],
locale
);
return (
<section className="py-24">
<Container>
{/* Kompakte Tafel statt eines dritten grossen Bild/Text-Blocks: ein
Angebot fuer eine kleinere Zielgruppe, entsprechend zurueckhaltend. */}
<ScrollReveal className="grid overflow-hidden border border-ink/10 bg-background md:grid-cols-5">
<div className="flex flex-col justify-center p-8 sm:p-12 md:col-span-3 lg:p-16">
<SectionHeading title={t.home.businessTeaser.title} />
<p className="mt-6 max-w-[55ch] text-ink/75 leading-relaxed">{content["home.businessTeaser.paragraph"]}</p>
<LinkButton href="/unternehmen" className="mt-8 self-start">
{t.home.businessTeaser.cta}
</LinkButton>
</div>
<ContentImage
src={content["home.businessTeaser.image"] || null}
alt=""
tone="honey"
rounded={false}
className="aspect-[4/3] w-full md:col-span-2 md:aspect-auto md:h-full md:min-h-[22rem]"
label={t.home.businessTeaser.eyebrow}
sizes="(min-width: 768px) 40vw, 100vw"
/>
</ScrollReveal>
</Container>
</section>
);
}
export async function Reviews() {
const t = getDictionary(await getLocale());
const google = await fetchGoogleReviews();
const placeId = process.env.GOOGLE_PLACE_ID;
const mapsUrl = placeId ? `https://www.google.com/maps/place/?q=place_id:${placeId}` : null;
// Echte Google-Rezensionen sind Zitate realer Kund:innen und werden nie
// uebersetzt oder umformuliert — nur die eigenen Fallback-Testimonials
// (ohne konfigurierte Google-Anbindung) existieren in beiden Sprachen.
const items = google && google.reviews.length > 0
? google.reviews.map((r) => ({
quote: r.text,
author: r.author,
rating: r.rating,
authorUrl: r.authorUrl,
}))
: t.home.reviews.testimonials.map((r) => ({ ...r, rating: 5, authorUrl: null }));
return (
<section className="relative overflow-hidden py-28 bg-forest text-cream">
<HoneycombPattern className="text-honey-light/[0.07]" size={30} />
<Container className="relative">
<ScrollReveal>
<SectionHeading
title={t.home.reviews.title}
align="center"
className="mx-auto"
tone="inverted"
/>
{google?.rating ? (
<p className="mt-4 text-center text-cream/60 text-sm">
{t.home.reviews.googleReviewsCount(google.rating.toFixed(1), google.totalCount ?? 0)}
</p>
) : null}
</ScrollReveal>
<div className="mt-14 grid md:grid-cols-3 gap-10">
{items.slice(0, 6).map((review, i) => (
<ScrollReveal key={`${review.author}-${i}`} delay={i * 0.08}>
{/* Gezeichnet statt als Schriftzeichen: ein grosses "„" sitzt auf
der Grundlinie und ragte mit enger Zeilenhoehe in die Sterne. */}
<svg
viewBox="0 0 40 28"
className="mb-4 h-7 w-10 text-honey-light/70"
fill="currentColor"
aria-hidden="true"
>
<path d="M8.5 2a7.5 7.5 0 0 1 7.5 7.5c0 7.4-4.6 13.9-11.6 16.5l-1-2.2c4.2-2.1 6.7-5.5 7.1-9.3A7.5 7.5 0 1 1 8.5 2z" />
<path d="M30 2a7.5 7.5 0 0 1 7.5 7.5c0 7.4-4.6 13.9-11.6 16.5l-1-2.2c4.2-2.1 6.7-5.5 7.1-9.3A7.5 7.5 0 1 1 30 2z" />
</svg>
<StarRating value={review.rating} />
<span className="sr-only">{t.home.reviews.starsOutOf5(review.rating)}</span>
<p className="mt-4 font-serif text-xl text-cream/90 leading-relaxed italic">
{review.quote}
</p>
{/* Zuordnung zur Quelle: die Nutzungsbedingungen der Places API
verlangen, dass eine angezeigte Rezension zu Google
zurueckfuehrt. Bei den eigenen Beispiel-Stimmen gibt es
nichts zu verlinken — dort bleibt es beim reinen Namen. */}
{review.authorUrl ? (
<a
href={review.authorUrl}
target="_blank"
rel="noopener noreferrer nofollow"
className="mt-4 inline-block text-sm text-cream/70 underline decoration-cream/30 hover:decoration-cream"
>
{review.author}
</a>
) : (
<p className="mt-4 text-sm text-cream/70">{review.author}</p>
)}
</ScrollReveal>
))}
</div>
{mapsUrl ? (
<p className="mt-14 text-center">
<a href={mapsUrl} target="_blank" rel="noopener noreferrer" className="text-sm text-cream/70 underline">
{t.home.reviews.seeAllOnGoogle}
</a>
</p>
) : null}
</Container>
</section>
);
}