← 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=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[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>