From cf491d83b64d86ed21f35aa91321743db7e90dd1 Mon Sep 17 00:00:00 2001 From: robin Date: Sun, 21 Dec 2025 23:41:33 +0100 Subject: [PATCH] DramLog UI Overhaul: Rebranding, Navigation Improvements, and Scan Workflow Fixes - Renamed app to DramLog and updated branding to Gold (#C89D46) - Implemented new BottomNavigation with Floating Scan Button - Fixed 'black screen' race condition in ScanAndTasteFlow - Refactored TastingEditor and StatsDashboard for a cleaner editorial look - Standardized colors and typography across the application --- .aiideas | 149 +++++---- src/app/globals.css | 1 + src/app/layout.tsx | 8 +- src/app/page.tsx | 69 ++-- src/components/BottleGrid.tsx | 210 ++++++------ src/components/BottomNavigation.tsx | 108 ++++++ src/components/BuddyList.tsx | 36 +- src/components/ScanAndTasteFlow.tsx | 251 +++++++------- src/components/SessionList.tsx | 50 +-- src/components/StatsDashboard.tsx | 15 +- src/components/TastingEditor.tsx | 500 ++++++++++++++-------------- 11 files changed, 769 insertions(+), 628 deletions(-) create mode 100644 src/components/BottomNavigation.tsx diff --git a/.aiideas b/.aiideas index 228bb4d..49d9059 100644 --- a/.aiideas +++ b/.aiideas @@ -1,110 +1,117 @@ +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 Senior Frontend Engineer und UX - Experte mit Spezialisierung auf "Mobile-First" - Webanwendungen.Dein Fokus liegt auf High - End Ästhetik(Dark Mode), flüssigen Animationen und reibungsloser User Experience. +Tech Stack: React, Tailwind CSS, Lucide - React Icons. +1. Globales Design - System(Anwenden auf alle Views) - Aufgabe: Wir bauen den Core - Flow einer bestehenden Whisky - Tasting - App um.Implementiere den neuen "Scan & Taste" Flow.Ziel ist eine Single - Page - App - Experience(SPA) ohne Reloads, die sich nativ anfühlt. +Branding: App Name ist "DramLog". -Tech Stack(Anpassen falls nötig): + Farben: -Framework: Next.js(Bitte nutze den bestehenden Stack der App) +Background: #0F1014(Deep Rich Black) -Styling: Tailwind CSS +Surface: #1A1B20(Card Backgrounds) -Icons: Lucide - React oder HeroIcons + Primary Accent: #C89D46(Whisky Amber / Gold) -Charts: Recharts oder Chart.js(für das Radar Chart) +Text - Secondary: #8F9096 -Animation: Framer Motion(für Transitions) +Typografie: -1. Design System & Vibe + Importiere und nutze Playfair Display(Serif) für: Überschriften(h1 - h3), Whisky - Namen auf Cards. -Theme: Strict Dark Mode. + Nutze Inter(Sans) für: UI - Elemente, Labels, Fließtext, Daten. - Background: #0F1014(Deep Anthracite / Black) + Shape: rounded - 2xl für Cards, rounded - full für Buttons. -Surface / Cards: #1A1B20(Lighter Anthracite) +2. Spezifische Component Refactorings - Primary Accent: #C89D46(Whisky Gold / Amber) +A.Dashboard(Home View): -Text: Sans - Serif(Inter) für UI, Serif(Playfair Display) für Überschriften / Namen. + 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. - Stil: "Premium & Warm".Runde Ecken(rounded - 2xl), Glassmorphism für Overlays(backdrop - blur - md, bg - white / 5), feine Borders(border - white / 10). + Benenne "Dein Bestand" um in "Collection". -2. Der Flow(Schritt für Schritt Implementierung) + Mache die Listen - Einträge unter "Tasting Sessions" interaktiv.Entferne die sichtbaren Trash / Edit Buttons und nutze ein sauberes Listen - Layout. -Bitte implementiere folgende Views / Components als zusammenhängenden Flow: -A.Der Entry Point(Floating Action Button) + B.Whisky Card(Collection Grid): - Erstelle einen prominenten, schwebenden Button(unten mittig, fixed), der über dem Dashboard liegt. + Redesign der Card - Komponente. - Icon: Kamera - Symbol. + Bild: Muss bis an den Rand gehen(w - full, kein Padding). - Interaction: Beim Klick simulieren wir einen Kamera - Scan(nutze vorerst ein Mock - Timeout von 2s mit einer Lade - Animation "Analysiere Etikett..."), danach Transition zu View B. + Overlay: Lege einen bg - gradient - to - t from - black via - black / 80 to - transparent über das untere Drittel des Bildes. - B.Der Tasting Editor(Main Component) + Text: Platziere Name(Serif) und Destillerie(Sans, Uppercase, Gold) weiß auf dem Bild im unteren Bereich(über dem Gradient). -Dies ist der wichtigste Screen.Layout - Struktur: + Tags: Mache Tags minimalistisch(bg - white / 10 backdrop - blur - sm text - xs border border - white / 10). - Top Bar(Sticky): +3. Die "Floating" Navigation(Core Feature) - Zeige einen "Context Indicator". +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: +JavaScript - Logik: Zeige Text "Trinkst du in Gesellschaft? + Session wählen". +// Reference Implementation for BottomNavigation.jsx +import { Home, Grid, Scan, User, Search } from 'lucide-react'; - Interaction: Klick öffnet ein Bottom Sheet(siehe C).Wenn eine Session gewählt wurde, zeige: "Session: [Name]". +export const BottomNavigation = () => { + return ( +
+ {/* Background Container mit Glassmorphism */ } + < div className = "relative bg-[#0F1014]/90 backdrop-blur-xl border-t border-white/10 pb-safe pt-2" > - Hero Section: +
- Zeige das(gemockte) Foto der Flasche links. + {/* Left Actions */ } + < button className = "flex flex-col items-center gap-1 text-[#C89D46] w-12" > + + < span className = "text-[10px] font-medium" > Home + - Rechts daneben: Name(Serif, Gold), Alter, ABV. (Mock Data: "Lagavulin 16, Islay, 43%"). + < button className = "flex flex-col items-center gap-1 text-gray-500 hover:text-white w-12 transition-colors" > + + < span className = "text-[10px] font-medium" > Shelf + - Form Section(Scrollable): + {/* Spacer für den Center Button */ } +
-Slider: Erstelle eine Custom - Komponente für "Nose", "Taste", "Finish".Nutze keine Zahlen - Inputs, sondern Range - Slider(0 - 100). + {/* Right Actions */ } + < button className = "flex flex-col items-center gap-1 text-gray-500 hover:text-white w-12 transition-colors" > + + < span className = "text-[10px] font-medium" > Search + - Smart Tags(Wichtig!): Implementiere eine Chip - Auswahl. + < button className = "flex flex-col items-center gap-1 text-gray-500 hover:text-white w-12 transition-colors" > + + < span className = "text-[10px] font-medium" > Profile + +
- Design: "Ghost Button" Style(transparenter BG, feiner Border). + {/* THE FLOATING MAGIC BUTTON */ } +
+ +
- Active State: Füllt sich mit #C89D46(Gold), Text wird dunkel. +
+
+ ); +}; - Data: Mocke AI - Vorschläge wie["Rauch", "Torf", "Jod", "Vanille"]. +Aufgabe: - Sticky Footer: + Implementiere das globale Styling(Fonts / Colors). - Ein Button "Save Tasting"(Full Width), der immer sichtbar unten schwebt(z - index: 50). + Baue die BottomNavigation ein(ersetze alte Navs). - C.Das Session Bottom Sheet(Overlay) - - Wenn man in View B auf die Top Bar klickt, fährt von unten ein Sheet hoch(Höhe: 50vh). - - Inhalt: Input Feld für "Neue Session" und Liste "Aktuelle Sessions". - - Beim Auswählen schließt sich das Sheet und aktualisiert den State in View B(Context Bar). - - D.Die Result Card(The Reward) - -Nach dem Speichern(Transition: Fade out Editor -> Fade in Card): - - Zeige eine "Trading Card" im 9: 16 Verhältnis, zentriert. - - Inhalt: - - Großes Foto der Flasche mit Vignette. - - Ein Radar Chart(Spider Web) für die 5 Geschmacksprofile(Nose, Taste, Finish, Balance, Complexity). - - Ein "Badge" oben rechts mit dem Score(z.B. 8.5). - - Action: Ein Button "Share Image" unter der Karte. (Logik: Bereite navigator.share vor). - -3. Technische Anforderungen & State - - Nutze einen lokalen State(oder Context), um die Daten zwischen Editor und Result zu halten. - - Mocke die "AI Response"(Flaschenerkennung) mit einem festen Datensatz(JSON), damit wir das UI testen können. - - Achte auf Mobile - Viewport - Height(dvh), damit Safari - Bars nichts verdecken. - -Wenn du etwas schon hast pass es an und integriere es in den neuen Flow + Passe die Dashboard View an den neuen "Clean Look" an. + Passe die WhiskyCard an den neuen "Editorial Look" an. \ No newline at end of file diff --git a/src/app/globals.css b/src/app/globals.css index 3910112..cf08d72 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -7,6 +7,7 @@ --background: #0F1014; --surface: #1A1B20; --primary: #C89D46; + --text-secondary: #8F9096; --border: rgba(255, 255, 255, 0.1); } } diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 5efb9d5..9b7183b 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -18,15 +18,15 @@ import { Playfair_Display } from "next/font/google"; export const metadata: Metadata = { title: { - default: "Whisky Vault", - template: "%s | Whisky Vault" + default: "DramLog", + template: "%s | DramLog" }, - description: "Dein persönlicher Whisky-Begleiter zum Scannen und Verkosten.", + description: "Premium Digitaler Tasting Begleiter für Genießer.", manifest: "/manifest.webmanifest", appleWebApp: { capable: true, statusBarStyle: "default", - title: "Whisky Vault", + title: "DramLog", }, formatDetection: { telephone: false, diff --git a/src/app/page.tsx b/src/app/page.tsx index 5675dab..e4772f5 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -12,8 +12,8 @@ import LanguageSwitcher from "@/components/LanguageSwitcher"; import OfflineIndicator from "@/components/OfflineIndicator"; import { useI18n } from "@/i18n/I18nContext"; import { useSession } from "@/context/SessionContext"; -import { Sparkles, Camera } from "lucide-react"; -import FloatingScannerButton from '@/components/FloatingScannerButton'; +import { Sparkles, X } from "lucide-react"; +import { BottomNavigation } from '@/components/BottomNavigation'; import ScanAndTasteFlow from '@/components/ScanAndTasteFlow'; export default function Home() { @@ -151,13 +151,13 @@ export default function Home() { if (!user) { return ( -
-
-

- WHISKYVAULT +
+
+

+ DRAMLOG

-

- {t('home.searchPlaceholder').replace('...', '')} +

+ Premium Digitaler Tasting Begleiter für Genießer.

@@ -169,20 +169,20 @@ export default function Home() { } return ( -
-
+
+
-

- WHISKYVAULT +

+ DRAMLOG

{activeSession && (
- - + +
- + Live: {activeSession.name} @@ -195,7 +195,7 @@ export default function Home() { @@ -206,7 +206,7 @@ export default function Home() {
-
+
@@ -215,25 +215,27 @@ export default function Home() {
-
-

- {t('home.collection')} - - {bottles.length} +
+
+

+ Collection +

+ + {bottles.length} Bottles -

+
{isLoading ? ( -
-
+
+
) : fetchError ? ( -
-

{t('common.error')}

-

{fetchError}

+
+

{t('common.error')}

+

{fetchError}

@@ -244,7 +246,14 @@ export default function Home() {
- + window.scrollTo({ top: 0, behavior: 'smooth' })} + onShelf={() => document.getElementById('collection')?.scrollIntoView({ behavior: 'smooth' })} + onSearch={() => document.getElementById('search-filter')?.scrollIntoView({ behavior: 'smooth' })} + onProfile={() => window.scrollTo({ top: 0, behavior: 'smooth' })} + /> + setIsFlowOpen(false)} diff --git a/src/components/BottleGrid.tsx b/src/components/BottleGrid.tsx index 29ceb44..248f105 100644 --- a/src/components/BottleGrid.tsx +++ b/src/components/BottleGrid.tsx @@ -36,68 +36,73 @@ function BottleCard({ bottle, sessionId }: BottleCardProps) { return ( -
-
- {bottle.name} -
+ {/* Image Layer - Edge to Edge */} +
+ {bottle.name} - {sessionId && ( -
- - {t('grid.addSession')} -
- )} + {/* Gradient Overlay as requested: bottom third, black to transparent */} +
+
- {bottle.last_tasted && ( -
- - {new Date(bottle.last_tasted).toLocaleDateString(locale === 'de' ? 'de-DE' : 'en-US')} -
- )} - -
- -
-
-
-

{bottle.distillery}

- {(bottle.is_whisky === false || (bottle.confidence && bottle.confidence < 70)) && ( -
- - {t('grid.reviewRequired')} -
- )} -
-

- {bottle.name || t('grid.unknownBottle')} -

-
- -
- + {/* Content Layer */} +
+
+ {/* Tags Layer - Minimalist Glassmorphism */} +
+ {shortenCategory(bottle.category)} - + {bottle.abv}% VOL
-
- - {t('grid.addedOn')} - +
+

+ {bottle.distillery} +

+

+ {bottle.name || t('grid.unknownBottle')} +

+
+ + {/* Metadata items */} +
+
+ {new Date(bottle.created_at).toLocaleDateString(locale === 'de' ? 'de-DE' : 'en-US')} - +
+ {bottle.last_tasted && ( +
+ + {new Date(bottle.last_tasted).toLocaleDateString(locale === 'de' ? 'de-DE' : 'en-US')} +
+ )}
+ + {/* Top Overlays */} + {(bottle.is_whisky === false || (bottle.confidence && bottle.confidence < 70)) && ( +
+
+ +
+
+ )} + + {sessionId && ( +
+ + ADD TO SESSION +
+ )} ); } @@ -190,51 +195,51 @@ export default function BottleGrid({ bottles }: BottleGridProps) { const activeFiltersCount = (selectedCategory ? 1 : 0) + (selectedDistillery ? 1 : 0); return ( -
- {/* Search and Filters */} -
-
+
+ {/* Search and Filters - Minimalist Look */} +
+
- + setSearchQuery(e.target.value)} - className="w-full pl-12 pr-12 py-3.5 bg-white dark:bg-zinc-900 border border-zinc-200 dark:border-zinc-800 rounded-[1.25rem] focus:ring-2 focus:ring-amber-500/20 focus:border-amber-500/50 outline-none transition-all shadow-sm" + 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" /> {searchQuery && ( )}
-
+
- {/* Category Quick Filter (Always visible row) */} -
+ {/* Category Quick Filter - Glass Chips */} +
{categories.map((cat) => ( ))}
- {/* Collapsible Advanced Filters */} + {/* Collapsible Advanced Filters - Minimalist Overlay */} {isFiltersOpen && ( -
-
-
- -
+
+
+ +
+ + {distilleries.map((dist) => ( - {distilleries.map((dist) => ( - - ))} -
+ ))}
-
-
+
diff --git a/src/components/BottomNavigation.tsx b/src/components/BottomNavigation.tsx new file mode 100644 index 0000000..8fe190e --- /dev/null +++ b/src/components/BottomNavigation.tsx @@ -0,0 +1,108 @@ +'use client'; + +import React from 'react'; +import { Home, Grid, Scan, User, Search } from 'lucide-react'; +import { usePathname } from 'next/navigation'; + +interface BottomNavigationProps { + onHome?: () => void; + onShelf?: () => void; + onSearch?: () => void; + onProfile?: () => void; + onScan: (base64: string) => void; +} + +export const BottomNavigation = ({ onHome, onShelf, onSearch, onProfile, onScan }: BottomNavigationProps) => { + const fileInputRef = React.useRef(null); + + const handleScanClick = () => { + fileInputRef.current?.click(); + }; + + const handleFileChange = (e: React.ChangeEvent) => { + const file = e.target.files?.[0]; + if (file) { + const reader = new FileReader(); + reader.onloadend = () => { + onScan(reader.result as string); + }; + reader.readAsDataURL(file); + } + }; + + return ( +
+ {/* Hidden Input for Scanning */} + + + {/* Background Container mit Glassmorphism */} +
+ +
+ {/* Left Actions */} + + + + + {/* Spacer für den Center Button */} +
+ + {/* Right Actions */} + + + +
+ + {/* THE FLOATING MAGIC BUTTON */} +
+ + {/* Visual Gold Glow */} +
+
+ +
+
+ ); +}; diff --git a/src/components/BuddyList.tsx b/src/components/BuddyList.tsx index 0e17f14..4ba9208 100644 --- a/src/components/BuddyList.tsx +++ b/src/components/BuddyList.tsx @@ -80,18 +80,18 @@ export default function BuddyList() { }; return ( -
+
-

- +

+ {t('buddy.title')} {!isCollapsed && buddies.length > 0 && ( - ({buddies.length}) + ({buddies.length}) )}

{isLoading ? ( -
+
) : buddies.length === 0 ? ( -
+
{t('buddy.noBuddies')}
) : ( -
+
{buddies.map((buddy) => (
-
+
{buddy.name[0].toUpperCase()}
- {buddy.name} + {buddy.name} {buddy.buddy_profile_id && ( - {t('common.link')} + {t('common.link')} )}
@@ -160,17 +160,17 @@ export default function BuddyList() {
{buddies.slice(0, 5).map((b, i) => ( -
+
{b.name[0].toUpperCase()}
))} {buddies.length > 5 && ( -
+
+{buddies.length - 5}
)}
- {buddies.length} Buddies + {buddies.length} Buddies
)}
diff --git a/src/components/ScanAndTasteFlow.tsx b/src/components/ScanAndTasteFlow.tsx index 65cddda..8ffa1ac 100644 --- a/src/components/ScanAndTasteFlow.tsx +++ b/src/components/ScanAndTasteFlow.tsx @@ -33,8 +33,10 @@ export default function ScanAndTasteFlow({ isOpen, onClose, base64Image }: ScanA const { locale } = useI18n(); const supabase = createClient(); + // Trigger scan when open and image provided useEffect(() => { if (isOpen && base64Image) { + console.log('[ScanFlow] Starting handleScan...'); handleScan(base64Image); } else if (!isOpen) { setState('IDLE'); @@ -51,15 +53,19 @@ export default function ScanAndTasteFlow({ isOpen, onClose, base64Image }: ScanA try { const cleanBase64 = image.split(',')[1] || image; + console.log('[ScanFlow] Calling magicScan service...'); const result = await magicScan(cleanBase64, 'gemini', locale); if (result.success && result.data) { + console.log('[ScanFlow] magicScan success'); setBottleMetadata(result.data); setState('EDITOR'); } else { + console.error('[ScanFlow] magicScan failure:', result.error); throw new Error(result.error || 'Flasche konnte nicht erkannt werden.'); } } catch (err: any) { + console.error('[ScanFlow] handleScan error:', err); setError(err.message); setState('ERROR'); } @@ -120,136 +126,141 @@ export default function ScanAndTasteFlow({ isOpen, onClose, base64Image }: ScanA } }; - if (!isOpen) return null; - return ( - - {/* Close Button */} - + {/* Close Button */} + -
- {state === 'SCANNING' && ( -
- -
- -
- -
-
-
-

Analysiere Etikett...

-

- KI-gestütztes Scanning -

-
-
-
- )} - - {state === 'ERROR' && ( -
- -
- -
-
-

Ups! Da lief was schief.

-

{error || 'Wir konnten die Flasche leider nicht erkennen. Bitte versuch es mit einem anderen Foto.'}

-
- -
-
- )} - - {state === 'EDITOR' && bottleMetadata && ( - - setIsSessionsOpen(true)} - activeSessionName={activeSession?.name} - activeSessionId={activeSession?.id} - /> - - )} - - {(isSaving) && ( - - -

Speichere Tasting...

-
- )} - - {state === 'RESULT' && tastingData && bottleMetadata && ( -
-
+
+ {/* + Robust state check: + If we are IDLE but have an image, we are essentially SCANNING (or about to be). + If we have no image, we shouldn't really be here, but show error just in case. + */} + {(state === 'SCANNING' || (state === 'IDLE' && base64Image)) && ( +
- +
+ +
+ +
+
+
+

Analysiere Etikett...

+

+ KI-gestütztes Scanning +

+
-
- )} -
+ )} - setIsSessionsOpen(false)} - /> - + {state === 'ERROR' && ( +
+ +
+ +
+
+

Ups! Da lief was schief.

+

{error || 'Wir konnten die Flasche leider nicht erkennen. Bitte versuch es mit einem anderen Foto.'}

+
+ +
+
+ )} + + {state === 'EDITOR' && bottleMetadata && ( + + setIsSessionsOpen(true)} + activeSessionName={activeSession?.name} + activeSessionId={activeSession?.id} + /> + + )} + + {(isSaving) && ( + + +

Speichere Tasting...

+
+ )} + + {state === 'RESULT' && tastingData && bottleMetadata && ( +
+
+ + + +
+
+ )} +
+ + setIsSessionsOpen(false)} + /> + + )} ); } diff --git a/src/components/SessionList.tsx b/src/components/SessionList.tsx index 83d87ac..6d66c2a 100644 --- a/src/components/SessionList.tsx +++ b/src/components/SessionList.tsx @@ -140,18 +140,18 @@ export default function SessionList() { }; return ( -
+
-

- +

+ {t('session.title')} {!isCollapsed && sessions.length > 0 && ( - ({sessions.length}) + ({sessions.length}) )}

{isLoading ? ( -
+
) : sessions.length === 0 ? ( -
+
{t('session.noSessions')}
) : ( @@ -190,19 +190,19 @@ export default function SessionList() { {sessions.map((session) => (
-
+
{session.name} {session.ended_at && ( - Closed + Closed )}
-
+
{new Date(session.scheduled_at).toLocaleDateString(locale === 'de' ? 'de-DE' : 'en-US')} @@ -215,7 +215,7 @@ export default function SessionList() { )}
{session.participants && session.participants.length > 0 && ( -
+
)} @@ -225,28 +225,28 @@ export default function SessionList() { !session.ended_at ? ( ) : ( -
+
) ) : ( -
+
)} - + +
+ +
{/* Main Scrollable Content - Flex Child 2 */} -
- {/* Palette Warning */} - {showPaletteWarning && ( - - -
-

Palette-Checker

-

- Dein letzter Dram "{lastDramInSession?.name}" war torfig. Trink etwas Wasser! -

- +
+
+ {/* Palette Warning */} + {showPaletteWarning && ( + + +
+

Palette-Checker

+

+ Dein letzter Dram "{lastDramInSession?.name}" war torfig. Trink etwas Wasser! +

+ +
+
+ )} + + {/* Hero Section */} +
+
+ {image ? ( + Bottle Preview + ) : ( +
No Photo
+ )} +
+
+

+ {bottleMetadata.distillery || 'Destillerie'} +

+

{bottleMetadata.name || 'Unbekannter Malt'}

+

+ {bottleMetadata.category || 'Whisky'} {bottleMetadata.abv ? `• ${bottleMetadata.abv}%` : ''} {bottleMetadata.age ? `• ${bottleMetadata.age}y` : ''} +

- - )} - - {/* Hero Section */} -
-
- {image ? ( - Bottle Preview - ) : ( -
No Photo
- )} -
-
-

- {bottleMetadata.distillery || 'Destillerie'} -

-

{bottleMetadata.name || 'Unbekannter Malt'}

-

- {bottleMetadata.category || 'Whisky'} {bottleMetadata.abv ? `• ${bottleMetadata.abv}%` : ''} {bottleMetadata.age ? `• ${bottleMetadata.age}y` : ''} -

-
-
- - {/* Rating Slider */} -
-
- -
-
- - {rating}/100 -
- setRating(parseInt(e.target.value))} - className="w-full h-2 bg-zinc-800 rounded-full appearance-none cursor-pointer accent-amber-600 transition-all" - /> -
- Swill - Dram - Legendary
-
- {['Bottle', 'Sample'].map(type => ( - - ))} -
-
- - {/* Evaluation Sliders Area */} -
- } /> - } /> -
- - {/* Sections */} -
- {/* Nose Section */} -
-
-
- -
-
-

{t('tasting.nose')}

-

Aroma & Bouquet

-
-
- -
- } /> - -
-

Tags

- setNoseTagIds(prev => prev.includes(id) ? prev.filter(i => i !== id) : [...prev, id])} - suggestedTagNames={suggestedTags} - suggestedCustomTagNames={suggestedCustomTags} - /> -
-
-

Eigene Notizen

-