style: redesign app following HIG with larger hero images and refined typography
This commit is contained in:
@@ -4,24 +4,24 @@
|
||||
|
||||
@layer base {
|
||||
:root {
|
||||
--background: #09090b;
|
||||
/* zinc-950 */
|
||||
--surface: #18181b;
|
||||
/* zinc-900 */
|
||||
--background: #1c1c1e;
|
||||
/* systemBackground */
|
||||
--surface: #2c2c2e;
|
||||
/* secondarySystemBackground */
|
||||
--primary: #ea580c;
|
||||
/* orange-600 */
|
||||
--secondary: #f97316;
|
||||
/* orange-500 */
|
||||
--text-primary: #fafafa;
|
||||
--text-secondary: #a1a1aa;
|
||||
--border: #27272a;
|
||||
/* zinc-800 */
|
||||
--border: #38383a;
|
||||
/* separator */
|
||||
--ring: #f97316;
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
@apply bg-[#09090b] text-[#fafafa] antialiased;
|
||||
@apply bg-[#1c1c1e] text-[#fafafa] antialiased selection:bg-orange-500/30;
|
||||
font-feature-settings: "cv02", "cv03", "cv04", "cv11";
|
||||
}
|
||||
|
||||
|
||||
@@ -204,7 +204,7 @@ export default function Home() {
|
||||
}
|
||||
|
||||
return (
|
||||
<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">
|
||||
<main className="flex min-h-screen flex-col items-center gap-6 md:gap-12 p-4 md:p-24 bg-[var(--background)] pb-32">
|
||||
<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">
|
||||
<div className="flex flex-col items-center sm:items-start group">
|
||||
|
||||
@@ -39,11 +39,22 @@ export default function BottleDetails({ bottleId, sessionId, userId }: BottleDet
|
||||
const handleQuickUpdate = async (newPrice?: string, newStatus?: string) => {
|
||||
if (isOffline) return;
|
||||
setIsUpdating(true);
|
||||
|
||||
// Haptic feedback for interaction
|
||||
if (window.navigator.vibrate) {
|
||||
window.navigator.vibrate(10);
|
||||
}
|
||||
|
||||
try {
|
||||
await updateBottle(bottleId, {
|
||||
purchase_price: newPrice !== undefined ? (newPrice ? parseFloat(newPrice) : null) : (price ? parseFloat(price) : null),
|
||||
status: newStatus !== undefined ? newStatus : status
|
||||
} as any);
|
||||
|
||||
// Success haptic
|
||||
if (window.navigator.vibrate) {
|
||||
window.navigator.vibrate([10, 50, 10]);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Quick update failed:', err);
|
||||
} finally {
|
||||
@@ -82,256 +93,245 @@ export default function BottleDetails({ bottleId, sessionId, userId }: BottleDet
|
||||
if (!bottle) return null; // Should not happen due to check above
|
||||
|
||||
return (
|
||||
<div className="max-w-2xl mx-auto px-4 pb-12 space-y-8">
|
||||
{/* Back Button */}
|
||||
<div className="pt-4">
|
||||
<Link
|
||||
href={`/${sessionId ? `?session_id=${sessionId}` : ''}`}
|
||||
className="inline-flex items-center gap-2 text-zinc-500 hover:text-zinc-300 transition-colors font-bold text-sm tracking-tight"
|
||||
>
|
||||
<ChevronLeft size={18} />
|
||||
Zurück
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{isOffline && (
|
||||
<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-orange-600" />
|
||||
<p className="text-[10px] font-bold uppercase tracking-widest text-orange-500">Offline-Modus</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Header & Hero Section */}
|
||||
<div className="space-y-6">
|
||||
{/* 1. Header (Title at top) */}
|
||||
<div className="text-center md:text-left">
|
||||
<h1 className="text-3xl md:text-5xl font-black text-white tracking-tighter uppercase leading-none">
|
||||
{bottle.name}
|
||||
</h1>
|
||||
<h2 className="text-lg md:text-xl text-orange-600 font-bold mt-2 uppercase tracking-[0.2em]">
|
||||
{bottle.distillery}
|
||||
</h2>
|
||||
<div className="max-w-4xl mx-auto pb-24">
|
||||
{/* Header / Hero Section */}
|
||||
<div className="relative w-full overflow-hidden bg-[var(--surface)] shadow-2xl">
|
||||
{/* Back Button Overlay */}
|
||||
<div className="absolute top-6 left-6 z-20">
|
||||
<Link
|
||||
href={`/${sessionId ? `?session_id=${sessionId}` : ''}`}
|
||||
className="flex items-center justify-center w-10 h-10 rounded-full bg-black/40 backdrop-blur-md text-white border border-white/10 active:scale-95 transition-all"
|
||||
>
|
||||
<ChevronLeft size={24} />
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{/* 2. Image (Below title) */}
|
||||
<div className="relative aspect-video w-full max-h-[280px] rounded-3xl overflow-hidden bg-radial-dark border border-zinc-800/50 flex items-center justify-center p-4">
|
||||
<div className="absolute inset-0 bg-[radial-gradient(circle_at_center,_var(--tw-gradient-stops))] from-zinc-800/20 via-transparent to-transparent opacity-50" />
|
||||
{/* Hero Image - Slightly More Compact Aspect for better title flow */}
|
||||
<div className="relative aspect-[4/3] md:aspect-[16/8] w-full flex items-center justify-center p-6 md:p-10 overflow-hidden">
|
||||
{/* Background Glow */}
|
||||
<div className="absolute inset-0 bg-[radial-gradient(circle_at_center,_var(--tw-gradient-stops))] from-orange-600/10 via-transparent to-transparent opacity-30" />
|
||||
<img
|
||||
src={getStorageUrl(bottle.image_url)}
|
||||
alt={bottle.name}
|
||||
className="max-h-full max-w-full object-contain drop-shadow-[0_20px_50px_rgba(0,0,0,0.5)] z-10"
|
||||
className="max-h-full max-w-full object-contain drop-shadow-[0_20px_60px_rgba(0,0,0,0.6)] z-10 transition-transform duration-700 hover:scale-105"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 3. Metadata Consolidation (Info Row) */}
|
||||
<div className="flex flex-wrap items-center justify-center md:justify-start gap-2 pt-2">
|
||||
<div className="flex items-center gap-1.5 px-3 py-1.5 bg-zinc-900/80 border border-zinc-800 rounded-full">
|
||||
<Wine size={14} className="text-orange-500" />
|
||||
<span className="text-[11px] font-black uppercase tracking-wider text-zinc-300">{bottle.category || 'Whisky'}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1.5 px-3 py-1.5 bg-zinc-900/80 border border-zinc-800 rounded-full">
|
||||
<Droplets size={14} className="text-blue-400" />
|
||||
<span className="text-[11px] font-black uppercase tracking-wider text-zinc-300">{bottle.abv}%</span>
|
||||
</div>
|
||||
{bottle.age && (
|
||||
<div className="flex items-center gap-1.5 px-3 py-1.5 bg-zinc-900/80 border border-zinc-800 rounded-full">
|
||||
<Calendar size={14} className="text-zinc-500" />
|
||||
<span className="text-[11px] font-black uppercase tracking-wider text-zinc-300">{bottle.age}J.</span>
|
||||
</div>
|
||||
)}
|
||||
{bottle.distilled_at && (
|
||||
<div className="flex items-center gap-1.5 px-3 py-1.5 bg-zinc-900/80 border border-zinc-800 rounded-full">
|
||||
<Calendar size={12} className="text-zinc-500" />
|
||||
<span className="text-[10px] font-black uppercase tracking-wider text-zinc-400">Dist. {bottle.distilled_at}</span>
|
||||
</div>
|
||||
)}
|
||||
{bottle.bottled_at && (
|
||||
<div className="flex items-center gap-1.5 px-3 py-1.5 bg-zinc-900/80 border border-zinc-800 rounded-full">
|
||||
<Package size={12} className="text-zinc-500" />
|
||||
<span className="text-[10px] font-black uppercase tracking-wider text-zinc-400">Bott. {bottle.bottled_at}</span>
|
||||
</div>
|
||||
)}
|
||||
{bottle.batch_info && (
|
||||
<div className="flex items-center gap-1.5 px-3 py-1.5 bg-zinc-900/80 border border-zinc-800 rounded-full">
|
||||
<Info size={12} className="text-zinc-500" />
|
||||
<span className="text-[10px] font-black uppercase tracking-wider text-zinc-400">{bottle.batch_info}</span>
|
||||
</div>
|
||||
)}
|
||||
{bottle.whiskybase_id && (
|
||||
<a
|
||||
href={`https://www.whiskybase.com/whiskies/whisky/${bottle.whiskybase_id}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="flex items-center gap-1.5 px-3 py-1.5 bg-zinc-900/80 border border-zinc-800 rounded-full hover:border-orange-600/50 transition-colors"
|
||||
>
|
||||
<ExternalLink size={12} className="text-zinc-600" />
|
||||
<span className="text-[10px] font-black uppercase tracking-wider text-zinc-500">WB {bottle.whiskybase_id}</span>
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
{/* Info Overlay - Mobile Gradient */}
|
||||
<div className="absolute inset-x-0 bottom-0 h-48 bg-gradient-to-t from-[var(--background)] to-transparent pointer-events-none" />
|
||||
</div>
|
||||
|
||||
{/* 4. Inventory Section (Cohesive Container) */}
|
||||
<section className="bg-zinc-900/30 border border-zinc-800/50 rounded-[32px] p-6 space-y-6">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<h3 className="text-[10px] font-black uppercase tracking-[0.2em] text-zinc-500">My Bottle</h3>
|
||||
<Package size={14} className="text-zinc-700" />
|
||||
</div>
|
||||
|
||||
<div className="space-y-6">
|
||||
{/* Segmented Control for Status */}
|
||||
<div className="space-y-2">
|
||||
<label className="text-[10px] font-bold uppercase text-zinc-600 ml-1">Status</label>
|
||||
<div className="grid grid-cols-3 bg-zinc-950 p-1 rounded-2xl border border-zinc-800/50">
|
||||
{['sealed', 'open', 'empty'].map((s) => (
|
||||
<button
|
||||
key={s}
|
||||
disabled={isOffline}
|
||||
onClick={() => handleQuickUpdate(undefined, s)}
|
||||
className={`py-2.5 rounded-xl text-[10px] font-black uppercase tracking-widest transition-all ${status === s
|
||||
? 'bg-orange-600 text-white shadow-lg'
|
||||
: 'text-zinc-600 hover:text-zinc-400'
|
||||
}`}
|
||||
>
|
||||
{s === 'sealed' ? 'Sealed' : s === 'open' ? 'Open' : 'Empty'}
|
||||
</button>
|
||||
))}
|
||||
{/* Content Container */}
|
||||
<div className="px-6 md:px-12 -mt-12 relative z-10 space-y-12">
|
||||
{/* Title Section - HIG Large Title Pattern */}
|
||||
<div className="space-y-2">
|
||||
{isOffline && (
|
||||
<div className="inline-flex bg-orange-600/10 border border-orange-600/20 px-3 py-1 rounded-full items-center gap-2 mb-2">
|
||||
<WifiOff size={12} className="text-orange-600" />
|
||||
<p className="text-[9px] font-black uppercase tracking-widest text-orange-500">Offline</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<label className="text-[10px] font-bold uppercase text-zinc-600 ml-1">Price</label>
|
||||
<div className="relative">
|
||||
<input
|
||||
type="number"
|
||||
inputMode="decimal"
|
||||
step="0.01"
|
||||
value={price}
|
||||
onChange={(e) => setPrice(e.target.value)}
|
||||
onBlur={() => handleQuickUpdate(price)}
|
||||
placeholder="0.00"
|
||||
className="w-full bg-zinc-950 border border-zinc-800 rounded-2xl pl-4 pr-8 py-3 text-sm font-bold text-zinc-100 focus:outline-none focus:border-orange-600"
|
||||
/>
|
||||
<div className="absolute right-4 top-1/2 -translate-y-1/2 text-[10px] font-bold text-zinc-700">€</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<label className="text-[10px] font-bold uppercase text-zinc-600 ml-1">Last Dram</label>
|
||||
<div className="w-full bg-zinc-950 border border-zinc-800 rounded-2xl px-4 py-3 text-sm font-bold text-zinc-400 flex items-center gap-2">
|
||||
<Calendar size={14} className="text-zinc-700" />
|
||||
{tastings && tastings.length > 0
|
||||
? new Date(tastings[0].created_at).toLocaleDateString(locale === 'de' ? 'de-DE' : 'en-US', { day: '2-digit', month: '2-digit' })
|
||||
: '-'}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* 5. Editing Form (Accordion) */}
|
||||
<section>
|
||||
<button
|
||||
onClick={() => setIsFormVisible(!isFormVisible)}
|
||||
className="w-full px-6 py-4 bg-zinc-900/50 border border-zinc-800/80 rounded-2xl flex items-center justify-between text-zinc-400 hover:text-orange-500 transition-all group"
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<Circle size={14} className={isFormVisible ? 'text-orange-600 fill-orange-600' : 'text-zinc-700'} />
|
||||
<span className="text-xs font-black uppercase tracking-widest">Details korrigieren</span>
|
||||
</div>
|
||||
<ChevronDown size={18} className={`transition-transform duration-300 ${isFormVisible ? 'rotate-180 text-orange-600' : ''}`} />
|
||||
</button>
|
||||
|
||||
<AnimatePresence>
|
||||
{isFormVisible && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, height: 0 }}
|
||||
animate={{ opacity: 1, height: 'auto' }}
|
||||
exit={{ opacity: 0, height: 0 }}
|
||||
transition={{ duration: 0.3, ease: 'circOut' }}
|
||||
className="overflow-hidden"
|
||||
>
|
||||
<div className="pt-4 px-2">
|
||||
<EditBottleForm
|
||||
bottle={bottle as any}
|
||||
onComplete={() => setIsFormVisible(false)}
|
||||
/>
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
<h2 className="text-sm font-black text-orange-600 uppercase tracking-[0.2em]">
|
||||
{bottle.distillery}
|
||||
</h2>
|
||||
<h1 className="text-3xl md:text-5xl font-extrabold text-white tracking-tight leading-[1.1]">
|
||||
{bottle.name}
|
||||
</h1>
|
||||
|
||||
{!isOffline && (
|
||||
<div className="flex gap-2 pt-6">
|
||||
<Link
|
||||
href={`/splits/create?bottle=${bottle.id}`}
|
||||
className="flex-1 py-4 bg-zinc-900 hover:bg-zinc-800 text-white rounded-2xl text-[10px] font-black uppercase tracking-[0.2em] flex items-center justify-center gap-2 border border-zinc-800 transition-all"
|
||||
>
|
||||
<Share2 size={14} className="text-orange-500" />
|
||||
Split starten
|
||||
</Link>
|
||||
<div className="flex-none">
|
||||
<DeleteBottleButton bottleId={bottle.id} />
|
||||
{/* Metadata Items - Text based for better readability */}
|
||||
<div className="flex flex-wrap items-center gap-3 pt-6">
|
||||
<div className="px-4 py-2 bg-white/5 border border-white/10 rounded-xl">
|
||||
<p className="text-[10px] font-bold text-zinc-500 uppercase tracking-widest mb-0.5">Category</p>
|
||||
<p className="text-sm font-black text-zinc-100 uppercase">{bottle.category || 'Whisky'}</p>
|
||||
</div>
|
||||
<div className="px-4 py-2 bg-white/5 border border-white/10 rounded-xl">
|
||||
<p className="text-[10px] font-bold text-zinc-500 uppercase tracking-widest mb-0.5">ABV</p>
|
||||
<p className="text-sm font-black text-zinc-100 uppercase">{bottle.abv}%</p>
|
||||
</div>
|
||||
{bottle.age && (
|
||||
<div className="px-4 py-2 bg-white/5 border border-white/10 rounded-xl">
|
||||
<p className="text-[10px] font-bold text-zinc-500 uppercase tracking-widest mb-0.5">Age</p>
|
||||
<p className="text-sm font-black text-zinc-100 uppercase">{bottle.age} Years</p>
|
||||
</div>
|
||||
)}
|
||||
{bottle.whiskybase_id && (
|
||||
<a
|
||||
href={`https://www.whiskybase.com/whiskies/whisky/${bottle.whiskybase_id}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="px-4 py-2 bg-orange-600 border border-orange-500 rounded-xl hover:bg-orange-500 transition-colors"
|
||||
>
|
||||
<p className="text-[10px] font-bold text-orange-200 uppercase tracking-widest mb-0.5">Whiskybase</p>
|
||||
<p className="text-sm font-black text-white uppercase flex items-center gap-2">
|
||||
#{bottle.whiskybase_id} <ExternalLink size={14} />
|
||||
</p>
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 4. Inventory Section (Cohesive Container) */}
|
||||
<section className="bg-zinc-800/30 backdrop-blur-xl border border-white/5 rounded-[40px] p-8 space-y-8">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h3 className="text-xs font-black uppercase tracking-[0.2em] text-zinc-500">Collection Stats</h3>
|
||||
<Package size={18} className="text-zinc-700" />
|
||||
</div>
|
||||
|
||||
<div className="space-y-6">
|
||||
{/* Segmented Control for Status */}
|
||||
<div className="space-y-2">
|
||||
<label className="text-[10px] font-bold uppercase text-zinc-600 ml-1">Status</label>
|
||||
<div className="grid grid-cols-3 bg-zinc-950 p-1 rounded-2xl border border-zinc-800/50">
|
||||
{['sealed', 'open', 'empty'].map((s) => (
|
||||
<button
|
||||
key={s}
|
||||
disabled={isOffline}
|
||||
onClick={() => handleQuickUpdate(undefined, s)}
|
||||
className={`py-2.5 rounded-xl text-[10px] font-black uppercase tracking-widest transition-all ${status === s
|
||||
? 'bg-orange-600 text-white shadow-lg'
|
||||
: 'text-zinc-600 hover:text-zinc-400'
|
||||
}`}
|
||||
>
|
||||
{s === 'sealed' ? 'Sealed' : s === 'open' ? 'Open' : 'Empty'}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<label className="text-[10px] font-bold uppercase text-zinc-600 ml-1">Price</label>
|
||||
<div className="relative">
|
||||
<input
|
||||
type="number"
|
||||
inputMode="decimal"
|
||||
step="0.01"
|
||||
value={price}
|
||||
onChange={(e) => setPrice(e.target.value)}
|
||||
onBlur={() => handleQuickUpdate(price)}
|
||||
placeholder="0.00"
|
||||
className="w-full bg-zinc-950 border border-zinc-800 rounded-2xl pl-4 pr-8 py-3 text-sm font-bold text-zinc-100 focus:outline-none focus:border-orange-600"
|
||||
/>
|
||||
<div className="absolute right-4 top-1/2 -translate-y-1/2 text-[10px] font-bold text-zinc-700">€</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<label className="text-[10px] font-bold uppercase text-zinc-600 ml-1">Last Dram</label>
|
||||
<div className="w-full bg-zinc-950 border border-zinc-800 rounded-2xl px-4 py-3 text-sm font-bold text-zinc-400 flex items-center gap-2">
|
||||
<Calendar size={14} className="text-zinc-700" />
|
||||
{tastings && tastings.length > 0
|
||||
? new Date(tastings[0].created_at).toLocaleDateString(locale === 'de' ? 'de-DE' : 'en-US', { day: '2-digit', month: '2-digit' })
|
||||
: '-'}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<hr className="border-zinc-800" />
|
||||
{/* 5. Editing Form (Accordion) */}
|
||||
<section>
|
||||
<button
|
||||
onClick={() => setIsFormVisible(!isFormVisible)}
|
||||
className="w-full px-6 py-4 bg-zinc-900/50 border border-zinc-800/80 rounded-2xl flex items-center justify-between text-zinc-400 hover:text-orange-500 transition-all group"
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<Circle size={14} className={isFormVisible ? 'text-orange-600 fill-orange-600' : 'text-zinc-700'} />
|
||||
<span className="text-xs font-black uppercase tracking-widest">Details korrigieren</span>
|
||||
</div>
|
||||
<ChevronDown size={18} className={`transition-transform duration-300 ${isFormVisible ? 'rotate-180 text-orange-600' : ''}`} />
|
||||
</button>
|
||||
|
||||
{/* Tasting Notes Section */}
|
||||
<section className="space-y-8">
|
||||
<div className="flex flex-col md:flex-row justify-between items-start md:items-end gap-4">
|
||||
<div>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
<AnimatePresence>
|
||||
{isFormVisible && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, height: 0 }}
|
||||
animate={{ opacity: 1, height: 'auto' }}
|
||||
exit={{ opacity: 0, height: 0 }}
|
||||
transition={{ duration: 0.3, ease: 'circOut' }}
|
||||
className="overflow-hidden"
|
||||
>
|
||||
<div className="pt-4 px-2">
|
||||
<EditBottleForm
|
||||
bottle={bottle as any}
|
||||
onComplete={() => setIsFormVisible(false)}
|
||||
/>
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6 md:gap-8 items-start">
|
||||
{/* Form */}
|
||||
<div className="lg:col-span-1 space-y-4 md:sticky md:top-24">
|
||||
<button
|
||||
onClick={() => setIsFormVisible(!isFormVisible)}
|
||||
className={`w-full p-6 rounded-3xl border flex items-center justify-between transition-all group ${isFormVisible ? 'bg-orange-600 border-orange-600 text-white shadow-xl shadow-orange-950/40' : 'bg-zinc-900/50 border-zinc-800 text-zinc-400 hover:border-orange-500/30'}`}
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
{isFormVisible ? <Plus size={20} className="rotate-45 transition-transform" /> : <Plus size={20} className="text-orange-600 transition-transform" />}
|
||||
<span className={`text-sm font-black uppercase tracking-widest ${isFormVisible ? 'text-white' : 'text-zinc-100'}`}>Neue Tasting Note</span>
|
||||
{!isOffline && (
|
||||
<div className="flex gap-2 pt-6">
|
||||
<Link
|
||||
href={`/splits/create?bottle=${bottle.id}`}
|
||||
className="flex-1 py-4 bg-zinc-900 hover:bg-zinc-800 text-white rounded-2xl text-[10px] font-black uppercase tracking-[0.2em] flex items-center justify-center gap-2 border border-zinc-800 transition-all"
|
||||
>
|
||||
<Share2 size={14} className="text-orange-500" />
|
||||
Split starten
|
||||
</Link>
|
||||
<div className="flex-none">
|
||||
<DeleteBottleButton bottleId={bottle.id} />
|
||||
</div>
|
||||
<ChevronDown size={20} className={`transition-transform duration-300 ${isFormVisible ? 'rotate-180' : 'opacity-0'}`} />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
|
||||
<AnimatePresence>
|
||||
{isFormVisible && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: -20, height: 0 }}
|
||||
animate={{ opacity: 1, y: 0, height: 'auto' }}
|
||||
exit={{ opacity: 0, y: -20, height: 0 }}
|
||||
className="overflow-hidden"
|
||||
>
|
||||
<div className="border border-zinc-800 rounded-3xl p-6 bg-zinc-900/50">
|
||||
<h3 className="text-lg font-bold mb-6 flex items-center gap-2 text-orange-600 uppercase tracking-widest">
|
||||
<Droplets size={20} /> Dram bewerten
|
||||
</h3>
|
||||
<TastingNoteForm
|
||||
bottleId={bottle.id}
|
||||
sessionId={sessionId}
|
||||
onSuccess={() => setIsFormVisible(false)}
|
||||
/>
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
<hr className="border-zinc-800" />
|
||||
|
||||
{/* Tasting Notes Section */}
|
||||
<section className="space-y-8">
|
||||
<div className="flex flex-col md:flex-row justify-between items-start md:items-end gap-4">
|
||||
<div>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* List */}
|
||||
<div className="lg:col-span-2">
|
||||
<TastingList initialTastings={tastings as any || []} currentUserId={userId} />
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6 md:gap-8 items-start">
|
||||
{/* Form */}
|
||||
<div className="lg:col-span-1 space-y-4 md:sticky md:top-24">
|
||||
<button
|
||||
onClick={() => setIsFormVisible(!isFormVisible)}
|
||||
className={`w-full p-6 rounded-3xl border flex items-center justify-between transition-all group ${isFormVisible ? 'bg-orange-600 border-orange-600 text-white shadow-xl shadow-orange-950/40' : 'bg-zinc-900/50 border-zinc-800 text-zinc-400 hover:border-orange-500/30'}`}
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
{isFormVisible ? <Plus size={20} className="rotate-45 transition-transform" /> : <Plus size={20} className="text-orange-600 transition-transform" />}
|
||||
<span className={`text-sm font-black uppercase tracking-widest ${isFormVisible ? 'text-white' : 'text-zinc-100'}`}>Neue Tasting Note</span>
|
||||
</div>
|
||||
<ChevronDown size={20} className={`transition-transform duration-300 ${isFormVisible ? 'rotate-180' : 'opacity-0'}`} />
|
||||
</button>
|
||||
|
||||
<AnimatePresence>
|
||||
{isFormVisible && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: -20, height: 0 }}
|
||||
animate={{ opacity: 1, y: 0, height: 'auto' }}
|
||||
exit={{ opacity: 0, y: -20, height: 0 }}
|
||||
className="overflow-hidden"
|
||||
>
|
||||
<div className="border border-zinc-800 rounded-3xl p-6 bg-zinc-900/50">
|
||||
<h3 className="text-lg font-bold mb-6 flex items-center gap-2 text-orange-600 uppercase tracking-widest">
|
||||
<Droplets size={20} /> Dram bewerten
|
||||
</h3>
|
||||
<TastingNoteForm
|
||||
bottleId={bottle.id}
|
||||
sessionId={sessionId}
|
||||
onSuccess={() => setIsFormVisible(false)}
|
||||
/>
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
|
||||
{/* List */}
|
||||
<div className="lg:col-span-2">
|
||||
<TastingList initialTastings={tastings as any || []} currentUserId={userId} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ function BottleCard({ bottle, sessionId }: BottleCardProps) {
|
||||
return (
|
||||
<Link
|
||||
href={`/bottles/${bottle.id}${sessionId ? `?session_id=${sessionId}` : ''}`}
|
||||
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]"
|
||||
className="block h-fit group relative overflow-hidden rounded-2xl bg-zinc-800/20 backdrop-blur-sm border border-white/[0.05] transition-all duration-500 hover:border-orange-500/30 hover:shadow-2xl hover:shadow-orange-950/20 active:scale-[0.98]"
|
||||
>
|
||||
{/* Image Layer - Clean Split Top */}
|
||||
<div className="aspect-[4/3] overflow-hidden">
|
||||
@@ -50,10 +50,10 @@ function BottleCard({ bottle, sessionId }: BottleCardProps) {
|
||||
{/* Info Layer - Clean Split Bottom */}
|
||||
<div className="p-4 space-y-4">
|
||||
<div className="space-y-1">
|
||||
<p className="text-[10px] font-bold text-orange-500 uppercase tracking-widest leading-none">
|
||||
<p className="text-[10px] font-black text-orange-600 uppercase tracking-[0.2em] leading-none mb-1">
|
||||
{bottle.distillery}
|
||||
</p>
|
||||
<h3 className="font-bold text-lg text-zinc-50 leading-tight">
|
||||
<h3 className="font-bold text-xl text-zinc-50 leading-tight tracking-tight">
|
||||
{bottle.name || t('grid.unknownBottle')}
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
@@ -22,11 +22,11 @@ interface NavButtonProps {
|
||||
const NavButton = ({ onClick, icon, label, ariaLabel }: NavButtonProps) => (
|
||||
<button
|
||||
onClick={onClick}
|
||||
className="flex flex-col items-center gap-0.5 px-3 py-1.5 text-zinc-400 hover:text-white transition-colors active:scale-95"
|
||||
className="flex flex-col items-center justify-center gap-1 w-full min-w-[44px] min-h-[44px] text-zinc-400 hover:text-white transition-colors active:scale-90"
|
||||
aria-label={ariaLabel}
|
||||
>
|
||||
{icon}
|
||||
<span className="text-[9px] font-medium tracking-wide">{label}</span>
|
||||
<span className="text-[10px] font-bold tracking-tight">{label}</span>
|
||||
</button>
|
||||
);
|
||||
|
||||
@@ -55,7 +55,7 @@ export const BottomNavigation = ({ onHome, onShelf, onSearch, onProfile, onScan
|
||||
className="hidden"
|
||||
/>
|
||||
|
||||
<div className="flex items-center justify-between px-1 py-1 bg-zinc-900/95 backdrop-blur-lg border border-zinc-800 rounded-full shadow-2xl pointer-events-auto">
|
||||
<div className="flex items-center justify-between px-2 py-1 bg-[#1c1c1e]/80 backdrop-blur-2xl border border-white/10 rounded-full shadow-2xl pointer-events-auto ring-1 ring-black/20">
|
||||
{/* Left Items */}
|
||||
<NavButton
|
||||
onClick={onHome}
|
||||
|
||||
@@ -85,10 +85,10 @@ export default function SessionBottomSheet({ isOpen, onClose }: SessionBottomShe
|
||||
animate={{ y: 0 }}
|
||||
exit={{ y: '100%' }}
|
||||
transition={{ type: 'spring', damping: 25, stiffness: 200 }}
|
||||
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)]"
|
||||
className="fixed bottom-0 left-0 right-0 bg-[var(--background)] border-t border-white/5 rounded-t-[40px] z-[90] p-8 pb-12 max-h-[85vh] overflow-y-auto shadow-[0_-20px_60px_rgba(0,0,0,0.8)] ring-1 ring-white/5"
|
||||
>
|
||||
{/* Drag Handle */}
|
||||
<div className="w-12 h-1.5 bg-zinc-800 rounded-full mx-auto mb-8" />
|
||||
<div className="w-10 h-1 bg-white/10 rounded-full mx-auto mb-8" />
|
||||
|
||||
<h2 className="text-2xl font-bold mb-6 text-zinc-50">Tasting Session</h2>
|
||||
|
||||
@@ -126,7 +126,7 @@ export default function SessionBottomSheet({ isOpen, onClose }: SessionBottomShe
|
||||
setActiveSession({ id: s.id, name: s.name });
|
||||
onClose();
|
||||
}}
|
||||
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'}`}
|
||||
className={`w-full flex items-center justify-between p-5 rounded-[24px] border transition-all active:scale-[0.98] ${activeSession?.id === s.id ? 'bg-orange-600/10 border-orange-600 text-orange-500' : 'bg-white/5 border-white/5 hover:border-white/10 text-zinc-50'}`}
|
||||
>
|
||||
<span className="font-bold">{s.name}</span>
|
||||
{activeSession?.id === s.id ? <Check size={20} /> : <ChevronRight size={20} className="text-zinc-700" />}
|
||||
|
||||
Reference in New Issue
Block a user