← back to Watches
public/pwa-index.html
437 lines
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0, user-scalable=yes">
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="apple-mobile-web-app-title" content="Omega Watches">
<meta name="theme-color" content="#C41E3A">
<meta name="description" content="Track historical prices and analytics for Omega luxury timepieces. AR try-on, camera scanner, and offline support.">
<title>Omega Watch Price History - PWA</title>
<!-- Manifest -->
<link rel="manifest" href="/manifest.json">
<!-- Icons -->
<link rel="icon" type="image/svg+xml" href="/icons/icon-72x72.svg">
<link rel="apple-touch-icon" href="/icons/icon-192x192.svg">
<!-- Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<!-- Styles -->
<link rel="stylesheet" href="pwa-styles.css">
<style>
/* Critical CSS */
body {
margin: 0;
padding: 0;
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
background: #0C1E3C;
color: #fff;
overflow-x: hidden;
}
.loading-screen {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(135deg, #0C1E3C 0%, #1a2f50 100%);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
z-index: 9999;
}
.loading-logo {
font-size: 72px;
margin-bottom: 20px;
animation: pulse 2s infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; transform: scale(1); }
50% { opacity: 0.7; transform: scale(1.05); }
}
.loading-text {
font-size: 18px;
color: #C41E3A;
font-weight: 600;
}
</style>
</head>
<body>
<!-- Loading Screen -->
<div class="loading-screen" id="loadingScreen">
<div class="loading-logo">Ω</div>
<div class="loading-text">Loading Omega Watches...</div>
</div>
<!-- Update Banner -->
<div id="update-banner" class="update-banner" style="display: none;">
<div class="update-content">
<span>A new version is available!</span>
<button id="update-button" class="btn-update">Update Now</button>
</div>
</div>
<!-- Install Banner -->
<div id="install-banner" class="install-banner" style="display: none;">
<div class="install-content">
<div class="install-icon">Ω</div>
<div class="install-text">
<strong>Install Omega Watches App</strong>
<p>Get the full experience with offline access and AR try-on</p>
</div>
<button id="install-button" class="btn-install">Install</button>
<button id="install-dismiss" class="btn-dismiss">×</button>
</div>
</div>
<!-- Offline Indicator -->
<div id="offline-indicator" class="offline-indicator" style="display: none;">
<svg width="16" height="16" fill="currentColor">
<circle cx="8" cy="8" r="7" stroke="currentColor" fill="none" stroke-width="2"/>
<line x1="4" y1="8" x2="12" y2="8" stroke="currentColor" stroke-width="2"/>
</svg>
<span>Offline Mode</span>
</div>
<!-- Main App Container -->
<div id="app" class="app-container">
<!-- Navigation -->
<nav class="main-nav">
<div class="nav-brand">
<span class="nav-logo">Ω</span>
<span class="nav-title">Omega Watches</span>
</div>
<div class="nav-actions">
<button id="biometric-login" class="btn-icon" style="display: none;" title="Biometric Login">
<svg width="24" height="24" fill="currentColor">
<path d="M12 1L3 5v6c0 5.55 3.84 10.74 9 12 5.16-1.26 9-6.45 9-12V5l-9-4zm0 6c1.1 0 2 .9 2 2s-.9 2-2 2-2-.9-2-2 .9-2 2-2zm0 14c-2.67 0-8-1.34-8-4v-1c0-2.66 5.33-4 8-4s8 1.34 8 4v1c0 2.66-5.33 4-8 4z"/>
</svg>
</button>
<button id="notification-toggle" class="btn-icon" title="Enable Notifications">
<svg width="24" height="24" fill="currentColor">
<path d="M12 22c1.1 0 2-.9 2-2h-4c0 1.1.9 2 2 2zm6-6v-5c0-3.07-1.63-5.64-4.5-6.32V4c0-.83-.67-1.5-1.5-1.5s-1.5.67-1.5 1.5v.68C7.64 5.36 6 7.92 6 11v5l-2 2v1h16v-1l-2-2zm-2 1H8v-6c0-2.48 1.51-4.5 4-4.5s4 2.02 4 4.5v6z"/>
</svg>
</button>
<button id="menu-toggle" class="btn-icon" title="Menu">
<svg width="24" height="24" fill="currentColor">
<path d="M3 18h18v-2H3v2zm0-5h18v-2H3v2zm0-7v2h18V6H3z"/>
</svg>
</button>
</div>
</nav>
<!-- Bottom Navigation -->
<div class="bottom-nav">
<button class="nav-item active" data-view="dashboard">
<svg width="24" height="24" fill="currentColor">
<path d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z"/>
</svg>
<span>Home</span>
</button>
<button class="nav-item" data-view="scanner">
<svg width="24" height="24" fill="currentColor">
<path d="M9.5 6.5v3h-3v-3h3M11 5H5v6h6V5zm-1.5 9.5v3h-3v-3h3M11 13H5v6h6v-6zm6.5-6.5v3h-3v-3h3M19 5h-6v6h6V5zm-6 8h1.5v1.5H13V13zm1.5 1.5H16V16h-1.5v-1.5zM16 13h1.5v1.5H16V13zm-3 3h1.5v1.5H13V16zm1.5 1.5H16V19h-1.5v-1.5zM16 16h1.5v1.5H16V16zm1.5-1.5H19V16h-1.5v-1.5zm0 3H19V19h-1.5v-1.5zM22 7h-2V4h-3V2h5v5zm0 15v-5h-2v3h-3v2h5zM2 22h5v-2H4v-3H2v5zM2 2v5h2V4h3V2H2z"/>
</svg>
<span>Scan</span>
</button>
<button class="nav-item" data-view="ar">
<svg width="24" height="24" fill="currentColor">
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"/>
</svg>
<span>AR Try-On</span>
</button>
<button class="nav-item" data-view="favorites">
<svg width="24" height="24" fill="currentColor">
<path d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z"/>
</svg>
<span>Favorites</span>
</button>
</div>
<!-- Main Content Views -->
<main class="main-content">
<!-- Dashboard View -->
<div id="dashboard-view" class="view active">
<div class="container">
<header class="page-header">
<h1>Omega Watch Price History</h1>
<p class="subtitle">Complete Historical Price Tracking</p>
</header>
<div class="stats-grid" id="statsGrid">
<div class="stat-card">
<div class="stat-value" id="totalModels">--</div>
<div class="stat-label">Total Models</div>
</div>
<div class="stat-card">
<div class="stat-value" id="avgPrice">--</div>
<div class="stat-label">Avg. Price</div>
</div>
<div class="stat-card">
<div class="stat-value" id="topAppreciation">--</div>
<div class="stat-label">Top Appreciation</div>
</div>
<div class="stat-card">
<div class="stat-value" id="dataPoints">--</div>
<div class="stat-label">Data Points</div>
</div>
</div>
<div class="watches-list" id="watchesList">
<!-- Watches loaded dynamically -->
</div>
</div>
</div>
<!-- Scanner View -->
<div id="scanner-view" class="view">
<div class="scanner-container">
<div class="scanner-header">
<h2>Scan Watch Reference</h2>
<button id="scanner-close" class="btn-close">×</button>
</div>
<div class="scanner-viewport">
<video id="scanner-video" autoplay playsinline></video>
<canvas id="scanner-canvas" style="display: none;"></canvas>
<div class="scanner-overlay">
<div class="scan-frame"></div>
<p class="scan-instructions">Position watch reference number in the frame</p>
</div>
</div>
<div class="scanner-controls">
<button id="scanner-capture" class="btn-primary">
<svg width="24" height="24" fill="currentColor">
<circle cx="12" cy="12" r="8" stroke="currentColor" fill="none" stroke-width="2"/>
<circle cx="12" cy="12" r="5" fill="currentColor"/>
</svg>
Capture
</button>
<button id="scanner-switch" class="btn-secondary">
<svg width="24" height="24" fill="currentColor">
<path d="M20 4h-3.17L15 2H9L7.17 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zM9 4l.83 1h4.34l.83-1h2v2H7V4h2z"/>
</svg>
Flip
</button>
<button id="scanner-torch" class="btn-secondary">
<svg width="24" height="24" fill="currentColor">
<path d="M6 14l3 3v5h6v-5l3-3V9H6v5zm5-12h2v3h-2V2zM3.5 5.88l1.41-1.41 2.12 2.12L5.62 8 3.5 5.88zm13.46.71l2.12-2.12 1.41 1.41L18.38 8l-1.42-1.41z"/>
</svg>
</button>
</div>
<div class="scan-history" id="scanHistory">
<h3>Recent Scans</h3>
<div id="scanHistoryList"></div>
</div>
</div>
</div>
<!-- AR View -->
<div id="ar-view" class="view">
<div class="ar-container">
<div class="ar-header">
<h2>AR Try-On</h2>
<button id="ar-close" class="btn-close">×</button>
</div>
<div class="ar-viewport">
<video id="ar-video" autoplay playsinline></video>
<canvas id="ar-canvas"></canvas>
<div class="ar-controls-overlay">
<button id="ar-screenshot" class="btn-icon ar-btn">
<svg width="24" height="24" fill="currentColor">
<circle cx="12" cy="12" r="3.2"/>
<path d="M9 2L7.17 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2h-3.17L15 2H9zm3 15c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5z"/>
</svg>
</button>
</div>
</div>
<div class="ar-watch-selector">
<h3>Select Watch Model</h3>
<div id="arWatchList" class="ar-watch-grid">
<!-- Populated dynamically -->
</div>
</div>
</div>
</div>
<!-- Favorites View -->
<div id="favorites-view" class="view">
<div class="container">
<header class="page-header">
<h1>Favorite Watches</h1>
<p class="subtitle">Your saved collection</p>
</header>
<div id="favoritesList" class="watches-list">
<div class="empty-state">
<svg width="64" height="64" fill="currentColor" opacity="0.3">
<path d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z"/>
</svg>
<p>No favorites yet</p>
<p class="empty-state-hint">Tap the heart icon on any watch to add it here</p>
</div>
</div>
</div>
</div>
</main>
</div>
<!-- PWA Scripts -->
<script src="biometric-auth.js"></script>
<script src="camera-scanner.js"></script>
<script src="ar-viewer.js"></script>
<script src="pwa-app.js"></script>
<script src="app.js"></script>
<script>
// Hide loading screen when app is ready
window.addEventListener('load', () => {
setTimeout(() => {
document.getElementById('loadingScreen').style.display = 'none';
}, 1000);
});
// Handle navigation
document.querySelectorAll('.nav-item').forEach(btn => {
btn.addEventListener('click', () => {
const view = btn.dataset.view;
// Update active nav
document.querySelectorAll('.nav-item').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
// Show view
document.querySelectorAll('.view').forEach(v => v.classList.remove('active'));
document.getElementById(`${view}-view`).classList.add('active');
// Initialize view-specific functionality
if (view === 'scanner') {
initScanner();
} else if (view === 'ar') {
initAR();
}
});
});
// Scanner initialization
async function initScanner() {
try {
const scanner = await window.pwaApp.initScanner();
await scanner.start();
document.getElementById('scanner-capture').addEventListener('click', async () => {
const imageData = scanner.captureFrame();
// Process image...
});
document.getElementById('scanner-switch').addEventListener('click', () => {
scanner.switchCamera();
});
document.getElementById('scanner-torch').addEventListener('click', () => {
scanner.toggleTorch();
});
} catch (error) {
console.error('Scanner init failed:', error);
alert('Camera not available: ' + error.message);
}
}
// AR initialization
async function initAR() {
try {
const ar = await window.pwaApp.initAR();
// Load watch list for AR
const response = await fetch('/api/watches?limit=10');
const data = await response.json();
const arWatchList = document.getElementById('arWatchList');
arWatchList.innerHTML = data.watches.map(watch => `
<div class="ar-watch-card" data-id="${watch.id}">
<img src="/images/watches/${watch.id}.jpg" alt="${watch.model}"
onerror="this.src='data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 width=%22100%22 height=%22100%22><rect fill=%22%23333%22 width=%22100%22 height=%22100%22/></svg>'">
<p>${watch.model}</p>
</div>
`).join('');
// Handle watch selection
document.querySelectorAll('.ar-watch-card').forEach(card => {
card.addEventListener('click', async () => {
await ar.start(card.dataset.id);
});
});
// Screenshot button
document.getElementById('ar-screenshot').addEventListener('click', async () => {
const imageData = await ar.takeScreenshot();
if (imageData) {
alert('Screenshot saved!');
}
});
} catch (error) {
console.error('AR init failed:', error);
alert('AR not available: ' + error.message);
}
}
// Notification toggle
document.getElementById('notification-toggle').addEventListener('click', async () => {
const granted = await window.pwaApp.requestNotificationPermission();
if (granted) {
alert('Notifications enabled! You will receive price alerts.');
}
});
// Install button
document.getElementById('install-dismiss').addEventListener('click', () => {
document.getElementById('install-banner').style.display = 'none';
});
// Show install banner after a few seconds
setTimeout(() => {
if (window.pwaApp && window.pwaApp.installPrompt) {
document.getElementById('install-banner').style.display = 'block';
}
}, 5000);
// Offline indicator
window.addEventListener('offline', () => {
document.getElementById('offline-indicator').style.display = 'flex';
});
window.addEventListener('online', () => {
document.getElementById('offline-indicator').style.display = 'none';
});
</script>
</body>
</html>