← back to Dw Photo Capture

public/kid-simple/v6-one-thing.html

461 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>v6 — One Thing At A Time</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{ --bg:#FBF9F6; --ink:#232323; --gold:#C9A24B; --taupe:#8A8074; --taupe-text:#6B6156; --green:#3FA66B; }
  *{box-sizing:border-box;}
  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(--bg); border-radius:36px; overflow:hidden;
    box-shadow:0 24px 60px rgba(0,0,0,.28); display:flex; flex-direction:column; position:relative;
  }
  .screen{ flex:1; display:flex; flex-direction:column; padding:36px 26px 30px; overflow-y:auto; }
  .stepline{ text-align:center; font-size:14px; font-weight:800; letter-spacing:.03em; text-transform:uppercase; color:var(--taupe-text); margin:0 0 8px; }
  .question{
    font-family:'Fraunces',serif; font-weight:600; font-size:34px; line-height:1.18;
    text-align:center; margin:6px 0 30px; color:var(--ink);
  }
  .answers{ flex:1; display:flex; flex-direction:column; gap:16px; }
  button{ font-family:'Inter',sans-serif; cursor:pointer; }
  .bigbtn{
    width:100%; min-height:64px; border-radius:18px; font-size:19px; font-weight:700;
    background:#fff; border:2px solid #e7e1d6; color:var(--ink); padding:12px 18px;
  }
  .bigbtn:active{transform:scale(.97);}
  .bigbtn-primary{ background:var(--gold); border:2px solid var(--gold); color:#3a2a06; }
  .bigbtn-secondary{ background:#fff; border:2px solid var(--taupe); color:var(--taupe-text); }
  .bigbtn:focus-visible, .linktext:focus-visible, .navfab:focus-visible, .demotoggle:focus-visible,
  .codeinput:focus-visible, .searchbox:focus-visible, .toastundo:focus-visible{ outline:3px solid #2B6CB0; outline-offset:2px; }
  .linktext{
    background:none; border:none; color:var(--taupe-text); font-size:17px; text-decoration:underline;
    text-align:center; min-height:44px; width:100%;
  }
  .brandlist{ display:flex; flex-direction:column; gap:12px; overflow-y:auto; max-height:420px; }
  .searchbox, .codeinput{
    width:100%; min-height:56px; border-radius:16px; border:2px solid #e7e1d6; padding:0 16px;
    font-size:17px; font-family:'Inter',sans-serif; background:#fff; color:var(--ink); margin-bottom:6px;
  }
  .sectionlabel{ margin:14px 0 0; font-size:14px; font-weight:800; letter-spacing:.04em; text-transform:uppercase; color:var(--taupe-text); }
  .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; margin-top:10px;
  }
  .demotoggle:active{transform:scale(.97);}
  .demotoggle-dark{
    position:absolute; top:96px; left:20px; right:20px; text-align:center;
    background:rgba(255,255,255,.14); border:2px dashed rgba(255,255,255,.45); color:#f5efe6;
    font-size:17px; font-weight:700; border-radius:12px; padding:8px 12px; min-height:56px;
  }
  .demotoggle-dark:active{transform:scale(.97);}
  .swatch{ width:100%; height:170px; border-radius:18px; margin-bottom:8px; }
  .swatch.small{ height:120px; }
  .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);}
  .itemname{ font-size:20px; font-weight:800; margin:0; text-align:center; }
  .itemmeta{ font-size:17px; color:#666; margin:4px 0 0; text-align:center; }
  .camwrap{ flex:1; display:flex; flex-direction:column; padding:0; margin:-36px -26px -30px; }
  .camview{
    flex:1; background:#1c1c1e; position:relative; display:flex; align-items:center; justify-content:center;
    color:#fff; min-height:520px;
  }
  .camview .hint{
    position:absolute; top:40px; left:20px; right:20px; text-align:center;
    font-family:'Fraunces',serif; font-size:28px; font-weight:600; color:#fdfbf7;
  }
  .camview .status{ font-size:19px; font-weight:700; color:#f0e8d8; }
  .shutter{
    position:absolute; bottom:34px; left:50%; transform:translateX(-50%);
    width:90px; height:90px; border-radius:50%; background:var(--gold); border:6px solid #fff;
    font-size:34px; display:flex; align-items:center; justify-content:center;
  }
  .shutter:active{transform:translateX(-50%) scale(.97);}
  .center{ display:flex; flex-direction:column; align-items:center; justify-content:center; flex:1; gap:10px; }
  .navcluster{
    position:absolute; top:14px; left:14px; z-index:20; display:none; gap:8px;
  }
  .navfab{
    min-width:56px; min-height:56px; border-radius:16px; background:rgba(255,255,255,.94); border:2px solid #e7e1d6; color:var(--ink);
    font-size:15px; font-weight:700; display:flex; align-items:center; justify-content:center; gap:4px;
    padding:0 12px; box-shadow:0 4px 10px rgba(0,0,0,.12);
  }
  .navfab:active{transform:scale(.97);}
  .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:16px; 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">
  <div class="navcluster" id="navCluster">
    <button class="navfab" aria-label="Back" onclick="goBack()">‹ Back</button>
    <button class="navfab" aria-label="Home" onclick="go('home')">⌂ Home</button>
  </div>
  <div class="screen" id="screen" role="main"></div>
  <div class="toast" id="toast" role="status"></div>
  <div class="sr-only" id="live" aria-live="polite"></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 DONE_TOAST = {'new-done':'Saved','update-done':'Updated','sample-done':'Marked Received'};
const STEP_MAP = {
  'new-brand':[2,3], 'new-save':[3,3],
  'update-match':[2,3],
  'find-method':[1,3], 'find-result':[2,3], 'find-detail':[3,3],
  'sample-match':[2,2],
};
const PHOTOCHECK_STEP = { 'new-cam':[1,3], 'update-cam2':[3,3] };

const state = { screen:'home', job:null, item:null, brand:null, resultIndex:0, demoNoMatch:false, checkNext:null, checkRetake:null, confirmScreen:null };
let historyStack = [];
let toastTimer = null;

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;
  const map = {new:'new-cam', update:'update-cam', find:'find-method', sample:'sample-cam'};
  go(map[job]);
}
function randomItem(){ return MOCK_ITEMS[Math.floor(Math.random()*MOCK_ITEMS.length)]; }
function stepLine(){ const m = STEP_MAP[state.screen]; return m ? `<p class="stepline">Step ${m[0]} of ${m[1]}</p>` : ''; }
function toggleDemo(){ state.demoNoMatch = !state.demoNoMatch; render(); }
function demoToggleHtml(dark){
  const cls = dark ? 'demotoggle-dark' : 'demotoggle';
  return `<button class="${cls}" aria-pressed="${state.demoNoMatch}" onclick="toggleDemo()">Demo: show "no match" ${state.demoNoMatch?'✅ On':'⬜ Off'}</button>`;
}

function cameraScreen(hint, onShoot, extra){
  return `<div class="camwrap"><div class="camview">
    <div class="hint">${hint}</div>
    <div class="status" id="camStatus">Tap the shutter</div>
    ${extra||''}
    <button class="shutter" aria-label="Take photo" onclick="${onShoot}">📸</button>
  </div></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 shootThen(nextScreen, retakeScreen){
  shoot(()=>{ state.checkNext=nextScreen; state.checkRetake=retakeScreen; go('photocheck'); });
}
function shootOrMiss(nextScreen, retakeScreen, missScreen, loadingLabel){
  shoot(()=>{
    if(state.demoNoMatch){ go(missScreen); return; }
    go(nextScreen);
  }, loadingLabel);
}
function performAction(next, toastMsg){
  state.confirmScreen = state.screen;
  go(next);
  if(toastMsg) showToast(toastMsg);
}
function finishCheck(){ performAction(state.checkNext, DONE_TOAST[state.checkNext]); }
function markReceived(){
  state.confirmScreen = state.screen;
  go('sample-printing');
  announce('Printing sticker…');
}
function tryFind(){
  go('find-loading');
  announce('Looking for matches…');
  setTimeout(()=>{ if(state.screen==='find-loading') goReplace(state.demoNoMatch ? 'find-nomatch' : 'find-result'); }, 600);
}
function filterBrands(q){
  q = q.trim().toLowerCase();
  const recentWrap = document.getElementById('brandRecent');
  if(recentWrap) recentWrap.style.display = q ? 'none' : '';
  const full = document.getElementById('brandFullList');
  if(!full) return;
  full.querySelectorAll('button').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 brandListHtml(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="brandlist">`;
  RECENT_BRANDS.forEach(b=>{ html += `<button class="bigbtn" onclick="state.brand='${b}'; go('${nextScreen}')">${b}</button>`; });
  html += `</div></div>`;
  html += `<p class="sectionlabel">All Brands</p><div class="brandlist" id="brandFullList">`;
  BRANDS.forEach(b=>{ html += `<button class="bigbtn" onclick="state.brand='${b}'; go('${nextScreen}')">${b}</button>`; });
  html += `</div>`;
  return html;
}

function render(){
  const s = document.getElementById('screen');
  const nav = document.getElementById('navCluster');
  nav.style.display = state.screen==='home' ? 'none' : 'flex';
  let html = '';

  if(state.screen==='home'){
    html += `<h1 class="question">What do you need to do?</h1>
      <div class="answers">
        <button class="bigbtn" onclick="startJob('new')">Add New Item</button>
        <button class="bigbtn" onclick="startJob('update')">Update an Item</button>
        <button class="bigbtn" onclick="startJob('find')">Find an Item</button>
        <button class="bigbtn" onclick="startJob('sample')">A Sample Came In</button>
      </div>`;
  }

  else if(state.screen==='photocheck'){
    const st = PHOTOCHECK_STEP[state.checkRetake];
    html += st ? `<p class="stepline">Step ${st[0]} of ${st[1]}</p>` : '';
    html += `<h1 class="question">Does this look right?</h1>
      <div class="swatch ${state.item.sw}"></div>
      <div class="answers" style="margin-top:20px;">
        <button class="linktext" onclick="go(state.checkRetake)">Retake Photo</button>
        <button class="bigbtn bigbtn-primary" onclick="finishCheck()">Looks Good</button>
      </div>`;
  }

  else if(state.screen==='new-cam'){
    html += cameraScreen('Take a photo of it.', "shootThen('new-brand','new-cam')");
  }
  else if(state.screen==='new-brand'){
    html += stepLine() + `<h1 class="question">Which brand is it?</h1>` + brandListHtml('new-save');
  }
  else if(state.screen==='new-save'){
    html += stepLine();
    html += `<h1 class="question">Save it?</h1>
      <div class="swatch small ${state.item.sw}"></div>
      <p class="itemmeta">${state.brand} · code ${state.item.code}</p>
      <div class="answers" style="margin-top:20px;">
        <button class="linktext" onclick="go('new-cam')">Retake Photo</button>
        <button class="bigbtn bigbtn-primary" onclick="performAction('new-done','Saved')">Save as Draft</button>
      </div>`;
  }
  else if(state.screen==='new-done'){
    html += `<div class="center">
        <h1 class="question">Saved! 🎉</h1>
        <div class="swatch small ${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>
      <div class="answers">
        <button class="bigbtn bigbtn-primary" onclick="startJob('new')">Do Another</button>
        <button class="linktext" onclick="go('home')">Home</button>
      </div>`;
  }

  else if(state.screen==='update-cam'){
    html += cameraScreen('Scan the label or pattern.', "shootOrMiss('update-match','update-cam','update-nomatch','Looking for matches…')", demoToggleHtml(true));
  }
  else if(state.screen==='update-nomatch'){
    html += `<h1 class="question">No match found.</h1>
      <div class="answers">
        <button class="bigbtn" onclick="go('update-cam')">Try again</button>
        <button class="bigbtn bigbtn-primary" onclick="go('update-typecode')">Type the code instead</button>
      </div>`;
  }
  else if(state.screen==='update-typecode'){
    html += `<h1 class="question">Type the code.</h1>
      <input type="text" class="codeinput" placeholder="e.g. DW-10423" aria-label="Item code">
      <div class="answers" style="margin-top:20px;">
        <button class="bigbtn bigbtn-primary" onclick="state.item=MOCK_ITEMS[0]; go('update-match')">Find</button>
      </div>`;
  }
  else if(state.screen==='update-match'){
    html += stepLine();
    html += `<h1 class="question">Is this it?</h1>
      <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 class="answers" style="margin-top:20px;">
        <button class="bigbtn bigbtn-secondary" onclick="go('update-cam')">No, Let Me Search</button>
        <button class="bigbtn bigbtn-primary" onclick="go('update-cam2')">Yes</button>
      </div>`;
  }
  else if(state.screen==='update-cam2'){
    html += cameraScreen('Take the new photo.', "shootThen('update-done','update-cam2')");
  }
  else if(state.screen==='update-done'){
    html += `<div class="center">
        <h1 class="question">Updated! ✅</h1>
        <div class="swatch small ${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>
      </div>
      <div class="answers">
        <button class="bigbtn bigbtn-primary" onclick="startJob('update')">Do Another</button>
        <button class="linktext" onclick="go('home')">Home</button>
      </div>`;
  }

  else if(state.screen==='find-method'){
    html += stepLine();
    html += `<h1 class="question">How do you want to find it?</h1>
      <div class="answers">
        <button class="bigbtn" onclick="tryFind()">Scan</button>
        <button class="bigbtn" onclick="tryFind()">Talk</button>
        <button class="bigbtn" onclick="tryFind()">Type</button>
      </div>`;
    html += demoToggleHtml(false);
  }
  else if(state.screen==='find-loading'){
    html += `<div class="center"><h1 class="question">Looking for matches…</h1></div>`;
  }
  else if(state.screen==='find-nomatch'){
    html += `<h1 class="question">No match found.</h1>
      <div class="answers">
        <button class="bigbtn" onclick="go('find-method')">Try again</button>
        <button class="bigbtn bigbtn-primary" onclick="go('find-typecode')">Type the code instead</button>
      </div>`;
  }
  else if(state.screen==='find-typecode'){
    html += `<h1 class="question">Type the code.</h1>
      <input type="text" class="codeinput" placeholder="e.g. DW-10423" aria-label="Item code">
      <div class="answers" style="margin-top:20px;">
        <button class="bigbtn bigbtn-primary" onclick="state.item=MOCK_ITEMS[0]; go('find-detail')">Find</button>
      </div>`;
  }
  else if(state.screen==='find-result'){
    const it = MOCK_ITEMS[state.resultIndex];
    html += stepLine();
    html += `<h1 class="question">Which one is it?</h1>
      <div class="swatch ${it.sw}"></div>
      <p class="itemname">${it.name} — ${it.color}</p>
      <p class="itemmeta">code ${it.code}</p>
      <div class="answers" style="margin-top:20px;">
        <button class="bigbtn bigbtn-secondary" onclick="nextResult()">Next Result</button>
        <button class="bigbtn bigbtn-primary" onclick="pickResult()">Yes, This One</button>
      </div>`;
  }
  else if(state.screen==='find-exhausted'){
    html += `<h1 class="question">That's all — try another way?</h1>
      <div class="answers">
        <button class="bigbtn" onclick="state.resultIndex=0; go('find-result')">Scan</button>
        <button class="bigbtn" onclick="state.resultIndex=0; go('find-result')">Talk</button>
        <button class="bigbtn" onclick="state.resultIndex=0; go('find-result')">Type</button>
      </div>`;
  }
  else if(state.screen==='find-detail'){
    html += stepLine();
    html += `<div class="center">
        <h1 class="question" style="font-size:26px; margin-bottom:14px;">${state.item.name}</h1>
        <div class="swatch ${state.item.sw}" style="width:260px;"></div>
        <p class="itemmeta">${state.item.color} · code ${state.item.code} · ${state.item.brand}</p>
      </div>
      <div class="answers">
        <button class="linktext" onclick="go('home')">Home</button>
        <button class="bigbtn bigbtn-secondary" onclick="go('find-method')">Search Again</button>
        <button class="bigbtn bigbtn-primary" onclick="go('update-cam2')">Update This</button>
      </div>`;
  }

  else if(state.screen==='sample-cam'){
    html += cameraScreen('Scan the box label.', "shootOrMiss('sample-match','sample-cam','sample-nomatch','Looking for matches…')", demoToggleHtml(true));
  }
  else if(state.screen==='sample-nomatch'){
    html += `<h1 class="question">No match found.</h1>
      <div class="answers">
        <button class="bigbtn" onclick="go('sample-cam')">Try again</button>
        <button class="bigbtn bigbtn-primary" onclick="go('sample-typecode')">Type the code instead</button>
      </div>`;
  }
  else if(state.screen==='sample-typecode'){
    html += `<h1 class="question">Type the code.</h1>
      <input type="text" class="codeinput" placeholder="e.g. DW-10423" aria-label="Item code">
      <div class="answers" style="margin-top:20px;">
        <button class="bigbtn bigbtn-primary" onclick="state.item=MOCK_ITEMS[0]; go('sample-match')">Find</button>
      </div>`;
  }
  else if(state.screen==='sample-match'){
    html += stepLine();
    html += `<h1 class="question">Mark it received?</h1>
      <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 class="answers" style="margin-top:20px;">
        <button class="bigbtn bigbtn-secondary" onclick="go('sample-cam')">No, Scan Again</button>
        <button class="bigbtn bigbtn-primary" onclick="markReceived()">Yes, Mark Received</button>
      </div>`;
  }
  else if(state.screen==='sample-printing'){
    html += `<div class="center"><h1 class="question">Printing your sticker…</h1><p class="itemmeta">🖨️ One sec…</p></div>`;
    setTimeout(()=>{ if(state.screen==='sample-printing'){ go('sample-done'); showToast('Marked Received'); } }, 1100);
  }
  else if(state.screen==='sample-done'){
    html += `<div class="center">
        <h1 class="question">Done! ✅</h1>
        <div class="swatch small ${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>
      <div class="answers">
        <button class="bigbtn bigbtn-primary" onclick="startJob('sample')">Do Another</button>
        <button class="linktext" onclick="go('home')">Home</button>
      </div>`;
  }

  s.innerHTML = html;
}
function pickResult(){ state.item = MOCK_ITEMS[state.resultIndex]; go('find-detail'); }
function nextResult(){
  if(state.resultIndex < MOCK_ITEMS.length-1){ state.resultIndex++; go('find-result'); }
  else { go('find-exhausted'); }
}

render();
</script>
</body>
</html>