← back to CelebritySignatures
CelebritySignatures: href-drill — category + usability are filter-links (URL-addressable)
fb921293313859b3c81938861a9f0ff48ad3775a · 2026-08-01 02:14:41 -0700 · Steve Abrams
Card category tag + usability badge are now <a href="?cat=…|use=…"> to their filtered
view (href-drill HARD rule); cat + use filter state is URL-addressable (shareable,
deep-linkable, back-button-safe via popstate/readURL). Click filters in place; cmd/ctrl-click
opens the shareable href. Chips / use-dropdown / sort / density / collages view untouched.
Verified LOCAL (Playwright, :9922): click category 'Oldest Signatures' -> ?cat + grid
128->18 all-matching; deep-link restores; back-button->128; canary FLAT->DONE; 0 pageerrors.
PUBLIC/customer-facing — local commit only; DEPLOY gated (pending-approval). grid 9/92. TK-10089.
Files touched
Diff
commit fb921293313859b3c81938861a9f0ff48ad3775a
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Sat Aug 1 02:14:41 2026 -0700
CelebritySignatures: href-drill — category + usability are filter-links (URL-addressable)
Card category tag + usability badge are now <a href="?cat=…|use=…"> to their filtered
view (href-drill HARD rule); cat + use filter state is URL-addressable (shareable,
deep-linkable, back-button-safe via popstate/readURL). Click filters in place; cmd/ctrl-click
opens the shareable href. Chips / use-dropdown / sort / density / collages view untouched.
Verified LOCAL (Playwright, :9922): click category 'Oldest Signatures' -> ?cat + grid
128->18 all-matching; deep-link restores; back-button->128; canary FLAT->DONE; 0 pageerrors.
PUBLIC/customer-facing — local commit only; DEPLOY gated (pending-approval). grid 9/92. TK-10089.
---
public/index.html | 21 +++++++++++++++++----
1 file changed, 17 insertions(+), 4 deletions(-)
diff --git a/public/index.html b/public/index.html
index cca09ff..3b6b03e 100644
--- a/public/index.html
+++ b/public/index.html
@@ -92,6 +92,12 @@ const notability = (r) => { const m=(r.reason_for_ranking||'').match(/(\d+) lang
const riskRank = { low:0, medium:1, high:2 };
function save() { localStorage.setItem(LS, JSON.stringify(prefs)); }
+// ── href-drill: filter state (category / usability) is URL-addressable (shareable + deep-link + back-button) ──
+const esc=s=>String(s==null?'':s).replace(/[&<>"]/g,c=>({'&':'&','<':'<','>':'>','"':'"'}[c]));
+function qstr(over){ const c=(over&&'cat'in over)?over.cat:prefs.cat, u=(over&&'use'in over)?over.use:prefs.use; const p=[]; if(c&&c!=='All')p.push('cat='+encodeURIComponent(c)); if(u&&u!=='all')p.push('use='+encodeURIComponent(u)); return p.length?('?'+p.join('&')):location.pathname; }
+function drill(dim,val,label){ if(val==null||val==='')return esc(label!=null?label:val); return `<a class="drill" data-filter="${dim}" data-v="${esc(val)}" href="${esc(qstr({[dim]:String(val)}))}" title="Filter to ${esc(val)}">${esc(label!=null?label:val)}</a>`; }
+function readURL(){ const u=new URLSearchParams(location.search); if(u.has('cat'))prefs.cat=u.get('cat')||'All'; if(u.has('use'))prefs.use=u.get('use')||'all'; }
+function writeURL(push){ history[push?'pushState':'replaceState']({},'',qstr()); }
function render() {
document.documentElement.style.setProperty('--cols', prefs.density);
@@ -121,7 +127,7 @@ function render() {
<div class="sig"><img loading="lazy" src="${r.signature_image_url}" alt="Signature of ${r.full_name}" onerror="this.parentNode.innerHTML='<span style=color:#bbb;font-size:12px>image unavailable</span>'"></div>
<div class="meta">
<div class="name">${r.full_name}</div>
- <div class="row"><span class="tag">${r.category}</span><span class="badge b-${r.risk_level}">${r.risk_level}</span><span class="badge ${uClass}">${uText}</span></div>
+ <div class="row"><span class="tag">${drill('cat',r.category)}</span><span class="badge b-${r.risk_level}">${r.risk_level}</span><span class="badge ${uClass}">${drill('use',ub,uText)}</span></div>
<div class="sub">${r.deceased==='yes'?'† ':'● living · '}${(r.image_license||'').slice(0,28)}</div>
<div class="sub"><a href="${r.wikidata}" target="_blank" rel="noopener noreferrer">Wikidata</a> · <a href="${r.backup_source}" target="_blank" rel="noopener noreferrer">source</a></div>
</div></div>`;
@@ -131,11 +137,18 @@ function render() {
function buildChips() {
const cats = ['All','Oldest Signatures','Declaration of Independence','Politics'];
$('#catChips').innerHTML = cats.map(c => `<span class="chip${c===prefs.cat?' on':''}" data-c="${c}">${c}</span>`).join('');
- $('#catChips').onclick = (e) => { const c=e.target.dataset.c; if(!c) return; prefs.cat=c; save();
+ $('#catChips').onclick = (e) => { const c=e.target.dataset.c; if(!c) return; prefs.cat=c; save(); writeURL(true);
document.querySelectorAll('.chip').forEach(x=>x.classList.toggle('on', x.dataset.c===c)); render(); };
}
$('#density').oninput = (e) => { prefs.density=+e.target.value; save(); render(); };
-$('#useFilter').onchange = (e) => { prefs.use=e.target.value; save(); render(); };
+$('#useFilter').onchange = (e) => { prefs.use=e.target.value; save(); writeURL(true); render(); };
+// a card data point (category / usability) is an href-drill atom → filter in place + reflect into the URL
+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, dflt=dim==='cat'?'All':'all';
+ prefs[dim] = (prefs[dim]===v?dflt:v); save(); writeURL(true);
+ document.querySelectorAll('.chip').forEach(x=>x.classList.toggle('on', x.dataset.c===prefs.cat)); if($('#useFilter')) $('#useFilter').value=prefs.use; render(); });
+addEventListener('popstate', () => { const u=new URLSearchParams(location.search); prefs.cat=u.get('cat')||'All'; prefs.use=u.get('use')||'all';
+ document.querySelectorAll('.chip').forEach(x=>x.classList.toggle('on', x.dataset.c===prefs.cat)); if($('#useFilter')) $('#useFilter').value=prefs.use; render(); });
$('#sort').onchange = (e) => { prefs.sort=e.target.value; save(); render(); };
const slug = (s) => s.toLowerCase().replace(/[^a-z0-9]+/g,'-').replace(/^-|-$/g,'');
@@ -167,7 +180,7 @@ function setView(v) {
}
document.querySelector('.tabs').onclick = (e) => { const v=e.target.dataset.v; if(v) setView(v); };
-fetch('/api/signatures').then(r=>r.json()).then(d => { DATA=d; buildChips(); render(); setView(prefs.view); });
+fetch('/api/signatures').then(r=>r.json()).then(d => { DATA=d; readURL(); buildChips(); render(); setView(prefs.view); });
</script>
</body>
</html>
← b3b1355 chore: macstudio3 migration — reconcile from mac2 + repoint
·
back to CelebritySignatures
·
fix: add package.json (type:module) so the ESM server.js can 31e56bf →