feat(ui): Seitenabschluesse, Wissens-Verzeichnis, Kopfzeile ohne Ueberlauf
- Imkerei, Herkunft und Ernten endeten mit dem letzten Absatz direkt vor der Fusszeile. Neuer PageOutro: zwei naechste Wege als grosse Serif-Links mit Kurztext (Ernten/Shop bzw. Herkunft/Shop). - Kopfzeile: bei 1280 px lief sie 54 px ueber (horizontale Scrollleiste), weil die nicht umbrechbare Slogan-Zeile unter dem Logo schon ab xl erschien. Jetzt erst ab 2xl; geprueft 1024 bis 1920 px ohne Ueberlauf. - Wissen: Inhaltsverzeichnis statt zehn gleicher Bloecke mit je einem "Weiterlesen" - ganze Zeile ist der Link, Titel und Kurztext nebeneinander. Ungenutzter Schluessel readMore entfernt. - Produktseite: bei ein, zwei Empfehlungen steht die Ueberschrift als erste Rasterspalte statt ueber einer einzelnen verlorenen Karte. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
1 parent
908afebacb
commit
b588975317
10 files changed
+118
-20
No files matched your search
@@ -8,6 +8,7 @@ import { getHarvests } from "@/lib/data";
|
|||||||
import { formatMonthYear } from "@/lib/format";
|
import { formatMonthYear } from "@/lib/format";
|
||||||
import { getLocale } from "@/i18n/locale";
|
import { getLocale } from "@/i18n/locale";
|
||||||
import { getDictionary } from "@/i18n/get-dictionary";
|
import { getDictionary } from "@/i18n/get-dictionary";
|
||||||
|
import { PageOutro } from "@/components/page-outro";
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "Unsere Ernten",
|
title: "Unsere Ernten",
|
||||||
@@ -96,6 +97,13 @@ export default async function ErntenPage() {
|
|||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</Container>
|
</Container>
|
||||||
|
<PageOutro
|
||||||
|
ariaLabel={t.pageOutro.ariaLabel}
|
||||||
|
links={[
|
||||||
|
{ href: "/herkunft", label: t.nav.origin, text: t.pageOutro.originText },
|
||||||
|
{ href: "/shop", label: t.nav.shop, text: t.pageOutro.shopText },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -6,6 +6,7 @@ import { ScrollReveal } from "@/components/scroll-reveal";
|
|||||||
import { getLocale } from "@/i18n/locale";
|
import { getLocale } from "@/i18n/locale";
|
||||||
import { getDictionary } from "@/i18n/get-dictionary";
|
import { getDictionary } from "@/i18n/get-dictionary";
|
||||||
import { getContentValue } from "@/lib/content";
|
import { getContentValue } from "@/lib/content";
|
||||||
|
import { PageOutro } from "@/components/page-outro";
|
||||||
|
|
||||||
export async function generateMetadata(): Promise<Metadata> {
|
export async function generateMetadata(): Promise<Metadata> {
|
||||||
const t = getDictionary(await getLocale());
|
const t = getDictionary(await getLocale());
|
||||||
@@ -72,6 +73,13 @@ export default async function HerkunftPage() {
|
|||||||
{t.herkunftPage.privacyNote}
|
{t.herkunftPage.privacyNote}
|
||||||
</p>
|
</p>
|
||||||
</Container>
|
</Container>
|
||||||
|
<PageOutro
|
||||||
|
ariaLabel={t.pageOutro.ariaLabel}
|
||||||
|
links={[
|
||||||
|
{ href: "/ernten", label: t.nav.harvests, text: t.pageOutro.harvestsText },
|
||||||
|
{ href: "/shop", label: t.nav.shop, text: t.pageOutro.shopText },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -525,15 +525,18 @@ export default async function ProductDetailPage(
|
|||||||
</Container>
|
</Container>
|
||||||
|
|
||||||
{relatedProducts.length > 0 ? (
|
{relatedProducts.length > 0 ? (
|
||||||
<Container className="mt-20">
|
// Bei ein, zwei Empfehlungen steht die Ueberschrift als erste Spalte im
|
||||||
<ScrollReveal>
|
// Raster (wie "Aktuelle Ernte" auf der Startseite) — sonst stuende eine
|
||||||
<h2 className="text-3xl md:text-4xl text-forest mb-10">{t.productPage.relatedProductsHeading}</h2>
|
// einzelne Karte verloren unter einer breiten Ueberschrift.
|
||||||
|
<Container className="mt-20 grid grid-cols-1 gap-x-8 gap-y-14 sm:grid-cols-2 lg:grid-cols-3">
|
||||||
|
<ScrollReveal
|
||||||
|
className={relatedProducts.length <= 2 ? "sm:col-span-2 lg:col-span-1" : "sm:col-span-2 lg:col-span-3"}
|
||||||
|
>
|
||||||
|
<h2 className="text-3xl md:text-4xl text-forest text-balance">{t.productPage.relatedProductsHeading}</h2>
|
||||||
</ScrollReveal>
|
</ScrollReveal>
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-x-8 gap-y-14">
|
|
||||||
{relatedProducts.map((related, index) => (
|
{relatedProducts.map((related, index) => (
|
||||||
<ProductCard key={related.id} product={related} index={index} />
|
<ProductCard key={related.id} product={related} index={index} />
|
||||||
))}
|
))}
|
||||||
</div>
|
|
||||||
</Container>
|
</Container>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { ScrollReveal } from "@/components/scroll-reveal";
|
|||||||
import { getLocale } from "@/i18n/locale";
|
import { getLocale } from "@/i18n/locale";
|
||||||
import { getDictionary } from "@/i18n/get-dictionary";
|
import { getDictionary } from "@/i18n/get-dictionary";
|
||||||
import { getContentValue } from "@/lib/content";
|
import { getContentValue } from "@/lib/content";
|
||||||
|
import { PageOutro } from "@/components/page-outro";
|
||||||
|
|
||||||
export async function generateMetadata(): Promise<Metadata> {
|
export async function generateMetadata(): Promise<Metadata> {
|
||||||
const t = getDictionary(await getLocale());
|
const t = getDictionary(await getLocale());
|
||||||
@@ -55,6 +56,13 @@ export default async function ImkereiPage() {
|
|||||||
/>
|
/>
|
||||||
</FramedMedia>
|
</FramedMedia>
|
||||||
</Container>
|
</Container>
|
||||||
|
<PageOutro
|
||||||
|
ariaLabel={t.pageOutro.ariaLabel}
|
||||||
|
links={[
|
||||||
|
{ href: "/ernten", label: t.nav.harvests, text: t.pageOutro.harvestsText },
|
||||||
|
{ href: "/shop", label: t.nav.shop, text: t.pageOutro.shopText },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
+18
-11
@@ -19,36 +19,43 @@ export default async function WissenPage() {
|
|||||||
const t = getDictionary(await getLocale());
|
const t = getDictionary(await getLocale());
|
||||||
return (
|
return (
|
||||||
<div className="py-20">
|
<div className="py-20">
|
||||||
<Container className="max-w-3xl">
|
<Container className="max-w-5xl">
|
||||||
<ScrollReveal>
|
<ScrollReveal>
|
||||||
<SectionHeading level="h1" title={t.wissenListPage.title} />
|
<SectionHeading level="h1" title={t.wissenListPage.title} />
|
||||||
</ScrollReveal>
|
</ScrollReveal>
|
||||||
<div className="mt-14 divide-y divide-ink/10 border-y border-ink/10">
|
{/* Inhaltsverzeichnis statt zehn gleicher Bloecke mit je einem
|
||||||
|
"Weiterlesen": die ganze Zeile ist der Link, Titel und Kurztext
|
||||||
|
stehen nebeneinander, der Pfeil zeigt die Richtung. */}
|
||||||
|
<ol className="mt-14 divide-y divide-ink/10 border-y border-ink/10">
|
||||||
{wissenSlugs.map((slug, i) => {
|
{wissenSlugs.map((slug, i) => {
|
||||||
const article = t.wissenArticles[slug];
|
const article = t.wissenArticles[slug];
|
||||||
return (
|
return (
|
||||||
<ScrollReveal key={slug} delay={(i % 5) * 0.06}>
|
<li key={slug}>
|
||||||
<Link href={`/wissen/${slug}`} className="group block py-9">
|
<ScrollReveal delay={(i % 5) * 0.05}>
|
||||||
<h2 className="link-draw inline pb-0.5 text-3xl leading-tight text-forest">{article.title}</h2>
|
<Link
|
||||||
<p className="mt-3 max-w-[60ch] text-ink/70 leading-relaxed">{article.excerpt}</p>
|
href={`/wissen/${slug}`}
|
||||||
<span className="mt-4 inline-flex items-center gap-2 text-sm text-honey-dark tracking-wide-caps">
|
className="group grid gap-3 py-8 md:grid-cols-12 md:items-baseline md:gap-8"
|
||||||
{t.wissenListPage.readMore}
|
>
|
||||||
|
<h2 className="text-2xl leading-tight text-forest text-balance md:col-span-5 md:text-3xl">
|
||||||
|
<span className="link-draw pb-0.5">{article.title}</span>
|
||||||
|
</h2>
|
||||||
|
<p className="max-w-[60ch] text-ink/70 leading-relaxed md:col-span-6">{article.excerpt}</p>
|
||||||
<svg
|
<svg
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
className="h-3.5 w-3.5 transition-transform duration-300 ease-[var(--ease-arrive)] group-hover:translate-x-1"
|
className="hidden h-5 w-5 justify-self-end text-honey-dark transition-transform duration-300 ease-[var(--ease-arrive)] group-hover:translate-x-1 motion-reduce:transition-none md:col-span-1 md:block"
|
||||||
fill="none"
|
fill="none"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
strokeWidth="1.6"
|
strokeWidth="1.6"
|
||||||
>
|
>
|
||||||
<path d="M4 12h15M13 6l6 6-6 6" strokeLinecap="round" strokeLinejoin="round" />
|
<path d="M4 12h15M13 6l6 6-6 6" strokeLinecap="round" strokeLinejoin="round" />
|
||||||
</svg>
|
</svg>
|
||||||
</span>
|
|
||||||
</Link>
|
</Link>
|
||||||
</ScrollReveal>
|
</ScrollReveal>
|
||||||
|
</li>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</ol>
|
||||||
</Container>
|
</Container>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
import Link from "next/link";
|
||||||
|
import { Container } from "@/components/ui";
|
||||||
|
|
||||||
|
export interface PageOutroLink {
|
||||||
|
href: string;
|
||||||
|
label: string;
|
||||||
|
text: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abschluss einer Inhaltsseite (Imkerei, Herkunft, Ernten): zwei naechste
|
||||||
|
* Wege als grosse Serif-Links, getrennt durch eine Linie. Ohne ihn endeten
|
||||||
|
* diese Seiten mit dem letzten Absatz direkt vor der Fusszeile — eine
|
||||||
|
* Sackgasse, gerade fuer Besuch, der ueber die Suche mitten einsteigt.
|
||||||
|
*/
|
||||||
|
export function PageOutro({ links, ariaLabel }: { links: PageOutroLink[]; ariaLabel: string }) {
|
||||||
|
return (
|
||||||
|
<nav aria-label={ariaLabel} className="mt-24 border-t border-ink/10">
|
||||||
|
<Container className="grid md:grid-cols-2">
|
||||||
|
{links.map((link, i) => (
|
||||||
|
<Link
|
||||||
|
key={link.href}
|
||||||
|
href={link.href}
|
||||||
|
className={`group flex items-end justify-between gap-6 py-12 md:py-16 ${
|
||||||
|
i === 0 ? "md:pr-12" : "border-t border-ink/10 md:border-l md:border-t-0 md:pl-12"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
<span className="block font-serif text-4xl leading-tight text-forest md:text-5xl">
|
||||||
|
{link.label}
|
||||||
|
</span>
|
||||||
|
<span className="mt-3 block max-w-[40ch] text-sm leading-relaxed text-ink/65">{link.text}</span>
|
||||||
|
</span>
|
||||||
|
<svg
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="1.5"
|
||||||
|
aria-hidden="true"
|
||||||
|
className="mb-2 h-6 w-6 flex-shrink-0 text-honey-dark transition-transform duration-500 ease-[var(--ease-arrive)] group-hover:translate-x-1.5 motion-reduce:transition-none"
|
||||||
|
>
|
||||||
|
<path d="M4 12h15M13 6l6 6-6 6" strokeLinecap="round" strokeLinejoin="round" />
|
||||||
|
</svg>
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</Container>
|
||||||
|
</nav>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -84,7 +84,7 @@ function Logo({ t, companyName, logoSrc }: { t: Dictionary; companyName: string;
|
|||||||
<span className="text-2xl tracking-[0.2em] text-forest font-serif">
|
<span className="text-2xl tracking-[0.2em] text-forest font-serif">
|
||||||
{companyName}
|
{companyName}
|
||||||
</span>
|
</span>
|
||||||
<span className="hidden xl:block text-[10px] tracking-[0.3em] uppercase text-honey-dark mt-1 whitespace-nowrap">
|
<span className="hidden 2xl:block text-[10px] tracking-[0.3em] uppercase text-honey-dark mt-1 whitespace-nowrap">
|
||||||
{t.footer.tagline}
|
{t.footer.tagline}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -14,6 +14,12 @@ export const de: Dictionary = {
|
|||||||
retry: "Erneut versuchen",
|
retry: "Erneut versuchen",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
pageOutro: {
|
||||||
|
ariaLabel: "Weiterlesen",
|
||||||
|
shopText: "Honig, Bienenwachskerzen und Propolis aus eigener Imkerei — solange die Charge reicht.",
|
||||||
|
harvestsText: "Jede Ernte mit Datum, Standort und Charge, zurückverfolgbar bis ins Glas.",
|
||||||
|
originText: "Wo unsere Bienenvölker stehen und was die Landschaft um Altenmünster prägt.",
|
||||||
|
},
|
||||||
nav: {
|
nav: {
|
||||||
shop: "Shop",
|
shop: "Shop",
|
||||||
harvests: "Unsere Ernten",
|
harvests: "Unsere Ernten",
|
||||||
@@ -476,7 +482,6 @@ export const de: Dictionary = {
|
|||||||
wissenListPage: {
|
wissenListPage: {
|
||||||
eyebrow: "Honigwissen",
|
eyebrow: "Honigwissen",
|
||||||
title: "Wissenswertes rund um Honig",
|
title: "Wissenswertes rund um Honig",
|
||||||
readMore: "Weiterlesen",
|
|
||||||
},
|
},
|
||||||
wissenArticlePage: {
|
wissenArticlePage: {
|
||||||
backLabel: "Zurück zum Honigwissen",
|
backLabel: "Zurück zum Honigwissen",
|
||||||
|
|||||||
@@ -14,6 +14,12 @@ export const en: Dictionary = {
|
|||||||
retry: "Try again",
|
retry: "Try again",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
pageOutro: {
|
||||||
|
ariaLabel: "Keep reading",
|
||||||
|
shopText: "Honey, beeswax candles and propolis from our own apiary — while each batch lasts.",
|
||||||
|
harvestsText: "Every harvest with date, location and batch, traceable down to the jar.",
|
||||||
|
originText: "Where our colonies live and what shapes the landscape around Altenmünster.",
|
||||||
|
},
|
||||||
nav: {
|
nav: {
|
||||||
shop: "Shop",
|
shop: "Shop",
|
||||||
harvests: "Our Harvests",
|
harvests: "Our Harvests",
|
||||||
@@ -464,7 +470,6 @@ export const en: Dictionary = {
|
|||||||
wissenListPage: {
|
wissenListPage: {
|
||||||
eyebrow: "Honey Knowledge",
|
eyebrow: "Honey Knowledge",
|
||||||
title: "Good to know about honey",
|
title: "Good to know about honey",
|
||||||
readMore: "Read more",
|
|
||||||
},
|
},
|
||||||
wissenArticlePage: {
|
wissenArticlePage: {
|
||||||
backLabel: "Back to Honey Knowledge",
|
backLabel: "Back to Honey Knowledge",
|
||||||
|
|||||||
@@ -52,6 +52,12 @@ export interface Dictionary {
|
|||||||
retry: string;
|
retry: string;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
pageOutro: {
|
||||||
|
ariaLabel: string;
|
||||||
|
shopText: string;
|
||||||
|
harvestsText: string;
|
||||||
|
originText: string;
|
||||||
|
};
|
||||||
nav: {
|
nav: {
|
||||||
shop: string;
|
shop: string;
|
||||||
harvests: string;
|
harvests: string;
|
||||||
@@ -249,7 +255,6 @@ export interface Dictionary {
|
|||||||
wissenListPage: {
|
wissenListPage: {
|
||||||
eyebrow: string;
|
eyebrow: string;
|
||||||
title: string;
|
title: string;
|
||||||
readMore: string;
|
|
||||||
};
|
};
|
||||||
wissenArticlePage: {
|
wissenArticlePage: {
|
||||||
backLabel: string;
|
backLabel: string;
|
||||||
|
|||||||
Reference in new issue
Block a user