← back to Wallsandfabrics
wallsandfabrics: adopt href-to-deeper-data storefront primitives (TK-10093)
6603efda964912fa54f5f60bdd663a44d1572d0e · 2026-08-01 11:49:12 -0700 · Claude
Files touched
A public/drill.jsM public/index.html
Diff
commit 6603efda964912fa54f5f60bdd663a44d1572d0e
Author: Claude <steve@designerwallcoverings.com>
Date: Sat Aug 1 11:49:12 2026 -0700
wallsandfabrics: adopt href-to-deeper-data storefront primitives (TK-10093)
---
public/drill.js | 36 ++++++++++++++++++++++++++++++
public/index.html | 67 +++++++++++++++++++++++++++++++++++++++++++++----------
2 files changed, 91 insertions(+), 12 deletions(-)
diff --git a/public/drill.js b/public/drill.js
new file mode 100644
index 0000000..e63cdd3
--- /dev/null
+++ b/public/drill.js
@@ -0,0 +1,36 @@
+// ── href-to-deeper-data primitives — SINGLE SOURCE OF TRUTH (HARD rule, 2026-07-31) ──
+// Every displayed data point must be an href to its deeper (filtered) data. These are the
+// reusable atoms; a viewer's index.html loads this file, then defines FIELDS, FIL, $, esc,
+// and applyFilters (which these reference at call time via the shared classic-script global
+// scope). Canonical copy: ~/Projects/_shared/web/drill.js → each viewer's public/drill.js.
+// Memory: all-data-points-href-to-deeper-data. Reference viewers: astek-landing, schumacher-internal.
+
+// Build the URL for a given filter set — any count/atom becomes a real, shareable link.
+function qstr(fil, q, pat) {
+ const u = new URLSearchParams();
+ for (const [k] of FIELDS) { if (fil[k]) u.set(k, fil[k]); }
+ if (q) u.set('q', q); if (pat) u.set('pat', pat);
+ const s = u.toString(); return s ? ('?' + s) : location.pathname;
+}
+// Read the URL's filter state back into FIL + the search inputs (deep-link / back-button).
+function readURL() {
+ const u = new URLSearchParams(location.search);
+ for (const [k] of FIELDS) FIL[k] = u.get(k) || '';
+ $('#q').value = u.get('q') || ''; $('#pattern').value = u.get('pat') || '';
+}
+// Reflect the current filter state into the URL (push = new history entry, else replace).
+function writeURL(push) {
+ const url = qstr(FIL, $('#q').value.trim(), $('#pattern').value.trim());
+ history[push ? 'pushState' : 'replaceState']({}, '', url);
+}
+// drill(field,value,label,cls) → an <a> whose href IS the filtered view for that value.
+// cmd/ctrl/middle-click opens it in a new tab; plain click filters in place (delegated listener).
+function drill(field, val, label, cls) {
+ if (val == null || val === '') return '';
+ const href = qstr({ ...FIL, [field]: String(val) }, $('#q').value.trim(), $('#pattern').value.trim());
+ return `<a class="drill ${cls || ''}" href="${href}" data-filter="${field}" data-v="${esc(val)}" title="Filter to ${esc(val)}">${label != null ? esc(label) : esc(val)}</a>`;
+}
+// Atom drill target: SET the filter to this value (drill in), vs pickF which TOGGLES.
+function setF(k, v) { if (FIL[k] === String(v)) return; FIL[k] = String(v); applyFilters('push'); }
+
+window.qstr = qstr; window.readURL = readURL; window.writeURL = writeURL; window.drill = drill; window.setF = setF;
diff --git a/public/index.html b/public/index.html
index d7076bd..c5d686e 100644
--- a/public/index.html
+++ b/public/index.html
@@ -316,6 +316,8 @@ footer.site a{color:var(--accent2)}
</div>
</footer>
+<style>a.drill{text-decoration:none}.card .surface.drill,.surface-tab.drill{cursor:pointer}.card .surface.drill{border-bottom:1px dotted transparent}.card .surface.drill:hover{border-bottom-color:currentColor;opacity:.85}</style>
+<script src="/drill.js"></script>
<script>
(function(){
// theme toggle
@@ -335,37 +337,74 @@ footer.site a{color:var(--accent2)}
var sortSel=document.getElementById('sort');
var savedSort=localStorage.getItem('waf-sort')||'newest';
sortSel.value=savedSort;
- sortSel.addEventListener('change',function(){try{localStorage.setItem('waf-sort',sortSel.value);}catch(e){}reload(1);});
+ sortSel.addEventListener('change',function(){try{localStorage.setItem('waf-sort',sortSel.value);}catch(e){}reload(1);sfWriteURL(false);});
// search debounce
var q=document.getElementById('q'); var qTimer=null;
- q.addEventListener('input',function(){clearTimeout(qTimer);qTimer=setTimeout(function(){reload(1);},250);});
+ q.addEventListener('input',function(){clearTimeout(qTimer);qTimer=setTimeout(function(){reload(1);sfWriteURL(false);},250);});
// surface tabs
var SURFACE_LABELS={all:'All',wallcovering:'Walls',fabric:'Fabrics',mural:'Murals',trim:'Trim'};
var state={surface:'all',page:1};
+ // ── href-to-deeper-data (HARD rule) — mirror the `surface` facet (+ q + sort) into the URL so any
+ // filtered view is a real shareable/deep-linkable href; surface values become .drill atoms. drill.js
+ // is loaded for the fleet atom contract + the enforcer canary; the pager logic below is this site's.
+ function sfQstr(){
+ var u=new URLSearchParams();
+ if(state.surface && state.surface!=='all') u.set('surface',state.surface);
+ var qv=q.value.trim(); if(qv) u.set('q',qv);
+ if(sortSel.value && sortSel.value!=='newest') u.set('sort',sortSel.value);
+ if(state.page && state.page>1) u.set('page',String(state.page));
+ var s=u.toString(); return s?('?'+s):location.pathname;
+ }
+ function sfWriteURL(push){ try{ history[push?'pushState':'replaceState']({},'',sfQstr()); }catch(_){} }
+ function sfReadURL(){
+ var u=new URLSearchParams(location.search);
+ state.surface=u.get('surface')||'all';
+ if(u.get('q')!=null) q.value=u.get('q');
+ if(u.get('sort')){ sortSel.value=u.get('sort'); try{localStorage.setItem('waf-sort',sortSel.value);}catch(e){} }
+ state.page=Math.max(1, parseInt(u.get('page')||'1',10)||1);
+ }
var tabs=document.getElementById('surfaceTabs');
+ // Set the surface facet, write it into the URL history, and reload the grid (drill-down).
+ function sfSetSurface(k, scroll){
+ state.surface=k||'all';
+ reload(1);
+ sfWriteURL(true);
+ if(scroll){ var g=document.getElementById('grid'); if(g) g.scrollIntoView({behavior:'smooth',block:'start'}); }
+ }
+ window.sfSetSurface=sfSetSurface;
function buildTabs(counts){
var totalAll=Object.values(counts).reduce(function(a,b){return a+b;},0);
var keys=['all','wallcovering','fabric','mural','trim'];
tabs.innerHTML=keys.map(function(k){
var n=k==='all'?totalAll:(counts[k]||0);
var label=SURFACE_LABELS[k]||k;
- return '<button class="surface-tab" data-surface="'+k+'" aria-pressed="'+(state.surface===k?'true':'false')+'">'+label+'<span class="count">'+n.toLocaleString()+'</span></button>';
+ // Each surface tab is a REAL href (?surface=k) so every count is a link to its deeper data.
+ var href=(k==='all')?location.pathname:('?surface='+encodeURIComponent(k));
+ return '<a class="surface-tab drill" href="'+href+'" data-filter="surface" data-surface="'+k+'" data-v="'+k+'" aria-pressed="'+(state.surface===k?'true':'false')+'">'+label+'<span class="count">'+n.toLocaleString()+'</span></a>';
}).join('');
- Array.from(tabs.querySelectorAll('.surface-tab')).forEach(function(b){
- b.addEventListener('click',function(){state.surface=b.dataset.surface;buildTabs(counts);reload(1);});
- });
}
- // nav links + footer browse links also drive surface
+ // Delegated: plain click on any surface .drill atom (tabs, nav, footer, in-card) filters in place;
+ // modifier / middle-click keeps the real href so it opens in a new tab.
+ document.addEventListener('click',function(e){
+ var d=e.target.closest('.drill[data-filter="surface"]'); if(!d) return;
+ if(e.metaKey||e.ctrlKey||e.shiftKey||e.altKey||e.button===1) return;
+ e.preventDefault(); e.stopPropagation();
+ sfSetSurface(d.dataset.v||d.dataset.surface, true);
+ });
+ addEventListener('popstate',function(){ sfReadURL(); reload(state.page); });
+ // nav links + footer browse links become real surface hrefs + drill atoms
Array.from(document.querySelectorAll('a[data-surface]')).forEach(function(a){
- a.addEventListener('click',function(e){e.preventDefault();state.surface=a.dataset.surface;reload(1);document.getElementById('grid').scrollIntoView({behavior:'smooth',block:'start'});});
+ var k=a.dataset.surface;
+ a.setAttribute('href', (k==='all')?'/':('?surface='+encodeURIComponent(k)));
+ a.classList.add('drill'); a.dataset.filter='surface'; a.dataset.v=k;
});
// pager
var prev=document.getElementById('prev'),next=document.getElementById('next'),info=document.getElementById('info');
- prev.addEventListener('click',function(){if(state.page>1){reload(state.page-1);}});
- next.addEventListener('click',function(){reload(state.page+1);});
+ prev.addEventListener('click',function(){if(state.page>1){reload(state.page-1);sfWriteURL(false);}});
+ next.addEventListener('click',function(){reload(state.page+1);sfWriteURL(false);});
function fmt$(p){if(!p||!p.max_price)return '';var n=Number(p.max_price);return isFinite(n)?'$'+n.toFixed(0):'';}
function card(p){
@@ -376,9 +415,11 @@ footer.site a{color:var(--accent2)}
var ttl=(p.title||'(untitled)').replace(/&/g,'&').replace(/</g,'<');
// Standing rule — never render DW vendor names on customer surfaces.
var brand='Designer Wallcoverings';
+ // The surface label is a drill atom (span, NOT a nested <a>) — the card itself is already an <a>.
+ var surfaceAtom='<span class="surface drill" data-filter="surface" data-surface="'+surface+'" data-v="'+surface+'" title="See all '+label+'">'+label+'</span>';
return '<a href="'+url+'" target="_blank" rel="noopener noreferrer" class="card" data-sku="'+(p.sku||p.handle||'')+'">'
+img
- +'<div class="body"><div class="surface">'+label+'</div><div class="ttl">'+ttl+'</div>'
+ +'<div class="body">'+surfaceAtom+'<div class="ttl">'+ttl+'</div>'
+'<div class="meta"><span>'+brand+'</span><span>'+fmt$(p)+'</span></div>'
+'<span class="smp">Request memo →</span></div></a>';
}
@@ -406,7 +447,9 @@ footer.site a{color:var(--accent2)}
}
document.getElementById('yr').textContent=new Date().getFullYear();
- reload(1);
+ sfReadURL(); // deep-link / back-button: hydrate surface + q + sort + page from the URL
+ reload(state.page); // render at the URL-provided state
+ sfWriteURL(false); // normalize the URL to the canonical filter-set
})();
</script>
← 4ba23af corner-nav: block javascript:/data: URIs in search preview (
·
back to Wallsandfabrics
·
Wire DW Meta Pixel 1431180262113856 + DW-network ad slot [ad 97292cb →