← back to Rentv 2026

public/desk.html

232 lines

<!doctype html><html lang="en"><head>
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>RENTV · Broker &amp; Owner Intelligence Desk</title>
<link href="https://fonts.googleapis.com/css2?family=Playfair+Display:wght@800&family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
<style>
  :root{--red:#c8102e;--line:#232a32}*{box-sizing:border-box;margin:0}
  body{background:#0d1014;color:#e7ecf1;font-family:'Inter',system-ui,sans-serif;font-size:13.5px}
  a{color:inherit;text-decoration:none}
  .top{position:sticky;top:0;z-index:900;display:flex;align-items:center;justify-content:space-between;height:52px;padding:0 14px;background:#0b0e12;border-bottom:1px solid var(--line)}
  .burger{display:inline-flex;align-items:center;gap:9px;border:0;background:none;color:#e7ecf1;cursor:pointer;font:inherit;padding:6px 8px;border-radius:8px}.burger:hover{background:rgba(255,255,255,.08)}
  .burger .lbl{font-size:12px;font-weight:800;letter-spacing:.14em;text-transform:uppercase;color:#8b939b}
  .brand{position:absolute;left:50%;transform:translateX(-50%);font-family:'Playfair Display',serif;font-weight:800;font-size:19px}.brand span{color:var(--red)}
  .user{width:35px;height:35px;border-radius:50%;border:1.5px solid #2a323b;background:#11161c;color:#cfd6dd;cursor:pointer;display:inline-flex;align-items:center;justify-content:center}.user:hover{border-color:var(--red);color:#fff}
  .scrim{position:fixed;inset:0;background:rgba(6,9,12,.5);opacity:0;pointer-events:none;transition:.22s;z-index:950}.scrim.open{opacity:1;pointer-events:auto}
  .drawer{position:fixed;top:0;left:0;height:100%;width:340px;max-width:88vw;background:#0b0e12;transform:translateX(-102%);transition:transform .26s cubic-bezier(.4,0,.2,1);z-index:951;display:flex;flex-direction:column;overflow-y:auto;box-shadow:2px 0 40px rgba(0,0,0,.4)}
  .drawer.open{transform:none}
  .drawer .dh{display:flex;justify-content:space-between;padding:16px 18px;border-bottom:1px solid var(--line)}.drawer .dh .w{font-family:'Playfair Display',serif;font-size:21px;font-weight:800}.drawer .dh .w span{color:var(--red)}.drawer .dh button{background:none;border:0;color:#8b949e;font-size:22px;cursor:pointer}
  .gt{font-size:10px;letter-spacing:.16em;text-transform:uppercase;color:#6b747d;padding:14px 18px 6px}
  .drawer nav a{display:block;padding:9px 18px;color:#dbe1e7;border-left:3px solid transparent}.drawer nav a:hover{background:#141a21;border-left-color:var(--red);color:#fff}
  /* shell = left rail + content (NO horizontal tabs) */
  .shell{display:grid;grid-template-columns:200px 1fr;height:calc(100vh - 52px)}
  .rail{border-right:1px solid var(--line);padding:14px 10px;display:flex;flex-direction:column;gap:6px;overflow-y:auto}
  .rail .h{font-size:10px;letter-spacing:.12em;text-transform:uppercase;color:#6b747d;padding:8px 10px 4px}
  .rail button{display:flex;align-items:center;gap:10px;width:100%;background:none;border:0;color:#c3cbd3;font:700 13px 'Inter';padding:10px 12px;border-radius:9px;cursor:pointer;text-align:left}
  .rail button:hover{background:#12161c}.rail button.on{background:#12161c;color:#fff;box-shadow:inset 3px 0 0 var(--red)}
  .rail button .i{font-size:15px;width:18px;text-align:center}.rail .c{margin-left:auto;font-size:11px;color:#8b939b}
  .content{overflow:hidden;position:relative}
  .view{position:absolute;inset:0;overflow-y:auto;display:none}.view.on{display:block}
  /* shared bits */
  .act{display:inline-flex;align-items:center;gap:6px;background:#12161c;border:1px solid #2a333d;border-radius:8px;padding:7px 11px;color:#dbe1e7;font-weight:700;font-size:12.5px;white-space:nowrap}.act:hover{border-color:var(--red);color:#fff}.act .i{font-size:14px}
  .acts{display:flex;gap:7px;flex-wrap:wrap}
  .field-link{color:#7cc0ff;border-bottom:1px dotted #3a5064}.field-link:hover{color:#a9d5ff}
  .lic{color:#37d67a;font-family:ui-monospace,monospace}
  /* directory (master-detail) */
  .md{display:grid;grid-template-columns:360px 1fr;height:100%}
  .md .list{border-right:1px solid var(--line);display:flex;flex-direction:column;min-height:0}
  .md .search{padding:12px;border-bottom:1px solid var(--line)}.md .search input{width:100%;background:#11161c;border:1px solid var(--line);border-radius:9px;color:#e7ecf1;padding:10px 12px;font:inherit}
  .md .rows{overflow-y:auto;flex:1}
  .md .row{padding:12px 14px;border-bottom:1px solid #171d24;cursor:pointer}.md .row:hover{background:#141b22}.md .row.sel{background:#11161c;border-left:3px solid var(--red)}
  .md .row .nm{font-weight:700;font-size:14px}.md .row .f{font-size:12px;color:#9aa4ad;margin-top:2px}
  .md .detail{overflow-y:auto;padding:30px 36px}
  .md .detail .k{font-size:11px;letter-spacing:.1em;text-transform:uppercase;color:var(--red);font-weight:800}
  .md .detail h2{font-size:30px;font-weight:800;margin:5px 0}.md .detail .meta{color:#9aa4ad;font-size:15px}
  .md .detail .grid{display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-top:20px}.md .detail .cell{background:#11161c;border:1px solid var(--line);border-radius:10px;padding:13px}.md .detail .cell .kk{font-size:11px;color:#8b939b;text-transform:uppercase}.md .detail .cell .vv{font-size:17px;font-weight:700;margin-top:3px}
  .md .detail .acts{margin:20px 0}
  /* map */
  .mapv{display:grid;grid-template-columns:1fr 340px;height:100%}
  .map{position:relative;background:radial-gradient(circle at 42% 38%,#1a2531,#0b1016);overflow:hidden}
  .map .g{position:absolute;inset:0;background-image:linear-gradient(rgba(255,255,255,.045) 1px,transparent 1px),linear-gradient(90deg,rgba(255,255,255,.045) 1px,transparent 1px);background-size:46px 46px}
  .pin{position:absolute;transform:translate(-50%,-100%);cursor:pointer;z-index:3;text-align:center}.pin .d{width:16px;height:16px;border-radius:50% 50% 50% 0;background:var(--red);transform:rotate(-45deg);margin:0 auto;border:2px solid #fff;box-shadow:0 3px 8px rgba(0,0,0,.5)}.pin .l{background:#0b0e12;border:1px solid var(--line);border-radius:6px;font-size:11px;font-weight:800;padding:3px 7px;margin-top:6px;white-space:nowrap}.pin.sel .d{background:#37d67a}
  .mside{border-left:1px solid var(--line);overflow-y:auto;padding:20px}.mside .k{font-size:11px;letter-spacing:.1em;text-transform:uppercase;color:var(--red);font-weight:800}.mside h2{font-size:22px;font-weight:800;margin:5px 0}.mside .r{display:flex;justify-content:space-between;border-bottom:1px solid #171d24;padding:8px 0}.mside b{color:#e7ecf1}.mside .acts{margin:16px 0}
  /* analytics */
  .an{padding:18px 22px}.kpis{display:grid;grid-template-columns:repeat(4,1fr);gap:14px;margin-bottom:16px}.kpi{background:#11161c;border:1px solid var(--line);border-radius:12px;padding:16px}.kpi .k{font-size:11px;text-transform:uppercase;letter-spacing:.06em;color:#8b939b}.kpi .v{font-size:28px;font-weight:800;margin-top:4px}.kpi.lk{cursor:pointer;transition:border-color .15s;position:relative}.kpi.lk:hover{border-color:#c8102e}.kpi.lk::after{content:"›";position:absolute;top:10px;right:12px;color:#8b939b;font-size:14px;line-height:1}
  .panel{background:#11161c;border:1px solid var(--line);border-radius:12px;padding:16px}.panel h3{font-size:12px;letter-spacing:.08em;text-transform:uppercase;color:#8b939b;margin-bottom:14px}
  .barwrap .bar{display:flex;align-items:center;gap:10px;margin-bottom:9px;cursor:pointer}.bar .lab{width:170px;font-size:12.5px;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.bar .track{flex:1;height:22px;background:#0e141a;border-radius:5px;overflow:hidden}.bar .fill{height:100%;background:linear-gradient(90deg,var(--red),#e0455f)}.bar .val{width:64px;text-align:right;font-weight:800}.barwrap .acts{display:none;margin:2px 0 12px 180px}.barwrap.open .acts{display:flex}
  /* command */
  .cmd{max-width:760px;margin:0 auto;padding:40px 20px}.cmd .box{background:#11161c;border:1px solid #2a333d;border-radius:14px;overflow:hidden;box-shadow:0 30px 80px rgba(0,0,0,.5)}.cmd .in{display:flex;align-items:center;gap:12px;padding:16px 18px;border-bottom:1px solid var(--line)}.cmd input{flex:1;background:none;border:0;color:#e7ecf1;font:600 18px 'Inter';outline:none}.cmd .results{max-height:56vh;overflow-y:auto}.res{padding:12px 16px;border-bottom:1px solid #171d24;cursor:pointer}.res:hover,.res.hot{background:#141b22}.res .nm{font-weight:700}.res .f{font-size:12px;color:#9aa4ad;margin-top:2px}.res .exp{display:none;margin-top:10px}.res.open .exp{display:block}
  .fresh{color:#6b747d;font-size:12px;padding:10px 14px}
  @media(max-width:900px){.shell{grid-template-columns:1fr;height:auto}.md,.mapv{grid-template-columns:1fr}.kpis{grid-template-columns:1fr 1fr}}
</style></head>
<body>
<header class="top">
  <button class="burger" id="burger" aria-label="Menu"><svg width="21" height="21" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round"><path d="M3 6h18M3 12h18M3 18h18"/></svg><span class="lbl">Menu</span></button>
  <a class="brand" href="/">REN<span>TV</span> · Intelligence Desk</a>
  <button class="user" id="user" title="Sign in"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="8" r="3.5"/><path d="M4.5 20a7.5 7.5 0 0 1 15 0" stroke-linecap="round"/></svg></button>
</header>
<div class="scrim" id="scrim"></div>
<aside class="drawer" id="drawer">
  <div class="dh"><span class="w">REN<span>TV</span></span><button id="dclose">✕</button></div>
  <div class="gt">Intelligence</div><nav><a href="/desk">Broker &amp; Owner Desk</a><a href="/audience">Audience &amp; CRM</a><a href="/la-commercial">LA Commercial</a><a href="/markets">Markets</a></nav>
  <div class="gt">Newsroom</div><nav><a href="/">Home</a><a href="/review">The REview</a><a href="/blog">Blog</a><a href="/conferences">Conferences</a></nav>
</aside>
<div class="shell">
  <nav class="rail" id="rail">
    <div class="h">View</div>
    <button data-v="directory" class="on"><span class="i">▤</span>Directory</button>
    <button data-v="analytics"><span class="i">▮</span>Analytics</button>
    <div class="h" style="margin-top:10px">Scope</div>
    <button data-s="brokers" class="on scope"><span class="i">👤</span>Brokers <span class="c" id="cB">0</span></button>
    <button data-s="firms" class="scope"><span class="i">🏢</span>Firms <span class="c" id="cF">0</span></button>
    <button data-s="owners" class="scope"><span class="i">🏛️</span>Owners <span class="c" id="cO">0</span></button>
    <button data-s="sublease" class="scope"><span class="i">🔑</span>Sublease <span class="c" id="cS">0</span></button>
    <button data-s="homes" class="scope"><span class="i">🏗️</span>New Homes <span class="c" id="cH">0</span></button>
    <button data-s="commercial" class="scope"><span class="i">🏬</span>Commercial <span class="c" id="cC">0</span></button>
    <div class="fresh" id="fresh"></div>
  </nav>
  <div class="content">
    <div class="view on" id="v-directory"></div>
    <div class="view" id="v-analytics"></div>
  </div>
</div>
<script>
const $=s=>document.getElementById(s), enc=encodeURIComponent;
const esc=s=>String(s==null?'':s).replace(/[&<>"]/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'}[c]));
const shortUsd=n=>{n=Number(n)||0;return n>=1e9?'$'+(n/1e9).toFixed(2)+'B':n>=1e6?'$'+(n/1e6).toFixed(1)+'M':'$'+n.toLocaleString();};
const dstr=d=>d?new Date(d).toLocaleDateString(undefined,{month:'short',day:'numeric',year:'numeric'}):'—';
const loc=x=>[x.city||x.hq_city,x.state_code||x.hq_state].filter(Boolean).join(', ');
const LI_P=(n,f)=>`https://www.linkedin.com/search/results/people/?keywords=${enc([n,f].filter(Boolean).join(' '))}`,LI_C=f=>`https://www.linkedin.com/search/results/companies/?keywords=${enc(f)}`,WEB=f=>`https://www.google.com/search?q=${enc('"'+f+'" real estate official website')}`,MAPS=q=>`https://www.google.com/maps/search/?api=1&query=${enc(q)}`,DRE=l=>`https://www2.dre.ca.gov/PublicASP/pplinfo.asp?License_id=${enc(l)}`,GOOG=q=>`https://www.google.com/search?q=${enc(q)}`,ASSR=a=>`https://portal.assessor.lacounty.gov/parceldetail/${enc(a)}`;
const A=(h,i,l)=>`<a class="act" href="${h}" target="_blank" rel="noopener" onclick="event.stopPropagation()"><span class="i">${i}</span>${l}</a>`;
function actions(k,x){
  if(k==='firms')return [A(LI_C(x.name),'🏢','LinkedIn'),A(WEB(x.name),'🌐','Website'),A(MAPS(x.name+' '+loc(x)),'📍','HQ / Map'),x.license_no?A(DRE(x.license_no),'🪪','DRE'):'',A(GOOG(x.name+' brokers '+loc(x)),'🔎','Google')].filter(Boolean).join('');
  if(k==='owners'){const ad=[x.address,x.city].filter(Boolean).join(', ');return [A(MAPS(ad),'📍','Map'),A(GOOG(ad+' '+(x.use_desc||'')+' owner'),'🔎','Owner / deal'),A('https://www.google.com/maps/@?api=1&map_action=pano&viewpoint='+enc(ad),'📸','Street View'),A(ASSR(x.ain),'🏛️','Assessor')].join('');}
  if(k==='sublease'){const ad=[x.address,x.city].filter(Boolean).join(', ');return [
    (x.listing_url&&/^https?:\/\//i.test(x.listing_url))?A(x.listing_url,'🔗','Listing'):'',
    A(MAPS(ad),'📍','Map'),
    A('https://www.google.com/maps/@?api=1&map_action=pano&viewpoint='+enc(ad),'📸','Street View'),
    x.broker_name?A(LI_P(x.broker_name,x.firm_name),'👤','Broker'):'',
    x.firm_name?A(LI_C(x.firm_name),'🏢','Firm'):'',
    x.broker_email?A('mailto:'+x.broker_email,'✉️','Email'):'',
    x.broker_phone?A('tel:'+String(x.broker_phone).replace(/[^\d+]/g,''),'📞','Call'):'',
    x.sublandlord?A(GOOG('"'+x.sublandlord+'" '+(x.city||'')+' lease'),'🔎','Sublandlord'):''
  ].filter(Boolean).join('');}
  if(k==='homes'){const ad=[x.street,x.city,x.state].filter(Boolean).join(', ');return [
    (x.sourceUrl&&/^https?:\/\//i.test(x.sourceUrl))?A(x.sourceUrl,'🔗','Listing'):'',
    A(MAPS(ad),'📍','Map'),
    A('https://www.google.com/maps/@?api=1&map_action=pano&viewpoint='+enc(ad),'📸','Street View'),
    (x.builder&&x.builder.name)?A(GOOG(x.builder.name+' new homes '+(x.city||'')),'🏗️','Builder'):'',
    A(GOOG(ad+' new construction home'),'🔎','Google')
  ].filter(Boolean).join('');}
  if(k==='commercial'){return [
    x.phone?A('tel:'+String(x.phone).replace(/[^\d+]/g,''),'📞','Call'):'',
    x.email?A('mailto:'+x.email,'✉️','Email'):'',
    x.website?A(/^https?:\/\//i.test(x.website)?x.website:'https://'+x.website,'🌐','Website'):'',
    x.linkedin?A(x.linkedin,'👤','LinkedIn'):A(LI_P(x.name,x.firm),'👤','LinkedIn'),
    x.firm?A(LI_C(x.firm),'🏢','Firm'):'',
    A(GOOG(x.name+' '+(x.firm||'')+' commercial real estate'),'🔎','Google')
  ].filter(Boolean).join('');}
  return [A(LI_P(x.name,x.firm_name),'👤','LinkedIn'),A(GOOG(x.name+' '+(x.firm_name||'')+' '+loc(x)),'🔎','Google'),x.firm_name?A(WEB(x.firm_name),'🌐','Firm site'):'',x.firm_name?A(LI_C(x.firm_name),'🏢','Firm'):'',loc(x)?A(MAPS((x.firm_name||x.name)+' '+loc(x)),'📍','Map'):'',x.license_no?A(DRE(x.license_no),'🪪','DRE'):''].filter(Boolean).join('');
}
const subRate=x=>x.asking_rate?('$'+Number(x.asking_rate).toFixed(2)+'/'+(x.rate_period||'yr')+' SF'):'Rate on request';
const nm=x=>x.name||x.address||x.street||x.ain, sub=(k,x)=>k==='brokers'?(x.firm_name||'—'):k==='firms'?((x.agent_count||0).toLocaleString()+' agents'):k==='sublease'?[x.sqft?Number(x.sqft).toLocaleString()+' SF':null,subRate(x),x.submarket||x.city].filter(Boolean).join(' · '):k==='homes'?[x.price?shortUsd(x.price):null,x.beds?x.beds+'bd':null,x.sqft?Number(x.sqft).toLocaleString()+' sf':null,[x.city,x.state].filter(Boolean).join(', ')].filter(Boolean).join(' · '):k==='commercial'?[x.firm,x.listings?x.listings+' listings':null,x.agent_type].filter(Boolean).join(' · '):(x.type_label||'');
let D={scope:'brokers',view:'directory',selDir:0,selMap:0};
// chrome
$('burger').onclick=()=>{$('scrim').classList.add('open');$('drawer').classList.add('open');};
const cd=()=>{$('scrim').classList.remove('open');$('drawer').classList.remove('open');};$('dclose').onclick=cd;$('scrim').onclick=cd;
$('user').onclick=()=>{$('scrim').classList.add('open');$('drawer').classList.add('open');};
async function jget(u,fb){try{const r=await fetch(u,{credentials:'same-origin'});if(!r.ok)return fb;return await r.json();}catch(e){return fb;}}
(async()=>{
  const [b,f,la,sl,hm,cb,cs]=await Promise.all([
    jget('/api/brokers?state=CA&limit=500',null).then(v=>(v&&v.rows&&v.rows.length)?v:jget('desk-assets/data/brokers.json',{rows:[]})),
    jget('/api/firms?state=CA&limit=200',null).then(v=>(v&&v.rows&&v.rows.length)?v:jget('desk-assets/data/firms.json',{rows:[]})),
    jget('/api/la-commercial',null).then(v=>v||jget('desk-assets/data/la-commercial.json',{marquee:[]})),
    jget('/api/sublease?limit=500',{rows:[],total:0}),
    jget('/api/homes/search?pageSize=60',{homes:[],total:0}),
    jget('/api/cre/brokers/all?limit=3000',{brokers:[]}),
    jget('/api/cre/crcp/stats',{})]);
  D.brokers=b.rows||[];D.firms=f.rows||[];D.owners=la.marquee||[];D.subleases=(sl&&sl.rows)||[];
  D.homes=(hm&&hm.homes)||[];D.totalH=(hm&&hm.total)||D.homes.length;
  D.commercial=(cb&&(cb.brokers||cb.rows))||(Array.isArray(cb)?cb:[]);D.totalC=(cs&&(cs.brokers_web||cs.brokers))||D.commercial.length;D.creStats=cs||{};
  D.totalB=b.total||D.brokers.length;D.totalF=f.total||D.firms.length;D.totalS=(sl&&sl.total)||D.subleases.length;
  $('cB').textContent=D.brokers.length;$('cF').textContent=D.firms.length;$('cO').textContent=D.owners.length;$('cS').textContent=D.subleases.length;$('cH').textContent=D.homes.length;$('cC').textContent=D.commercial.length;
  $('fresh').textContent='CA DRE registry · '+D.brokers.length+' brokers of '+D.totalB.toLocaleString()+' statewide';
  renderAll();
})();
function scopeData(){return D.scope==='brokers'?D.brokers:D.scope==='firms'?D.firms:D.scope==='sublease'?D.subleases:D.scope==='homes'?D.homes:D.scope==='commercial'?D.commercial:D.owners;}
const debounce=(fn,ms)=>{let t;return(...a)=>{clearTimeout(t);t=setTimeout(()=>fn(...a),ms);};};
// live server-side search across the FULL registry (382k brokers / 34k firms) via the proxy;
// owners are only 12 so they filter client-side; empty term returns the loaded set.
async function liveRows(scope,term){
  term=(term||'').trim();
  if(!term) return scopeData();
  if(scope==='owners') return D.owners.filter(x=>JSON.stringify(x).toLowerCase().includes(term.toLowerCase()));
  if(scope==='sublease'){const v=await jget('/api/sublease?limit=200&q='+encodeURIComponent(term),null);if(v&&v.rows)return v.rows;return D.subleases.filter(x=>JSON.stringify(x).toLowerCase().includes(term.toLowerCase()));}
  if(scope==='homes'){const v=await jget('/api/homes/search?limit=200&city='+encodeURIComponent(term),null);if(v&&v.homes&&v.homes.length)return v.homes;return D.homes.filter(x=>JSON.stringify(x).toLowerCase().includes(term.toLowerCase()));}
  if(scope==='commercial')return D.commercial.filter(x=>JSON.stringify(x).toLowerCase().includes(term.toLowerCase()));
  const ep=scope==='firms'?'/api/firms':'/api/brokers';
  const v=await jget(ep+'?state=CA&q='+encodeURIComponent(term)+'&limit=100',null);
  if(v&&v.rows&&v.rows.length) return v.rows;
  return scopeData().filter(x=>JSON.stringify(x).toLowerCase().includes(term.toLowerCase()));
}
// rail
// Views are contextual to the scope — a scope only exposes the views that actually render ITS data,
// so you can never land on a mismatched combo (e.g. Sublease + a firm-only chart).
const SCOPE_VIEWS={brokers:['directory','analytics'],firms:['directory','analytics'],owners:['directory'],sublease:['directory'],homes:['directory'],commercial:['directory']};
function applyScopeViews(){
  const ok=SCOPE_VIEWS[D.scope]||['directory'];
  document.querySelectorAll('#rail [data-v]').forEach(b=>{const good=ok.includes(b.dataset.v);b.disabled=!good;b.style.opacity=good?'':'.32';b.style.cursor=good?'':'not-allowed';});
  if(!ok.includes(D.view)){D.view='directory';
    document.querySelectorAll('#rail [data-v]').forEach(x=>x.classList.toggle('on',x.dataset.v==='directory'));
    document.querySelectorAll('.view').forEach(v=>v.classList.toggle('on',v.id==='v-directory'));}
}
$('rail').addEventListener('click',e=>{const btn=e.target.closest('button');if(!btn||btn.disabled)return;
  if(btn.dataset.v){document.querySelectorAll('#rail [data-v]').forEach(x=>x.classList.remove('on'));btn.classList.add('on');D.view=btn.dataset.v;document.querySelectorAll('.view').forEach(v=>v.classList.remove('on'));$('v-'+D.view).classList.add('on');}
  if(btn.dataset.s){document.querySelectorAll('#rail .scope').forEach(x=>x.classList.remove('on'));btn.classList.add('on');D.scope=btn.dataset.s;D.selDir=0;renderAll();}
});
function renderAll(){directory();analytics();applyScopeViews();}
// ── Directory (master-detail) ──
function directory(){
  const rows=scopeData();
  $('v-directory').innerHTML=`<div class="md"><div class="list"><div class="search"><input id="dq" placeholder="Search ${D.scope}…"><div class="fresh" id="dcount" style="padding:7px 2px 0"></div></div><div class="rows" id="drows"></div></div><div class="detail" id="ddetail"></div></div>`;
  function list(){let r=rows.slice();const v=($('dq').value||'').toLowerCase();if(v)r=r.filter(x=>JSON.stringify(x).toLowerCase().includes(v));return r;}
  function detail(x){if(!x){$('ddetail').innerHTML='';return;}
    const grid=D.scope==='sublease'?[['SqFt',x.sqft?Number(x.sqft).toLocaleString():'—'],['Asking',subRate(x)],['Term',x.term_months?x.term_months+' mo':'—'],['Available',x.available_date?dstr(x.available_date):'Now'],['Sublandlord',x.sublandlord||'—'],['Listing broker',x.firm_name||'—']]:D.scope==='owners'?[['Assessed',shortUsd(x.assessed_total)],['SqFt',(x.sqft||0).toLocaleString()],['Built',x.year_built||'—'],['AIN',x.ain]]:D.scope==='firms'?[['Agents',(x.agent_count||0).toLocaleString()],['HQ',loc(x)||'—'],['License',x.license_no||'—'],['Source',x.source||'—']]:D.scope==='homes'?[['Price',x.price?shortUsd(x.price):'—'],['Beds / Baths',(x.beds||'—')+' / '+(x.bathsTotal||'—')],['SqFt',(x.sqft||0).toLocaleString()],['Type',x.homeType||'—'],['Status',x.constructionStatus||x.status||'—'],['Builder',(x.builder&&x.builder.name)||'—'],['Community',(x.community&&x.community.name)||'—'],['Completion',x.estCompletionDate?dstr(x.estCompletionDate):'—']]:D.scope==='commercial'?[['Firm',x.firm||'—'],['Phone',x.phone||'—'],['Email',x.email||'—'],['Listings',String(x.listings??'0')],['Type',x.agent_type||'—'],['Specialties',x.specialties||'—'],['License',x.dre_license||x.license||'—'],['Office',x.office_addr||'—']]:[['License',x.license_no||'—'],['Type',x.license_type||'—'],['Firm',x.firm_name||'—'],['City',x.city||'—']];
    const dk=D.scope==='sublease'?(x.lease_type==='direct'?'Direct Lease':'Sublease Available'):D.scope==='owners'?(x.type_label||'Asset'):D.scope==='firms'?'Brokerage':D.scope==='homes'?'New Construction':D.scope==='commercial'?'Commercial Broker':(x.license_type||'Licensee');
    const dmeta=D.scope==='sublease'?[x.floor_suite,x.submarket,x.city].filter(Boolean).join(' · '):D.scope==='owners'?[x.address,x.city].filter(Boolean).join(', '):D.scope==='homes'?[x.street,x.city,x.state,x.zip].filter(Boolean).join(', '):D.scope==='commercial'?[x.firm,x.agent_type,x.office_addr].filter(Boolean).join(' · '):(sub(D.scope,x)+' · '+(loc(x)||''));
    $('ddetail').innerHTML=`<div class="k">${esc(dk)}</div><h2>${esc(nm(x))}</h2><div class="meta">${esc(dmeta)}</div><div class="acts">${actions(D.scope,x)}</div><div class="grid">${grid.map(([k,v])=>`<div class="cell"><div class="kk">${k}</div><div class="vv">${esc(v)}</div></div>`).join('')}</div>`;}
  const emptyMsg=D.scope==='sublease'
    ? '<div class="fresh" style="padding:20px 16px;line-height:1.6">No sublease listings yet.<br>Inventory is entered by the RENTV desk from live broker deals — <a class="field-link" href="/desk-admin">add a listing →</a></div>'
    : '<div class="fresh">No matches in the registry.</div>';
  async function draw(){const r=await liveRows(D.scope,$('dq').value);
    const tot=D.scope==='brokers'?D.totalB:D.scope==='firms'?D.totalF:D.scope==='sublease'?D.totalS:D.scope==='homes'?D.totalH:D.scope==='commercial'?D.totalC:D.owners.length;
    const term=($('dq').value||'').trim();
    $('dcount').textContent=r.length?(term?`${r.length} match${r.length===1?'':'es'}`:`Showing ${r.length.toLocaleString()} of ${Number(tot).toLocaleString()}`):'';
    $('drows').innerHTML=r.map((x,i)=>`<div class="row${i===D.selDir?' sel':''}" data-i="${i}"><div class="nm">${esc(nm(x))}</div><div class="f">${esc(sub(D.scope,x))}</div></div>`).join('')||emptyMsg;
    if(!r.length&&D.scope==='sublease'){$('ddetail').innerHTML='<div class="fresh" style="padding:30px 36px;line-height:1.7;max-width:520px">This desk surfaces <b style="color:#e7ecf1">real sublease space</b> — sqft, asking rate, term, the sublandlord, and the listing broker, each with click-outs to the listing, map, and broker. It fills as the RENTV team logs deals from their broker network.</div>';return;}
    $('drows').querySelectorAll('.row').forEach(row=>row.onclick=()=>{D.selDir=+row.dataset.i;$('drows').querySelectorAll('.row').forEach(x=>x.classList.remove('sel'));row.classList.add('sel');detail(r[D.selDir]);});detail(r[D.selDir]||r[0]);}
  $('dq').oninput=debounce(()=>{D.selDir=0;draw();},300);draw();
}
// Programmatic scope switch — the drill target for a count (Steve's "counts → filtered list" rule).
// Reuses the rail's scope/view machinery so a KPI card lands on that scope's directory list.
function gotoScope(s){
  if(!s) return;
  document.querySelectorAll('#rail .scope').forEach(x=>x.classList.toggle('on',x.dataset.s===s));
  D.scope=s; D.selDir=0; D.view='directory';
  document.querySelectorAll('#rail [data-v]').forEach(x=>x.classList.toggle('on',x.dataset.v==='directory'));
  document.querySelectorAll('.view').forEach(v=>v.classList.toggle('on',v.id==='v-directory'));
  renderAll();
}
// ── Analytics ──
function analytics(){
  const top=D.firms.slice().sort((a,b)=>(b.agent_count||0)-(a.agent_count||0)).slice(0,9);const max=top[0]?.agent_count||1;
  // each KPI carries the directory scope it drills to (null = not drillable)
  const kpis=[['CA Brokers',D.totalB.toLocaleString(),'brokers'],['CA Firms',D.totalF.toLocaleString(),'firms'],['Top firm',(top[0]?.agent_count||0).toLocaleString()+' agents','firms'],['Marquee assets',D.owners.length,'owners']];
  $('v-analytics').innerHTML=`<div class="an"><div class="kpis">${kpis.map(([k,v,s])=>`<div class="kpi${s?' lk':''}"${s?` data-goto="${esc(s)}" role="button" tabindex="0" title="Open ${esc(k)} in the directory"`:''}><div class="k">${k}</div><div class="v">${v}</div></div>`).join('')}</div><div class="panel"><h3>Top brokerages by licensed agents</h3><div id="bars">${
    top.map(f=>`<div class="barwrap"><div class="bar"><span class="lab" title="${esc(f.name)}">${esc(f.name)}</span><span class="track"><span class="fill" style="width:${Math.round((f.agent_count||0)/max*100)}%"></span></span><span class="val">${(f.agent_count||0).toLocaleString()}</span></div><div class="acts">${actions('firms',f)}</div></div>`).join('')
  }</div></div></div>`;
  $('bars').querySelectorAll('.barwrap').forEach(w=>w.querySelector('.bar').onclick=e=>{if(e.target.closest('a'))return;w.classList.toggle('open');});
  $('v-analytics').querySelectorAll('.kpi[data-goto]').forEach(c=>{const go=()=>gotoScope(c.dataset.goto);c.onclick=go;c.onkeydown=e=>{if(e.key==='Enter'||e.key===' '){e.preventDefault();go();}};});
}
</script>
</body></html>