feat(ui): Kopfzeile, Fusszeile und Details mit mehr Leben
Docker Image bauen und veroeffentlichen / build-and-push (push) Successful in 4m11s

- Kopfzeile: aktive Seite mit Honig-Unterstrich (auch im Handy-Menue),
  einlaufende Unterstriche beim Hover, beim Scrollen Glas und Schatten;
  weicht beim Runterscrollen aus und ist beim Hochscrollen sofort zurueck
  (steht still, solange Warenkorb oder Menue offen sind)
- Fusszeile mit Wabenraster und einlaufenden Link-Unterstrichen
- Mengensteller: gezeichnete Plus/Minus, taktiles Druecken, tickende Zahl
- Newsletter als eigene Flaeche mit Wabenraster; Erfolg zeichnet ein
  Haekchen in eine Wabe, Fehler werden als Alert angesagt
- 404: Wabe mit einer fehlenden Zelle als Bild fuer die fehlende Seite

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
Nick SzaunigandClaude Opus 5.5 committed 2026-09-25 08:05:01 +02:00
1 parent 4ee5bafe20
commit 064ca3ff6d
5 files changed
+188 -25

No files matched your search

+30 -8
View File
@@ -1,7 +1,14 @@
"use client";
import { AnimatePresence, motion } from "motion/react";
/**
* Gemeinsamer Mengen-Steller (−/Zahl/+), bisher als Inline-Markup nur auf der
* Produktdetailseite vorhanden, waehrend Warenkorb-Drawer und Warenkorb-Seite
* noch ein natives <select> nutzten — vereinheitlicht das Bedienelement.
*
* Die Zahl "tickt" bei jeder Aenderung, die Knoepfe geben beim Druecken nach
* — die Menge fuehlt sich an wie ein Zaehlwerk statt wie ein Textfeld.
*/
export function QuantityStepper({
quantity,
@@ -14,30 +21,45 @@ export function QuantityStepper({
max?: number;
size?: "sm" | "md";
}) {
const buttonSize = size === "sm" ? "w-8 h-8 text-base" : "w-10 h-10 text-lg";
const buttonSize = size === "sm" ? "w-8 h-8" : "w-10 h-10";
const buttonClass = `${buttonSize} flex items-center justify-center text-ink/70 transition-[color,background-color,transform] duration-200 hover:bg-honey/10 hover:text-forest active:scale-90 disabled:opacity-30 disabled:hover:bg-transparent disabled:hover:text-ink/70 disabled:active:scale-100`;
return (
<div className="inline-flex items-stretch border border-ink/20">
<div className="inline-flex items-stretch border border-ink/20 bg-background">
<button
type="button"
onClick={() => onChange(Math.max(1, quantity - 1))}
disabled={quantity <= 1}
aria-label="Menge verringern"
className={`${buttonSize} flex items-center justify-center text-ink/70 hover:text-forest disabled:opacity-30 disabled:hover:text-ink/70`}
className={buttonClass}
>
−
<svg viewBox="0 0 24 24" className="h-3.5 w-3.5" fill="none" stroke="currentColor" strokeWidth="1.8" aria-hidden="true">
<path d="M5 12h14" strokeLinecap="round" />
</svg>
</button>
<span className="w-8 flex items-center justify-center text-sm" aria-live="polite">
{quantity}
<span className="relative w-8 overflow-hidden flex items-center justify-center text-sm tabular-nums" aria-live="polite">
<AnimatePresence mode="popLayout" initial={false}>
<motion.span
key={quantity}
initial={{ y: -12, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
exit={{ y: 12, opacity: 0 }}
transition={{ duration: 0.18, ease: [0.16, 1, 0.3, 1] }}
>
{quantity}
</motion.span>
</AnimatePresence>
</span>
<button
type="button"
onClick={() => onChange(Math.min(max, quantity + 1))}
disabled={quantity >= max}
aria-label="Menge erhöhen"
className={`${buttonSize} flex items-center justify-center text-ink/70 hover:text-forest disabled:opacity-30 disabled:hover:text-ink/70`}
className={buttonClass}
>
+
<svg viewBox="0 0 24 24" className="h-3.5 w-3.5" fill="none" stroke="currentColor" strokeWidth="1.8" aria-hidden="true">
<path d="M5 12h14M12 5v14" strokeLinecap="round" />
</svg>
</button>
</div>
);