← back to Atomic50 Onboard
public/product.html
213 lines
<!doctype html><html lang="en"><head>
<meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">
<title>Atomic 50 — Designer Wallcoverings</title>
<style>
:root{--bg:#efece6;--paper:#fbf9f4;--ink:#20211d;--accent:#7a6a4c;--gold:#9a7c3f;--line:#ddd6c9;--taupe:#a89e8a}
*{box-sizing:border-box}
body{margin:0;background:var(--bg);color:var(--ink);font:15px/1.6 "Georgia","Times New Roman",serif}
.sans{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif}
header.house{position:sticky;top:0;z-index:20;background:var(--paper);border-bottom:1px solid var(--line);
display:flex;align-items:center;justify-content:space-between;padding:14px 26px}
.wordmark{font-size:15px;font-weight:700;letter-spacing:.14em;text-transform:uppercase;text-decoration:none;color:var(--ink)}
.wordmark small{display:block;font-size:9px;letter-spacing:.24em;color:var(--taupe);font-weight:600}
nav.house a{font-family:-apple-system,sans-serif;font-size:12px;letter-spacing:.06em;text-transform:uppercase;text-decoration:none;color:var(--ink);opacity:.8;margin-left:20px}
.wrap{max-width:1180px;margin:0 auto;padding:26px}
.crumb{font-family:-apple-system,sans-serif;font-size:12px;letter-spacing:.05em;color:var(--taupe);margin-bottom:18px}
.crumb a{color:var(--accent);text-decoration:none}
.pdp{display:grid;grid-template-columns:1.1fr 1fr;gap:38px}
.gal{background:var(--paper);border:1px solid var(--line);border-radius:4px;overflow:hidden;position:relative}
.gal .main{width:100%;aspect-ratio:1/1;object-fit:cover;display:block}
.gal.multi{cursor:pointer}
.gal .spinbtn{position:absolute;top:12px;right:12px;font-family:-apple-system,sans-serif;font-size:11px;letter-spacing:.06em;
background:#000a;color:#fff;border:none;border-radius:6px;padding:6px 12px;cursor:pointer}
/* ── finish-swatch gallery ── */
.gal .finishbadge{position:absolute;top:12px;left:12px;background:#000a;color:#fff;font-size:10px;font-weight:700;
border-radius:5px;padding:4px 9px;letter-spacing:.06em;font-family:-apple-system,sans-serif;z-index:3;pointer-events:none}
.gal .dots{position:absolute;left:0;right:0;bottom:12px;display:flex;justify-content:center;gap:6px;z-index:3;pointer-events:none}
.gal .dots i{width:7px;height:7px;border-radius:50%;background:#fff;opacity:.45;box-shadow:0 0 0 1px #0007;transition:opacity .15s,transform .15s}
.gal .dots i.on{opacity:1;transform:scale(1.3)}
.thumbs{display:flex;flex-wrap:wrap;gap:7px;margin-top:10px}
.thumbs img{width:46px;height:46px;object-fit:cover;border:1px solid var(--line);border-radius:3px;cursor:pointer;opacity:.7;transition:opacity .15s,border-color .15s}
.thumbs img:hover{opacity:1}
.thumbs img.on{opacity:1;border-color:var(--accent);border-width:2px}
.eyebrow{font-family:-apple-system,sans-serif;font-size:11px;letter-spacing:.24em;text-transform:uppercase;color:var(--gold);margin:0 0 8px}
h1{font-size:34px;font-weight:400;line-height:1.1;margin:0 0 6px}
.color{font-family:-apple-system,sans-serif;font-size:13px;color:var(--taupe);letter-spacing:.04em;margin:0 0 18px}
.desc{color:#3a3a34;margin:0 0 22px}
.specs{width:100%;border-collapse:collapse;font-family:-apple-system,sans-serif;font-size:13px;margin:0 0 24px}
.specs th,.specs td{text-align:left;padding:8px 0;border-bottom:1px solid var(--line);vertical-align:top}
.specs th{color:var(--taupe);font-weight:600;width:38%;letter-spacing:.03em}
.cta{display:flex;gap:12px;flex-wrap:wrap;margin:0 0 10px}
.cta button{font-family:-apple-system,sans-serif;font-size:13px;letter-spacing:.06em;text-transform:uppercase;
border-radius:6px;padding:13px 22px;cursor:pointer;border:1px solid var(--ink)}
.cta .quote{background:var(--ink);color:#fff}
.cta .quote:hover{background:#000}
.cta .sample{background:var(--paper);color:var(--ink)}
.cta .sample:hover{border-color:var(--accent)}
.note{font-family:-apple-system,sans-serif;font-size:12px;color:var(--taupe)}
.pairs{margin:60px 0 20px}
.pairs h2{font-size:20px;font-weight:400;border-bottom:1px solid var(--line);padding-bottom:10px}
.pgrid{display:grid;grid-template-columns:repeat(6,1fr);gap:12px}
.pgrid a{text-decoration:none;color:inherit}
.pgrid .pi{width:100%;aspect-ratio:1/1;object-fit:cover;border:1px solid var(--line);border-radius:3px;background:#e6e1d8}
.pgrid .pn{font-size:13px;margin-top:6px;line-height:1.3}
/* request modal */
.modal{position:fixed;inset:0;background:#0007;display:none;align-items:center;justify-content:center;z-index:50;padding:20px}
.modal.open{display:flex}
.mbox{background:var(--paper);border-radius:8px;max-width:420px;width:100%;padding:26px}
.mbox h3{margin:0 0 4px;font-weight:400;font-size:22px}
.mbox p.sub{font-family:-apple-system,sans-serif;font-size:12.5px;color:var(--taupe);margin:0 0 16px}
.mbox label{font-family:-apple-system,sans-serif;font-size:12px;display:block;margin:10px 0 4px;color:#4a463c}
.mbox input,.mbox textarea{width:100%;font-family:-apple-system,sans-serif;font-size:14px;padding:9px;border:1px solid var(--line);border-radius:5px;background:#fff}
.mbox .row{display:flex;gap:10px;margin-top:18px}
.mbox .row button{flex:1;font-family:-apple-system,sans-serif;font-size:13px;padding:11px;border-radius:6px;cursor:pointer}
.mbox .send{background:var(--ink);color:#fff;border:none}
.mbox .cancel{background:none;border:1px solid var(--line)}
.mbox .ok{font-family:-apple-system,sans-serif;font-size:13px;color:var(--accent);margin-top:14px}
@media(max-width:820px){.pdp{grid-template-columns:1fr}.pgrid{grid-template-columns:repeat(3,1fr)}nav.house{display:none}}
</style></head>
<body>
<header class="house">
<a href="/" class="wordmark" id="wm">Designer Wallcoverings<small id="wmsub"></small></a>
<nav class="house"><a href="/">The Collection</a><a href="https://www.designerwallcoverings.com">Designer Wallcoverings</a></nav>
</header>
<div class="wrap">
<div class="crumb"><a href="/">Atomic 50</a> · <span id="cb"></span></div>
<div class="pdp">
<div class="gal" id="gal">
<img class="main" id="main" alt="">
<span class="finishbadge" id="finishbadge" style="display:none"></span>
<div class="dots" id="galdots" style="display:none"></div>
<button class="spinbtn" id="spinbtn" style="display:none">▶ 360° View</button>
</div>
<div class="thumbs" id="thumbs"></div>
<div>
<p class="eyebrow" id="eyebrow">A Designer Wallcoverings Collection</p>
<h1 id="name"></h1>
<p class="color" id="color"></p>
<p class="desc" id="desc"></p>
<table class="specs" id="specs"></table>
<div class="cta">
<button class="quote" id="quoteBtn">Request a Quote</button>
<button class="sample" id="sampleBtn">Order a Sample ($4.25)</button>
</div>
<p class="note">To the trade. Pricing and lead time confirmed on request — this is a quote-only line.</p>
</div>
</div>
<div class="pairs" id="pairsWrap" style="display:none">
<h2>Pairs Well With</h2>
<div class="pgrid" id="pairs"></div>
</div>
</div>
<div class="modal" id="modal"><div class="mbox">
<h3 id="mtitle"></h3>
<p class="sub" id="msub"></p>
<label>Name</label><input id="fname" autocomplete="name">
<label>Email *</label><input id="femail" type="email" autocomplete="email">
<label>Note (optional)</label><textarea id="fnote" rows="2"></textarea>
<div class="ok" id="mok" style="display:none"></div>
<div class="row">
<button class="cancel" id="mcancel">Cancel</button>
<button class="send" id="msend">Send</button>
</div>
</div></div>
<script>
const $=s=>document.querySelector(s);
const handle=decodeURIComponent(location.pathname.split('/').pop());
function esc(s){return String(s==null?'':s).replace(/[&<>"']/g,c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));}
let P=null,MODE='quote';
async function boot(){
const p=await fetch(location.origin+'/api/product/'+encodeURIComponent(handle)).then(r=>r.json());
if(p.error){document.querySelector('.wrap').innerHTML='<p>Not found.</p>';return;}
P=p;
const name=p.display_name||p.sku||'Pressed-Tin Tile';
document.title=name+' — Atomic 50 · Designer Wallcoverings';
$('#cb').textContent=name;
$('#name').textContent=name;
$('#color').textContent=[p.product_type==='Architectural Wallcoverings'?(p.collection||''):p.product_type,p.color].filter(Boolean).join(' · ');
$('#desc').textContent=p.body_html||'';
// ── finish-swatch gallery: cycle through the powder-coat finish images ──
// Prefer the capped `swatches` set; fall back to the single swatch. HTTPS-only
// (feed already enforced). Spin GIFs are dead (NULL) so no 360° button here.
const setRaw=(Array.isArray(p.swatches)&&p.swatches.length?p.swatches:(p.swatch?[p.swatch]:[]));
const SET=[];for(const u of setRaw){if(u&&!SET.includes(u)){SET.push(u);if(SET.length===12)break;}}
let GI=0;
function showFinish(i){
if(!SET.length)return;
GI=((i%SET.length)+SET.length)%SET.length;
$('#main').src=SET[GI];
const dots=$('#galdots').querySelectorAll('i');dots.forEach((d,k)=>d.classList.toggle('on',k===GI));
const th=$('#thumbs').querySelectorAll('img');th.forEach((t,k)=>t.classList.toggle('on',k===GI));
}
if(SET.length){
$('#main').src=SET[0];
if(SET.length>1){
$('#gal').classList.add('multi');
$('#finishbadge').style.display='block';$('#finishbadge').textContent=SET.length+' FINISHES';
const gd=$('#galdots');gd.style.display='flex';
gd.innerHTML=SET.map((_,i)=>`<i class="${i===0?'on':''}"></i>`).join('');
// thumbnail strip
$('#thumbs').innerHTML=SET.map((u,i)=>`<img loading="lazy" class="${i===0?'on':''}" src="${esc(u)}" alt="finish ${i+1}">`).join('');
$('#thumbs').querySelectorAll('img').forEach((t,i)=>t.onclick=()=>showFinish(i));
// desktop hover-scrub across the main image; touch tap-to-advance
const isTouch=matchMedia('(hover:none),(pointer:coarse)').matches;
const gal=$('#gal');
if(isTouch){
gal.onclick=()=>showFinish(GI+1);
}else{
gal.onmousemove=e=>{const r=gal.getBoundingClientRect();
const frac=Math.min(.999,Math.max(0,(e.clientX-r.left)/r.width));showFinish(Math.floor(frac*SET.length));};
gal.onmouseleave=()=>showFinish(0);
}
}
}else{$('#main').style.background='#e6e1d8';}
const rows=[
['Material',p.material],['Finish',p.finish],['Application',p.application],
['Pattern',[p.pattern,p.style].filter(Boolean).join(' · ')],
['Fire Rating',p.fire_rating],['Size',[p.width,p.length].filter(Boolean).join(' × ')],
['MFR SKU',p.sku],
].filter(([,v])=>v);
$('#specs').innerHTML=rows.map(([k,v])=>`<tr><th>${esc(k)}</th><td>${esc(v)}</td></tr>`).join('');
// pairs
const pr=await fetch(location.origin+'/api/pairs/'+encodeURIComponent(handle)).then(r=>r.json()).catch(()=>({pairs:[]}));
if(pr.pairs&&pr.pairs.length){
$('#pairsWrap').style.display='block';
$('#pairs').innerHTML=pr.pairs.map(x=>`<a href="/product/${encodeURIComponent(x.handle)}">
<img class="pi" loading="lazy" src="${esc(x.swatch||'')}" alt="${esc(x.display_name||'')}">
<div class="pn">${esc(x.display_name||x.sku||'')}</div></a>`).join('');
}
}
boot();
// request modal
function openModal(mode){
MODE=mode;
$('#mtitle').textContent=mode==='quote'?'Request a Quote':'Order a Sample ($4.25)';
$('#msub').textContent=mode==='quote'
? 'We’ll confirm pricing and lead time for '+(P.display_name||P.sku)+'.'
: 'A $4.25 memo sample of '+(P.display_name||P.sku)+' will be prepared and shipped.';
$('#mok').style.display='none';$('#modal').classList.add('open');
}
$('#quoteBtn').onclick=()=>openModal('quote');
$('#sampleBtn').onclick=()=>openModal('sample');
$('#mcancel').onclick=()=>$('#modal').classList.remove('open');
$('#modal').onclick=e=>{if(e.target.id==='modal')$('#modal').classList.remove('open');};
$('#msend').onclick=async()=>{
const email=$('#femail').value.trim();
if(!email){$('#femail').focus();return;}
const r=await fetch(location.origin+'/api/request',{method:'POST',headers:{'Content-Type':'application/json'},
body:JSON.stringify({type:MODE,sku:P.sku||P.handle,name:$('#fname').value.trim(),email,note:$('#fnote').value.trim()})})
.then(r=>r.json()).catch(()=>({ok:false,error:'network'}));
$('#mok').style.display='block';
$('#mok').textContent=r.ok?r.message:('Error: '+(r.error||'try again'));
if(r.ok)setTimeout(()=>$('#modal').classList.remove('open'),2200);
};
</script>
</body></html>