style: Improve active session visibility and fix mobile timeline layout
This commit is contained in:
@@ -11,6 +11,8 @@ import StatsDashboard from "@/components/StatsDashboard";
|
||||
import DramOfTheDay from "@/components/DramOfTheDay";
|
||||
import LanguageSwitcher from "@/components/LanguageSwitcher";
|
||||
import { useI18n } from "@/i18n/I18nContext";
|
||||
import { useSession } from "@/context/SessionContext";
|
||||
import { Sparkles } from "lucide-react";
|
||||
|
||||
export default function Home() {
|
||||
const supabase = createClient();
|
||||
@@ -19,6 +21,7 @@ export default function Home() {
|
||||
const [user, setUser] = useState<any>(null);
|
||||
const [fetchError, setFetchError] = useState<string | null>(null);
|
||||
const { t } = useI18n();
|
||||
const { activeSession } = useSession();
|
||||
|
||||
useEffect(() => {
|
||||
// Check session
|
||||
@@ -160,9 +163,23 @@ export default function Home() {
|
||||
<main className="flex min-h-screen flex-col items-center gap-6 md:gap-12 p-4 md:p-24 bg-zinc-50 dark:bg-black">
|
||||
<div className="z-10 max-w-5xl w-full flex flex-col items-center gap-8">
|
||||
<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">
|
||||
<h1 className="text-4xl font-black text-zinc-900 dark:text-white tracking-tighter">
|
||||
WHISKY<span className="text-amber-600">VAULT</span>
|
||||
</h1>
|
||||
{activeSession && (
|
||||
<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">
|
||||
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-red-400 opacity-75"></span>
|
||||
<span className="relative inline-flex rounded-full h-2 w-2 bg-red-500"></span>
|
||||
</div>
|
||||
<span className="text-[9px] font-black uppercase tracking-widest text-red-500 flex items-center gap-1">
|
||||
<Sparkles size={10} className="animate-pulse" />
|
||||
Live: {activeSession.name}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-wrap items-center justify-center sm:justify-end gap-3 md:gap-4">
|
||||
<LanguageSwitcher />
|
||||
<DramOfTheDay bottles={bottles} />
|
||||
|
||||
@@ -19,14 +19,20 @@ export default function ActiveSessionBanner() {
|
||||
href={`/sessions/${activeSession.id}`}
|
||||
className="flex items-center gap-3 flex-1 min-w-0"
|
||||
>
|
||||
<div className="bg-white/20 p-1.5 rounded-lg shrink-0">
|
||||
<div className="relative shrink-0">
|
||||
<div className="bg-white/20 p-1.5 rounded-lg">
|
||||
<Sparkles size={16} className="text-white animate-pulse" />
|
||||
</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>
|
||||
<div className="min-w-0">
|
||||
<p className="text-[10px] font-black uppercase tracking-wider opacity-90 leading-none mb-1">{t('session.activeSession')}</p>
|
||||
<div className="flex items-center gap-2 mb-0.5">
|
||||
<span className="text-[9px] font-black uppercase tracking-widest bg-white/20 px-1.5 py-0.5 rounded leading-none text-white whitespace-nowrap">Live Jetzt</span>
|
||||
<p className="text-[10px] font-black uppercase tracking-wider opacity-90 leading-none truncate">{t('session.activeSession')}</p>
|
||||
</div>
|
||||
<p className="text-sm font-bold truncate leading-none">{activeSession.name}</p>
|
||||
</div>
|
||||
<ArrowRight size={14} className="opacity-50 ml-2" />
|
||||
<ArrowRight size={14} className="opacity-50 ml-1 shrink-0" />
|
||||
</Link>
|
||||
|
||||
<button
|
||||
|
||||
@@ -351,6 +351,7 @@ export default function CameraCapture({ onImageCaptured, onAnalysisComplete, onS
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center gap-4 md:gap-6 w-full max-w-md mx-auto p-4 md:p-6 bg-white dark:bg-zinc-900 rounded-3xl shadow-2xl border border-zinc-200 dark:border-zinc-800 transition-all hover:shadow-whisky-amber/20">
|
||||
<div className="flex flex-col w-full gap-1">
|
||||
<div className="flex items-center justify-between w-full">
|
||||
<h2 className="text-xl md:text-2xl font-bold text-zinc-800 dark:text-zinc-100 italic">{t('camera.magicShot')}</h2>
|
||||
|
||||
@@ -371,6 +372,16 @@ export default function CameraCapture({ onImageCaptured, onAnalysisComplete, onS
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{activeSession && (
|
||||
<div className="flex items-center gap-1.5 text-[9px] font-black uppercase tracking-widest text-amber-600 animate-in slide-in-from-left-2 duration-500">
|
||||
<div className="relative flex h-1.5 w-1.5">
|
||||
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-amber-400 opacity-75"></span>
|
||||
<span className="relative inline-flex rounded-full h-1.5 w-1.5 bg-amber-500"></span>
|
||||
</div>
|
||||
{activeSession.name}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="relative group cursor-pointer w-full aspect-square rounded-2xl border-2 border-dashed border-zinc-300 dark:border-zinc-700 overflow-hidden flex items-center justify-center bg-zinc-50 dark:bg-zinc-800/50 hover:border-amber-500 transition-colors"
|
||||
|
||||
@@ -45,14 +45,12 @@ export default function SessionTimeline({ tastings, sessionStart }: SessionTimel
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="relative pl-8 space-y-8 before:absolute before:inset-0 before:left-[11px] before:w-[2px] before:bg-zinc-200 dark:before:bg-zinc-800 before:h-full">
|
||||
<div className="relative pl-6 space-y-6 before:absolute before:inset-0 before:left-[9px] before:w-[2px] before:bg-zinc-200 dark:before:bg-zinc-800 before:h-full mb-12">
|
||||
{sortedTastings.map((tasting, index) => {
|
||||
const currentTime = new Date(tasting.tasted_at).getTime();
|
||||
const diffMinutes = Math.round((currentTime - firstTastingTime) / (1000 * 60));
|
||||
const isSmoky = checkIsSmoky(tasting);
|
||||
|
||||
// Palette warning logic: if this dram is peaty, warn about the NEXT one (metaphorically)
|
||||
// Or if the PREVIOUS was peaty, show a warning on this one.
|
||||
const wasPreviousSmoky = index > 0 && checkIsSmoky(sortedTastings[index - 1]);
|
||||
const timeSinceLastDram = index > 0
|
||||
? Math.round((currentTime - new Date(sortedTastings[index - 1].tasted_at).getTime()) / (1000 * 60))
|
||||
@@ -61,34 +59,30 @@ export default function SessionTimeline({ tastings, sessionStart }: SessionTimel
|
||||
return (
|
||||
<div key={tasting.id} className="relative group">
|
||||
{/* Dot */}
|
||||
<div className={`absolute -left-[30px] w-6 h-6 rounded-full border-4 border-white dark:border-zinc-900 shadow-sm z-10 flex items-center justify-center ${isSmoky ? 'bg-amber-600' : 'bg-zinc-400'}`}>
|
||||
{isSmoky && <Droplets size={10} className="text-white fill-white" />}
|
||||
</div>
|
||||
|
||||
{/* Relative Time */}
|
||||
<div className="absolute -left-16 -top-1 w-12 text-right">
|
||||
<span className="text-[10px] font-black text-zinc-400 dark:text-zinc-600 block leading-none">
|
||||
{index === 0 ? 'START' : `+${diffMinutes}'`}
|
||||
</span>
|
||||
<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'}`}>
|
||||
{isSmoky && <Droplets size={8} className="text-white fill-white" />}
|
||||
</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="flex justify-between items-start gap-3">
|
||||
<div className="min-w-0">
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<span className="text-[10px] font-black text-amber-600 uppercase tracking-widest">Dram #{index + 1}</span>
|
||||
<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-zinc-400 dark:text-zinc-600 uppercase tracking-tight leading-none">
|
||||
{index === 0 ? 'Start' : `+${diffMinutes}m`}
|
||||
</span>
|
||||
{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>
|
||||
)}
|
||||
</div>
|
||||
<Link
|
||||
href={`/bottles/${tasting.bottle_id}`}
|
||||
className="text-sm font-bold text-zinc-800 dark:text-zinc-100 hover:text-amber-600 truncate block"
|
||||
className="text-sm font-bold text-zinc-800 dark:text-zinc-100 hover:text-amber-600 truncate block mt-0.5"
|
||||
>
|
||||
{tasting.bottle_name}
|
||||
</Link>
|
||||
<div className="mt-2 flex flex-wrap gap-1">
|
||||
{tasting.tags.slice(0, 3).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">
|
||||
{tag}
|
||||
</span>
|
||||
@@ -96,8 +90,8 @@ export default function SessionTimeline({ tastings, sessionStart }: SessionTimel
|
||||
</div>
|
||||
</div>
|
||||
<div className="shrink-0 flex flex-col items-end">
|
||||
<div className="text-lg font-black text-zinc-900 dark:text-white">{tasting.rating}</div>
|
||||
<div className="text-[9px] font-bold text-zinc-400 uppercase tracking-tighter">Punkte</div>
|
||||
<div className="text-lg font-black text-zinc-900 dark:text-white leading-none">{tasting.rating}</div>
|
||||
<div className="text-[9px] font-bold text-zinc-400 uppercase tracking-tighter mt-1">Punkte</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user