Jede zweite Ueberschrift trug eine kleine Versalien-Zeile mit Wabenpunkt
("• STIMMEN", "• AKTUELL VERFUEGBAR", "• KUNDENKONTO" ...). Auf 29 Seiten
und Abschnitten wiederholt las sich das wie eine Vorlage. SectionHeading
rendert sie nicht mehr, die Prop ist entfernt; die Ueberschrift traegt den
Abschnitt allein.
Die eyebrow-Texte, die weiterhin als Seitentitel, Bildbeschriftung oder
Breadcrumb dienen, bleiben; 18 danach ungenutzte Eintraege sind aus Typ und
beiden Woerterbuechern entfernt.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
204 lines
7.6 KiB
TypeScript
204 lines
7.6 KiB
TypeScript
import { Container, SectionHeading, LinkButton, FramedMedia } from "@/components/ui";
|
|
import { HoneycombPattern, HexMark } from "@/components/honeycomb";
|
|
import { ScrollReveal } from "@/components/scroll-reveal";
|
|
import { ContentImage } from "@/components/content-image";
|
|
import { ProductCard, TasteScale, AddToCartButton } from "@/components/product-card";
|
|
import { formatPrice, formatDate } from "@/lib/format";
|
|
import { getLocale } from "@/i18n/locale";
|
|
import { getDictionary } from "@/i18n/get-dictionary";
|
|
import { getContentValues, parseListSimple, parseListTitled } from "@/lib/content";
|
|
import type { ProductWithRelations } from "@/lib/data";
|
|
|
|
export async function CurrentHarvest({
|
|
products,
|
|
reviewSummaries,
|
|
}: {
|
|
products: ProductWithRelations[];
|
|
reviewSummaries?: Map<string, { average: number | null; count: number }>;
|
|
}) {
|
|
const t = getDictionary(await getLocale());
|
|
return (
|
|
<section className="py-24">
|
|
<Container>
|
|
<ScrollReveal>
|
|
<SectionHeading
|
|
title={t.home.currentHarvest.title}
|
|
subtitle={t.home.currentHarvest.subtitle}
|
|
/>
|
|
</ScrollReveal>
|
|
<div className="mt-14 grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-x-8 gap-y-14">
|
|
{products.map((product, index) => (
|
|
<ProductCard
|
|
key={product.id}
|
|
product={product}
|
|
reviewSummary={reviewSummaries?.get(product.id)}
|
|
index={index}
|
|
/>
|
|
))}
|
|
</div>
|
|
</Container>
|
|
</section>
|
|
);
|
|
}
|
|
|
|
export async function OriginStory() {
|
|
const locale = await getLocale();
|
|
const t = getDictionary(locale);
|
|
const content = await getContentValues(
|
|
[
|
|
"home.originStory.paragraph1",
|
|
"home.originStory.paragraph2",
|
|
"home.originStory.facts",
|
|
"home.originStory.image",
|
|
],
|
|
locale
|
|
);
|
|
const facts = parseListSimple(content["home.originStory.facts"]);
|
|
return (
|
|
<section className="py-28 bg-cream/60">
|
|
<Container className="grid md:grid-cols-2 gap-16 lg:gap-24 items-center">
|
|
<ScrollReveal>
|
|
<FramedMedia>
|
|
<ContentImage
|
|
src={content["home.originStory.image"] || null}
|
|
alt=""
|
|
tone="forest"
|
|
className="aspect-[4/5] w-full"
|
|
label={t.home.originStory.eyebrow}
|
|
sizes="(min-width: 768px) 50vw, 100vw"
|
|
/>
|
|
</FramedMedia>
|
|
</ScrollReveal>
|
|
<ScrollReveal delay={0.1}>
|
|
<SectionHeading
|
|
title={t.home.originStory.title}
|
|
/>
|
|
<p className="mt-6 text-ink/75 leading-relaxed">{content["home.originStory.paragraph1"]}</p>
|
|
<p className="mt-4 text-ink/75 leading-relaxed">{content["home.originStory.paragraph2"]}</p>
|
|
<ul className="mt-8 grid gap-2.5 text-sm text-ink/75 sm:grid-cols-2">
|
|
{facts.map((fact) => (
|
|
<li key={fact} className="flex items-start gap-2.5">
|
|
<HexMark className="mt-1.5 h-2 w-2 flex-shrink-0 text-honey" />
|
|
{fact}
|
|
</li>
|
|
))}
|
|
</ul>
|
|
<LinkButton href="/herkunft" variant="secondary" className="mt-8">
|
|
{t.home.originStory.cta}
|
|
</LinkButton>
|
|
</ScrollReveal>
|
|
</Container>
|
|
</section>
|
|
);
|
|
}
|
|
|
|
export async function Philosophy() {
|
|
const locale = await getLocale();
|
|
const t = getDictionary(locale);
|
|
const content = await getContentValues(["home.philosophy.items"], locale);
|
|
const items = parseListTitled(content["home.philosophy.items"]);
|
|
return (
|
|
<section className="py-24">
|
|
<Container>
|
|
<ScrollReveal>
|
|
<SectionHeading
|
|
title={t.home.philosophy.title}
|
|
align="center"
|
|
className="mx-auto"
|
|
/>
|
|
</ScrollReveal>
|
|
<div className="mt-16 grid sm:grid-cols-2 lg:grid-cols-4 gap-x-8 gap-y-14">
|
|
{items.map((item, i) => (
|
|
<ScrollReveal key={item.title} delay={i * 0.08}>
|
|
<div className={`border-t border-ink/15 pt-6 ${i % 2 === 1 ? "lg:mt-10" : ""}`}>
|
|
{/* Wabe statt "01"-Nummer: die vier Punkte sind gleichrangig, keine Reihenfolge. */}
|
|
<HexMark filled={i % 2 === 0} className="h-5 w-5 text-honey" />
|
|
<h3 className="mt-4 text-2xl text-forest">{item.title}</h3>
|
|
<p className="mt-3 text-sm text-ink/70 leading-relaxed">
|
|
{item.text}
|
|
</p>
|
|
</div>
|
|
</ScrollReveal>
|
|
))}
|
|
</div>
|
|
</Container>
|
|
</section>
|
|
);
|
|
}
|
|
|
|
type HoneySpotlightProduct = ProductWithRelations & {
|
|
harvest: NonNullable<ProductWithRelations["harvest"]>;
|
|
batch: NonNullable<ProductWithRelations["batch"]>;
|
|
};
|
|
|
|
export async function ProductSpotlight({ product }: { product: HoneySpotlightProduct }) {
|
|
const locale = await getLocale();
|
|
const t = getDictionary(locale);
|
|
return (
|
|
<section className="relative overflow-hidden py-28 bg-forest text-cream">
|
|
<HoneycombPattern className="text-honey-light/[0.08]" size={28} fade="right" />
|
|
<Container className="relative grid md:grid-cols-2 gap-14 lg:gap-24 items-center">
|
|
<ScrollReveal>
|
|
<ContentImage
|
|
src={product.images[0] ?? null}
|
|
alt={product.name}
|
|
tone="honey"
|
|
className="aspect-square w-full"
|
|
label={product.name}
|
|
sizes="(min-width: 768px) 50vw, 100vw"
|
|
/>
|
|
</ScrollReveal>
|
|
<ScrollReveal delay={0.1}>
|
|
<p className="tracking-wide-caps text-xs text-honey-light mb-3">
|
|
{product.harvest.type} {product.harvest.year}
|
|
</p>
|
|
<h2 className="text-5xl md:text-6xl leading-[1] tracking-[-0.015em]">{product.name}</h2>
|
|
<p className="mt-4 text-cream/75 leading-relaxed">
|
|
{t.home.productSpotlight.description}
|
|
</p>
|
|
|
|
<div className="mt-8 space-y-3">
|
|
<TasteScale label={t.product.tasteMild} value={product.intensityMild} tone="inverted" />
|
|
<TasteScale label={t.product.tasteFloral} value={product.intensityFloral} tone="inverted" />
|
|
<TasteScale label={t.product.tasteIntense} value={product.intensityKraeftig} tone="inverted" />
|
|
</div>
|
|
<p className="mt-4 text-sm text-cream/60">
|
|
{t.home.productSpotlight.consistency}: {product.consistency}
|
|
</p>
|
|
|
|
<dl className="mt-8 grid grid-cols-2 gap-4 text-sm text-cream/70">
|
|
<div>
|
|
<dt className="text-cream/65">{t.home.productSpotlight.harvest}</dt>
|
|
<dd>{formatDate(product.harvest.harvestDate, locale)}</dd>
|
|
</div>
|
|
<div>
|
|
<dt className="text-cream/65">{t.home.productSpotlight.batch}</dt>
|
|
<dd>{product.batch.code}</dd>
|
|
</div>
|
|
<div>
|
|
<dt className="text-cream/65">{t.home.productSpotlight.weight}</dt>
|
|
<dd>{product.weightGrams} g</dd>
|
|
</div>
|
|
<div>
|
|
<dt className="text-cream/65">{t.home.productSpotlight.price}</dt>
|
|
<dd className="font-serif text-2xl text-cream tabular-nums">
|
|
{formatPrice(product.priceCents, product.currency, locale)}
|
|
</dd>
|
|
</div>
|
|
</dl>
|
|
|
|
<div className="mt-10 flex flex-wrap gap-4">
|
|
<LinkButton
|
|
href={`/honig/${product.slug}`}
|
|
variant="inverted"
|
|
>
|
|
{t.home.productSpotlight.cta}
|
|
</LinkButton>
|
|
<AddToCartButton product={product} variant="accent" />
|
|
</div>
|
|
</ScrollReveal>
|
|
</Container>
|
|
</section>
|
|
);
|
|
}
|