diff --git a/src/components/content-image.tsx b/src/components/content-image.tsx
index d35b876..5f528fa 100644
--- a/src/components/content-image.tsx
+++ b/src/components/content-image.tsx
@@ -14,6 +14,12 @@ interface ContentImageProps {
* true setzen — laedt das Bild mit hoher Prioritaet ohne Lazy-Loading,
* statt es erst beim Scannen weiter unten im Dokument zu entdecken. */
priority?: boolean;
+ /** Leicht abgerundete Ecken. Standard true — passt fuer Karten, Teaser
+ * und Inhaltsbilder, die als eigene Flaeche auf dem Seitenhintergrund
+ * liegen. Fuer randlose Flaechen (Hero ueber die volle Fensterbreite)
+ * bewusst false: dort saesse die Rundung direkt an der Fensterkante und
+ * wirkte wie ein Darstellungsfehler. */
+ rounded?: boolean;
}
/**
@@ -34,9 +40,16 @@ export function ContentImage({
label,
sizes = "100vw",
priority = false,
+ rounded = true,
}: ContentImageProps) {
+ // Dieselbe Rundung fuer Platzhalter UND echtes Bild — sonst spraenge die
+ // Form in dem Moment um, in dem ein Bild gepflegt wird.
+ const roundedClass = rounded ? "rounded-lg" : "";
+
if (!src) {
- return ;
+ return (
+
+ );
}
// "label" traegt bereits eine kurze inhaltliche Einordnung (Ernte-Jahr/Typ,
// Kategorie, Ort — siehe Aufrufer), die bisher nur auf der Platzhalterkachel
@@ -45,8 +58,22 @@ export function ContentImage({
// Bildersuche als der Produkt-/Ernteame allein.
const descriptiveAlt = label ? `${alt} – ${label}` : alt;
return (
-
-
+
+
, setzt
+ // am
selbst aber weder fetchpriority noch loading. Gemessen kam
+ // das Hero-Bild damit trotz "priority" mit niedriger Netzwerk-
+ // Prioritaet an. Die Dokumentation nennt fuer diesen Fall
+ // ausdruecklich loading="eager" und fetchPriority="high" als Ersatz.
+ loading={priority ? "eager" : "lazy"}
+ fetchPriority={priority ? "high" : "auto"}
+ className="object-cover"
+ />
);
}
diff --git a/src/components/hero.tsx b/src/components/hero.tsx
index 5c0d6ea..459a223 100644
--- a/src/components/hero.tsx
+++ b/src/components/hero.tsx
@@ -5,11 +5,6 @@ import { Container, LinkButton } from "@/components/ui";
import { ContentImage } from "@/components/content-image";
import { useI18n } from "@/i18n/i18n-provider";
-const sectionVariants: Variants = {
- hidden: { opacity: 0 },
- visible: { opacity: 1, transition: { duration: 0.6 } },
-};
-
const itemContainerVariants: Variants = {
hidden: {},
visible: { transition: { delayChildren: 0.3, staggerChildren: 0.12 } },
@@ -41,17 +36,38 @@ export function Hero({
const shouldReduceMotion = useReducedMotion();
return (
-
+ // Bewusst KEINE Einblend-Animation auf der aeussersten Ebene: eine
+ // Section, die bei opacity 0 startet, wird erst sichtbar, wenn das
+ // JS-Bundle geladen und die Animation gelaufen ist. Das Hero-Bild ist
+ // aber das groesste Element im ersten Viewport (LCP) — es so lange
+ // zurueckzuhalten verschiebt den gemessenen Ladezeitpunkt der Seite um
+ // die gesamte Hydrationsdauer. Der gestaffelte Aufbau der Textspalte
+ // weiter unten bleibt unveraendert, der laeuft unabhaengig davon.
+
{video ? (
+ {/* Standbild als eigene Ebene UNTER dem Video statt im
+ poster-Attribut. poster nimmt nur eine fertige URL und laedt die
+ hochgeladene Datei damit in voller gespeicherter Groesse (bis
+ MAX_IMAGE_DIMENSION, siehe lib/uploads.ts) — am Bildoptimierer
+ vorbei und ohne Ruecksicht auf das Geraet. Als ContentImage
+ laeuft dasselbe Bild durch next/image: passende Breite je
+ Bildschirm, WebP statt JPEG, und mit "priority" als erstes
+ angefordert. Das Video liegt darueber und ist transparent, bis es
+ eigene Bilddaten hat — der Uebergang ist deshalb nicht sichtbar. */}
+
+
+
)}
@@ -124,6 +146,6 @@ export function Hero({
-
+
);
}
diff --git a/src/components/product-gallery.tsx b/src/components/product-gallery.tsx
index b7ce985..0e0ccff 100644
--- a/src/components/product-gallery.tsx
+++ b/src/components/product-gallery.tsx
@@ -63,7 +63,7 @@ export function ProductGallery({ images, productName, description }: ProductGall
return (
@@ -72,14 +72,18 @@ export function ProductGallery({ images, productName, description }: ProductGall
// /uploads/... (siehe uploads.ts/isValidUploadPath) — next/image
// braucht dafuer KEINEN remotePatterns-Eintrag (nur externe
// Bildquellen brauchen den), siehe auch content-image.tsx. Als
- // wahrscheinlichstes LCP-Element der Produktseite mit "priority"
- // geladen, statt erst beim Scannen des Bild-Tags entdeckt zu werden.
+ // wahrscheinlichstes LCP-Element der Produktseite sofort und mit
+ // hoher Netzwerk-Prioritaet geladen, statt erst beim Scannen des
+ // Bild-Tags entdeckt zu werden. Bewusst NICHT ueber die Prop
+ // "priority": die ist seit Next.js 16 abgekuendigt und erzeugt nur
+ // noch einen Preload-Link, ohne fetchpriority am
![]()
zu setzen.
) : (
@@ -95,7 +99,7 @@ export function ProductGallery({ images, productName, description }: ProductGall
onClick={() => setActiveIndex(index)}
aria-label={`${isImageSlide(slide) ? `Produktbild ${index + 1}` : slide.label} – Bild ${index + 1} von ${slides.length} anzeigen`}
aria-current={index === activeIndex}
- className={`relative aspect-square overflow-hidden border-2 transition-colors ${
+ className={`relative aspect-square overflow-hidden rounded-lg border-2 transition-colors ${
index === activeIndex ? "border-forest" : "border-transparent hover:border-ink/20"
}`}
>