← back to Dw Photo Capture

public/kid-simple/v9-story-mode.html

387 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>v9 — Story Mode</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Playfair+Display:wght@600;700;800&family=Manrope:wght@500;600;700;800&display=swap" rel="stylesheet">
<style>
  :root{
    --parchment:#F6F1EA; --ink:#2A2320;
    --canvas:#201A1D; --cream:#FBF4EC; --muted:rgba(251,244,236,.58);
    --accent:#E0764A; --accent-ink:#3A1F12;
    --success:#6FA98A; --track:rgba(255,255,255,.22);
    --ring-new:#E0764A; --ring-update:#3E7C7C; --ring-find:#C97A96; --ring-sample:#8A9B5E;
  }
  *{box-sizing:border-box; -webkit-tap-highlight-color:transparent;}
  html,body{margin:0;padding:0;background:#ddd;font-family:'Manrope',sans-serif;color:var(--ink);}
  body{display:flex; align-items:flex-start; justify-content:center; min-height:100vh; padding:20px 0;}
  .phone{
    width:390px; height:780px; background:var(--parchment); border-radius:36px; overflow:hidden;
    box-shadow:0 24px 60px rgba(0,0,0,.28); position:relative; display:flex; flex-direction:column;
  }
  .screen{ flex:1; position:relative; overflow:hidden; }
  h1,h2{ font-family:'Playfair Display',serif; margin:0; }
  button{ font-family:'Manrope',sans-serif; cursor:pointer; }

  /* ---------- HOME (light chrome, story selector) ---------- */
  .home{ height:100%; display:flex; flex-direction:column; padding:34px 26px 30px; }
  .home-mark{ font-family:'Playfair Display',serif; font-weight:800; font-size:15px; letter-spacing:.14em; color:#8A7F6E; }
  .home-sub{ font-size:12px; letter-spacing:.16em; text-transform:uppercase; color:#B5A896; margin-top:2px; }
  .home-headline{ font-size:30px; font-weight:700; line-height:1.2; margin:26px 0 8px; color:var(--ink); }
  .home-hint{ font-size:15px; color:#8A7F6E; margin:0 0 26px; }
  .storygrid{ display:grid; grid-template-columns:1fr 1fr; gap:20px; flex:1; }
  .storytile{
    background:none; border:none; padding:0; display:flex; flex-direction:column; align-items:center;
    justify-content:flex-start; gap:12px; min-height:130px;
  }
  .ring{
    width:92px; height:92px; border-radius:50%; padding:4px; display:flex; align-items:center; justify-content:center;
  }
  .ring-inner{
    width:100%; height:100%; border-radius:50%; background:var(--canvas); display:flex; align-items:center;
    justify-content:center; font-size:34px;
  }
  .r-new{ background:conic-gradient(from -40deg, var(--ring-new), #F4C79A, var(--ring-new)); }
  .r-update{ background:conic-gradient(from -40deg, var(--ring-update), #A9D3CF, var(--ring-update)); }
  .r-find{ background:conic-gradient(from -40deg, var(--ring-find), #EFC7D4, var(--ring-find)); }
  .r-sample{ background:conic-gradient(from -40deg, var(--ring-sample), #D9E2B8, var(--ring-sample)); }
  .storytile .label{ font-size:14.5px; font-weight:700; color:var(--ink); text-align:center; line-height:1.25; }
  .storytile:focus-visible .ring-inner{ outline:3px solid #2B6CB0; outline-offset:2px; }

  /* ---------- STORY (dark canvas) ---------- */
  .story{ position:absolute; inset:0; background:var(--canvas); display:flex; flex-direction:column; color:var(--cream); }
  .story.cam{ background:radial-gradient(120% 90% at 50% 0%, #2c2428 0%, #171214 75%); }
  .progressrow{ display:flex; gap:6px; padding:16px 16px 0; z-index:30; }
  .seg{ flex:1; height:4px; border-radius:3px; background:var(--track); overflow:hidden; }
  .seg i{ display:block; height:100%; width:0%; background:var(--accent); border-radius:3px; }
  .seg.done i{ width:100%; }
  .seg.now i{ width:100%; }
  .storytop{ display:flex; align-items:center; justify-content:space-between; padding:10px 10px 0 16px; z-index:30; }
  .storytop .who{ font-size:12.5px; font-weight:700; letter-spacing:.08em; text-transform:uppercase; color:var(--muted); }
  .exitbtn{
    width:56px; height:56px; border-radius:50%; background:rgba(0,0,0,.28); border:none; color:var(--cream);
    font-size:20px; display:flex; align-items:center; justify-content:center;
  }
  .exitbtn:focus-visible{ outline:3px solid #7EB6E8; outline-offset:2px; }

  .tapzone{ position:absolute; top:64px; bottom:150px; width:17%; background:none; border:none; z-index:5; padding:0; }
  .tapzone.left{ left:0; }
  .tapzone.right{ right:0; }
  .tapzone .chev{ position:absolute; top:50%; transform:translateY(-50%); font-size:22px; color:rgba(255,255,255,.30); }
  .tapzone.left .chev{ left:8px; }
  .tapzone.right .chev{ right:8px; }

  .framebody{ flex:1; display:flex; flex-direction:column; align-items:center; justify-content:center; padding:10px 30px; text-align:center; position:relative; z-index:8; gap:10px; }
  .framebody .cue{ font-size:12.5px; font-weight:700; letter-spacing:.14em; text-transform:uppercase; color:var(--accent); margin-bottom:2px; }
  .framebody h2{ font-size:27px; font-weight:700; line-height:1.24; color:var(--cream); text-shadow:0 2px 14px rgba(0,0,0,.35); }
  .framebody .sub{ font-size:15px; color:var(--muted); margin-top:2px; }

  .frameactions{ position:relative; z-index:10; padding:14px 22px 26px; display:flex; flex-direction:column; gap:12px; }
  .bigbtn{
    width:100%; min-height:58px; border-radius:18px; font-size:17.5px; font-weight:700;
    border:2px solid rgba(255,255,255,.18); background:rgba(255,255,255,.08); color:var(--cream); padding:10px 16px;
  }
  .bigbtn-primary{ background:var(--accent); border-color:var(--accent); color:var(--accent-ink); }
  .bigbtn-ghost{ background:none; border:2px solid rgba(255,255,255,.3); color:var(--muted); }
  .bigbtn:focus-visible{ outline:3px solid #7EB6E8; outline-offset:2px; }

  .shutterwrap{ position:relative; z-index:10; display:flex; justify-content:center; padding-bottom:30px; }
  .shutter{
    width:88px; height:88px; border-radius:50%; background:var(--accent); border:6px solid rgba(255,255,255,.85);
    font-size:32px; display:flex; align-items:center; justify-content:center; color:var(--accent-ink);
  }
  .shutter:disabled{ opacity:.55; }
  .shutter:focus-visible{ outline:3px solid #7EB6E8; outline-offset:4px; }

  .brandlist{ width:100%; display:flex; flex-direction:column; gap:12px; overflow-y:auto; max-height:390px; padding:2px 2px 4px; }
  .brandbtn{
    width:100%; min-height:58px; border-radius:16px; background:rgba(255,255,255,.07); border:2px solid rgba(255,255,255,.16);
    color:var(--cream); font-size:16.5px; font-weight:700; padding:10px 18px; text-align:left;
  }
  .brandbtn:focus-visible{ outline:3px solid #7EB6E8; outline-offset:2px; }

  .swatch{ width:180px; height:130px; border-radius:16px; margin:4px auto 2px; box-shadow:0 10px 26px rgba(0,0,0,.35); }
  .sw1{background:repeating-linear-gradient(45deg,#E0764A 0 16px,#c25f37 16px 32px);}
  .sw2{background:repeating-linear-gradient(-45deg,#3E7C7C 0 14px,#2f6161 14px 28px);}
  .sw3{background:repeating-linear-gradient(90deg,#C97A96 0 14px,#b05f7c 14px 28px);}
  .itemname{ font-size:19px; font-weight:800; margin:0; color:var(--cream); }
  .itemmeta{ font-size:14.5px; color:var(--muted); margin:2px 0 0; }

  .doneflash{ font-size:52px; }
</style>
</head>
<body>
<div class="phone">
  <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 state = { screen:'home', item:null, brand:null, resultIndex:0 };

function randomItem(){ return MOCK_ITEMS[Math.floor(Math.random()*MOCK_ITEMS.length)]; }
function go(screen){ state.screen = screen; render(); }
function startJob(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]);
}

/* progress-bar position tables: [index(1-based), total] per screen key */
const POS = {
  'new-cam':[1,3], 'new-brand':[2,3], 'new-review':[3,3],
  'update-cam':[1,3], 'update-match':[2,3], 'update-cam2':[3,3],
  'find-method':[1,3], 'find-result':[2,3], 'find-detail':[3,3],
  'sample-cam':[1,3], 'sample-match':[2,3], 'sample-printing':[3,3],
};

function progressRow(){
  const p = POS[state.screen];
  if(!p) return '';
  const [idx,total] = p;
  let segs = '';
  for(let i=1;i<=total;i++){
    segs += `<div class="seg ${i<idx?'done':(i===idx?'now':'')}"><i></i></div>`;
  }
  return `<div class="progressrow">${segs}</div>`;
}

function tapZones(leftFn, rightFn){
  const l = leftFn ? `<button class="tapzone left" aria-label="Back" onclick="${leftFn}"><span class="chev">‹</span></button>` : '';
  const r = rightFn ? `<button class="tapzone right" aria-label="Continue" onclick="${rightFn}"><span class="chev">›</span></button>` : '';
  return l+r;
}

function storyShell(canvasClass, innerHtml, opts){
  opts = opts || {};
  const exit = `<button class="exitbtn" aria-label="Exit story" onclick="go('home')">✕</button>`;
  return `<div class="story ${canvasClass||''}">
      ${progressRow()}
      <div class="storytop"><span class="who">${opts.who||''}</span>${exit}</div>
      ${tapZones(opts.tapLeft, opts.tapRight)}
      ${innerHtml}
    </div>`;
}

function shoot(cb){
  const st = document.getElementById('camStatus');
  const btn = document.querySelector('.shutter');
  if(btn) btn.disabled = true;
  if(st) st.textContent = 'Snapping…';
  setTimeout(()=>{ state.item = randomItem(); cb(); }, 650);
}

function cameraFrame(who, cue, question, hint, onShoot, tapLeft){
  return storyShell('cam', `
    <div class="framebody">
      <div class="cue">${cue}</div>
      <h2>${question}</h2>
      <p class="sub" id="camStatus">${hint}</p>
    </div>
    <div class="shutterwrap"><button class="shutter" aria-label="Take photo" onclick="${onShoot}">📸</button></div>
  `, {who, tapLeft, tapRight:onShoot});
}

function render(){
  const s = document.getElementById('screen');
  let html = '';

  if(state.screen==='home'){
    html = `<div class="home">
      <div class="home-mark">DW</div>
      <div class="home-sub">Story Mode</div>
      <h1 class="home-headline">Pick your story</h1>
      <p class="home-hint">Tap one to start — swipe through, tap ‹ › to move around.</p>
      <div class="storygrid">
        <button class="storytile" onclick="startJob('new')">
          <div class="ring r-new"><div class="ring-inner">🆕</div></div>
          <div class="label">Add New Item</div>
        </button>
        <button class="storytile" onclick="startJob('update')">
          <div class="ring r-update"><div class="ring-inner">🔄</div></div>
          <div class="label">Update an Item</div>
        </button>
        <button class="storytile" onclick="startJob('find')">
          <div class="ring r-find"><div class="ring-inner">🔍</div></div>
          <div class="label">Find an Item</div>
        </button>
        <button class="storytile" onclick="startJob('sample')">
          <div class="ring r-sample"><div class="ring-inner">📦</div></div>
          <div class="label">A Sample Came In</div>
        </button>
      </div>
    </div>`;
  }

  /* ---------------- ADD NEW ITEM ---------------- */
  else if(state.screen==='new-cam'){
    html = cameraFrame('Add New Item','Frame 1','Take a photo of it','Tap the shutter (or tap the right edge)', "shoot(()=>go('new-brand'))");
  }
  else if(state.screen==='new-brand'){
    let list = '';
    BRANDS.forEach(b=>{ list += `<button class="brandbtn" onclick="state.brand='${b}'; go('new-review')">${b}</button>`; });
    html = storyShell('', `
      <div class="framebody" style="justify-content:flex-start; padding-top:20px;">
        <div class="cue">Frame 2</div>
        <h2>Which brand is it?</h2>
        <div class="brandlist">${list}</div>
      </div>
    `, {who:'Add New Item', tapLeft:"go('new-cam')"});
  }
  else if(state.screen==='new-review'){
    html = storyShell('', `
      <div class="framebody">
        <div class="cue">Frame 3</div>
        <h2>Save it?</h2>
        <div class="swatch ${state.item.sw}"></div>
        <p class="itemmeta">${state.brand} · code ${state.item.code}</p>
      </div>
      <div class="frameactions">
        <button class="bigbtn bigbtn-primary" onclick="go('new-done')">Save as Draft</button>
        <button class="bigbtn bigbtn-ghost" onclick="go('new-cam')">Retake Photo</button>
      </div>
    `, {who:'Add New Item', tapLeft:"go('new-brand')", tapRight:"go('new-done')"});
  }
  else if(state.screen==='new-done'){
    html = doneFrame('Add New Item', '🎉', 'Saved!', 'Add Another', "startJob('new')");
  }

  /* ---------------- UPDATE AN ITEM ---------------- */
  else if(state.screen==='update-cam'){
    html = cameraFrame('Update an Item','Frame 1','Scan the label or pattern','Tap the shutter (or tap the right edge)', "shoot(()=>go('update-match'))");
  }
  else if(state.screen==='update-match'){
    html = storyShell('', `
      <div class="framebody">
        <div class="cue">Frame 2</div>
        <h2>Is this it?</h2>
        <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>
      <div class="frameactions">
        <button class="bigbtn bigbtn-primary" onclick="go('update-cam2')">Yes</button>
        <button class="bigbtn bigbtn-ghost" onclick="go('update-cam')">No, Let Me Search</button>
      </div>
    `, {who:'Update an Item', tapLeft:"go('update-cam')", tapRight:"go('update-cam2')"});
  }
  else if(state.screen==='update-cam2'){
    html = cameraFrame('Update an Item','Frame 3','Take the new photo','Tap the shutter (or tap the right edge)', "shoot(()=>go('update-done'))", "go('update-match')");
  }
  else if(state.screen==='update-done'){
    html = doneFrame('Update an Item', '✅', 'Updated!', 'Back Home', "go('home')");
  }

  /* ---------------- FIND AN ITEM ---------------- */
  else if(state.screen==='find-method'){
    html = storyShell('', `
      <div class="framebody">
        <div class="cue">Frame 1</div>
        <h2>How do you want to find it?</h2>
      </div>
      <div class="frameactions">
        <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>
    `, {who:'Find an Item', tapLeft:"go('home')"});
  }
  else if(state.screen==='find-result'){
    const it = MOCK_ITEMS[state.resultIndex];
    html = storyShell('', `
      <div class="framebody">
        <div class="cue">Frame 2</div>
        <h2>Is this the one?</h2>
        <div class="swatch ${it.sw}"></div>
        <p class="itemname">${it.name} — ${it.color}</p>
        <p class="itemmeta">code ${it.code}</p>
      </div>
      <div class="frameactions">
        <button class="bigbtn bigbtn-primary" onclick="pickResult()">Yes, This One</button>
        <button class="bigbtn bigbtn-ghost" onclick="nextResult()">Next Result</button>
      </div>
    `, {who:'Find an Item', tapLeft:"go('find-method')", tapRight:"nextResult()"});
  }
  else if(state.screen==='find-detail'){
    html = storyShell('', `
      <div class="framebody">
        <div class="cue">Frame 3</div>
        <h2>${state.item.name}</h2>
        <div class="swatch ${state.item.sw}"></div>
        <p class="itemmeta">${state.item.color} · code ${state.item.code}</p>
      </div>
      <div class="frameactions">
        <button class="bigbtn bigbtn-primary" onclick="go('update-cam2')">Update This</button>
        <button class="bigbtn bigbtn-ghost" onclick="go('find-method')">Search Again</button>
      </div>
    `, {who:'Find an Item', tapLeft:"go('find-result')"});
  }

  /* ---------------- A SAMPLE CAME IN ---------------- */
  else if(state.screen==='sample-cam'){
    html = cameraFrame('A Sample Came In','Frame 1','Scan the box label','Tap the shutter (or tap the right edge)', "shoot(()=>go('sample-match'))");
  }
  else if(state.screen==='sample-match'){
    html = storyShell('', `
      <div class="framebody">
        <div class="cue">Frame 2</div>
        <h2>Mark it received?</h2>
        <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>
      <div class="frameactions">
        <button class="bigbtn bigbtn-primary" onclick="go('sample-printing')">Yes, Mark Received</button>
      </div>
    `, {who:'A Sample Came In', tapLeft:"go('sample-cam')", tapRight:"go('sample-printing')"});
  }
  else if(state.screen==='sample-printing'){
    html = storyShell('', `
      <div class="framebody">
        <div class="cue">Frame 3</div>
        <h2>Printing your sticker…</h2>
        <p class="sub">🖨️ One sec…</p>
      </div>
    `, {who:'A Sample Came In'});
    setTimeout(()=>{ if(state.screen==='sample-printing') go('sample-done'); }, 1000);
  }
  else if(state.screen==='sample-done'){
    html = doneFrame('A Sample Came In', '✅', 'Done!', 'Back Home', "go('home')");
  }

  s.innerHTML = html;
}

function doneFrame(who, emoji, title, btnLabel, btnAction){
  return storyShell('', `
      <div class="framebody">
        <div class="doneflash">${emoji}</div>
        <h2>${title}</h2>
      </div>
      <div class="frameactions">
        <button class="bigbtn bigbtn-primary" onclick="${btnAction}">${btnLabel}</button>
        <button class="bigbtn bigbtn-ghost" onclick="go('home')">Home</button>
      </div>
    `, {who});
}

function pickResult(){ state.item = MOCK_ITEMS[state.resultIndex]; go('find-detail'); }
function nextResult(){
  state.resultIndex = (state.resultIndex + 1) % MOCK_ITEMS.length;
  go('find-result');
}

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