← 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,'&').replace(/</g,'<').replace(/"/g,'"'); 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,'"')} — ${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 →