← back to Commercialrealestate
public/gov-agents.html
95 lines
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Government-Licensed Agents — CRCP</title>
<style>
:root{ --bg:#0c0f15; --card:#141925; --line:#232a38; --ink:#E6E8EC; --mut:#8A93A2; --gold:#C8A24B; --acc:#70AD47; --blue:#58a6ff; --warn:#d29922; }
*{box-sizing:border-box;} body{margin:0;background:var(--bg);color:var(--ink);font:14px/1.45 -apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif;}
header{display:flex;align-items:center;gap:12px;padding:11px 18px;border-bottom:1px solid var(--line);position:sticky;top:0;z-index:40;background:rgba(12,15,21,.98);backdrop-filter:blur(8px);flex-wrap:wrap;}
header h1{font-size:16px;margin:0;} header h1 b{color:var(--gold);}
header .nav{display:flex;gap:12px;margin-left:6px;} header .nav a{color:var(--blue);text-decoration:none;font-size:13px;} header .nav a:hover{color:var(--gold);}
.spacer{flex:1;}
select,.search{background:var(--card);border:1px solid var(--line);color:var(--ink);border-radius:9px;padding:8px 12px;font-size:13px;}
.search{min-width:230px;}
#density{accent-color:var(--acc);width:100px;}
label.densl{display:inline-flex;align-items:center;gap:6px;color:var(--mut);font-size:12px;}
.count{color:var(--mut);font-size:12px;}
.metros{display:flex;gap:6px;flex-wrap:wrap;padding:10px 18px;border-bottom:1px solid var(--line);}
.mchip{background:var(--card);border:1px solid var(--line);border-radius:16px;padding:5px 12px;font-size:12.5px;color:var(--mut);cursor:pointer;}
.mchip:hover{color:var(--ink);} .mchip.on{border-color:var(--gold);color:var(--gold);background:rgba(200,162,75,.1);}
.mchip .st{color:var(--blue);font-weight:700;} .mchip .ct{color:var(--mut);font-size:10.5px;margin-left:5px;}
.note{color:var(--mut);font-size:11.5px;padding:8px 18px;border-bottom:1px solid var(--line);}
.tblwrap{overflow:auto;max-height:calc(100vh - 190px);border-top:1px solid var(--line);}
table.g{border-collapse:separate;border-spacing:0;width:100%;font-size:12.5px;white-space:nowrap;}
table.g thead th{position:sticky;top:0;background:#1b2230;color:var(--mut);text-transform:uppercase;letter-spacing:.3px;font-size:10.5px;padding:8px 12px;border-bottom:1px solid var(--line);cursor:pointer;user-select:none;z-index:2;}
table.g thead th:hover{color:var(--ink);} table.g thead th.asc::after{content:' ▲';color:var(--gold);} table.g thead th.desc::after{content:' ▼';color:var(--gold);}
table.g tbody td{padding:7px 12px;border-bottom:1px solid #20262f;} table.g tbody tr:hover td{background:rgba(200,162,75,.07);}
.lic{display:inline-block;font-size:10px;font-weight:700;padding:1px 7px;border-radius:8px;}
.lic.b{background:rgba(200,162,75,.16);color:var(--gold);} .lic.s{background:rgba(88,166,255,.16);color:var(--blue);} .lic.o{background:rgba(139,148,158,.14);color:var(--mut);}
.miss{color:#5A6472;} .cap{color:var(--warn);}
</style>
</head>
<body>
<header>
<h1>🏛️ <b>Government-Licensed</b> Agents</h1>
<div class="nav"><a href="/broker-grid.html">← Brokers</a><a href="/mls.html">📋 MLS</a><a href="/crcp.html">🛰️ CRCP</a></div>
<span class="spacer"></span>
<input class="search" id="q" type="text" placeholder="search name, firm, license #, city…">
<select id="sortsel" title="Sort"></select>
<label class="densl" title="Rows per fetch">⤢<input type="range" id="density" min="200" max="2000" step="100" value="500"></label>
<span class="count" id="count">…</span>
</header>
<div class="metros" id="metros"></div>
<div class="note" id="note"></div>
<div class="tblwrap"><table class="g"><thead id="thead"></thead><tbody id="tbody"></tbody></table></div>
<script>
const $=s=>document.querySelector(s);
const esc=s=>(s==null?'':String(s)).replace(/&/g,'&').replace(/</g,'<').replace(/"/g,'"');
const COLS=[['name','Name'],['firm','Firm / Company'],['license_type','License Type'],['license_number','License #'],
['city','City'],['county','County'],['zip','Zip'],['expiration','Expires'],['state','State']];
let METRO='', AGENTS=[], META={}, q='', sortKey='name', sortDir=1, limit=500, timer=null;
$('#sortsel').innerHTML=COLS.map(([k,l])=>`<option value="${k}">Sort: ${l}</option>`).join('');
function licCls(t){ t=(t||'').toLowerCase(); if(t.includes('broker'))return 'b'; if(t.includes('sales'))return 's'; return 'o'; }
function cell(a,k){
if(k==='license_type') return `<span class="lic ${licCls(a[k])}">${esc(a[k]||'—')}</span>`;
if(k==='name') return '<b>'+esc(a[k])+'</b>';
const v=a[k]; return v==null||v===''?'<span class="miss">—</span>':esc(v);
}
function render(){
let rows=AGENTS.slice().sort((x,y)=>{
let a=(x[sortKey]||'').toString().toLowerCase(), b=(y[sortKey]||'').toString().toLowerCase();
return a<b?-sortDir:a>b?sortDir:0;
});
$('#thead').innerHTML='<tr>'+COLS.map(([k,l])=>`<th data-k="${k}" class="${sortKey===k?(sortDir>0?'asc':'desc'):''}">${esc(l)}</th>`).join('')+'</tr>';
$('#tbody').innerHTML=rows.map(a=>'<tr>'+COLS.map(([k])=>`<td>${cell(a,k)}</td>`).join('')+'</tr>').join('')||'<tr><td colspan="9" class="miss" style="padding:20px">no matches</td></tr>';
const capped=META.total>rows.length;
$('#count').textContent=`${rows.length}${capped?' of '+META.total.toLocaleString():''} agents`;
$('#note').innerHTML=`Source: <b>${esc(META.source||'?')}</b> · ${esc(META.label||'')}`+(capped?` <span class="cap">— showing first ${rows.length} of ${META.total.toLocaleString()} matches; refine your search to narrow.</span>`:'');
}
async function loadMetro(){
$('#count').textContent='loading…';
const u=`/api/gov-agents?metro=${encodeURIComponent(METRO)}&limit=${limit}`+(q?`&q=${encodeURIComponent(q)}`:'');
let d; try{ d=await (await fetch(u)).json(); }catch(e){ $('#note').textContent='load failed'; return; }
AGENTS=d.agents||[]; META=d; render();
}
async function loadIndex(){
let d; try{ d=await (await fetch('/api/gov-agents')).json(); }catch(e){ $('#metros').textContent='load failed'; return; }
const ms=d.metros||[];
$('#metros').innerHTML=ms.map(m=>`<span class="mchip" data-m="${esc(m.metro)}"><span class="st">${esc(m.state)}</span> ${esc(m.metro)}<span class="ct">${m.n.toLocaleString()} · ${m.brokers.toLocaleString()} br</span></span>`).join('');
if(ms.length){ METRO=ms[0].metro; setActive(); loadMetro(); }
}
function setActive(){ document.querySelectorAll('.mchip').forEach(c=>c.classList.toggle('on',c.dataset.m===METRO)); }
$('#metros').addEventListener('click',e=>{const c=e.target.closest('.mchip');if(!c)return;METRO=c.dataset.m;setActive();loadMetro();});
$('#q').addEventListener('input',()=>{q=$('#q').value.trim();clearTimeout(timer);timer=setTimeout(loadMetro,280);});
$('#sortsel').addEventListener('change',()=>{sortKey=$('#sortsel').value;render();});
$('#thead').addEventListener('click',e=>{const th=e.target.closest('th');if(!th)return;const k=th.dataset.k;if(sortKey===k)sortDir*=-1;else{sortKey=k;sortDir=1;}$('#sortsel').value=sortKey;render();});
$('#density').addEventListener('change',()=>{limit=+$('#density').value;loadMetro();});
loadIndex();
</script>
<script src="/col-resize.js" defer></script>
</body>
</html>