Industrial Dark UI Overhaul: Updated colors, typography, navigation, and component styling across the application

This commit is contained in:
2025-12-22 00:05:31 +01:00
parent cf491d83b6
commit f0588418c8
28 changed files with 582 additions and 613 deletions

126
.aiideas
View File

@@ -1,117 +1,103 @@
Rolle: Du bist ein Senior Frontend Engineer(React / Tailwind) und UI / UX Designer.Ziel: Wir machen ein umfassendes UI - Overhaul einer bestehenden Whisky - App("WhiskyVault") und benennen sie um in "DramLog".Das Ziel ist eine Premium - Mobile - Experience(Dark Mode, Gold Akzente, Serif Fonts). Rolle: Du bist ein UI - Designer mit Fokus auf "Modern Minimalist" Design(Stilrichtung: Linear, Teenage Engineering, Vercel).Ziel: Redesign der Whisky - App "DramLog".Wir verabschieden uns vom klassischen "Luxus-Look"(Gold / Serifen) und nutzen einen "Industrial Dark" Stil.
Tech Stack: React, Tailwind CSS, Lucide - React Icons. Design Rules & Tokens:
1. Globales Design - System(Anwenden auf alle Views)
Branding: App Name ist "DramLog". Color Palette(Tailwind):
Farben: Bg - App: bg - zinc - 950(Ein sehr dunkles, warmes Grau, kein hartes Schwarz).
Background: #0F1014(Deep Rich Black) Bg - Card: bg - zinc - 900(Deutlich abgesetzt vom Hintergrund).
Surface: #1A1B20(Card Backgrounds) Text - Primary: text - zinc - 50(Fast Weiß, hoher Kontrast).
Primary Accent: #C89D46(Whisky Amber / Gold) Text - Secondary: text - zinc - 400(Mittelgrau für Labels).
Text - Secondary: #8F9096 Accent: text - orange - 500(Ein sattes, mattes Orange für Highlights) und bg - orange - 600 für Primary Buttons.Keine Verläufe / Gradients, sondern flache("flat") Farben.
Typografie: Typography:
Importiere und nutze Playfair Display(Serif) für: Überschriften(h1 - h3), Whisky - Namen auf Cards. Nutze Inter oder DM Sans für alles.
Nutze Inter(Sans) für: UI - Elemente, Labels, Fließtext, Daten. Headlines: font - bold tracking - tight(Enger Buchstabenabstand, wirkt kompakt und modern).
Shape: rounded - 2xl für Cards, rounded - full für Buttons. Labels: uppercase text - xs tracking - widest font - semibold(Technischer Look).
2. Spezifische Component Refactorings Component: Whisky Card(Clean Split):
A.Dashboard(Home View): Kein Text mehr über dem Bild!
Entferne die grauen Hintergründe der 4 oberen Statistik - Boxen.Zeige die Werte(Zahlen) groß in Playfair Display(Weiß) und die Labels klein darunter(Grau).Ordne sie in einem Grid oder einer flex - row mit justify - between an. Top: Bild(Aspect Ratio 4: 3 oder 16: 9), rounded - t - xl.
Benenne "Dein Bestand" um in "Collection". Bottom: Info - Block(p - 4 bg - zinc - 900 rounded - b - xl).
Mache die Listen - Einträge unter "Tasting Sessions" interaktiv.Entferne die sichtbaren Trash / Edit Buttons und nutze ein sauberes Listen - Layout. Inhalt: Whisky Name(Bold, White), darunter Destillerie(Orange, Small).
B.Whisky Card(Collection Grid): Tags: Kleine "Pills" mit bg - zinc - 800 text - zinc - 300.
Redesign der Card - Komponente. Component: Dashboard Stats:
Bild: Muss bis an den Rand gehen(w - full, kein Padding). Minimalistisch.Nur die Zahl(riesig, z.B.text - 4xl font - bold text - white) und darunter das Label(text - zinc - 500).
Overlay: Lege einen bg - gradient - to - t from - black via - black / 80 to - transparent über das untere Drittel des Bildes. Keine Boxen, keine Rahmen. "Data Ink Ratio" optimieren.
Text: Platziere Name(Serif) und Destillerie(Sans, Uppercase, Gold) weiß auf dem Bild im unteren Bereich(über dem Gradient). Component: Floating Navigation(The Capsule):
Tags: Mache Tags minimalistisch(bg - white / 10 backdrop - blur - sm text - xs border border - white / 10). Statt einer durchgehenden Leiste unten, nutze eine "Floating Capsule".
3. Die "Floating" Navigation(Core Feature) Eine abgerundete "Insel"(rounded - full) die ca. 20px über dem unteren Bildschirmrand schwebt.
Erstelle eine neue Komponente BottomNavigation.Sie ersetzt alle bisherigen Menüs.Sie muss am unteren Bildschirmrand fixiert sein.WICHTIG: Implementiere exakt dieses Layout - Pattern für den schwebenden Button: Breite: ca. 90 % des Screens oder max - w - md.
Farbe: bg - zinc - 800 / 90 backdrop - blur - md border border - zinc - 700.
Scan Button: In der Mitte der Kapsel, Kreis in bg - orange - 600(Flat, kein Schatten), weißes Icon.
Code - Snippet für die "Industrial Capsule Navigation":
JavaScript JavaScript
// Reference Implementation for BottomNavigation.jsx import { Home, Grid, Scan, User } from 'lucide-react';
import { Home, Grid, Scan, User, Search } from 'lucide-react';
export const BottomNavigation = () => { export const NavigationCapsule = () => {
return ( return (
<div className= "fixed bottom-0 left-0 w-full z-50" > <div className= "fixed bottom-6 left-1/2 -translate-x-1/2 w-[90%] max-w-sm z-50" >
{/* Background Container mit Glassmorphism */ } <div className="flex items-center justify-between px-2 py-2 bg-zinc-900/90 backdrop-blur-lg border border-zinc-800 rounded-full shadow-2xl" >
< div className = "relative bg-[#0F1014]/90 backdrop-blur-xl border-t border-white/10 pb-safe pt-2" >
<div className="flex justify-between items-end px-6 h-16" > {/* Left Item */ }
< button className = "p-3 text-zinc-400 hover:text-white transition-colors" >
{/* Left Actions */ } <Home size={ 22 } strokeWidth = { 2.5} />
< button className = "flex flex-col items-center gap-1 text-[#C89D46] w-12" >
<Home size={ 24 } />
< span className = "text-[10px] font-medium" > Home </span>
</button> </button>
< button className = "flex flex-col items-center gap-1 text-gray-500 hover:text-white w-12 transition-colors" > {/* Left Item */ }
<Grid size={ 24 } /> <button className="p-3 text-zinc-400 hover:text-white transition-colors" >
< span className = "text-[10px] font-medium" > Shelf </span> <Grid size={ 22 } strokeWidth = { 2.5} />
</button> </button>
{/* Spacer für den Center Button */ } {/* PRIMARY ACTION - The "Industrial Button" */ }
<div className="w-16" /> <button className="flex items-center justify-center w-14 h-14 rounded-full bg-orange-600 text-white hover:bg-orange-500 active:scale-95 transition-all mx-2" >
<Scan size={ 26 } strokeWidth = { 2.5} />
{/* Right Actions */ }
< button className = "flex flex-col items-center gap-1 text-gray-500 hover:text-white w-12 transition-colors" >
<Search size={ 24 } />
< span className = "text-[10px] font-medium" > Search </span>
</button> </button>
< button className = "flex flex-col items-center gap-1 text-gray-500 hover:text-white w-12 transition-colors" > {/* Right Item */ }
<User size={ 24 } /> <button className="p-3 text-zinc-400 hover:text-white transition-colors" >
< span className = "text-[10px] font-medium" > Profile </span> <User size={ 22 } strokeWidth = { 2.5} />
</button> </button>
</div>
{/* THE FLOATING MAGIC BUTTON */ } {/* Right Item (Settings/More) */ }
<div className="absolute -top-8 left-1/2 -translate-x-1/2" > <button className="p-3 text-zinc-400 hover:text-white transition-colors" >
<button <div className="w-1 h-1 bg-current rounded-full mb-1" />
className="flex items-center justify-center w-16 h-16 rounded-full <div className="w-1 h-1 bg-current rounded-full" />
bg - gradient - to - tr from - [#C89D46] to - [#E0B456]
shadow - [0_0_20px_rgba(200, 157, 70, 0.4)]
border - 4 border - [#0F1014]
active: scale - 95 transition - transform duration - 200"
aria - label="Scan Bottle"
>
<Scan color="#0F1014" size = { 32} strokeWidth = { 2} />
</button> </button>
</div>
</div> </div>
</div> </div>
); );
}; };
Aufgabe: Aufgabe: Setze die bestehenden Views(Dashboard und Liste) mit diesen neuen "Industrial Dark" Regeln um.Sorge für klare Kontraste durch zinc - 950 vs zinc - 900 Flächen.
Was dieser Look ändert:
Implementiere das globale Styling(Fonts / Colors). Lesbarkeit: Durch den zinc - 900 Hintergrund der Karten hebt sich der weiße Text extrem gut ab.Kein "Text auf unruhigem Foto" - Problem mehr.
Baue die BottomNavigation ein(ersetze alte Navs). Modernität: Das "Pill" - Menü(Capsule) sieht aus wie bei modernen iOS Apps(Dynamic Island Ästhetik).
Passe die Dashboard View an den neuen "Clean Look" an. Ehrlichkeit: Es versucht nicht, "altes Geld"(Gold / Serifen) zu imitieren, sondern wirkt wie ein modernes Werkzeug für Enthusiasten.
Passe die WhiskyCard an den neuen "Editorial Look" an.

View File

@@ -12,10 +12,10 @@ export default function GlobalError({
return ( return (
<html lang="de"> <html lang="de">
<body> <body>
<div className="flex min-h-screen flex-col items-center justify-center p-6 bg-zinc-50 dark:bg-black text-center"> <div className="flex min-h-screen flex-col items-center justify-center p-6 bg-black text-center">
<div className="bg-white dark:bg-zinc-900 p-8 rounded-3xl border border-zinc-200 dark:border-zinc-800 shadow-xl max-w-md w-full space-y-6"> <div className="bg-zinc-900 p-8 rounded-[32px] border border-zinc-800 shadow-xl max-w-md w-full space-y-6">
<div className="space-y-2"> <div className="space-y-2">
<h2 className="text-2xl font-black text-zinc-900 dark:text-white">Kritischer Fehler</h2> <h2 className="text-2xl font-bold text-white uppercase tracking-tighter">Kritischer Fehler</h2>
<p className="text-zinc-500 text-sm"> <p className="text-zinc-500 text-sm">
Ein schwerwiegender Fehler ist aufgetreten. Bitte versuche die Seite neu zu laden. Ein schwerwiegender Fehler ist aufgetreten. Bitte versuche die Seite neu zu laden.
</p> </p>
@@ -23,7 +23,7 @@ export default function GlobalError({
<button <button
onClick={() => reset()} onClick={() => reset()}
className="w-full py-4 bg-amber-600 hover:bg-amber-700 text-white rounded-xl font-bold flex items-center justify-center gap-2 transition-all" className="w-full py-4 bg-orange-600 hover:bg-orange-700 text-white rounded-2xl font-bold flex items-center justify-center gap-2 transition-all shadow-lg shadow-orange-950/20"
> >
<RefreshCcw size={18} /> <RefreshCcw size={18} />
Erneut versuchen Erneut versuchen

View File

@@ -4,16 +4,24 @@
@layer base { @layer base {
:root { :root {
--background: #0F1014; --background: #09090b;
--surface: #1A1B20; /* zinc-950 */
--primary: #C89D46; --surface: #18181b;
--text-secondary: #8F9096; /* zinc-900 */
--border: rgba(255, 255, 255, 0.1); --primary: #ea580c;
/* orange-600 */
--secondary: #f97316;
/* orange-500 */
--text-primary: #fafafa;
--text-secondary: #a1a1aa;
--border: #27272a;
/* zinc-800 */
--ring: #f97316;
} }
} }
body { body {
@apply bg-[#0F1014] text-white antialiased; @apply bg-[#09090b] text-[#fafafa] antialiased;
font-feature-settings: "cv02", "cv03", "cv04", "cv11"; font-feature-settings: "cv02", "cv03", "cv04", "cv11";
} }
@@ -22,16 +30,17 @@ h2,
h3, h3,
h4, h4,
.font-display { .font-display {
font-family: var(--font-playfair), serif; font-family: var(--font-inter), system-ui, sans-serif;
letter-spacing: -0.02em;
} }
@layer utilities { @layer utilities {
.glass { .glass {
@apply backdrop-blur-md bg-white/5 border border-white/10; @apply backdrop-blur-md bg-zinc-900/50 border border-zinc-800/50;
} }
.glass-dark { .glass-dark {
@apply backdrop-blur-md bg-black/40 border border-white/5; @apply backdrop-blur-md bg-zinc-950/80 border border-zinc-900/50;
} }
.scrollbar-hide::-webkit-scrollbar { .scrollbar-hide::-webkit-scrollbar {

View File

@@ -12,16 +12,13 @@ import AuthListener from "@/components/AuthListener";
import SyncHandler from "@/components/SyncHandler"; import SyncHandler from "@/components/SyncHandler";
const inter = Inter({ subsets: ["latin"], variable: '--font-inter' }); const inter = Inter({ subsets: ["latin"], variable: '--font-inter' });
const playfair = Playfair_Display({ subsets: ["latin"], variable: '--font-playfair' });
import { Playfair_Display } from "next/font/google";
export const metadata: Metadata = { export const metadata: Metadata = {
title: { title: {
default: "DramLog", default: "DramLog",
template: "%s | DramLog" template: "%s | DramLog"
}, },
description: "Premium Digitaler Tasting Begleiter für Genießer.", description: "Modern Whisky Enthusiast Tool",
manifest: "/manifest.webmanifest", manifest: "/manifest.webmanifest",
appleWebApp: { appleWebApp: {
capable: true, capable: true,
@@ -48,7 +45,7 @@ export default function RootLayout({
}>) { }>) {
return ( return (
<html lang="de"> <html lang="de">
<body className={`${inter.variable} ${playfair.variable} font-sans`}> <body className={`${inter.variable} font-sans`}>
<I18nProvider> <I18nProvider>
<SessionProvider> <SessionProvider>
<AuthListener /> <AuthListener />

View File

@@ -2,10 +2,10 @@ import { Loader2 } from 'lucide-react';
export default function Loading() { export default function Loading() {
return ( return (
<div className="flex min-h-screen flex-col items-center justify-center p-6 bg-zinc-50 dark:bg-black text-center"> <div className="flex min-h-screen flex-col items-center justify-center p-6 bg-black text-center">
<div className="flex flex-col items-center gap-4"> <div className="flex flex-col items-center gap-4">
<Loader2 size={40} className="animate-spin text-amber-600" /> <Loader2 size={40} className="animate-spin text-orange-600" />
<p className="text-zinc-500 font-medium animate-pulse">Whisky Vault wird geladen...</p> <p className="text-zinc-500 font-bold animate-pulse uppercase tracking-widest text-[10px]">Whisky Vault wird geladen...</p>
</div> </div>
</div> </div>
); );

View File

@@ -3,12 +3,12 @@ import { Home, MoveLeft } from 'lucide-react';
export default function NotFound() { export default function NotFound() {
return ( return (
<div className="flex min-h-screen flex-col items-center justify-center p-6 bg-zinc-50 dark:bg-black text-center"> <div className="flex min-h-screen flex-col items-center justify-center p-6 bg-black text-center">
<div className="bg-white dark:bg-zinc-900 p-8 rounded-3xl border border-zinc-200 dark:border-zinc-800 shadow-xl max-w-md w-full space-y-6"> <div className="bg-zinc-900 p-8 rounded-[32px] border border-zinc-800 shadow-xl max-w-md w-full space-y-6">
<div className="text-8xl font-black text-amber-600/20">404</div> <div className="text-8xl font-bold text-orange-600/10 tracking-tighter">404</div>
<div className="space-y-2"> <div className="space-y-2">
<h2 className="text-2xl font-black text-zinc-900 dark:text-white">Seite nicht gefunden</h2> <h2 className="text-2xl font-bold text-white uppercase tracking-tighter">Seite nicht gefunden</h2>
<p className="text-zinc-500 text-sm"> <p className="text-zinc-500 text-sm">
Die gesuchte Seite existiert leider nicht oder wurde verschoben. Die gesuchte Seite existiert leider nicht oder wurde verschoben.
</p> </p>
@@ -17,7 +17,7 @@ export default function NotFound() {
<div className="pt-4"> <div className="pt-4">
<Link <Link
href="/" href="/"
className="w-full py-4 bg-amber-600 hover:bg-amber-700 text-white rounded-xl font-bold flex items-center justify-center gap-2 transition-all shadow-lg shadow-amber-600/20" className="w-full py-4 bg-orange-600 hover:bg-orange-700 text-white rounded-2xl font-bold flex items-center justify-center gap-2 transition-all shadow-lg shadow-orange-950/20"
> >
<Home size={18} /> <Home size={18} />
Zurück zum Vault Zurück zum Vault

View File

@@ -151,13 +151,13 @@ export default function Home() {
if (!user) { if (!user) {
return ( return (
<main className="flex min-h-screen flex-col items-center justify-center p-6 bg-[#0F1014]"> <main className="flex min-h-screen flex-col items-center justify-center p-6 bg-zinc-950">
<div className="mb-12 text-center animate-in fade-in zoom-in duration-1000"> <div className="mb-12 text-center animate-in fade-in zoom-in duration-1000">
<h1 className="text-6xl font-display font-bold text-white tracking-tighter mb-4"> <h1 className="text-6xl font-bold text-zinc-50 tracking-tighter mb-4">
DRAM<span className="text-[#C89D46]">LOG</span> DRAM<span className="text-orange-600">LOG</span>
</h1> </h1>
<p className="text-[#8F9096] max-w-sm mx-auto font-sans tracking-wide"> <p className="text-zinc-500 max-w-sm mx-auto font-bold tracking-wide">
Premium Digitaler Tasting Begleiter für Genießer. Modern Minimalist Tasting Tool.
</p> </p>
<div className="mt-8"> <div className="mt-8">
<LanguageSwitcher /> <LanguageSwitcher />
@@ -169,20 +169,20 @@ export default function Home() {
} }
return ( return (
<main className="flex min-h-screen flex-col items-center gap-6 md:gap-12 p-4 md:p-24 bg-[#0F1014] pb-32"> <main className="flex min-h-screen flex-col items-center gap-6 md:gap-12 p-4 md:p-24 bg-zinc-950 pb-32">
<div className="z-10 max-w-5xl w-full flex flex-col items-center gap-12"> <div className="z-10 max-w-5xl w-full flex flex-col items-center gap-12">
<header className="w-full flex flex-col sm:flex-row justify-between items-center gap-4 sm:gap-0"> <header className="w-full flex flex-col sm:flex-row justify-between items-center gap-4 sm:gap-0">
<div className="flex flex-col items-center sm:items-start group"> <div className="flex flex-col items-center sm:items-start group">
<h1 className="text-4xl font-display font-bold text-white tracking-tighter"> <h1 className="text-4xl font-bold text-zinc-50 tracking-tighter">
DRAM<span className="text-[#C89D46]">LOG</span> DRAM<span className="text-orange-600">LOG</span>
</h1> </h1>
{activeSession && ( {activeSession && (
<div className="flex items-center gap-2 mt-1 animate-in fade-in slide-in-from-left-2 duration-700"> <div className="flex items-center gap-2 mt-1 animate-in fade-in slide-in-from-left-2 duration-700">
<div className="relative flex h-2 w-2"> <div className="relative flex h-2 w-2">
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-[#C89D46] opacity-75"></span> <span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-orange-600 opacity-75"></span>
<span className="relative inline-flex rounded-full h-2 w-2 bg-[#C89D46]"></span> <span className="relative inline-flex rounded-full h-2 w-2 bg-orange-600"></span>
</div> </div>
<span className="text-[9px] font-sans font-bold uppercase tracking-widest text-[#C89D46] flex items-center gap-1"> <span className="text-[9px] font-bold uppercase tracking-widest text-orange-600 flex items-center gap-1">
<Sparkles size={10} className="animate-pulse" /> <Sparkles size={10} className="animate-pulse" />
Live: {activeSession.name} Live: {activeSession.name}
</span> </span>
@@ -195,7 +195,7 @@ export default function Home() {
<DramOfTheDay bottles={bottles} /> <DramOfTheDay bottles={bottles} />
<button <button
onClick={handleLogout} onClick={handleLogout}
className="text-xs font-sans font-bold uppercase tracking-widest text-[#8F9096] hover:text-white transition-colors" className="text-[10px] font-bold uppercase tracking-widest text-zinc-500 hover:text-white transition-colors"
> >
{t('home.logout')} {t('home.logout')}
</button> </button>
@@ -206,7 +206,7 @@ export default function Home() {
<StatsDashboard bottles={bottles} /> <StatsDashboard bottles={bottles} />
</div> </div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-12 w-full max-w-5xl"> <div className="grid grid-cols-1 lg:grid-cols-2 gap-12 w-full max-w-5xl">
<div className="flex flex-col gap-8"> <div className="flex flex-col gap-8">
<SessionList /> <SessionList />
</div> </div>
@@ -215,27 +215,27 @@ export default function Home() {
</div> </div>
</div> </div>
<div className="w-full mt-4"> <div className="w-full mt-4" id="collection">
<div className="flex items-end justify-between mb-8"> <div className="flex items-end justify-between mb-8">
<h2 className="text-3xl font-display font-bold text-white"> <h2 className="text-3xl font-bold text-zinc-50 uppercase tracking-tight">
Collection Collection
</h2> </h2>
<span className="text-xs font-sans font-bold text-[#8F9096] uppercase tracking-widest pb-1"> <span className="text-[10px] font-bold text-zinc-500 uppercase tracking-widest pb-1">
{bottles.length} Bottles {bottles.length} Bottles
</span> </span>
</div> </div>
{isLoading ? ( {isLoading ? (
<div className="flex justify-center py-20"> <div className="flex justify-center py-20">
<div className="animate-spin rounded-full h-10 w-10 border-b-2 border-[#C89D46]"></div> <div className="animate-spin rounded-full h-10 w-10 border-b-2 border-orange-600"></div>
</div> </div>
) : fetchError ? ( ) : fetchError ? (
<div className="p-12 bg-[#1A1B20] border border-white/10 rounded-3xl text-center"> <div className="p-12 bg-zinc-900 border border-zinc-800 rounded-3xl text-center">
<p className="text-white font-display text-xl mb-4">{t('common.error')}</p> <p className="text-zinc-50 font-bold text-xl mb-2">{t('common.error')}</p>
<p className="text-[#8F9096] text-sm italic mb-8 mx-auto max-w-xs">{fetchError}</p> <p className="text-zinc-500 text-xs italic mb-8 mx-auto max-w-xs">{fetchError}</p>
<button <button
onClick={fetchCollection} onClick={fetchCollection}
className="px-10 py-4 bg-[#C89D46] hover:bg-[#E0B456] text-[#0F1014] rounded-full text-xs font-sans font-bold uppercase tracking-widest transition-all" className="px-10 py-4 bg-orange-600 hover:bg-orange-700 text-white rounded-2xl text-[10px] font-bold uppercase tracking-widest transition-all shadow-lg shadow-orange-950/20"
> >
{t('home.reTry')} {t('home.reTry')}
</button> </button>

View File

@@ -14,7 +14,7 @@ export default function ActiveSessionBanner() {
return ( return (
<div className="fixed top-0 left-0 right-0 z-[100] animate-in slide-in-from-top duration-500"> <div className="fixed top-0 left-0 right-0 z-[100] animate-in slide-in-from-top duration-500">
<div className="bg-amber-600 text-white px-4 py-2 flex items-center justify-between shadow-lg"> <div className="bg-orange-600 text-white px-4 py-2 flex items-center justify-between shadow-lg">
<Link <Link
href={`/sessions/${activeSession.id}`} href={`/sessions/${activeSession.id}`}
className="flex items-center gap-3 flex-1 min-w-0" className="flex items-center gap-3 flex-1 min-w-0"
@@ -23,7 +23,7 @@ export default function ActiveSessionBanner() {
<div className="bg-white/20 p-1.5 rounded-lg"> <div className="bg-white/20 p-1.5 rounded-lg">
<Sparkles size={16} className="text-white animate-pulse" /> <Sparkles size={16} className="text-white animate-pulse" />
</div> </div>
<div className="absolute -top-1 -right-1 w-2.5 h-2.5 bg-red-500 rounded-full border-2 border-amber-600 animate-ping" /> <div className="absolute -top-1 -right-1 w-2.5 h-2.5 bg-red-500 rounded-full border-2 border-orange-600 animate-ping" />
</div> </div>
<div className="min-w-0"> <div className="min-w-0">
<div className="flex items-center gap-2 mb-0.5"> <div className="flex items-center gap-2 mb-0.5">

View File

@@ -30,7 +30,7 @@ export default function AvatarStack({ names, limit = 3, size = 'sm' }: AvatarSta
{visibleNames.map((name, i) => ( {visibleNames.map((name, i) => (
<div <div
key={`${name}-${i}`} key={`${name}-${i}`}
className={`${sizeClasses} rounded-full bg-amber-100 dark:bg-amber-900/30 border-2 border-white dark:border-zinc-900 flex items-center justify-center text-amber-700 dark:text-amber-400 font-black ring-1 ring-amber-500/10 shadow-sm relative group`} className={`${sizeClasses} rounded-full bg-orange-900/30 border-2 border-zinc-950 flex items-center justify-center text-orange-400 font-bold ring-1 ring-orange-500/10 shadow-sm relative group`}
title={name} title={name}
> >
{getInitials(name)} {getInitials(name)}

View File

@@ -24,7 +24,7 @@ export default function BottleDetails({ bottleId, sessionId, userId }: BottleDet
if (loading) { if (loading) {
return ( return (
<div className="min-h-[60vh] flex flex-col items-center justify-center gap-4"> <div className="min-h-[60vh] flex flex-col items-center justify-center gap-4">
<Loader2 size={48} className="animate-spin text-amber-600" /> <Loader2 size={48} className="animate-spin text-orange-600" />
<p className="text-zinc-500 font-bold animate-pulse uppercase tracking-widest text-xs">{t('common.loading')}</p> <p className="text-zinc-500 font-bold animate-pulse uppercase tracking-widest text-xs">{t('common.loading')}</p>
</div> </div>
); );
@@ -42,7 +42,7 @@ export default function BottleDetails({ bottleId, sessionId, userId }: BottleDet
Inhalte konnten nicht geladen werden. Bitte stelle eine Internetverbindung her, um diese Flasche zum ersten Mal zu laden. Inhalte konnten nicht geladen werden. Bitte stelle eine Internetverbindung her, um diese Flasche zum ersten Mal zu laden.
</p> </p>
</div> </div>
<Link href="/" className="px-6 py-3 bg-amber-600 text-white rounded-2xl text-sm font-black uppercase tracking-widest shadow-xl shadow-amber-600/20"> <Link href="/" className="px-6 py-3 bg-orange-600 text-white rounded-2xl text-sm font-black uppercase tracking-widest shadow-xl shadow-orange-950/20">
Zurück zum Vault Zurück zum Vault
</Link> </Link>
</div> </div>
@@ -56,22 +56,22 @@ export default function BottleDetails({ bottleId, sessionId, userId }: BottleDet
{/* Back Button */} {/* Back Button */}
<Link <Link
href={`/${sessionId ? `?session_id=${sessionId}` : ''}`} href={`/${sessionId ? `?session_id=${sessionId}` : ''}`}
className="inline-flex items-center gap-2 text-zinc-500 hover:text-amber-600 transition-colors font-medium mb-4" className="inline-flex items-center gap-2 text-zinc-500 hover:text-orange-600 transition-colors font-bold mb-4"
> >
<ChevronLeft size={20} /> <ChevronLeft size={20} />
Zurück zur Sammlung Zurück zur Sammlung
</Link> </Link>
{isOffline && ( {isOffline && (
<div className="bg-amber-600/10 border border-amber-600/20 p-3 rounded-2xl flex items-center gap-3 animate-in fade-in slide-in-from-top-2"> <div className="bg-orange-600/10 border border-orange-600/20 p-3 rounded-2xl flex items-center gap-3 animate-in fade-in slide-in-from-top-2">
<WifiOff size={16} className="text-amber-600" /> <WifiOff size={16} className="text-orange-600" />
<p className="text-[10px] font-black uppercase tracking-widest text-amber-700">Offline-Modus: Daten aus dem Cache</p> <p className="text-[10px] font-bold uppercase tracking-widest text-orange-500">Offline-Modus: Daten aus dem Cache</p>
</div> </div>
)} )}
{/* Hero Section */} {/* Hero Section */}
<section className="grid grid-cols-1 md:grid-cols-2 gap-8 items-start"> <section className="grid grid-cols-1 md:grid-cols-2 gap-8 items-start">
<div className="aspect-[4/5] rounded-3xl overflow-hidden shadow-2xl border border-zinc-200 dark:border-zinc-800 bg-white dark:bg-zinc-900"> <div className="aspect-[4/5] rounded-3xl overflow-hidden shadow-2xl border border-zinc-800 bg-zinc-900">
<img <img
src={getStorageUrl(bottle.image_url)} src={getStorageUrl(bottle.image_url)}
alt={bottle.name} alt={bottle.name}
@@ -81,10 +81,10 @@ export default function BottleDetails({ bottleId, sessionId, userId }: BottleDet
<div className="space-y-6"> <div className="space-y-6">
<div> <div>
<h1 className="text-2xl md:text-4xl font-black text-zinc-900 dark:text-white tracking-tighter leading-tight"> <h1 className="text-2xl md:text-4xl font-bold text-zinc-50 tracking-tighter leading-tight uppercase">
{bottle.name} {bottle.name}
</h1> </h1>
<p className="text-sm md:text-xl text-amber-600 font-bold mt-1 uppercase tracking-widest">{bottle.distillery}</p> <p className="text-sm md:text-xl text-orange-600 font-bold mt-1 uppercase tracking-widest">{bottle.distillery}</p>
{bottle.whiskybase_id && ( {bottle.whiskybase_id && (
<div className="mt-4"> <div className="mt-4">
@@ -92,9 +92,9 @@ export default function BottleDetails({ bottleId, sessionId, userId }: BottleDet
href={`https://www.whiskybase.com/whiskies/whisky/${bottle.whiskybase_id}`} href={`https://www.whiskybase.com/whiskies/whisky/${bottle.whiskybase_id}`}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="inline-flex items-center gap-2 px-4 py-2 bg-[#db0000] text-white rounded-xl text-sm font-bold shadow-lg shadow-red-600/20 hover:scale-[1.05] transition-transform" className="inline-flex items-center gap-2 px-4 py-2 bg-zinc-900 text-zinc-400 border border-zinc-800 rounded-xl text-xs font-bold hover:text-orange-600 transition-colors"
> >
<ExternalLink size={16} /> <ExternalLink size={14} />
Whiskybase ID: {bottle.whiskybase_id} Whiskybase ID: {bottle.whiskybase_id}
</a> </a>
</div> </div>
@@ -102,59 +102,59 @@ export default function BottleDetails({ bottleId, sessionId, userId }: BottleDet
</div> </div>
<div className="grid grid-cols-2 md:grid-cols-3 gap-4"> <div className="grid grid-cols-2 md:grid-cols-3 gap-4">
<div className="p-4 bg-white dark:bg-zinc-900 rounded-2xl border border-zinc-100 dark:border-zinc-800 shadow-sm flex flex-col justify-between"> <div className="p-4 bg-zinc-900 rounded-2xl border border-zinc-800 shadow-sm flex flex-col justify-between">
<div> <div>
<div className="flex items-center gap-2 text-zinc-400 text-[10px] font-black uppercase mb-1"> <div className="flex items-center gap-2 text-zinc-500 text-[10px] font-bold uppercase mb-1">
<Tag size={12} /> Kategorie <Tag size={12} /> Kategorie
</div> </div>
<div className="font-bold text-sm dark:text-zinc-200">{bottle.category || '-'}</div> <div className="font-bold text-sm text-zinc-200">{bottle.category || '-'}</div>
</div> </div>
</div> </div>
<div className="p-4 bg-white dark:bg-zinc-900 rounded-2xl border border-zinc-100 dark:border-zinc-800 shadow-sm"> <div className="p-4 bg-zinc-900 rounded-2xl border border-zinc-800 shadow-sm">
<div className="flex items-center gap-2 text-zinc-400 text-[10px] font-black uppercase mb-1"> <div className="flex items-center gap-2 text-zinc-500 text-[10px] font-bold uppercase mb-1">
<Droplets size={12} /> Alkoholgehalt <Droplets size={12} /> Alkoholgehalt
</div> </div>
<div className="font-bold text-sm dark:text-zinc-200">{bottle.abv}% Vol.</div> <div className="font-bold text-sm text-zinc-200">{bottle.abv}% Vol.</div>
</div> </div>
<div className="p-4 bg-white dark:bg-zinc-900 rounded-2xl border border-zinc-100 dark:border-zinc-800 shadow-sm"> <div className="p-4 bg-zinc-900 rounded-2xl border border-zinc-800 shadow-sm">
<div className="flex items-center gap-2 text-zinc-400 text-[10px] font-black uppercase mb-1"> <div className="flex items-center gap-2 text-zinc-500 text-[10px] font-bold uppercase mb-1">
<Award size={12} /> Alter <Award size={12} /> Alter
</div> </div>
<div className="font-bold text-sm dark:text-zinc-200">{bottle.age ? `${bottle.age} J.` : '-'}</div> <div className="font-bold text-sm text-zinc-200">{bottle.age ? `${bottle.age} J.` : '-'}</div>
</div> </div>
{bottle.distilled_at && ( {bottle.distilled_at && (
<div className="p-4 bg-white dark:bg-zinc-900 rounded-2xl border border-zinc-100 dark:border-zinc-800 shadow-sm"> <div className="p-4 bg-zinc-900 rounded-2xl border border-zinc-800 shadow-sm">
<div className="flex items-center gap-2 text-zinc-400 text-[10px] font-black uppercase mb-1"> <div className="flex items-center gap-2 text-zinc-500 text-[10px] font-bold uppercase mb-1">
<Calendar size={12} /> Destilliert <Calendar size={12} /> Destilliert
</div> </div>
<div className="font-bold text-sm dark:text-zinc-200">{bottle.distilled_at}</div> <div className="font-bold text-sm text-zinc-200">{bottle.distilled_at}</div>
</div> </div>
)} )}
{bottle.bottled_at && ( {bottle.bottled_at && (
<div className="p-4 bg-white dark:bg-zinc-900 rounded-2xl border border-zinc-100 dark:border-zinc-800 shadow-sm"> <div className="p-4 bg-zinc-900 rounded-2xl border border-zinc-800 shadow-sm">
<div className="flex items-center gap-2 text-zinc-400 text-[10px] font-black uppercase mb-1"> <div className="flex items-center gap-2 text-zinc-500 text-[10px] font-bold uppercase mb-1">
<Package size={12} /> Abgefüllt <Package size={12} /> Abgefüllt
</div> </div>
<div className="font-bold text-sm dark:text-zinc-200">{bottle.bottled_at}</div> <div className="font-bold text-sm text-zinc-200">{bottle.bottled_at}</div>
</div> </div>
)} )}
{bottle.batch_info && ( {bottle.batch_info && (
<div className="p-4 bg-zinc-50 dark:bg-zinc-800/30 rounded-2xl border border-dashed border-zinc-200 dark:border-zinc-700/50 md:col-span-1"> <div className="p-4 bg-zinc-800/30 rounded-2xl border border-dashed border-zinc-700/50 md:col-span-1">
<div className="flex items-center gap-2 text-zinc-400 text-[10px] font-black uppercase mb-1"> <div className="flex items-center gap-2 text-zinc-500 text-[10px] font-bold uppercase mb-1">
<Info size={12} /> Batch / Code <Info size={12} /> Batch / Code
</div> </div>
<div className="font-mono text-xs dark:text-zinc-300 truncate" title={bottle.batch_info}>{bottle.batch_info}</div> <div className="font-mono text-xs text-zinc-300 truncate" title={bottle.batch_info}>{bottle.batch_info}</div>
</div> </div>
)} )}
<div className="p-4 bg-white dark:bg-zinc-900 rounded-2xl border border-zinc-100 dark:border-zinc-800 shadow-sm"> <div className="p-4 bg-zinc-900 rounded-2xl border border-zinc-800 shadow-sm">
<div className="flex items-center gap-2 text-zinc-400 text-[10px] font-black uppercase mb-1"> <div className="flex items-center gap-2 text-zinc-500 text-[10px] font-bold uppercase mb-1">
<Calendar size={12} /> Letzter Dram <Calendar size={12} /> Letzter Dram
</div> </div>
<div className="font-bold text-sm dark:text-zinc-200"> <div className="font-bold text-sm text-zinc-200">
{tastings && tastings.length > 0 {tastings && tastings.length > 0
? new Date(tastings[0].created_at).toLocaleDateString('de-DE') ? new Date(tastings[0].created_at).toLocaleDateString('de-DE')
: 'Noch nie'} : 'Noch nie'}
@@ -164,9 +164,9 @@ export default function BottleDetails({ bottleId, sessionId, userId }: BottleDet
<div className="pt-2 flex flex-wrap gap-4"> <div className="pt-2 flex flex-wrap gap-4">
{isOffline ? ( {isOffline ? (
<div className="w-full p-4 bg-zinc-100 dark:bg-zinc-800/50 border border-dashed border-zinc-200 dark:border-zinc-700 rounded-2xl flex items-center justify-center gap-2"> <div className="w-full p-4 bg-zinc-900 border border-dashed border-zinc-800 rounded-2xl flex items-center justify-center gap-2">
<Info size={14} className="text-zinc-400" /> <Info size={14} className="text-zinc-500" />
<span className="text-[10px] font-black uppercase text-zinc-400 tracking-widest">Bearbeiten & Löschen nur online möglich</span> <span className="text-[10px] font-bold uppercase text-zinc-500 tracking-widest">Bearbeiten & Löschen nur online möglich</span>
</div> </div>
) : ( ) : (
<> <>
@@ -178,21 +178,21 @@ export default function BottleDetails({ bottleId, sessionId, userId }: BottleDet
</div> </div>
</section> </section>
<hr className="border-zinc-200 dark:border-zinc-800" /> <hr className="border-zinc-800" />
{/* Tasting Notes Section */} {/* Tasting Notes Section */}
<section className="space-y-8"> <section className="space-y-8">
<div className="flex flex-col md:flex-row justify-between items-start md:items-end gap-4"> <div className="flex flex-col md:flex-row justify-between items-start md:items-end gap-4">
<div> <div>
<h2 className="text-3xl font-black text-zinc-900 dark:text-white tracking-tight">Tasting Notes</h2> <h2 className="text-3xl font-bold text-zinc-50 tracking-tight uppercase">Tasting Notes</h2>
<p className="text-zinc-500 mt-1">Hier findest du deine bisherigen Eindrücke.</p> <p className="text-zinc-500 mt-1">Hier findest du deine bisherigen Eindrücke.</p>
</div> </div>
</div> </div>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6 md:gap-8 items-start"> <div className="grid grid-cols-1 lg:grid-cols-3 gap-6 md:gap-8 items-start">
{/* Form */} {/* Form */}
<div className="lg:col-span-1 border border-zinc-200 dark:border-zinc-800 rounded-3xl p-6 bg-white dark:bg-zinc-900/50 md:sticky md:top-24"> <div className="lg:col-span-1 border border-zinc-800 rounded-3xl p-6 bg-zinc-900/50 md:sticky md:top-24">
<h3 className="text-lg font-bold mb-6 flex items-center gap-2 text-amber-600"> <h3 className="text-lg font-bold mb-6 flex items-center gap-2 text-orange-600 uppercase tracking-widest">
<Droplets size={20} /> Dram bewerten <Droplets size={20} /> Dram bewerten
</h3> </h3>
<TastingNoteForm bottleId={bottle.id} sessionId={sessionId} /> <TastingNoteForm bottleId={bottle.id} sessionId={sessionId} />

View File

@@ -36,71 +36,65 @@ function BottleCard({ bottle, sessionId }: BottleCardProps) {
return ( return (
<Link <Link
href={`/bottles/${bottle.id}${sessionId ? `?session_id=${sessionId}` : ''}`} href={`/bottles/${bottle.id}${sessionId ? `?session_id=${sessionId}` : ''}`}
className="block h-[420px] group relative overflow-hidden rounded-2xl border border-white/5 transition-all duration-700 hover:shadow-[0_20px_50px_rgba(0,0,0,0.5)] active:scale-[0.98]" className="block h-fit group relative overflow-hidden rounded-xl bg-zinc-900 border border-zinc-800 transition-all duration-300 hover:border-zinc-700 active:scale-[0.98]"
> >
{/* Image Layer - Edge to Edge */} {/* Image Layer - Clean Split Top */}
<div className="absolute inset-0"> <div className="aspect-[4/3] overflow-hidden">
<img <img
src={getStorageUrl(bottle.image_url)} src={getStorageUrl(bottle.image_url)}
alt={bottle.name} alt={bottle.name}
className="w-full h-full object-cover group-hover:scale-110 transition-transform duration-1000 ease-out" className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-500 ease-out"
/> />
{/* Gradient Overlay as requested: bottom third, black to transparent */}
<div className="absolute inset-0 bg-gradient-to-t from-black via-black/60 to-transparent" />
</div> </div>
{/* Content Layer */} {/* Info Layer - Clean Split Bottom */}
<div className="absolute inset-0 flex flex-col justify-end p-6"> <div className="p-4 space-y-4">
<div className="space-y-3"> <div className="space-y-1">
{/* Tags Layer - Minimalist Glassmorphism */} <p className="text-[10px] font-bold text-orange-500 uppercase tracking-widest leading-none">
<div className="flex flex-wrap gap-2 opacity-0 group-hover:opacity-100 translate-y-4 group-hover:translate-y-0 transition-all duration-500">
<span className="px-3 py-1 bg-white/10 backdrop-blur-md border border-white/10 text-[9px] font-sans font-bold uppercase tracking-widest text-[#C89D46] rounded-full">
{shortenCategory(bottle.category)}
</span>
<span className="px-3 py-1 bg-white/10 backdrop-blur-md border border-white/10 text-[9px] font-sans font-bold uppercase tracking-widest text-white/60 rounded-full">
{bottle.abv}% VOL
</span>
</div>
<div>
<p className="text-[10px] font-sans font-bold text-[#C89D46] uppercase tracking-[0.2em] mb-1">
{bottle.distillery} {bottle.distillery}
</p> </p>
<h3 className="font-display font-bold text-2xl text-white leading-tight drop-shadow-lg"> <h3 className="font-bold text-lg text-zinc-50 leading-tight">
{bottle.name || t('grid.unknownBottle')} {bottle.name || t('grid.unknownBottle')}
</h3> </h3>
</div> </div>
<div className="flex flex-wrap gap-2">
<span className="px-2 py-0.5 bg-zinc-800 text-zinc-400 text-[9px] font-bold uppercase tracking-widest rounded-md">
{shortenCategory(bottle.category)}
</span>
<span className="px-2 py-0.5 bg-zinc-800 text-zinc-400 text-[9px] font-bold uppercase tracking-widest rounded-md">
{bottle.abv}% VOL
</span>
</div>
{/* Metadata items */} {/* Metadata items */}
<div className="flex items-center gap-4 pt-2 border-t border-white/10 opacity-60 group-hover:opacity-100 transition-opacity"> <div className="flex items-center gap-4 pt-3 border-t border-zinc-800/50">
<div className="flex items-center gap-1.5 text-[10px] font-sans font-medium text-white/70"> <div className="flex items-center gap-1 text-[10px] font-medium text-zinc-500">
<Calendar size={12} className="text-[#C89D46]" /> <Calendar size={12} className="text-zinc-500" />
{new Date(bottle.created_at).toLocaleDateString(locale === 'de' ? 'de-DE' : 'en-US')} {new Date(bottle.created_at).toLocaleDateString(locale === 'de' ? 'de-DE' : 'en-US')}
</div> </div>
{bottle.last_tasted && ( {bottle.last_tasted && (
<div className="flex items-center gap-1.5 text-[10px] font-sans font-medium text-white/70"> <div className="flex items-center gap-1 text-[10px] font-medium text-zinc-500">
<Clock size={12} className="text-[#C89D46]" /> <Clock size={12} className="text-zinc-500" />
{new Date(bottle.last_tasted).toLocaleDateString(locale === 'de' ? 'de-DE' : 'en-US')} {new Date(bottle.last_tasted).toLocaleDateString(locale === 'de' ? 'de-DE' : 'en-US')}
</div> </div>
)} )}
</div> </div>
</div> </div>
</div>
{/* Top Overlays */} {/* Top Overlays */}
{(bottle.is_whisky === false || (bottle.confidence && bottle.confidence < 70)) && ( {(bottle.is_whisky === false || (bottle.confidence && bottle.confidence < 70)) && (
<div className="absolute top-4 right-4 z-10"> <div className="absolute top-3 right-3 z-10">
<div className="bg-red-500/90 backdrop-blur-sm text-white p-2 rounded-full animate-pulse shadow-lg"> <div className="bg-red-500 text-white p-1.5 rounded-full shadow-lg">
<AlertCircle size={14} /> <AlertCircle size={12} />
</div> </div>
</div> </div>
)} )}
{sessionId && ( {sessionId && (
<div className="absolute top-4 left-4 z-10 bg-[#C89D46] text-[#0F1014] text-[9px] font-black px-3 py-1.5 rounded-full flex items-center gap-2 border border-white/20 shadow-xl"> <div className="absolute top-3 left-3 z-10 bg-orange-600 text-white text-[9px] font-bold px-2 py-1 rounded-md flex items-center gap-1.5 shadow-xl">
<PlusCircle size={14} /> <PlusCircle size={12} />
ADD TO SESSION ADD
</div> </div>
)} )}
</Link> </Link>
@@ -200,13 +194,13 @@ export default function BottleGrid({ bottles }: BottleGridProps) {
<div id="search-filter" className="w-full max-w-6xl mx-auto px-4 space-y-6 scroll-mt-32"> <div id="search-filter" className="w-full max-w-6xl mx-auto px-4 space-y-6 scroll-mt-32">
<div className="flex flex-col md:flex-row gap-4"> <div className="flex flex-col md:flex-row gap-4">
<div className="relative flex-1 group"> <div className="relative flex-1 group">
<Search className="absolute left-0 top-1/2 -translate-y-1/2 text-[#8F9096] group-focus-within:text-[#C89D46] transition-colors" size={20} /> <Search className="absolute left-0 top-1/2 -translate-y-1/2 text-zinc-500 group-focus-within:text-orange-500 transition-colors" size={20} />
<input <input
type="text" type="text"
placeholder={t('grid.searchPlaceholder')} placeholder={t('grid.searchPlaceholder')}
value={searchQuery} value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)} onChange={(e) => setSearchQuery(e.target.value)}
className="w-full pl-8 pr-8 py-4 bg-transparent border-b border-white/10 focus:border-[#C89D46] outline-none transition-all text-white placeholder:text-[#8F9096] font-sans" className="w-full pl-8 pr-8 py-4 bg-transparent border-b border-zinc-800 focus:border-orange-500 outline-none transition-all text-zinc-50 placeholder:text-zinc-500"
/> />
{searchQuery && ( {searchQuery && (
<button <button
@@ -222,24 +216,24 @@ export default function BottleGrid({ bottles }: BottleGridProps) {
<select <select
value={sortBy} value={sortBy}
onChange={(e) => setSortBy(e.target.value as any)} onChange={(e) => setSortBy(e.target.value as any)}
className="bg-transparent border-none text-[#8F9096] text-xs font-sans font-bold uppercase tracking-widest outline-none cursor-pointer hover:text-white transition-colors appearance-none" className="bg-transparent border-none text-zinc-500 text-xs font-bold uppercase tracking-widest outline-none cursor-pointer hover:text-white transition-colors appearance-none"
> >
<option value="created_at" className="bg-[#0F1014]">{t('grid.sortBy.createdAt')}</option> <option value="created_at" className="bg-zinc-950">{t('grid.sortBy.createdAt')}</option>
<option value="last_tasted" className="bg-[#0F1014]">{t('grid.sortBy.lastTasted')}</option> <option value="last_tasted" className="bg-zinc-950">{t('grid.sortBy.lastTasted')}</option>
<option value="name" className="bg-[#0F1014]">{t('grid.sortBy.name')}</option> <option value="name" className="bg-zinc-950">{t('grid.sortBy.name')}</option>
</select> </select>
<button <button
onClick={() => setIsFiltersOpen(!isFiltersOpen)} onClick={() => setIsFiltersOpen(!isFiltersOpen)}
className={`flex items-center gap-2 text-xs font-sans font-bold uppercase tracking-widest transition-all ${isFiltersOpen || activeFiltersCount > 0 className={`flex items-center gap-2 text-xs font-bold uppercase tracking-widest transition-all ${isFiltersOpen || activeFiltersCount > 0
? 'text-[#C89D46]' ? 'text-orange-500'
: 'text-[#8F9096] hover:text-white' : 'text-zinc-500 hover:text-white'
}`} }`}
> >
<Filter size={18} /> <Filter size={18} />
<span className="hidden sm:inline">{t('grid.filters')}</span> <span className="hidden sm:inline">{t('grid.filters')}</span>
{activeFiltersCount > 0 && ( {activeFiltersCount > 0 && (
<span className="bg-[#C89D46] text-[#0F1014] w-4 h-4 rounded-full flex items-center justify-center text-[8px] font-black"> <span className="bg-orange-600 text-white w-4 h-4 rounded-full flex items-center justify-center text-[8px] font-bold">
{activeFiltersCount} {activeFiltersCount}
</span> </span>
)} )}
@@ -247,13 +241,13 @@ export default function BottleGrid({ bottles }: BottleGridProps) {
</div> </div>
</div> </div>
{/* Category Quick Filter - Glass Chips */} {/* Category Quick Filter - Flat Chips */}
<div className="flex gap-3 overflow-x-auto pb-2 -mx-4 px-4 scrollbar-hide touch-pan-x"> <div className="flex gap-3 overflow-x-auto pb-2 -mx-4 px-4 scrollbar-hide touch-pan-x">
<button <button
onClick={() => setSelectedCategory(null)} onClick={() => setSelectedCategory(null)}
className={`px-4 py-2 rounded-full text-[10px] font-sans font-bold uppercase tracking-widest whitespace-nowrap transition-all border ${selectedCategory === null className={`px-4 py-2 rounded-full text-[10px] font-bold uppercase tracking-widest whitespace-nowrap transition-all border ${selectedCategory === null
? 'bg-[#C89D46] border-[#C89D46] text-[#0F1014]' ? 'bg-orange-600 border-orange-600 text-white'
: 'bg-white/5 border-white/10 text-[#8F9096] hover:border-white/20' : 'bg-zinc-900 border-zinc-800 text-zinc-400 hover:border-zinc-700'
}`} }`}
> >
{t('common.all')} {t('common.all')}
@@ -262,9 +256,9 @@ export default function BottleGrid({ bottles }: BottleGridProps) {
<button <button
key={cat} key={cat}
onClick={() => setSelectedCategory(selectedCategory === cat ? null : cat)} onClick={() => setSelectedCategory(selectedCategory === cat ? null : cat)}
className={`px-4 py-2 rounded-full text-[10px] font-sans font-bold uppercase tracking-widest whitespace-nowrap transition-all border ${selectedCategory === cat className={`px-4 py-2 rounded-full text-[10px] font-bold uppercase tracking-widest whitespace-nowrap transition-all border ${selectedCategory === cat
? 'bg-[#C89D46] border-[#C89D46] text-[#0F1014]' ? 'bg-orange-600 border-orange-600 text-white'
: 'bg-white/5 border-white/10 text-[#8F9096] hover:border-white/20' : 'bg-zinc-900 border-zinc-800 text-zinc-400 hover:border-zinc-700'
}`} }`}
> >
{shortenCategory(cat)} {shortenCategory(cat)}
@@ -272,17 +266,17 @@ export default function BottleGrid({ bottles }: BottleGridProps) {
))} ))}
</div> </div>
{/* Collapsible Advanced Filters - Minimalist Overlay */} {/* Collapsible Advanced Filters - Industrial Overlay */}
{isFiltersOpen && ( {isFiltersOpen && (
<div className="p-8 bg-[#1A1B20] border border-white/10 rounded-3xl space-y-8 animate-in fade-in slide-in-from-top-4 duration-500"> <div className="p-8 bg-zinc-900 border border-zinc-800 rounded-2xl space-y-8 animate-in fade-in slide-in-from-top-4 duration-500">
<div className="space-y-4"> <div className="space-y-4">
<label className="text-[10px] font-sans font-bold uppercase tracking-[0.2em] text-[#8F9096]">{t('grid.filter.distillery')}</label> <label className="text-[10px] font-bold uppercase tracking-[0.2em] text-zinc-500">{t('grid.filter.distillery')}</label>
<div className="flex flex-wrap gap-2"> <div className="flex flex-wrap gap-2">
<button <button
onClick={() => setSelectedDistillery(null)} onClick={() => setSelectedDistillery(null)}
className={`px-4 py-2 rounded-full text-[10px] font-sans font-bold uppercase tracking-widest transition-all border ${selectedDistillery === null className={`px-4 py-2 rounded-full text-[10px] font-bold uppercase tracking-widest transition-all border ${selectedDistillery === null
? 'bg-[#C89D46] border-[#C89D46] text-[#0F1014]' ? 'bg-orange-600 border-orange-600 text-white'
: 'bg-white/5 border-white/10 text-[#8F9096]' : 'bg-zinc-800 border-zinc-700 text-zinc-400'
}`} }`}
> >
{t('common.all')} {t('common.all')}
@@ -291,9 +285,9 @@ export default function BottleGrid({ bottles }: BottleGridProps) {
<button <button
key={dist} key={dist}
onClick={() => setSelectedDistillery(selectedDistillery === dist ? null : dist)} onClick={() => setSelectedDistillery(selectedDistillery === dist ? null : dist)}
className={`px-4 py-2 rounded-full text-[10px] font-sans font-bold uppercase tracking-widest transition-all border ${selectedDistillery === dist className={`px-4 py-2 rounded-full text-[10px] font-bold uppercase tracking-widest transition-all border ${selectedDistillery === dist
? 'bg-[#C89D46] border-[#C89D46] text-[#0F1014]' ? 'bg-orange-600 border-orange-600 text-white'
: 'bg-white/5 border-white/10 text-[#8F9096]' : 'bg-zinc-800 border-zinc-700 text-zinc-400'
}`} }`}
> >
{dist} {dist}
@@ -302,20 +296,20 @@ export default function BottleGrid({ bottles }: BottleGridProps) {
</div> </div>
</div> </div>
<div className="pt-6 border-t border-white/5 flex justify-between items-center"> <div className="pt-6 border-t border-zinc-800 flex justify-between items-center">
<button <button
onClick={() => { onClick={() => {
setSelectedCategory(null); setSelectedCategory(null);
setSelectedDistillery(null); setSelectedDistillery(null);
setSearchQuery(''); setSearchQuery('');
}} }}
className="text-[10px] font-sans font-bold text-red-500 uppercase tracking-widest hover:text-red-400" className="text-[10px] font-bold text-red-500 uppercase tracking-widest hover:text-red-400"
> >
{t('grid.resetAll')} {t('grid.resetAll')}
</button> </button>
<button <button
onClick={() => setIsFiltersOpen(false)} onClick={() => setIsFiltersOpen(false)}
className="px-8 py-3 bg-white text-[#0F1014] text-[10px] font-sans font-bold rounded-full uppercase tracking-widest transition-transform active:scale-95" className="px-8 py-3 bg-zinc-50 text-zinc-950 text-[10px] font-bold rounded-full uppercase tracking-widest transition-transform active:scale-95"
> >
{t('grid.close')} {t('grid.close')}
</button> </button>

View File

@@ -31,7 +31,7 @@ export const BottomNavigation = ({ onHome, onShelf, onSearch, onProfile, onScan
}; };
return ( return (
<div className="fixed bottom-0 left-0 w-full z-50 pointer-events-none"> <div className="fixed bottom-6 left-1/2 -translate-x-1/2 w-[90%] max-w-sm z-50 pointer-events-none">
{/* Hidden Input for Scanning */} {/* Hidden Input for Scanning */}
<input <input
type="file" type="file"
@@ -41,68 +41,50 @@ export const BottomNavigation = ({ onHome, onShelf, onSearch, onProfile, onScan
className="hidden" className="hidden"
/> />
{/* Background Container mit Glassmorphism */} <div className="flex items-center justify-between px-2 py-2 bg-zinc-900/90 backdrop-blur-lg border border-zinc-800 rounded-full shadow-2xl pointer-events-auto">
<div className="relative bg-[#0F1014]/90 backdrop-blur-xl border-t border-white/10 pb-safe pt-2 pointer-events-auto shadow-[0_-10px_40px_rgba(0,0,0,0.5)]"> {/* Left Items */}
<div className="flex justify-between items-end px-6 h-16">
{/* Left Actions */}
<button <button
onClick={onHome} onClick={onHome}
className="flex flex-col items-center gap-1 text-[#C89D46] w-12 transition-all active:scale-90" className="p-3 text-zinc-400 hover:text-white transition-colors active:scale-90"
aria-label="Home"
> >
<Home size={24} /> <Home size={22} strokeWidth={2.5} />
<span className="text-[10px] font-medium font-sans uppercase tracking-widest opacity-80">Home</span>
</button> </button>
<button <button
onClick={onShelf} onClick={onShelf}
className="flex flex-col items-center gap-1 text-[#8F9096] hover:text-white w-12 transition-all active:scale-90" className="p-3 text-zinc-400 hover:text-white transition-colors active:scale-90"
aria-label="Shelf"
> >
<Grid size={24} /> <Grid size={22} strokeWidth={2.5} />
<span className="text-[10px] font-medium font-sans uppercase tracking-widest opacity-80">Shelf</span>
</button> </button>
{/* Spacer für den Center Button */} {/* PRIMARY ACTION - The "Industrial Button" */}
<div className="w-16" /> <button
onClick={handleScanClick}
className="flex items-center justify-center w-14 h-14 rounded-full bg-orange-600 text-white hover:bg-orange-500 active:scale-95 transition-all mx-2 shadow-lg shadow-orange-950/40"
aria-label="Scan Bottle"
>
<Scan size={26} strokeWidth={2.5} />
</button>
{/* Right Actions */} {/* Right Items */}
<button <button
onClick={onSearch} onClick={onSearch}
className="flex flex-col items-center gap-1 text-[#8F9096] hover:text-white w-12 transition-all active:scale-90" className="p-3 text-zinc-400 hover:text-white transition-colors active:scale-90"
aria-label="Search"
> >
<Search size={24} /> <Search size={22} strokeWidth={2.5} />
<span className="text-[10px] font-medium font-sans uppercase tracking-widest opacity-80">Search</span>
</button> </button>
<button <button
onClick={onProfile} onClick={onProfile}
className="flex flex-col items-center gap-1 text-[#8F9096] hover:text-white w-12 transition-all active:scale-90" className="p-3 text-zinc-400 hover:text-white transition-colors active:scale-90"
aria-label="Profile"
> >
<User size={24} /> <User size={22} strokeWidth={2.5} />
<span className="text-[10px] font-medium font-sans uppercase tracking-widest opacity-80">Admin</span>
</button> </button>
</div> </div>
{/* THE FLOATING MAGIC BUTTON */}
<div className="absolute -top-10 left-1/2 -translate-x-1/2 pointer-events-auto">
<button
onClick={handleScanClick}
className="flex items-center justify-center w-20 h-20 rounded-full
bg-gradient-to-tr from-[#C89D46] to-[#E0B456]
shadow-[0_0_30px_rgba(200,157,70,0.4)]
border-[6px] border-[#0F1014]
active:scale-95 transition-transform duration-200"
aria-label="Scan Bottle"
>
<div className="bg-[#0F1014] p-3 rounded-full">
<Scan color="#C89D46" size={32} strokeWidth={2.5} />
</div>
</button>
{/* Visual Gold Glow */}
<div className="absolute inset-0 rounded-full bg-[#C89D46]/20 blur-2xl -z-10 animate-pulse" />
</div>
</div>
</div> </div>
); );
}; };

View File

@@ -80,18 +80,18 @@ export default function BuddyList() {
}; };
return ( return (
<div className="bg-[#1A1B21] rounded-3xl p-6 border border-white/5 shadow-xl transition-all duration-300"> <div className="bg-zinc-900 rounded-3xl p-6 border border-zinc-800 shadow-xl transition-all duration-300">
<div className="flex items-center justify-between mb-6"> <div className="flex items-center justify-between mb-6">
<h3 className="text-sm font-sans font-bold uppercase tracking-[0.2em] flex items-center gap-2 text-[#8F9096]"> <h3 className="text-sm font-bold uppercase tracking-[0.2em] flex items-center gap-2 text-zinc-400">
<Users size={18} className="text-[#C89D46]" /> <Users size={18} className="text-orange-600" />
{t('buddy.title')} {t('buddy.title')}
{!isCollapsed && buddies.length > 0 && ( {!isCollapsed && buddies.length > 0 && (
<span className="text-[10px] font-sans font-bold opacity-50 ml-2">({buddies.length})</span> <span className="text-[10px] font-bold opacity-50 ml-2">({buddies.length})</span>
)} )}
</h3> </h3>
<button <button
onClick={handleToggleCollapse} onClick={handleToggleCollapse}
className="p-2 hover:bg-white/5 rounded-xl transition-colors text-[#8F9096] hover:text-[#C89D46]" className="p-2 hover:bg-zinc-800 rounded-xl transition-colors text-zinc-500 hover:text-orange-600"
title={isCollapsed ? 'Aufklappen' : 'Einklappen'} title={isCollapsed ? 'Aufklappen' : 'Einklappen'}
> >
{isCollapsed ? <ChevronDown size={20} /> : <ChevronUp size={20} />} {isCollapsed ? <ChevronDown size={20} /> : <ChevronUp size={20} />}
@@ -106,23 +106,23 @@ export default function BuddyList() {
value={newName} value={newName}
onChange={(e) => setNewName(e.target.value)} onChange={(e) => setNewName(e.target.value)}
placeholder={t('buddy.placeholder')} placeholder={t('buddy.placeholder')}
className="flex-1 bg-white/5 border border-white/10 rounded-xl px-4 py-2 text-sm text-white placeholder:text-[#8F9096] focus:outline-none focus:border-[#C89D46] transition-colors" className="flex-1 bg-zinc-950 border border-zinc-800 rounded-xl px-4 py-2 text-sm text-zinc-50 placeholder:text-zinc-600 focus:outline-none focus:border-orange-600 transition-colors"
/> />
<button <button
type="submit" type="submit"
disabled={isAdding || !newName.trim()} disabled={isAdding || !newName.trim()}
className="bg-[#C89D46] hover:bg-[#A67D2E] text-[#0F1014] p-2 rounded-xl transition-all disabled:opacity-50" className="bg-orange-600 hover:bg-orange-700 text-white p-2 rounded-xl transition-all disabled:opacity-50"
> >
{isAdding ? <Loader2 size={20} className="animate-spin" /> : <UserPlus size={20} />} {isAdding ? <Loader2 size={20} className="animate-spin" /> : <UserPlus size={20} />}
</button> </button>
</form> </form>
{isLoading ? ( {isLoading ? (
<div className="flex justify-center py-8 text-[#8F9096]"> <div className="flex justify-center py-8 text-zinc-500">
<Loader2 size={24} className="animate-spin" /> <Loader2 size={24} className="animate-spin" />
</div> </div>
) : buddies.length === 0 ? ( ) : buddies.length === 0 ? (
<div className="text-center py-8 text-[#8F9096] text-xs font-sans"> <div className="text-center py-8 text-zinc-600 text-xs font-bold">
{t('buddy.noBuddies')} {t('buddy.noBuddies')}
</div> </div>
) : ( ) : (
@@ -130,22 +130,22 @@ export default function BuddyList() {
{buddies.map((buddy) => ( {buddies.map((buddy) => (
<div <div
key={buddy.id} key={buddy.id}
className="flex items-center justify-between p-4 bg-white/5 rounded-[2rem] border border-white/5 group hover:border-white/10 transition-all duration-300" className="flex items-center justify-between p-4 bg-zinc-950 rounded-2xl border border-zinc-800 group hover:border-zinc-700 transition-all duration-300"
> >
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-2xl bg-white/5 border border-white/10 flex items-center justify-center text-[#C89D46] font-display font-bold shadow-inner"> <div className="w-10 h-10 rounded-xl bg-zinc-900 border border-zinc-800 flex items-center justify-center text-orange-600 font-bold shadow-inner">
{buddy.name[0].toUpperCase()} {buddy.name[0].toUpperCase()}
</div> </div>
<div className="flex flex-col"> <div className="flex flex-col">
<span className="font-bold text-white text-sm tracking-tight">{buddy.name}</span> <span className="font-bold text-zinc-50 text-sm tracking-tight">{buddy.name}</span>
{buddy.buddy_profile_id && ( {buddy.buddy_profile_id && (
<span className="text-[9px] font-sans font-black uppercase text-[#C89D46]/80 tracking-widest">{t('common.link')}</span> <span className="text-[9px] font-bold uppercase text-orange-500/80 tracking-widest">{t('common.link')}</span>
)} )}
</div> </div>
</div> </div>
<button <button
onClick={() => handleDeleteBuddy(buddy.id)} onClick={() => handleDeleteBuddy(buddy.id)}
className="text-[#8F9096] hover:text-red-400 opacity-0 group-hover:opacity-100 transition-all p-2 hover:bg-white/5 rounded-xl" className="text-zinc-600 hover:text-red-500 opacity-0 group-hover:opacity-100 transition-all p-2 hover:bg-zinc-800 rounded-xl"
> >
<Trash2 size={16} /> <Trash2 size={16} />
</button> </button>
@@ -160,17 +160,17 @@ export default function BuddyList() {
<div className="flex items-center gap-2 animate-in fade-in slide-in-from-top-1"> <div className="flex items-center gap-2 animate-in fade-in slide-in-from-top-1">
<div className="flex -space-x-1.5 overflow-hidden"> <div className="flex -space-x-1.5 overflow-hidden">
{buddies.slice(0, 5).map((b, i) => ( {buddies.slice(0, 5).map((b, i) => (
<div key={b.id} className="w-7 h-7 rounded-lg bg-white/5 border border-white/10 flex items-center justify-center text-[10px] font-display font-bold text-[#C89D46] shadow-sm"> <div key={b.id} className="w-7 h-7 rounded-lg bg-zinc-950 border border-zinc-800 flex items-center justify-center text-[10px] font-bold text-orange-600 shadow-sm">
{b.name[0].toUpperCase()} {b.name[0].toUpperCase()}
</div> </div>
))} ))}
{buddies.length > 5 && ( {buddies.length > 5 && (
<div className="w-7 h-7 rounded-lg bg-white/5 border border-white/10 flex items-center justify-center text-[8px] font-sans font-bold text-[#8F9096] shadow-sm"> <div className="w-7 h-7 rounded-lg bg-zinc-950 border border-zinc-800 flex items-center justify-center text-[8px] font-bold text-zinc-500 shadow-sm">
+{buddies.length - 5} +{buddies.length - 5}
</div> </div>
)} )}
</div> </div>
<span className="text-[10px] text-[#8F9096] font-sans font-bold uppercase tracking-widest ml-1">{buddies.length} Buddies</span> <span className="text-[10px] text-zinc-500 font-bold uppercase tracking-widest ml-1">{buddies.length} Buddies</span>
</div> </div>
)} )}
</div> </div>

View File

@@ -44,7 +44,7 @@ export default function DramOfTheDay({ bottles }: DramOfTheDayProps) {
<button <button
onClick={suggestDram} onClick={suggestDram}
disabled={isRolling} disabled={isRolling}
className="flex items-center gap-2 px-4 sm:px-6 py-2 sm:py-3 bg-amber-600 hover:bg-amber-700 text-white rounded-2xl font-black uppercase tracking-widest text-[10px] sm:text-xs transition-all shadow-lg shadow-amber-600/20 active:scale-95 disabled:opacity-50" className="flex items-center gap-2 px-4 sm:px-6 py-2 sm:py-3 bg-orange-600 hover:bg-orange-700 text-white rounded-2xl font-bold uppercase tracking-widest text-[10px] sm:text-xs transition-all shadow-lg shadow-orange-950/20 active:scale-95 disabled:opacity-50"
> >
{isRolling ? ( {isRolling ? (
<Dices size={18} className="animate-spin" /> <Dices size={18} className="animate-spin" />
@@ -55,23 +55,23 @@ export default function DramOfTheDay({ bottles }: DramOfTheDayProps) {
</button> </button>
{suggestion && ( {suggestion && (
<div className="fixed inset-0 z-[100] flex items-center justify-center p-6 bg-black/60 backdrop-blur-sm animate-in fade-in duration-300"> <div className="fixed inset-0 z-[100] flex items-center justify-center p-6 bg-zinc-950/80 backdrop-blur-sm animate-in fade-in duration-300">
<div className="bg-white dark:bg-zinc-900 w-full max-w-sm rounded-[40px] p-8 shadow-2xl border border-amber-500/20 relative animate-in zoom-in-95 duration-300"> <div className="bg-zinc-900 w-full max-w-sm rounded-[40px] p-8 shadow-2xl border border-orange-500/20 relative animate-in zoom-in-95 duration-300">
<button <button
onClick={() => setSuggestion(null)} onClick={() => setSuggestion(null)}
className="absolute top-6 right-6 text-zinc-400 hover:text-zinc-600 dark:hover:text-zinc-200" className="absolute top-6 right-6 text-zinc-500 hover:text-orange-600"
> >
<X size={24} /> <X size={24} />
</button> </button>
<div className="flex flex-col items-center text-center space-y-6"> <div className="flex flex-col items-center text-center space-y-6">
<div className="w-20 h-20 bg-amber-100 dark:bg-amber-900/30 rounded-3xl flex items-center justify-center text-amber-600"> <div className="w-20 h-20 bg-orange-900/30 rounded-3xl flex items-center justify-center text-orange-600">
<GlassWater size={40} strokeWidth={2.5} /> <GlassWater size={40} strokeWidth={2.5} />
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
<h3 className="text-[10px] font-black uppercase tracking-[0.2em] text-amber-600">{t('home.dramOfDay.title')}</h3> <h3 className="text-[10px] font-bold uppercase tracking-[0.2em] text-orange-600">{t('home.dramOfDay.title')}</h3>
<h2 className="text-2xl font-black text-zinc-900 dark:text-white leading-tight"> <h2 className="text-2xl font-bold text-zinc-50 leading-tight uppercase">
{suggestion.name} {suggestion.name}
</h2> </h2>
{suggestion.distillery && ( {suggestion.distillery && (
@@ -83,13 +83,13 @@ export default function DramOfTheDay({ bottles }: DramOfTheDayProps) {
<Link <Link
href={`/bottles/${suggestion.id}`} href={`/bottles/${suggestion.id}`}
onClick={() => setSuggestion(null)} onClick={() => setSuggestion(null)}
className="block w-full py-4 bg-zinc-900 dark:bg-zinc-100 text-white dark:text-zinc-900 rounded-2xl font-black uppercase tracking-widest text-xs hover:bg-amber-600 dark:hover:bg-amber-600 hover:text-white transition-all shadow-xl" className="block w-full py-4 bg-orange-600 text-white rounded-2xl font-bold uppercase tracking-widest text-xs hover:bg-orange-700 transition-all shadow-xl shadow-orange-950/20"
> >
{t('home.dramOfDay.viewBottle')} {t('home.dramOfDay.viewBottle')}
</Link> </Link>
<button <button
onClick={suggestDram} onClick={suggestDram}
className="w-full mt-3 py-2 text-zinc-400 hover:text-amber-600 text-[10px] font-black uppercase tracking-widest transition-colors" className="w-full mt-3 py-2 text-zinc-500 hover:text-orange-600 text-[10px] font-bold uppercase tracking-widest transition-colors"
> >
{t('home.dramOfDay.rollAgain')} {t('home.dramOfDay.rollAgain')}
</button> </button>

View File

@@ -123,9 +123,9 @@ export default function EditBottleForm({ bottle, onComplete }: EditBottleFormPro
} }
return ( return (
<div className="p-6 bg-white dark:bg-zinc-900 border border-amber-500/30 rounded-3xl shadow-xl space-y-4 animate-in zoom-in-95 duration-200"> <div className="p-6 bg-zinc-900 border border-orange-500/20 rounded-3xl shadow-2xl space-y-4 animate-in zoom-in-95 duration-200">
<div className="flex justify-between items-center mb-2"> <div className="flex justify-between items-center mb-2">
<h3 className="text-lg font-black text-amber-600 uppercase tracking-widest flex items-center gap-2"> <h3 className="text-lg font-black text-orange-600 uppercase tracking-widest flex items-center gap-2">
<Info size={18} /> {t('bottle.editTitle')} <Info size={18} /> {t('bottle.editTitle')}
</h3> </h3>
<button <button
@@ -143,7 +143,7 @@ export default function EditBottleForm({ bottle, onComplete }: EditBottleFormPro
type="text" type="text"
value={formData.name} value={formData.name}
onChange={(e) => setFormData({ ...formData, name: e.target.value })} onChange={(e) => setFormData({ ...formData, name: e.target.value })}
className="w-full px-4 py-2 bg-zinc-50 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-xl outline-none focus:ring-2 focus:ring-amber-500" className="w-full px-4 py-2 bg-zinc-50 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-xl outline-none focus:ring-2 focus:ring-orange-600/50"
/> />
</div> </div>
<div className="space-y-1"> <div className="space-y-1">
@@ -152,7 +152,7 @@ export default function EditBottleForm({ bottle, onComplete }: EditBottleFormPro
type="text" type="text"
value={formData.distillery} value={formData.distillery}
onChange={(e) => setFormData({ ...formData, distillery: e.target.value })} onChange={(e) => setFormData({ ...formData, distillery: e.target.value })}
className="w-full px-4 py-2 bg-zinc-50 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-xl outline-none focus:ring-2 focus:ring-amber-500" className="w-full px-4 py-2 bg-zinc-50 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-xl outline-none focus:ring-2 focus:ring-orange-600/50"
/> />
</div> </div>
<div className="space-y-1"> <div className="space-y-1">
@@ -161,7 +161,7 @@ export default function EditBottleForm({ bottle, onComplete }: EditBottleFormPro
type="text" type="text"
value={formData.category} value={formData.category}
onChange={(e) => setFormData({ ...formData, category: e.target.value })} onChange={(e) => setFormData({ ...formData, category: e.target.value })}
className="w-full px-4 py-2 bg-zinc-50 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-xl outline-none focus:ring-2 focus:ring-amber-500" className="w-full px-4 py-2 bg-zinc-50 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-xl outline-none focus:ring-2 focus:ring-orange-600/50"
/> />
</div> </div>
<div className="grid grid-cols-2 gap-2"> <div className="grid grid-cols-2 gap-2">
@@ -172,7 +172,7 @@ export default function EditBottleForm({ bottle, onComplete }: EditBottleFormPro
step="0.1" step="0.1"
value={formData.abv} value={formData.abv}
onChange={(e) => setFormData({ ...formData, abv: parseFloat(e.target.value) })} onChange={(e) => setFormData({ ...formData, abv: parseFloat(e.target.value) })}
className="w-full px-4 py-2 bg-zinc-50 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-xl outline-none focus:ring-2 focus:ring-amber-500" className="w-full px-4 py-2 bg-zinc-50 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-xl outline-none focus:ring-2 focus:ring-orange-600/50"
/> />
</div> </div>
<div className="space-y-1"> <div className="space-y-1">
@@ -181,7 +181,7 @@ export default function EditBottleForm({ bottle, onComplete }: EditBottleFormPro
type="number" type="number"
value={formData.age} value={formData.age}
onChange={(e) => setFormData({ ...formData, age: parseInt(e.target.value) })} onChange={(e) => setFormData({ ...formData, age: parseInt(e.target.value) })}
className="w-full px-4 py-2 bg-zinc-50 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-xl outline-none focus:ring-2 focus:ring-amber-500" className="w-full px-4 py-2 bg-zinc-50 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-xl outline-none focus:ring-2 focus:ring-orange-600/50"
/> />
</div> </div>
</div> </div>
@@ -192,7 +192,7 @@ export default function EditBottleForm({ bottle, onComplete }: EditBottleFormPro
type="button" type="button"
onClick={handleDiscover} onClick={handleDiscover}
disabled={isSearching} disabled={isSearching}
className="text-amber-600 hover:text-amber-700 flex items-center gap-1 normal-case font-bold" className="text-orange-600 hover:text-orange-700 flex items-center gap-1 normal-case font-bold"
> >
{isSearching ? <Loader2 size={10} className="animate-spin" /> : <Search size={10} />} {isSearching ? <Loader2 size={10} className="animate-spin" /> : <Search size={10} />}
{t('bottle.autoSearch')} {t('bottle.autoSearch')}
@@ -202,17 +202,17 @@ export default function EditBottleForm({ bottle, onComplete }: EditBottleFormPro
type="text" type="text"
value={formData.whiskybase_id} value={formData.whiskybase_id}
onChange={(e) => setFormData({ ...formData, whiskybase_id: e.target.value })} onChange={(e) => setFormData({ ...formData, whiskybase_id: e.target.value })}
className="w-full px-4 py-2 bg-zinc-50 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-xl outline-none focus:ring-2 focus:ring-amber-500" className="w-full px-4 py-2 bg-zinc-50 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-xl outline-none focus:ring-2 focus:ring-orange-600/50"
/> />
{discoveryResult && ( {discoveryResult && (
<div className="mt-2 p-3 bg-zinc-50 dark:bg-zinc-800/50 border border-amber-500/20 rounded-xl animate-in fade-in slide-in-from-top-2"> <div className="mt-2 p-3 bg-zinc-950 border border-orange-500/20 rounded-xl animate-in fade-in slide-in-from-top-2">
<p className="text-[10px] text-zinc-500 mb-2">Treffer gefunden:</p> <p className="text-[10px] text-zinc-500 mb-2">Treffer gefunden:</p>
<p className="text-[11px] font-bold text-zinc-800 dark:text-zinc-200 mb-2 truncate">{discoveryResult.title}</p> <p className="text-[11px] font-bold text-zinc-200 mb-2 truncate">{discoveryResult.title}</p>
<div className="flex gap-2"> <div className="flex gap-2">
<button <button
type="button" type="button"
onClick={applyDiscovery} onClick={applyDiscovery}
className="px-3 py-1.5 bg-amber-600 text-white text-[10px] font-black uppercase rounded-lg hover:bg-amber-700 transition-colors" className="px-3 py-1.5 bg-orange-600 text-white text-[10px] font-black uppercase rounded-lg hover:bg-orange-700 transition-colors"
> >
{t('bottle.applyId')} {t('bottle.applyId')}
</button> </button>
@@ -236,7 +236,7 @@ export default function EditBottleForm({ bottle, onComplete }: EditBottleFormPro
placeholder="0.00" placeholder="0.00"
value={formData.purchase_price} value={formData.purchase_price}
onChange={(e) => setFormData({ ...formData, purchase_price: e.target.value })} onChange={(e) => setFormData({ ...formData, purchase_price: e.target.value })}
className="w-full px-4 py-2 bg-amber-50 dark:bg-amber-900/10 border border-amber-200 dark:border-amber-900/30 rounded-xl outline-none focus:ring-2 focus:ring-amber-500 font-bold text-amber-700 dark:text-amber-400" className="w-full px-4 py-2 bg-zinc-950 border border-zinc-800 rounded-xl outline-none focus:ring-2 focus:ring-orange-600/50 font-bold text-zinc-100"
/> />
</div> </div>
@@ -247,7 +247,7 @@ export default function EditBottleForm({ bottle, onComplete }: EditBottleFormPro
placeholder="z.B. 2010" placeholder="z.B. 2010"
value={formData.distilled_at} value={formData.distilled_at}
onChange={(e) => setFormData({ ...formData, distilled_at: e.target.value })} onChange={(e) => setFormData({ ...formData, distilled_at: e.target.value })}
className="w-full px-4 py-2 bg-zinc-50 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-xl outline-none focus:ring-2 focus:ring-amber-500" className="w-full px-4 py-2 bg-zinc-50 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-xl outline-none focus:ring-2 focus:ring-orange-600/50"
/> />
</div> </div>
@@ -258,7 +258,7 @@ export default function EditBottleForm({ bottle, onComplete }: EditBottleFormPro
placeholder="z.B. 2022" placeholder="z.B. 2022"
value={formData.bottled_at} value={formData.bottled_at}
onChange={(e) => setFormData({ ...formData, bottled_at: e.target.value })} onChange={(e) => setFormData({ ...formData, bottled_at: e.target.value })}
className="w-full px-4 py-2 bg-zinc-50 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-xl outline-none focus:ring-2 focus:ring-amber-500" className="w-full px-4 py-2 bg-zinc-50 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-xl outline-none focus:ring-2 focus:ring-orange-600/50"
/> />
</div> </div>
@@ -269,7 +269,7 @@ export default function EditBottleForm({ bottle, onComplete }: EditBottleFormPro
placeholder="z.B. Batch 12 oder L-Code" placeholder="z.B. Batch 12 oder L-Code"
value={formData.batch_info} value={formData.batch_info}
onChange={(e) => setFormData({ ...formData, batch_info: e.target.value })} onChange={(e) => setFormData({ ...formData, batch_info: e.target.value })}
className="w-full px-4 py-2 bg-zinc-50 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-xl outline-none focus:ring-2 focus:ring-amber-500" className="w-full px-4 py-2 bg-zinc-50 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-xl outline-none focus:ring-2 focus:ring-orange-600/50"
/> />
</div> </div>
</div> </div>
@@ -279,7 +279,7 @@ export default function EditBottleForm({ bottle, onComplete }: EditBottleFormPro
<button <button
onClick={handleSave} onClick={handleSave}
disabled={isSaving} disabled={isSaving}
className="w-full py-4 bg-amber-600 hover:bg-amber-700 text-white rounded-2xl font-black uppercase tracking-widest transition-all flex items-center justify-center gap-2 shadow-lg shadow-amber-600/20 disabled:opacity-50" className="w-full py-4 bg-orange-600 hover:bg-orange-700 text-white rounded-2xl font-black uppercase tracking-widest transition-all flex items-center justify-center gap-2 shadow-xl shadow-orange-950/20 disabled:opacity-50"
> >
{isSaving ? <div className="animate-spin rounded-full h-5 w-5 border-b-2 border-white"></div> : <Save size={20} />} {isSaving ? <div className="animate-spin rounded-full h-5 w-5 border-b-2 border-white"></div> : <Save size={20} />}
{t('bottle.saveChanges')} {t('bottle.saveChanges')}

View File

@@ -38,7 +38,7 @@ export default function FloatingScannerButton({ onImageSelected }: FloatingScann
whileTap={{ scale: 0.9 }} whileTap={{ scale: 0.9 }}
initial={{ y: 100, opacity: 0 }} initial={{ y: 100, opacity: 0 }}
animate={{ y: 0, opacity: 1 }} animate={{ y: 0, opacity: 1 }}
className="relative group p-6 rounded-full bg-amber-600 text-black shadow-[0_0_30px_rgba(217,119,6,0.4)] hover:shadow-[0_0_40px_rgba(217,119,6,0.6)] transition-all overflow-hidden" className="relative group p-6 rounded-full bg-orange-600 text-black shadow-lg shadow-orange-950/40 hover:shadow-orange-950/60 transition-all overflow-hidden"
> >
{/* Shine Animation */} {/* Shine Animation */}
<motion.div <motion.div
@@ -57,7 +57,7 @@ export default function FloatingScannerButton({ onImageSelected }: FloatingScann
<Camera size={32} strokeWidth={2.5} className="relative z-10" /> <Camera size={32} strokeWidth={2.5} className="relative z-10" />
{/* Pulse ring */} {/* Pulse ring */}
<span className="absolute inset-0 rounded-full border-4 border-amber-600 animate-ping opacity-20" /> <span className="absolute inset-0 rounded-full border-4 border-orange-600 animate-ping opacity-20" />
</motion.button> </motion.button>
</div> </div>
); );

View File

@@ -11,7 +11,7 @@ const LanguageSwitcher = () => {
<button <button
onClick={() => setLocale('de')} onClick={() => setLocale('de')}
className={`p-1.5 rounded-lg transition-all ${locale === 'de' className={`p-1.5 rounded-lg transition-all ${locale === 'de'
? 'bg-amber-100 dark:bg-amber-900/30 scale-110 shadow-sm' ? 'bg-orange-950/30 scale-110 shadow-sm shadow-orange-950/20'
: 'opacity-50 hover:opacity-100 grayscale hover:grayscale-0' : 'opacity-50 hover:opacity-100 grayscale hover:grayscale-0'
}`} }`}
title="Deutsch" title="Deutsch"
@@ -21,7 +21,7 @@ const LanguageSwitcher = () => {
<button <button
onClick={() => setLocale('en')} onClick={() => setLocale('en')}
className={`p-1.5 rounded-lg transition-all ${locale === 'en' className={`p-1.5 rounded-lg transition-all ${locale === 'en'
? 'bg-amber-100 dark:bg-amber-900/30 scale-110 shadow-sm' ? 'bg-orange-950/30 scale-110 shadow-sm shadow-orange-950/20'
: 'opacity-50 hover:opacity-100 grayscale hover:grayscale-0' : 'opacity-50 hover:opacity-100 grayscale hover:grayscale-0'
}`} }`}
title="English" title="English"

View File

@@ -103,8 +103,8 @@ export default function OfflineIndicator() {
return ( return (
<div className="flex items-center gap-1.5 px-2.5 py-1 bg-green-600/10 border border-green-600/20 rounded-full group cursor-help relative animate-in fade-in zoom-in duration-500"> <div className="flex items-center gap-1.5 px-2.5 py-1 bg-green-600/10 border border-green-600/20 rounded-full group cursor-help relative animate-in fade-in zoom-in duration-500">
<ShieldCheck size={10} className="text-green-600" /> <ShieldCheck size={10} className="text-green-600" />
<span className="text-[9px] font-black uppercase tracking-widest text-green-600">Offline-Modus aktiv</span> <span className="text-[9px] font-bold uppercase tracking-widest text-green-600">Offline-Modus aktiv</span>
<div className="absolute top-full left-1/2 -translate-x-1/2 mt-2 w-48 p-2 bg-zinc-900 text-[10px] text-zinc-400 rounded-xl border border-white/10 opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none z-50 shadow-2xl text-center"> <div className="absolute top-full left-1/2 -translate-x-1/2 mt-2 w-48 p-2 bg-zinc-900 text-[10px] text-zinc-400 rounded-xl border border-zinc-800 opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none z-50 shadow-2xl text-center">
Alle Funktionen sind vollständig offline verfügbar. Alle Funktionen sind vollständig offline verfügbar.
</div> </div>
</div> </div>
@@ -112,9 +112,9 @@ export default function OfflineIndicator() {
} }
return ( return (
<div className="flex items-center gap-1.5 px-2.5 py-1 bg-amber-600/10 border border-amber-600/20 rounded-full animate-in fade-in slide-in-from-right-2"> <div className="flex items-center gap-1.5 px-2.5 py-1 bg-orange-600/10 border border-orange-600/20 rounded-full animate-in fade-in slide-in-from-right-2">
<Loader2 size={10} className="text-amber-600 animate-spin" /> <Loader2 size={10} className="text-orange-600 animate-spin" />
<span className="text-[9px] font-black uppercase tracking-widest text-amber-600"> <span className="text-[9px] font-bold uppercase tracking-widest text-orange-600">
{progress > 0 ? `Vorbereitung... ${progress}%` : 'Warte auf System...'} {progress > 0 ? `Vorbereitung... ${progress}%` : 'Warte auf System...'}
</span> </span>
</div> </div>

View File

@@ -129,11 +129,11 @@ export default function PlanManagementClient({ initialPlans }: PlanManagementCli
return ( return (
<div className="space-y-6"> <div className="space-y-6">
{/* Actions Bar */} {/* Actions Bar */}
<div className="bg-white dark:bg-zinc-900 rounded-2xl p-6 border border-zinc-200 dark:border-zinc-800 shadow-sm"> <div className="bg-zinc-900 rounded-2xl p-6 border border-zinc-800 shadow-sm">
<div className="flex gap-3"> <div className="flex gap-3">
<button <button
onClick={handleCreate} onClick={handleCreate}
className="flex-1 py-3 bg-amber-600 hover:bg-amber-700 text-white font-bold rounded-xl transition-colors flex items-center justify-center gap-2" className="flex-1 py-3 bg-orange-600 hover:bg-orange-700 text-white font-bold rounded-2xl transition-colors flex items-center justify-center gap-2"
> >
<Plus size={18} /> <Plus size={18} />
Create New Plan Create New Plan
@@ -141,7 +141,7 @@ export default function PlanManagementClient({ initialPlans }: PlanManagementCli
<button <button
onClick={handleGrantCredits} onClick={handleGrantCredits}
disabled={loading} disabled={loading}
className="flex-1 py-3 bg-green-600 hover:bg-green-700 text-white font-bold rounded-xl transition-colors flex items-center justify-center gap-2 disabled:opacity-50" className="flex-1 py-3 bg-zinc-800 hover:bg-zinc-700 text-white font-bold rounded-2xl transition-colors flex items-center justify-center gap-2 disabled:opacity-50 border border-zinc-700"
> >
<Zap size={18} /> <Zap size={18} />
Grant Monthly Credits Grant Monthly Credits
@@ -164,27 +164,27 @@ export default function PlanManagementClient({ initialPlans }: PlanManagementCli
{plans.map((plan) => ( {plans.map((plan) => (
<div <div
key={plan.id} key={plan.id}
className={`bg-white dark:bg-zinc-900 rounded-2xl p-6 border-2 ${plan.is_active className={`bg-zinc-900 rounded-[32px] p-6 border-2 ${plan.is_active
? 'border-amber-200 dark:border-amber-800' ? 'border-orange-500/30'
: 'border-zinc-200 dark:border-zinc-800 opacity-60' : 'border-zinc-800 opacity-60'
} shadow-sm relative`} } shadow-sm relative`}
> >
{!plan.is_active && ( {!plan.is_active && (
<div className="absolute top-4 right-4 px-2 py-1 bg-zinc-200 dark:bg-zinc-700 text-zinc-600 dark:text-zinc-400 text-xs font-bold rounded"> <div className="absolute top-4 right-4 px-2 py-1 bg-zinc-800 text-zinc-400 text-[8px] font-bold uppercase tracking-widest rounded">
Inactive Inactive
</div> </div>
)} )}
<div className="mb-4"> <div className="mb-4">
<h3 className="text-2xl font-black text-zinc-900 dark:text-white">{plan.display_name}</h3> <h3 className="text-2xl font-bold text-white uppercase tracking-tighter">{plan.display_name}</h3>
<p className="text-xs text-zinc-500 mt-1">{plan.name}</p> <p className="text-[10px] font-bold text-zinc-500 uppercase tracking-widest mt-1">{plan.name}</p>
</div> </div>
<div className="mb-4"> <div className="mb-4">
<div className="text-3xl font-black text-amber-600">{plan.monthly_credits}</div> <div className="text-3xl font-bold text-orange-600 tracking-tighter">{plan.monthly_credits}</div>
<div className="text-xs text-zinc-500">Credits/Month</div> <div className="text-[10px] font-bold text-zinc-500 uppercase tracking-widest">Credits/Month</div>
</div> </div>
<div className="mb-4"> <div className="mb-4">
<div className="text-2xl font-bold text-zinc-900 dark:text-white">{plan.price.toFixed(2)}</div> <div className="text-2xl font-bold text-white tracking-tight">{plan.price.toFixed(2)}</div>
<div className="text-xs text-zinc-500">per month</div> <div className="text-[10px] font-bold text-zinc-500 uppercase tracking-widest leading-none mt-1">per month</div>
</div> </div>
{plan.description && ( {plan.description && (
<p className="text-sm text-zinc-600 dark:text-zinc-400 mb-4">{plan.description}</p> <p className="text-sm text-zinc-600 dark:text-zinc-400 mb-4">{plan.description}</p>
@@ -192,14 +192,14 @@ export default function PlanManagementClient({ initialPlans }: PlanManagementCli
<div className="flex gap-2"> <div className="flex gap-2">
<button <button
onClick={() => handleEdit(plan)} onClick={() => handleEdit(plan)}
className="flex-1 py-2 bg-zinc-900 dark:bg-zinc-100 text-white dark:text-zinc-900 font-bold rounded-lg text-sm transition-colors flex items-center justify-center gap-2" className="flex-1 py-2 bg-zinc-800 hover:bg-zinc-700 text-white font-bold rounded-xl text-[10px] uppercase tracking-widest transition-colors flex items-center justify-center gap-2 border border-zinc-700"
> >
<Edit size={14} /> <Edit size={14} />
Edit Edit
</button> </button>
<button <button
onClick={() => handleDelete(plan.id)} onClick={() => handleDelete(plan.id)}
className="px-3 py-2 bg-red-600 hover:bg-red-700 text-white font-bold rounded-lg text-sm transition-colors" className="px-3 py-2 bg-red-600/20 hover:bg-red-600 text-red-500 hover:text-white font-bold rounded-xl text-sm transition-colors border border-red-900/50"
> >
<Trash2 size={14} /> <Trash2 size={14} />
</button> </button>
@@ -210,15 +210,15 @@ export default function PlanManagementClient({ initialPlans }: PlanManagementCli
{/* Edit/Create Modal */} {/* Edit/Create Modal */}
{(editingPlan || isCreating) && ( {(editingPlan || isCreating) && (
<div className="fixed inset-0 bg-black/50 flex items-center justify-center p-4 z-50"> <div className="fixed inset-0 bg-black/80 backdrop-blur-sm flex items-center justify-center p-4 z-50">
<div className="bg-white dark:bg-zinc-900 rounded-2xl p-6 max-w-2xl w-full border border-zinc-200 dark:border-zinc-800"> <div className="bg-zinc-900 rounded-[32px] p-6 max-w-2xl w-full border border-zinc-800 shadow-2xl">
<div className="flex items-center justify-between mb-6"> <div className="flex items-center justify-between mb-6">
<h3 className="text-2xl font-black text-zinc-900 dark:text-white"> <h3 className="text-2xl font-bold text-white uppercase tracking-tighter">
{isCreating ? 'Create Plan' : 'Edit Plan'} {isCreating ? 'Create Plan' : 'Edit Plan'}
</h3> </h3>
<button <button
onClick={() => { setEditingPlan(null); setIsCreating(false); }} onClick={() => { setEditingPlan(null); setIsCreating(false); }}
className="p-2 hover:bg-zinc-100 dark:hover:bg-zinc-800 rounded-lg transition-colors" className="p-2 hover:bg-zinc-800 rounded-xl transition-colors text-zinc-500"
> >
<X size={20} /> <X size={20} />
</button> </button>
@@ -227,79 +227,79 @@ export default function PlanManagementClient({ initialPlans }: PlanManagementCli
<div className="space-y-4"> <div className="space-y-4">
<div className="grid grid-cols-2 gap-4"> <div className="grid grid-cols-2 gap-4">
<div> <div>
<label className="block text-xs font-bold text-zinc-400 uppercase mb-2">Name (ID)</label> <label className="block text-[10px] font-bold text-zinc-500 uppercase tracking-widest mb-2">Name (ID)</label>
<input <input
type="text" type="text"
value={formData.name} value={formData.name}
onChange={(e) => setFormData({ ...formData, name: e.target.value })} onChange={(e) => setFormData({ ...formData, name: e.target.value })}
placeholder="e.g. starter" placeholder="e.g. starter"
className="w-full px-4 py-2 bg-zinc-50 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-amber-500/50" className="w-full px-4 py-2 bg-zinc-800 border border-zinc-700 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-orange-600/50 text-white"
/> />
</div> </div>
<div> <div>
<label className="block text-xs font-bold text-zinc-400 uppercase mb-2">Display Name</label> <label className="block text-[10px] font-bold text-zinc-500 uppercase tracking-widest mb-2">Display Name</label>
<input <input
type="text" type="text"
value={formData.display_name} value={formData.display_name}
onChange={(e) => setFormData({ ...formData, display_name: e.target.value })} onChange={(e) => setFormData({ ...formData, display_name: e.target.value })}
placeholder="e.g. Starter" placeholder="e.g. Starter"
className="w-full px-4 py-2 bg-zinc-50 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-amber-500/50" className="w-full px-4 py-2 bg-zinc-800 border border-zinc-700 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-orange-600/50 text-white"
/> />
</div> </div>
</div> </div>
<div className="grid grid-cols-2 gap-4"> <div className="grid grid-cols-2 gap-4">
<div> <div>
<label className="block text-xs font-bold text-zinc-400 uppercase mb-2">Monthly Credits</label> <label className="block text-[10px] font-bold text-zinc-500 uppercase tracking-widest mb-2">Monthly Credits</label>
<input <input
type="number" type="number"
value={formData.monthly_credits} value={formData.monthly_credits}
onChange={(e) => setFormData({ ...formData, monthly_credits: parseInt(e.target.value) || 0 })} onChange={(e) => setFormData({ ...formData, monthly_credits: parseInt(e.target.value) || 0 })}
className="w-full px-4 py-2 bg-zinc-50 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-amber-500/50" className="w-full px-4 py-2 bg-zinc-800 border border-zinc-700 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-orange-600/50 text-white"
/> />
</div> </div>
<div> <div>
<label className="block text-xs font-bold text-zinc-400 uppercase mb-2">Price ()</label> <label className="block text-[10px] font-bold text-zinc-500 uppercase tracking-widest mb-2">Price ()</label>
<input <input
type="number" type="number"
step="0.01" step="0.01"
value={formData.price} value={formData.price}
onChange={(e) => setFormData({ ...formData, price: parseFloat(e.target.value) || 0 })} onChange={(e) => setFormData({ ...formData, price: parseFloat(e.target.value) || 0 })}
className="w-full px-4 py-2 bg-zinc-50 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-amber-500/50" className="w-full px-4 py-2 bg-zinc-800 border border-zinc-700 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-orange-600/50 text-white"
/> />
</div> </div>
</div> </div>
<div> <div>
<label className="block text-xs font-bold text-zinc-400 uppercase mb-2">Description</label> <label className="block text-[10px] font-bold text-zinc-500 uppercase tracking-widest mb-2">Description</label>
<textarea <textarea
value={formData.description} value={formData.description}
onChange={(e) => setFormData({ ...formData, description: e.target.value })} onChange={(e) => setFormData({ ...formData, description: e.target.value })}
placeholder="Brief description of the plan" placeholder="Brief description of the plan"
rows={3} rows={3}
className="w-full px-4 py-2 bg-zinc-50 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-amber-500/50" className="w-full px-4 py-2 bg-zinc-800 border border-zinc-700 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-orange-600/50 text-white"
/> />
</div> </div>
<div className="grid grid-cols-2 gap-4"> <div className="grid grid-cols-2 gap-4">
<div> <div>
<label className="block text-xs font-bold text-zinc-400 uppercase mb-2">Sort Order</label> <label className="block text-[10px] font-bold text-zinc-500 uppercase tracking-widest mb-2">Sort Order</label>
<input <input
type="number" type="number"
value={formData.sort_order} value={formData.sort_order}
onChange={(e) => setFormData({ ...formData, sort_order: parseInt(e.target.value) || 0 })} onChange={(e) => setFormData({ ...formData, sort_order: parseInt(e.target.value) || 0 })}
className="w-full px-4 py-2 bg-zinc-50 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-amber-500/50" className="w-full px-4 py-2 bg-zinc-800 border border-zinc-700 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-orange-600/50 text-white"
/> />
</div> </div>
<div className="flex items-end"> <div className="flex items-end">
<label className="flex items-center gap-2 cursor-pointer"> <label className="flex items-center gap-2 cursor-pointer pb-2">
<input <input
type="checkbox" type="checkbox"
checked={formData.is_active} checked={formData.is_active}
onChange={(e) => setFormData({ ...formData, is_active: e.target.checked })} onChange={(e) => setFormData({ ...formData, is_active: e.target.checked })}
className="w-5 h-5 rounded border-zinc-300 text-amber-600 focus:ring-amber-500" className="w-5 h-5 rounded border-zinc-700 bg-zinc-800 text-orange-600 focus:ring-orange-600"
/> />
<span className="text-sm font-bold text-zinc-900 dark:text-white">Active</span> <span className="text-sm font-bold text-white">Active</span>
</label> </label>
</div> </div>
</div> </div>
@@ -307,7 +307,7 @@ export default function PlanManagementClient({ initialPlans }: PlanManagementCli
<button <button
onClick={handleSave} onClick={handleSave}
disabled={loading} disabled={loading}
className="w-full py-3 bg-amber-600 hover:bg-amber-700 text-white font-bold rounded-xl transition-colors disabled:opacity-50 flex items-center justify-center gap-2" className="w-full py-4 bg-orange-600 hover:bg-orange-700 text-white font-bold rounded-2xl transition-all shadow-lg shadow-orange-950/20 disabled:opacity-50 flex items-center justify-center gap-2 mt-4"
> >
<Check size={18} /> <Check size={18} />
{isCreating ? 'Create Plan' : 'Save Changes'} {isCreating ? 'Create Plan' : 'Save Changes'}

View File

@@ -34,75 +34,75 @@ export default function ResultCard({ data, bottleName, image, onShare }: ResultC
<motion.div <motion.div
initial={{ scale: 0.9, opacity: 0, rotateY: 30 }} initial={{ scale: 0.9, opacity: 0, rotateY: 30 }}
animate={{ scale: 1, opacity: 1, rotateY: 0 }} animate={{ scale: 1, opacity: 1, rotateY: 0 }}
transition={{ type: 'spring', damping: 15 }} transition={{ type: 'spring', damping: 20 }}
className="flex flex-col items-center gap-8 w-full max-w-sm" className="flex flex-col items-center gap-6 w-full max-w-sm"
> >
{/* The Trading Card */} {/* The Trading Card */}
<div className="relative w-full aspect-[9/16] rounded-[32px] overflow-hidden shadow-[0_20px_60px_rgba(0,0,0,0.8)] border border-white/20 bg-gradient-to-b from-[#1A1B20] to-black group"> <div className="relative w-full aspect-[3/4] rounded-[32px] overflow-hidden shadow-[0_20px_60px_rgba(0,0,0,0.9)] border border-zinc-800 bg-zinc-950 group">
{/* Bottle Image with Vignette */} {/* Bottle Image with Vignette */}
<div className="absolute inset-0"> <div className="absolute inset-0">
{image ? ( {image ? (
<img src={image} alt={bottleName} className="w-full h-full object-cover" /> <img src={image} alt={bottleName} className="w-full h-full object-cover" />
) : ( ) : (
<div className="absolute inset-0 bg-white/5 flex items-center justify-center opacity-20 text-[20px] font-black uppercase tracking-[1em] rotate-90">No Image</div> <div className="absolute inset-0 bg-zinc-900 flex items-center justify-center opacity-40 text-[20px] font-bold uppercase tracking-[1em] rotate-90">No Data</div>
)} )}
<div className="absolute inset-0 bg-gradient-to-t from-black via-black/20 to-transparent opacity-80" /> <div className="absolute inset-0 bg-gradient-to-t from-zinc-950 via-zinc-950/20 to-transparent opacity-90" />
</div> </div>
{/* Content Overlay */} {/* Content Overlay */}
<div className="absolute inset-x-0 bottom-0 p-8 space-y-6"> <div className="absolute inset-x-0 bottom-0 p-6 space-y-4">
<div className="space-y-1"> <div className="space-y-0.5">
<p className="text-[10px] font-black uppercase tracking-[0.3em] text-amber-500">Tasting Record</p> <p className="text-[10px] font-bold uppercase tracking-[0.3em] text-orange-600">Verified Tasting Record</p>
<h2 className="text-4xl font-black text-white truncate uppercase tracking-tight leading-none">{bottleName}</h2> <h2 className="text-3xl font-bold text-white truncate uppercase tracking-tighter leading-none">{bottleName}</h2>
</div> </div>
{/* Radar Chart Area */} {/* Radar Chart Area */}
<div className="h-64 w-full glass-dark rounded-3xl p-4 flex flex-col items-center justify-center"> <div className="h-48 w-full bg-zinc-900/40 backdrop-blur-md rounded-[24px] p-2 flex flex-col items-center justify-center border border-zinc-800/50">
<ResponsiveContainer width="100%" height="100%"> <ResponsiveContainer width="100%" height="100%">
<RadarChart cx="50%" cy="50%" outerRadius="80%" data={chartData}> <RadarChart cx="50%" cy="50%" outerRadius="70%" data={chartData}>
<PolarGrid stroke="rgba(255,255,255,0.1)" /> <PolarGrid stroke="rgba(255,255,255,0.05)" />
<PolarAngleAxis <PolarAngleAxis
dataKey="subject" dataKey="subject"
tick={{ fill: 'rgba(255,255,255,0.4)', fontSize: 10, fontWeight: 900 }} tick={{ fill: '#71717a', fontSize: 9, fontWeight: 700 }}
/> />
<Radar <Radar
name="Whisky Profile" name="Whisky Profile"
dataKey="A" dataKey="A"
stroke="#D97706" stroke="#ea580c"
fill="#D97706" fill="#ea580c"
fillOpacity={0.5} fillOpacity={0.6}
/> />
</RadarChart> </RadarChart>
</ResponsiveContainer> </ResponsiveContainer>
</div> </div>
<div className="flex items-center justify-between"> <div className="flex items-center justify-between pt-1">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Sparkles size={16} className="text-amber-500" /> <Sparkles size={14} className="text-orange-600" />
<span className="text-xs font-black uppercase tracking-widest text-white/40">Verified Report</span> <span className="text-[9px] font-bold uppercase tracking-widest text-zinc-500">Authenticated Report</span>
</div> </div>
</div> </div>
</div> </div>
{/* Score Badge */} {/* Score Badge */}
<div className="absolute top-8 right-8 w-20 h-20 glass rounded-2xl flex flex-col items-center justify-center border-amber-500/40 shadow-xl"> <div className="absolute top-6 right-6 w-16 h-16 bg-zinc-900/60 backdrop-blur-md rounded-2xl flex flex-col items-center justify-center border border-orange-500/30 shadow-2xl">
<span className="text-2xl font-black text-amber-500">{displayScore}</span> <span className="text-2xl font-bold text-orange-600 leading-none">{displayScore}</span>
<span className="text-[8px] font-black uppercase tracking-widest text-white/40">Score</span> <span className="text-[7px] font-bold uppercase tracking-widest text-zinc-500 mt-1">Rating</span>
</div> </div>
{/* Decorative Elements */} {/* Decorative Elements */}
<div className="absolute top-8 left-8 p-2 rounded-xl bg-amber-600 text-white shadow-lg shadow-amber-600/40"> <div className="absolute top-6 left-6 p-2 rounded-xl bg-orange-600 text-white shadow-lg shadow-orange-950/40">
<Award size={20} /> <Award size={18} />
</div> </div>
</div> </div>
{/* Share Button */} {/* Share Button */}
<button <button
onClick={onShare} onClick={onShare}
className="w-full py-5 bg-zinc-900 hover:bg-zinc-800 border border-white/10 text-white rounded-3xl font-black uppercase tracking-widest text-xs flex items-center justify-center gap-3 transition-all" className="w-full py-4 bg-orange-600 hover:bg-orange-700 text-white rounded-2xl font-bold uppercase tracking-widest text-[10px] flex items-center justify-center gap-3 transition-all shadow-xl shadow-orange-950/20 active:scale-95"
> >
<Share2 size={18} /> <Share2 size={16} />
Share Report Share Tasting Report
</button> </button>
</motion.div> </motion.div>
); );

View File

@@ -133,12 +133,12 @@ export default function ScanAndTasteFlow({ isOpen, onClose, base64Image }: ScanA
initial={{ opacity: 0 }} initial={{ opacity: 0 }}
animate={{ opacity: 1 }} animate={{ opacity: 1 }}
exit={{ opacity: 0 }} exit={{ opacity: 0 }}
className="fixed inset-0 z-[60] bg-[#0F1014] flex flex-col h-[100dvh] w-screen overflow-hidden overscroll-none" className="fixed inset-0 z-[60] bg-zinc-950 flex flex-col h-[100dvh] w-screen overflow-hidden overscroll-none"
> >
{/* Close Button */} {/* Close Button */}
<button <button
onClick={onClose} onClick={onClose}
className="absolute top-6 right-6 z-[70] p-2 rounded-full bg-white/5 border border-white/10 text-white/60 hover:text-white transition-colors" className="absolute top-6 right-6 z-[70] p-2 rounded-full bg-zinc-900 border border-zinc-800 text-zinc-400 hover:text-white transition-colors"
> >
<X size={24} /> <X size={24} />
</button> </button>
@@ -160,15 +160,15 @@ export default function ScanAndTasteFlow({ isOpen, onClose, base64Image }: ScanA
<motion.div <motion.div
animate={{ rotate: 360 }} animate={{ rotate: 360 }}
transition={{ duration: 3, repeat: Infinity, ease: "linear" }} transition={{ duration: 3, repeat: Infinity, ease: "linear" }}
className="w-32 h-32 rounded-full border-2 border-dashed border-[#C89D46]/30" className="w-32 h-32 rounded-full border-2 border-dashed border-orange-600/30"
/> />
<div className="absolute inset-0 flex items-center justify-center"> <div className="absolute inset-0 flex items-center justify-center">
<Loader2 size={48} className="animate-spin text-[#C89D46]" /> <Loader2 size={48} className="animate-spin text-orange-600" />
</div> </div>
</div> </div>
<div className="text-center space-y-2"> <div className="text-center space-y-2">
<h2 className="text-2xl font-display font-bold text-white uppercase tracking-tight">Analysiere Etikett...</h2> <h2 className="text-2xl font-bold text-zinc-50 uppercase tracking-tight">Analysiere Etikett...</h2>
<p className="text-[#C89D46] font-sans font-bold uppercase tracking-widest text-[10px] flex items-center justify-center gap-2"> <p className="text-orange-500 font-bold uppercase tracking-widest text-[10px] flex items-center justify-center gap-2">
<Sparkles size={12} /> KI-gestütztes Scanning <Sparkles size={12} /> KI-gestütztes Scanning
</p> </p>
</div> </div>
@@ -223,10 +223,10 @@ export default function ScanAndTasteFlow({ isOpen, onClose, base64Image }: ScanA
<motion.div <motion.div
initial={{ opacity: 0 }} initial={{ opacity: 0 }}
animate={{ opacity: 1 }} animate={{ opacity: 1 }}
className="absolute inset-0 z-[80] bg-[#0F1014]/80 backdrop-blur-sm flex flex-col items-center justify-center gap-6" className="absolute inset-0 z-[80] bg-zinc-950/80 backdrop-blur-sm flex flex-col items-center justify-center gap-6"
> >
<Loader2 size={48} className="animate-spin text-[#C89D46]" /> <Loader2 size={48} className="animate-spin text-orange-600" />
<h2 className="text-xl font-display font-bold text-white uppercase tracking-tight">Speichere Tasting...</h2> <h2 className="text-xl font-bold text-zinc-50 uppercase tracking-tight">Speichere Tasting...</h2>
</motion.div> </motion.div>
)} )}

View File

@@ -76,7 +76,7 @@ export default function SessionBottomSheet({ isOpen, onClose }: SessionBottomShe
animate={{ opacity: 1 }} animate={{ opacity: 1 }}
exit={{ opacity: 0 }} exit={{ opacity: 0 }}
onClick={onClose} onClick={onClose}
className="fixed inset-0 bg-black/60 backdrop-blur-sm z-[80]" className="fixed inset-0 bg-zinc-950/80 backdrop-blur-sm z-[80]"
/> />
{/* Sheet */} {/* Sheet */}
@@ -85,12 +85,12 @@ export default function SessionBottomSheet({ isOpen, onClose }: SessionBottomShe
animate={{ y: 0 }} animate={{ y: 0 }}
exit={{ y: '100%' }} exit={{ y: '100%' }}
transition={{ type: 'spring', damping: 25, stiffness: 200 }} transition={{ type: 'spring', damping: 25, stiffness: 200 }}
className="fixed bottom-0 left-0 right-0 bg-[#1A1B20] border-t border-white/10 rounded-t-[32px] z-[90] p-8 pb-12 max-h-[80vh] overflow-y-auto shadow-[0_-10px_40px_rgba(0,0,0,0.5)]" className="fixed bottom-0 left-0 right-0 bg-zinc-950 border-t border-zinc-800 rounded-t-[32px] z-[90] p-8 pb-12 max-h-[80vh] overflow-y-auto shadow-[0_-10px_40px_rgba(0,0,0,0.5)]"
> >
{/* Drag Handle */} {/* Drag Handle */}
<div className="w-12 h-1.5 bg-white/10 rounded-full mx-auto mb-8" /> <div className="w-12 h-1.5 bg-zinc-800 rounded-full mx-auto mb-8" />
<h2 className="text-2xl font-bold mb-6 font-display text-white">Tasting Session</h2> <h2 className="text-2xl font-bold mb-6 text-zinc-50">Tasting Session</h2>
{/* New Session Input */} {/* New Session Input */}
<div className="relative mb-8"> <div className="relative mb-8">
@@ -100,12 +100,12 @@ export default function SessionBottomSheet({ isOpen, onClose }: SessionBottomShe
onChange={(e) => setNewSessionName(e.target.value)} onChange={(e) => setNewSessionName(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && handleCreateSession()} onKeyDown={(e) => e.key === 'Enter' && handleCreateSession()}
placeholder="Neue Session erstellen..." placeholder="Neue Session erstellen..."
className="w-full bg-white/5 border border-white/10 rounded-2xl py-4 px-6 text-white focus:outline-none focus:border-[#C89D46] transition-colors" className="w-full bg-zinc-900 border border-zinc-800 rounded-2xl py-4 px-6 text-zinc-50 focus:outline-none focus:border-orange-600 transition-colors"
/> />
<button <button
onClick={handleCreateSession} onClick={handleCreateSession}
disabled={isCreating || !newSessionName.trim()} disabled={isCreating || !newSessionName.trim()}
className="absolute right-3 top-1/2 -translate-y-1/2 p-2 bg-[#C89D46] text-black rounded-xl disabled:opacity-50" className="absolute right-3 top-1/2 -translate-y-1/2 p-2 bg-orange-600 text-white rounded-xl disabled:opacity-50"
> >
{isCreating ? <Loader2 size={20} className="animate-spin" /> : <Plus size={20} />} {isCreating ? <Loader2 size={20} className="animate-spin" /> : <Plus size={20} />}
</button> </button>
@@ -113,10 +113,10 @@ export default function SessionBottomSheet({ isOpen, onClose }: SessionBottomShe
{/* Session List */} {/* Session List */}
<div className="space-y-4"> <div className="space-y-4">
<p className="text-xs font-black uppercase tracking-widest text-white/40 mb-2">Aktuelle Sessions</p> <p className="text-xs font-bold uppercase tracking-widest text-zinc-500 mb-2">Aktuelle Sessions</p>
{isLoading ? ( {isLoading ? (
<div className="flex justify-center py-8"> <div className="flex justify-center py-8">
<Loader2 size={24} className="animate-spin text-white/20" /> <Loader2 size={24} className="animate-spin text-zinc-700" />
</div> </div>
) : sessions.length > 0 ? ( ) : sessions.length > 0 ? (
sessions.map((s) => ( sessions.map((s) => (
@@ -126,14 +126,14 @@ export default function SessionBottomSheet({ isOpen, onClose }: SessionBottomShe
setActiveSession({ id: s.id, name: s.name }); setActiveSession({ id: s.id, name: s.name });
onClose(); onClose();
}} }}
className={`w-full flex items-center justify-between p-4 rounded-2xl border transition-all ${activeSession?.id === s.id ? 'bg-[#C89D46]/10 border-[#C89D46] text-[#C89D46]' : 'bg-white/5 border-white/5 hover:border-white/20 text-white'}`} className={`w-full flex items-center justify-between p-4 rounded-2xl border transition-all ${activeSession?.id === s.id ? 'bg-orange-600/10 border-orange-600 text-orange-500' : 'bg-zinc-900 border-zinc-800 hover:border-zinc-700 text-zinc-50'}`}
> >
<span className="font-bold">{s.name}</span> <span className="font-bold">{s.name}</span>
{activeSession?.id === s.id ? <Check size={20} /> : <ChevronRight size={20} className="text-white/20" />} {activeSession?.id === s.id ? <Check size={20} /> : <ChevronRight size={20} className="text-zinc-700" />}
</button> </button>
)) ))
) : ( ) : (
<div className="text-center py-8 text-white/30 italic">Keine aktiven Sessions gefunden</div> <div className="text-center py-8 text-zinc-600 italic">Keine aktiven Sessions gefunden</div>
)} )}
</div> </div>
</motion.div> </motion.div>

View File

@@ -140,18 +140,18 @@ export default function SessionList() {
}; };
return ( return (
<div className="bg-[#1A1B21] rounded-3xl p-6 border border-white/5 shadow-xl transition-all duration-300"> <div className="bg-zinc-900 rounded-3xl p-6 border border-zinc-800 shadow-xl transition-all duration-300">
<div className="flex items-center justify-between mb-6"> <div className="flex items-center justify-between mb-6">
<h3 className="text-sm font-sans font-bold uppercase tracking-[0.2em] flex items-center gap-2 text-[#8F9096]"> <h3 className="text-sm font-bold uppercase tracking-[0.2em] flex items-center gap-2 text-zinc-400">
<Calendar size={18} className="text-[#C89D46]" /> <Calendar size={18} className="text-orange-600" />
{t('session.title')} {t('session.title')}
{!isCollapsed && sessions.length > 0 && ( {!isCollapsed && sessions.length > 0 && (
<span className="text-[10px] font-sans font-bold opacity-50 ml-2">({sessions.length})</span> <span className="text-[10px] font-bold opacity-50 ml-2">({sessions.length})</span>
)} )}
</h3> </h3>
<button <button
onClick={handleToggleCollapse} onClick={handleToggleCollapse}
className="p-2 hover:bg-white/5 rounded-xl transition-colors text-[#8F9096] hover:text-[#C89D46]" className="p-2 hover:bg-zinc-800 rounded-xl transition-colors text-zinc-500 hover:text-orange-600"
title={isCollapsed ? 'Aufklappen' : 'Einklappen'} title={isCollapsed ? 'Aufklappen' : 'Einklappen'}
> >
{isCollapsed ? <ChevronDown size={20} /> : <ChevronUp size={20} />} {isCollapsed ? <ChevronDown size={20} /> : <ChevronUp size={20} />}
@@ -166,23 +166,23 @@ export default function SessionList() {
value={newName} value={newName}
onChange={(e) => setNewName(e.target.value)} onChange={(e) => setNewName(e.target.value)}
placeholder={t('session.sessionName')} placeholder={t('session.sessionName')}
className="flex-1 bg-white/5 border border-white/10 rounded-xl px-4 py-2 text-sm text-white placeholder:text-[#8F9096] focus:outline-none focus:border-[#C89D46] transition-colors" className="flex-1 bg-zinc-950 border border-zinc-800 rounded-xl px-4 py-2 text-sm text-zinc-50 placeholder:text-zinc-600 focus:outline-none focus:border-orange-600 transition-colors"
/> />
<button <button
type="submit" type="submit"
disabled={isCreating || !newName.trim()} disabled={isCreating || !newName.trim()}
className="bg-[#C89D46] hover:bg-[#A67D2E] text-[#0F1014] p-2 rounded-xl transition-all disabled:opacity-50" className="bg-orange-600 hover:bg-orange-700 text-white p-2 rounded-xl transition-all disabled:opacity-50"
> >
{isCreating ? <Loader2 size={20} className="animate-spin" /> : <Plus size={20} />} {isCreating ? <Loader2 size={20} className="animate-spin" /> : <Plus size={20} />}
</button> </button>
</form> </form>
{isLoading ? ( {isLoading ? (
<div className="flex justify-center py-8 text-[#8F9096]"> <div className="flex justify-center py-8 text-zinc-500">
<Loader2 size={24} className="animate-spin" /> <Loader2 size={24} className="animate-spin" />
</div> </div>
) : sessions.length === 0 ? ( ) : sessions.length === 0 ? (
<div className="text-center py-8 text-[#8F9096] text-xs font-sans"> <div className="text-center py-8 text-zinc-600 text-xs font-bold">
{t('session.noSessions')} {t('session.noSessions')}
</div> </div>
) : ( ) : (
@@ -191,18 +191,18 @@ export default function SessionList() {
<div <div
key={session.id} key={session.id}
className={`flex items-center justify-between p-4 rounded-2xl border transition-all ${activeSession?.id === session.id className={`flex items-center justify-between p-4 rounded-2xl border transition-all ${activeSession?.id === session.id
? 'bg-[#C89D46] border-[#C89D46] shadow-lg shadow-[#C89D46]/20' ? 'bg-orange-600 border-orange-600 shadow-lg shadow-orange-950/20'
: 'bg-white/5 border-white/5 hover:border-white/10' : 'bg-zinc-950 border-zinc-800 hover:border-zinc-700'
}`} }`}
> >
<Link href={`/sessions/${session.id}`} className="flex-1 space-y-1 min-w-0"> <Link href={`/sessions/${session.id}`} className="flex-1 space-y-1 min-w-0">
<div className={`font-display font-bold text-lg truncate flex items-center gap-2 ${activeSession?.id === session.id ? 'text-[#0F1014]' : 'text-white'}`}> <div className={`font-bold text-lg truncate flex items-center gap-2 ${activeSession?.id === session.id ? 'text-white' : 'text-zinc-50'}`}>
{session.name} {session.name}
{session.ended_at && ( {session.ended_at && (
<span className={`text-[8px] font-sans font-black uppercase px-1.5 py-0.5 rounded border ${activeSession?.id === session.id ? 'bg-black/10 border-black/20 text-[#0F1014]' : 'bg-white/10 border-white/20 text-[#8F9096]'}`}>Closed</span> <span className={`text-[8px] font-bold uppercase px-1.5 py-0.5 rounded border ${activeSession?.id === session.id ? 'bg-black/10 border-black/20 text-white' : 'bg-zinc-800 border-zinc-700 text-zinc-500'}`}>Closed</span>
)} )}
</div> </div>
<div className={`flex items-center gap-4 text-[10px] font-sans font-bold uppercase tracking-widest ${activeSession?.id === session.id ? 'text-[#0F1014]/60' : 'text-[#8F9096]'}`}> <div className={`flex items-center gap-4 text-[10px] font-bold uppercase tracking-widest ${activeSession?.id === session.id ? 'text-white/60' : 'text-zinc-500'}`}>
<span className="flex items-center gap-1"> <span className="flex items-center gap-1">
<Calendar size={12} /> <Calendar size={12} />
{new Date(session.scheduled_at).toLocaleDateString(locale === 'de' ? 'de-DE' : 'en-US')} {new Date(session.scheduled_at).toLocaleDateString(locale === 'de' ? 'de-DE' : 'en-US')}
@@ -225,28 +225,28 @@ export default function SessionList() {
!session.ended_at ? ( !session.ended_at ? (
<button <button
onClick={() => setActiveSession({ id: session.id, name: session.name })} onClick={() => setActiveSession({ id: session.id, name: session.name })}
className="p-2 bg-white/10 text-white rounded-xl hover:bg-[#C89D46] hover:text-[#0F1014] transition-all" className="p-2 bg-zinc-800 text-zinc-50 rounded-xl hover:bg-orange-600 hover:text-white transition-all"
title="Start Session" title="Start Session"
> >
<GlassWater size={18} /> <GlassWater size={18} />
</button> </button>
) : ( ) : (
<div className="p-2 bg-white/5 text-[#8F9096] rounded-xl border border-white/5 opacity-50"> <div className="p-2 bg-zinc-900 text-zinc-500 rounded-xl border border-zinc-800 opacity-50">
<Check size={18} /> <Check size={18} />
</div> </div>
) )
) : ( ) : (
<div className="p-2 bg-black/10 text-[#0F1014] rounded-xl"> <div className="p-2 bg-black/10 text-white rounded-xl">
<Check size={18} /> <Check size={18} />
</div> </div>
)} )}
<ChevronRight size={20} className={activeSession?.id === session.id ? 'text-[#0F1014]/40' : 'text-white/20'} /> <ChevronRight size={20} className={activeSession?.id === session.id ? 'text-white/40' : 'text-zinc-700'} />
<button <button
onClick={(e) => handleDeleteSession(e, session.id)} onClick={(e) => handleDeleteSession(e, session.id)}
disabled={!!isDeleting} disabled={!!isDeleting}
className={`p-2 rounded-xl transition-all ${activeSession?.id === session.id className={`p-2 rounded-xl transition-all ${activeSession?.id === session.id
? 'text-[#0F1014]/40 hover:text-[#0F1014]' ? 'text-white/40 hover:text-white'
: 'text-[#8F9096] hover:text-red-400' : 'text-zinc-600 hover:text-red-500'
}`} }`}
title="Session löschen" title="Session löschen"
> >
@@ -268,17 +268,17 @@ export default function SessionList() {
<div className="flex items-center gap-2 animate-in fade-in slide-in-from-top-1"> <div className="flex items-center gap-2 animate-in fade-in slide-in-from-top-1">
<div className="flex -space-x-1.5 overflow-hidden"> <div className="flex -space-x-1.5 overflow-hidden">
{sessions.slice(0, 3).map((s, i) => ( {sessions.slice(0, 3).map((s, i) => (
<div key={s.id} className="w-7 h-7 rounded-lg bg-white/5 border border-white/10 flex items-center justify-center text-[10px] font-display font-bold text-[#C89D46] shadow-sm"> <div key={s.id} className="w-7 h-7 rounded-lg bg-zinc-950 border border-zinc-800 flex items-center justify-center text-[10px] font-bold text-orange-600 shadow-sm">
{s.name[0].toUpperCase()} {s.name[0].toUpperCase()}
</div> </div>
))} ))}
{sessions.length > 3 && ( {sessions.length > 3 && (
<div className="w-7 h-7 rounded-lg bg-white/5 border border-white/10 flex items-center justify-center text-[8px] font-sans font-bold text-[#8F9096] shadow-sm"> <div className="w-7 h-7 rounded-lg bg-zinc-950 border border-zinc-800 flex items-center justify-center text-[8px] font-bold text-zinc-500 shadow-sm">
+{sessions.length - 3} +{sessions.length - 3}
</div> </div>
)} )}
</div> </div>
<span className="text-[10px] text-[#8F9096] font-sans font-bold uppercase tracking-widest ml-1">{sessions.length} Sessions</span> <span className="text-[10px] text-zinc-500 font-bold uppercase tracking-widest ml-1">{sessions.length} Sessions</span>
</div> </div>
)} )}
</div> </div>

View File

@@ -61,46 +61,46 @@ export default function SessionTimeline({ tastings, sessionStart }: SessionTimel
return ( return (
<div key={tasting.id} className="relative group"> <div key={tasting.id} className="relative group">
{/* Dot */} {/* Dot */}
<div className={`absolute -left-[22px] top-4 w-5 h-5 rounded-full border-[3px] border-zinc-100 dark:border-black shadow-sm z-10 flex items-center justify-center ${isSmoky ? 'bg-amber-600' : 'bg-zinc-400'}`}> <div className={`absolute -left-[22px] top-4 w-5 h-5 rounded-full border-[3px] border-zinc-950 shadow-sm z-10 flex items-center justify-center ${isSmoky ? 'bg-orange-600' : 'bg-zinc-600'}`}>
{isSmoky && <Droplets size={8} className="text-white fill-white" />} {isSmoky && <Droplets size={8} className="text-white fill-white" />}
</div> </div>
<div className="bg-white dark:bg-zinc-900 p-4 rounded-2xl border border-zinc-200 dark:border-zinc-800 shadow-sm hover:shadow-md transition-shadow group-hover:border-amber-500/30"> <div className="bg-zinc-900 p-4 rounded-2xl border border-zinc-800 shadow-sm hover:shadow-md transition-shadow group-hover:border-orange-500/30">
<div className="flex justify-between items-start gap-3"> <div className="flex justify-between items-start gap-3">
<div className="min-w-0"> <div className="min-w-0">
<div className="flex items-center gap-2 mb-1"> <div className="flex items-center gap-2 mb-1">
<span className="text-[10px] font-black text-amber-600 uppercase tracking-widest leading-none">Dram #{index + 1}</span> <span className="text-[10px] font-bold text-orange-600 uppercase tracking-widest leading-none">Dram #{index + 1}</span>
<span className="text-[10px] font-bold text-zinc-400 dark:text-zinc-600 uppercase tracking-tight leading-none"> <span className="text-[10px] font-bold text-zinc-500 uppercase tracking-tight leading-none">
{wallClockTime} ({index === 0 ? 'Start' : `+${diffMinutes}m`}) {wallClockTime} ({index === 0 ? 'Start' : `+${diffMinutes}m`})
</span> </span>
{isSmoky && ( {isSmoky && (
<span className="bg-amber-100 dark:bg-amber-900/30 text-amber-700 dark:text-amber-400 text-[8px] font-black px-1.5 py-0.5 rounded-md uppercase tracking-tighter">Peat Bomb</span> <span className="bg-orange-900/40 text-orange-500 text-[8px] font-bold px-1.5 py-0.5 rounded-md uppercase tracking-tighter border border-orange-500/20">Peat Bomb</span>
)} )}
</div> </div>
<Link <Link
href={`/bottles/${tasting.bottle_id}`} href={`/bottles/${tasting.bottle_id}`}
className="text-sm font-bold text-zinc-800 dark:text-zinc-100 hover:text-amber-600 truncate block mt-0.5" className="text-sm font-bold text-zinc-100 hover:text-orange-600 truncate block mt-0.5 uppercase tracking-tight"
> >
{tasting.bottle_name} {tasting.bottle_name}
</Link> </Link>
<div className="mt-2 flex flex-wrap gap-1"> <div className="mt-2 flex flex-wrap gap-1">
{tasting.tags.slice(0, 2).map(tag => ( {tasting.tags.slice(0, 2).map(tag => (
<span key={tag} className="text-[9px] text-zinc-500 dark:text-zinc-500 bg-zinc-100 dark:bg-zinc-800/50 px-2 py-0.5 rounded-full"> <span key={tag} className="text-[9px] text-zinc-500 bg-zinc-800/50 px-2 py-0.5 rounded-full border border-zinc-800">
{tag} {tag}
</span> </span>
))} ))}
</div> </div>
</div> </div>
<div className="shrink-0 flex flex-col items-end"> <div className="shrink-0 flex flex-col items-end">
<div className="text-lg font-black text-zinc-900 dark:text-white leading-none">{tasting.rating}</div> <div className="text-lg font-bold text-zinc-50 leading-none">{tasting.rating}</div>
<div className="text-[9px] font-bold text-zinc-400 uppercase tracking-tighter mt-1">Punkte</div> <div className="text-[9px] font-bold text-zinc-500 uppercase tracking-tighter mt-1">Punkte</div>
</div> </div>
</div> </div>
{wasPreviousSmoky && timeSinceLastDram < 20 && ( {wasPreviousSmoky && timeSinceLastDram < 20 && (
<div className="mt-4 p-2 bg-amber-50 dark:bg-amber-900/10 border border-amber-200/50 dark:border-amber-900/30 rounded-xl flex items-center gap-2 animate-in slide-in-from-top-1"> <div className="mt-4 p-2 bg-orange-900/10 border border-orange-900/30 rounded-xl flex items-center gap-2 animate-in slide-in-from-top-1">
<AlertTriangle size={12} className="text-amber-600 shrink-0" /> <AlertTriangle size={12} className="text-orange-600 shrink-0" />
<p className="text-[9px] text-amber-800 dark:text-amber-400 font-bold leading-tight"> <p className="text-[9px] text-orange-400 font-bold leading-tight">
Achtung: Gaumen war noch torf-belegt (nur {timeSinceLastDram}m Abstand). Achtung: Gaumen war noch torf-belegt (nur {timeSinceLastDram}m Abstand).
</p> </p>
</div> </div>

View File

@@ -61,8 +61,8 @@ export default function StatsDashboard({ bottles }: StatsDashboardProps) {
label: t('home.stats.avgRating'), label: t('home.stats.avgRating'),
value: `${stats.avgRating}/100`, value: `${stats.avgRating}/100`,
icon: Star, icon: Star,
color: 'text-amber-600', color: 'text-orange-600',
bg: 'bg-amber-50 dark:bg-amber-900/20' bg: 'bg-orange-900/20'
}, },
{ {
label: t('home.stats.topDistillery'), label: t('home.stats.topDistillery'),
@@ -74,17 +74,18 @@ export default function StatsDashboard({ bottles }: StatsDashboardProps) {
]; ];
return ( return (
<div className="w-full grid grid-cols-2 md:grid-cols-4 gap-8 md:gap-4 h-fit animate-in fade-in slide-in-from-top-4 duration-500"> <div className="w-full grid grid-cols-2 md:grid-cols-4 gap-8 md:gap-4 h-fit animate-in fade-in slide-in-from-top-4 duration-500 mb-12">
{statItems.map((item, idx) => { {statItems.map((item, idx) => {
const value = idx === 1 ? stats.totalCount : item.value;
return ( return (
<div <div
key={idx} key={idx}
className="flex flex-col gap-1 text-center md:text-left" className="flex flex-col gap-1 text-center md:text-left"
> >
<div className="text-4xl md:text-5xl font-display font-bold text-white tracking-tighter"> <div className="text-4xl md:text-5xl font-bold text-zinc-50 tracking-tighter">
{idx === 1 ? stats.totalCount : item.value} {value}
</div> </div>
<div className="text-[10px] font-sans font-bold uppercase text-[#8F9096] tracking-widest px-1"> <div className="text-[10px] font-bold uppercase text-zinc-500 tracking-[0.2em]">
{item.label} {item.label}
</div> </div>
</div> </div>

View File

@@ -70,35 +70,35 @@ export default function TagSelector({ category, selectedTagIds, onToggleTag, lab
key={tag.id} key={tag.id}
type="button" type="button"
onClick={() => onToggleTag(tag.id)} onClick={() => onToggleTag(tag.id)}
className="inline-flex items-center gap-1.5 px-3 py-1.5 bg-amber-600 text-white rounded-full text-[10px] font-black uppercase tracking-tight shadow-sm shadow-amber-600/20 animate-in fade-in zoom-in-95" className="inline-flex items-center gap-1.5 px-3 py-1.5 bg-orange-600 text-white rounded-full text-[10px] font-bold uppercase tracking-tight shadow-sm shadow-orange-600/20 animate-in fade-in zoom-in-95"
> >
{tag.is_system_default ? t(`aroma.${tag.name}`) : tag.name} {tag.is_system_default ? t(`aroma.${tag.name}`) : tag.name}
<X size={12} /> <X size={12} />
</button> </button>
)) ))
) : ( ) : (
<span className="text-[10px] italic text-zinc-400 font-medium">Noch keine Tags gewählt...</span> <span className="text-[10px] italic text-zinc-500 font-medium">Noch keine Tags gewählt...</span>
)} )}
</div> </div>
{/* Search and Suggest */} {/* Search and Suggest */}
<div className="relative"> <div className="relative">
<div className="relative flex items-center"> <div className="relative flex items-center">
<Search className="absolute left-3 text-zinc-400" size={14} /> <Search className="absolute left-3 text-zinc-500" size={14} />
<input <input
type="text" type="text"
value={search} value={search}
onChange={(e) => setSearch(e.target.value)} onChange={(e) => setSearch(e.target.value)}
placeholder="Tag suchen oder hinzufügen..." placeholder="Tag suchen oder hinzufügen..."
className="w-full pl-9 pr-4 py-2 bg-zinc-50 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-xl text-xs focus:ring-2 focus:ring-amber-500 outline-none transition-all dark:text-zinc-200 placeholder:text-zinc-400" className="w-full pl-9 pr-4 py-2 bg-zinc-900 border border-zinc-800 rounded-xl text-xs focus:ring-1 focus:ring-orange-600 outline-none transition-all text-zinc-200 placeholder:text-zinc-600"
/> />
{isCreating && ( {isCreating && (
<Loader2 className="absolute right-3 animate-spin text-amber-600" size={14} /> <Loader2 className="absolute right-3 animate-spin text-orange-600" size={14} />
)} )}
</div> </div>
{search && ( {search && (
<div className="absolute z-10 w-full mt-2 bg-white dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-2xl shadow-xl overflow-hidden animate-in fade-in slide-in-from-top-2"> <div className="absolute z-10 w-full mt-2 bg-zinc-900 border border-zinc-800 rounded-2xl shadow-xl overflow-hidden animate-in fade-in slide-in-from-top-2">
<div className="max-h-48 overflow-y-auto"> <div className="max-h-48 overflow-y-auto">
{filteredTags.length > 0 ? ( {filteredTags.length > 0 ? (
filteredTags.map(tag => ( filteredTags.map(tag => (
@@ -109,17 +109,17 @@ export default function TagSelector({ category, selectedTagIds, onToggleTag, lab
onToggleTag(tag.id); onToggleTag(tag.id);
setSearch(''); setSearch('');
}} }}
className="w-full px-4 py-2.5 text-left text-xs font-bold text-zinc-700 dark:text-zinc-300 hover:bg-zinc-50 dark:hover:bg-zinc-700/50 flex items-center justify-between border-b border-zinc-100 dark:border-zinc-700 last:border-0" className="w-full px-4 py-2.5 text-left text-xs font-bold text-zinc-300 hover:bg-zinc-800/50 flex items-center justify-between border-b border-zinc-800 last:border-0"
> >
{tag.is_system_default ? t(`aroma.${tag.name}`) : tag.name} {tag.is_system_default ? t(`aroma.${tag.name}`) : tag.name}
{selectedTagIds.includes(tag.id) && <Check size={12} className="text-amber-600" />} {selectedTagIds.includes(tag.id) && <Check size={12} className="text-orange-600" />}
</button> </button>
)) ))
) : ( ) : (
<button <button
type="button" type="button"
onClick={handleCreateTag} onClick={handleCreateTag}
className="w-full px-4 py-3 text-left text-xs font-bold text-amber-600 hover:bg-amber-50 dark:hover:bg-amber-900/10 flex items-center gap-2" className="w-full px-4 py-3 text-left text-xs font-bold text-orange-600 hover:bg-orange-950/10 flex items-center gap-2"
> >
<Plus size={14} /> <Plus size={14} />
"{search}" als neuen Tag hinzufügen "{search}" als neuen Tag hinzufügen
@@ -133,7 +133,7 @@ export default function TagSelector({ category, selectedTagIds, onToggleTag, lab
{/* AI Suggestions */} {/* AI Suggestions */}
{!search && suggestedTagNames && suggestedTagNames.length > 0 && ( {!search && suggestedTagNames && suggestedTagNames.length > 0 && (
<div className="space-y-2"> <div className="space-y-2">
<div className="flex items-center gap-1.5 text-[9px] font-black uppercase tracking-widest text-amber-500"> <div className="flex items-center gap-1.5 text-[9px] font-bold uppercase tracking-widest text-orange-500">
<Sparkles size={10} /> {t('camera.wbMatchFound') ? 'KI Vorschläge' : 'AI Suggestions'} <Sparkles size={10} /> {t('camera.wbMatchFound') ? 'KI Vorschläge' : 'AI Suggestions'}
</div> </div>
<div className="flex flex-wrap gap-1.5"> <div className="flex flex-wrap gap-1.5">
@@ -144,7 +144,7 @@ export default function TagSelector({ category, selectedTagIds, onToggleTag, lab
key={tag.id} key={tag.id}
type="button" type="button"
onClick={() => onToggleTag(tag.id)} onClick={() => onToggleTag(tag.id)}
className="px-2.5 py-1 rounded-lg bg-amber-50 dark:bg-amber-900/20 text-amber-600 dark:text-amber-400 text-[10px] font-bold uppercase tracking-tight hover:bg-amber-100 dark:hover:bg-amber-900/30 transition-colors border border-amber-200 dark:border-amber-800/50 flex items-center gap-1.5" className="px-2.5 py-1 rounded-lg bg-orange-950/20 text-orange-500 text-[10px] font-bold uppercase tracking-tight hover:bg-orange-900/30 transition-colors border border-orange-900/50 flex items-center gap-1.5"
> >
<Sparkles size={10} /> <Sparkles size={10} />
{tag.is_system_default ? t(`aroma.${tag.name}`) : tag.name} {tag.is_system_default ? t(`aroma.${tag.name}`) : tag.name}
@@ -157,7 +157,7 @@ export default function TagSelector({ category, selectedTagIds, onToggleTag, lab
{/* AI Custom Suggestions */} {/* AI Custom Suggestions */}
{!search && suggestedCustomTagNames && suggestedCustomTagNames.length > 0 && ( {!search && suggestedCustomTagNames && suggestedCustomTagNames.length > 0 && (
<div className="space-y-2"> <div className="space-y-2">
<div className="flex items-center gap-1.5 text-[9px] font-black uppercase tracking-widest text-zinc-400"> <div className="flex items-center gap-1.5 text-[9px] font-bold uppercase tracking-widest text-zinc-500">
Dominante Note anlegen? Dominante Note anlegen?
</div> </div>
<div className="flex flex-wrap gap-1.5"> <div className="flex flex-wrap gap-1.5">
@@ -177,7 +177,7 @@ export default function TagSelector({ category, selectedTagIds, onToggleTag, lab
} }
setCreatingSuggestion(null); setCreatingSuggestion(null);
}} }}
className="px-2.5 py-1 rounded-lg bg-zinc-50 dark:bg-zinc-800/50 text-zinc-500 dark:text-zinc-400 text-[10px] font-bold uppercase tracking-tight hover:bg-amber-600 hover:text-white transition-all border border-dashed border-zinc-200 dark:border-zinc-700/50 flex items-center gap-1.5 disabled:opacity-50" className="px-2.5 py-1 rounded-lg bg-zinc-900/50 text-zinc-400 text-[10px] font-bold uppercase tracking-tight hover:bg-orange-600 hover:text-white transition-all border border-dashed border-zinc-800 flex items-center gap-1.5 disabled:opacity-50"
> >
{creatingSuggestion === name ? <Loader2 size={10} className="animate-spin" /> : <Plus size={10} />} {creatingSuggestion === name ? <Loader2 size={10} className="animate-spin" /> : <Plus size={10} />}
{name} {name}
@@ -188,7 +188,7 @@ export default function TagSelector({ category, selectedTagIds, onToggleTag, lab
)} )}
{/* Suggestions Chips (limit to 6 random or most common) */} {/* Suggestions Chips (limit to 6 random or most common) */}
{!search && tags.length > 0 && ( {!search && (tags || []).length > 0 && (
<div className="flex flex-wrap gap-1.5 pt-1"> <div className="flex flex-wrap gap-1.5 pt-1">
{(tags || []) {(tags || [])
.filter(t => !selectedTagIds.includes(t.id) && (!suggestedTagNames || !suggestedTagNames.some((s: string) => s.toLowerCase() === t.name.toLowerCase()))) .filter(t => !selectedTagIds.includes(t.id) && (!suggestedTagNames || !suggestedTagNames.some((s: string) => s.toLowerCase() === t.name.toLowerCase())))
@@ -198,7 +198,7 @@ export default function TagSelector({ category, selectedTagIds, onToggleTag, lab
key={tag.id} key={tag.id}
type="button" type="button"
onClick={() => onToggleTag(tag.id)} onClick={() => onToggleTag(tag.id)}
className="px-2.5 py-1 rounded-lg bg-zinc-100 dark:bg-zinc-800 text-zinc-500 dark:text-zinc-400 text-[10px] font-bold uppercase tracking-tight hover:bg-zinc-200 dark:hover:bg-zinc-700 hover:text-zinc-700 dark:hover:text-zinc-200 transition-colors border border-zinc-200/50 dark:border-zinc-700/50" className="px-2.5 py-1 rounded-lg bg-zinc-900 text-zinc-500 text-[10px] font-bold uppercase tracking-tight hover:bg-zinc-800 hover:text-zinc-200 transition-colors border border-zinc-800"
> >
{tag.is_system_default ? t(`aroma.${tag.name}`) : tag.name} {tag.is_system_default ? t(`aroma.${tag.name}`) : tag.name}
</button> </button>

View File

@@ -123,18 +123,18 @@ export default function TastingEditor({ bottleMetadata, image, onSave, onOpenSes
}; };
return ( return (
<div className="flex-1 flex flex-col w-full bg-[#0F1014] h-full overflow-hidden"> <div className="flex-1 flex flex-col w-full bg-zinc-950 h-full overflow-hidden">
{/* Top Context Bar - Flex Child 1 */} {/* Top Context Bar - Flex Child 1 */}
<div className="w-full bg-black/40 backdrop-blur-md border-b border-white/10 shrink-0"> <div className="w-full bg-zinc-900 border-b border-zinc-800 shrink-0">
<button <button
onClick={onOpenSessions} onClick={onOpenSessions}
className="max-w-2xl mx-auto w-full p-6 flex items-center justify-between group" className="max-w-2xl mx-auto w-full p-6 flex items-center justify-between group"
> >
<div className="text-left"> <div className="text-left">
<p className="text-[10px] font-black uppercase tracking-widest text-[#C89D46]">Kontext</p> <p className="text-[10px] font-bold uppercase tracking-widest text-orange-500">Kontext</p>
<p className="font-bold text-white leading-none mt-1">{activeSessionName || 'Trinkst du in Gesellschaft?'}</p> <p className="font-bold text-zinc-50 leading-none mt-1">{activeSessionName || 'Trinkst du in Gesellschaft?'}</p>
</div> </div>
<ChevronDown size={20} className="text-[#C89D46] group-hover:translate-y-1 transition-transform" /> <ChevronDown size={20} className="text-orange-500 group-hover:translate-y-1 transition-transform" />
</button> </button>
</div> </div>
@@ -146,50 +146,50 @@ export default function TastingEditor({ bottleMetadata, image, onSave, onOpenSes
<motion.div <motion.div
initial={{ opacity: 0, y: -20 }} initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
className="p-5 bg-amber-500/10 border border-amber-500/20 rounded-3xl flex items-start gap-3" className="p-5 bg-orange-500/10 border border-orange-500/20 rounded-2xl flex items-start gap-3"
> >
<AlertTriangle size={24} className="text-amber-500 shrink-0 mt-0.5" /> <AlertTriangle size={24} className="text-orange-500 shrink-0 mt-0.5" />
<div className="space-y-1"> <div className="space-y-1">
<p className="text-[10px] font-black uppercase tracking-wider text-amber-500">Palette-Checker</p> <p className="text-[10px] font-bold uppercase tracking-wider text-orange-500">Palette-Checker</p>
<p className="text-xs font-bold text-white leading-relaxed"> <p className="text-xs font-bold text-zinc-50 leading-relaxed">
Dein letzter Dram "{lastDramInSession?.name}" war torfig. Trink etwas Wasser! Dein letzter Dram "{lastDramInSession?.name}" war torfig. Trink etwas Wasser!
</p> </p>
<button onClick={() => setShowPaletteWarning(false)} className="text-[10px] font-black uppercase text-amber-500 underline mt-2 block">Verstanden</button> <button onClick={() => setShowPaletteWarning(false)} className="text-[10px] font-bold uppercase text-orange-500 underline mt-2 block">Verstanden</button>
</div> </div>
</motion.div> </motion.div>
)} )}
{/* Hero Section */} {/* Hero Section */}
<div className="flex items-center gap-6"> <div className="flex items-center gap-6">
<div className="w-24 h-32 bg-white/5 rounded-2xl border border-white/10 flex items-center justify-center overflow-hidden shrink-0 shadow-2xl relative"> <div className="w-24 h-32 bg-zinc-900 rounded-xl border border-zinc-800 flex items-center justify-center overflow-hidden shrink-0 shadow-2xl relative">
{image ? ( {image ? (
<img src={image} alt="Bottle Preview" className="w-full h-full object-cover" /> <img src={image} alt="Bottle Preview" className="w-full h-full object-cover" />
) : ( ) : (
<div className="text-[10px] text-white/20 uppercase font-black rotate-[-15deg]">No Photo</div> <div className="text-[10px] text-zinc-700 uppercase font-bold rotate-[-15deg]">No Photo</div>
)} )}
</div> </div>
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<h1 className="text-3xl font-black text-amber-600 mb-1 truncate leading-none uppercase tracking-tight"> <h1 className="text-3xl font-bold text-orange-600 mb-1 truncate leading-none uppercase tracking-tight">
{bottleMetadata.distillery || 'Destillerie'} {bottleMetadata.distillery || 'Destillerie'}
</h1> </h1>
<p className="text-white text-xl font-bold truncate mb-2">{bottleMetadata.name || 'Unbekannter Malt'}</p> <p className="text-zinc-50 text-xl font-bold truncate mb-2">{bottleMetadata.name || 'Unbekannter Malt'}</p>
<p className="text-white/40 text-[10px] font-black uppercase tracking-widest leading-none"> <p className="text-zinc-500 text-[10px] font-bold uppercase tracking-widest leading-none">
{bottleMetadata.category || 'Whisky'} {bottleMetadata.abv ? `${bottleMetadata.abv}%` : ''} {bottleMetadata.age ? `${bottleMetadata.age}y` : ''} {bottleMetadata.category || 'Whisky'} {bottleMetadata.abv ? `${bottleMetadata.abv}%` : ''} {bottleMetadata.age ? `${bottleMetadata.age}y` : ''}
</p> </p>
</div> </div>
</div> </div>
{/* Rating Slider */} {/* Rating Slider */}
<div className="space-y-6 bg-white/5 p-8 rounded-[40px] border border-white/10 shadow-inner relative overflow-hidden"> <div className="space-y-6 bg-zinc-900 p-8 rounded-3xl border border-zinc-800 shadow-inner relative overflow-hidden">
<div className="absolute top-0 right-0 p-4 opacity-5 pointer-events-none"> <div className="absolute top-0 right-0 p-4 opacity-5 pointer-events-none">
<Zap size={120} className="text-amber-500" /> <Zap size={120} className="text-orange-500" />
</div> </div>
<div className="flex items-center justify-between relative z-10"> <div className="flex items-center justify-between relative z-10">
<label className="text-xs font-black text-white/40 uppercase tracking-[0.2em] flex items-center gap-2"> <label className="text-xs font-bold text-zinc-500 uppercase tracking-[0.2em] flex items-center gap-2">
<Star size={14} className="text-amber-500 fill-amber-500" /> <Star size={14} className="text-orange-500 fill-orange-500" />
{t('tasting.rating')} {t('tasting.rating')}
</label> </label>
<span className="text-4xl font-black text-amber-600 tracking-tighter">{rating}<span className="text-white/20 text-sm ml-1">/100</span></span> <span className="text-4xl font-bold text-orange-600 tracking-tighter">{rating}<span className="text-zinc-700 text-sm ml-1">/100</span></span>
</div> </div>
<input <input
type="range" type="range"
@@ -197,9 +197,9 @@ export default function TastingEditor({ bottleMetadata, image, onSave, onOpenSes
max="100" max="100"
value={rating} value={rating}
onChange={(e) => setRating(parseInt(e.target.value))} onChange={(e) => setRating(parseInt(e.target.value))}
className="w-full h-2 bg-zinc-800 rounded-full appearance-none cursor-pointer accent-amber-600 transition-all" className="w-full h-2 bg-zinc-800 rounded-full appearance-none cursor-pointer accent-orange-600 transition-all"
/> />
<div className="flex justify-between text-[10px] text-zinc-500 font-black uppercase tracking-widest px-1 relative z-10"> <div className="flex justify-between text-[10px] text-zinc-500 font-bold uppercase tracking-widest px-1 relative z-10">
<span>Swill</span> <span>Swill</span>
<span>Dram</span> <span>Dram</span>
<span>Legendary</span> <span>Legendary</span>
@@ -210,9 +210,9 @@ export default function TastingEditor({ bottleMetadata, image, onSave, onOpenSes
<button <button
key={type} key={type}
onClick={() => setIsSample(type === 'Sample')} onClick={() => setIsSample(type === 'Sample')}
className={`flex-1 py-4 rounded-2xl text-[10px] font-black uppercase tracking-widest border transition-all ${(type === 'Sample' ? isSample : !isSample) className={`flex-1 py-4 rounded-xl text-[10px] font-bold uppercase tracking-widest border transition-all ${(type === 'Sample' ? isSample : !isSample)
? 'bg-zinc-100 border-zinc-100 text-zinc-900 shadow-lg' ? 'bg-orange-600 border-orange-600 text-white shadow-lg'
: 'bg-transparent border-white/10 text-white/40 hover:border-white/30' : 'bg-transparent border-zinc-800 text-zinc-500 hover:border-zinc-700'
}`} }`}
> >
{type} {type}
@@ -230,14 +230,14 @@ export default function TastingEditor({ bottleMetadata, image, onSave, onOpenSes
{/* Sections */} {/* Sections */}
<div className="space-y-12 pb-12"> <div className="space-y-12 pb-12">
{/* Nose Section */} {/* Nose Section */}
<div className="space-y-8 bg-zinc-900/50 p-8 rounded-[40px] border border-white/5"> <div className="space-y-8 bg-zinc-900/50 p-8 rounded-3xl border border-zinc-800">
<div className="flex items-center gap-5"> <div className="flex items-center gap-5">
<div className="w-14 h-14 rounded-3xl bg-amber-500/10 flex items-center justify-center text-amber-500 shrink-0 border border-amber-500/20 shadow-xl"> <div className="w-14 h-14 rounded-2xl bg-orange-500/10 flex items-center justify-center text-orange-500 shrink-0 border border-orange-500/20 shadow-xl">
<Wind size={28} /> <Wind size={28} />
</div> </div>
<div> <div>
<h3 className="text-xl font-black text-white uppercase tracking-widest leading-none">{t('tasting.nose')}</h3> <h3 className="text-xl font-bold text-zinc-50 uppercase tracking-widest leading-none">{t('tasting.nose')}</h3>
<p className="text-[10px] text-white/30 font-black uppercase tracking-widest mt-2 px-0.5">Aroma & Bouquet</p> <p className="text-[10px] text-zinc-500 font-bold uppercase tracking-widest mt-2 px-0.5">Aroma & Bouquet</p>
</div> </div>
</div> </div>
@@ -245,7 +245,7 @@ export default function TastingEditor({ bottleMetadata, image, onSave, onOpenSes
<CustomSlider label="Nose Intensity" value={noseScore} onChange={setNoseScore} icon={<Sparkles size={16} />} /> <CustomSlider label="Nose Intensity" value={noseScore} onChange={setNoseScore} icon={<Sparkles size={16} />} />
<div className="space-y-3"> <div className="space-y-3">
<p className="text-[10px] font-black text-white/20 uppercase tracking-widest px-1">Tags</p> <p className="text-[10px] font-bold text-zinc-700 uppercase tracking-widest px-1">Tags</p>
<TagSelector <TagSelector
category="nose" category="nose"
selectedTagIds={noseTagIds} selectedTagIds={noseTagIds}
@@ -255,26 +255,26 @@ export default function TastingEditor({ bottleMetadata, image, onSave, onOpenSes
/> />
</div> </div>
<div className="space-y-3"> <div className="space-y-3">
<p className="text-[10px] font-black text-white/20 uppercase tracking-widest px-1">Eigene Notizen</p> <p className="text-[10px] font-bold text-zinc-700 uppercase tracking-widest px-1">Eigene Notizen</p>
<textarea <textarea
value={noseNotes} value={noseNotes}
onChange={(e) => setNoseNotes(e.target.value)} onChange={(e) => setNoseNotes(e.target.value)}
placeholder={t('tasting.notesPlaceholder') || "Wie riecht er?..."} placeholder={t('tasting.notesPlaceholder') || "Wie riecht er?..."}
className="w-full p-6 bg-zinc-900 border-none rounded-3xl text-sm text-zinc-200 focus:ring-2 focus:ring-amber-500 outline-none min-h-[120px] resize-none transition-all placeholder:text-zinc-600 shadow-inner" className="w-full p-6 bg-zinc-900 border border-zinc-800 rounded-2xl text-sm text-zinc-200 focus:ring-1 focus:ring-orange-500 outline-none min-h-[120px] resize-none transition-all placeholder:text-zinc-600"
/> />
</div> </div>
</div> </div>
</div> </div>
{/* Palate Section */} {/* Palate Section */}
<div className="space-y-8 bg-zinc-900/50 p-8 rounded-[40px] border border-white/5"> <div className="space-y-8 bg-zinc-900/50 p-8 rounded-3xl border border-zinc-800">
<div className="flex items-center gap-5"> <div className="flex items-center gap-5">
<div className="w-14 h-14 rounded-3xl bg-amber-500/10 flex items-center justify-center text-amber-500 shrink-0 border border-amber-500/20 shadow-xl"> <div className="w-14 h-14 rounded-2xl bg-orange-500/10 flex items-center justify-center text-orange-500 shrink-0 border border-orange-500/20 shadow-xl">
<Utensils size={28} /> <Utensils size={28} />
</div> </div>
<div> <div>
<h3 className="text-xl font-black text-white uppercase tracking-widest leading-none">{t('tasting.palate')}</h3> <h3 className="text-xl font-bold text-zinc-50 uppercase tracking-widest leading-none">{t('tasting.palate')}</h3>
<p className="text-[10px] text-white/30 font-black uppercase tracking-widest mt-2 px-0.5">Geschmack & Textur</p> <p className="text-[10px] text-zinc-500 font-bold uppercase tracking-widest mt-2 px-0.5">Geschmack & Textur</p>
</div> </div>
</div> </div>
@@ -282,7 +282,7 @@ export default function TastingEditor({ bottleMetadata, image, onSave, onOpenSes
<CustomSlider label="Taste Impact" value={tasteScore} onChange={setTasteScore} icon={<Sparkles size={16} />} /> <CustomSlider label="Taste Impact" value={tasteScore} onChange={setTasteScore} icon={<Sparkles size={16} />} />
<div className="space-y-3"> <div className="space-y-3">
<p className="text-[10px] font-black text-white/20 uppercase tracking-widest px-1">Tags</p> <p className="text-[10px] font-bold text-zinc-700 uppercase tracking-widest px-1">Tags</p>
<TagSelector <TagSelector
category="taste" category="taste"
selectedTagIds={palateTagIds} selectedTagIds={palateTagIds}
@@ -292,35 +292,35 @@ export default function TastingEditor({ bottleMetadata, image, onSave, onOpenSes
/> />
</div> </div>
<div className="space-y-3"> <div className="space-y-3">
<p className="text-[10px] font-black text-white/20 uppercase tracking-widest px-1">Eigene Notizen</p> <p className="text-[10px] font-bold text-zinc-700 uppercase tracking-widest px-1">Eigene Notizen</p>
<textarea <textarea
value={palateNotes} value={palateNotes}
onChange={(e) => setPalateNotes(e.target.value)} onChange={(e) => setPalateNotes(e.target.value)}
placeholder={t('tasting.notesPlaceholder') || "Wie schmeckt er?..."} placeholder={t('tasting.notesPlaceholder') || "Wie schmeckt er?..."}
className="w-full p-6 bg-zinc-900 border-none rounded-3xl text-sm text-zinc-200 focus:ring-2 focus:ring-amber-500 outline-none min-h-[120px] resize-none transition-all placeholder:text-zinc-600 shadow-inner" className="w-full p-6 bg-zinc-900 border border-zinc-800 rounded-2xl text-sm text-zinc-200 focus:ring-1 focus:ring-orange-500 outline-none min-h-[120px] resize-none transition-all placeholder:text-zinc-600"
/> />
</div> </div>
</div> </div>
</div> </div>
{/* Finish Section */} {/* Finish Section */}
<div className="space-y-8 bg-zinc-900/50 p-8 rounded-[40px] border border-white/5"> <div className="space-y-8 bg-zinc-900/50 p-8 rounded-3xl border border-zinc-800">
<div className="flex items-center gap-5"> <div className="flex items-center gap-5">
<div className="w-14 h-14 rounded-3xl bg-amber-500/10 flex items-center justify-center text-amber-500 shrink-0 border border-amber-500/20 shadow-xl"> <div className="w-14 h-14 rounded-2xl bg-orange-500/10 flex items-center justify-center text-orange-500 shrink-0 border border-orange-500/20 shadow-xl">
<Droplets size={28} /> <Droplets size={28} />
</div> </div>
<div> <div>
<h3 className="text-xl font-black text-white uppercase tracking-widest leading-none">{t('tasting.finish')}</h3> <h3 className="text-xl font-bold text-zinc-50 uppercase tracking-widest leading-none">{t('tasting.finish')}</h3>
<p className="text-[10px] text-white/30 font-black uppercase tracking-widest mt-2 px-0.5">Abgang & Nachklang</p> <p className="text-[10px] text-zinc-500 font-bold uppercase tracking-widest mt-2 px-0.5">Abgang & Nachklang</p>
</div> </div>
</div> </div>
<div className="space-y-6"> <div className="space-y-6">
<CustomSlider label="Finish Duration" value={finishScore} onChange={setFinishScore} icon={<Sparkles size={16} />} /> <CustomSlider label="Finish Duration" value={finishScore} onChange={setFinishScore} icon={<Sparkles size={16} />} />
<div className="space-y-6 pt-4 border-t border-white/5"> <div className="space-y-6 pt-4 border-t border-zinc-800">
<div className="space-y-3"> <div className="space-y-3">
<p className="text-[10px] font-black text-white/20 uppercase tracking-widest px-1">Aroma Tags</p> <p className="text-[10px] font-bold text-zinc-700 uppercase tracking-widest px-1">Aroma Tags</p>
<TagSelector <TagSelector
category="finish" category="finish"
selectedTagIds={finishTagIds} selectedTagIds={finishTagIds}
@@ -330,7 +330,7 @@ export default function TastingEditor({ bottleMetadata, image, onSave, onOpenSes
/> />
</div> </div>
<div className="space-y-3"> <div className="space-y-3">
<p className="text-[10px] font-black text-white/20 uppercase tracking-widest px-1">Gefühl & Textur</p> <p className="text-[10px] font-bold text-zinc-700 uppercase tracking-widest px-1">Gefühl & Textur</p>
<TagSelector <TagSelector
category="texture" category="texture"
selectedTagIds={textureTagIds} selectedTagIds={textureTagIds}
@@ -341,12 +341,12 @@ export default function TastingEditor({ bottleMetadata, image, onSave, onOpenSes
</div> </div>
</div> </div>
<div className="space-y-3"> <div className="space-y-3">
<p className="text-[10px] font-black text-white/20 uppercase tracking-widest px-1">Eigene Notizen</p> <p className="text-[10px] font-bold text-zinc-700 uppercase tracking-widest px-1">Eigene Notizen</p>
<textarea <textarea
value={finishNotes} value={finishNotes}
onChange={(e) => setFinishNotes(e.target.value)} onChange={(e) => setFinishNotes(e.target.value)}
placeholder={t('tasting.notesPlaceholder') || "Der bleibende Eindruck..."} placeholder={t('tasting.notesPlaceholder') || "Der bleibende Eindruck..."}
className="w-full p-6 bg-zinc-900 border-none rounded-3xl text-sm text-zinc-200 focus:ring-2 focus:ring-amber-500 outline-none min-h-[120px] resize-none transition-all placeholder:text-zinc-600 shadow-inner" className="w-full p-6 bg-zinc-900 border border-zinc-800 rounded-2xl text-sm text-zinc-200 focus:ring-1 focus:ring-orange-500 outline-none min-h-[120px] resize-none transition-all placeholder:text-zinc-600"
/> />
</div> </div>
</div> </div>
@@ -354,14 +354,14 @@ export default function TastingEditor({ bottleMetadata, image, onSave, onOpenSes
{/* Buddy Selection */} {/* Buddy Selection */}
{buddies && buddies.length > 0 && ( {buddies && buddies.length > 0 && (
<div className="space-y-8 bg-zinc-900/50 p-8 rounded-[40px] border border-white/5"> <div className="space-y-8 bg-zinc-900/50 p-8 rounded-3xl border border-zinc-800">
<div className="flex items-center gap-5"> <div className="flex items-center gap-5">
<div className="w-14 h-14 rounded-3xl bg-amber-500/10 flex items-center justify-center text-amber-500 shrink-0 border border-amber-500/20 shadow-xl"> <div className="w-14 h-14 rounded-2xl bg-orange-500/10 flex items-center justify-center text-orange-500 shrink-0 border border-orange-500/20 shadow-xl">
<Users size={28} /> <Users size={28} />
</div> </div>
<div> <div>
<h3 className="text-xl font-black text-white uppercase tracking-widest leading-none">Mit wem trinkst du?</h3> <h3 className="text-xl font-bold text-zinc-50 uppercase tracking-widest leading-none">Mit wem trinkst du?</h3>
<p className="text-[10px] text-white/30 font-black uppercase tracking-widest mt-2 px-0.5">Gesellschaft & Buddies</p> <p className="text-[10px] text-zinc-500 font-bold uppercase tracking-widest mt-2 px-0.5">Gesellschaft & Buddies</p>
</div> </div>
</div> </div>
<div className="flex flex-wrap gap-2"> <div className="flex flex-wrap gap-2">
@@ -369,9 +369,9 @@ export default function TastingEditor({ bottleMetadata, image, onSave, onOpenSes
<button <button
key={buddy.id} key={buddy.id}
onClick={() => toggleBuddy(buddy.id)} onClick={() => toggleBuddy(buddy.id)}
className={`px-5 py-3 rounded-2xl text-[10px] font-black uppercase transition-all border flex items-center gap-2 ${selectedBuddyIds.includes(buddy.id) className={`px-5 py-3 rounded-xl text-[10px] font-bold uppercase transition-all border flex items-center gap-2 ${selectedBuddyIds.includes(buddy.id)
? 'bg-amber-600 border-amber-600 text-white shadow-lg' ? 'bg-orange-600 border-orange-600 text-white shadow-lg'
: 'bg-transparent border-white/10 text-white/40 hover:border-white/30' : 'bg-transparent border-zinc-800 text-zinc-500 hover:border-zinc-700'
}`} }`}
> >
{selectedBuddyIds.includes(buddy.id) && <Check size={14} />} {selectedBuddyIds.includes(buddy.id) && <Check size={14} />}
@@ -385,15 +385,15 @@ export default function TastingEditor({ bottleMetadata, image, onSave, onOpenSes
</div> </div>
{/* Sticky Footer - Flex Child 3 */} {/* Sticky Footer - Flex Child 3 */}
<div className="w-full p-8 bg-black/60 backdrop-blur-xl border-t border-white/10 shrink-0"> <div className="w-full p-8 bg-zinc-950 border-t border-zinc-800 shrink-0">
<div className="max-w-2xl mx-auto"> <div className="max-w-2xl mx-auto">
<button <button
onClick={handleInternalSave} onClick={handleInternalSave}
className="w-full py-5 bg-[#C89D46] text-[#0F1014] rounded-3xl font-black uppercase tracking-widest text-xs flex items-center justify-center gap-4 shadow-xl active:scale-[0.98] transition-all" className="w-full py-5 bg-orange-600 text-white rounded-2xl font-bold uppercase tracking-widest text-xs flex items-center justify-center gap-4 shadow-xl active:scale-[0.98] transition-all"
> >
<Send size={20} /> <Send size={20} />
{t('tasting.saveTasting')} {t('tasting.saveTasting')}
<div className="ml-auto bg-black/20 px-3 py-1 rounded-full text-[10px] font-black text-[#0F1014]/60">{rating}</div> <div className="ml-auto bg-black/20 px-3 py-1 rounded-full text-[10px] font-bold text-white/60">{rating}</div>
</button> </button>
</div> </div>
</div> </div>
@@ -404,15 +404,15 @@ export default function TastingEditor({ bottleMetadata, image, onSave, onOpenSes
function CustomSlider({ label, value, onChange, icon }: any) { function CustomSlider({ label, value, onChange, icon }: any) {
return ( return (
<div className="space-y-4 bg-zinc-900/50 p-6 rounded-3xl border border-white/5 shadow-inner"> <div className="space-y-4 bg-zinc-900/50 p-6 rounded-2xl border border-zinc-800 shadow-inner">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div className="flex items-center gap-3 text-white/40"> <div className="flex items-center gap-3 text-zinc-500">
<div className="p-2 rounded-xl bg-[#C89D46]/10 text-[#C89D46]"> <div className="p-2 rounded-xl bg-orange-500/10 text-orange-500">
{icon} {icon}
</div> </div>
<span className="text-[10px] font-black uppercase tracking-[0.2em]">{label}</span> <span className="text-[10px] font-bold uppercase tracking-[0.2em]">{label}</span>
</div> </div>
<span className="text-2xl font-black text-[#C89D46] tracking-tighter">{value}</span> <span className="text-2xl font-bold text-orange-500 tracking-tighter">{value}</span>
</div> </div>
<input <input
type="range" type="range"
@@ -420,7 +420,7 @@ function CustomSlider({ label, value, onChange, icon }: any) {
max="100" max="100"
value={value} value={value}
onChange={(e) => onChange(parseInt(e.target.value))} onChange={(e) => onChange(parseInt(e.target.value))}
className="w-full h-1.5 bg-zinc-800 rounded-full appearance-none cursor-pointer accent-[#C89D46] transition-all" className="w-full h-1.5 bg-zinc-800 rounded-full appearance-none cursor-pointer accent-orange-600 transition-all"
/> />
</div> </div>
); );

View File

@@ -166,61 +166,61 @@ export default function UserManagementClient({ initialUsers, plans }: UserManage
return ( return (
<div className="space-y-6"> <div className="space-y-6">
{/* Search Bar */} {/* Search Bar */}
<div className="bg-white dark:bg-zinc-900 rounded-2xl p-6 border border-zinc-200 dark:border-zinc-800 shadow-sm"> <div className="bg-zinc-900 rounded-[32px] p-6 border border-zinc-800 shadow-sm">
<div className="relative"> <div className="relative">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 text-zinc-400" size={20} /> <Search className="absolute left-3 top-1/2 -translate-y-1/2 text-zinc-500" size={20} />
<input <input
type="text" type="text"
placeholder="Search users by email or username..." placeholder="Search users by email or username..."
value={searchTerm} value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)} onChange={(e) => setSearchTerm(e.target.value)}
className="w-full pl-10 pr-4 py-3 bg-zinc-50 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-amber-500/50" className="w-full pl-10 pr-4 py-3 bg-zinc-800 border border-zinc-700 rounded-2xl text-sm focus:outline-none focus:ring-2 focus:ring-orange-600/50 text-white"
/> />
</div> </div>
</div> </div>
{/* User Table */} {/* User Table */}
<div className="bg-white dark:bg-zinc-900 rounded-2xl p-6 border border-zinc-200 dark:border-zinc-800 shadow-sm"> <div className="bg-zinc-900 rounded-[32px] p-6 border border-zinc-800 shadow-sm overflow-hidden">
<h2 className="text-xl font-black text-zinc-900 dark:text-white mb-4">Users ({filteredUsers.length})</h2> <h2 className="text-xl font-bold text-white uppercase tracking-tighter mb-6">Users ({filteredUsers.length})</h2>
<div className="overflow-x-auto"> <div className="overflow-x-auto -mx-6">
<table className="w-full"> <table className="w-full">
<thead> <thead>
<tr className="border-b border-zinc-200 dark:border-zinc-800"> <tr className="border-b border-zinc-800">
<th className="text-left py-3 px-4 text-xs font-black uppercase text-zinc-400">User</th> <th className="text-left py-4 px-6 text-[10px] font-bold uppercase tracking-widest text-zinc-500">User</th>
<th className="text-left py-3 px-4 text-xs font-black uppercase text-zinc-400">Balance</th> <th className="text-left py-4 px-6 text-[10px] font-bold uppercase tracking-widest text-zinc-500">Balance</th>
<th className="text-left py-3 px-4 text-xs font-black uppercase text-zinc-400">Used</th> <th className="text-left py-4 px-6 text-[10px] font-bold uppercase tracking-widest text-zinc-500">Used</th>
<th className="text-left py-3 px-4 text-xs font-black uppercase text-zinc-400">Daily Limit</th> <th className="text-left py-4 px-6 text-[10px] font-bold uppercase tracking-widest text-zinc-500">Daily Limit</th>
<th className="text-left py-3 px-4 text-xs font-black uppercase text-zinc-400">Costs</th> <th className="text-left py-4 px-6 text-[10px] font-bold uppercase tracking-widest text-zinc-500">Costs</th>
<th className="text-right py-3 px-4 text-xs font-black uppercase text-zinc-400">Actions</th> <th className="text-right py-4 px-6 text-[10px] font-bold uppercase tracking-widest text-zinc-500">Actions</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody className="divide-y divide-zinc-800/50">
{filteredUsers.map((user) => ( {filteredUsers.map((user) => (
<tr key={user.id} className="border-b border-zinc-100 dark:border-zinc-800/50 hover:bg-zinc-50 dark:hover:bg-zinc-800/30"> <tr key={user.id} className="hover:bg-zinc-800/30 transition-colors">
<td className="py-3 px-4"> <td className="py-4 px-6">
<div> <div>
<div className="font-semibold text-zinc-900 dark:text-white">{user.username}</div> <div className="font-bold text-white uppercase tracking-tight text-sm">{user.username}</div>
<div className="text-xs text-zinc-500">{user.email}</div> <div className="text-[10px] font-medium text-zinc-500">{user.email}</div>
</div> </div>
</td> </td>
<td className="py-3 px-4"> <td className="py-4 px-6">
<span className={`text-lg font-black ${user.balance < 10 ? 'text-red-600' : 'text-green-600'}`}> <span className={`text-lg font-bold tracking-tighter ${user.balance < 10 ? 'text-red-500' : 'text-orange-600'}`}>
{user.balance} {user.balance}
</span> </span>
</td> </td>
<td className="py-3 px-4 text-sm text-zinc-600 dark:text-zinc-400"> <td className="py-4 px-6 text-sm font-medium text-zinc-400">
{user.total_used} {user.total_used}
</td> </td>
<td className="py-3 px-4 text-sm text-zinc-600 dark:text-zinc-400"> <td className="py-4 px-6 text-xs text-zinc-500 uppercase tracking-widest">
{user.daily_limit || 'Global (80)'} {user.daily_limit || 'Global (80)'}
</td> </td>
<td className="py-3 px-4 text-xs text-zinc-500"> <td className="py-4 px-6 text-[10px] font-bold text-zinc-600 uppercase tracking-widest">
G:{user.google_search_cost} / AI:{user.gemini_ai_cost} G:{user.google_search_cost} / AI:{user.gemini_ai_cost}
</td> </td>
<td className="py-3 px-4 text-right"> <td className="py-4 px-6 text-right">
<button <button
onClick={() => handleEditUser(user)} onClick={() => handleEditUser(user)}
className="inline-flex items-center gap-2 px-3 py-1.5 bg-amber-600 hover:bg-amber-700 text-white rounded-lg text-xs font-bold transition-colors" className="inline-flex items-center gap-2 px-4 py-2 bg-orange-600 hover:bg-orange-700 text-white rounded-xl text-[10px] font-bold uppercase tracking-widest transition-all shadow-md shadow-orange-950/20"
> >
<Edit size={14} /> <Edit size={14} />
Edit Edit
@@ -235,18 +235,18 @@ export default function UserManagementClient({ initialUsers, plans }: UserManage
{/* Edit Modal */} {/* Edit Modal */}
{editingUser && ( {editingUser && (
<div className="fixed inset-0 bg-black/50 flex items-center justify-center p-4 z-50"> <div className="fixed inset-0 bg-black/80 backdrop-blur-sm flex items-center justify-center p-4 z-50">
<div className="bg-white dark:bg-zinc-900 rounded-2xl p-6 max-w-2xl w-full max-h-[90vh] overflow-y-auto border border-zinc-200 dark:border-zinc-800"> <div className="bg-zinc-900 rounded-[32px] p-8 max-w-2xl w-full max-h-[90vh] overflow-y-auto border border-zinc-800 shadow-2xl space-y-8">
<div className="flex items-center justify-between mb-6"> <div className="flex items-center justify-between">
<div> <div>
<h3 className="text-2xl font-black text-zinc-900 dark:text-white">Edit User</h3> <h3 className="text-2xl font-bold text-white uppercase tracking-tighter">Edit User</h3>
<p className="text-sm text-zinc-500 mt-1">{editingUser.email}</p> <p className="text-[10px] font-bold text-zinc-500 uppercase tracking-widest mt-1">{editingUser.email}</p>
</div> </div>
<button <button
onClick={() => setEditingUser(null)} onClick={() => setEditingUser(null)}
className="p-2 hover:bg-zinc-100 dark:hover:bg-zinc-800 rounded-lg transition-colors" className="p-2 hover:bg-zinc-800 rounded-xl transition-colors text-zinc-500"
> >
<X size={20} /> <X size={24} />
</button> </button>
</div> </div>
@@ -262,40 +262,40 @@ export default function UserManagementClient({ initialUsers, plans }: UserManage
<div className="space-y-6"> <div className="space-y-6">
{/* Current Balance */} {/* Current Balance */}
<div className="p-4 bg-zinc-50 dark:bg-zinc-800 rounded-xl"> <div className="p-6 bg-zinc-800/50 rounded-2xl border border-zinc-800">
<div className="text-xs font-black uppercase text-zinc-400 mb-1">Current Balance</div> <div className="text-[10px] font-bold uppercase text-zinc-500 tracking-widest mb-1">Current Balance</div>
<div className="text-3xl font-black text-zinc-900 dark:text-white">{editingUser.balance} Credits</div> <div className="text-4xl font-bold text-orange-600 tracking-tighter">{editingUser.balance} <span className="text-xl text-zinc-500 ml-1">Credits</span></div>
</div> </div>
{/* Add/Remove Credits */} {/* Add/Remove Credits */}
<div className="space-y-4"> <div className="space-y-4">
<h4 className="font-bold text-zinc-900 dark:text-white">Adjust Credits</h4> <h4 className="text-[10px] font-bold text-zinc-500 uppercase tracking-widest">Adjust Credits</h4>
<div className="grid grid-cols-2 gap-4"> <div className="grid grid-cols-2 gap-4">
<div> <div>
<label className="block text-xs font-bold text-zinc-400 uppercase mb-2">Amount</label> <label className="block text-[8px] font-bold text-zinc-600 uppercase tracking-widest mb-1 ml-1">Amount</label>
<input <input
type="number" type="number"
value={creditAmount} value={creditAmount}
onChange={(e) => setCreditAmount(e.target.value)} onChange={(e) => setCreditAmount(e.target.value)}
placeholder="e.g. 100 or -50" placeholder="e.g. 100 or -50"
className="w-full px-4 py-2 bg-zinc-50 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-amber-500/50" className="w-full px-4 py-2 bg-zinc-800 border border-zinc-700 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-orange-600/50 text-white"
/> />
</div> </div>
<div> <div>
<label className="block text-xs font-bold text-zinc-400 uppercase mb-2">Reason</label> <label className="block text-[8px] font-bold text-zinc-600 uppercase tracking-widest mb-1 ml-1">Reason</label>
<input <input
type="text" type="text"
value={reason} value={reason}
onChange={(e) => setReason(e.target.value)} onChange={(e) => setReason(e.target.value)}
placeholder="e.g. Monthly bonus" placeholder="e.g. Monthly bonus"
className="w-full px-4 py-2 bg-zinc-50 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-amber-500/50" className="w-full px-4 py-2 bg-zinc-800 border border-zinc-700 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-orange-600/50 text-white"
/> />
</div> </div>
</div> </div>
<button <button
onClick={handleUpdateCredits} onClick={handleUpdateCredits}
disabled={loading || !creditAmount || !reason} disabled={loading || !creditAmount || !reason}
className="w-full py-3 bg-amber-600 hover:bg-amber-700 text-white font-bold rounded-xl transition-colors disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2" className="w-full py-4 bg-orange-600 hover:bg-orange-700 text-white font-bold rounded-2xl transition-all shadow-lg shadow-orange-950/20 disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
> >
<Plus size={18} /> <Plus size={18} />
Update Credits Update Credits
@@ -306,20 +306,20 @@ export default function UserManagementClient({ initialUsers, plans }: UserManage
{/* Subscription Plan */} {/* Subscription Plan */}
<div className="space-y-4"> <div className="space-y-4">
<h4 className="font-bold text-zinc-900 dark:text-white">Subscription Plan</h4> <h4 className="text-[10px] font-bold text-zinc-500 uppercase tracking-widest">Subscription Plan</h4>
{currentPlan && ( {currentPlan && (
<div className="p-4 bg-amber-50 dark:bg-amber-900/20 rounded-xl border border-amber-200 dark:border-amber-800"> <div className="p-4 bg-orange-900/20 rounded-2xl border border-orange-500/30">
<div className="text-xs font-bold text-amber-600 dark:text-amber-400 uppercase mb-1">Current Plan</div> <div className="text-[10px] font-bold text-orange-400 uppercase tracking-widest mb-1">Current Plan</div>
<div className="text-lg font-black text-amber-900 dark:text-amber-100">{currentPlan.display_name}</div> <div className="text-xl font-bold text-orange-100 uppercase tracking-tight">{currentPlan.display_name}</div>
<div className="text-sm text-amber-700 dark:text-amber-300 mt-1">{currentPlan.monthly_credits} credits/month</div> <div className="text-[10px] font-bold text-orange-400/60 uppercase tracking-widest mt-1">{currentPlan.monthly_credits} credits/month</div>
</div> </div>
)} )}
<div> <div>
<label className="block text-xs font-bold text-zinc-400 uppercase mb-2">Assign Plan</label> <label className="block text-[8px] font-bold text-zinc-600 uppercase tracking-widest mb-1 ml-1">Assign Plan</label>
<select <select
value={selectedPlan} value={selectedPlan}
onChange={(e) => setSelectedPlan(e.target.value)} onChange={(e) => setSelectedPlan(e.target.value)}
className="w-full px-4 py-2 bg-zinc-50 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-amber-500/50" className="w-full px-4 py-3 bg-zinc-800 border border-zinc-700 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-orange-600/50 text-white appearance-none"
> >
<option value="">Select a plan...</option> <option value="">Select a plan...</option>
{plans.map(plan => ( {plans.map(plan => (
@@ -343,41 +343,41 @@ export default function UserManagementClient({ initialUsers, plans }: UserManage
{/* Settings */} {/* Settings */}
<div className="space-y-4"> <div className="space-y-4">
<h4 className="font-bold text-zinc-900 dark:text-white">User Settings</h4> <h4 className="text-[10px] font-bold text-zinc-500 uppercase tracking-widest">User Settings</h4>
<div className="grid grid-cols-3 gap-4"> <div className="grid grid-cols-3 gap-4">
<div> <div>
<label className="block text-xs font-bold text-zinc-400 uppercase mb-2">Daily Limit</label> <label className="block text-[8px] font-bold text-zinc-600 uppercase tracking-widest mb-1 ml-1">Daily Limit</label>
<input <input
type="number" type="number"
value={dailyLimit} value={dailyLimit}
onChange={(e) => setDailyLimit(e.target.value)} onChange={(e) => setDailyLimit(e.target.value)}
placeholder="Global (80)" placeholder="Global (80)"
className="w-full px-4 py-2 bg-zinc-50 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-amber-500/50" className="w-full px-4 py-2 bg-zinc-800 border border-zinc-700 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-orange-600/50 text-white"
/> />
</div> </div>
<div> <div>
<label className="block text-xs font-bold text-zinc-400 uppercase mb-2">Google Cost</label> <label className="block text-[8px] font-bold text-zinc-600 uppercase tracking-widest mb-1 ml-1">Google Cost</label>
<input <input
type="number" type="number"
value={googleCost} value={googleCost}
onChange={(e) => setGoogleCost(e.target.value)} onChange={(e) => setGoogleCost(e.target.value)}
className="w-full px-4 py-2 bg-zinc-50 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-amber-500/50" className="w-full px-4 py-2 bg-zinc-800 border border-zinc-700 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-orange-600/50 text-white"
/> />
</div> </div>
<div> <div>
<label className="block text-xs font-bold text-zinc-400 uppercase mb-2">Gemini Cost</label> <label className="block text-[8px] font-bold text-zinc-600 uppercase tracking-widest mb-1 ml-1">Gemini Cost</label>
<input <input
type="number" type="number"
value={geminiCost} value={geminiCost}
onChange={(e) => setGeminiCost(e.target.value)} onChange={(e) => setGeminiCost(e.target.value)}
className="w-full px-4 py-2 bg-zinc-50 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-amber-500/50" className="w-full px-4 py-2 bg-zinc-800 border border-zinc-700 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-orange-600/50 text-white"
/> />
</div> </div>
</div> </div>
<button <button
onClick={handleUpdateSettings} onClick={handleUpdateSettings}
disabled={loading} disabled={loading}
className="w-full py-3 bg-zinc-900 dark:bg-zinc-100 text-white dark:text-zinc-900 font-bold rounded-xl transition-colors disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2" className="w-full py-4 bg-zinc-800 hover:bg-zinc-700 text-white font-bold rounded-2xl transition-all border border-zinc-700 disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
> >
<Check size={18} /> <Check size={18} />
Save Settings Save Settings