← back to Commercialrealestate
feat(index): collapsible filter tabs (all collapsed on load) + wait-for-selection empty state (no 2134-card dump on load) + ◆ Non-QM in-grid filter (unwarrantable condos + small multifamily 1-9u + mixed-use) + lazy assessor data points (assessed value / land-imp split / use-code / prior sale via /api/property-history, IntersectionObserver + eager first batch, concurrency-capped, cached)
e9d13a8d8540cd65ed9ac5e7716c8d9c91fd63b7 · 2026-07-02 10:29:40 -0700 · Steve Abrams
Files touched
Diff
commit e9d13a8d8540cd65ed9ac5e7716c8d9c91fd63b7
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Thu Jul 2 10:29:40 2026 -0700
feat(index): collapsible filter tabs (all collapsed on load) + wait-for-selection empty state (no 2134-card dump on load) + ◆ Non-QM in-grid filter (unwarrantable condos + small multifamily 1-9u + mixed-use) + lazy assessor data points (assessed value / land-imp split / use-code / prior sale via /api/property-history, IntersectionObserver + eager first batch, concurrency-capped, cached)
---
public/index.html | 81 +++++++++++++++++++++++++++++++++++++++++++++++++------
1 file changed, 73 insertions(+), 8 deletions(-)
diff --git a/public/index.html b/public/index.html
index 5ded577..48d6ea3 100644
--- a/public/index.html
+++ b/public/index.html
@@ -166,6 +166,12 @@
#drawer { position:sticky; top:57px; height:calc(100vh - 57px); transform:none; flex:0 0 var(--drawerW); box-shadow:none; }
#drawer .dhead .x { display:none; }
}
+ /* collapsible filter tabs */
+ .fsec > h4 { cursor:pointer; display:flex; align-items:center; justify-content:space-between; user-select:none; }
+ .fsec > h4::after { content:'▾'; color:var(--mut); font-size:11px; transition:transform .15s; margin-left:8px; }
+ .fsec.collapsed > h4::after { transform:rotate(-90deg); }
+ .fsec.collapsed > *:not(h4) { display:none !important; }
+ .assessline { border-top:1px dashed rgba(255,255,255,.06); padding-top:6px; margin-top:2px; }
</style>
</head>
<body>
@@ -551,6 +557,10 @@ const FIELDS = [
{k:'hoa', label:'HOA / mo (condo)', card:1, col:1, def:0},
{k:'warr', label:'Warrantability', card:1, col:1, def:0},
{k:'broker', label:'Broker (condo)', card:1, col:1, def:0},
+ {k:'assessed', label:'Assessed value ✦', card:1, col:0, def:0, assessor:1},
+ {k:'landimp', label:'Land / Improvement ✦',card:1, col:0, def:0, assessor:1},
+ {k:'usecode', label:'Assessor use code ✦', card:1, col:0, def:0, assessor:1},
+ {k:'priorsale', label:'Prior sale ✦', card:1, col:0, def:0, assessor:1},
];
const VIS = (function(){ let s={}; try{ s=JSON.parse(localStorage.getItem('cre_fields')||'{}'); }catch(e){} const o={}; FIELDS.forEach(f=>o[f.k]=(f.k in s)?!!s[f.k]:!!f.def); return o; })();
function vis(k){ return !!VIS[k]; }
@@ -651,7 +661,7 @@ function loopnetLink(p){
}
// Rich filter state
const F = { types:new Set(), firms:new Set(), cityQuery:'', priceMin:null, priceMax:null,
- capMin:0, unitsMin:null, yearMin:null, status:'all', afford:false, unwarrantable:false };
+ capMin:0, unitsMin:null, yearMin:null, status:'all', afford:false, unwarrantable:false, nonqm:false };
function recompute(){
const A = SCEN[scenario];
@@ -750,11 +760,54 @@ function card(p){
${vis('demo')?demoLine(p):''}
${vis('rent')?rentLine(p):''}
${vis('links')?`<div class="src"><a href="${p.source}" target="_blank" rel="noopener noreferrer">View listing ↗</a> · ${loopnetLink(p)} · <button class="lcbtn" data-id="${p.id}" title="Pull fresh comps from LoopNet via a cloud browser (~$0.03)">🔄 Live comps</button> · <button class="histbtn" data-id="${p.id}" data-address="${(p.address||'').replace(/"/g,'"')}" data-city="${(p.city||'').replace(/"/g,'"')}" data-zip="${p.zip||''}" title="LA County assessor + permit + film history (free)">📜 Property history</button></div>`:''}
+ ${anyAssessorVisible()?`<div class="assessline small" data-addr="${(p.address||'').replace(/"/g,'"')}" data-city="${(p.city||'').replace(/"/g,'"')}">📋 assessor…</div>`:''}
<div class="lcout" id="lc-${p.id}"></div>
<div class="histout" id="hist-${p.id}"></div>
</div>`;
}
+// ---- Non-QM target set: unwarrantable condos + small multifamily (1–9u) + mixed-use ----
+function isNonQM(p){
+ const t=(p.type||'').toLowerCase();
+ if(p.isCondo && p.warrantable_status && p.warrantable_status!=='fha_approved') return true;
+ if(/multi/.test(t) && p.units>=1 && p.units<=9) return true;
+ if(/mixed/.test(t)) return true;
+ return false;
+}
+// ---- lazy assessor enrichment (LA County parcel roll via /api/property-history, free) ----
+function anyAssessorVisible(){ return FIELDS.some(f=>f.assessor && vis(f.k)); }
+window.ASSESS = window.ASSESS || {};
+let assessObs = null;
+function assessLine(d){
+ const a=d&&(d.assessor||d.assessorArchive); const bits=[];
+ if(vis('assessed')){ const v=a&&(a.roll_total_value||a.total_value); if(v) bits.push('💰 assessed $'+(+v).toLocaleString()); }
+ if(vis('landimp')&&a){ const l=a.roll_land_value, im=a.roll_imp_value; if(l||im) bits.push('land $'+(+(l||0)).toLocaleString()+' / imp $'+(+(im||0)).toLocaleString()); }
+ if(vis('usecode')&&a){ const u=a.use_desc2||a.use_desc1||a.use_description; if(u) bits.push('🏷 '+u); }
+ if(vis('priorsale')&&d&&d.sales&&d.sales.length){ const s=d.sales[0]; bits.push('last sold $'+(+(s.sold_price||0)).toLocaleString()+' · '+String(s.sold_date||'').slice(0,10)); }
+ return bits.length ? ('📋 '+bits.join(' · ')) : '<span class="na">📋 no assessor record on file</span>';
+}
+function enrichAssessor(){
+ if(assessObs){ assessObs.disconnect(); assessObs=null; }
+ if(!anyAssessorVisible()) return;
+ const els=[...document.querySelectorAll('.assessline')];
+ const queue=[]; let active=0; const MAX=6;
+ function pump(){ while(active<MAX && queue.length){ fetchOne(queue.shift()); } }
+ function fetchOne(el){
+ const addr=el.dataset.addr, city=el.dataset.city, key=(addr+'|'+city).toLowerCase();
+ if(window.ASSESS[key]!==undefined){ el.innerHTML=assessLine(window.ASSESS[key]); pump(); return; }
+ active++;
+ fetch('/api/property-history?address='+encodeURIComponent(addr)+'&city='+encodeURIComponent(city||''))
+ .then(r=>r.json()).then(d=>{ window.ASSESS[key]=d; el.innerHTML=assessLine(d); })
+ .catch(()=>{ el.innerHTML='<span class="na">📋 assessor lookup failed</span>'; })
+ .finally(()=>{ active--; pump(); });
+ }
+ // As cards approach the viewport, queue them for enrichment (concurrency-capped).
+ assessObs=new IntersectionObserver((ents,obs)=>{ ents.forEach(en=>{ if(en.isIntersecting){ obs.unobserve(en.target); queue.push(en.target); pump(); } }); },{rootMargin:'400px'});
+ els.forEach(el=>assessObs.observe(el));
+ // Eager first batch so the top of the grid always resolves (no dependence on a scroll event).
+ els.slice(0,30).forEach(el=>{ assessObs.unobserve(el); queue.push(el); }); pump();
+}
+
// ---- the rich filter pass ----
function applyFilters(list){
return list.filter(p=>{
@@ -769,6 +822,7 @@ function applyFilters(list){
if(F.status==='active' && /Under Contract|Pending|OUT OF BUDGET/i.test(p.status)) return false;
if(F.afford && !p.finance.affordable) return false;
if(F.unwarrantable && !(p.isCondo && p.warrantable_status && p.warrantable_status!=='fha_approved')) return false;
+ if(F.nonqm && !isNonQM(p)) return false;
return true;
});
}
@@ -777,7 +831,7 @@ function activeFilterCount(){
if(F.types.size) n++; if(F.firms.size) n++; if(F.cityQuery) n++;
if(F.priceMin!=null||F.priceMax!=null) n++; if(F.capMin>0) n++;
if(F.unitsMin!=null) n++; if(F.yearMin!=null) n++;
- if(F.status!=='all') n++; if(F.afford) n++; if(F.unwarrantable) n++;
+ if(F.status!=='all') n++; if(F.afford) n++; if(F.unwarrantable) n++; if(F.nonqm) n++;
return n;
}
@@ -791,15 +845,21 @@ const num=v=>v==null?-1e9:v;
const dealNum=p=>(p.deal && p.deal.score!=null) ? p.deal.score : -1e9; // unscored sink to the bottom
function render(){
+ const g=$('#grid'); const fc=activeFilterCount();
+ $('#fcount').textContent=fc; $('#fcount').classList.toggle('on',fc>0);
+ // Wait-for-selection: on a clean load (no filter chosen) don't dump 2,134 cards — prompt for a pick.
+ if(fc===0 && !F.cityQuery){
+ g.className='grid';
+ g.innerHTML=`<div class="empty">👈 Pick an asset type or filter to begin.<br><span class="small">${(DATA.ranked.length).toLocaleString()} listings loaded · ${DATA.meta.market}. Try <b>Condo</b>, <b>⚑ Unwarrantable</b>, <b>◆ Non-QM</b>, a city, or a price range — results appear instantly.</span></div>`;
+ $('#count').innerHTML=`<b>${(DATA.ranked.length).toLocaleString()}</b> loaded · choose a filter to view`;
+ saveState(); return;
+ }
let r = applyFilters(DATA.ranked.slice());
r.sort(SORTS[$('#sort').value]||SORTS.composite);
- const g=$('#grid');
if(!r.length){ g.className='grid'; g.innerHTML=`<div class="empty">No properties match these filters.<br><span class="small">Loosen a filter or hit “Reset all”.</span></div>`; }
else if(view==='list'){ g.className='listhost'; g.innerHTML=renderList(r); }
- else { g.className='grid'; g.innerHTML=r.map(card).join(''); }
- const fc=activeFilterCount();
+ else { g.className='grid'; g.innerHTML=r.map(card).join(''); enrichAssessor(); }
$('#count').innerHTML = `<b>${r.length}</b> of ${DATA.ranked.length} properties${fc?` · ${fc} filter${fc>1?'s':''} active`:''}`;
- $('#fcount').textContent=fc; $('#fcount').classList.toggle('on',fc>0);
$('#foot').textContent = `${scenario==='cash'?'all-cash':SCEN[scenario].downPct+'% down @ '+SCEN[scenario].ratePct+'%'} · * →X% = projected cap after value-add (not in-place). Cap rates broker-stated — verify NOI in DD.`;
saveState();
}
@@ -812,8 +872,9 @@ function buildFacets(){
.map(([k,c])=>`<span class="chip" data-${key}="${k.replace(/"/g,'"')}">${k}<span class="ct">${c}</span></span>`).join('');
// Condos are merged into the grid as a real 'Condo' asset type (chip comes from the data below).
// Prepend one special in-grid filter: ⚑ Unwarrantable = Condo AND not FHA-approved (Arcstone non-QM target).
+ const nonqmChip='<span class="chip" data-warr="nonqm" style="border-color:rgba(188,140,255,.55);color:var(--purp,#bc8cff)" title="Arcstone non-QM target set: unwarrantable condos + small multifamily (1–9 units) + mixed-use">◆ Non-QM</span>';
const unwChip='<span class="chip" data-warr="unwarrantable" style="border-color:rgba(248,81,73,.55);color:var(--bad)" title="Non-FHA-approved condos — filters the grid to Arcstone non-QM targets">⚑ Unwarrantable</span>';
- $('#fType').innerHTML=unwChip+chipHTML(tCount,'t');
+ $('#fType').innerHTML=nonqmChip+unwChip+chipHTML(tCount,'t');
$('#fFirm').innerHTML=chipHTML(fCount,'fm');
// top 14 cities as quick chips; the search box covers the rest
const topCities=Object.entries(cCount).sort((a,b)=>b[1]-a[1]).slice(0,14);
@@ -867,7 +928,7 @@ function setScenario(s){ scenario=s; localStorage.setItem('cre_scenario',s);
$$('#scenario button').forEach(b=>b.classList.toggle('active',b.dataset.s===s)); recompute(); }
function syncChips(){
- $$('#fType .chip').forEach(c=>{ if(c.dataset.warr==='unwarrantable') c.classList.toggle('active',F.unwarrantable); else c.classList.toggle('active',F.types.has(c.dataset.t)); });
+ $$('#fType .chip').forEach(c=>{ if(c.dataset.warr==='unwarrantable') c.classList.toggle('active',F.unwarrantable); else if(c.dataset.warr==='nonqm') c.classList.toggle('active',F.nonqm); else c.classList.toggle('active',F.types.has(c.dataset.t)); });
$$('#fFirm .chip').forEach(c=>c.classList.toggle('active',F.firms.has(c.dataset.fm)));
$$('#fCityChips .chip').forEach(c=>c.classList.toggle('active',F.cityQuery===c.dataset.cy.toLowerCase()));
$$('#fStatus .chip').forEach(c=>c.classList.toggle('active',F.status===c.dataset.st));
@@ -993,8 +1054,12 @@ fetch('data/ranked.json').then(r=>r.json()).then(async d=>{
// drawer wiring
$('#burger').onclick=openDrawer; $('#dclose').onclick=closeDrawer; $('#scrim').onclick=closeDrawer;
$('#reset').onclick=resetAll;
+ // collapsible filter tabs — click a section header to expand/collapse; all collapsed on load
+ $('#drawer').addEventListener('click',e=>{ const h=e.target.closest('h4'); if(h && h.parentElement.classList.contains('fsec')) h.parentElement.classList.toggle('collapsed'); });
+ $$('#drawer .fsec').forEach(s=>s.classList.add('collapsed'));
// facet chip handlers (multi-select toggles)
$('#fType').onclick=e=>{ const c=e.target.closest('.chip'); if(!c) return;
+ if(c.dataset.warr==='nonqm'){ F.nonqm=!F.nonqm; syncChips(); render(); return; }
if(c.dataset.warr==='unwarrantable'){ F.unwarrantable=!F.unwarrantable; if(F.unwarrantable) F.types=new Set(['Condo']); syncChips(); render(); return; }
F.types.has(c.dataset.t)?F.types.delete(c.dataset.t):F.types.add(c.dataset.t);
if(!F.types.has('Condo')) F.unwarrantable=false;
← 44c3be8 feat(index+mls): merge condos IN-GRID as a real 'Condo' asse
·
back to Commercialrealestate
·
feat(index): active-filter summary bar — removable chips for 9dc699f →