diff --git a/src/app/globals.css b/src/app/globals.css
index 54b5eb8..ef4cc0f 100644
--- a/src/app/globals.css
+++ b/src/app/globals.css
@@ -305,6 +305,49 @@ textarea::placeholder {
}
}
+/* Biene (components/bee.tsx): Fluegelschlag, leichtes Schweben im Flug und
+ die Runde um das Imker-Portraet auf der Startseite. */
+@keyframes wh-flutter {
+ from {
+ transform: scaleY(1);
+ }
+ to {
+ transform: scaleY(0.35);
+ }
+}
+
+@keyframes wh-hover {
+ 0%,
+ 100% {
+ transform: translateY(0);
+ }
+ 50% {
+ transform: translateY(-5px);
+ }
+}
+
+@keyframes wh-orbit {
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+/* Punkte laufen entlang einer gestrichelten Linie (Wabenring um das Portraet). */
+@keyframes wh-march {
+ to {
+ stroke-dashoffset: -35;
+ }
+}
+
+@media (prefers-reduced-motion: reduce) {
+ .motion-flutter,
+ .motion-hover,
+ .motion-orbit,
+ .motion-march {
+ animation: none !important;
+ }
+}
+
/* ---------------------------------------------------------------------------
Admin-Arbeitsbereich (components/admin-shell.tsx): einheitliche Details
fuer alle Admin-Seiten zentral statt in jeder der rund 20 Seiten einzeln.
diff --git a/src/app/page.tsx b/src/app/page.tsx
index c2368cf..0b9e40d 100644
--- a/src/app/page.tsx
+++ b/src/app/page.tsx
@@ -1,6 +1,7 @@
import type { Metadata } from "next";
import { Hero } from "@/components/hero";
import {
+ BeekeeperNote,
CurrentHarvest,
OriginStory,
Philosophy,
@@ -57,6 +58,7 @@ export default async function HomePage() {
+
{spotlightProduct ? : null}
diff --git a/src/components/bee.tsx b/src/components/bee.tsx
new file mode 100644
index 0000000..4a54d0b
--- /dev/null
+++ b/src/components/bee.tsx
@@ -0,0 +1,129 @@
+"use client";
+
+import { useCallback, useEffect, useId, useRef } from "react";
+import { motion, useMotionValueEvent, useReducedMotion, useScroll, useSpring } from "motion/react";
+
+/**
+ * Gezeichnete Biene (Blick nach rechts) im Strich der uebrigen Marken-Grafik.
+ * Die Fluegel schlagen per CSS (wh-flutter); bei reduzierter Bewegung stehen
+ * sie still (.motion-flutter, globals.css).
+ */
+export function BeeGlyph({ className = "h-8 w-10" }: { className?: string }) {
+ const clipId = `bee-body-${useId().replace(/:/g, "")}`;
+ return (
+
+ );
+}
+
+// Flugbahn im Koordinatenraum 1000 x 200: kommt links herein, schlaegt zwei
+// Boegen und verlaesst den Abschnitt rechts oben.
+const FLIGHT_PATH = "M -40 150 C 120 40, 260 190, 430 100 S 700 10, 820 120 S 980 90, 1060 30";
+const VIEW_W = 1000;
+const VIEW_H = 200;
+// Wo die Biene bei reduzierter Bewegung ruhig sitzt (Anteil der Bahn).
+const RESTING_POINT = 0.62;
+
+/**
+ * Eine Biene fliegt beim Scrollen eine geschwungene Bahn ueber den Abschnitt
+ * und zieht eine gepunktete Honigspur hinter sich her. Rein dekorativ
+ * (aria-hidden, pointer-events-none). Bei reduzierter Bewegung sitzt sie
+ * still an einer Stelle, die Spur ist bis dorthin sichtbar.
+ */
+export function BeeFlight({ className = "" }: { className?: string }) {
+ const boxRef = useRef(null);
+ const pathRef = useRef(null);
+ const beeRef = useRef(null);
+ const maskId = `bee-trail-${useId().replace(/:/g, "")}`;
+ const reduceMotion = useReducedMotion();
+
+ const { scrollYProgress } = useScroll({ target: boxRef, offset: ["start end", "end start"] });
+ // Gefedert: die Biene folgt dem Scrollen mit etwas Traegheit, statt ruckartig zu springen.
+ const progress = useSpring(scrollYProgress, { stiffness: 55, damping: 18, mass: 0.7 });
+
+ const placeBee = useCallback((p: number) => {
+ const path = pathRef.current;
+ const bee = beeRef.current;
+ const box = boxRef.current;
+ if (!path || !bee || !box) return;
+ const length = path.getTotalLength();
+ const at = Math.min(1, Math.max(0, p)) * length;
+ const point = path.getPointAtLength(at);
+ const ahead = path.getPointAtLength(Math.min(length, at + 2));
+ const sx = box.clientWidth / VIEW_W;
+ const sy = box.clientHeight / VIEW_H;
+ const angle = (Math.atan2((ahead.y - point.y) * sy, (ahead.x - point.x) * sx) * 180) / Math.PI;
+ bee.style.transform = `translate(${point.x * sx}px, ${point.y * sy}px) translate(-50%, -50%) rotate(${angle}deg)`;
+ }, []);
+
+ useMotionValueEvent(progress, "change", (p) => {
+ if (!reduceMotion) placeBee(p);
+ });
+
+ useEffect(() => {
+ const place = () => placeBee(reduceMotion ? RESTING_POINT : progress.get());
+ place();
+ window.addEventListener("resize", place);
+ return () => window.removeEventListener("resize", place);
+ }, [placeBee, progress, reduceMotion]);
+
+ return (
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/components/home-sections-a.tsx b/src/components/home-sections-a.tsx
index 043b4bd..845a95e 100644
--- a/src/components/home-sections-a.tsx
+++ b/src/components/home-sections-a.tsx
@@ -2,6 +2,9 @@ import { Container, SectionHeading, LinkButton } from "@/components/ui";
import { HoneycombPattern, HexMark } from "@/components/honeycomb";
import { ScrollReveal } from "@/components/scroll-reveal";
import { ContentImage } from "@/components/content-image";
+import { BeeFlight, BeeGlyph } from "@/components/bee";
+import { ParallaxMedia } from "@/components/parallax-media";
+import { getCompanyInfo } from "@/lib/company-info";
import { ProductCard, TasteScale, AddToCartButton } from "@/components/product-card";
import { formatPrice, formatDate } from "@/lib/format";
import { getLocale } from "@/i18n/locale";
@@ -22,7 +25,11 @@ export async function CurrentHarvest({
// Dreierraster.
const isFew = products.length <= 2;
return (
-
+ // overflow-x-clip statt overflow-hidden: die Biene verlaesst den Abschnitt
+ // rechts, ohne eine Scrollleiste zu erzeugen — und die klebende
+ // Ueberschrift (sticky) funktioniert weiter.
+
+
@@ -68,15 +75,17 @@ export async function OriginStory() {
// Fensterrand, der Text steht daneben — kein Rahmen, keine Karte.
-
+
+
+
@@ -130,6 +139,81 @@ export async function Philosophy() {
);
}
+/**
+ * Das Gesicht hinter dem Honig: Portraet des Imkers in Wabenform, ein
+ * langsam drehender Wabenring und eine Biene, die das Bild umkreist.
+ * Daneben sein eigener Satz von der Imkerei-Seite — kein neuer Text, nur
+ * hierher geholt, damit die Leidenschaft schon auf der Startseite spricht.
+ */
+export async function BeekeeperNote() {
+ const locale = await getLocale();
+ const t = getDictionary(locale);
+ const [content, company] = await Promise.all([getContentValues(["imkerei.image"], locale), getCompanyInfo()]);
+ const quote = t.imkereiPage.paragraphs[0];
+ return (
+
+
+
+
+
+ {/* Wabenring deckungsgleich zum Portraet; die Punkte wandern
+ langsam an der Kante entlang, wie Bienen auf ihrem Weg. */}
+
+
+
+
+ {/* Die Biene umkreist das Portraet */}
+
+
+
+
+
+
+
+
+
+
+
+
+ „
+ {quote}
+ “
+
+
+
+
+ {t.home.regionalSection.ctaAbout}
+
+
+
+
+ );
+}
+
type HoneySpotlightProduct = ProductWithRelations & {
harvest: NonNullable;
batch: NonNullable;
diff --git a/src/components/home-sections-b.tsx b/src/components/home-sections-b.tsx
index 95351a6..a959f75 100644
--- a/src/components/home-sections-b.tsx
+++ b/src/components/home-sections-b.tsx
@@ -3,6 +3,7 @@ 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 { ParallaxMedia } from "@/components/parallax-media";
import { StarRating } from "@/components/star-rating";
import { fetchGoogleReviews } from "@/lib/google-reviews";
import { getLocale } from "@/i18n/locale";
@@ -47,14 +48,16 @@ export async function RegionalSection() {
{/* Breites Bild, der Text legt sich als Tafel darueber — Tiefe statt
eines weiteren Bild-neben-Text-Wechsels. */}
-
+
+
+
-
+
+
+
diff --git a/src/components/parallax-media.tsx b/src/components/parallax-media.tsx
new file mode 100644
index 0000000..4c0d37b
--- /dev/null
+++ b/src/components/parallax-media.tsx
@@ -0,0 +1,27 @@
+"use client";
+
+import { useRef, type ReactNode } from "react";
+import { motion, useReducedMotion, useScroll, useTransform } from "motion/react";
+
+/**
+ * Foto, das sich beim Scrollen leicht mitbewegt: es kommt etwas vergroessert
+ * herein, setzt sich beim Durchscrollen und wandert dabei ein wenig gegen die
+ * Scrollrichtung — die Aufnahme wirkt wie ein Blick durch ein Fenster statt
+ * wie ein aufgeklebtes Bild. Nur transform (scale/translate), kein Layout.
+ * Bei reduzierter Bewegung steht das Bild still.
+ */
+export function ParallaxMedia({ children, className = "" }: { children: ReactNode; className?: string }) {
+ const ref = useRef(null);
+ const reduceMotion = useReducedMotion();
+ const { scrollYProgress } = useScroll({ target: ref, offset: ["start end", "end start"] });
+ const scale = useTransform(scrollYProgress, [0, 0.5, 1], [1.14, 1.05, 1]);
+ const y = useTransform(scrollYProgress, [0, 1], ["-4%", "4%"]);
+
+ return (
+