[object Object]

← back to Nationalrealestate

States tab on rankings (51 states, raw metrics), national insight strip on map (median home value, % counties rising, median DOM), index favicon

ae4b420dfb1665bbd760c08c594354cdcce6e2e5 · 2026-07-21 15:44:22 -0700 · Steve Abrams

Files touched

Diff

commit ae4b420dfb1665bbd760c08c594354cdcce6e2e5
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Tue Jul 21 15:44:22 2026 -0700

    States tab on rankings (51 states, raw metrics), national insight strip on map (median home value, % counties rising, median DOM), index favicon
---
 public/index.html   | 22 ++++++++++++++++++++++
 public/markets.html |  1 +
 src/server/index.ts |  2 +-
 3 files changed, 24 insertions(+), 1 deletion(-)

diff --git a/public/index.html b/public/index.html
index 39079c1..196cb74 100644
--- a/public/index.html
+++ b/public/index.html
@@ -3,6 +3,7 @@
 <head>
 <meta charset="utf-8">
 <meta name="viewport" content="width=device-width, initial-scale=1">
+<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Crect width='16' height='16' rx='3' fill='%230a0d13'/%3E%3Ctext x='8' y='12' font-size='10' text-anchor='middle' fill='%23c8a24b'%3EUS%3C/text%3E%3C/svg%3E">
 <title>USRealEstate — National Market Map</title>
 <link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"
       integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY=" crossorigin="">
@@ -56,6 +57,9 @@
 <div id="map"></div>
 <div id="legend"><div class="t">Loading…</div></div>
 <div id="status">loading county data…</div>
+<div id="natstrip" style="position:fixed;left:50%;transform:translateX(-50%);bottom:18px;z-index:1000;
+  display:none;gap:22px;background:rgba(17,21,29,.94);border:1px solid var(--line);border-radius:10px;
+  padding:8px 18px;font-size:12px;color:var(--muted);white-space:nowrap;"></div>
 <div id="topmkts" style="position:fixed;right:14px;top:64px;z-index:1000;background:rgba(17,21,29,.94);
   border:1px solid var(--line);border-radius:10px;padding:10px 12px;max-width:250px;display:none;">
   <div style="font-size:12px;color:var(--gold);font-weight:700;margin-bottom:6px;">Top Markets
@@ -168,6 +172,23 @@ function onEachFeature(f, layer){
   layer.on('click', () => { location.href = `/market.html?key=county:${f.id}`; });
 }
 
+async function loadNationalStrip(){
+  try {
+    const [z, y, d] = await Promise.all(['zhvi','zhvi_yoy','dom'].map(m =>
+      fetch('/api/choropleth?metric=' + m).then(r => r.json())));
+    const med = obj => { const v = Object.values(obj.values || {}).filter(Number.isFinite).sort((a,b)=>a-b);
+      return v.length ? v[Math.floor(v.length/2)] : null; };
+    const yv = Object.values(y.values || {}).filter(Number.isFinite);
+    const rising = yv.length ? Math.round(100 * yv.filter(v => v > 0).length / yv.length) : null;
+    const parts = [];
+    const mz = med(z); if (mz != null) parts.push(`median county home value <b style="color:var(--gold)">$${Math.round(mz).toLocaleString()}</b>`);
+    if (rising != null) parts.push(`<b style="color:${rising >= 50 ? '#3fb950' : '#f85149'}">${rising}%</b> of counties rising YoY`);
+    const md = med(d); if (md != null) parts.push(`median DOM <b style="color:var(--gold)">${Math.round(md)}d</b>`);
+    if (parts.length){ const el = document.getElementById('natstrip');
+      el.innerHTML = parts.map(p => `<span>${p}</span>`).join(''); el.style.display = 'flex'; }
+  } catch {}
+}
+
 async function loadTopMarkets(){
   try {
     const d = await fetch('/api/markets?type=county').then(r => r.json());
@@ -193,6 +214,7 @@ async function loadTopMarkets(){
   geojson = geo;
   geoLayer = L.geoJSON(geo, { style: styleFeature, onEachFeature }).addTo(map);
   loadTopMarkets();
+  loadNationalStrip();
 })();
 </script>
 </body>
diff --git a/public/markets.html b/public/markets.html
index 206eeb4..9fcb9ae 100644
--- a/public/markets.html
+++ b/public/markets.html
@@ -45,6 +45,7 @@
   <span id="typeSeg" style="margin-left:0">
     <button data-type="county">Counties</button>
     <button data-type="metro">Metros</button>
+    <button data-type="state">States</button>
   </span>
 </div>
 <div class="cg-shell">
diff --git a/src/server/index.ts b/src/server/index.ts
index bcbf907..de4785c 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -125,7 +125,7 @@ const MARKETS_TTL_MS = 5 * 60_000;
 app.get('/api/markets', async (req, res) => {
   try {
     const type = String(req.query.type || 'county');
-    if (type !== 'county' && type !== 'metro') return res.status(400).json({ error: 'type must be county|metro' });
+    if (type !== 'county' && type !== 'metro' && type !== 'state') return res.status(400).json({ error: 'type must be county|metro|state' });
     const hit = marketsCache.get(type);
     if (hit && Date.now() - hit.at < MARKETS_TTL_MS) return res.json(hit.body);
     const metricsQ = query<{ region_id: number; metric: string; value: string }>(

← fabf9ec auto-save: 2026-07-21T15:38:16 (1 files) — src/jobs/broker_r  ·  back to Nationalrealestate  ·  CSV export on rankings (/api/markets.csv, type-synced downlo 58c461c →