[object Object]

← back to Commercialrealestate

CRCP sweep: Day-theme default on crcp/fha-loans/graphics; Fields panel to top + column drag-reorder on broker-grid/gov-agents/sales

7fa1abc796619f092375e27aef0459b1c3aa61d3 · 2026-08-07 18:17:44 -0700 · Steve Abrams

Files touched

Diff

commit 7fa1abc796619f092375e27aef0459b1c3aa61d3
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Fri Aug 7 18:17:44 2026 -0700

    CRCP sweep: Day-theme default on crcp/fha-loans/graphics; Fields panel to top + column drag-reorder on broker-grid/gov-agents/sales
---
 public/broker-grid.html | 51 +++++++++++++++++++++++++++++++++---------
 public/crcp.html        | 27 +++++++++++++++++++++-
 public/fha-loans.html   | 27 +++++++++++++++++++++-
 public/gov-agents.html  | 59 +++++++++++++++++++++++++++++++++++--------------
 public/graphics.html    | 27 +++++++++++++++++++++-
 public/sales.html       | 48 +++++++++++++++++++++++++++++++---------
 6 files changed, 199 insertions(+), 40 deletions(-)

diff --git a/public/broker-grid.html b/public/broker-grid.html
index c37b8dc..9afc7a1 100644
--- a/public/broker-grid.html
+++ b/public/broker-grid.html
@@ -79,6 +79,10 @@
   .chip .ct{color:var(--mut);font-size:10px;margin-left:4px;}
   .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(200,162,75,.08);} .ftog input{accent-color:var(--gold);}
+  .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(--gold);} .ftog.fdropB{box-shadow:inset 0 -3px 0 var(--gold);}
   .fgrp{font-size:9.5px;text-transform:uppercase;letter-spacing:.6px;color:var(--gold);margin:9px 0 3px;font-weight:700;opacity:.85;} .fgrp:first-child{margin-top:0;}
   .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(--gold);color:var(--ink);}
   .wrap{flex:1 1 auto;min-width:0;}
@@ -103,13 +107,13 @@
 </header>
 <div class="shell">
 <aside class="rail">
-  <div class="rsec"><h4>Agent type</h4><div class="chips" id="fType"></div></div>
-  <div class="rsec"><h4>Company / firm</h4><div class="chips" id="fFirm"></div></div>
-  <div class="rsec"><h4>Fields / columns — show / hide</h4>
+  <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 column header to reorder · drag its right edge to resize · click to sort.</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 List view you can also drag a column header to reorder · its right edge to resize · click to sort.</div>
   </div>
+  <div class="rsec"><h4>Agent type</h4><div class="chips" id="fType"></div></div>
+  <div class="rsec"><h4>Company / firm</h4><div class="chips" id="fFirm"></div></div>
 </aside>
 <div class="wrap">
   <div class="statsbar" id="statsbar"></div>
@@ -231,10 +235,15 @@ function buildRail(){
   buildFieldToggles();
 }
 function buildFieldToggles(){
-  const groups={};
-  COLS.forEach(c=>{ (groups[c.g||'Other']=groups[c.g||'Other']||[]).push(c); });
-  $('#fFields').innerHTML=Object.entries(groups).map(([g,cs])=>
-    `<div class="fgrp">${esc(g)}</div>`+cs.map(c=>`<label class="ftog"><input type="checkbox" data-ck="${c.k}" ${colVis(c.k)?'checked':''}><span>${esc(c.l)}</span></label>`).join('')
+  // Emit rows in current COLORDER, grouped by g for the section headers.
+  // Each row is draggable via the ⠿ handle (FIX B); data-fk on the label drives
+  // the panel drag listeners; data-ck on the checkbox is kept for the change listener.
+  const ordered=orderedCols();
+  // build group → ordered cols mapping (preserving COLORDER within each group)
+  const groupsSeen=[], groupMap={};
+  ordered.forEach(c=>{ const g=c.g||'Other'; if(!groupMap[g]){ groupMap[g]=[]; groupsSeen.push(g); } groupMap[g].push(c); });
+  $('#fFields').innerHTML=groupsSeen.map(g=>
+    `<div class="fgrp">${esc(g)}</div>`+groupMap[g].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('')
   ).join('');
 }
 function passFacets(r){
@@ -430,16 +439,38 @@ $('#view').addEventListener('click',e=>{const b=e.target.closest('button');if(!b
 $('#fType').addEventListener('click',e=>{const c=e.target.closest('.chip');if(!c)return;tog(F.types,c.dataset.ty);c.classList.toggle('active');render();});
 $('#fFirm').addEventListener('click',e=>{const c=e.target.closest('.chip');if(!c)return;tog(F.firms,c.dataset.fm);c.classList.toggle('active');render();});
 $('#fFields').addEventListener('change',e=>{const cb=e.target.closest('input[data-ck]');if(!cb)return;VISCOL[cb.dataset.ck]=cb.checked;saveCols();render();});
+// ── panel drag-to-reorder columns (FIX B: Steve rule — resort order via ⠿ handle) ──
+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; });
 $('#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.
 $('#fReset').addEventListener('click',()=>{ VISCOL={}; saveCols(); COLORDER=[]; saveColOrder();
   try{Object.keys(localStorage).forEach(k=>{ if(k.indexOf('cr:'+location.pathname+':')===0) localStorage.removeItem(k); });}catch(e){}
   lastColSig=''; buildFieldToggles(); render(); });
-// collapsible rail tabs — all collapsed on load
+// collapsible rail tabs — all collapsed on load except #fieldsSec (FIX A: fields panel stays expanded)
 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('/api/brokers/all').then(r=>r.json()).then(d=>{DATA=d.brokers||[];autoCols();if(window.__buildSortSel)window.__buildSortSel();buildRail();
   // deep-link: ?type=residential|commercial pre-selects the Agent-type facet (from the crcp Residential-agents tile)
   const _tp=new URLSearchParams(location.search).get('type');
diff --git a/public/crcp.html b/public/crcp.html
index ae923b8..022161f 100644
--- a/public/crcp.html
+++ b/public/crcp.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='%230a0d13'/%3E%3Ctext x='8' y='12' font-size='11' text-anchor='middle' fill='%23c8a24b'%3E%24%3C/text%3E%3C/svg%3E">
@@ -802,5 +802,30 @@ loadSegments(); setInterval(loadSegments, 20000);
   <script src="/col-resize.js" defer></script>
 <script src="/corner-nav.js" defer></script>
 <script src="/crcp-theme.js" defer></script>
+<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>
 </body>
 </html>
diff --git a/public/fha-loans.html b/public/fha-loans.html
index 3890d15..b7d3337 100644
--- a/public/fha-loans.html
+++ b/public/fha-loans.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='%230a0d13'/%3E%3Ctext x='8' y='12' font-size='11' text-anchor='middle' fill='%23c8a24b'%3E%24%3C/text%3E%3C/svg%3E">
@@ -500,6 +500,31 @@ load();
   <script src="/col-resize.js" defer></script>
   <script src="/global-search.js" defer></script>
 <script src="/corner-nav.js" defer></script>
+<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 src="/crcp-theme.js" defer></script>
 </body>
 </html>
diff --git a/public/gov-agents.html b/public/gov-agents.html
index be22168..38a5d0d 100644
--- a/public/gov-agents.html
+++ b/public/gov-agents.html
@@ -42,6 +42,10 @@
   .rail h4{margin:0 0 9px;font-size:11px;text-transform:uppercase;letter-spacing:.6px;color:var(--mut);}
   .fieldtoggles{display:flex;flex-direction:column;gap:1px;max-height:340px;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(200,162,75,.08);} .ftog input{accent-color:var(--gold);}
+  .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(--gold);} .ftog.fdropB{box-shadow:inset 0 -3px 0 var(--gold);}
   .fgrp{font-size:9.5px;text-transform:uppercase;letter-spacing:.6px;color:var(--gold);margin:9px 0 3px;font-weight:700;opacity:.85;} .fgrp:first-child{margin-top:0;}
   .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(--gold);color:var(--ink);}
   .rwrap{flex:1 1 auto;min-width:0;}
@@ -73,10 +77,10 @@
 <div class="note" id="note"></div>
 <div class="shell">
 <aside class="rail">
-  <div class="rsec"><h4>Fields / columns — show / hide</h4>
+  <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;color:var(--mut)">Drag a column header to reorder · drag its right edge to resize · click to sort · click a row for full detail.</div>
+    <div class="mut" style="font-size:10.5px;margin-top:7px;line-height:1.35;color:var(--mut)">Drag a row by its ⠿ handle to reorder columns. Drag a column header to reorder · its right edge to resize · click to sort.</div>
   </div>
 </aside>
 <div class="rwrap">
@@ -148,20 +152,19 @@ function cell(a,c){
 // drop-in (TK-10120), as a VIEW over this page's own VISCOL/COLORDER state.
 let __cm=null;
 function buildFieldToggles(){
-  if(!window.ColumnManager){ // fallback if the module hasn't loaded (keeps page usable)
-    const groups={};
-    COLS.forEach(c=>{ (groups[c.g||'Other']=groups[c.g||'Other']||[]).push(c); });
-    $('#fFields').innerHTML=Object.entries(groups).map(([g,cs])=>
-      `<div class="fgrp">${esc(g)}</div>`+cs.map(c=>`<label class="ftog"><input type="checkbox" data-ck="${c.k}" ${colVis(c.k)?'checked':''}><span>${esc(c.l)}</span></label>`).join('')
-    ).join(''); return; }
-  const cols=COLS.map(c=>({k:c.k,l:c.l,g:c.g||'Other'}));
-  if(!__cm){
-    __cm=ColumnManager.init({ mount:'#fFields', footer:false, hint:false, cols,
+  // Always use the native panel (draggable rows in COLORDER order) — ColumnManager is
+  // used only for its onChange hook when present; the panel DOM is always ours.
+  syncColOrder();
+  const ordered=COLORDER.map(k=>COLS.find(c=>c.k===k)).filter(Boolean);
+  $('#fFields').innerHTML=ordered.map(c=>`<label class="ftog" draggable="true" data-fk="${c.k}"><span class="fdrag" title="Drag to reorder column">⠿</span><input type="checkbox" data-fk="${c.k}" ${colVis(c.k)?'checked':''}><span class="flbl">${esc(c.l)}</span></label>`).join('');
+  if(window.ColumnManager && __cm){ __cm.setCols(COLS.map(c=>({k:c.k,l:c.l,g:c.g||'Other'}))); }
+  else if(window.ColumnManager && !__cm){
+    __cm=ColumnManager.init({ mount:'#fFields', footer:false, hint:false, cols:COLS.map(c=>({k:c.k,l:c.l,g:c.g||'Other'})),
       state:{ visible:k=>colVis(k), setVisible:(k,v)=>{VISCOL[k]=v;saveCols();},
               order:()=>{syncColOrder();return COLORDER.slice();}, setOrder:a=>{COLORDER=a;saveColOrder();},
               reset:()=>{VISCOL={};saveCols();COLORDER=[];saveColOrder();} },
-      onChange:()=>render() });
-  } else { __cm.setCols(cols); }
+      onChange:()=>{ buildFieldToggles(); render(); } });
+  }
 }
 function filtered(){
   let rows=AGENTS.slice();
@@ -258,16 +261,40 @@ $('#thead').addEventListener('drop',e=>{ if(!dragKey) return; const th=e.target.
   dragKey=null; });
 $('#thead').addEventListener('dragend',()=>{ document.querySelectorAll('#thead th.dragging,#thead th.dropL,#thead th.dropR').forEach(x=>x.classList.remove('dragging','dropL','dropR')); dragKey=null; });
 // ── left-rail column show/hide toggles ──
-// (#fFields change is handled inside column-manager.js now — its onChange calls render)
 $('#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.
 $('#fReset').addEventListener('click',()=>{ VISCOL={}; saveCols(); COLORDER=[]; saveColOrder();
   try{Object.keys(localStorage).forEach(k=>{ if(k.indexOf('cr:'+location.pathname+':')===0) localStorage.removeItem(k); });}catch(e){}
   lastColSig=''; buildFieldToggles(); render(); });
-// collapsible rail tabs
+// ── panel checkbox toggle (data-fk emitted by our buildFieldToggles) ──
+$('#fFields').addEventListener('change',e=>{ const cb=e.target.closest('input[data-fk]'); if(!cb) return; VISCOL[cb.dataset.fk]=cb.checked; saveCols(); 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 (so checkbox clicks still work)
+  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; });
+// collapsible rail tabs — all collapsed on load except fieldsSec (Steve rule: fields section stays open)
 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'); }); }
+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=>{ if(s.id!=='fieldsSec') s.classList.add('collapsed'); }); }
 buildSortSel(); buildFieldToggles();
 loadIndex();
 </script>
diff --git a/public/graphics.html b/public/graphics.html
index a33fe9f..e010903 100644
--- a/public/graphics.html
+++ b/public/graphics.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'%3E%F0%9F%93%8A%3C/text%3E%3C/svg%3E">
@@ -100,6 +100,31 @@
   </div>
 </main>
 
+<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 GH={ ink:'#e6edf3', mut:'#8b949e', line:'#2a313c', acc:'#3fb950', blue:'#58a6ff', gold:'#d29922', red:'#f85149' };
diff --git a/public/sales.html b/public/sales.html
index bd62f81..4b30313 100644
--- a/public/sales.html
+++ b/public/sales.html
@@ -71,6 +71,10 @@
   .chip .ct{color:var(--mut);font-size:10px;margin-left:4px;}
   .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(200,162,75,.08);} .ftog input{accent-color:var(--gold);}
+  .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(--gold);} .ftog.fdropB{box-shadow:inset 0 -3px 0 var(--gold);}
   .fgrp{font-size:9.5px;text-transform:uppercase;letter-spacing:.6px;color:var(--gold);margin:9px 0 3px;font-weight:700;opacity:.85;} .fgrp:first-child{margin-top:0;}
   .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(--gold);color:var(--ink);}
   .wrap{flex:1 1 auto;min-width:0;}
@@ -97,14 +101,14 @@
 <div id="depthNote"></div>
 <div class="shell">
 <aside class="rail">
-  <div class="rsec"><h4>City</h4><div class="chips" id="fCity"></div></div>
-  <div class="rsec"><h4>Sold year</h4><div class="chips" id="fYear"></div></div>
-  <div class="rsec"><h4>Beds</h4><div class="chips" id="fBeds"></div></div>
-  <div class="rsec"><h4>Fields / columns — show / hide</h4>
+  <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 column header to reorder · drag its right edge to resize · click to sort.</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 List view you can also drag a column header to reorder · its right edge to resize · click to sort.</div>
   </div>
+  <div class="rsec"><h4>City</h4><div class="chips" id="fCity"></div></div>
+  <div class="rsec"><h4>Sold year</h4><div class="chips" id="fYear"></div></div>
+  <div class="rsec"><h4>Beds</h4><div class="chips" id="fBeds"></div></div>
 </aside>
 <div class="wrap">
   <div id="tableView" class="tblwrap"><table class="g" data-no-sort><thead id="thead"></thead><tbody id="tbody"></tbody></table></div>
@@ -198,10 +202,10 @@ function buildRail(){
 let __cm=null;
 function buildFieldToggles(){
   if(!window.ColumnManager){ // fallback if the module hasn't loaded (keeps page usable)
-    const groups={};
-    COLS.forEach(c=>{ (groups[c.g||'Other']=groups[c.g||'Other']||[]).push(c); });
-    $('#fFields').innerHTML=Object.entries(groups).map(([g,cs])=>
-      `<div class="fgrp">${esc(g)}</div>`+cs.map(c=>`<label class="ftog"><input type="checkbox" data-ck="${c.k}" ${colVis(c.k)?'checked':''}><span>${esc(c.l)}</span></label>`).join('')
+    // emit rows in COLORDER order with draggable ⠿ handle (mirrors condos.html panel drag)
+    syncColOrder();
+    $('#fFields').innerHTML=COLORDER.map(k=>COLS.find(c=>c.k===k)).filter(Boolean).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 cols=COLS.map(c=>({k:c.k,l:c.l,g:c.g||'Other'}));
   if(!__cm){
@@ -347,6 +351,28 @@ $('#view').addEventListener('click',e=>{const b=e.target.closest('button');if(!b
   $('#denswrap').style.display=(view==='grid')?'inline-flex':'none';
   window.__syncSortSel=()=>{if(Array.from(sel.options).some(o=>o.value===sortKey)&&sel.value!==sortKey)sel.value=sortKey;};
 })();
+// ── panel drag-to-reorder columns (mirrors condos.html — ⠿ handle only starts a move) ──
+let pDragKey=null;
+$('#fFields').addEventListener('dragstart',e=>{ const row=e.target.closest('.ftog'); if(!row) return;
+  if(!e.target.closest('.fdrag')){ e.preventDefault(); return; }
+  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; });
 // left-rail facets + column toggles
 $('#fCity').addEventListener('click',e=>{const c=e.target.closest('.chip');if(!c)return;tog(F.city,c.dataset.ci);c.classList.toggle('active');render();});
 $('#fYear').addEventListener('click',e=>{const c=e.target.closest('.chip');if(!c)return;tog(F.year,c.dataset.yr);c.classList.toggle('active');render();});
@@ -358,10 +384,10 @@ $('#fNone').addEventListener('click',()=>{COLS.forEach(c=>VISCOL[c.k]=false);sav
 $('#fReset').addEventListener('click',()=>{ VISCOL={}; saveCols(); COLORDER=[]; saveColOrder();
   try{Object.keys(localStorage).forEach(k=>{ if(k.indexOf('cr:'+location.pathname+':')===0) localStorage.removeItem(k); });}catch(e){}
   lastColSig=''; buildFieldToggles(); render(); });
-// collapsible rail tabs — all collapsed on load
+// collapsible rail tabs — all collapsed on load except fieldsSec (matches condos.html)
 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'); }); }
 // ── charts ──
 let cityChart, yearChart;
 function drawCharts(d){

← ad59feb CRE leads: wide sweep chunks 1-4 (72 firms) - +8 marketing c  ·  back to Commercialrealestate  ·  auto-data-snapshot: 2026-08-07T18:31:41 (5 data files) — dat 6ce6fa6 →