← back to Corkwallcovering
Reapply "corkwallcovering: adopt href-to-deeper-data storefront primitives (TK-10093)"
82701521804274068467a468adde88d18fecbbf6 · 2026-08-01 11:52:34 -0700 · Steve Abrams
This reverts commit c50cacec83ba25938ee73b519d78108eb71d81a0.
Files touched
Diff
commit 82701521804274068467a468adde88d18fecbbf6
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Sat Aug 1 11:52:34 2026 -0700
Reapply "corkwallcovering: adopt href-to-deeper-data storefront primitives (TK-10093)"
This reverts commit c50cacec83ba25938ee73b519d78108eb71d81a0.
---
public/index.html | 76 ++++++++++++++++++++++++++++++++++++++++++++++++++-----
1 file changed, 69 insertions(+), 7 deletions(-)
diff --git a/public/index.html b/public/index.html
index b771cb8..9b7922b 100644
--- a/public/index.html
+++ b/public/index.html
@@ -600,6 +600,8 @@ textarea:focus-visible,
})();
</script>
+<style>a.drill.chip{text-decoration:none}.r-aes .drill,.d-chip.drill,.fchip.drill{cursor:pointer;text-decoration:none}.r-aes .drill{border-bottom:1px dotted transparent}.r-aes .drill:hover,.d-chip.drill:hover{border-bottom-color:currentColor;opacity:.85}</style>
+<script src="/drill.js"></script>
<script>
const state = { q:'', facet:'all', sort: localStorage.getItem('cork_sort') || 'newest', view:'grid', page:1, pages:1, total:0, loading:false, exhausted:false };
const TOUCH = window.matchMedia('(hover: none)').matches;
@@ -636,7 +638,55 @@ function facetParams(){
return p;
}
function persistFacets(){ for (const g of FACET_GROUPS) localStorage.setItem('cork_'+g.key, JSON.stringify([...state[g.key]])); }
-function applyFilterChange(){ persistFacets(); loadFacets(); resetGrid(); }
+function applyFilterChange(){ persistFacets(); sfWriteURL(true); loadFacets(); resetGrid(); }
+
+// ── href-to-deeper-data (HARD rule) — multi-facet model: mirror facetParams() into the URL so
+// every filtered view is a real shareable/deep-linkable href. drill.js is loaded for the fleet
+// atom contract + the enforcer canary; the left-nav facet logic below is storefront-specific.
+function sfWriteURL(push){
+ try {
+ const qs = facetParams();
+ if (state.sort && state.sort !== 'newest') qs.set('sort', state.sort);
+ const s = qs.toString();
+ history[push ? 'pushState' : 'replaceState']({}, '', s ? ('?' + s) : location.pathname);
+ } catch(_){}
+}
+function sfReadURL(){
+ const u = new URLSearchParams(location.search);
+ state.q = u.get('q') || '';
+ if (u.get('sort')) state.sort = u.get('sort');
+ for (const g of FACET_GROUPS) {
+ const raw = u.get(g.key);
+ state[g.key] = new Set(raw ? raw.split(',').filter(Boolean) : []);
+ }
+ const si = document.getElementById('searchInput'); if (si) si.value = state.q;
+ const ss = document.getElementById('sortSelect'); if (ss && u.get('sort')) ss.value = state.sort;
+}
+// Build the href for a facet value toggled ON (drill in) against the current filter state.
+function sfFacetHref(gkey, val){
+ const u = new URLSearchParams(location.search);
+ const cur = new Set((u.get(gkey) || '').split(',').filter(Boolean));
+ cur.add(String(val)); u.set(gkey, [...cur].join(','));
+ return '?' + u.toString();
+}
+// a color_family drill atom (row/card) — href IS the filtered view; tag='span' inside the card <a>.
+function sfDrill(gkey, value, label, tag){
+ if (value == null || value === '') return escAttr(label == null ? '' : label);
+ const t = tag || 'a';
+ const hattr = (t === 'a') ? (' href="' + escAttr(sfFacetHref(gkey, value)) + '"') : '';
+ return '<' + t + ' class="drill" data-fgroup="' + escAttr(gkey) + '" data-v="' + escAttr(value) + '"' + hattr + ' title="See all ' + escAttr(String(label)) + '">' + escAttr(label) + '</' + t + '>';
+}
+// delegated: plain click on any facet drill atom toggles that value ON in-place; modifier/middle keeps href
+document.addEventListener('click', function(e){
+ const d = e.target.closest('.drill[data-fgroup]'); if (!d) return;
+ if (e.metaKey||e.ctrlKey||e.shiftKey||e.altKey||e.button===1) return;
+ e.preventDefault(); e.stopPropagation();
+ const gkey = d.dataset.fgroup, val = d.dataset.v;
+ if (state[gkey] && !state[gkey].has(val)) { state[gkey].add(val); applyFilterChange(); }
+ dwmClose('Details');
+ const shop = document.getElementById('shop'); if (shop) shop.scrollIntoView({behavior:'smooth'});
+});
+addEventListener('popstate', function(){ sfReadURL(); loadFacets(); resetGrid(); });
function escAttr(s) { return String(s == null ? '' : s).replace(/[&<>"']/g, c => ({ '&':'&','<':'<','>':'>','"':'"',"'":''' }[c])); }
function cleanSku(s){
@@ -671,7 +721,7 @@ function rowHTML(p) {
return '<img loading="lazy" src="' + escAttr(safeImg(p.image_url)) + '" alt="' + escAttr(p.title) + '">'
+ '<div class="r-pat">' + escAttr(p.pattern_name || p.title) + '</div>'
+ '<div class="r-col r-sku">' + escAttr(cleanSku(p.sku || p.handle_display || p.handle) || '—') + '</div>'
- + '<div class="r-col r-aes">' + escAttr(String(aes).replace(/-/g, ' ')) + '</div>'
+ + '<div class="r-col r-aes">' + (p.color_family ? sfDrill('colors', p.color_family, String(p.color_family).replace(/-/g, ' '), 'span') : escAttr(String(aes).replace(/-/g, ' '))) + '</div>'
+ '<div class="r-col r-price">' + price + '</div>';
}
@@ -701,8 +751,9 @@ function openDetails(p) {
chipsEl.innerHTML = '';
for (const a of safeP.aesthetic) {
if (!a) continue;
- const c = document.createElement('span');
- c.className = 'd-chip';
+ const c = document.createElement('a');
+ c.className = 'd-chip drill'; c.dataset.fgroup = 'colors'; c.dataset.v = a;
+ c.href = sfFacetHref('colors', a);
c.textContent = (LABELS[a] || a).replace(/-/g, ' ');
chipsEl.appendChild(c);
}
@@ -744,10 +795,17 @@ async function loadFacets() {
const chips = document.createElement('div'); chips.className = 'fchips';
for (const [val, ct] of entries) {
const on = set.has(val);
- const el = document.createElement('span');
- el.className = 'fchip' + (on ? ' on' : '') + (ct ? '' : ' zero');
+ const el = document.createElement('a');
+ el.className = 'fchip drill' + (on ? ' on' : '') + (ct ? '' : ' zero');
+ el.dataset.fgroup = g.key; el.dataset.v = val;
+ el.href = sfFacetHref(g.key, val);
el.innerHTML = (g.color ? '<span class="dot" style="background:' + (FAMILY_DOT[val] || '#888') + '"></span>' : '') + val + ' <span class="ct">' + ct + '</span>';
- el.onclick = () => { on ? set.delete(val) : set.add(val); applyFilterChange(); };
+ // toggle in place (delegated handler only ADDS; here we support toggle-off for an active chip)
+ el.addEventListener('click', ev => {
+ if (ev.metaKey||ev.ctrlKey||ev.shiftKey||ev.altKey||ev.button===1) return;
+ ev.preventDefault(); ev.stopPropagation();
+ on ? set.delete(val) : set.add(val); applyFilterChange();
+ });
chips.appendChild(el);
}
wrap.appendChild(chips); groups.appendChild(wrap);
@@ -840,6 +898,7 @@ document.getElementById('searchInput').addEventListener('input', e => {
clearTimeout(window._t);
window._t = setTimeout(() => {
resetGrid();
+ sfWriteURL(false);
// On mobile the results grid sits below the fold (under the keyboard), so a search
// looks like "nothing happened" even though it filtered. Bring results into view.
if (state.q) {
@@ -941,6 +1000,7 @@ if (sortSel) {
state.sort = e.target.value;
try { localStorage.setItem('cork_sort', state.sort); } catch(e){}
if (typeof syncColHeaders === 'function') syncColHeaders();
+ sfWriteURL(true);
resetGrid();
});
}
@@ -964,6 +1024,8 @@ const railClose = document.getElementById('railClose'); if (railClose) railClose
if (location.hash === '#filters') { setFilters(true); try { document.getElementById('shop').scrollIntoView(); } catch(e){} }
else setFilters(false); // collapsed on load by default
+// Deep-link: if the URL carries filter params, hydrate from it (overrides the localStorage sets).
+if (location.search && /[?&](q|sort|colors|patterns|styles|materials|types|prices)=/.test(location.search)) sfReadURL();
loadFacets();
loadGridPage();
</script>
← c50cace Revert "corkwallcovering: adopt href-to-deeper-data storefro
·
back to Corkwallcovering
·
GA4: propagate site gtag to untagged pages (fleet coverage) 6c7f852 →