← back to Handbag Authentication
public/mobile.html
737 lines
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<title>Handbag Data - Mobile</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
-webkit-tap-highlight-color: transparent;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
background: #f5f5f7;
color: #1d1d1f;
overflow-x: hidden;
}
/* Header */
.mobile-header {
position: fixed;
top: 0;
left: 0;
right: 0;
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
z-index: 1000;
padding: env(safe-area-inset-top) 16px 12px;
}
.header-content {
display: flex;
align-items: center;
justify-content: space-between;
}
.logo {
font-size: 20px;
font-weight: 700;
color: #1d1d1f;
}
.menu-btn {
background: none;
border: none;
font-size: 24px;
cursor: pointer;
padding: 8px;
}
/* Search Bar */
.search-container {
position: fixed;
top: calc(env(safe-area-inset-top) + 52px);
left: 0;
right: 0;
padding: 12px 16px;
background: #f5f5f7;
z-index: 999;
}
.search-bar {
display: flex;
align-items: center;
background: white;
border-radius: 12px;
padding: 10px 16px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.search-bar input {
flex: 1;
border: none;
outline: none;
font-size: 16px;
background: transparent;
}
.search-icon {
margin-right: 8px;
opacity: 0.6;
}
/* Filter Pills */
.filter-pills {
position: fixed;
top: calc(env(safe-area-inset-top) + 116px);
left: 0;
right: 0;
padding: 8px 16px;
background: #f5f5f7;
overflow-x: auto;
white-space: nowrap;
z-index: 998;
-webkit-overflow-scrolling: touch;
}
.filter-pills::-webkit-scrollbar {
display: none;
}
.pill {
display: inline-block;
padding: 8px 16px;
background: white;
border-radius: 20px;
margin-right: 8px;
font-size: 14px;
border: 1px solid #e0e0e0;
cursor: pointer;
transition: all 0.2s;
}
.pill.active {
background: #1d1d1f;
color: white;
border-color: #1d1d1f;
}
/* Main Content */
.main-content {
margin-top: calc(env(safe-area-inset-top) + 180px);
padding: 16px;
padding-bottom: calc(env(safe-area-inset-bottom) + 80px);
}
/* Stats Cards */
.stats-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
margin-bottom: 24px;
}
.stat-card {
background: white;
border-radius: 16px;
padding: 16px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
}
.stat-value {
font-size: 28px;
font-weight: 700;
color: #1d1d1f;
margin-bottom: 4px;
}
.stat-label {
font-size: 12px;
color: #86868b;
text-transform: uppercase;
letter-spacing: 0.5px;
}
/* Product Card */
.product-card {
background: white;
border-radius: 16px;
overflow: hidden;
margin-bottom: 16px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
}
.product-image {
width: 100%;
height: 240px;
object-fit: cover;
background: #f0f0f0;
}
.product-info {
padding: 16px;
}
.product-brand {
font-size: 12px;
color: #86868b;
text-transform: uppercase;
letter-spacing: 0.5px;
margin-bottom: 4px;
}
.product-name {
font-size: 16px;
font-weight: 600;
color: #1d1d1f;
margin-bottom: 8px;
line-height: 1.3;
}
.product-price {
font-size: 18px;
font-weight: 700;
color: #1d1d1f;
margin-bottom: 12px;
}
.product-tags {
display: flex;
flex-wrap: wrap;
gap: 6px;
}
.tag {
padding: 4px 10px;
background: #f5f5f7;
border-radius: 12px;
font-size: 11px;
color: #1d1d1f;
}
/* 360 Viewer Badge */
.badge-360 {
position: absolute;
top: 12px;
right: 12px;
background: rgba(0, 0, 0, 0.8);
color: white;
padding: 6px 12px;
border-radius: 20px;
font-size: 11px;
font-weight: 600;
backdrop-filter: blur(10px);
}
/* Bottom Navigation */
.bottom-nav {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
border-top: 1px solid rgba(0, 0, 0, 0.1);
padding: 8px 0 calc(env(safe-area-inset-bottom) + 8px);
z-index: 1000;
}
.nav-items {
display: flex;
justify-content: space-around;
}
.nav-item {
flex: 1;
text-align: center;
padding: 8px;
color: #86868b;
text-decoration: none;
transition: color 0.2s;
}
.nav-item.active {
color: #1d1d1f;
}
.nav-icon {
font-size: 24px;
margin-bottom: 4px;
}
.nav-label {
font-size: 10px;
display: block;
}
/* Loading Spinner */
.loading {
text-align: center;
padding: 40px;
color: #86868b;
}
.spinner {
border: 3px solid #f3f3f3;
border-top: 3px solid #1d1d1f;
border-radius: 50%;
width: 40px;
height: 40px;
animation: spin 1s linear infinite;
margin: 0 auto 16px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* Pull to Refresh */
.pull-to-refresh {
position: absolute;
top: 0;
left: 0;
right: 0;
height: 60px;
display: flex;
align-items: center;
justify-content: center;
color: #86868b;
font-size: 14px;
transform: translateY(-100%);
transition: transform 0.3s;
}
.pull-to-refresh.active {
transform: translateY(0);
}
/* Modal for 360 Viewer */
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.95);
z-index: 2000;
padding: env(safe-area-inset-top) 0 env(safe-area-inset-bottom);
}
.modal.active {
display: flex;
flex-direction: column;
}
.modal-header {
padding: 16px;
color: white;
display: flex;
justify-content: space-between;
align-items: center;
}
.close-btn {
background: none;
border: none;
color: white;
font-size: 28px;
cursor: pointer;
padding: 8px;
}
.viewer-360 {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
}
.viewer-360 img {
max-width: 100%;
max-height: 100%;
object-fit: contain;
}
.viewer-controls {
padding: 16px;
color: white;
text-align: center;
}
.angle-indicator {
font-size: 24px;
font-weight: 700;
margin-bottom: 8px;
}
.swipe-hint {
font-size: 12px;
opacity: 0.7;
}
/* Dark Mode Support */
@media (prefers-color-scheme: dark) {
body {
background: #000;
color: #f5f5f7;
}
.mobile-header,
.search-container,
.filter-pills,
.bottom-nav {
background: rgba(29, 29, 29, 0.95);
border-color: rgba(255, 255, 255, 0.1);
}
.search-bar,
.stat-card,
.product-card {
background: #1d1d1f;
}
.pill {
background: #2d2d2d;
border-color: #3d3d3d;
color: #f5f5f7;
}
.pill.active {
background: #f5f5f7;
color: #1d1d1f;
}
.tag {
background: #2d2d2d;
color: #f5f5f7;
}
}
</style>
</head>
<body>
<!-- Header -->
<div class="mobile-header">
<div class="header-content">
<div class="logo">👜 Handbags</div>
<button class="menu-btn" onclick="toggleMenu()">☰</button>
</div>
</div>
<!-- Search -->
<div class="search-container">
<div class="search-bar">
<span class="search-icon">🔍</span>
<input type="text" placeholder="Search brands, styles..." id="searchInput" onkeyup="filterProducts()">
</div>
</div>
<!-- Filter Pills -->
<div class="filter-pills">
<span class="pill active" onclick="filterByCategory('all')">All</span>
<span class="pill" onclick="filterByCategory('luxury')">Luxury</span>
<span class="pill" onclick="filterByCategory('designer')">Designer</span>
<span class="pill" onclick="filterByCategory('360')">360° View</span>
<span class="pill" onclick="filterByCategory('french')">French Brands</span>
<span class="pill" onclick="filterByCategory('italian')">Italian Brands</span>
</div>
<!-- Main Content -->
<div class="main-content">
<!-- Stats -->
<div class="stats-grid">
<div class="stat-card">
<div class="stat-value" id="totalItems">4,056</div>
<div class="stat-label">Total Items</div>
</div>
<div class="stat-card">
<div class="stat-value" id="totalBrands">100+</div>
<div class="stat-label">Brands</div>
</div>
<div class="stat-card">
<div class="stat-value">586K</div>
<div class="stat-label">360° Images</div>
</div>
<div class="stat-card">
<div class="stat-value">$45-37K</div>
<div class="stat-label">Price Range</div>
</div>
</div>
<!-- Products List -->
<div id="productsList">
<div class="loading">
<div class="spinner"></div>
Loading handbags...
</div>
</div>
</div>
<!-- Bottom Navigation -->
<div class="bottom-nav">
<div class="nav-items">
<a href="#" class="nav-item active">
<div class="nav-icon">🏠</div>
<span class="nav-label">Home</span>
</a>
<a href="mobile-camera.html" class="nav-item">
<div class="nav-icon">📸</div>
<span class="nav-label">Camera</span>
</a>
<a href="#" class="nav-item" onclick="showFilter()">
<div class="nav-icon">🔍</div>
<span class="nav-label">Search</span>
</a>
<a href="#" class="nav-item" onclick="show360Gallery()">
<div class="nav-icon">360°</div>
<span class="nav-label">360° View</span>
</a>
<a href="#" class="nav-item" onclick="showStats()">
<div class="nav-icon">📊</div>
<span class="nav-label">Stats</span>
</a>
</div>
</div>
<!-- 360 Viewer Modal -->
<div class="modal" id="viewer360Modal">
<div class="modal-header">
<h3 id="modalTitle">360° View</h3>
<button class="close-btn" onclick="close360Viewer()">×</button>
</div>
<div class="viewer-360" id="viewer360">
<img id="viewer360Image" src="" alt="360 view">
</div>
<div class="viewer-controls">
<div class="angle-indicator" id="angleIndicator">0°</div>
<div class="swipe-hint">← Swipe to rotate →</div>
</div>
</div>
<script>
// Sample data structure
const sampleProducts = [
{
id: 1,
brand: 'Hermès',
name: 'Birkin 30 Togo Leather',
price: 12500,
image: '/api/placeholder/400/400',
has360: true,
type: 'luxury',
tags: ['French', 'Iconic', 'Investment']
},
{
id: 2,
brand: 'Chanel',
name: 'Classic Flap Medium',
price: 8800,
image: '/api/placeholder/400/400',
has360: true,
type: 'luxury',
tags: ['French', 'Timeless', 'Quilted']
},
{
id: 3,
brand: 'Louis Vuitton',
name: 'Neverfull MM Monogram',
price: 1850,
image: '/api/placeholder/400/400',
has360: false,
type: 'luxury',
tags: ['French', 'Tote', 'Canvas']
},
{
id: 4,
brand: 'Gucci',
name: 'GG Marmont Small',
price: 2450,
image: '/api/placeholder/400/400',
has360: true,
type: 'designer',
tags: ['Italian', 'Crossbody', 'Leather']
},
{
id: 5,
brand: 'Prada',
name: 'Galleria Saffiano Medium',
price: 3200,
image: '/api/placeholder/400/400',
has360: true,
type: 'luxury',
tags: ['Italian', 'Professional', 'Structured']
}
];
let currentFilter = 'all';
let currentAngle = 0;
// Initialize on load
window.addEventListener('load', function() {
loadProducts();
setupTouchEvents();
});
// Load products from API
async function loadProducts() {
try {
// In production, fetch from API
// const response = await fetch('/api/handbags');
// const products = await response.json();
// For now, use sample data
setTimeout(() => {
displayProducts(sampleProducts);
}, 1000);
} catch (error) {
console.error('Error loading products:', error);
document.getElementById('productsList').innerHTML =
'<p style="text-align:center;color:#86868b;">Error loading products</p>';
}
}
// Display products
function displayProducts(products) {
const container = document.getElementById('productsList');
container.innerHTML = products.map(product => `
<div class="product-card" onclick="viewProduct(${product.id})">
<div style="position:relative">
<img src="${product.image}" alt="${product.name}" class="product-image">
${product.has360 ? '<div class="badge-360">360° VIEW</div>' : ''}
</div>
<div class="product-info">
<div class="product-brand">${product.brand}</div>
<div class="product-name">${product.name}</div>
<div class="product-price">$${product.price.toLocaleString()}</div>
<div class="product-tags">
${product.tags.map(tag => `<span class="tag">${tag}</span>`).join('')}
</div>
</div>
</div>
`).join('');
}
// Filter products
function filterByCategory(category) {
currentFilter = category;
// Update pill states
document.querySelectorAll('.pill').forEach(pill => {
pill.classList.remove('active');
});
event.target.classList.add('active');
// Filter products
let filtered = sampleProducts;
if (category !== 'all') {
if (category === '360') {
filtered = sampleProducts.filter(p => p.has360);
} else if (category === 'french') {
filtered = sampleProducts.filter(p => p.tags.includes('French'));
} else if (category === 'italian') {
filtered = sampleProducts.filter(p => p.tags.includes('Italian'));
} else {
filtered = sampleProducts.filter(p => p.type === category);
}
}
displayProducts(filtered);
}
// Search products
function filterProducts() {
const searchTerm = document.getElementById('searchInput').value.toLowerCase();
const filtered = sampleProducts.filter(p =>
p.brand.toLowerCase().includes(searchTerm) ||
p.name.toLowerCase().includes(searchTerm) ||
p.tags.some(tag => tag.toLowerCase().includes(searchTerm))
);
displayProducts(filtered);
}
// View product details
function viewProduct(id) {
const product = sampleProducts.find(p => p.id === id);
if (product && product.has360) {
open360Viewer(product);
} else {
alert('Product details coming soon!');
}
}
// Open 360 viewer
function open360Viewer(product) {
document.getElementById('modalTitle').textContent = product.brand + ' - ' + product.name;
document.getElementById('viewer360Image').src = product.image;
document.getElementById('viewer360Modal').classList.add('active');
currentAngle = 0;
updateAngleIndicator();
}
// Close 360 viewer
function close360Viewer() {
document.getElementById('viewer360Modal').classList.remove('active');
}
// Touch events for 360 rotation
function setupTouchEvents() {
const viewer = document.getElementById('viewer360');
let startX = 0;
viewer.addEventListener('touchstart', (e) => {
startX = e.touches[0].clientX;
});
viewer.addEventListener('touchmove', (e) => {
const deltaX = e.touches[0].clientX - startX;
const angleChange = Math.floor(deltaX / 5);
currentAngle = (currentAngle + angleChange + 360) % 360;
updateAngleIndicator();
// In production: load image at current angle
startX = e.touches[0].clientX;
});
}
// Update angle indicator
function updateAngleIndicator() {
document.getElementById('angleIndicator').textContent = currentAngle + '°';
}
// Navigation functions
function showFilter() {
document.getElementById('searchInput').focus();
}
function show360Gallery() {
filterByCategory('360');
}
function showStats() {
alert('Detailed stats coming soon!');
}
function toggleMenu() {
alert('Menu coming soon!');
}
// Service Worker for offline capability
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js').catch(err => {
console.log('ServiceWorker registration failed:', err);
});
}
</script>
</body>
</html>