← back to Dw Photo Capture
public/kid-simple/v5-swipe-deck.html
491 lines
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<title>v5 — Swipe Deck</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,500;9..144,700&family=Inter:wght@400;600;700;800&display=swap" rel="stylesheet">
<style>
:root{ --blush:#F4EDE7; --green:#3FA66B; --red:#C0503F; --gold:#C9A24B; --gold-text:#7A5C22; --ink:#2B2B2B; }
*{box-sizing:border-box; touch-action:pan-y;}
html,body{margin:0;padding:0;background:#ddd;font-family:'Inter',sans-serif;color:var(--ink);}
body{display:flex; align-items:flex-start; justify-content:center; min-height:100vh; padding:20px 0;}
.phone{
width:390px; min-height:780px; background:var(--blush); border-radius:36px; overflow:hidden;
box-shadow:0 24px 60px rgba(0,0,0,.28); display:flex; flex-direction:column; position:relative;
}
header{ padding:10px 14px; display:flex; align-items:center; justify-content:space-between; gap:8px; }
header .mark{ font-family:'Fraunces',serif; font-weight:700; font-size:18px; color:var(--gold-text); }
.navbtn{
background:#fff; border:2px solid #e6dccb; color:var(--ink); font-size:15px; font-weight:800; cursor:pointer;
min-width:56px; min-height:56px; border-radius:14px; padding:0 14px; font-family:'Inter',sans-serif;
display:inline-flex; align-items:center; justify-content:center; gap:4px;
}
.navbtn:active{transform:scale(.97);}
.screen-area{ flex:1; position:relative; display:flex; align-items:center; justify-content:center; padding:14px 18px; overflow:hidden; }
.inner{ width:100%; display:flex; flex-direction:column; gap:14px; }
h1.title{ font-family:'Fraunces',serif; font-weight:700; font-size:24px; margin:0 0 4px; text-align:center; }
p.sub{ margin:0; font-size:17px; color:#555; text-align:center; }
.steplabel{ text-align:center; font-size:14px; font-weight:800; letter-spacing:.03em; text-transform:uppercase; color:#a08a5a; margin:0; }
.card{
background:#fff; border-radius:22px; padding:20px; box-shadow:0 10px 28px rgba(0,0,0,.14);
display:flex; flex-direction:column; gap:12px; user-select:none;
}
.card.swiper{ cursor:grab; will-change:transform, opacity; }
.card.swiper:active{ cursor:grabbing; }
.itemname{ font-size:20px; font-weight:800; margin:0; }
.itemmeta{ font-size:17px; color:#555; margin:0; }
.swatch{ width:100%; height:170px; border-radius:16px; }
.sw1{background:repeating-linear-gradient(45deg,#8FAE8B 0 18px,#7c9c78 18px 36px);}
.sw2{background:repeating-linear-gradient(-45deg,#7C93B0 0 16px,#617a99 16px 32px);}
.sw3{background:repeating-linear-gradient(90deg,#D9A441 0 14px,#c98f2c 14px 28px);}
.camview{
height:280px; background:#1c1c1e; border-radius:20px; position:relative;
display:flex; align-items:center; justify-content:center; color:#eee;
}
.camview .hint{position:absolute; top:14px; left:0; right:0; text-align:center; font-size:15px; color:#f0e8d8;}
.camview .status{ font-size:19px; font-weight:700; }
.shutter{
position:absolute; bottom:18px; left:50%; transform:translateX(-50%);
width:74px; height:74px; border-radius:50%; background:var(--gold); border:5px solid #fff; font-size:28px;
display:flex; align-items:center; justify-content:center;
}
.shutter:active{transform:translateX(-50%) scale(.97);}
button{ font-family:'Inter',sans-serif; cursor:pointer; }
.btn{ min-height:56px; width:100%; border-radius:18px; font-size:18px; font-weight:700; border:none;
display:flex; align-items:center; justify-content:center; gap:8px; padding:0 16px; }
.btn:active{transform:scale(.97);}
.btn-primary{ background:var(--gold); color:#3a2a06; }
.btn-secondary{ background:#fff; color:var(--ink); border:2px solid #ddd; }
.btn:focus-visible, .brandcard:focus-visible, .tabbtn:focus-visible, .navbtn:focus-visible,
.demotoggle:focus-visible, .codeinput:focus-visible, .searchbox:focus-visible, .toastundo:focus-visible,
.circle:focus-visible{ outline:3px solid #2B6CB0; outline-offset:2px; }
.row{ display:flex; gap:12px; }
.row .btn{ flex:1; }
.swipebtns{ display:flex; justify-content:center; gap:36px; margin-top:6px; }
.circlewrap{ display:flex; flex-direction:column; align-items:center; gap:6px; }
.circle{
width:64px; height:64px; border-radius:50%; border:none; font-size:26px; font-weight:800;
display:flex; align-items:center; justify-content:center; box-shadow:0 6px 14px rgba(0,0,0,.15);
}
.circle:active{transform:scale(.97);}
.circle.reject{ background:#fff; color:var(--red); border:3px solid var(--red); }
.circle.accept{ background:#fff; color:var(--green); border:3px solid var(--green); }
.circle-label{ font-size:14px; font-weight:700; color:#555; }
.brandgrid{ display:grid; grid-template-columns:1fr 1fr; gap:10px; }
.brandcard{ min-height:60px; border-radius:14px; background:#fff; border:2px solid #e6dccb; font-size:16px; font-weight:700; }
.brandcard:active{transform:scale(.97);}
.sectionlabel{ margin:0; font-size:14px; font-weight:800; letter-spacing:.04em; text-transform:uppercase; color:#8a7a5a; }
.searchbox, .codeinput{
width:100%; min-height:56px; border-radius:16px; border:2px solid #e6dccb; padding:0 16px;
font-size:17px; font-family:'Inter',sans-serif; background:#fff; color:var(--ink);
}
.demotoggle{
background:none; border:2px dashed #cbbfa0; color:#4a4a4a; font-size:17px; font-weight:700;
border-radius:12px; padding:8px 12px; min-height:56px; align-self:center;
}
.demotoggle:active{transform:scale(.97);}
.resultlabel{ position:absolute; top:10px; left:10px; background:rgba(0,0,0,.55); color:#fff; font-size:13px; padding:4px 10px; border-radius:999px; }
.swipehint{ background:#232323; color:#fff; border-radius:16px; padding:16px; text-align:center; display:flex; flex-direction:column; gap:12px; font-size:15px; font-weight:600; }
.bottombar{
display:flex; justify-content:space-around; align-items:center; padding:10px 6px 16px;
background:#fff; border-top:1px solid #eee;
}
.tabbtn{
background:none; border:none; display:flex; flex-direction:column; align-items:center; gap:4px;
min-width:64px; min-height:56px; font-size:12px; font-weight:700; color:#7a7a7a;
}
.tabbtn:active{transform:scale(.97);}
.tabbtn .ic{ font-size:24px; }
.tabbtn.active{ color:var(--gold-text); }
.sr-only{position:absolute; width:1px; height:1px; padding:0; margin:-1px; overflow:hidden; clip:rect(0,0,0,0); white-space:nowrap; border:0;}
.toast{
position:absolute; left:16px; right:16px; bottom:96px; background:#232323; color:#fff;
border-radius:14px; padding:14px 18px; display:flex; align-items:center; justify-content:space-between;
gap:12px; font-size:16px; font-weight:700; opacity:0; transform:translateY(12px); pointer-events:none;
transition:opacity .25s ease, transform .25s ease; z-index:30;
}
.toast.show{opacity:1; transform:translateY(0); pointer-events:auto;}
.toastundo{background:none; border:none; color:var(--gold); font-weight:800; font-size:16px; min-height:56px; padding:0 6px;}
@media (prefers-reduced-motion: reduce){
*{transition:none !important; animation:none !important;}
}
</style>
</head>
<body>
<div class="phone">
<header>
<button class="navbtn" aria-label="Back" onclick="goBack()" id="backBtn">‹ Back</button>
<span class="mark">DW · Photo</span>
<button class="navbtn" aria-label="Home" onclick="go('home')" id="homeBtnTop">⌂ Home</button>
</header>
<div class="screen-area" id="screen"></div>
<div class="toast" id="toast" role="status"></div>
<div class="sr-only" id="live" aria-live="polite"></div>
<div class="bottombar">
<button class="tabbtn" onclick="startJob('new')" id="tab-new"><span class="ic">🆕</span>New</button>
<button class="tabbtn" onclick="startJob('update')" id="tab-update"><span class="ic">🔄</span>Update</button>
<button class="tabbtn" onclick="startJob('find')" id="tab-find"><span class="ic">🔍</span>Find</button>
<button class="tabbtn" onclick="startJob('sample')" id="tab-sample"><span class="ic">📦</span>Sample</button>
</div>
</div>
<script>
const MOCK_ITEMS = [
{name:'Palm Damask', color:'Sage', code:'DW-10423', sw:'sw1', brand:'York Wallcoverings'},
{name:'Trellis Weave', color:'Indigo', code:'DW-88214', sw:'sw2', brand:'Cole & Son'},
{name:'Linen Bloom', color:'Blush', code:'DW-55071', sw:'sw3', brand:'Thibaut'},
];
const BRANDS = ['York Wallcoverings','Brewster Home','Kravet','Cole & Son','Thibaut','Ronald Redding'];
const RECENT_BRANDS = BRANDS.slice(0,3);
const STEP_MAP = {
'new-cam':[1,4], 'new-review':[2,4], 'new-brand':[3,4], 'new-savecheck':[4,4],
'update-scan':[1,4], 'update-match':[2,4], 'update-newphoto':[3,4], 'update-review':[4,4],
'find-method':[1,3], 'find-stack':[2,3], 'find-detail':[3,3],
'sample-scan':[1,3], 'sample-match':[2,3], 'sample-printing':[3,3],
};
// screens that render around state.item — if item is missing (e.g. a job switch
// mid-flow nulled it while an old screen was still in historyStack), render()
// must not throw; it routes home instead.
const ITEM_SCREENS = ['new-review','new-savecheck','new-done','update-match','update-review','update-done','find-detail','sample-match','sample-done'];
const state = { screen:'home', job:null, item:null, brand:null, resultIndex:0, demoNoMatch:false, confirmScreen:null };
let currentSwiper = null;
let historyStack = [];
let toastTimer = null;
let hintShown = false;
function announce(text){ const el=document.getElementById('live'); if(el) el.textContent = text; }
function go(screen){
if(screen==='home'){ historyStack = []; }
else if(state.screen && state.screen!==screen){ historyStack.push(state.screen); }
state.screen = screen; render();
}
function goReplace(screen){ state.screen = screen; render(); }
function goBack(){
if(historyStack.length){ state.screen = historyStack.pop(); render(); }
else go('home');
}
function startJob(job){
state.job = job; state.item=null; state.brand=null; state.resultIndex=0;
// switching jobs (incl. via the always-visible bottom tab bar) is a fresh
// start — any screens left in historyStack belong to the old job and may
// reference state (item/brand) that just got cleared. Clear the stack AND
// use goReplace (not go) so the old screen never gets re-pushed as a back
// target — Back from a freshly-started job always lands on Home.
historyStack = [];
const map = {new:'new-cam', update:'update-scan', find:'find-method', sample:'sample-scan'};
goReplace(map[job]);
}
function randomItem(){ return MOCK_ITEMS[Math.floor(Math.random()*MOCK_ITEMS.length)]; }
function stepLabel(){ const m = STEP_MAP[state.screen]; return m ? `<p class="steplabel">Card ${m[0]} of ${m[1]}</p>` : ''; }
function toggleDemo(){ state.demoNoMatch = !state.demoNoMatch; render(); }
function demoToggleHtml(){
return `<button class="demotoggle" aria-pressed="${state.demoNoMatch}" onclick="toggleDemo()">Demo: show "no match" ${state.demoNoMatch?'✅ On':'⬜ Off'}</button>`;
}
function maybeHint(text){
if(hintShown) return '';
hintShown = true;
text = text || 'Swipe right = yes, left = redo<br>(or tap the buttons)';
return `<div class="swipehint" role="note"><p>${text}</p><button class="btn btn-primary" style="width:auto;min-width:120px;align-self:center;" onclick="this.closest('.swipehint').remove()">Got it</button></div>`;
}
function shoot(cb, loadingLabel){
const fromScreen = state.screen;
const st = document.getElementById('camStatus');
const btn = document.querySelector('.shutter');
if(btn) btn.disabled = true;
if(st) st.textContent = 'Snapping…';
announce('Snapping photo…');
setTimeout(()=>{
if(state.screen!==fromScreen) return; // user navigated away mid-shot
state.item = randomItem();
if(loadingLabel){ if(st) st.textContent = loadingLabel; announce(loadingLabel); setTimeout(()=>{ if(state.screen===fromScreen) cb(); }, 500); }
else cb();
}, 700);
}
function afterUpdateScan(){ state.demoNoMatch ? go('update-nomatch') : go('update-match'); }
function afterSampleScan(){ state.demoNoMatch ? go('sample-nomatch') : go('sample-match'); }
function tryFindMethod(){
go('find-loading');
announce('Looking for matches…');
setTimeout(()=>{ if(state.screen==='find-loading') goReplace(state.demoNoMatch ? 'find-nomatch' : 'find-stack'); }, 600);
}
function swipeButtons(rejectAria, acceptAria){
acceptAria = acceptAria || 'Yes';
return `<div class="swipebtns">
<div class="circlewrap">
<button class="circle reject" aria-label="${rejectAria}" onclick="currentSwiper.swipeLeft()">✗</button>
<span class="circle-label">Redo</span>
</div>
<div class="circlewrap">
<button class="circle accept" aria-label="${acceptAria}" onclick="currentSwiper.swipeRight()">✓</button>
<span class="circle-label">Yes</span>
</div>
</div>`;
}
function cameraCard(hint, onShoot){
return `<div class="camview">
<div class="hint">${hint}</div>
<div class="status" id="camStatus">Tap the shutter</div>
<button class="shutter" aria-label="Snap It" onclick="${onShoot}">📸</button>
</div>`;
}
function brandGridHtml(nextScreen){
let html = `<input type="text" class="searchbox" placeholder="Search brands…" aria-label="Search brands" oninput="filterBrands(this.value)">`;
html += `<div id="brandRecent"><p class="sectionlabel">Recent</p><div class="brandgrid">`;
RECENT_BRANDS.forEach(b=>{ html += `<button class="brandcard" onclick="state.brand='${b}'; go('${nextScreen}')">${b}</button>`; });
html += `</div></div>`;
html += `<p class="sectionlabel">All Brands</p><div class="brandgrid" id="brandFullGrid">`;
BRANDS.forEach(b=>{ html += `<button class="brandcard" onclick="state.brand='${b}'; go('${nextScreen}')">${b}</button>`; });
html += `</div>`;
return html;
}
function filterBrands(q){
q = q.trim().toLowerCase();
const recentWrap = document.getElementById('brandRecent');
if(recentWrap) recentWrap.style.display = q ? 'none' : '';
const full = document.getElementById('brandFullGrid');
if(!full) return;
full.querySelectorAll('.brandcard').forEach(btn=>{
btn.style.display = btn.textContent.toLowerCase().includes(q) ? '' : 'none';
});
}
function showToast(msg){
const t = document.getElementById('toast');
t.innerHTML = `${msg} <button class="toastundo" onclick="undoLast()">Undo</button>`;
t.classList.add('show');
announce(msg + '. Undo available for 5 seconds.');
clearTimeout(toastTimer);
toastTimer = setTimeout(()=>{ t.classList.remove('show'); }, 5000);
}
function undoLast(){
const t = document.getElementById('toast');
t.classList.remove('show');
clearTimeout(toastTimer);
if(state.confirmScreen) go(state.confirmScreen);
}
function saveDraft(){ state.confirmScreen='new-savecheck'; go('new-done'); showToast('Saved'); }
function finishUpdate(){ state.confirmScreen='update-review'; go('update-done'); showToast('Updated'); }
// generic pointer-drag swipe; buttons call swipeRight()/swipeLeft() on the returned handle
function attachSwipe(el, onRight, onLeft){
let startX=0, dragging=false, dx=0;
function down(e){ dragging=true; startX=e.clientX; el.style.transition='none'; el.setPointerCapture && el.setPointerCapture(e.pointerId); }
function move(e){ if(!dragging) return; dx = e.clientX-startX;
el.style.transform = `translateX(${dx}px) rotate(${dx/18}deg)`;
el.style.opacity = 1 - Math.min(Math.abs(dx)/450,.5);
}
function up(){ if(!dragging) return; dragging=false;
el.style.transition='transform .25s ease, opacity .25s ease';
if(dx>90) flyOut(1,onRight); else if(dx<-90) flyOut(-1,onLeft); else { el.style.transform=''; el.style.opacity=1; }
dx=0;
}
function flyOut(dir,cb){
el.style.transform = `translateX(${dir*520}px) rotate(${dir*28}deg)`;
el.style.opacity=0;
setTimeout(()=>{ if(cb) cb(); }, 200);
}
el.addEventListener('pointerdown', down);
el.addEventListener('pointermove', move);
el.addEventListener('pointerup', up);
el.addEventListener('pointercancel', up);
const handle = { swipeRight:()=>flyOut(1,onRight), swipeLeft:()=>flyOut(-1,onLeft) };
return handle;
}
function render(){
// defensive: never let a screen render around missing item/brand state —
// route home instead of throwing (e.g. tab-switch mid-flow, or any other
// path that leaves a stale screen pointing at cleared state).
if(ITEM_SCREENS.includes(state.screen) && !state.item){
state.screen = 'home';
historyStack = [];
}
const s = document.getElementById('screen');
const onHome = state.screen==='home';
document.getElementById('backBtn').style.display = onHome ? 'none' : 'inline-flex';
document.getElementById('homeBtnTop').style.display = onHome ? 'none' : 'inline-flex';
['new','update','find','sample'].forEach(j=>{
document.getElementById('tab-'+j).classList.toggle('active', state.job===j);
});
let html = '<div class="inner">';
if(state.screen==='home'){
html += `<div class="card" style="text-align:center; align-items:center;">
<h1 class="title">Ready when you are</h1>
<p class="sub">Pick a job below to get started.</p>
</div>`;
}
else if(state.screen==='new-cam'){
html += stepLabel() + `<h1 class="title">Add New Item</h1>` + cameraCard('Take a photo of the sample', "shoot(()=>go('new-review'))");
}
else if(state.screen==='new-review'){
html += stepLabel();
html += maybeHint();
html += `<p class="sub">Swipe or tap to decide</p>`;
html += `<div class="card swiper" id="swipeCard">
<div class="swatch ${state.item.sw}"></div>
<p class="itemname">${state.item.name} — ${state.item.color}</p>
<p class="itemmeta">code ${state.item.code}</p>
</div>`;
html += swipeButtons('Retake', 'Looks Good');
}
else if(state.screen==='new-brand'){
html += stepLabel() + `<h1 class="title">Which Brand?</h1>` + brandGridHtml('new-savecheck');
}
else if(state.screen==='new-savecheck'){
html += stepLabel();
html += `<div class="card">
<h1 class="title" style="font-size:20px;">Save this?</h1>
<div class="swatch ${state.item.sw}"></div>
<p class="itemname">${state.item.name} — ${state.item.color}</p>
<p class="itemmeta">${state.brand} · code ${state.item.code}</p>
</div>`;
html += `<button class="btn btn-primary" onclick="saveDraft()">Save as Draft</button>`;
}
else if(state.screen==='new-done'){
html += `<div class="card" style="text-align:center;"><h1 class="title">Saved! 🎉</h1><div class="swatch ${state.item.sw}"></div><p class="itemname">${state.item.name} — ${state.item.color}</p><p class="itemmeta">Brand: ${state.brand} · Code: ${state.item.code}</p><p class="itemmeta">Saved as a draft.</p></div>`;
html += `<div class="row"><button class="btn btn-primary" onclick="startJob('new')">Do Another</button><button class="btn btn-secondary" onclick="go('home')">Home</button></div>`;
}
else if(state.screen==='update-scan'){
html += stepLabel() + `<h1 class="title">Update an Item</h1>` + cameraCard('Show me the label or pattern', "shoot(afterUpdateScan,'Looking for matches…')");
html += demoToggleHtml();
}
else if(state.screen==='update-nomatch'){
html += `<div class="card" style="text-align:center;"><h1 class="title">No Match Found</h1><p class="sub">We couldn't find a matching item.</p></div>`;
html += `<div class="row"><button class="btn btn-secondary" onclick="go('update-scan')">Try again</button><button class="btn btn-primary" onclick="go('update-typecode')">Type the code instead</button></div>`;
}
else if(state.screen==='update-typecode'){
html += `<div class="card"><h1 class="title" style="font-size:20px;">Type the Code</h1><input type="text" class="codeinput" placeholder="e.g. DW-10423" aria-label="Item code"></div>`;
html += `<button class="btn btn-primary" onclick="state.item=MOCK_ITEMS[0]; go('update-match')">Find</button>`;
}
else if(state.screen==='update-match'){
html += stepLabel();
html += maybeHint();
html += `<p class="sub">Is this it?</p>`;
html += `<div class="card swiper" id="swipeCard">
<div class="swatch ${state.item.sw}"></div>
<p class="itemname">${state.item.name} — ${state.item.color}</p>
<p class="itemmeta">code ${state.item.code}</p>
</div>`;
html += swipeButtons('Not it', "Yes, that's it");
}
else if(state.screen==='update-newphoto'){
html += stepLabel() + `<h1 class="title">New Photo</h1>` + cameraCard('Take the new photo', "shoot(()=>go('update-review'))");
}
else if(state.screen==='update-review'){
html += stepLabel();
html += maybeHint();
html += `<p class="sub">Swipe right to confirm</p>`;
html += `<div class="card swiper" id="swipeCard">
<div class="swatch ${state.item.sw}"></div>
<p class="itemname">${state.item.name} — ${state.item.color}</p>
<p class="itemmeta">code ${state.item.code}</p>
</div>`;
html += swipeButtons('Retake', 'Looks Good');
}
else if(state.screen==='update-done'){
html += `<div class="card" style="text-align:center;"><h1 class="title">Updated! ✅</h1><div class="swatch ${state.item.sw}"></div><p class="itemname">${state.item.name} — ${state.item.color}</p><p class="itemmeta">Brand: ${state.item.brand} · Code: ${state.item.code}</p><p class="itemmeta">Photo updated.</p></div>`;
html += `<div class="row"><button class="btn btn-primary" onclick="startJob('update')">Do Another</button><button class="btn btn-secondary" onclick="go('home')">Home</button></div>`;
}
else if(state.screen==='find-method'){
html += stepLabel() + `<h1 class="title">Find an Item</h1>`;
html += `<button class="btn btn-secondary" onclick="tryFindMethod()">Scan the Label</button>`;
html += `<button class="btn btn-secondary" onclick="tryFindMethod()">Say the Name</button>`;
html += `<button class="btn btn-secondary" onclick="tryFindMethod()">Type It</button>`;
html += demoToggleHtml();
}
else if(state.screen==='find-loading'){
html += `<div class="card" style="text-align:center;"><p class="sub">Looking for matches…</p></div>`;
}
else if(state.screen==='find-nomatch'){
html += `<div class="card" style="text-align:center;"><h1 class="title">No Match Found</h1><p class="sub">We couldn't find a matching item.</p></div>`;
html += `<div class="row"><button class="btn btn-secondary" onclick="go('find-method')">Try again</button><button class="btn btn-primary" onclick="go('find-typecode')">Type the code instead</button></div>`;
}
else if(state.screen==='find-typecode'){
html += `<div class="card"><h1 class="title" style="font-size:20px;">Type the Code</h1><input type="text" class="codeinput" placeholder="e.g. DW-10423" aria-label="Item code"></div>`;
html += `<button class="btn btn-primary" onclick="state.item=MOCK_ITEMS[0]; go('find-detail')">Find</button>`;
}
else if(state.screen==='find-stack'){
const it = MOCK_ITEMS[state.resultIndex % MOCK_ITEMS.length];
html += stepLabel();
html += maybeHint('Swipe right = pick this one, left = skip');
html += `<p class="sub">Swipe ✗ to skip, ✓ or tap to pick</p>`;
html += `<div class="card swiper" id="swipeCard" onclick="pickResult()" style="position:relative;">
<span class="resultlabel">${(state.resultIndex%MOCK_ITEMS.length)+1} of ${MOCK_ITEMS.length}</span>
<div class="swatch ${it.sw}"></div>
<p class="itemname">${it.name} — ${it.color}</p>
<p class="itemmeta">code ${it.code}</p>
</div>`;
html += swipeButtons('Skip', 'Pick this one');
}
else if(state.screen==='find-detail'){
html += stepLabel();
html += `<div class="card" style="text-align:center;"><h1 class="title" style="font-size:20px;">${state.item.name}</h1><div class="swatch ${state.item.sw}"></div><p class="itemmeta">${state.item.color} · code ${state.item.code} · ${state.item.brand}</p></div>`;
html += `<div class="row"><button class="btn btn-secondary" onclick="go('find-stack')">Back</button><button class="btn btn-primary" onclick="go('update-newphoto')">Update This</button></div>`;
}
else if(state.screen==='sample-scan'){
html += stepLabel() + `<h1 class="title">A Sample Came In</h1>` + cameraCard('Scan the label on the box', "shoot(afterSampleScan,'Looking for matches…')");
html += demoToggleHtml();
}
else if(state.screen==='sample-nomatch'){
html += `<div class="card" style="text-align:center;"><h1 class="title">No Match Found</h1><p class="sub">We couldn't find a matching item.</p></div>`;
html += `<div class="row"><button class="btn btn-secondary" onclick="go('sample-scan')">Try again</button><button class="btn btn-primary" onclick="go('sample-typecode')">Type the code instead</button></div>`;
}
else if(state.screen==='sample-typecode'){
html += `<div class="card"><h1 class="title" style="font-size:20px;">Type the Code</h1><input type="text" class="codeinput" placeholder="e.g. DW-10423" aria-label="Item code"></div>`;
html += `<button class="btn btn-primary" onclick="state.item=MOCK_ITEMS[0]; go('sample-match')">Find</button>`;
}
else if(state.screen==='sample-match'){
html += stepLabel();
html += maybeHint();
html += `<p class="sub">Mark Received?</p>`;
html += `<div class="card swiper" id="swipeCard">
<div class="swatch ${state.item.sw}"></div>
<p class="itemname">${state.item.name} — ${state.item.color}</p>
<p class="itemmeta">code ${state.item.code}</p>
</div>`;
html += swipeButtons('Not it, rescan', 'Mark Received');
}
else if(state.screen==='sample-printing'){
html += stepLabel();
html += `<div class="card" style="text-align:center;"><h1 class="title" style="font-size:20px;">Printing sticker…</h1><p class="sub">🖨️ One sec…</p></div>`;
}
else if(state.screen==='sample-done'){
html += `<div class="card" style="text-align:center;"><h1 class="title">Done! ✅</h1><div class="swatch ${state.item.sw}"></div><p class="itemname">${state.item.name} — ${state.item.color}</p><p class="itemmeta">Brand: ${state.item.brand} · Code: ${state.item.code}</p><p class="itemmeta">Sticker printed and item received.</p></div>`;
html += `<div class="row"><button class="btn btn-primary" onclick="startJob('sample')">Do Another</button><button class="btn btn-secondary" onclick="go('home')">Home</button></div>`;
}
html += '</div>';
s.innerHTML = html;
const swipeEl = document.getElementById('swipeCard');
if(swipeEl){
if(state.screen==='new-review'){ currentSwiper = attachSwipe(swipeEl, ()=>go('new-brand'), ()=>go('new-cam')); }
else if(state.screen==='update-match'){ currentSwiper = attachSwipe(swipeEl, ()=>go('update-newphoto'), ()=>go('update-scan')); }
else if(state.screen==='update-review'){ currentSwiper = attachSwipe(swipeEl, finishUpdate, ()=>go('update-newphoto')); }
else if(state.screen==='find-stack'){ currentSwiper = attachSwipe(swipeEl, pickResult, skipResult); }
else if(state.screen==='sample-match'){ currentSwiper = attachSwipe(swipeEl, markReceived, ()=>go('sample-scan')); }
}
}
function pickResult(){ state.item = MOCK_ITEMS[state.resultIndex % MOCK_ITEMS.length]; go('find-detail'); }
function skipResult(){ state.resultIndex++; go('find-stack'); }
function markReceived(){
state.confirmScreen = 'sample-match';
go('sample-printing');
announce('Printing sticker…');
setTimeout(()=>{ if(state.screen==='sample-printing'){ go('sample-done'); showToast('Marked Received'); } }, 900);
}
render();
</script>
</body>
</html>