feat(ui): Kundenkonto im Wabenstil

- Konto-Kacheln: Icon in einer Wabe, die sich beim Hover mit Honig fuellt,
  einlaufender Pfeil, Serifen-Titel
- Bestell-Statusleiste: Waben statt Punkte, erledigte Abschnitte fliessen
  nacheinander voll, aktueller Schritt pulsiert sanft
- Status-Badge mit pulsierendem Punkt bei laufenden Bestellungen
- Leere Zustaende mit gestrichelter Wabe und Serifen-Titel
- Formularfelder: einheitlicher Honig-Schein beim Fokus
- Groessere Abschnittstitel im Konto, Bestellsummen in Serifen-Preisschrift
- Nur CSS-Animationen (Komponenten rendern auch auf Admin-Serverseiten);
  reduzierte Bewegung schaltet Puls und Fuellen ab

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 07:53:02 +02:00
1 parent 3de17ca070
commit 6ac7cc5d1f
5 files changed
+141 -35

No files matched your search

+78 -26
View File
@@ -24,23 +24,41 @@ export function AccountNavCard({
return (
<Link
href={href}
className={`group flex items-start gap-4 border p-6 transition-all duration-300 hover:-translate-y-0.5 ${
className={`group relative flex items-start gap-5 border p-6 transition-[transform,border-color,box-shadow] duration-500 ease-[var(--ease-arrive)] hover:-translate-y-1 ${
accent
? "border-honey/40 bg-honey/5 hover:border-honey hover:shadow-[0_16px_32px_-20px_rgba(184,138,68,0.35)]"
: "border-ink/10 hover:border-forest hover:shadow-[0_16px_32px_-20px_rgba(36,53,43,0.25)]"
? "border-honey/40 bg-honey/5 hover:border-honey hover:shadow-[0_22px_40px_-24px_rgba(184,138,68,0.45)]"
: "border-ink/10 bg-background hover:border-forest/60 hover:shadow-[0_22px_40px_-24px_rgba(36,53,43,0.35)]"
} ${className}`}
>
<span
className={`flex-shrink-0 flex items-center justify-center w-11 h-11 border transition-colors ${
accent ? "border-honey/40 text-honey-dark" : "border-ink/15 text-forest"
}`}
{/* Icon in einer Wabe, die sich beim Hover mit Honig fuellt. */}
<span className="relative flex-shrink-0 flex items-center justify-center w-12 h-[3.4rem]">
<span aria-hidden="true" className={`clip-hex absolute inset-0 ${accent ? "bg-honey/30" : "bg-ink/10"}`} />
<span
aria-hidden="true"
className="clip-hex absolute inset-[1.5px] bg-background transition-colors duration-500 group-hover:bg-honey"
/>
<span
className={`relative transition-colors duration-500 group-hover:text-cream ${
accent ? "text-honey-dark" : "text-forest"
}`}
>
{icon}
</span>
</span>
<span className="flex-1 pt-1">
<span className="block font-serif text-2xl leading-tight text-forest">{title}</span>
<span className="block mt-1.5 text-sm text-ink/70">{description}</span>
</span>
<svg
aria-hidden="true"
viewBox="0 0 24 24"
className="mt-2 h-4 w-4 flex-shrink-0 -translate-x-2 text-forest opacity-0 transition-[opacity,transform] duration-500 ease-[var(--ease-arrive)] group-hover:translate-x-0 group-hover:opacity-100"
fill="none"
stroke="currentColor"
strokeWidth="1.6"
>
{icon}
</span>
<span>
<span className="block text-lg text-forest">{title}</span>
<span className="block mt-1 text-sm text-ink/70">{description}</span>
</span>
<path d="M4 12h15M13 6l6 6-6 6" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</Link>
);
}
@@ -57,10 +75,19 @@ const ORDER_STATUS_STYLES: Record<string, string> = {
};
/** Farbcodiertes Status-Label statt reinem Fließtext, damit der Bestellstatus auf einen Blick erkennbar ist. */
/** Status, bei denen gerade etwas passiert — ihr Punkt pulsiert sanft. */
const ORDER_STATUS_IN_PROGRESS = new Set(["BEZAHLT", "IN_VORBEREITUNG", "VERSANDBEREIT", "ABHOLBEREIT"]);
export function OrderStatusBadge({ status, label }: { status: string; label: string }) {
const style = ORDER_STATUS_STYLES[status] ?? "border-ink/20 text-ink/70";
return (
<span className={`inline-flex items-center border px-2.5 py-1 text-xs tracking-wide-caps ${style}`}>
<span className={`inline-flex items-center gap-2 border px-2.5 py-1 text-xs tracking-wide-caps ${style}`}>
<span
aria-hidden="true"
className={`h-1.5 w-1.5 rounded-full bg-current ${
ORDER_STATUS_IN_PROGRESS.has(status) ? "motion-pulse animate-[wh-pulse_2s_ease-out_infinite]" : ""
}`}
/>
{label}
</span>
);
@@ -128,18 +155,34 @@ export function OrderStatusTimeline({
return (
<div key={step.key} role="listitem" className="flex-1 min-w-0 relative">
<div className="flex items-center">
<span
className={`flex-shrink-0 w-2.5 h-2.5 rounded-full ${isDone ? "bg-forest" : "bg-ink/20"}`}
<svg
viewBox="0 0 24 26"
aria-hidden="true"
/>
{i < steps.length - 1 ? (
<span
className={`ml-1 h-px flex-1 ${isLineDone ? "bg-forest" : "bg-ink/15"}`}
aria-hidden="true"
className={`h-3.5 w-3.5 flex-shrink-0 rounded-full ${
i === currentIndex && i < steps.length - 1 ? "motion-pulse animate-[wh-pulse_2.2s_ease-out_infinite]" : ""
}`}
>
<polygon
points="12,1 22.4,7 22.4,19 12,25 1.6,19 1.6,7"
fill={isDone ? "var(--color-honey)" : "var(--color-background)"}
stroke={isDone ? "var(--color-honey-dark)" : "color-mix(in oklab, var(--color-ink) 25%, transparent)"}
strokeWidth="2"
/>
</svg>
{i < steps.length - 1 ? (
<span className="relative ml-1 h-[2px] flex-1 overflow-hidden rounded-full bg-ink/10" aria-hidden="true">
{isLineDone ? (
// Erledigte Abschnitte fliessen nacheinander voll — der
// Fortschritt wird beim Oeffnen der Seite nachvollzogen.
<span
className="motion-grow absolute inset-0 origin-left bg-honey animate-[wh-grow_0.55s_var(--ease-arrive)_both]"
style={{ animationDelay: `${150 + i * 140}ms` }}
/>
) : null}
</span>
) : null}
</div>
<p className={`mt-2 text-[11px] text-center leading-tight ${isDone ? "text-forest" : "text-ink/45"}`}>
<p className={`mt-2 text-[11px] text-center leading-tight ${isDone ? "text-forest" : "text-ink/55"}`}>
{step.label}
</p>
</div>
@@ -162,11 +205,20 @@ export function AccountEmptyState({
action?: ReactNode;
}) {
return (
<div className="mt-10 border border-dashed border-ink/15 px-8 py-14 text-center">
<span className="inline-flex items-center justify-center w-12 h-12 border border-ink/15 text-ink/40 mx-auto">
{icon}
<div className="mt-10 border border-dashed border-ink/15 px-8 py-16 text-center">
<span className="relative mx-auto flex h-16 w-14 items-center justify-center text-honey-dark">
<svg viewBox="0 0 24 26" aria-hidden="true" className="absolute inset-0 h-full w-full text-honey">
<polygon
points="12,1 22.4,7 22.4,19 12,25 1.6,19 1.6,7"
fill="none"
stroke="currentColor"
strokeWidth="0.8"
strokeDasharray="2 1.6"
/>
</svg>
<span className="relative">{icon}</span>
</span>
<p className="mt-4 text-forest">{title}</p>
<p className="mt-5 font-serif text-2xl text-forest">{title}</p>
{description ? (
<p className="mt-1 text-sm text-ink/70 max-w-sm mx-auto">{description}</p>
) : null}