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