[object Object]

← back to Quadrille House Site

quadrille-house-site: href-drill — card series is a filter-link (color+pattern URL-addressable)

dd87a2c76d2f229331ca6fa40ad22d5202c32d91 · 2026-08-01 02:23:56 -0700 · Steve Abrams

Card series is now an <a href="?series=…"> to its filtered view (href-drill HARD rule);
color + pattern(series) filter state is URL-addressable (shareable, deep-linkable,
back-button-safe via popstate/readURL). Card click still opens the product (grid handler
skips .drill); cmd/ctrl-click on a series opens the shareable href. Colorbar / pattern
select / brand chips untouched.

Verified LOCAL (Playwright, :9945): click series -> ?series= + grid 3675->1657 all-matching;
deep-link restores; back-button->3675; canary FLAT->DONE; 0 pageerrors. PUBLIC/customer-facing
— local commit only; DEPLOY gated (pending-approval). grid 10/92. TK-10089.

Files touched

Diff

commit dd87a2c76d2f229331ca6fa40ad22d5202c32d91
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Sat Aug 1 02:23:56 2026 -0700

    quadrille-house-site: href-drill — card series is a filter-link (color+pattern URL-addressable)
    
    Card series is now an <a href="?series=…"> to its filtered view (href-drill HARD rule);
    color + pattern(series) filter state is URL-addressable (shareable, deep-linkable,
    back-button-safe via popstate/readURL). Card click still opens the product (grid handler
    skips .drill); cmd/ctrl-click on a series opens the shareable href. Colorbar / pattern
    select / brand chips untouched.
    
    Verified LOCAL (Playwright, :9945): click series -> ?series= + grid 3675->1657 all-matching;
    deep-link restores; back-button->3675; canary FLAT->DONE; 0 pageerrors. PUBLIC/customer-facing
    — local commit only; DEPLOY gated (pending-approval). grid 10/92. TK-10089.
---
 public/index.html | 21 ++++++++++++++++-----
 1 file changed, 16 insertions(+), 5 deletions(-)

diff --git a/public/index.html b/public/index.html
index e00549c..d6afa0d 100644
--- a/public/index.html
+++ b/public/index.html
@@ -138,6 +138,11 @@
 <script>
 const $=s=>document.querySelector(s), grid=$('#grid');
 let ALL=[], activeBrand='all', activeColor=null, activeSeries=null, FACETS=null, CFG=null;
+// ── href-drill: color + pattern(series) filter state is URL-addressable (shareable + deep-link + back-button) ──
+function qstr(over){ const c=(over&&'color'in over)?over.color:activeColor, s=(over&&'series'in over)?over.series:activeSeries; const p=[]; if(c)p.push('color='+encodeURIComponent(c)); if(s)p.push('series='+encodeURIComponent(s)); return p.length?('?'+p.join('&')):location.pathname; }
+function drill(dim,val,label){ const e=s=>String(s==null?'':s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/"/g,'&quot;'); if(val==null||val==='')return e(label!=null?label:val); return `<a class="drill" data-filter="${dim}" data-v="${e(val)}" href="${e(qstr({[dim]:String(val)}))}" title="Filter to ${e(val)}">${e(label!=null?label:val)}</a>`; }
+function readURL(){ const u=new URLSearchParams(location.search); activeColor=u.get('color')||null; activeSeries=u.get('series')||null; }
+function writeURL(push){ history[push?'pushState':'replaceState']({},'',qstr()); }
 const LS={sort:'qhouse.sort', dens:'qhouse.density', brand:'qhouse.brand', view:'qhouse.view'};
 // LIST-VIEW columns — every non-always-null data field a product carries
 // (match_type / finish / application / coverage are always null in house.json → skipped;
@@ -267,7 +272,7 @@ function render(){
     <div class="card" data-h="${p.handle}">
       <img class="ph" loading="lazy" decoding="async" width="400" height="400" alt="${(p.title||'').replace(/"/g,'&quot;')} — ${p.brand}" src="${p.swatch||p.room||''}">
       <div class="body">
-        <div class="ser">${p.display_eyebrow||p.series||''}</div>
+        <div class="ser">${p.series?drill('series',p.series,p.display_eyebrow||p.series):(p.display_eyebrow||'')}</div>
         <div class="nm">${p.display_name||p.color||p.title}</div>
         <div class="meta">
           <span class="col">${p.hex?`<span class="dot" style="background:${p.hex}"></span>`:''}${p.brand}</span>
@@ -277,7 +282,12 @@ function render(){
       </div>
     </div>`).join('');
 }
-grid.addEventListener('click',e=>{const c=e.target.closest('.card');if(c)location.href='/product/'+c.dataset.h;});
+grid.addEventListener('click',e=>{ if(e.target.closest('a.drill'))return; const c=e.target.closest('.card');if(c)location.href='/product/'+c.dataset.h;});
+// a card's series is an href-drill atom → filter in place + reflect into the URL (cmd/ctrl-click opens the shareable href)
+document.addEventListener('click',e=>{ const d=e.target.closest('a.drill[data-filter]'); if(!d)return; if(e.metaKey||e.ctrlKey||e.shiftKey||e.button===1)return; e.preventDefault(); e.stopPropagation();
+  const dim=d.dataset.filter,v=d.dataset.v; if(dim==='series') activeSeries=(activeSeries===v?null:v); else if(dim==='color') activeColor=(activeColor===v?null:v);
+  const ps=$('#pattern'); if(ps)ps.value=activeSeries||''; writeURL(true); render(); });
+addEventListener('popstate',()=>{ readURL(); const ps=$('#pattern'); if(ps)ps.value=activeSeries||''; render(); });
 
 // BRAND / LINE filter chips
 function brandChips(){
@@ -298,7 +308,7 @@ function colorbar(facets){
   wrap.style.display='';
   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();});
+  wrap.querySelectorAll('.cdot').forEach(b=>b.onclick=()=>{activeColor=b.dataset.c||null;writeURL(true);colorbar(facets);render();});
 }
 function fillPatterns(facets){
   const sel=$('#pattern');
@@ -308,7 +318,7 @@ function fillPatterns(facets){
 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'});});
+$('#pattern').addEventListener('change',()=>{activeSeries=$('#pattern').value||null;writeURL(true);render();$('#catalog').scrollIntoView({behavior:'smooth'});});
 
 addEventListener('scroll',()=>{
   const was=document.body.classList.contains('scrolled');
@@ -397,13 +407,14 @@ async function loadBrand(){
   ALL=pd.products; FACETS=fc;
   // collection-aware shopbar
   const sb=$('#shopbar'); if(sb) sb.innerHTML=`Shop ${activeBrand==='all'?'the Quadrille house':activeBrand} at <b>Designer Wallcoverings</b> →`;
-  brandChips(); colorbar(fc); fillPatterns(fc); render(); applyAbout();
+  brandChips(); colorbar(fc); fillPatterns(fc); const _ps=$('#pattern'); if(_ps)_ps.value=activeSeries||''; render(); applyAbout();
 }
 
 (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 savedBrand=localStorage.getItem(LS.brand); if(savedBrand)activeBrand=savedBrand;
+  readURL();   // href-drill: land in the color/pattern filter carried by the URL (deep-link / shareable)
   applyView();
   const cfg=await fetch(location.origin+'/api/config').then(r=>r.json());
   applyConfig(cfg);

← 2845a90 chore: macstudio3 migration — reconcile from mac2 + repoint  ·  back to Quadrille House Site  ·  SEO: rel=nofollow sponsored on designerwallcoverings.com lin 9127fe1 →