feat(ui): implement dual view/manage modes, live barcode camera scanner, and interactive grams adjuster

This commit is contained in:
2026-10-10 22:37:27 +02:00
parent 8c7fa79b0c
commit 494f76c9c1
3 changed files with 1636 additions and 0 deletions
+1050
View File
File diff suppressed because it is too large Load Diff
+504
View File
@@ -0,0 +1,504 @@
<!DOCTYPE html>
<html lang="en" class="h-full bg-slate-900 text-slate-100">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>Fridger - Smart Fridge Inventory</title>
<!-- Tailwind CSS -->
<script src="https://cdn.tailwindcss.com"></script>
<script>
tailwind.config = {
darkMode: 'class',
theme: {
extend: {
colors: {
brand: {
50: '#f0fdfa',
100: '#ccfbf1',
400: '#2dd4bf',
500: '#14b8a6',
600: '#0d9488',
700: '#0f766e',
}
}
}
}
}
</script>
<!-- Lucide Icons -->
<script src="https://unpkg.com/lucide@latest"></script>
<!-- HTML5 QR Code Scanner -->
<script src="https://unpkg.com/html5-qrcode" type="text/javascript"></script>
<link rel="stylesheet" href="/styles.css">
</head>
<body class="h-full flex flex-col bg-slate-950 text-slate-100 antialiased selection:bg-teal-500 selection:text-white">
<!-- TOP APP BAR -->
<header class="sticky top-0 z-30 bg-slate-900/90 backdrop-blur-md border-b border-slate-800">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex items-center justify-between h-16 gap-4">
<!-- Brand Logo -->
<div class="flex items-center gap-3 shrink-0">
<div class="w-10 h-10 rounded-xl bg-gradient-to-tr from-teal-600 to-cyan-500 flex items-center justify-center shadow-lg shadow-teal-500/20 text-white font-black text-xl">
🧊
</div>
<div>
<h1 class="font-bold text-lg leading-tight tracking-tight bg-gradient-to-r from-teal-400 to-cyan-300 bg-clip-text text-transparent">Fridger</h1>
<p class="text-xs text-slate-400 hidden sm:block">Fridge Inventory & Grams Tracker</p>
</div>
</div>
<!-- TWO-MODE SWITCHER -->
<div class="flex items-center bg-slate-800/80 p-1 rounded-xl border border-slate-700/80 shadow-inner">
<button id="mode-view-btn" onclick="switchMode('view')" class="flex items-center gap-2 px-3 sm:px-5 py-2 rounded-lg text-sm font-semibold transition-all duration-200 bg-teal-500 text-white shadow-md">
<i data-lucide="eye" class="w-4 h-4"></i>
<span>Fridge View</span>
</button>
<button id="mode-manage-btn" onclick="switchMode('manage')" class="flex items-center gap-2 px-3 sm:px-5 py-2 rounded-lg text-sm font-semibold transition-all duration-200 text-slate-300 hover:text-white">
<i data-lucide="sliders-horizontal" class="w-4 h-4"></i>
<span>Manage Fridge</span>
</button>
</div>
<!-- QUICK SCAN / ADD ACTIONS -->
<div class="flex items-center gap-2">
<button onclick="openScannerModal()" class="flex items-center gap-2 px-3 sm:px-4 py-2 rounded-xl bg-gradient-to-r from-teal-500 to-emerald-600 hover:from-teal-400 hover:to-emerald-500 text-white text-sm font-medium shadow-lg shadow-teal-500/25 transition-transform active:scale-95" title="Scan Barcode">
<i data-lucide="scan-barcode" class="w-4 h-4"></i>
<span class="hidden md:inline">Scan Barcode</span>
</button>
<button onclick="openItemModal()" class="flex items-center gap-2 px-3 sm:px-4 py-2 rounded-xl bg-slate-800 hover:bg-slate-700 border border-slate-700 text-slate-200 text-sm font-medium transition active:scale-95" title="Add Manually">
<i data-lucide="plus" class="w-4 h-4"></i>
<span class="hidden md:inline">Add Item</span>
</button>
</div>
</div>
</div>
</header>
<!-- STATS BAR -->
<section class="bg-slate-900/50 border-b border-slate-800/60 py-3">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="grid grid-cols-2 sm:grid-cols-4 gap-3 text-sm">
<div class="bg-slate-800/40 rounded-xl p-3 border border-slate-800 flex items-center gap-3">
<div class="w-9 h-9 rounded-lg bg-teal-500/10 text-teal-400 flex items-center justify-center shrink-0">
<i data-lucide="refrigerator" class="w-5 h-5"></i>
</div>
<div>
<div id="stat-total" class="text-lg font-bold text-white leading-none">0</div>
<div class="text-xs text-slate-400 mt-0.5">Total In Fridge</div>
</div>
</div>
<div class="bg-slate-800/40 rounded-xl p-3 border border-slate-800 flex items-center gap-3">
<div class="w-9 h-9 rounded-lg bg-emerald-500/10 text-emerald-400 flex items-center justify-center shrink-0">
<i data-lucide="check-circle-2" class="w-5 h-5"></i>
</div>
<div>
<div id="stat-fresh" class="text-lg font-bold text-emerald-400 leading-none">0</div>
<div class="text-xs text-slate-400 mt-0.5">Fresh & Good</div>
</div>
</div>
<div class="bg-slate-800/40 rounded-xl p-3 border border-slate-800 flex items-center gap-3">
<div class="w-9 h-9 rounded-lg bg-amber-500/10 text-amber-400 flex items-center justify-center shrink-0">
<i data-lucide="clock" class="w-5 h-5"></i>
</div>
<div>
<div id="stat-expiring" class="text-lg font-bold text-amber-400 leading-none">0</div>
<div class="text-xs text-slate-400 mt-0.5">Expiring &le; 3 Days</div>
</div>
</div>
<div class="bg-slate-800/40 rounded-xl p-3 border border-slate-800 flex items-center gap-3">
<div class="w-9 h-9 rounded-lg bg-indigo-500/10 text-indigo-400 flex items-center justify-center shrink-0">
<i data-lucide="pie-chart" class="w-5 h-5"></i>
</div>
<div>
<div id="stat-opened" class="text-lg font-bold text-indigo-300 leading-none">0</div>
<div class="text-xs text-slate-400 mt-0.5">Opened / Partial</div>
</div>
</div>
</div>
</div>
</section>
<!-- SEARCH & FILTER CONTROLS -->
<section class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 pt-5 pb-3 w-full">
<div class="flex flex-col md:flex-row gap-3 items-stretch md:items-center justify-between">
<!-- Search Input -->
<div class="relative flex-1">
<i data-lucide="search" class="w-4 h-4 text-slate-400 absolute left-3.5 top-1/2 -translate-y-1/2"></i>
<input
type="text"
id="search-input"
placeholder="Search items, brands, barcodes, or notes..."
oninput="handleSearch(this.value)"
class="w-full bg-slate-900 border border-slate-800 focus:border-teal-500 focus:ring-1 focus:ring-teal-500 rounded-xl pl-10 pr-10 py-2.5 text-sm text-slate-100 placeholder-slate-500 transition outline-none"
>
<button id="clear-search" onclick="clearSearch()" class="hidden absolute right-3 top-1/2 -translate-y-1/2 text-slate-400 hover:text-white">
<i data-lucide="x" class="w-4 h-4"></i>
</button>
</div>
<!-- Quick Status Filters -->
<div class="flex items-center gap-1.5 overflow-x-auto pb-1 md:pb-0 scrollbar-none text-xs font-medium">
<button onclick="setStatusFilter('all')" data-status-btn="all" class="status-chip active px-3 py-2 rounded-lg bg-slate-800 border border-slate-700 text-white whitespace-nowrap transition">All</button>
<button onclick="setStatusFilter('fresh')" data-status-btn="fresh" class="status-chip px-3 py-2 rounded-lg bg-slate-900 border border-slate-800 text-slate-400 hover:text-slate-200 whitespace-nowrap transition">Fresh</button>
<button onclick="setStatusFilter('expiring_soon')" data-status-btn="expiring_soon" class="status-chip px-3 py-2 rounded-lg bg-slate-900 border border-slate-800 text-amber-400/80 hover:text-amber-300 whitespace-nowrap transition">Expiring Soon</button>
<button onclick="setStatusFilter('low')" data-status-btn="low" class="status-chip px-3 py-2 rounded-lg bg-slate-900 border border-slate-800 text-indigo-400/80 hover:text-indigo-300 whitespace-nowrap transition">Low &le; 25%</button>
<button onclick="setStatusFilter('expired')" data-status-btn="expired" class="status-chip px-3 py-2 rounded-lg bg-slate-900 border border-slate-800 text-rose-400/80 hover:text-rose-300 whitespace-nowrap transition">Expired</button>
</div>
</div>
<!-- Category Filter Bar -->
<div id="category-filter-bar" class="flex items-center gap-2 overflow-x-auto pt-3 pb-1 scrollbar-none text-xs font-medium">
<!-- Injected by JS -->
</div>
</section>
<!-- MAIN CONTENT AREA -->
<main class="flex-1 max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4 w-full">
<!-- MODE 1: FRIDGE VIEW CONTAINER -->
<div id="view-mode-container" class="space-y-6">
<!-- View controls: Group by Shelf or Group by Category or Card Grid -->
<div class="flex items-center justify-between pb-2 border-b border-slate-800 text-xs text-slate-400">
<div class="flex items-center gap-2">
<span class="font-medium text-slate-300" id="item-count-label">Showing all items</span>
</div>
<div class="flex items-center gap-2">
<span class="hidden sm:inline">Group by:</span>
<button onclick="setViewGrouping('shelf')" id="grp-shelf-btn" class="px-2.5 py-1 rounded bg-teal-500/20 text-teal-300 border border-teal-500/30 font-medium">Shelves / Location</button>
<button onclick="setViewGrouping('category')" id="grp-cat-btn" class="px-2.5 py-1 rounded bg-slate-800 text-slate-400 border border-slate-700 font-medium">Category</button>
<button onclick="setViewGrouping('grid')" id="grp-grid-btn" class="px-2.5 py-1 rounded bg-slate-800 text-slate-400 border border-slate-700 font-medium">Grid</button>
</div>
</div>
<!-- Fridge Visual Display Area -->
<div id="fridge-view-content" class="space-y-6">
<!-- Rendered items grouped by shelf/location or category -->
</div>
</div>
<!-- MODE 2: MANAGE INVENTORY CONTAINER -->
<div id="manage-mode-container" class="hidden space-y-6">
<!-- Top Action Ribbon for Management -->
<div class="bg-gradient-to-r from-slate-900 to-slate-850 p-4 rounded-2xl border border-slate-800 flex flex-col sm:flex-row items-center justify-between gap-4 shadow-lg">
<div class="flex items-center gap-3">
<div class="w-10 h-10 rounded-xl bg-teal-500/10 border border-teal-500/20 text-teal-400 flex items-center justify-center">
<i data-lucide="package-plus" class="w-5 h-5"></i>
</div>
<div>
<h3 class="font-semibold text-white text-base">Inventory Management</h3>
<p class="text-xs text-slate-400">Add products, adjust grams after cooking, and manage stock</p>
</div>
</div>
<div class="flex items-center gap-2 w-full sm:w-auto">
<button onclick="openScannerModal()" class="flex-1 sm:flex-initial flex items-center justify-center gap-2 px-4 py-2.5 rounded-xl bg-teal-500 hover:bg-teal-400 text-white text-sm font-semibold shadow-md shadow-teal-500/20 transition">
<i data-lucide="scan" class="w-4 h-4"></i>
<span>Scan Barcode</span>
</button>
<button onclick="openItemModal()" class="flex-1 sm:flex-initial flex items-center justify-center gap-2 px-4 py-2.5 rounded-xl bg-slate-800 hover:bg-slate-700 border border-slate-700 text-slate-200 text-sm font-semibold transition">
<i data-lucide="plus-circle" class="w-4 h-4"></i>
<span>Add Item</span>
</button>
</div>
</div>
<!-- Manage Items List -->
<div id="manage-items-list" class="space-y-3">
<!-- Dynamic list of management rows with quick grams adjustment sliders and +/- buttons -->
</div>
</div>
<!-- EMPTY STATE -->
<div id="empty-state" class="hidden text-center py-16 px-4">
<div class="w-16 h-16 rounded-2xl bg-slate-800 text-slate-400 flex items-center justify-center mx-auto mb-4 border border-slate-700">
<i data-lucide="refrigerator" class="w-8 h-8"></i>
</div>
<h3 class="text-lg font-bold text-white mb-1">No items found</h3>
<p class="text-sm text-slate-400 max-w-sm mx-auto mb-6">Your fridge is either empty or matches no search filters. Scan a barcode or add an item to get started!</p>
<div class="flex items-center justify-center gap-3">
<button onclick="openScannerModal()" class="px-4 py-2 rounded-xl bg-teal-500 hover:bg-teal-400 text-white text-sm font-medium flex items-center gap-2">
<i data-lucide="scan-barcode" class="w-4 h-4"></i>
<span>Scan Product</span>
</button>
<button onclick="seedDemoData()" class="px-4 py-2 rounded-xl bg-slate-800 hover:bg-slate-700 border border-slate-700 text-slate-300 text-sm font-medium">
Load Sample Fridge
</button>
</div>
</div>
</main>
<!-- QUICK GRAMS / QUANTITY ADJUSTMENT MODAL -->
<div id="grams-modal" class="fixed inset-0 z-50 bg-black/70 backdrop-blur-sm hidden flex items-center justify-center p-4">
<div class="bg-slate-900 border border-slate-800 rounded-2xl max-w-md w-full p-6 shadow-2xl space-y-5 animate-scale-up">
<div class="flex items-start justify-between">
<div>
<span class="text-xs uppercase tracking-wider font-bold text-teal-400">Adjust Amount</span>
<h3 id="grams-modal-title" class="text-lg font-bold text-white mt-0.5">Item Name</h3>
<p id="grams-modal-sub" class="text-xs text-slate-400">Initial: 500g &bull; Opened 2 days ago</p>
</div>
<button onclick="closeGramsModal()" class="text-slate-400 hover:text-white p-1">
<i data-lucide="x" class="w-5 h-5"></i>
</button>
</div>
<!-- Remaining Grams Gauge / Slider -->
<div class="bg-slate-950/60 p-4 rounded-xl border border-slate-800/80 space-y-4 text-center">
<div>
<div class="text-3xl font-black text-white flex items-center justify-center gap-1.5">
<span id="grams-current-val">250</span>
<span id="grams-modal-unit" class="text-lg font-normal text-teal-400">g</span>
</div>
<div id="grams-percent-text" class="text-xs text-slate-400 mt-1">50% remaining</div>
</div>
<input
type="range"
id="grams-slider"
min="0"
max="500"
step="5"
value="250"
oninput="onGramsSliderChange(this.value)"
class="w-full accent-teal-500 cursor-pointer h-2 bg-slate-800 rounded-lg"
>
<!-- Quick Decrement Buttons -->
<div class="flex items-center justify-center gap-2 pt-1 flex-wrap">
<button onclick="applyGramsDelta(-10)" class="px-2.5 py-1.5 rounded-lg bg-slate-800 hover:bg-slate-700 text-xs text-slate-200 border border-slate-700 font-medium">-10</button>
<button onclick="applyGramsDelta(-25)" class="px-2.5 py-1.5 rounded-lg bg-slate-800 hover:bg-slate-700 text-xs text-slate-200 border border-slate-700 font-medium">-25</button>
<button onclick="applyGramsDelta(-50)" class="px-2.5 py-1.5 rounded-lg bg-slate-800 hover:bg-slate-700 text-xs text-slate-200 border border-slate-700 font-medium">-50</button>
<button onclick="applyGramsDelta(-100)" class="px-2.5 py-1.5 rounded-lg bg-slate-800 hover:bg-slate-700 text-xs text-slate-200 border border-slate-700 font-medium">-100</button>
<button onclick="setGramsPercent(0.5)" class="px-2.5 py-1.5 rounded-lg bg-slate-800 hover:bg-slate-700 text-xs text-slate-200 border border-slate-700 font-medium">Half</button>
<button onclick="setGramsPercent(0)" class="px-2.5 py-1.5 rounded-lg bg-rose-950/40 hover:bg-rose-900/50 text-xs text-rose-300 border border-rose-800/40 font-medium">Empty (0)</button>
</div>
</div>
<!-- Direct Numeric Input Option -->
<div class="flex items-center gap-3">
<label class="text-xs text-slate-400 shrink-0">Exact remaining:</label>
<div class="relative flex-1">
<input
type="number"
id="grams-exact-input"
min="0"
step="any"
oninput="onGramsExactInput(this.value)"
class="w-full bg-slate-950 border border-slate-800 rounded-lg px-3 py-1.5 text-sm text-white focus:border-teal-500 outline-none"
>
</div>
</div>
<div class="flex items-center justify-between pt-2">
<button onclick="consumeAllAndRemove()" class="text-xs text-rose-400 hover:text-rose-300 flex items-center gap-1.5">
<i data-lucide="trash-2" class="w-3.5 h-3.5"></i>
<span>Consumed all / Remove</span>
</button>
<div class="flex items-center gap-2">
<button onclick="closeGramsModal()" class="px-4 py-2 rounded-xl bg-slate-800 hover:bg-slate-700 text-slate-300 text-sm font-medium">Cancel</button>
<button onclick="saveGramsAdjustment()" class="px-5 py-2 rounded-xl bg-teal-500 hover:bg-teal-400 text-white text-sm font-semibold shadow-md">Save Amount</button>
</div>
</div>
</div>
</div>
<!-- ADD / EDIT ITEM MODAL -->
<div id="item-modal" class="fixed inset-0 z-50 bg-black/70 backdrop-blur-sm hidden flex items-center justify-center p-4 overflow-y-auto">
<div class="bg-slate-900 border border-slate-800 rounded-2xl max-w-lg w-full p-6 shadow-2xl space-y-4 my-8 animate-scale-up">
<div class="flex items-start justify-between">
<div>
<h3 id="item-modal-title" class="text-lg font-bold text-white">Add New Fridge Item</h3>
<p class="text-xs text-slate-400">Scan barcode or type item details below</p>
</div>
<button onclick="closeItemModal()" class="text-slate-400 hover:text-white p-1">
<i data-lucide="x" class="w-5 h-5"></i>
</button>
</div>
<!-- Barcode Search & Scan quick strip -->
<div class="bg-slate-950/60 p-3 rounded-xl border border-slate-800/80 flex items-center gap-2">
<div class="relative flex-1">
<i data-lucide="barcode" class="w-4 h-4 text-slate-400 absolute left-3 top-1/2 -translate-y-1/2"></i>
<input
type="text"
id="modal-barcode"
placeholder="Barcode (e.g. 4008452023451)"
class="w-full bg-slate-900 border border-slate-800 focus:border-teal-500 rounded-lg pl-9 pr-3 py-1.5 text-xs text-white outline-none"
>
</div>
<button type="button" onclick="lookupCurrentBarcode()" id="modal-barcode-lookup-btn" class="px-3 py-1.5 rounded-lg bg-teal-600 hover:bg-teal-500 text-white text-xs font-semibold shrink-0">
Lookup
</button>
<button type="button" onclick="openScannerModal(true)" class="p-1.5 rounded-lg bg-slate-800 hover:bg-slate-700 text-slate-300 border border-slate-700 shrink-0" title="Scan with Camera">
<i data-lucide="camera" class="w-4 h-4"></i>
</button>
</div>
<form id="item-form" onsubmit="handleItemFormSubmit(event)" class="space-y-3.5 text-sm">
<input type="hidden" id="modal-item-id">
<input type="hidden" id="modal-image-url">
<!-- Product Image Preview if found -->
<div id="modal-image-container" class="hidden items-center gap-3 p-2 bg-slate-950 rounded-xl border border-slate-800">
<img id="modal-image-preview" src="" alt="Product" class="w-12 h-12 object-contain rounded-lg bg-white/5">
<div class="flex-1 min-w-0">
<span class="text-xs text-teal-400 font-medium">Product photo matched</span>
<p id="modal-image-label" class="text-xs text-slate-400 truncate"></p>
</div>
</div>
<!-- Name & Brand -->
<div class="grid grid-cols-1 sm:grid-cols-3 gap-3">
<div class="sm:col-span-2">
<label class="block text-xs text-slate-300 font-medium mb-1">Item Name *</label>
<input type="text" id="modal-name" required placeholder="e.g. Gouda Cheese" class="w-full bg-slate-950 border border-slate-800 focus:border-teal-500 rounded-xl px-3 py-2 text-white outline-none">
</div>
<div>
<label class="block text-xs text-slate-300 font-medium mb-1">Brand</label>
<input type="text" id="modal-brand" placeholder="e.g. Milbona" class="w-full bg-slate-950 border border-slate-800 focus:border-teal-500 rounded-xl px-3 py-2 text-white outline-none">
</div>
</div>
<!-- Quantity & Unit -->
<div class="grid grid-cols-3 gap-3">
<div>
<label class="block text-xs text-slate-300 font-medium mb-1">Current Amount *</label>
<input type="number" id="modal-quantity" step="any" min="0" required placeholder="250" class="w-full bg-slate-950 border border-slate-800 focus:border-teal-500 rounded-xl px-3 py-2 text-white outline-none">
</div>
<div>
<label class="block text-xs text-slate-300 font-medium mb-1">Package Total</label>
<input type="number" id="modal-initial-quantity" step="any" min="0" placeholder="250" class="w-full bg-slate-950 border border-slate-800 focus:border-teal-500 rounded-xl px-3 py-2 text-white outline-none">
</div>
<div>
<label class="block text-xs text-slate-300 font-medium mb-1">Unit</label>
<select id="modal-unit" class="w-full bg-slate-950 border border-slate-800 focus:border-teal-500 rounded-xl px-3 py-2 text-white outline-none">
<option value="g">Grams (g)</option>
<option value="ml">Milliliters (ml)</option>
<option value="pcs">Pieces (Stk)</option>
<option value="kg">Kilograms (kg)</option>
<option value="l">Liters (L)</option>
<option value="pack">Pack / Packung</option>
<option value="slices">Slices</option>
</select>
</div>
</div>
<!-- Category & Location -->
<div class="grid grid-cols-1 sm:grid-cols-2 gap-3">
<div>
<label class="block text-xs text-slate-300 font-medium mb-1">Category</label>
<select id="modal-category" class="w-full bg-slate-950 border border-slate-800 focus:border-teal-500 rounded-xl px-3 py-2 text-white outline-none">
<!-- Options populated by meta -->
</select>
</div>
<div>
<label class="block text-xs text-slate-300 font-medium mb-1">Location in Fridge</label>
<select id="modal-location" class="w-full bg-slate-950 border border-slate-800 focus:border-teal-500 rounded-xl px-3 py-2 text-white outline-none">
<!-- Options populated by meta -->
</select>
</div>
</div>
<!-- Expiration Date & Helpers -->
<div>
<div class="flex items-center justify-between mb-1">
<label class="block text-xs text-slate-300 font-medium">Expiration Date (MHD)</label>
<div class="flex items-center gap-1.5 text-[11px]">
<button type="button" onclick="setModalExpiryDays(3)" class="text-slate-400 hover:text-teal-400">+3d</button>
<span>&bull;</span>
<button type="button" onclick="setModalExpiryDays(7)" class="text-slate-400 hover:text-teal-400">+1w</button>
<span>&bull;</span>
<button type="button" onclick="setModalExpiryDays(14)" class="text-slate-400 hover:text-teal-400">+2w</button>
<span>&bull;</span>
<button type="button" onclick="setModalExpiryDays(30)" class="text-slate-400 hover:text-teal-400">+1m</button>
</div>
</div>
<input type="date" id="modal-expiry" class="w-full bg-slate-950 border border-slate-800 focus:border-teal-500 rounded-xl px-3 py-2 text-white outline-none">
</div>
<!-- Opened Status & Notes -->
<div class="flex items-center gap-2 pt-1">
<input type="checkbox" id="modal-opened" class="w-4 h-4 accent-teal-500 rounded bg-slate-950 border-slate-800">
<label for="modal-opened" class="text-xs text-slate-300 cursor-pointer">Already opened pack / container</label>
</div>
<div>
<label class="block text-xs text-slate-300 font-medium mb-1">Notes (Optional)</label>
<input type="text" id="modal-notes" placeholder="e.g. Left half for pasta tonight" class="w-full bg-slate-950 border border-slate-800 focus:border-teal-500 rounded-xl px-3 py-2 text-white outline-none">
</div>
<div class="flex items-center justify-end gap-3 pt-3 border-t border-slate-800">
<button type="button" onclick="closeItemModal()" class="px-4 py-2.5 rounded-xl bg-slate-800 hover:bg-slate-700 text-slate-300 text-sm font-medium">Cancel</button>
<button type="submit" id="modal-save-btn" class="px-6 py-2.5 rounded-xl bg-teal-500 hover:bg-teal-400 text-white text-sm font-semibold shadow-lg shadow-teal-500/20">Save Item</button>
</div>
</form>
</div>
</div>
<!-- LIVE CAMERA BARCODE SCANNER MODAL -->
<div id="scanner-modal" class="fixed inset-0 z-50 bg-black/85 backdrop-blur-md hidden flex items-center justify-center p-4">
<div class="bg-slate-900 border border-slate-800 rounded-3xl max-w-md w-full overflow-hidden shadow-2xl flex flex-col animate-scale-up">
<div class="p-4 bg-slate-850 border-b border-slate-800 flex items-center justify-between">
<div class="flex items-center gap-2">
<div class="w-8 h-8 rounded-lg bg-teal-500/10 text-teal-400 flex items-center justify-center">
<i data-lucide="scan-barcode" class="w-4 h-4"></i>
</div>
<span class="font-bold text-white text-sm">Scan Product Barcode</span>
</div>
<button onclick="closeScannerModal()" class="text-slate-400 hover:text-white p-1">
<i data-lucide="x" class="w-5 h-5"></i>
</button>
</div>
<!-- Camera Preview Box -->
<div class="relative bg-black flex items-center justify-center min-h-[300px] w-full overflow-hidden">
<div id="barcode-reader" class="w-full"></div>
<div id="scanner-overlay" class="absolute inset-0 pointer-events-none border-2 border-teal-400/40 rounded-2xl m-8 flex items-center justify-center">
<div class="w-full h-0.5 bg-red-500/80 animate-pulse shadow-lg shadow-red-500"></div>
</div>
</div>
<!-- Scanner Status / Manual Fallback -->
<div class="p-4 bg-slate-900 space-y-3">
<div id="scanner-status" class="text-xs text-center text-slate-400">
Point camera at standard EAN / UPC product barcode
</div>
<div class="flex items-center gap-2">
<input
type="text"
id="scanner-manual-input"
placeholder="Or type barcode number manually..."
class="flex-1 bg-slate-950 border border-slate-800 focus:border-teal-500 rounded-xl px-3 py-2 text-xs text-white outline-none"
onkeydown="if(event.key === 'Enter') handleManualBarcodeSubmit()"
>
<button onclick="handleManualBarcodeSubmit()" class="px-3 py-2 rounded-xl bg-teal-600 hover:bg-teal-500 text-white text-xs font-semibold">
Lookup
</button>
</div>
</div>
</div>
</div>
<!-- TOAST NOTIFICATION CONTAINER -->
<div id="toast-container" class="fixed bottom-5 right-5 z-50 flex flex-col gap-2 max-w-sm pointer-events-none"></div>
<!-- MAIN SCRIPT -->
<script src="/app.js"></script>
</body>
</html>
+82
View File
@@ -0,0 +1,82 @@
/* Custom styling & animations for Fridger */
@keyframes scaleUp {
from {
opacity: 0;
transform: scale(0.96);
}
to {
opacity: 1;
transform: scale(1);
}
}
.animate-scale-up {
animation: scaleUp 0.18s cubic-bezier(0.16, 1, 0.3, 1) forwards;
}
/* Glass Shelf Card effect */
.shelf-card {
background: linear-gradient(135deg, rgba(30, 41, 59, 0.7) 0%, rgba(15, 23, 42, 0.8) 100%);
backdrop-filter: blur(12px);
border: 1px solid rgba(51, 65, 85, 0.6);
box-shadow: 0 4px 20px -2px rgba(0, 0, 0, 0.4);
}
.shelf-card:hover {
border-color: rgba(45, 212, 191, 0.4);
}
/* Custom Scrollbar */
::-webkit-scrollbar {
width: 6px;
height: 6px;
}
::-webkit-scrollbar-track {
background: rgba(15, 23, 42, 0.5);
}
::-webkit-scrollbar-thumb {
background: rgba(51, 65, 85, 0.6);
border-radius: 9999px;
}
::-webkit-scrollbar-thumb:hover {
background: rgba(20, 184, 166, 0.6);
}
/* Hide scrollbar for category pills */
.scrollbar-none::-webkit-scrollbar {
display: none;
}
.scrollbar-none {
-ms-overflow-style: none;
scrollbar-width: none;
}
/* HTML5 Barcode Reader overlay styling */
#barcode-reader video {
width: 100% !important;
height: 100% !important;
object-fit: cover !important;
border-radius: 0.5rem;
}
#barcode-reader {
border: none !important;
}
#barcode-reader__scan_region {
background: transparent !important;
}
#barcode-reader__dashboard_section_csr button {
background-color: #0d9488 !important;
color: white !important;
border-radius: 0.5rem !important;
padding: 0.35rem 0.75rem !important;
border: none !important;
font-size: 0.75rem !important;
margin: 0.25rem !important;
}