← 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
M public/broker-grid.htmlM public/crcp.htmlM public/fha-loans.htmlM public/gov-agents.htmlM public/graphics.htmlM public/sales.html
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 & 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 & 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 & 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 →