← 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>