← back to Dw Photo Capture
public/themes/v1-magic-camera.html
361 lines
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>v1 — Magic Camera</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Fraunces:wght@500;600;700&family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<style>
:root{
--charcoal:#1C1C1E;
--gold:#C9A24B;
--cream:#FDFBF7;
--green:#3FA66B;
}
*{box-sizing:border-box;}
html,body{margin:0;padding:0;background:#0a0a0a;font-family:'Inter',sans-serif;}
.stage{
width:390px;height:780px;margin:24px auto;position:relative;
background:var(--charcoal);border-radius:36px;overflow:hidden;
box-shadow:0 20px 60px rgba(0,0,0,.5);border:8px solid #000;
}
.screen{
position:absolute;inset:0;display:none;flex-direction:column;
background:var(--charcoal);color:var(--cream);
}
.screen.active{display:flex;}
.light{background:var(--cream);color:var(--charcoal);}
h1,h2,h3{font-family:'Fraunces',serif;margin:0;}
.dw-mark{
position:absolute;top:18px;left:18px;font-family:'Fraunces',serif;
font-weight:700;font-size:15px;letter-spacing:1px;color:var(--cream);opacity:.85;z-index:5;
}
.hint{
position:absolute;top:56px;left:0;right:0;text-align:center;
font-size:17px;color:var(--cream);opacity:.9;z-index:5;
}
.viewfinder{
position:absolute;inset:0;
background:
repeating-linear-gradient(135deg, #232326 0 40px, #1c1c1e 40px 80px);
}
.shutter-wrap{
position:absolute;bottom:48px;left:0;right:0;display:flex;justify-content:center;z-index:5;
}
.shutter{
width:96px;height:96px;border-radius:50%;background:var(--gold);
border:6px solid var(--cream);cursor:pointer;font-size:40px;
display:flex;align-items:center;justify-content:center;
box-shadow:0 8px 24px rgba(0,0,0,.4);
}
.shutter:focus-visible, button:focus-visible, .card-btn:focus-visible, .brand-tile:focus-visible{
outline:3px solid var(--gold);outline-offset:2px;
}
.btn:disabled{opacity:.4;cursor:not-allowed;}
.demo-caption{font-size:13px;opacity:.55;margin-top:-10px;}
.looking-wrap{align-items:center;justify-content:center;gap:18px;}
.looking-wrap .spinner{
width:64px;height:64px;border-radius:50%;
border:6px solid rgba(255,255,255,.15);border-top-color:var(--gold);
animation:spin 1s linear infinite;
}
@keyframes spin{to{transform:rotate(360deg);}}
.looking-wrap p{font-size:19px;}
.content{padding:28px 24px;display:flex;flex-direction:column;gap:18px;flex:1;overflow-y:auto;}
.swatch{
width:100%;height:220px;border-radius:18px;margin-top:4px;
background:
repeating-linear-gradient(45deg,#8FAE8B 0 22px,#B9CBB2 22px 44px),
linear-gradient(135deg,#8FAE8B,#6f8d6c);
background-blend-mode:multiply;
}
.swatch.blue{background:
repeating-linear-gradient(45deg,#7C93B0 0 22px,#B3C3D6 22px 44px),
linear-gradient(135deg,#7C93B0,#5f7692);
background-blend-mode:multiply;}
.item-title{font-size:22px;font-weight:700;}
.item-sub{font-size:15px;opacity:.75;}
.btn{
min-height:56px;border-radius:16px;border:none;font-family:'Inter',sans-serif;
font-size:18px;font-weight:600;cursor:pointer;padding:0 20px;width:100%;
}
.btn-primary{background:var(--gold);color:#1C1C1E;}
.btn-secondary{background:transparent;color:var(--cream);border:2px solid rgba(253,251,247,.4);}
.btn-light-primary{background:var(--gold);color:#1C1C1E;}
.btn-light-secondary{background:#ece7dd;color:var(--charcoal);border:2px solid #ddd4c2;}
.btn-success{background:var(--green);color:#fff;}
.brand-grid{display:grid;grid-template-columns:1fr 1fr;gap:12px;overflow-y:auto;padding-right:2px;}
.brand-tile{
background:#2a2a2d;border:2px solid #3a3a3d;border-radius:14px;
min-height:72px;display:flex;align-items:center;justify-content:center;
font-size:17px;font-weight:600;color:var(--cream);cursor:pointer;text-align:center;padding:8px;
}
.brand-tile:hover{border-color:var(--gold);}
.brand-tile.selected{border-color:var(--gold);background:#3a331f;box-shadow:inset 0 0 0 2px var(--gold);}
.center-flex{align-items:center;justify-content:center;text-align:center;}
.big-emoji{font-size:64px;}
.pick-list{display:flex;flex-direction:column;gap:14px;margin-top:8px;}
.home-hdr{text-align:center;font-size:17px;margin-top:4px;}
.ux-home{position:absolute;top:14px;right:14px;z-index:20;min-height:44px;min-width:44px;padding:0 14px;border-radius:12px;
border:2px solid var(--cream);background:rgba(28,28,30,.85);color:var(--cream);font:600 15px Inter,sans-serif;cursor:pointer}
.ux-home[hidden]{display:none}
.ux-cancel{margin-top:18px;min-height:48px;padding:0 22px;border-radius:12px;border:2px solid var(--cream);background:transparent;color:var(--cream);font:600 16px Inter,sans-serif;cursor:pointer}
</style>
<style id="ux-baseline">/* TK-12343 UX baseline: full-bleed on real phones, visible focus everywhere */
@media (max-width:405px){ html,body{margin:0;padding:0} body{overflow-x:hidden} .stage,.phone{width:100% !important;max-width:100%;margin:0 !important;border:0 !important;border-radius:0 !important;box-shadow:none !important;height:100dvh;min-height:640px} }
button:focus-visible,a:focus-visible,[tabindex]:focus-visible,input:focus-visible{outline:3px solid #9C7A33 !important;outline-offset:2px}
</style>
</head>
<body>
<div class="stage" id="stage">
<button class="ux-home" id="uxHome" type="button" onclick="goHome()" hidden>⌂ Home</button>
<!-- HOME -->
<div class="screen active" id="home">
<div class="dw-mark">DW</div>
<div class="hint">Point at anything and tap 📸</div>
<div class="viewfinder"></div>
<div class="shutter-wrap">
<button class="shutter" id="shutterBtn" aria-label="Shoot photo">📸</button>
</div>
</div>
<!-- LOOKING -->
<div class="screen looking-wrap" id="looking">
<div class="spinner"></div>
<p>Looking…</p>
<button class="ux-cancel" type="button" onclick="goHome()">Cancel</button>
</div>
<!-- GUESS: ADD NEW ITEM -->
<div class="screen" id="guessAdd">
<div class="content">
<h2>Looks like a New Item</h2>
<p class="demo-caption">Demo: cycles through jobs — real version would ask</p>
<div class="swatch"></div>
<button class="btn btn-primary" onclick="show('addBrand')">Yes, that's right</button>
<button class="btn btn-secondary" onclick="show('pickAnother')">No, pick another</button>
</div>
</div>
<!-- ADD: BRAND GRID -->
<div class="screen" id="addBrand">
<div class="content">
<h3>Which brand?</h3>
<div class="brand-grid">
<div class="brand-tile" tabindex="0" role="button" onclick="selectBrand(this,'York')" onkeydown="onBrandKey(event,this,'York')">York</div>
<div class="brand-tile" tabindex="0" role="button" onclick="selectBrand(this,'Brewster')" onkeydown="onBrandKey(event,this,'Brewster')">Brewster</div>
<div class="brand-tile" tabindex="0" role="button" onclick="selectBrand(this,'Kravet')" onkeydown="onBrandKey(event,this,'Kravet')">Kravet</div>
<div class="brand-tile" tabindex="0" role="button" onclick="selectBrand(this,'Cole & Son')" onkeydown="onBrandKey(event,this,'Cole & Son')">Cole & Son</div>
<div class="brand-tile" tabindex="0" role="button" onclick="selectBrand(this,'Thibaut')" onkeydown="onBrandKey(event,this,'Thibaut')">Thibaut</div>
<div class="brand-tile" tabindex="0" role="button" onclick="selectBrand(this,'Schumacher')" onkeydown="onBrandKey(event,this,'Schumacher')">Schumacher</div>
<div class="brand-tile" tabindex="0" role="button" onclick="selectBrand(this,'Mulberry')" onkeydown="onBrandKey(event,this,'Mulberry')">Mulberry</div>
<div class="brand-tile" tabindex="0" role="button" onclick="selectBrand(this,'Other / Unsure')" onkeydown="onBrandKey(event,this,'Other / Unsure')">Other / Unsure</div>
</div>
<button class="btn btn-primary" id="saveDraftBtn" disabled onclick="saveDraft()">Save as Draft</button>
</div>
</div>
<!-- ADD: SAVED -->
<div class="screen light center-flex" id="addSaved">
<div class="content center-flex">
<div class="big-emoji">🎉</div>
<h2>Saved!</h2>
<div class="item-sub">Added as <strong id="savedBrandName"></strong></div>
<button class="btn btn-light-primary" onclick="goHome()">Take Another</button>
</div>
</div>
<!-- GUESS: UPDATE -->
<div class="screen" id="guessUpdate">
<div class="content">
<h2>Found a Match</h2>
<p class="demo-caption">Demo: cycles through jobs — real version would ask</p>
<div class="swatch blue"></div>
<div class="item-title">Palm Damask — Sage</div>
<div class="item-sub">code DW-10423</div>
<button class="btn btn-primary" onclick="show('updateCam2')">Yes, that's right</button>
<button class="btn btn-secondary" onclick="show('pickAnother')">Not this item</button>
</div>
</div>
<!-- UPDATE: TAKE NEW PHOTO -->
<div class="screen" id="updateCam2">
<div class="dw-mark">DW</div>
<div class="hint">Take the new photo</div>
<div class="viewfinder"></div>
<div class="shutter-wrap">
<button class="shutter" onclick="show('updateDone')" aria-label="Shoot new photo">📸</button>
</div>
</div>
<!-- UPDATE: DONE -->
<div class="screen light center-flex" id="updateDone">
<div class="content center-flex">
<div class="big-emoji">✅</div>
<h2>Updated!</h2>
<button class="btn btn-light-primary" onclick="goHome()">Take Another</button>
</div>
</div>
<!-- GUESS: FIND -->
<div class="screen" id="guessFind">
<div class="content">
<h2>Best Match</h2>
<p class="demo-caption">Demo: cycles through jobs — real version would ask</p>
<div class="swatch"></div>
<div class="item-title">Palm Damask — Sage</div>
<div class="item-sub">code DW-10423</div>
<button class="btn btn-primary" onclick="show('findDone')">Yes, that's right</button>
<button class="btn btn-secondary" onclick="show('findFallback')">Not this item</button>
</div>
</div>
<!-- FIND: FALLBACK SEARCH -->
<div class="screen" id="findFallback">
<div class="content">
<h3>Search another way</h3>
<div class="pick-list">
<button class="btn btn-secondary" onclick="show('findResult')">📷 Scan</button>
<button class="btn btn-secondary" onclick="show('findResult')">🎤 Voice</button>
<button class="btn btn-secondary" onclick="show('findResult')">⌨️ Type</button>
<button class="btn btn-secondary" onclick="show('findResult')">🗂️ Browse</button>
</div>
</div>
</div>
<!-- FIND: RESULT -->
<div class="screen" id="findResult">
<div class="content">
<h2>Here it is</h2>
<div class="swatch blue"></div>
<div class="item-title">Palm Damask — Sage</div>
<div class="item-sub">code DW-10423</div>
<button class="btn btn-primary" onclick="show('findDone')">Yes, that's right</button>
<button class="btn btn-secondary" onclick="show('findFallback')">Not this item</button>
</div>
</div>
<!-- FIND: DONE -->
<div class="screen light center-flex" id="findDone">
<div class="content center-flex">
<div class="big-emoji">🔍</div>
<h2>Found it!</h2>
<button class="btn btn-light-primary" onclick="goHome()">Take Another</button>
</div>
</div>
<!-- GUESS: SAMPLE -->
<div class="screen" id="guessSample">
<div class="content">
<h2>Sample Matched</h2>
<p class="demo-caption">Demo: cycles through jobs — real version would ask</p>
<div class="swatch"></div>
<div class="item-title">Palm Damask — Sage</div>
<div class="item-sub">code DW-10423</div>
<button class="btn btn-primary" onclick="show('samplePrinting')">Mark It Received</button>
<button class="btn btn-secondary" onclick="show('pickAnother')">Not this item</button>
</div>
</div>
<!-- SAMPLE: PRINTING -->
<div class="screen looking-wrap" id="samplePrinting">
<div class="spinner"></div>
<p>Printing your sticker…</p>
<button class="ux-cancel" type="button" onclick="goHome()">Cancel</button>
</div>
<!-- SAMPLE: DONE -->
<div class="screen light center-flex" id="sampleDone">
<div class="content center-flex">
<div class="big-emoji">✅</div>
<h2>Done!</h2>
<button class="btn btn-light-primary" onclick="goHome()">Take Another</button>
</div>
</div>
<!-- PICK ANOTHER (No, pick another fallback chooser) -->
<div class="screen" id="pickAnother">
<div class="content">
<h3>Which job is this?</h3>
<div class="pick-list">
<button class="btn btn-secondary" onclick="show('addBrand')">Add New Item</button>
<button class="btn btn-secondary" onclick="show('updateCam2')">Update an Item</button>
<button class="btn btn-secondary" onclick="show('findFallback')">Find an Item</button>
<button class="btn btn-secondary" onclick="show('guessSample')">A Sample Came In</button>
</div>
</div>
</div>
</div>
<script>
let jobIndex = 0;
const jobGuesses = ['guessAdd','guessUpdate','guessFind','guessSample'];
let selectedBrand = null;
function show(id){
document.querySelectorAll('.screen').forEach(s=>s.classList.remove('active'));
document.getElementById(id).classList.add('active');
document.getElementById('uxHome').hidden = (id === 'home');
if(id === 'addBrand'){
selectedBrand = null;
document.querySelectorAll('#addBrand .brand-tile').forEach(t=>t.classList.remove('selected'));
document.getElementById('saveDraftBtn').disabled = true;
}
}
function selectBrand(el, name){
document.querySelectorAll('#addBrand .brand-tile').forEach(t=>t.classList.remove('selected'));
el.classList.add('selected');
selectedBrand = name;
document.getElementById('saveDraftBtn').disabled = false;
}
function onBrandKey(evt, el, name){
if(evt.key === 'Enter' || evt.key === ' '){
evt.preventDefault();
selectBrand(el, name);
}
}
function saveDraft(){
if(!selectedBrand) return;
document.getElementById('savedBrandName').textContent = selectedBrand;
show('addSaved');
}
let pendingTimer = null;
function goHome(){
if (pendingTimer) { clearTimeout(pendingTimer); pendingTimer = null; }
show('home');
}
document.getElementById('shutterBtn').addEventListener('click', function(){
show('looking');
pendingTimer = setTimeout(function(){
pendingTimer = null;
const target = jobGuesses[jobIndex % jobGuesses.length];
jobIndex++;
show(target);
}, 900);
});
// sample "Mark It Received" leads through printing -> done automatically
document.addEventListener('DOMContentLoaded', function(){
const printingScreen = document.getElementById('samplePrinting');
});
// auto-advance printing screen to done
const origShow = show;
show = function(id){
origShow(id);
if(id === 'samplePrinting'){
pendingTimer = setTimeout(function(){ pendingTimer = null; origShow('sampleDone'); document.getElementById('uxHome').hidden = false; }, 1100);
}
};
</script>
</body>
</html>