← back to Dw Photo Capture
public/pick.html
301 lines
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover">
<meta name="theme-color" content="#0f0e0c">
<link rel="manifest" href="/manifest.webmanifest">
<link rel="apple-touch-icon" href="/apple-touch-icon.png">
<title>DW Sample Station</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=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<style>
/* TK-12348 — front page, rebuilt to the Figma spec (10 working themes only: 5 Figma + 5 Photoshop). */
:root{
--bg:#0F0E0C; --card:#1A1815; --card-selected:#221F1A; --btn-disabled:#24211D;
--border:#2E2A25; --text:#F5F1EA; --muted:#A89F92; --accent:#C9A96E; --on-accent:#0F0E0C;
--disabled-label:#7A7268;
}
*{box-sizing:border-box}
html{background:var(--bg);overflow-x:hidden}
html,body{margin:0;padding:0;height:100%}
body{
background:var(--bg); color:var(--text);
font-family:'Inter',-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;
-webkit-font-smoothing:antialiased; display:flex; flex-direction:column;
min-height:100dvh; overflow-x:hidden;
}
@media (prefers-reduced-motion:reduce){ *{scroll-behavior:auto !important; transition:none !important; animation:none !important} }
button{font:inherit;color:inherit;background:none;border:0;padding:0;margin:0}
a{color:inherit}
/* ---- header ---- */
header{display:flex;flex-direction:column;gap:6px;padding:52px 24px 20px}
.eyebrow{font-size:11px;font-weight:600;letter-spacing:1.5px;color:var(--accent);text-transform:uppercase}
h1{margin:0;font-size:28px;font-weight:700;line-height:34px;letter-spacing:-0.4px}
.subtitle{margin:0;font-size:16px;font-weight:400;line-height:22px;color:var(--muted)}
/* ---- theme list ---- */
.list{flex:1;min-height:0}
.group{margin-top:16px}
.group:first-child{margin-top:0}
.group-head{display:flex;align-items:baseline;gap:8px;padding:0 24px}
.group-label{margin:0;font-size:12px;font-weight:600;letter-spacing:1.2px;color:var(--accent);text-transform:uppercase}
.group-count{font-size:12px;font-weight:400;color:var(--muted)}
.row{
display:flex; gap:12px; padding:10px 24px 4px; margin:0;
overflow-x:auto; overflow-y:hidden; scroll-snap-type:x proximity; -webkit-overflow-scrolling:touch;
scrollbar-width:none;
}
.row::-webkit-scrollbar{display:none}
/* ---- card ---- */
.card{
position:relative; flex:0 0 150px; width:150px; height:234px;
border-radius:16px; overflow:hidden; border:1px solid var(--border);
background:var(--card); text-align:left; cursor:pointer; scroll-snap-align:start;
display:flex; flex-direction:column;
}
.card:focus-visible{outline:3px solid var(--accent); outline-offset:2px}
.card[aria-checked="true"]{border:2px solid var(--accent); background:var(--card-selected)}
.thumb-wrap{position:relative; width:100%; height:150px; flex:0 0 150px; background:#000}
.thumb-wrap img{width:100%;height:100%;object-fit:cover;object-position:top;display:block}
.tag{
position:absolute; top:8px; left:8px; padding:4px 8px; border-radius:999px;
background:rgba(15,14,12,.78); color:var(--text); font-size:11px; font-weight:600; letter-spacing:.3px;
}
.check{
position:absolute; top:8px; right:8px; width:28px; height:28px; border-radius:50%;
background:var(--accent); display:none; align-items:center; justify-content:center;
}
.card[aria-checked="true"] .check{display:flex}
.check svg path{stroke:var(--on-accent)}
.last-used{
position:absolute; bottom:8px; left:8px; padding:4px 8px; border-radius:999px;
background:var(--accent); color:var(--on-accent); font-size:11px; font-weight:600;
}
.body{padding:10px 12px 12px; display:flex; flex-direction:column; gap:4px; min-height:0}
.name{font-size:15px; font-weight:600; line-height:20px}
.desc{
font-size:13px; font-weight:400; line-height:18px; color:var(--muted);
display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden;
}
/* ---- footer ---- */
footer{
flex:0 0 auto; background:var(--bg); border-top:1px solid var(--border);
padding:12px 24px calc(20px + env(safe-area-inset-bottom));
display:flex; flex-direction:column; align-items:center; gap:0;
}
.continue{
width:100%; max-width:342px; height:56px; border-radius:14px; font-size:17px; font-weight:600;
display:flex; align-items:center; justify-content:center;
background:var(--btn-disabled); border:1px solid var(--border); color:var(--disabled-label);
}
.continue:not(:disabled){background:var(--accent); border-color:var(--accent); color:var(--on-accent); cursor:pointer}
.continue:focus-visible{outline:3px solid var(--accent); outline-offset:2px}
.classic-link{
margin-top:8px; min-height:44px; padding:0 12px; display:flex; align-items:center;
font-size:15px; font-weight:500; color:var(--accent); text-decoration:underline;
}
.classic-link:focus-visible{outline:3px solid var(--accent); outline-offset:2px; border-radius:6px}
.helper{display:none; margin:4px 0 0; font-size:13px; color:var(--muted); text-align:center}
/* ---- mobile: fixed viewport, no page scroll (rows scroll sideways by design) ---- */
@media (max-width:699.98px){
html,body{height:100dvh; overflow:hidden}
.list{overflow-y:auto; overflow-x:hidden}
}
/* ---- tablet / landscape iPad: 700px+ ---- */
@media (min-width:700px){
html,body{height:auto; overflow-x:hidden}
body{min-height:100dvh}
.page{max-width:820px; margin:0 auto; width:100%; display:flex; flex-direction:column; flex:1}
header{padding:64px 32px 20px}
h1{font-size:36px; line-height:43px}
.subtitle{font-size:18px; line-height:25px}
.group{margin-top:32px}
.group-head{padding:0 32px}
.row{
padding:10px 32px 4px; display:grid; grid-template-columns:repeat(5,1fr); gap:16px;
overflow:visible; scroll-snap-type:none;
}
.card{width:100%; height:332px; flex:auto}
.thumb-wrap{height:248px; flex-basis:248px}
footer{padding:12px 32px calc(32px + env(safe-area-inset-bottom)); gap:4px}
.continue{max-width:420px; margin:0 auto}
.classic-link{margin-top:4px}
.helper{display:block}
}
</style>
</head>
<body>
<div class="page">
<header>
<div class="eyebrow" id="eyebrow">DESIGNER WALLCOVERINGS</div>
<h1>DW Sample Station</h1>
<p class="subtitle">Pick how you want the app to look</p>
</header>
<main class="list" id="list">
<section class="group" data-group="figma">
<div class="group-head">
<h2 class="group-label" id="label-figma">FIGMA</h2>
<span class="group-count">· 5 themes</span>
</div>
<div class="row" id="row-figma" role="radiogroup" aria-labelledby="label-figma"></div>
</section>
<section class="group" data-group="photoshop">
<div class="group-head">
<h2 class="group-label" id="label-photoshop">PHOTOSHOP</h2>
<span class="group-count">· 5 themes</span>
</div>
<div class="row" id="row-photoshop" role="radiogroup" aria-labelledby="label-photoshop"></div>
</section>
</main>
<footer>
<button type="button" class="continue" id="continueBtn" disabled aria-disabled="true">Continue</button>
<a class="classic-link" href="/app">Use the classic app</a>
<p class="helper">You can switch themes anytime in Settings</p>
</footer>
</div>
<script>
(function(){
// The 10 working themes shown on the front page (5 Figma mockups + 5 Photoshop
// "kid-simple" mockups). Everything else under /themes/ and /kid-simple/ is still
// reachable directly by URL, just not listed here.
var THEMES = [
{ group:'figma', key:'fg-v05', ver:'v05', slug:'f-swipe-deck', name:'Swipe Deck', desc:'Swipe cards to confirm' },
{ group:'figma', key:'fg-v10', ver:'v10', slug:'f-scan-first', name:'Scan First', desc:'Camera opens first' },
{ group:'figma', key:'fg-v12', ver:'v12', slug:'f-bottom-tabs', name:'Bottom Tabs', desc:'Classic tab bar' },
{ group:'figma', key:'fg-v20', ver:'v20', slug:'f-luxe-gold', name:'Luxe Gold', desc:'Premium showroom look' },
{ group:'figma', key:'fg-v24', ver:'v24', slug:'f-split-screen', name:'Split Screen', desc:'Photo + details side by side' },
{ group:'photoshop', key:'ps-v07', ver:'v07', slug:'p-scan-and-go', name:'Scan & Go', desc:'Scan, then one big action' },
{ group:'photoshop', key:'ps-v08', ver:'v08', slug:'p-mode-dial', name:'Mode Dial', desc:'Turn a dial to pick the job' },
{ group:'photoshop', key:'ps-v10', ver:'v10', slug:'p-todays-inbox', name:'Today’s Inbox', desc:'Today’s tasks first' },
{ group:'photoshop', key:'ps-v12', ver:'v12', slug:'p-hold-to-talk', name:'Hold to Talk', desc:'Voice-first' },
{ group:'photoshop', key:'ps-v13', ver:'v13', slug:'p-sticker-book', name:'Sticker Book', desc:'Playful sticker cards' }
];
var STORE_KEY = 'dwTheme';
var $ = function(id){ return document.getElementById(id); };
var reduceMotion = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
function getStored(){ try { return localStorage.getItem(STORE_KEY); } catch(e){ return null; } }
function setStored(v){ try { localStorage.setItem(STORE_KEY, v); } catch(e){} }
var initialSlug = getStored();
var isReturning = !!(initialSlug && THEMES.some(function(t){ return t.slug === initialSlug; }));
var selectedSlug = isReturning ? initialSlug : null;
if (isReturning) $('eyebrow').textContent = 'WELCOME BACK';
var rows = { figma: $('row-figma'), photoshop: $('row-photoshop') };
var cardEls = {}; // slug -> button element
function checkSvg(){
return '<svg width="14" height="11" viewBox="0 0 14 11" fill="none" aria-hidden="true">' +
'<path d="M1.5 5.5L5 9L12.5 1.5" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/></svg>';
}
THEMES.forEach(function(t, i){
var btn = document.createElement('button');
btn.type = 'button';
btn.className = 'card';
btn.setAttribute('role', 'radio');
btn.setAttribute('aria-checked', String(t.slug === selectedSlug));
btn.setAttribute('data-slug', t.slug);
btn.tabIndex = -1; // roving tabindex set below
btn.innerHTML =
'<span class="thumb-wrap">' +
'<img src="/pick-thumbs/' + t.key + '.jpg" alt="" width="150" height="150" loading="' + (i < 3 ? 'eager' : 'lazy') + '">' +
'<span class="tag">' + t.ver + '</span>' +
'<span class="check">' + checkSvg() + '</span>' +
'<span class="last-used" hidden>Last used</span>' +
'</span>' +
'<span class="body">' +
'<span class="name">' + t.name + '</span>' +
'<span class="desc">' + t.desc + '</span>' +
'</span>';
btn.setAttribute('aria-label', t.name + ' — ' + t.desc);
btn.addEventListener('click', function(){ select(t.slug); });
rows[t.group].appendChild(btn);
cardEls[t.slug] = btn;
});
// "Last used" pill sticks to whichever card was selected at page-load time on a
// returning visit, independent of anything the user re-picks during this session.
if (isReturning && cardEls[initialSlug]) {
cardEls[initialSlug].querySelector('.last-used').hidden = false;
}
function updateRovingTabindex(){
Object.keys(rows).forEach(function(g){
var els = Array.prototype.slice.call(rows[g].children);
var activeSlug = selectedSlug && THEMES.some(function(t){ return t.group===g && t.slug===selectedSlug; }) ? selectedSlug : (els[0] && els[0].getAttribute('data-slug'));
els.forEach(function(el){ el.tabIndex = (el.getAttribute('data-slug') === activeSlug) ? 0 : -1; });
});
}
function updateContinue(){
var btn = $('continueBtn');
var t = THEMES.filter(function(x){ return x.slug === selectedSlug; })[0];
if (t) {
btn.disabled = false;
btn.removeAttribute('aria-disabled');
btn.textContent = 'Continue with ' + t.name;
btn.dataset.href = '/t/' + t.slug;
} else {
btn.disabled = true;
btn.setAttribute('aria-disabled', 'true');
btn.textContent = 'Continue';
delete btn.dataset.href;
}
}
function select(slug){
selectedSlug = slug;
THEMES.forEach(function(t){ cardEls[t.slug].setAttribute('aria-checked', String(t.slug === slug)); });
updateRovingTabindex();
updateContinue();
}
// Arrow-key roving focus within each row's radiogroup (Enter/Space select natively,
// since every card is a real <button>).
Object.keys(rows).forEach(function(g){
rows[g].addEventListener('keydown', function(e){
if (e.key !== 'ArrowRight' && e.key !== 'ArrowLeft') return;
var els = Array.prototype.slice.call(rows[g].children);
var idx = els.indexOf(document.activeElement);
if (idx === -1) return;
e.preventDefault();
var next = e.key === 'ArrowRight' ? Math.min(idx + 1, els.length - 1) : Math.max(idx - 1, 0);
els.forEach(function(el, i){ el.tabIndex = (i === next) ? 0 : -1; });
els[next].focus();
});
});
$('continueBtn').addEventListener('click', function(){
var href = this.dataset.href;
if (!href || this.disabled) return;
location.href = href;
});
updateRovingTabindex();
updateContinue();
if (isReturning && cardEls[initialSlug]) {
// Scroll the returning user's card into view without moving the page itself.
requestAnimationFrame(function(){
cardEls[initialSlug].scrollIntoView({ block: 'nearest', inline: 'center', behavior: reduceMotion ? 'auto' : 'smooth' });
});
}
})();
</script>
</body>
</html>