diff --git a/src/components/cart-landing-effect.tsx b/src/components/cart-landing-effect.tsx new file mode 100644 index 0000000..9d6eadf --- /dev/null +++ b/src/components/cart-landing-effect.tsx @@ -0,0 +1,119 @@ +"use client"; + +import { useEffect } from "react"; +import { motion, useAnimationControls, useReducedMotion } from "motion/react"; +import { onCartLanded } from "@/components/cart-flight"; + +// Der Korb im Kopfzeilen-Symbol (20 px in einem 40-px-Knopf, viewBox 24): +// seine Unterkante liegt bei y=15, die Mitte bei x=13.5. Dort bildet sich +// der Tropfen — umgerechnet in Pixel des Knopfes. +const DRIP_LEFT_PX = 10 + (13.5 / 24) * 20; +const DRIP_TOP_PX = 10 + (15 / 24) * 20; +const DRIP_WIDTH_PX = 6; +const DRIP_DELAY_S = 0.3; + +/** + * Nachspiel der Landung eines Honigtropfens (components/cart-flight.tsx): + * Der Korb leuchtet zweimal warm in Honigfarbe auf, dann sammelt sich an + * seiner Unterkante ein kleiner Tropfen, zieht sich lang und perlt nach + * unten ab — als waere der Honig durch den Korb gesickert. + * + * Liegt als Overlay im Warenkorb-Knopf (relative) und faengt keine Klicks. + * Das Leuchten ist eine eigene, honigfarbene Kopie des Korbs, deren Deckkraft + * blinkt — so stimmt die Farbe im hellen wie im dunklen Farbschema, ohne die + * Strichfarbe des eigentlichen Symbols anzufassen. Bei reduzierter Bewegung + * bleibt nur ein einzelnes, ruhiges Aufleuchten ohne Tropfen. + */ +export function CartLandingEffect() { + const glowControls = useAnimationControls(); + const dripControls = useAnimationControls(); + const reduceMotion = useReducedMotion(); + + useEffect( + () => + onCartLanded(() => { + if (reduceMotion) { + void glowControls.start({ + opacity: [0, 0.9, 0], + transition: { duration: 0.8, ease: "easeInOut" }, + }); + return; + } + + void glowControls.start({ + opacity: [0, 1, 0.35, 1, 0], + scale: [0.9, 1.08, 1, 1.05, 1], + transition: { duration: 0.75, ease: "easeInOut", times: [0, 0.2, 0.45, 0.65, 1] }, + }); + void dripControls.start({ + // An der Kante sammeln, schwer werden und lang ziehen, + // abloesen und nach unten perlen, verblassen. + opacity: [0, 1, 1, 1, 0], + scaleX: [0.3, 1, 0.85, 0.9, 0.7], + scaleY: [0.2, 1, 1.35, 1, 0.8], + y: [0, 0, 3, 13, 17], + transition: { + duration: 1.1, + delay: DRIP_DELAY_S, + ease: "easeIn", + times: [0, 0.3, 0.55, 0.85, 1], + }, + }); + }), + [glowControls, dripControls, reduceMotion] + ); + + return ( + + ); +} diff --git a/src/components/site-chrome.tsx b/src/components/site-chrome.tsx index 4f0f6e1..a61a36f 100644 --- a/src/components/site-chrome.tsx +++ b/src/components/site-chrome.tsx @@ -15,6 +15,7 @@ import { QuantityStepper } from "@/components/quantity-stepper"; import { LanguageSwitcher } from "@/components/language-switcher"; import { useI18n } from "@/i18n/i18n-provider"; import { CartFlight, onCartLanded } from "@/components/cart-flight"; +import { CartLandingEffect } from "@/components/cart-landing-effect"; import { HexMark } from "@/components/honeycomb"; import type { Dictionary } from "@/i18n/dictionary-type"; import { ThemeToggle } from "@/components/theme-toggle"; @@ -285,7 +286,8 @@ function CartIconButton() { const basketControls = useAnimationControls(); // Landet ein Honigtropfen (components/cart-flight.tsx), faengt der Korb - // ihn sichtbar auf: kurzes Einsinken und Nachwippen. + // ihn sichtbar auf: kurzes Einsinken und Nachwippen. Leuchten und + // abperlender Tropfen kommen aus CartLandingEffect. useEffect( () => onCartLanded(() => { @@ -319,6 +321,7 @@ function CartIconButton() { + {count > 0 ? (