[object Object]

← back to Firecities

href-drill: add drill primitives to firecities (TK-10903)

9477ec9f1bf75cd87f3d0fe21a325f246d1b07b5 · 2026-08-27 04:47:12 -0700 · Steve

Files touched

Diff

commit 9477ec9f1bf75cd87f3d0fe21a325f246d1b07b5
Author: Steve <steve@designerwallcoverings.com>
Date:   Thu Aug 27 04:47:12 2026 -0700

    href-drill: add drill primitives to firecities (TK-10903)
---
 public/drill.js   | 36 ++++++++++++++++++++++++++++++++++++
 public/index.html | 17 +++++++++++++++--
 2 files changed, 51 insertions(+), 2 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 39e3ca7..59da93b 100644
--- a/public/index.html
+++ b/public/index.html
@@ -1,6 +1,7 @@
 <!doctype html>
 <html lang="en">
 <head>
+<script src="/drill.js"></script>
 <script>(function(){try{var t=localStorage.getItem('firecities-theme');document.documentElement.setAttribute('data-theme',t==='light'?'light':'dark');}catch(e){document.documentElement.setAttribute('data-theme','dark');}})();</script>
 <meta charset="utf-8">
 <meta name="viewport" content="width=device-width,initial-scale=1">
@@ -113,6 +114,16 @@
 </style>
 </head>
 <body>
+<input type="hidden" id="q" value=""><input type="hidden" id="pattern" value="">
+<script>
+// href-drill globals (TK-10903) — must be var so drill.js's free-variable refs resolve at call time
+var FIELDS=[['county','County'],['damage','Damage'],['city','City']];
+var FIL={county:'',damage:'',city:''};
+var $=function(s){return document.querySelector(s);};
+function esc(s){return String(s||'').replace(/[&<>"]/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'}[c]));}
+// applyFilters — placeholder; overridden inside the IIFE once loadPage is in scope
+var applyFilters=function(push){if(push&&typeof writeURL==='function')writeURL(true);if(typeof window.__fcLoadPage==='function')window.__fcLoadPage(0);};
+</script>
 <div class="wrap">
   <header class="top">
     <h1><span class="fl">🔥</span> California Fire Communities</h1>
@@ -259,6 +270,7 @@
   }
 
   // ── Load page ──────────────────────────────────────────────────────────────
+  window.__fcLoadPage = function(off){ loadPage(off); };  // expose for applyFilters (href-drill)
   function loadPage(off) {
     if (!dataOk) return;
     document.getElementById('loadingEl').style.display = 'block';
@@ -303,8 +315,8 @@
       var lastFire = c.lastDate ? c.lastDate.slice(0,4) + ' fire' : '';
       return '<div class="card" data-city="' + encodeURIComponent(c.city) + '" onclick="openCity(this)">' +
         '<span class="total-badge">' + Number(c.total).toLocaleString() + ' total</span>' +
-        '<div class="city-name">' + esc(c.city) + '</div>' +
-        '<div class="county">' + esc(c.county || '') + (c.county ? ' County' : '') + '</div>' +
+        '<div class="city-name">' + drill('city', c.city) + '</div>' +
+        '<div class="county">' + drill('county', c.county || '', esc(c.county || '') + (c.county ? ' County' : '')) + '</div>' +
         '<div class="dmg-row">' +
           dmgBadge('Destroyed', destroyed, 'destroyed') +
           dmgBadge('Major', major, 'major') +
@@ -429,6 +441,7 @@
   }
 
   // ── Init ───────────────────────────────────────────────────────────────────
+  readURL();  // restore URL filter state (href-drill TK-10903)
   loadStatus();
 })();
 </script>

← 193afb3 Initial CA Fire Communities DINS viewer  ·  back to Firecities  ·  (newest)