← back to Commercialrealestate
mls + properties grid: Day theme default (fresh key), Fields panel to top+expanded, panel drag-to-reorder columns
1229a737fc665e42cf9b31649dcb020520a5955c · 2026-08-07 16:32:59 -0700 · Steve Abrams
Files touched
M public/index.htmlM public/mls.html
Diff
commit 1229a737fc665e42cf9b31649dcb020520a5955c
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Fri Aug 7 16:32:59 2026 -0700
mls + properties grid: Day theme default (fresh key), Fields panel to top+expanded, panel drag-to-reorder columns
---
public/index.html | 83 +++++++++++++++++++++++++++++++++++++++++++++++--------
public/mls.html | 67 ++++++++++++++++++++++++++++++++++++++------
2 files changed, 131 insertions(+), 19 deletions(-)
diff --git a/public/index.html b/public/index.html
index ea91b62..dca815a 100644
--- a/public/index.html
+++ b/public/index.html
@@ -1,7 +1,7 @@
<!doctype html>
<html lang="en">
<head>
-<script>(function(){try{var t=localStorage.getItem('crcp-theme');document.documentElement.setAttribute('data-theme',t==='light'?'light':'dark');}catch(e){document.documentElement.setAttribute('data-theme','dark');}})();</script>
+<script>(function(){try{var t=localStorage.getItem('crcp-theme2');document.documentElement.setAttribute('data-theme',t==='dark'?'dark':'light');}catch(e){document.documentElement.setAttribute('data-theme','light');}})();</script>
<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='%230e1116'/%3E%3Ctext x='8' y='12' font-size='11' text-anchor='middle' fill='%233fb950'%3E%24%3C/text%3E%3C/svg%3E">
@@ -164,6 +164,13 @@
.ftog:has(input:checked) { border-color:var(--blue); color:var(--blue); background:rgba(88,166,255,.10); }
.ftog:has(input:checked)::before { content:'●'; color:var(--blue); }
.ftog .colbadge { color:var(--mut); font-size:10px; }
+ /* panel drag-to-reorder (Fix 3) */
+ .ftog .flbl { flex:1; }
+ .ftog .fdrag { cursor:grab; color:var(--mut); font-size:12px; opacity:.5; user-select:none; line-height:1; margin-right:2px; }
+ .ftog:hover .fdrag { opacity:1; }
+ .ftog.fdragging { opacity:.45; }
+ .ftog.fdropT { box-shadow:inset 0 3px 0 var(--gold); }
+ .ftog.fdropB { box-shadow:inset 0 -3px 0 var(--gold); }
.minibtn { background:var(--card); color:var(--mut); border:1px solid var(--line); border-radius:7px; padding:4px 9px; font-size:11px; cursor:pointer; margin-right:5px; }
.minibtn:hover { border-color:var(--blue); color:var(--ink); }
/* ---- list view ---- */
@@ -410,6 +417,11 @@
<aside id="drawer">
<div class="dhead"><b>Filters</b><button class="x" id="dclose" aria-label="Close">✕</button></div>
+ <div class="fsec" id="fieldsSec"><h4>Fields — show / hide · reorder</h4>
+ <div class="fieldtoggles" id="fFields"></div>
+ <div style="margin-top:8px"><button class="minibtn" id="fAll">All</button><button class="minibtn" id="fNone">None</button><button class="minibtn" id="fDef">Default</button><button class="minibtn" id="idxReset" title="Reset List columns to the tight default — order, widths & visibility">↺ Reset layout</button></div>
+ <div class="small" style="margin-top:6px">Drag a row by its ⠿ handle to reorder columns. ▤ = also a column in List view · drag a List header to reorder · drag its right edge to resize</div>
+ </div>
<div class="fsec"><h4>Saved searches</h4><div id="savedList"></div><button id="savebtn">💾 Save current view</button></div>
<div class="fsec"><h4>Asset type</h4><div class="chips" id="fType"></div></div>
<div class="fsec"><h4>Listing firm</h4><div class="chips" id="fFirm"></div></div>
@@ -453,11 +465,6 @@
<div style="margin-top:10px"><label class="small">Cards per row (grid) <span class="rangeval" id="densVal">3</span></label>
<input type="range" id="density" min="1" max="10" step="1" value="3"></div>
</div>
- <div class="fsec"><h4>Fields — show / hide</h4>
- <div class="fieldtoggles" id="fFields"></div>
- <div style="margin-top:8px"><button class="minibtn" id="fAll">All</button><button class="minibtn" id="fNone">None</button><button class="minibtn" id="fDef">Default</button><button class="minibtn" id="idxReset" title="Reset List columns to the tight default — order, widths & visibility">↺ Reset layout</button></div>
- <div class="small" style="margin-top:6px">▤ = also a column in List view · drag a List header to reorder · drag its right edge to resize</div>
- </div>
<button class="resetbtn" id="reset">Reset all filters</button>
</aside>
@@ -813,7 +820,14 @@ function applyHeat(rows){
}
function buildFieldToggles(){
const host=$('#fFields'); if(!host) return;
- host.innerHTML = FIELDS.map(f=>`<label class="ftog"><input type="checkbox" data-fk="${f.k}" ${vis(f.k)?'checked':''}><span>${f.label}</span>${f.col?'<span class="colbadge" title="Also shows as a column in List view">▤</span>':''}</label>`).join('');
+ // emit rows in COLORDER so panel order matches list column order; non-col fields appended after
+ const ordered=(function(){
+ syncColOrder();
+ const colFs=COLORDER.map(k=>FIELDS.find(f=>f.k===k)).filter(Boolean);
+ const rest=FIELDS.filter(f=>!f.col);
+ return colFs.concat(rest);
+ })();
+ host.innerHTML = ordered.map(f=>`<label class="ftog" draggable="true" data-fk="${f.k}"><span class="fdrag" title="Drag to reorder column">⠿</span><input type="checkbox" data-fk="${f.k}" ${vis(f.k)?'checked':''}><span class="flbl">${f.label}</span>${f.col?'<span class="colbadge" title="Also shows as a column in List view">▤</span>':''}</label>`).join('');
}
// ---- extra computed data points ----
function ppu(p){ return (p.price&&p.units)?Math.round(p.price/p.units):null; }
@@ -1908,9 +1922,8 @@ function loadRanked(_retry){ return fetch('data/ranked.json').then(r=>{ if(!r.ok
$('#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'));
- // Fields section starts EXPANDED so the field-toggle cards are visible; the rest stay collapsed.
- const fieldsSec=$('#fFields')&&$('#fFields').closest('.fsec'); if(fieldsSec) fieldsSec.classList.remove('collapsed');
+ // Fields section starts EXPANDED and first — all other fsec sections collapse on load.
+ $$('#drawer .fsec').forEach(s=>{ if(s.id!=='fieldsSec') s.classList.add('collapsed'); });
// active-filter summary bar: remove one filter or reset all
$('#activebar').addEventListener('click',e=>{ if(e.target.closest('#afreset')){ resetAll(); return; } const c=e.target.closest('.afchip'); if(c) clearFilter(c.dataset.clear); });
// saved searches
@@ -1962,6 +1975,28 @@ function loadRanked(_retry){ return fetch('data/ranked.json').then(r=>{ if(!r.ok
$('#fAll').onclick=()=>{ FIELDS.forEach(f=>VIS[f.k]=true); saveVis(); buildFieldToggles(); render(); };
$('#fNone').onclick=()=>{ FIELDS.forEach(f=>VIS[f.k]=false); saveVis(); buildFieldToggles(); render(); };
$('#fDef').onclick=()=>{ FIELDS.forEach(f=>VIS[f.k]=!!f.def); saveVis(); buildFieldToggles(); render(); };
+ // ── panel drag-to-reorder columns (Fix 3) ──
+ // Dragging the ⠿ handle on a .ftog row reorders COLORDER then rebuilds toggles + re-renders list.
+ let pDragKey=null;
+ $('#fFields').addEventListener('dragstart',e=>{ const row=e.target.closest('.ftog'); if(!row) return;
+ if(!e.target.closest('.fdrag')){ e.preventDefault(); return; } // only ⠿ handle starts a move
+ pDragKey=row.dataset.fk; e.dataTransfer.effectAllowed='move'; try{e.dataTransfer.setData('text/plain',pDragKey);}catch(_){}
+ row.classList.add('fdragging'); });
+ $('#fFields').addEventListener('dragover',e=>{ if(!pDragKey) return; const row=e.target.closest('.ftog'); if(!row||row.dataset.fk===pDragKey) return;
+ e.preventDefault(); e.dataTransfer.dropEffect='move';
+ const r=row.getBoundingClientRect(), after=(e.clientY-r.top)>r.height/2;
+ $('#fFields').querySelectorAll('.ftog').forEach(x=>x.classList.remove('fdropT','fdropB'));
+ row.classList.toggle('fdropB',after); row.classList.toggle('fdropT',!after); });
+ $('#fFields').addEventListener('dragleave',e=>{ const row=e.target.closest('.ftog'); if(row) row.classList.remove('fdropT','fdropB'); });
+ $('#fFields').addEventListener('drop',e=>{ if(!pDragKey) return; const row=e.target.closest('.ftog'); if(!row){ pDragKey=null; return; } e.preventDefault();
+ const tgt=row.dataset.fk;
+ if(tgt!==pDragKey){
+ const r=row.getBoundingClientRect(), after=(e.clientY-r.top)>r.height/2;
+ syncColOrder(); const from=COLORDER.indexOf(pDragKey);
+ if(from>=0){ COLORDER.splice(from,1); let to=COLORDER.indexOf(tgt); if(after) to+=1; COLORDER.splice(to,0,pDragKey);
+ saveColOrder(); lastColSig=''; buildFieldToggles(); render(); } }
+ pDragKey=null; });
+ $('#fFields').addEventListener('dragend',()=>{ $('#fFields').querySelectorAll('.fdragging,.fdropT,.fdropB').forEach(x=>x.classList.remove('fdragging','fdropT','fdropB')); pDragKey=null; });
$$('#viewseg button').forEach(b=>b.classList.toggle('active',b.dataset.view===view));
$('#viewseg').onclick=e=>{ const b=e.target.closest('button[data-view]'); if(!b) return; view=b.dataset.view; localStorage.setItem('cre_view',view); $$('#viewseg button').forEach(x=>x.classList.toggle('active',x.dataset.view===view)); render(); };
@@ -2018,6 +2053,32 @@ function loadRanked(_retry){ return fetch('data/ranked.json').then(r=>{ if(!r.ok
<script src="/col-resize.js" defer></script>
<script src="/global-search.js" defer></script>
<script src="/corner-nav.js" defer></script>
-<script src="/crcp-theme.js" defer></script>
+<script>
+// Day/Night toggle — theme already applied before first paint by the head snippet.
+// crcp-theme2 key; Day (light) is the default. Only an explicit click persists.
+(function(){
+ var root=document.documentElement;
+ function label(t){
+ var b=document.getElementById('crcpThemeToggle'); if(!b) return;
+ b.innerHTML=(t==='light'?'☀️':'🌙')+' <span>'+(t==='light'?'Day':'Night')+'</span>';
+ }
+ function applyTheme(t){ root.setAttribute('data-theme',t); label(t); }
+ function mount(){
+ if(document.getElementById('crcpThemeToggle')){ label(root.getAttribute('data-theme')||'light'); return; }
+ var btn=document.createElement('button');
+ btn.id='crcpThemeToggle'; btn.type='button';
+ btn.title='Toggle day / night'; btn.setAttribute('aria-label','Toggle day / night theme');
+ btn.addEventListener('click',function(){
+ var nt=root.getAttribute('data-theme')==='light'?'dark':'light';
+ applyTheme(nt);
+ try{ localStorage.setItem('crcp-theme2',nt); }catch(e){}
+ });
+ var host=document.querySelector('header .nav')||document.querySelector('header');
+ if(host){ host.appendChild(btn); } else { btn.style.cssText+=';position:fixed;top:12px;right:14px;z-index:99999'; document.body.appendChild(btn); }
+ label(root.getAttribute('data-theme')||'light');
+ }
+ if(document.readyState==='loading') document.addEventListener('DOMContentLoaded',mount); else mount();
+})();
+</script>
</body>
</html>
diff --git a/public/mls.html b/public/mls.html
index 7135dbc..865f715 100644
--- a/public/mls.html
+++ b/public/mls.html
@@ -1,7 +1,7 @@
<!doctype html>
<html lang="en">
<head>
-<script>(function(){try{var t=localStorage.getItem('crcp-theme');document.documentElement.setAttribute('data-theme',t==='light'?'light':'dark');}catch(e){document.documentElement.setAttribute('data-theme','dark');}})();</script>
+<script>(function(){try{var t=localStorage.getItem('crcp-theme2');document.documentElement.setAttribute('data-theme',t==='dark'?'dark':'light');}catch(e){document.documentElement.setAttribute('data-theme','light');}})();</script>
<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='%230e1116'/%3E%3Ctext x='8' y='12' font-size='10' text-anchor='middle' fill='%233fb950'%3EMLS%3C/text%3E%3C/svg%3E">
@@ -81,6 +81,10 @@
.rail input[type=number]{width:100%;background:var(--card);color:var(--ink);border:1px solid var(--line);border-radius:8px;padding:7px 9px;font-size:13px;}
.fieldtoggles{display:flex;flex-direction:column;gap:1px;max-height:320px;overflow-y:auto;}
.ftog{display:flex;align-items:center;gap:8px;font-size:12.5px;color:var(--ink);cursor:pointer;padding:3px 4px;border-radius:6px;} .ftog:hover{background:rgba(88,166,255,.06);} .ftog input{accent-color:var(--blue);}
+ .ftog .flbl{flex:1;}
+ .ftog .fdrag{cursor:grab;color:var(--mut);font-size:12px;opacity:.5;user-select:none;line-height:1;} .ftog:hover .fdrag{opacity:1;}
+ .ftog.fdragging{opacity:.45;}
+ .ftog.fdropT{box-shadow:inset 0 3px 0 var(--acc);} .ftog.fdropB{box-shadow:inset 0 -3px 0 var(--acc);}
.minibtn{background:var(--card);color:var(--mut);border:1px solid var(--line);border-radius:7px;padding:4px 9px;font-size:11px;cursor:pointer;margin-right:5px;} .minibtn:hover{border-color:var(--blue);color:var(--ink);}
.wrap{flex:1 1 auto;min-width:0;}
@media(max-width:820px){ .rail{display:none;} }
@@ -123,6 +127,11 @@
</header>
<div class="shell">
<aside class="rail">
+ <div class="rsec" id="fieldsSec"><h4>Fields / columns — show / hide · reorder</h4>
+ <div class="fieldtoggles" id="fFields"></div>
+ <div style="margin-top:8px"><button class="minibtn" id="fAll">All</button><button class="minibtn" id="fNone">None</button><button class="minibtn" id="fReset" title="Reset columns to the tight default — order, widths & visibility">↺ Reset</button></div>
+ <div class="mut" style="font-size:10.5px;margin-top:7px;line-height:1.35">Drag a row by its ⠿ handle to reorder columns. In Table view you can also drag a column header to reorder · its right edge to resize · click to sort.</div>
+ </div>
<div class="rsec"><h4>Asset class</h4><div class="chips" id="fType"></div>
<div style="margin-top:9px;display:flex;flex-wrap:wrap;gap:6px;align-items:center"><span class="chip" id="listedChip" style="border-color:rgba(63,185,80,.5);color:var(--acc)" title="Homes newly on the market within the day window">🆕 Just Listed</span><span class="chip" id="soldChip" style="border-color:rgba(88,166,255,.5);color:var(--blue)" title="Closed escrow — pulled listings matched to a recorded sale within the day window">✅ Just Sold</span><span style="display:inline-flex;align-items:center;gap:4px;font-size:11px;color:var(--mut)">within <input type="number" id="recentDays" min="1" max="365" step="1" style="width:52px;background:var(--card);color:var(--ink);border:1px solid var(--line);border-radius:7px;padding:4px 6px;font-size:12px" title="Day window for Just Listed / Just Sold"> days</span><span class="chip" id="escrowChip" style="border-color:rgba(210,153,34,.5);color:var(--warn)" title="Currently in escrow — pending or contingent">🤝 In Escrow</span><span class="chip" id="fellChip" style="border-color:rgba(248,81,73,.45);color:var(--bad)" title="Back on the market — was pending/contingent, now active again">↩️ Fell Out of Escrow</span><span class="chip" id="cutChip" style="border-color:rgba(255,166,0,.5);color:#ffa600" title="Price reduced below its prior price within the day window">💸 Price Cut</span></div>
<div style="margin-top:9px;display:flex;flex-wrap:wrap;gap:6px"><span class="chip" id="nqChip" data-warr="nonqm" style="border-color:rgba(188,140,255,.5);color:#bc8cff" title="Non-QM target: unwarrantable condos + small multifamily (1–9u) + mixed-use">◆ Non-QM</span><span class="chip" id="unwChip" data-warr="unwarrantable" style="border-color:rgba(248,81,73,.4);color:var(--bad)" title="Filter the table to non-FHA-approved condos (non-QM target)">⚑ Unwarrantable condos</span><a class="navchip" href="/condos" title="Full condo drill-down page">condos page →</a></div></div>
@@ -131,11 +140,6 @@
<div class="rsec"><h4>Source of truth</h4><div class="chips" id="fSource"></div><div style="font-size:10px;color:var(--mut);margin-top:5px;line-height:1.4">firm-direct = the brokerage's own listing · broker-of-record = real listing broker known · aggregator = CREXi/portal only</div></div>
<div class="rsec"><h4>Brokerage <span style="font-size:10px;font-weight:400;color:var(--mut)">(listing broker)</span></h4><div class="chips" id="fFirm"></div></div>
<div class="rsec"><h4>Price</h4><div class="row2"><div class="fld"><label>Min $</label><input type="number" id="pMin" placeholder="any" step="50000"></div><div class="fld"><label>Max $</label><input type="number" id="pMax" placeholder="any" step="50000"></div></div></div>
- <div class="rsec"><h4>Fields / columns — show / hide</h4>
- <div class="fieldtoggles" id="fFields"></div>
- <div style="margin-top:8px"><button class="minibtn" id="fAll">All</button><button class="minibtn" id="fNone">None</button><button class="minibtn" id="fReset" title="Reset columns to the tight default — order, widths & visibility">↺ Reset</button></div>
- <div class="mut" style="font-size:10.5px;margin-top:7px;line-height:1.35">Drag a column header to reorder · drag its right edge to resize · click to sort.</div>
- </div>
</aside>
<div class="wrap">
<div class="statsbar" id="statsbar"></div>
@@ -146,6 +150,31 @@
</div>
<div id="ov"><div id="modal"><button class="ph-x" aria-label="Close" onclick="document.getElementById('ov').classList.remove('on')">✕</button><div id="mbody"></div></div></div>
<script>
+// Day/Night theme — pre-applied before first paint (see head snippet).
+// apply() only PAINTS — does NOT persist (passive loads must not lock in a theme;
+// only an explicit toggle click below saves a pref, so everyone gets the Day default).
+(function(){
+ var root=document.documentElement;
+ function applyTheme(t){ root.setAttribute('data-theme',t); }
+ var cur; try{ cur=localStorage.getItem('crcp-theme2'); }catch(e){}
+ applyTheme(cur==='dark'?'dark':'light');
+ // Wire up the toggle button injected by crcp-theme.js (defer — may not exist yet;
+ // observe the header for it and intercept its click to persist crcp-theme2).
+ function wireToggle(){
+ var btn=document.getElementById('crcpThemeToggle'); if(!btn||btn.__wired2) return;
+ btn.__wired2=true;
+ btn.addEventListener('click',function(e){
+ e.stopImmediatePropagation(); // suppress crcp-theme.js's own handler so we don't double-toggle
+ var nt=root.getAttribute('data-theme')==='light'?'dark':'light';
+ applyTheme(nt); try{ localStorage.setItem('crcp-theme2',nt); }catch(e){}
+ },true); // capture phase — fires before crcp-theme.js's own click handler
+ }
+ if(document.readyState==='loading') document.addEventListener('DOMContentLoaded',wireToggle); else wireToggle();
+ // also wire after defer scripts finish (crcp-theme.js injects the button on DOMContentLoaded)
+ document.addEventListener('DOMContentLoaded',wireToggle);
+})();
+</script>
+<script>
const $=s=>document.querySelector(s);
const esc=s=>(s==null?'':String(s)).replace(/&/g,'&').replace(/</g,'<').replace(/"/g,'"');
const money=n=>(n==null||n==='')?'—':'$'+Number(n).toLocaleString();
@@ -315,7 +344,7 @@ function buildRail(){
let __cm=null;
function buildFieldToggles(){
if(!window.ColumnManager){ // fallback if the module hasn't loaded (keeps the page usable)
- $('#fFields').innerHTML=COLS.map(c=>`<label class="ftog"><input type="checkbox" data-ck="${c.k}" ${colVis(c.k)?'checked':''}><span>${esc(c.l)}</span></label>`).join(''); return; }
+ $('#fFields').innerHTML=orderedCols().map(c=>`<label class="ftog" draggable="true" data-fk="${c.k}"><span class="fdrag" title="Drag to reorder column">⠿</span><input type="checkbox" data-ck="${c.k}" ${colVis(c.k)?'checked':''}><span class="flbl">${esc(c.l)}</span></label>`).join(''); return; }
const groupOf=k=>DEFAULT_ON.has(k)?'Shown by default':'More columns';
const cols=COLS.map(c=>({k:c.k,l:c.l,g:groupOf(c.k)}));
if(!__cm){
@@ -530,6 +559,28 @@ $('#fFirm').addEventListener('click',e=>{const c=e.target.closest('.chip');if(!c
$('#pMin').addEventListener('input',()=>{F.pmin=$('#pMin').value?+$('#pMin').value:null;render();});
$('#pMax').addEventListener('input',()=>{F.pmax=$('#pMax').value?+$('#pMax').value:null;render();});
// (#fFields change is handled inside column-manager.js now — its onChange calls render)
+// ── panel drag-to-reorder columns (Steve rule: resort order here in the panel too) ──
+let pDragKey=null;
+$('#fFields').addEventListener('dragstart',e=>{ const row=e.target.closest('.ftog'); if(!row) return;
+ if(!e.target.closest('.fdrag')){ e.preventDefault(); return; } // only the ⠿ handle starts a move
+ pDragKey=row.dataset.fk; e.dataTransfer.effectAllowed='move'; try{e.dataTransfer.setData('text/plain',pDragKey);}catch(_){}
+ row.classList.add('fdragging'); });
+$('#fFields').addEventListener('dragover',e=>{ if(!pDragKey) return; const row=e.target.closest('.ftog'); if(!row||row.dataset.fk===pDragKey) return;
+ e.preventDefault(); e.dataTransfer.dropEffect='move';
+ const r=row.getBoundingClientRect(), after=(e.clientY-r.top)>r.height/2;
+ $('#fFields').querySelectorAll('.ftog').forEach(x=>x.classList.remove('fdropT','fdropB'));
+ row.classList.toggle('fdropB',after); row.classList.toggle('fdropT',!after); });
+$('#fFields').addEventListener('dragleave',e=>{ const row=e.target.closest('.ftog'); if(row) row.classList.remove('fdropT','fdropB'); });
+$('#fFields').addEventListener('drop',e=>{ if(!pDragKey) return; const row=e.target.closest('.ftog'); if(!row){ pDragKey=null; return; } e.preventDefault();
+ const tgt=row.dataset.fk;
+ if(tgt!==pDragKey){
+ const r=row.getBoundingClientRect(), after=(e.clientY-r.top)>r.height/2;
+ syncColOrder(); const from=COLORDER.indexOf(pDragKey);
+ if(from>=0){ COLORDER.splice(from,1); let to=COLORDER.indexOf(tgt); if(after) to+=1; COLORDER.splice(to,0,pDragKey);
+ saveColOrder(); lastColSig=''; buildFieldToggles(); render(); }
+ }
+ pDragKey=null; });
+$('#fFields').addEventListener('dragend',()=>{ $('#fFields').querySelectorAll('.fdragging,.fdropT,.fdropB').forEach(x=>x.classList.remove('fdragging','fdropT','fdropB')); pDragKey=null; });
$('#fAll').addEventListener('click',()=>{COLS.forEach(c=>VISCOL[c.k]=true);saveCols();buildFieldToggles();render();});
$('#fNone').addEventListener('click',()=>{COLS.forEach(c=>VISCOL[c.k]=false);saveCols();buildFieldToggles();render();});
// Reset the whole table layout back to the tight default: visibility, order, widths.
@@ -554,7 +605,7 @@ if(cutEl) cutEl.addEventListener('click',()=>setQuick('pricecut',cutEl));
// collapsible rail tabs — all collapsed on load
const railEl=document.querySelector('.rail');
if(railEl){ railEl.addEventListener('click',e=>{ const h=e.target.closest('h4'); if(h && h.parentElement.classList.contains('rsec')) h.parentElement.classList.toggle('collapsed'); });
- document.querySelectorAll('.rail .rsec').forEach(s=>s.classList.add('collapsed')); }
+ document.querySelectorAll('.rail .rsec').forEach(s=>{ if(s.id!=='fieldsSec') s.classList.add('collapsed'); }); }
fetch('/data/ranked.json').then(r=>r.json()).then(async d=>{ DATA=(d.ranked||d)||[];
// Residential inventory bolted onto the CRE table (asset-class chips auto-build from r.type):
try{ const cd=await (await fetch('/api/condos')).json(); DATA=DATA.concat((cd.condos||[]).map(mapCondo)); }catch(e){}
← 7c4d9f3 CRE leads: Lead Desk viewer (sortable grid + density + filte
·
back to Commercialrealestate
·
auto-data-snapshot: 2026-08-07T16:56:57 (12 data files) — da 7a5e829 →