← back to Dw Photo Capture

public/kid-simple/v13-sticker-book.html

410 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>v13 — Sticker Book</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=DM+Serif+Display:ital@0;1&family=Poppins:wght@400;500;600;700;800&family=Caveat:wght@600;700&display=swap" rel="stylesheet">
<style>
  :root{
    --bg:#EFE3CF;        /* kraft paper */
    --paper:#FBF6EC;     /* cream card */
    --ink:#2E2620;       /* deep coffee */
    --muted:#8C7C68;     /* taupe */
    --gold:#A8763A;      /* brass CTA */
    --teal:#5F9A91;      /* New Item sticker */
    --rose:#C97B6B;      /* Update sticker */
    --honey:#B98D3E;     /* Find sticker */
    --olive:#86A466;     /* Sample sticker */
    --green:#4C9A6A;     /* success */
    --cam:#2E2620;       /* camera bg */
  }
  *{box-sizing:border-box;}
  html,body{margin:0;padding:0;background:#ccc;font-family:'Poppins',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);
    background-image:
      radial-gradient(rgba(255,255,255,.35) 1px, transparent 1px);
    background-size:14px 14px;
    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:26px 22px 26px; overflow-y:auto; position:relative; }

  /* ---- home ---- */
  .wordmark{ font-family:'DM Serif Display', serif; font-size:13px; letter-spacing:.14em; color:var(--muted); text-transform:uppercase; margin:0 0 6px; }
  .title{ font-family:'DM Serif Display', serif; font-size:32px; margin:0 0 4px; color:var(--ink); }
  .subtitle{ font-family:'Caveat', cursive; font-weight:700; font-size:20px; color:var(--gold); margin:0 0 14px; }

  .booth{
    background:var(--cam); border-radius:22px; padding:18px 16px 54px; position:relative;
    display:flex; flex-direction:column; align-items:center; margin-bottom:34px;
  }
  .lens{
    width:74px; height:74px; border-radius:50%; background:radial-gradient(circle at 35% 32%, #6b6660, #17130f 70%);
    border:5px solid #46403a; margin-bottom:10px;
  }
  .boothtext{ color:#F3ECDD; font-size:14px; font-weight:500; text-align:center; margin:0 0 2px; }
  .boothhint{ color:#c9bfae; font-size:12px; text-align:center; margin:0; }
  .shutter{
    position:absolute; left:50%; bottom:-30px; transform:translateX(-50%);
    width:72px; height:72px; border-radius:50%; background:var(--gold); border:5px solid var(--paper);
    font-size:28px; display:flex; align-items:center; justify-content:center; box-shadow:0 8px 16px rgba(0,0,0,.3);
    cursor:pointer;
  }
  .shutter:focus-visible{ outline:3px solid #2B6CB0; outline-offset:3px; }

  .dropzone{ min-height:98px; display:flex; align-items:center; justify-content:center; margin-bottom:10px; }
  .dropzone .placeholder{
    font-family:'Caveat', cursive; font-size:18px; color:var(--muted); text-align:center; padding:0 20px;
    border:2px dashed #cfc2ab; border-radius:14px; width:100%; min-height:70px; display:flex; align-items:center; justify-content:center;
  }
  .polaroid{
    width:104px; background:#fff; padding:8px 8px 16px; border-radius:5px; box-shadow:0 10px 18px rgba(0,0,0,.28);
    transform:rotate(-6deg); touch-action:none; user-select:none; cursor:grab; position:relative; z-index:5;
  }
  .polaroid.dragging{ cursor:grabbing; transform:rotate(0deg) scale(1.06); }
  .polaroid .shot{ width:100%; height:82px; border-radius:2px; }
  .polaroid .cap{ font-family:'Caveat', cursive; font-weight:700; font-size:14px; text-align:center; color:#3a3a3a; margin-top:5px; }
  .ps1{background:repeating-linear-gradient(45deg,#8FAE8B 0 12px,#7c9c78 12px 24px);}
  .ps2{background:repeating-linear-gradient(-45deg,#7C93B0 0 11px,#617a99 11px 22px);}
  .ps3{background:repeating-linear-gradient(90deg,#D9A441 0 10px,#c98f2c 10px 20px);}

  .stickerlabel{ font-size:13px; font-weight:700; color:var(--muted); text-transform:uppercase; letter-spacing:.08em; margin:0 0 10px; text-align:center; }
  .stickers{ display:grid; grid-template-columns:1fr 1fr; gap:16px; }
  .sticker{
    aspect-ratio:1/1; border-radius:50%; display:flex; flex-direction:column; align-items:center; justify-content:center;
    gap:4px; color:#fff; font-family:'Poppins',sans-serif; font-weight:700; font-size:14px; text-align:center;
    border:4px dashed rgba(255,255,255,.55); box-shadow:0 10px 18px rgba(0,0,0,.18), inset 0 0 0 6px rgba(255,255,255,.12);
    cursor:pointer; position:relative; min-height:56px; min-width:56px; padding:8px; transition:transform .15s, box-shadow .15s;
    -webkit-tap-highlight-color:transparent;
  }
  .sticker .emoji{ font-size:32px; }
  .sticker:nth-child(1){ background:var(--teal); transform:rotate(-4deg); }
  .sticker:nth-child(2){ background:var(--rose); transform:rotate(3deg); }
  .sticker:nth-child(3){ background:var(--honey); transform:rotate(3deg); }
  .sticker:nth-child(4){ background:var(--olive); transform:rotate(-3deg); }
  .sticker.dropready{ transform:scale(1.1) rotate(0deg) !important; box-shadow:0 0 0 6px rgba(255,255,255,.55), 0 16px 24px rgba(0,0,0,.3); }
  .sticker:focus-visible{ outline:3px solid #2B6CB0; outline-offset:4px; }

  /* ---- shared flow screens ---- */
  .question{ font-family:'DM Serif Display', serif; font-size:28px; line-height:1.2; text-align:center; margin:6px 0 24px; color:var(--ink); }
  .answers{ flex:1; display:flex; flex-direction:column; gap:14px; }
  button{ font-family:'Poppins',sans-serif; cursor:pointer; }
  .bigbtn{
    width:100%; min-height:60px; border-radius:16px; font-size:17px; font-weight:700;
    background:var(--paper); border:2px solid #e2d6c0; color:var(--ink); padding:10px 16px;
  }
  .bigbtn-primary{ background:var(--gold); border:2px solid var(--gold); color:#fff; }
  .bigbtn-secondary{ background:var(--paper); border:2px solid var(--muted); color:var(--muted); }
  .bigbtn:focus-visible, .linktext:focus-visible{ outline:3px solid #2B6CB0; outline-offset:2px; }
  .linktext{
    background:none; border:none; color:var(--muted); font-size:16px; text-decoration:underline;
    text-align:center; min-height:56px; width:100%;
  }
  .brandlist{ display:flex; flex-direction:column; gap:12px; overflow-y:auto; }
  .swatch{ width:100%; height:150px; border-radius:16px; margin-bottom:8px; }
  .swatch.small{ height:110px; }
  .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:19px; font-weight:800; margin:0; text-align:center; }
  .itemmeta{ font-size:15px; color:var(--muted); margin:4px 0 0; text-align:center; }

  .camwrap{ flex:1; display:flex; flex-direction:column; padding:0; margin:-26px -22px -26px; }
  .camview{
    flex:1; background:var(--cam); position:relative; display:flex; align-items:center; justify-content:center;
    color:#fff; min-height:480px;
  }
  .camview .hint{
    position:absolute; top:36px; left:20px; right:20px; text-align:center;
    font-family:'DM Serif Display', serif; font-size:25px; color:#F3ECDD;
  }
  .camview .status{ font-size:16px; font-weight:600; color:#d9cdb8; }
  .camview .shutter2{
    position:absolute; bottom:32px; left:50%; transform:translateX(-50%);
    width:82px; height:82px; border-radius:50%; background:var(--gold); border:6px solid #fff;
    font-size:30px; display:flex; align-items:center; justify-content:center; box-shadow:0 8px 16px rgba(0,0,0,.3);
  }
  .center{ display:flex; flex-direction:column; align-items:center; justify-content:center; flex:1; gap:10px; }
  .homefab{
    position:absolute; top:14px; left:14px; z-index:20; min-width:56px; min-height:56px;
    border-radius:16px; background:rgba(255,255,255,.94); border:2px solid #e2d6c0; color:var(--ink);
    font-size:14px; font-weight:700; display:none; align-items:center; justify-content:center; gap:4px;
    padding:0 12px; box-shadow:0 4px 10px rgba(0,0,0,.15);
  }
  .homefab:focus-visible{ outline:3px solid #2B6CB0; outline-offset:2px; }
</style>
</head>
<body>
<div class="phone">
  <button class="homefab" id="homeFab" aria-label="Home" onclick="go('home')">⌂ Home</button>
  <div class="screen" id="screen" role="main"></div>
</div>

<script>
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 BRANDS = ['York Wallcoverings','Brewster Home','Kravet','Cole & Son','Thibaut','Ronald Redding'];
const POLA_SWATCH = ['ps1','ps2','ps3'];

const state = { screen:'home', job:null, item:null, brand:null, resultIndex:0, photoTaken:false, polaSwatch:'ps1' };

function go(screen){ state.screen = screen; render(); }
function randomItem(){ return MOCK_ITEMS[Math.floor(Math.random()*MOCK_ITEMS.length)]; }

function shootHome(){
  state.photoTaken = true;
  state.polaSwatch = POLA_SWATCH[Math.floor(Math.random()*POLA_SWATCH.length)];
  render();
}

function startJobFromSticker(job, hasPhoto){
  if(hasPhoto){ state.item = randomItem(); }
  state.photoTaken = false;
  state.job = job; state.brand = null; state.resultIndex = 0;
  const jumpMap = { new:'new-brand', update:'update-match', find:'find-result', sample:'sample-match' };
  const startMap = { new:'new-cam', update:'update-cam', find:'find-method', sample:'sample-cam' };
  go(hasPhoto ? jumpMap[job] : startMap[job]);
}
function stickerTap(job){
  const hadPhoto = state.photoTaken;
  startJobFromSticker(job, hadPhoto);
}

/* ---- drag the polaroid onto a sticker ---- */
let dragEl = null, dragStartX = 0, dragStartY = 0, dragElLeft = 0, dragElTop = 0;
function onPolaroidPointerDown(e){
  dragEl = e.currentTarget;
  const rect = dragEl.getBoundingClientRect();
  dragElLeft = rect.left; dragElTop = rect.top;
  dragStartX = e.clientX; dragStartY = e.clientY;
  dragEl.setPointerCapture(e.pointerId);
  dragEl.classList.add('dragging');
  dragEl.style.position = 'fixed';
  dragEl.style.left = dragElLeft + 'px';
  dragEl.style.top = dragElTop + 'px';
  dragEl.style.margin = '0';
  window.addEventListener('pointermove', onPolaroidMove);
  window.addEventListener('pointerup', onPolaroidUp);
}
function stickerUnderPoint(x, y){
  const el = document.elementFromPoint(x, y);
  return el && el.closest ? el.closest('.sticker') : null;
}
function onPolaroidMove(e){
  if(!dragEl) return;
  const dx = e.clientX - dragStartX;
  const dy = e.clientY - dragStartY;
  dragEl.style.left = (dragElLeft + dx) + 'px';
  dragEl.style.top = (dragElTop + dy) + 'px';
  document.querySelectorAll('.sticker').forEach(s=>s.classList.remove('dropready'));
  const st = stickerUnderPoint(e.clientX, e.clientY);
  if(st) st.classList.add('dropready');
}
function onPolaroidUp(e){
  window.removeEventListener('pointermove', onPolaroidMove);
  window.removeEventListener('pointerup', onPolaroidUp);
  const st = stickerUnderPoint(e.clientX, e.clientY);
  document.querySelectorAll('.sticker').forEach(s=>s.classList.remove('dropready'));
  dragEl = null;
  if(st){
    startJobFromSticker(st.dataset.job, true);
  } else {
    render(); // snap back to its resting spot
  }
}

function cameraScreen(hint, onShoot){
  return `<div class="camwrap"><div class="camview">
    <div class="hint">${hint}</div>
    <div class="status" id="camStatus">Tap the shutter</div>
    <button class="shutter2" aria-label="Take photo" onclick="${onShoot}">📸</button>
  </div></div>`;
}
function shoot(cb){
  const st = document.getElementById('camStatus');
  const btn = document.querySelector('.shutter2');
  if(btn) btn.disabled = true;
  if(st) st.textContent = 'Snapping…';
  setTimeout(()=>{ state.item = randomItem(); cb(); }, 650);
}

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

  if(state.screen==='home'){
    html += `
      <p class="wordmark">Designer Wallcoverings</p>
      <h1 class="title">📖 Sticker Book</h1>
      <p class="subtitle">${state.photoTaken ? 'Drag your photo onto a sticker!' : 'Snap a photo, then stick it on a job.'}</p>

      <div class="booth">
        <div class="lens"></div>
        <p class="boothtext">Point at anything</p>
        <p class="boothhint">then tap the button below</p>
        <button class="shutter" aria-label="Take a photo" onclick="shootHome()">📸</button>
      </div>

      <div class="dropzone">
        ${state.photoTaken
          ? `<div class="polaroid ${state.polaSwatch === 'pola' ? '' : ''}" onpointerdown="onPolaroidPointerDown(event)">
               <div class="shot ${state.polaSwatch}"></div>
               <div class="cap">your photo!</div>
             </div>`
          : `<div class="placeholder">your photo shows up here ✂️</div>`}
      </div>

      <p class="stickerlabel">or just tap a sticker</p>
      <div class="stickers">
        <div class="sticker" data-job="new" tabindex="0" role="button" aria-label="Add a New Item"
             onclick="stickerTap('new')" onkeydown="if(event.key==='Enter')stickerTap('new')">
          <span class="emoji">🆕</span><span>New Item</span>
        </div>
        <div class="sticker" data-job="update" tabindex="0" role="button" aria-label="Update an Item"
             onclick="stickerTap('update')" onkeydown="if(event.key==='Enter')stickerTap('update')">
          <span class="emoji">🔄</span><span>Update</span>
        </div>
        <div class="sticker" data-job="find" tabindex="0" role="button" aria-label="Find an Item"
             onclick="stickerTap('find')" onkeydown="if(event.key==='Enter')stickerTap('find')">
          <span class="emoji">🔍</span><span>Find It</span>
        </div>
        <div class="sticker" data-job="sample" tabindex="0" role="button" aria-label="A Sample Came In"
             onclick="stickerTap('sample')" onkeydown="if(event.key==='Enter')stickerTap('sample')">
          <span class="emoji">📦</span><span>Sample In</span>
        </div>
      </div>`;
  }

  /* ---- Add New Item ---- */
  else if(state.screen==='new-cam'){
    html += cameraScreen('Take a photo of it.', "shoot(()=>go('new-brand'))");
  }
  else if(state.screen==='new-brand'){
    html += `<h1 class="question">Which brand is it?</h1><div class="brandlist">`;
    BRANDS.forEach(b=>{ html += `<button class="bigbtn" onclick="state.brand='${b}'; go('new-save')">${b}</button>`; });
    html += `</div>`;
  }
  else if(state.screen==='new-save'){
    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:18px;">
        <button class="bigbtn bigbtn-primary" onclick="go('new-done')">Save as Draft</button>
        <button class="linktext" onclick="go('new-cam')">Retake Photo</button>
      </div>`;
  }
  else if(state.screen==='new-done'){
    html += `<div class="center"><h1 class="question">Saved! 🎉</h1></div>
      <div class="answers">
        <button class="bigbtn bigbtn-primary" onclick="go('new-cam')">Add Another</button>
        <button class="linktext" onclick="go('home')">Home</button>
      </div>`;
  }

  /* ---- Update an Item ---- */
  else if(state.screen==='update-cam'){
    html += cameraScreen('Scan the label or pattern.', "shoot(()=>go('update-match'))");
  }
  else if(state.screen==='update-match'){
    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:18px;">
        <button class="bigbtn bigbtn-primary" onclick="go('update-cam2')">Yes</button>
        <button class="bigbtn bigbtn-secondary" onclick="go('update-cam')">No, Let Me Search</button>
      </div>`;
  }
  else if(state.screen==='update-cam2'){
    html += cameraScreen('Take the new photo.', "shoot(()=>go('update-done'))");
  }
  else if(state.screen==='update-done'){
    html += `<div class="center"><h1 class="question">Updated! ✅</h1></div>
      <button class="bigbtn bigbtn-primary" onclick="go('home')">Back Home</button>`;
  }

  /* ---- Find an Item ---- */
  else if(state.screen==='find-method'){
    html += `<h1 class="question">How do you want to find it?</h1>
      <div class="answers">
        <button class="bigbtn" onclick="go('find-result')">📷 Scan the Label</button>
        <button class="bigbtn" onclick="go('find-result')">🎤 Say the Name</button>
        <button class="bigbtn" onclick="go('find-result')">⌨️ Type It</button>
      </div>`;
  }
  else if(state.screen==='find-result'){
    const it = MOCK_ITEMS[state.resultIndex];
    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:18px;">
        <button class="bigbtn bigbtn-primary" onclick="pickResult()">Yes, This One</button>
        <button class="bigbtn bigbtn-secondary" onclick="nextResult()">Next Result</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 the Label</button>
        <button class="bigbtn" onclick="state.resultIndex=0; go('find-result')">🎤 Say the Name</button>
        <button class="bigbtn" onclick="state.resultIndex=0; go('find-result')">⌨️ Type It</button>
      </div>`;
  }
  else if(state.screen==='find-detail'){
    html += `<div class="center">
        <h1 class="question" style="font-size:24px; margin-bottom:12px;">${state.item.name}</h1>
        <div class="swatch ${state.item.sw}" style="width:240px;"></div>
        <p class="itemmeta">${state.item.color} · code ${state.item.code}</p>
      </div>
      <div class="answers">
        <button class="bigbtn bigbtn-primary" onclick="go('update-cam2')">Update This</button>
        <button class="bigbtn bigbtn-secondary" onclick="go('find-method')">Search Again</button>
        <button class="linktext" onclick="go('home')">Home</button>
      </div>`;
  }

  /* ---- A Sample Came In ---- */
  else if(state.screen==='sample-cam'){
    html += cameraScreen('Scan the box label.', "shoot(()=>go('sample-match'))");
  }
  else if(state.screen==='sample-match'){
    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:18px;">
        <button class="bigbtn bigbtn-primary" onclick="go('sample-printing')">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'); }, 1000);
  }
  else if(state.screen==='sample-done'){
    html += `<div class="center"><h1 class="question">Done! ✅</h1></div>
      <button class="bigbtn bigbtn-primary" onclick="go('home')">Back Home</button>`;
  }

  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>