← back to Dw Photo Capture

public/themes/v3-text-buddy.html

323 lines

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>v3 — Text Buddy</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Fraunces:wght@600;700&family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<style>
  :root{
    --paper:#EFEAE3;
    --gold:#C9A24B;
    --sage:#DCE8DA;
    --text:#2B2B2B;
  }
  *{box-sizing:border-box;}
  html,body{margin:0;padding:0;background:#0a0a0a;font-family:'Inter',sans-serif;}
  .stage{
    width:390px;height:780px;margin:24px auto;position:relative;
    background:var(--paper);border-radius:36px;overflow:hidden;
    box-shadow:0 20px 60px rgba(0,0,0,.5);border:8px solid #000;color:var(--text);
    display:flex;flex-direction:column;
  }
  header{
    display:flex;align-items:center;justify-content:space-between;
    padding:18px 20px 14px;border-bottom:1px solid #ded6c8;background:var(--paper);flex-shrink:0;
  }
  header .wordmark{font-family:'Fraunces',serif;font-weight:700;font-size:18px;}
  header .sam-label{font-size:13px;color:#6B6258;margin-top:2px;}
  header .home-btn{
    background:#fff;border:2px solid #ded6c8;border-radius:12px;min-height:44px;padding:0 14px;
    font-size:14px;font-weight:600;cursor:pointer;color:var(--text);
  }
  header .home-btn:focus-visible{outline:3px solid var(--gold);outline-offset:2px;}
  #thread{
    flex:1;overflow-y:auto;padding:18px 16px 24px;display:flex;flex-direction:column;gap:12px;
  }
  .bubble{
    max-width:78%;padding:14px 16px;border-radius:16px;font-size:17px;line-height:1.4;
  }
  .sam{
    align-self:flex-start;background:#fff;border-left:5px solid var(--gold);
    border-radius:4px 16px 16px 16px;
  }
  .user{
    align-self:flex-end;background:var(--sage);border-radius:16px 4px 16px 16px;
  }
  .chips{display:flex;flex-wrap:wrap;gap:10px;align-self:flex-start;max-width:92%;}
  .chip{
    min-height:56px;padding:0 18px;border-radius:20px;background:#fff;border:2px solid var(--gold);
    color:var(--text);font-size:17px;font-weight:600;cursor:pointer;
  }
  .chip:focus-visible{outline:3px solid var(--gold);outline-offset:2px;}
  .cam-bubble{
    font:inherit;color:inherit;
    align-self:flex-start;background:#fff;border:2px dashed var(--gold);border-radius:16px;
    padding:18px;display:flex;flex-direction:column;align-items:center;gap:8px;cursor:pointer;
    min-width:180px;
  }
  .cam-bubble .cam-icon{font-size:40px;}
  .cam-bubble .cam-label{font-size:15px;color:#6B6258;}
  .swatch-bubble{
    align-self:flex-end;width:180px;height:130px;border-radius:14px;
    background:repeating-linear-gradient(45deg,#8FAE8B 0 20px,#B9CBB2 20px 40px),linear-gradient(135deg,#8FAE8B,#6f8d6c);
    background-blend-mode:multiply;
  }
  .result-card{
    font:inherit;color:inherit;text-align:left;
    align-self:flex-start;display:flex;gap:10px;background:#fff;border-radius:14px;padding:10px;
    border:2px solid #ded6c8;cursor:pointer;max-width:88%;align-items:center;
  }
  .result-card .thumb{width:56px;height:56px;border-radius:10px;flex-shrink:0;
    background:repeating-linear-gradient(45deg,#C97B5B 0 12px,#e0a184 12px 24px);}
  .result-card .rc-title{font-size:16px;font-weight:700;}
  .result-card .rc-sub{font-size:13px;color:#6B6258;}
  .full-item{
    align-self:flex-start;background:#fff;border-radius:16px;padding:14px;border:2px solid #ded6c8;max-width:88%;
  }
  .full-item .swatch{width:100%;height:150px;border-radius:12px;margin-bottom:10px;
    background:repeating-linear-gradient(45deg,#8FAE8B 0 20px,#B9CBB2 20px 40px),linear-gradient(135deg,#8FAE8B,#6f8d6c);
    background-blend-mode:multiply;}
  .full-item .rc-title{font-size:18px;font-weight:700;}
  .full-item .rc-sub{font-size:14px;color:#6B6258;}
  .search-mock{
    align-self:flex-start;background:#fff;border-radius:14px;padding:14px 16px;font-size:16px;
    color:#6B6258;border:2px solid #ded6c8;min-width:220px;
  }
  .text-input{
    width:100%;border:none;outline:none;font-size:17px;font-family:'Inter',sans-serif;
    background:transparent;color:var(--text);
  }
  .text-input:focus-visible{outline:3px solid var(--gold);outline-offset:2px;border-radius:4px;}
  .spinner-inline{display:inline-block;width:16px;height:16px;border-radius:50%;
    border:3px solid rgba(0,0,0,.12);border-top-color:var(--gold);animation:spin 1s linear infinite;margin-right:8px;vertical-align:-3px;}
  @keyframes spin{to{transform:rotate(360deg);}}
</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="stage">
  <header>
    <div>
      <div class="wordmark">DW</div>
      <div class="sam-label">chatting with Sam</div>
    </div>
    <button class="home-btn" onclick="startHome()">⌂ New chat</button>
  </header>
  <div id="thread"></div>
</div>

<script>
  const thread = document.getElementById('thread');

  function scrollBottom(){ thread.scrollTop = thread.scrollHeight; }

  function addSam(text){
    const el = document.createElement('div');
    el.className = 'bubble sam';
    el.innerHTML = text;
    thread.appendChild(el);
    scrollBottom();
    return el;
  }
  function addUser(text){
    const el = document.createElement('div');
    el.className = 'bubble user';
    el.innerHTML = text;
    thread.appendChild(el);
    scrollBottom();
    return el;
  }
  function addChips(options){
    const wrap = document.createElement('div');
    wrap.className = 'chips';
    options.forEach(opt => {
      const b = document.createElement('button');
      b.className = 'chip';
      b.textContent = opt.label;
      b.onclick = () => { wrap.remove(); opt.action(); };
      wrap.appendChild(b);
    });
    thread.appendChild(wrap);
    scrollBottom();
    return wrap;
  }
  function addCameraBubble(onShot){
    function showCameraStep(){
      const el = document.createElement('button');
      el.type = 'button';
      el.className = 'cam-bubble';
      el.setAttribute('aria-label', 'Take photo');
      el.innerHTML = '<div class="cam-icon">📸</div><div class="cam-label">Tap to shoot</div>';
      el.onclick = () => {
        el.remove();
        const photo = addUser('<div class="swatch-bubble"></div>');
        const looking = addSam('<span class="spinner-inline"></span>Looking…');
        setTimeout(() => {
          looking.remove();
          addSam("Here's what I got — good?");
          addChips([
            {label:'Looks good', action: () => { onShot(); }},
            {label:'Retake', action: () => { photo.remove(); showCameraStep(); }},
          ]);
        }, 900);
      };
      thread.appendChild(el);
      scrollBottom();
    }
    showCameraStep();
  }

  function startHome(){
    thread.innerHTML = '';
    addSam('Hey! What are we doing today?');
    addChips([
      {label:'New Item', action: flowAddStart},
      {label:'Update', action: flowUpdateStart},
      {label:'Find', action: flowFindStart},
      {label:'Sample In', action: flowSampleStart},
    ]);
  }

  // ---------- ADD NEW ITEM ----------
  function flowAddStart(){
    addUser('New Item');
    addSam('Cool, snap a photo of it 📸');
    addCameraBubble(() => {
      addSam('Which brand?');
      addChips([
        {label:'York', action: () => addAddSaved()},
        {label:'Brewster', action: () => addAddSaved()},
        {label:'Kravet', action: () => addAddSaved()},
        {label:'Thibaut', action: () => addAddSaved()},
      ]);
    });
  }
  function addAddSaved(){
    addSam('Saved it as a draft! 🎉');
    addChips([
      {label:'Yes, another', action: () => { addUser('Yes, another'); flowAddStart(); }},
      {label:'Nope, done', action: () => { addUser('Nope, done'); addSam("Great, see you next time! 👋 Tap '⌂ New chat' any time."); }},
    ]);
  }

  // ---------- UPDATE AN ITEM ----------
  function flowUpdateStart(){
    addUser('Update');
    addSam('Show me the label or pattern');
    addCameraBubble(() => {
      addSam("Found it — Palm Damask — Sage, code DW-10423. That the one?");
      addChips([
        {label:"Yep, that's it", action: () => {
          addUser("Yep, that's it");
          addSam('Send me the new photo');
          addCameraBubble(() => {
            addSam('Updated! ✅');
          });
        }},
        {label:'Nope, let me search', action: () => {
          addUser('Nope, let me search');
          addSam('No problem — type the name or code:');
          const el = document.createElement('div');
          el.className = 'search-mock';
          el.innerHTML = '<input type="text" class="text-input" enterkeyhint="search" placeholder="Palm Damask…" aria-label="Type the item name or code">';
          thread.appendChild(el);
          scrollBottom();
          const searchInput = el.querySelector('input');
          const sendIt = () => {
            const val = searchInput.value.trim() || 'Palm Damask';
            addUser(val);
            addSam("Great — Palm Damask — Sage, code DW-10423. That the one?");
            addChips([
              {label:"Yep, that's it", action: () => {
                addUser("Yep, that's it");
                addSam('Send me the new photo');
                addCameraBubble(() => { addSam('Updated! ✅'); });
              }},
            ]);
          };
          searchInput.addEventListener('keydown', (e) => { if(e.key === 'Enter'){ e.preventDefault(); sendIt(); } });
          addChips([{label:'Send', action: sendIt}]);
        }},
      ]);
    });
  }

  // ---------- FIND AN ITEM ----------
  function flowFindStart(){
    addUser('Find');
    findAskMethod();
  }
  function findAskMethod(){
    addSam('How do you want to search? 📷 label 🎤 voice ⌨️ type');
    addChips([
      {label:'📷 label', action: () => { addUser('📷 label'); findShowResults(); }},
      {label:'🎤 voice', action: () => { addUser('🎤 voice'); findShowResults(); }},
      {label:'⌨️ type', action: () => { addUser('⌨️ type'); findShowResults(); }},
    ]);
  }
  function findShowResults(){
    addSam("Here's what I found:");
    const items = [
      {name:'Palm Damask — Sage', code:'DW-10423'},
      {name:'Meadow Stripe — Gold', code:'DW-20115'},
      {name:'Coastal Weave — Blue', code:'DW-30871'},
    ];
    items.forEach(it => {
      const card = document.createElement('button');
      card.type = 'button';
      card.className = 'result-card';
      card.innerHTML = '<div class="thumb"></div><div><div class="rc-title">'+it.name+'</div><div class="rc-sub">code '+it.code+'</div></div>';
      card.onclick = () => {
        document.querySelectorAll('.result-card').forEach(c => c.remove());
        addUser(it.name);
        findShowDetail(it);
      };
      thread.appendChild(card);
    });
    scrollBottom();
  }
  function findShowDetail(it){
    addSam('Here it is —');
    const el = document.createElement('div');
    el.className = 'full-item';
    el.innerHTML = '<div class="swatch"></div><div class="rc-title">'+it.name+'</div><div class="rc-sub">code '+it.code+'</div>';
    thread.appendChild(el);
    scrollBottom();
    addChips([
      {label:'Update This', action: () => {
        addUser('Update This');
        addSam('Send me the new photo');
        addCameraBubble(() => { addSam('Updated! ✅'); });
      }},
      {label:'Search Again', action: () => { addUser('Search Again'); findAskMethod(); }},
    ]);
  }

  // ---------- SAMPLE IN ----------
  function flowSampleStart(){
    addUser('Sample In');
    addSam('Scan the box label');
    addCameraBubble(() => {
      addSam('Found it — mark it received?');
      addChips([
        {label:'Yes', action: () => {
          addUser('Yes');
          const printing = addSam('<span class="spinner-inline"></span>Printing your sticker now 🖨️');
          setTimeout(() => {
            printing.innerHTML = 'All set! ✅';
          }, 1200);
        }},
      ]);
    });
  }

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