[object Object]

← back to Rentv

desk analytics: KPI cards (CA Brokers/Firms/Top firm/Marquee assets) now drill into their directory scope per the counts-must-href rule — added gotoScope() reusing the rail scope/view machinery; kpi-link hover/›/keyboard(role=button,Enter/Space) affordances. Previously dead-text numbers

13fd2b8645e36a61ce65cc47b0a572a14ca95658 · 2026-08-05 17:55:16 -0700 · Steve

Files touched

Diff

commit 13fd2b8645e36a61ce65cc47b0a572a14ca95658
Author: Steve <steve@designerwallcoverings.com>
Date:   Wed Aug 5 17:55:16 2026 -0700

    desk analytics: KPI cards (CA Brokers/Firms/Top firm/Marquee assets) now drill into their directory scope per the counts-must-href rule — added gotoScope() reusing the rail scope/view machinery; kpi-link hover/›/keyboard(role=button,Enter/Space) affordances. Previously dead-text numbers
---
 public/desk.html | 18 +++++++++++++++---
 1 file changed, 15 insertions(+), 3 deletions(-)

diff --git a/public/desk.html b/public/desk.html
index cafdccb8..e2a7c046 100644
--- a/public/desk.html
+++ b/public/desk.html
@@ -50,7 +50,7 @@
   .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}
+  .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}.kpi.lk:hover{border-color:#c8102e}.kpi.lk .v{color:#fff}.kpi.lk::after{content:" ›";color:#8b939b}
   .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 */
@@ -206,14 +206,26 @@ function directory(){
     $('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;
-  const kpis=[['CA Brokers',D.totalB.toLocaleString()],['CA Firms',D.totalF.toLocaleString()],['Top firm',(top[0]?.agent_count||0).toLocaleString()+' agents'],['Marquee assets',D.owners.length]];
-  $('v-analytics').innerHTML=`<div class="an"><div class="kpis">${kpis.map(([k,v])=>`<div class="kpi"><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">${
+  // 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>

← c7e15594 auto-save: 2026-08-05T17:44:25 (7 files) — data/deals-regist  ·  back to Rentv  ·  desk KPI: Cody gate — position the drill chevron top-right ( 7c02e37a →