[object Object]

← 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

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 &amp; 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 &amp; 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 &amp; 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 &amp; 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,'&amp;').replace(/</g,'&lt;').replace(/"/g,'&quot;');
 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 →