- Hero: deutlich groessere Headline, langsam atmendes Bild, Wabenraster, Honigtropfen als Scroll-Hinweis, Hauptaktion in Honig - "Sechs Schritte" als Weg: eine Honiglinie fuellt sich beim Scrollen und laesst die Waben der Schritte nacheinander aufleuchten (waagerecht ab lg, senkrecht auf dem Handy; reduzierte Bewegung: sofort gefuellt) - Philosophie mit Waben statt Nummern, Verantwortung als geteiltes Layout mit mitlaufender Ueberschrift - Bilder mit versetztem Honigrahmen, Wabenraster auf dunklen Flaechen, grosse Anfuehrungszeichen bei den Kundenstimmen - Knopf-Varianten "inverted"/"accent" fuer dunkle Flaechen ersetzen !-Overrides, die den Fuell-Effekt ausgehebelt haetten - Fix: Ueberschrift der Kundenstimmen war Waldgruen auf Waldgruen Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
207 lines
7.7 KiB
TypeScript
207 lines
7.7 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
|
|
eyebrow={t.home.currentHarvest.eyebrow}
|
|
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
|
|
eyebrow={t.home.originStory.eyebrow}
|
|
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
|
|
eyebrow={t.home.philosophy.eyebrow}
|
|
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>
|
|
);
|
|
}
|