← back to Dw Photo Capture
public/themes/v4-level-up.html
283 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>v4 — Level Up</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=Nunito:wght@500;700;800;900&display=swap" rel="stylesheet">
<style>
:root{
--plum:#4A2E4E; --gold:#E8B84B; --cream:#F5EFE6; --green:#59B37A;
--ink:#2B2B2B;
}
*{box-sizing:border-box;}
html,body{margin:0;padding:0;background:#ddd;font-family:'Nunito',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(--cream);
border-radius:36px; overflow:hidden; box-shadow:0 24px 60px rgba(0,0,0,.28);
display:flex; flex-direction:column; position:relative;
}
header{
background:var(--plum); color:#fff; padding:18px 20px 16px;
display:flex; align-items:center; justify-content:space-between; min-height:56px;
}
header .mark{font-family:'Fraunces',serif; font-weight:700; font-size:18px; letter-spacing:.5px;}
header .homebtn{
background:transparent;border:none;color:#fff;font-size:22px;cursor:pointer;
min-width:44px;min-height:44px;line-height:1;border-radius:12px;
}
header .homebtn:hover{background:rgba(255,255,255,.12);}
.screen{flex:1; display:flex; flex-direction:column; gap:16px; padding:22px 20px 28px; overflow-y:auto;}
h1.title{font-family:'Fraunces',serif; font-weight:700; font-size:26px; margin:0 0 2px; color:var(--plum);}
p.sub{margin:0; font-size:17px; color:#4a4a4a;}
.dots{display:flex; gap:8px; margin:4px 0 6px;}
.dots span{width:16px;height:16px;border-radius:50%;background:#e2d9c9;border:2px solid #d8cdb8;}
.dots span.filled{background:var(--gold); border-color:var(--gold);}
button{font-family:'Nunito',sans-serif; cursor:pointer;}
.btn{
min-height:56px; width:100%; border-radius:18px; font-size:18px; font-weight:800;
border:none; display:flex; align-items:center; justify-content:center; gap:8px; padding:0 16px;
}
.btn-primary{background:var(--gold); color:#3a2a06;}
.btn-primary:active{transform:scale(.98);}
.btn-secondary{background:#fff; color:var(--plum); border:2px solid var(--plum);}
.btn-ghost{background:transparent; color:var(--plum); text-decoration:underline; min-height:44px;}
.btn:focus-visible, a:focus-visible, .brandcard:focus-visible, .resultcard:focus-visible{
outline:3px solid #2B6CB0; outline-offset:2px;
}
.card{background:#fff; border-radius:20px; padding:18px; box-shadow:0 6px 18px rgba(0,0,0,.08);}
.levelcard{background:#fff; border-radius:20px; padding:18px; display:flex; flex-direction:column; gap:10px; box-shadow:0 6px 18px rgba(0,0,0,.08);}
.levelcard h2{margin:0; font-family:'Fraunces',serif; font-size:20px; color:var(--plum);}
.levelcard .startrow{display:flex; align-items:center; justify-content:space-between; gap:12px; margin-top:8px;}
.camview{
flex:1; min-height:260px; background:#1c1c1e; border-radius:20px; position:relative;
display:flex; align-items:center; justify-content:center; color:#eee; overflow:hidden;
}
.camview .hint{position:absolute; top:14px; left:0; right:0; text-align:center; font-size:15px; color:#f0e8d8;}
.camview .status{font-size:20px; font-weight:700;}
.shutter{
position:absolute; bottom:20px; left:50%; transform:translateX(-50%);
width:76px; height:76px; border-radius:50%; background:var(--gold); border:5px solid #fff;
font-size:30px; display:flex; align-items:center; justify-content:center;
}
.swatch{width:100%; height:180px; border-radius:16px;}
.sw1{background:repeating-linear-gradient(45deg,#8FAE8B 0 18px,#7c9c78 18px 36px), radial-gradient(circle at 30% 30%, #a9c7a4, transparent 60%);}
.sw2{background:repeating-linear-gradient(-45deg,#7C93B0 0 16px,#617a99 16px 32px);}
.sw3{background:repeating-linear-gradient(90deg,#D9A441 0 14px,#c98f2c 14px 28px), radial-gradient(circle at 70% 40%, #e9c06b, transparent 55%);}
.brandgrid{display:grid; grid-template-columns:1fr 1fr; gap:12px;}
.brandcard{
min-height:64px; border-radius:16px; background:#fff; border:2px solid #e3d9c6;
font-size:17px; font-weight:700; color:var(--plum); display:flex; align-items:center; justify-content:center; text-align:center; padding:8px;
}
.brandcard.selected{border-color:var(--gold); background:#fff7e6;}
.itemname{font-size:19px; font-weight:800; margin:0 0 2px;}
.itemmeta{font-size:15px; color:#666; margin:0;}
.resultlist{display:flex; flex-direction:column; gap:10px;}
.resultcard{
display:flex; gap:12px; align-items:center; background:#fff; border-radius:16px; padding:10px;
border:2px solid #eee; min-height:72px; text-align:left; width:100%;
}
.resultcard .thumb{width:52px; height:52px; border-radius:10px; flex:none;}
.confetti-banner{
background:#2F7D55; color:#fff; border-radius:18px; padding:18px; text-align:center;
font-family:'Fraunces',serif; font-size:22px; font-weight:700;
}
.row{display:flex; gap:12px;}
.row .btn{flex:1;}
.homegrid{display:flex; flex-direction:column; gap:16px;}
</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="phone">
<header>
<span class="mark">DW · Photo</span>
<button class="homebtn" aria-label="Home" onclick="go('home')" id="homeBtnTop">🏠</button>
</header>
<div class="screen" id="screen" role="main"></div>
</div>
<script>
const JOBS = [
{id:'new', title:'Add New Item', emoji:'🆕', levels:3},
{id:'update', title:'Update an Item', emoji:'🔄', levels:3},
{id:'find', title:'Find an Item', emoji:'🔍', levels:2},
{id:'sample', title:'Sample Arrived', emoji:'📦', levels:2},
];
const BRANDS = ['York Wallcoverings','Brewster Home','Kravet','Cole & Son','Thibaut','Ronald Redding'];
const MOCK_ITEMS = [
{name:'Palm Damask', color:'Sage', code:'DW-10423', sw:'sw1'},
{name:'Trellis Weave', color:'Indigo', code:'DW-88214', sw:'sw2'},
{name:'Linen Bloom', color:'Blush', code:'DW-55071', sw:'sw3'},
];
const state = {screen:'home', job:null, brand:null, item:null};
function go(screen){ state.screen = screen; render(); }
function startJob(id){ state.job = id; state.brand=null; state.item=null; go(id+'-1'); }
function jobTitle(){ return JOBS.find(j=>j.id===state.job).title; }
function jobLevels(){ return JOBS.find(j=>j.id===state.job).levels; }
function dots(filled, total){
total = total || jobLevels();
let out = '<div class="dots">';
for(let i=1;i<=total;i++) out += `<span class="${i<=filled?'filled':''}"></span>`;
out += '</div>';
return out;
}
function levelHeader(n, name){
return `<h1 class="title">${jobTitle()}</h1>${dots(n)}<p class="sub"><strong>Level ${n} of ${jobLevels()}</strong> — ${name}</p>`;
}
function cameraScreen(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 shoot(cb){
const st = document.getElementById('camStatus');
const btn = document.querySelector('.shutter');
if(btn) btn.disabled = true;
if(st) st.textContent = 'Snapping…';
const from = state.screen;
setTimeout(()=>{
if (state.screen !== from) return;
const item = MOCK_ITEMS[Math.floor(Math.random()*MOCK_ITEMS.length)];
state.item = item;
cb();
}, 700);
}
function render(){
const s = document.getElementById('screen');
let html = '';
document.getElementById('homeBtnTop').style.display = state.screen==='home' ? 'none' : 'inline-flex';
if(state.screen==='home'){
html += `<h1 class="title">Pick a Job</h1><p class="sub">Every job is a quick level path.</p>
<div class="homegrid">`;
JOBS.forEach(j=>{
html += `<div class="levelcard">
<h2>${j.emoji} ${j.title}</h2>
<p class="sub">Level 1 of ${j.levels}</p>
<div class="startrow">
${dots(0, j.levels)}
<button class="btn btn-primary" style="width:auto;min-width:110px;" onclick="startJob('${j.id}')">Start</button>
</div>
</div>`;
});
html += `</div>`;
}
else if(state.screen==='new-1'){
html += levelHeader(1,'Snap It');
html += cameraScreen('Take a photo of the sample', "shoot(()=>go('new-2'))");
}
else if(state.screen==='new-2'){
html += levelHeader(2,'Pick the Brand');
html += `<div class="brandgrid">`;
BRANDS.forEach(b=>{
html += `<button class="brandcard" onclick="state.brand='${b}'; go('new-3')">${b}</button>`;
});
html += `</div>`;
}
else if(state.screen==='new-3'){
html += levelHeader(3,'Save It');
html += `<div class="card">
<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>
</div>`;
html += `<button class="btn btn-primary" onclick="go('new-done')">Save as Draft ⭐</button>`;
}
else if(state.screen==='new-done'){
html += `<div class="confetti-banner">🎉 +10 points! Nice work</div>`;
html += `<div class="card"><p class="itemname">${state.item.name} — ${state.item.color}</p><p class="itemmeta">Saved as a draft · ${state.brand}</p></div>`;
html += `<div class="row"><button class="btn btn-secondary" onclick="startJob('new')">Do Another</button><button class="btn btn-primary" onclick="go('home')">Home</button></div>`;
}
else if(state.screen==='update-1'){
html += levelHeader(1,'Find It');
html += `<p class="sub">Scan the label, or search by typing.</p>`;
html += cameraScreen('Show me the label or pattern', "shoot(()=>go('update-2'))");
}
else if(state.screen==='update-2'){
html += levelHeader(2,'Confirm It');
html += `<div class="card">
<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 += `<div class="row"><button class="btn btn-secondary" onclick="go('update-1')">No, search again</button><button class="btn btn-primary" onclick="go('update-3')">Yes, that's it</button></div>`;
}
else if(state.screen==='update-3'){
html += levelHeader(3,'Snap the New Photo');
html += cameraScreen('Take the new photo', "shoot(()=>go('update-done'))");
}
else if(state.screen==='update-done'){
html += `<div class="confetti-banner">✅ Updated!</div>`;
html += `<div class="card"><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 += `<div class="row"><button class="btn btn-secondary" onclick="startJob('update')">Do Another</button><button class="btn btn-primary" onclick="go('home')">Home</button></div>`;
}
else if(state.screen==='find-1'){
html += levelHeader(1,'Search');
html += `<div class="row" style="flex-direction:column;">
<button class="btn btn-secondary" onclick="go('find-2')">📷 Scan</button>
<button class="btn btn-secondary" onclick="go('find-2')">🎤 Voice</button>
<button class="btn btn-secondary" onclick="go('find-2')">⌨️ Type</button>
</div>`;
}
else if(state.screen==='find-2'){
html += levelHeader(2,'Pick It');
html += `<div class="resultlist">`;
MOCK_ITEMS.forEach((it,i)=>{
html += `<button class="resultcard" onclick="state.item=MOCK_ITEMS[${i}]; go('find-done')">
<div class="thumb ${it.sw}"></div>
<div><p class="itemname" style="font-size:17px;">${it.name} — ${it.color}</p><p class="itemmeta">code ${it.code}</p></div>
</button>`;
});
html += `</div>`;
}
else if(state.screen==='find-done'){
html += `<h1 class="title">Found It</h1>`;
html += `<div class="card"><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 += `<div class="row"><button class="btn btn-secondary" onclick="startJob('find')">Do Another</button><button class="btn btn-primary" onclick="go('home')">Home</button></div>`;
}
else if(state.screen==='sample-1'){
html += levelHeader(1,'Scan the Box');
html += cameraScreen('Scan the label on the box', "shoot(()=>go('sample-2'))");
}
else if(state.screen==='sample-2'){
html += levelHeader(2,'Confirm & Print');
html += `<div class="card"><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 += `<button class="btn btn-primary" id="markReceivedBtn" onclick="markReceived()">Mark It Received</button>`;
}
else if(state.screen==='sample-done'){
html += `<div class="confetti-banner">🖨️ Sticker printed! +10 points</div>`;
html += `<div class="row"><button class="btn btn-secondary" onclick="startJob('sample')">Do Another</button><button class="btn btn-primary" onclick="go('home')">Home</button></div>`;
}
s.innerHTML = html;
}
function markReceived(){
const btn = document.getElementById('markReceivedBtn');
if(btn){ btn.disabled = true; btn.textContent = 'Printing…'; }
const from = state.screen; setTimeout(()=>{ if (state.screen === from) go('sample-done'); }, 700);
}
render();
</script>
</body>
</html>