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