Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Barcode Ninja Scanner</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <script src="https://unpkg.com/feather-icons"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script> | |
| <style> | |
| .scan-animation { | |
| animation: pulse 2s infinite; | |
| } | |
| @keyframes pulse { | |
| 0% { box-shadow: 0 0 0 0 rgba(59, 130, 246, 0.4); } | |
| 70% { box-shadow: 0 0 0 10px rgba(59, 130, 246, 0); } | |
| 100% { box-shadow: 0 0 0 0 rgba(59, 130, 246, 0); } | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-gray-100 min-h-screen"> | |
| <div class="container mx-auto px-4 py-8 max-w-md"> | |
| <!-- Header --> | |
| <header class="mb-8 text-center"> | |
| <h1 class="text-3xl font-bold text-indigo-600 mb-2">Barcode Ninja</h1> | |
| <p class="text-gray-600">Scan, track, and manage barcodes efficiently</p> | |
| <div class="flex space-x-2 mt-2"> | |
| <a href="scan-app.html" class="text-blue-500 hover:text-blue-700 inline-block">UniScan X App</a> | |
| <span class="text-gray-400">|</span> | |
| <a href="scan.html" class="text-blue-500 hover:text-blue-700 inline-block">Classic Scanner</a> | |
| <span class="text-gray-400">|</span> | |
| <a href="history.html" class="text-blue-500 hover:text-blue-700 inline-block">Scan History</a> | |
| </div> | |
| </header> | |
| <!-- Scanner Section --> | |
| <div class="bg-white rounded-xl shadow-md p-6 mb-6"> | |
| <div class="flex justify-between items-center mb-4"> | |
| <h2 class="text-xl font-semibold text-gray-800">Barcode Scanner</h2> | |
| <button id="scanBtn" class="bg-indigo-600 hover:bg-indigo-700 text-white px-4 py-2 rounded-lg flex items-center transition"> | |
| <i data-feather="maximize" class="mr-2"></i> Scan Now | |
| </button> | |
| </div> | |
| <div class="text-center"> | |
| <div id="scannerPlaceholder" class="mx-auto w-full h-48 bg-gray-200 rounded-lg flex items-center justify-center scan-animation"> | |
| <div class="text-center"> | |
| <i data-feather="camera" class="w-12 h-12 mx-auto text-gray-400 mb-2"></i> | |
| <p class="text-gray-500">Ready to scan</p> | |
| </div> | |
| </div> | |
| <div id="lastScanned" class="mt-4 p-3 bg-indigo-50 rounded-lg hidden"> | |
| <p class="text-sm text-gray-600 mb-1">Last scanned:</p> | |
| <p id="lastScannedCode" class="font-mono text-lg font-bold text-indigo-700"></p> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Watchlist Section --> | |
| <div class="bg-white rounded-xl shadow-md p-6 mb-6"> | |
| <h2 class="text-xl font-semibold text-gray-800 mb-4">Special Watchlist</h2> | |
| <textarea id="watchlistInput" rows="3" class="w-full p-3 border border-gray-300 rounded-lg mb-3 focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500" placeholder="Enter barcodes to watch (one per line)"></textarea> | |
| <p class="text-sm text-gray-500 mb-4">Barcodes in this list will trigger special notifications when scanned.</p> | |
| </div> | |
| <!-- History Section --> | |
| <div class="bg-white rounded-xl shadow-md p-6"> | |
| <div class="flex justify-between items-center mb-4"> | |
| <h2 class="text-xl font-semibold text-gray-800">Recent Scans</h2> | |
| <button id="clearHistoryBtn" class="text-sm text-indigo-600 hover:text-indigo-800">Clear All</button> | |
| </div> | |
| <div id="scanHistory" class="space-y-2"> | |
| <p class="text-gray-500 text-center py-4">No scans yet</p> | |
| </div> | |
| </div> | |
| <!-- Toast Notification --> | |
| <div id="toast" class="fixed bottom-4 right-4 bg-gray-800 text-white px-4 py-2 rounded-lg shadow-lg transform translate-y-16 transition-transform duration-300 opacity-0 hidden"> | |
| <div class="flex items-center"> | |
| <span id="toastMessage"></span> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| // App State | |
| const state = { | |
| scanHistory: [], | |
| watchlist: [], | |
| maxHistorySize: 10 | |
| }; | |
| // DOM Elements | |
| const elements = { | |
| scanBtn: document.getElementById('scanBtn'), | |
| scannerPlaceholder: document.getElementById('scannerPlaceholder'), | |
| lastScanned: document.getElementById('lastScanned'), | |
| lastScannedCode: document.getElementById('lastScannedCode'), | |
| watchlistInput: document.getElementById('watchlistInput'), | |
| scanHistory: document.getElementById('scanHistory'), | |
| clearHistoryBtn: document.getElementById('clearHistoryBtn'), | |
| toast: document.getElementById('toast'), | |
| toastMessage: document.getElementById('toastMessage') | |
| }; | |
| // Initialize Feather Icons | |
| feather.replace(); | |
| // Event Listeners | |
| elements.scanBtn.addEventListener('click', initiateScan); | |
| elements.clearHistoryBtn.addEventListener('click', clearHistory); | |
| elements.watchlistInput.addEventListener('change', updateWatchlist); | |
| // Functions | |
| function initiateScan() { | |
| // In a real app, this would trigger the device camera | |
| // For demo purposes, we'll simulate a scan | |
| simulateScan(); | |
| } | |
| function simulateScan() { | |
| // Generate a random barcode for demo | |
| const barcode = 'BC' + Math.floor(100000 + Math.random() * 900000); | |
| handleScannedBarcode(barcode); | |
| } | |
| function handleScannedBarcode(barcode) { | |
| // Check if already scanned | |
| if (state.scanHistory.includes(barcode)) { | |
| showToast('This barcode has already been scanned!', 'warning'); | |
| return; | |
| } | |
| // Check if in watchlist | |
| if (state.watchlist.includes(barcode)) { | |
| showToast('⚠️ Special barcode detected!', 'warning'); | |
| } | |
| // Add to history | |
| addToHistory(barcode); | |
| // Send to server | |
| sendToServer(barcode); | |
| } | |
| function addToHistory(barcode) { | |
| // Maintain max history size | |
| if (state.scanHistory.length >= state.maxHistorySize) { | |
| state.scanHistory.pop(); | |
| } | |
| state.scanHistory.unshift(barcode); | |
| updateHistoryDisplay(); | |
| } | |
| function updateHistoryDisplay() { | |
| if (state.scanHistory.length === 0) { | |
| elements.scanHistory.innerHTML = '<p class="text-gray-500 text-center py-4">No scans yet</p>'; | |
| elements.lastScanned.classList.add('hidden'); | |
| return; | |
| } | |
| // Update last scanned display | |
| elements.lastScannedCode.textContent = state.scanHistory[0]; | |
| elements.lastScanned.classList.remove('hidden'); | |
| // Update history list | |
| elements.scanHistory.innerHTML = state.scanHistory.map(barcode => ` | |
| <div class="flex justify-between items-center p-2 bg-gray-50 rounded-lg"> | |
| <span class="font-mono">${barcode}</span> | |
| <span class="text-xs text-gray-500">Just now</span> | |
| </div> | |
| `).join(''); | |
| } | |
| function clearHistory() { | |
| state.scanHistory = []; | |
| updateHistoryDisplay(); | |
| showToast('Scan history cleared', 'info'); | |
| } | |
| function updateWatchlist() { | |
| const text = elements.watchlistInput.value; | |
| state.watchlist = text.split('\n').filter(barcode => barcode.trim() !== ''); | |
| showToast('Watchlist updated', 'success'); | |
| } | |
| function sendToServer(barcode) { | |
| // In a real app, this would be a POST request | |
| console.log('Sending to server:', barcode); | |
| // fetch('/api/scan', { | |
| // method: 'POST', | |
| // headers: { 'Content-Type': 'application/json' }, | |
| // body: JSON.stringify({ barcode }) | |
| // }); | |
| showToast(`Barcode "${barcode}" sent to server`, 'success'); | |
| } | |
| function showToast(message, type = 'info') { | |
| elements.toastMessage.textContent = message; | |
| // Set color based on type | |
| const colors = { | |
| info: 'bg-gray-800', | |
| success: 'bg-green-600', | |
| warning: 'bg-yellow-600', | |
| error: 'bg-red-600' | |
| }; | |
| elements.toast.className = `fixed bottom-4 right-4 text-white px-4 py-2 rounded-lg shadow-lg transform translate-y-16 transition-transform duration-300 ${colors[type]}`; | |
| // Show toast | |
| elements.toast.classList.remove('hidden'); | |
| elements.toast.style.opacity = '1'; | |
| elements.toast.style.transform = 'translateY(0)'; | |
| // Hide after delay | |
| setTimeout(() => { | |
| elements.toast.style.opacity = '0'; | |
| elements.toast.style.transform = 'translateY(16px)'; | |
| setTimeout(() => elements.toast.classList.add('hidden'), 300); | |
| }, 3000); | |
| } | |
| // For demo: simulate scanning by pressing 'S' key | |
| document.addEventListener('keydown', (e) => { | |
| if (e.key === 's') { | |
| simulateScan(); | |
| } | |
| }); | |
| </script> | |
| </body> | |
| </html> | |