← back to Dw Yolo Loop

artmura-site/public/index.html

271 lines

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Designer Wallcoverings</title>
<meta name="description" content="A curated collection at Designer Wallcoverings — to the trade.">
<meta property="og:type" content="website">
<meta property="og:title" content="Designer Wallcoverings">
<meta property="og:description" content="A curated collection at Designer Wallcoverings.">
<meta name="theme-color" content="#f6f2ec">
<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=Lora:ital,wght@0,400;0,500;0,600;1,400;1,500&family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/styles.css">
</head>
<body>

<a class="corner ul on-hero wordmark" id="cUL" href="https://www.designerwallcoverings.com">Designer Wallcoverings</a>
<nav class="corner ur on-hero" id="cUR"></nav>

<!-- HERO -->
<header class="hero" id="hero">
  <div class="hero-inner">
    <div class="hero-eyebrow" id="heroEyebrow">A Designer Wallcoverings Collection</div>
    <h1 id="heroH1">&nbsp;</h1>
    <div class="sub" id="heroSub">Architectural surfaces, hand-finished — curated by Designer Wallcoverings.</div>
    <div class="hero-kicker" id="heroKicker">Made in Italy · Non-Woven Wallcoverings</div>
  </div>
  <a class="enter" href="#catalog">Explore the Collection</a>
</header>

<!-- COLLECTIONS -->
<section class="books" id="collections">
  <div class="eyebrow" id="booksEyebrow">The Collection</div>
  <h2 id="booksHeading">Designer Wallcoverings</h2>
  <p class="about" id="heroAbout" hidden style="max-width:760px;margin:14px auto 0;opacity:.85;line-height:1.6"></p>
  <div class="chips" id="chips"></div>
</section>

<!-- CONTROLS -->
<div class="controls" id="catalog">
  <div class="controls-in">
    <a href="https://www.designerwallcoverings.com" class="bar-wm">Designer Wallcoverings</a>
    <div class="count"><b id="cnt">—</b> wallcoverings</div>
    <div class="ctl">
      <label for="pattern">Pattern</label>
      <select id="pattern"><option value="">All patterns</option></select>
    </div>
    <div class="ctl">
      <label for="style">Style</label>
      <select id="style"><option value="">All styles</option></select>
    </div>
    <div class="ctl">
      <label for="motif">Motif</label>
      <select id="motif"><option value="">All motifs</option></select>
    </div>
    <div class="ctl">
      <label for="sort">Sort</label>
      <select id="sort">
        <option value="newest">Newest</option>
        <option value="color">Color</option>
        <option value="series">Collection</option>
        <option value="sku">SKU A→Z</option>
        <option value="title">Title A→Z</option>
      </select>
    </div>
    <div class="ctl">
      <label for="density">Density</label>
      <input type="range" id="density" min="2" max="6" step="1" value="4">
    </div>
  </div>
</div>

<!-- COLOR FILTER -->
<div class="colorbar" id="colorbar"></div>

<!-- GRID -->
<main class="grid-wrap">
  <div class="grid" id="grid"></div>
</main>

<footer>
  <div class="foot-in">
    <div>
      <a class="wm" href="https://www.designerwallcoverings.com">Designer Wallcoverings</a>
      <div style="margin-top:8px">Designer Wallcoverings &amp; Fabrics · To the Trade</div>
      <div id="footLine" style="margin-top:4px;opacity:.8">One of 200+ lines we carry.</div>
      <div class="share" id="shareCollection" style="margin-top:18px"></div>
    </div>
    <div class="gate" id="footGate">Sold by the yard at Designer Wallcoverings. Order a sample before specifying. <a id="footGateLink" href="https://www.designerwallcoverings.com" style="color:var(--gold)">Shop the collection →</a></div>
  </div>
</footer>

<a class="shopbar" id="shopbar" href="https://www.designerwallcoverings.com" target="_blank" rel="noopener">Shop the collection at <b>Designer Wallcoverings</b> →</a>

<script>
const $=s=>document.querySelector(s), grid=$('#grid');
let ALL=[], view=[], activeBook=null, activeColor=null, activeSeries=null, activeStyle=null, activeMotif=null, FACETS=null, LINE='';
const LS={sort:'dwline.sort', dens:'dwline.density'};
const BUCKET_HEX={white:'#efeae0',grey:'#9e978c',black:'#2a2722',red:'#a23b2e',orange:'#c8763a',brown:'#7b5a3c',gold:'#b8923f',green:'#5d7150',teal:'#3f8a86',blue:'#5a7494',purple:'#7a5f86',pink:'#bb8aa0'};

const heroBg = url => `linear-gradient(0deg,rgba(0,0,0,0),rgba(0,0,0,0)),url("${url}")`;

// hero rotation from room shots
function startHero(items){
  const pool=items.filter(p=>p.room).sort(()=>0).slice(0,7); // first 7 with room shots
  const h=$('#hero');
  pool.forEach((p,i)=>{
    const d=document.createElement('div');
    d.className='slide'+(i===0?' on':'');
    d.style.backgroundImage=`url("${p.room}")`;
    h.insertBefore(d,h.firstChild);
  });
  const slides=[...document.querySelectorAll('.hero .slide')];
  let i=0; if(slides.length>1) setInterval(()=>{slides[i].classList.remove('on');i=(i+1)%slides.length;slides[i].classList.add('on');},5200);
}

// true color-wheel order: neutrals first (by lightness), then chromatic by hue
function colorKey(p){
  const b=p.color_bucket, hue=p.hue??999, sat=p.sat??0;
  if(b==='white')return [0, p.val??1];
  if(b==='grey') return [1, p.val??.5];
  if(b==='black')return [2, -(p.val??0)];
  return [3, hue]; // chromatic by hue angle
}
function sortItems(items,mode){
  const a=[...items];
  const num=p=>p.price||0;
  switch(mode){
    case 'color': a.sort((x,y)=>{const kx=colorKey(x),ky=colorKey(y);return kx[0]-ky[0]||kx[1]-ky[1];});break;
    case 'series': a.sort((x,y)=>(x.series||'').localeCompare(y.series)|| (x.color||'').localeCompare(y.color));break;
    case 'sku': a.sort((x,y)=>(x.sku||'').localeCompare(y.sku));break;
    case 'title': a.sort((x,y)=>(x.title||'').localeCompare(y.title));break;
    case 'price_asc': a.sort((x,y)=>num(x)-num(y));break;
    case 'price_desc': a.sort((x,y)=>num(y)-num(x));break;
    default: a.sort((x,y)=> new Date(y.published_at||0)-new Date(x.published_at||0));
  }
  return a;
}

function render(){
  let items = ALL;
  if(activeBook) items = items.filter(p=>p.book===activeBook);
  if(activeColor) items = items.filter(p=>p.color_bucket===activeColor);
  if(activeSeries) items = items.filter(p=>p.series===activeSeries);
  if(activeStyle) items = items.filter(p=>(p.styles||[]).includes(activeStyle));
  if(activeMotif) items = items.filter(p=>(p.motifs||[]).includes(activeMotif));
  items = sortItems(items, $('#sort').value);
  view=items;
  $('#cnt').textContent=items.length;
  grid.innerHTML = items.map(p=>`
    <div class="card" data-h="${p.handle}">
      <img class="ph" loading="lazy" decoding="async" width="400" height="400" alt="${(p.title||'').replace(/"/g,'&quot;')}" src="${p.swatch||p.room||''}">
      <div class="body">
        <div class="ser">${p.display_eyebrow||''}</div>
        <div class="nm">${p.display_name||p.title}</div>
        <div class="meta">
          <span class="col">${p.hex?`<span class="dot" style="background:${p.hex}"></span>`:''}${p.book||LINE}</span>
          <span class="pr view">View →</span>
        </div>
      </div>
    </div>`).join('');
}
grid.addEventListener('click',e=>{const c=e.target.closest('.card');if(c)location.href='/product/'+c.dataset.h;});

function chips(facets){
  const wrap=$('#chips');
  const all=`<div class="chip ${!activeBook?'active':''}" data-b="">All<span class="n">${facets.total}</span></div>`;
  wrap.innerHTML=all+facets.books.map(([b,n])=>`<div class="chip ${activeBook===b?'active':''}" data-b="${b}">${b}<span class="n">${n}</span></div>`).join('');
  wrap.querySelectorAll('.chip').forEach(ch=>ch.onclick=()=>{activeBook=ch.dataset.b||null;chips(facets);render();$('#catalog').scrollIntoView({behavior:'smooth'});});
}
function colorbar(facets){
  const wrap=$('#colorbar');
  if(!facets.colors||!facets.colors.length){wrap.style.display='none';return;}
  const all=`<button class="cdot all ${!activeColor?'active':''}" data-c="" title="All colors">All</button>`;
  wrap.innerHTML=all+facets.colors.map(([c,n])=>`<button class="cdot ${activeColor===c?'active':''}" data-c="${c}" title="${c} (${n})"><span style="background:${BUCKET_HEX[c]||'#ccc'}"></span>${c}</button>`).join('');
  wrap.querySelectorAll('.cdot').forEach(b=>b.onclick=()=>{activeColor=b.dataset.c||null;colorbar(facets);render();});
}

// density + sort persistence
function applyDensity(v){document.documentElement.style.setProperty('--cols',v);}
$('#density').addEventListener('input',e=>{applyDensity(e.target.value);localStorage.setItem(LS.dens,e.target.value);});
$('#sort').addEventListener('change',()=>{localStorage.setItem(LS.sort,$('#sort').value);render();});
$('#pattern').addEventListener('change',()=>{activeSeries=$('#pattern').value||null;render();$('#catalog').scrollIntoView({behavior:'smooth'});});
$('#style').addEventListener('change',()=>{activeStyle=$('#style').value||null;render();$('#catalog').scrollIntoView({behavior:'smooth'});});
$('#motif').addEventListener('change',()=>{activeMotif=$('#motif').value||null;render();$('#catalog').scrollIntoView({behavior:'smooth'});});
function fillPatterns(facets){
  const sel=$('#pattern');
  sel.innerHTML='<option value="">All patterns</option>'+facets.series.map(([s,n])=>`<option value="${s}">${s} (${n})</option>`).join('');
}
function fillSelect(id,rows,label){
  const sel=$(id); if(!sel) return;
  if(!rows||!rows.length){ const c=sel.closest('.ctl'); if(c)c.style.display='none'; return; }
  sel.innerHTML=`<option value="">${label}</option>`+rows.map(([s,n])=>`<option value="${s}">${s} (${n})</option>`).join('');
}

// hide hero corners + show bar wordmark once scrolled into catalog
addEventListener('scroll',()=>{document.body.classList.toggle('scrolled',scrollY>innerHeight*0.82);});

// render house chrome (wordmark + nav + hero text) from config → template stays line-agnostic
function applyConfig(cfg){
  if(cfg.house){ $('#cUL').textContent=cfg.house; if(cfg.houseUrl)$('#cUL').href=cfg.houseUrl; }
  if(cfg.nav&&cfg.nav.length){
    $('#cUR').innerHTML=cfg.nav.map(n=>{
      const ext=/^https?:/.test(n.href);
      return `<a href="${n.href}"${ext?' target="_blank" rel="noopener"':''}>${n.label}</a>`;
    }).join('');
  }
  if(cfg.eyebrow) $('#heroEyebrow').textContent=cfg.eyebrow;
  if(cfg.line)    $('#heroH1').textContent=cfg.line;
  if(cfg.tagline) $('#heroSub').textContent=cfg.tagline;
  if(cfg.kicker)  $('#heroKicker').textContent=cfg.kicker;
  if(cfg.title)   document.title=cfg.title;
  if(cfg.metaDescription){ const m=document.querySelector('meta[name=description]'); if(m)m.content=cfg.metaDescription; }
  if(cfg.collectionUrl){ let c=document.querySelector('link[rel=canonical]'); if(!c){c=document.createElement('link');c.rel='canonical';document.head.appendChild(c);} c.href=cfg.collectionUrl; }
  LINE = cfg.line || LINE;
  if(cfg.palette){ const rs=document.documentElement.style, pl=cfg.palette;
    ['bg','paper','ink','accent','gold','line','taupe'].forEach(k=>{ if(pl[k]) rs.setProperty('--'+k, pl[k]); }); }
  if(cfg.booksHeading){ const bh=$('#booksHeading'); if(bh)bh.textContent=cfg.booksHeading; }
  if(cfg.about){ const ab=$('#heroAbout'); if(ab){ ab.textContent=cfg.about; ab.hidden=false; } }
  if(cfg.collectionUrl){ const sb=$('#shopbar'); if(sb)sb.href=cfg.collectionUrl; }
  const fl=$('#footLine'); if(fl) fl.textContent=`The ${LINE} Collection — one of 200+ lines we carry.`;
  const fg=$('#footGateLink'); if(fg){ fg.textContent=`Shop the ${LINE} collection →`; if(cfg.collectionUrl)fg.href=cfg.collectionUrl; }
  renderShare(cfg.collectionUrl||cfg.storeBase, (cfg.title||`${LINE} at Designer Wallcoverings`));
}

// social share cluster — shares the real DW collection page
function renderShare(url, title){
  const el=$('#shareCollection'); if(!el||!url)return;
  const U=encodeURIComponent(url), T=encodeURIComponent(title);
  const ic={
    pin:'<svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor"><path d="M12 2C6.5 2 4 5.8 4 8.9c0 1.9.7 3.6 2.3 4.2.3.1.5 0 .6-.3l.2-.9c.1-.3 0-.4-.2-.6-.4-.5-.7-1.2-.7-2.1 0-2.7 2-5.1 5.3-5.1 2.9 0 4.5 1.8 4.5 4.1 0 3.1-1.4 5.7-3.4 5.7-1.1 0-1.9-.9-1.7-2 .3-1.3.9-2.7.9-3.6 0-.8-.4-1.5-1.4-1.5-1.1 0-2 1.1-2 2.7 0 1 .3 1.6.3 1.6l-1.4 5.7c-.4 1.6 0 3.7 0 3.9 0 .1.2.2.3.1.1-.1 1.6-2 2.1-3.8l.8-3c.4.8 1.5 1.4 2.7 1.4 3.6 0 6-3.3 6-7.6C20.4 5 17 2 12 2z"/></svg>',
    fb:'<svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor"><path d="M22 12a10 10 0 10-11.6 9.9v-7H7.9V12h2.5V9.8c0-2.5 1.5-3.9 3.8-3.9 1.1 0 2.2.2 2.2.2v2.5h-1.2c-1.2 0-1.6.8-1.6 1.6V12h2.7l-.4 2.9h-2.3v7A10 10 0 0022 12z"/></svg>',
    x:'<svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor"><path d="M18.9 2H22l-7.3 8.3L23.3 22h-6.8l-5.3-7-6.1 7H2l7.8-8.9L1.2 2H8l4.8 6.3L18.9 2zm-1.2 18h1.9L7.1 4H5.1l12.6 16z"/></svg>',
    mail:'<svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor"><path d="M4 4h16a2 2 0 012 2v12a2 2 0 01-2 2H4a2 2 0 01-2-2V6a2 2 0 012-2zm8 7L4 6.5V18h16V6.5L12 11z"/></svg>',
    link:'<svg viewBox="0 0 24 24" width="17" height="17" fill="currentColor"><path d="M10.6 13.4a1 1 0 001.4 0l4-4a3 3 0 00-4.2-4.2l-1.3 1.3a1 1 0 101.4 1.4l1.3-1.3a1 1 0 011.4 1.4l-4 4a1 1 0 000 1.4zm2.8-2.8a1 1 0 00-1.4 0l-4 4a3 3 0 004.2 4.2l1.3-1.3a1 1 0 10-1.4-1.4l-1.3 1.3a1 1 0 01-1.4-1.4l4-4a1 1 0 000-1.4z"/></svg>',
  };
  el.innerHTML=`<span class="share-lbl">Share</span>
    <a class="sbtn pin" title="Pin on Pinterest" target="_blank" rel="noopener" href="https://pinterest.com/pin/create/button/?url=${U}&description=${T}">${ic.pin}</a>
    <a class="sbtn fb" title="Share on Facebook" target="_blank" rel="noopener" href="https://www.facebook.com/sharer/sharer.php?u=${U}">${ic.fb}</a>
    <a class="sbtn x" title="Share on X" target="_blank" rel="noopener" href="https://twitter.com/intent/tweet?url=${U}&text=${T}">${ic.x}</a>
    <a class="sbtn mail" title="Email" href="mailto:?subject=${T}&body=${T}%20${U}">${ic.mail}</a>
    <button class="sbtn copy" title="Copy link" type="button">${ic.link}</button>`;
  el.querySelector('.copy').addEventListener('click',async()=>{
    const b=el.querySelector('.copy');
    try{ if(navigator.share){await navigator.share({title,url});return;}
      await navigator.clipboard.writeText(url); b.classList.add('done'); b.title='Copied!';
      setTimeout(()=>{b.classList.remove('done');b.title='Copy link';},1600);
    }catch(e){}
  });
}

(async()=>{
  const savedSort=localStorage.getItem(LS.sort); if(savedSort)$('#sort').value=savedSort;
  const savedDens=localStorage.getItem(LS.dens)||'4'; $('#density').value=savedDens; applyDensity(savedDens);
  const [cfg,pd,fc]=await Promise.all([
    fetch('/api/config').then(r=>r.json()),
    fetch('/api/products').then(r=>r.json()),
    fetch('/api/facets').then(r=>r.json())]);
  applyConfig(cfg);
  ALL=pd.products; FACETS=fc;
  const sb=$('#shopbar'); if(sb) sb.innerHTML=`The ${cfg.line||'collection'} collection is live at <b>Designer Wallcoverings</b> — shop all ${ALL.length} designs →`;
  startHero(ALL); chips(fc); colorbar(fc); fillPatterns(fc);
  fillSelect('#style', fc.styles, 'All styles'); fillSelect('#motif', fc.motifs, 'All motifs');
  render();
})();
</script>
</body>
</html>