feat: Add Spotify-style backdrop, Cascade OCR, Smart Scan Flow & OCR Dashboard

- BottleGrid: Implement blurred backdrop effect for bottle cards
- Cascade OCR: TextDetector → RegEx → Fuzzy Match → window.ai pipeline
- Smart Scan: Native OCR for Android, Live Text fallback for iOS
- OCR Dashboard: Admin page at /admin/ocr-logs with stats and scan history
- Features: Add feature flags in src/config/features.ts
- SQL: Add ocr_logs table migration
- Services: Update analyze-bottle to use OpenRouter, add save-ocr-log
This commit is contained in:
2026-01-18 20:38:48 +01:00
parent 83e852e5fb
commit 9ba0825bcd
46 changed files with 3874 additions and 741 deletions

View File

@@ -6,43 +6,62 @@ import { GlassWater, Square, ArrowRight, Sparkles } from 'lucide-react';
import Link from 'next/link';
import { useI18n } from '@/i18n/I18nContext';
import { motion, AnimatePresence } from 'framer-motion';
export default function ActiveSessionBanner() {
const { activeSession, setActiveSession } = useSession();
const { t } = useI18n();
if (!activeSession) return null;
return (
<div className="fixed top-0 left-0 right-0 z-[100] animate-in slide-in-from-top duration-500">
<div className="bg-orange-600 text-white px-4 py-2 flex items-center justify-between shadow-lg">
<Link
href={`/sessions/${activeSession.id}`}
className="flex items-center gap-3 flex-1 min-w-0"
<AnimatePresence>
{activeSession && (
<motion.div
initial={{ y: 50, opacity: 0, x: '-50%' }}
animate={{ y: 0, opacity: 1, x: '-50%' }}
exit={{ y: 50, opacity: 0, x: '-50%' }}
className="fixed bottom-32 left-1/2 z-[50] w-[calc(100%-2rem)] max-w-sm"
>
<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-orange-600 animate-ping" />
</div>
<div className="min-w-0">
<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-1 shrink-0" />
</Link>
<div className="bg-zinc-900/90 backdrop-blur-2xl border border-orange-500/20 rounded-[32px] p-2 flex items-center justify-between shadow-2xl ring-1 ring-white/5 overflow-hidden">
{/* Session Info Link */}
<Link
href={`/sessions/${activeSession.id}`}
className="flex items-center gap-3 px-3 py-2 flex-1 min-w-0 hover:bg-white/5 rounded-2xl transition-colors"
>
<div className="relative shrink-0">
<div className="bg-orange-600/10 p-2.5 rounded-2xl border border-orange-500/20">
<Sparkles size={16} className="text-orange-500" />
</div>
<div className="absolute -top-1 -right-1 w-3 h-3 bg-orange-600 rounded-full border-2 border-zinc-900 animate-pulse shadow-[0_0_8px_rgba(234,88,12,0.6)]" />
</div>
<div className="min-w-0">
<div className="flex items-center gap-2 mb-0.5">
<span className="text-[8px] font-black uppercase tracking-widest text-orange-600 animate-pulse">Live</span>
<p className="text-[9px] font-bold uppercase tracking-wider text-zinc-500 truncate leading-none">{t('session.activeSession')}</p>
</div>
<p className="text-sm font-bold text-zinc-100 truncate leading-none tracking-tight">{activeSession.name}</p>
</div>
</Link>
<button
onClick={() => setActiveSession(null)}
className="ml-4 p-2 hover:bg-white/10 rounded-full transition-colors"
title="End Session"
>
<Square size={20} fill="currentColor" />
</button>
</div>
</div>
{/* Action Buttons */}
<div className="flex items-center gap-1 pr-1">
<Link
href={`/sessions/${activeSession.id}`}
className="p-3 text-zinc-400 hover:text-orange-500 transition-colors"
>
<ArrowRight size={18} />
</Link>
<div className="w-px h-8 bg-zinc-800 mx-1" />
<button
onClick={() => setActiveSession(null)}
className="p-3 text-zinc-600 hover:text-red-500 transition-colors"
title="End Session"
>
<Square size={16} fill="currentColor" className="opacity-40" />
</button>
</div>
</div>
</motion.div>
)}
</AnimatePresence>
);
}