[object Object]

← back to Dw Catalog 3d

D2: fold in-panel πŸ“Š Charts overlay (status composition + top-vendors-by-active + staged pipeline) β€” pure client-side from loaded scene.json, esc-aware

8b5ce326e81cf3fc4ba3e6218585692479940614 Β· 2026-06-26 10:14:54 -0700 Β· Steve Abrams

Files touched

Diff

commit 8b5ce326e81cf3fc4ba3e6218585692479940614
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Fri Jun 26 10:14:54 2026 -0700

    D2: fold in-panel πŸ“Š Charts overlay (status composition + top-vendors-by-active + staged pipeline) β€” pure client-side from loaded scene.json, esc-aware
---
 public/index.html | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 65 insertions(+), 1 deletion(-)

diff --git a/public/index.html b/public/index.html
index a97b59b..7943dc6 100644
--- a/public/index.html
+++ b/public/index.html
@@ -58,6 +58,27 @@
   #stchips button.off{opacity:.4}
   #stchips button .d{width:9px;height:9px;border-radius:3px;box-shadow:0 0 7px currentColor}
   #stchips button .n{font-variant-numeric:tabular-nums;color:var(--muted)}
+  /* D2: Charts tab */
+  #chartsBtn{position:fixed;z-index:11;right:24px;bottom:54px;background:var(--panel);backdrop-filter:blur(8px);
+    border:1px solid var(--gold);color:var(--gold);border-radius:10px;padding:8px 14px;font-size:13px;font-weight:700;cursor:pointer}
+  #charts{position:fixed;inset:0;z-index:40;display:none;background:#0a0d13ee;backdrop-filter:blur(6px);overflow:auto;padding:64px 40px 48px}
+  #charts.show{display:block}
+  #charts .close{position:fixed;top:20px;right:28px;background:var(--panel);border:1px solid var(--gold);color:var(--gold);
+    border-radius:10px;padding:8px 16px;font-size:13px;font-weight:700;cursor:pointer;z-index:41}
+  #charts h2{margin:0 0 26px;font-size:20px;font-weight:800;letter-spacing:.5px}
+  #charts h2 .g{color:var(--gold)}
+  #charts .panel{background:var(--panel);border:1px solid #2A2F3A;border-radius:14px;padding:18px 20px;margin:0 auto 22px;max-width:880px}
+  #charts .panel h3{margin:0 0 14px;font-size:12px;letter-spacing:1.4px;color:var(--muted);text-transform:uppercase}
+  #charts .stack{display:flex;height:34px;border-radius:8px;overflow:hidden;border:1px solid #2A2F3A}
+  #charts .stack span{display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:700;color:#0c0f15;min-width:0;overflow:hidden;white-space:nowrap}
+  #charts .stacklegend{display:flex;flex-wrap:wrap;gap:14px;margin-top:12px;font-size:12px;color:var(--muted)}
+  #charts .stacklegend span{display:flex;align-items:center;gap:6px}
+  #charts .stacklegend .d{width:11px;height:11px;border-radius:3px}
+  #charts .hbar{display:flex;align-items:center;gap:12px;margin:7px 0;font-size:12.5px}
+  #charts .hbar .nm{width:170px;flex:0 0 auto;color:var(--fg);font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
+  #charts .hbar .track{flex:1;height:18px;background:#161c28;border-radius:5px;overflow:hidden}
+  #charts .hbar .fill{height:100%;border-radius:5px}
+  #charts .hbar .ct{width:74px;flex:0 0 auto;text-align:right;color:var(--muted);font-variant-numeric:tabular-nums}
 </style>
 </head>
 <body>
@@ -77,6 +98,14 @@
   <div id="scaleToggle"><button data-s="sqrt">√ scale</button><button data-s="log" class="sel">log scale</button><button data-s="lin">linear</button></div>
 </div>
 <button id="back" class="hud">β€Ή back</button>
+<button id="chartsBtn" class="hud" title="Aggregate catalog charts">πŸ“Š Charts</button>
+<div id="charts">
+  <button class="close" id="chartsClose">βœ• close</button>
+  <h2>DESIGNER WALLCOVERINGS Β· <span class="g">CATALOG CHARTS</span></h2>
+  <div class="panel"><h3 id="chTotal">Status Composition</h3><div id="chStack" class="stack"></div><div id="chStackLeg" class="stacklegend"></div></div>
+  <div class="panel"><h3>Top Vendors by Active Products</h3><div id="chTopVendors"></div></div>
+  <div class="panel"><h3>Staged Pipeline Β· Vendors with the Most Staged</h3><div id="chStaged"></div></div>
+</div>
 <div id="hint" class="hud">click a vendor β†’ drill into collections β†’ products β†’ open on store Β· drag to orbit Β· esc to go up</div>
 <div id="tooltip"></div>
 <div id="loading" class="hud">loading…</div>
@@ -327,7 +356,7 @@ addEventListener('mousemove',e=>{
   }
 });
 document.getElementById('back').onclick=()=>{ if(state.level===2) drillVendor(state.vendor); else renderVendors(); };
-addEventListener('keydown',e=>{ if(e.key==='Escape'){ if(state.level===2)drillVendor(state.vendor); else if(state.level===1)renderVendors(); }});
+addEventListener('keydown',e=>{ if(e.key==='Escape'){ if(document.getElementById('charts').classList.contains('show'))return; if(state.level===2)drillVendor(state.vendor); else if(state.level===1)renderVendors(); }});
 document.querySelectorAll('#scaleToggle button').forEach(b=>b.onclick=()=>{
   document.querySelectorAll('#scaleToggle button').forEach(x=>x.classList.remove('sel'));
   b.classList.add('sel'); scaleMode=b.dataset.s; if(state.level===0)renderVendors(); });
@@ -360,6 +389,41 @@ sinput.addEventListener('keydown',e=>{
 });
 document.addEventListener('click',e=>{ if(!document.getElementById('search').contains(e.target)) sresults.style.display='none'; });
 
+// ---- D2: client-side aggregate charts (pure DOM, from already-loaded DATA) ----
+function renderCharts(){
+  if(!DATA.length) return;
+  // 1) status composition β€” stacked bar of catalog-wide totals
+  const tot=Object.fromEntries(LAYERS.map(L=>[L.key,DATA.reduce((s,d)=>s+(d[L.key]||0),0)]));
+  const grand=LAYERS.reduce((s,L)=>s+tot[L.key],0)||1;
+  document.getElementById('chTotal').textContent=`Status Composition Β· ${grand.toLocaleString()} total products`;
+  document.getElementById('chStack').innerHTML=LAYERS.map(L=>{
+    const pct=tot[L.key]/grand*100; if(pct<=0)return'';
+    return `<span style="background:${HEX(L.key)};flex:${tot[L.key]} ${tot[L.key]} 0" title="${L.name}: ${tot[L.key].toLocaleString()}">${pct>=5?Math.round(pct)+'%':''}</span>`;
+  }).join('');
+  document.getElementById('chStackLeg').innerHTML=LAYERS.map(L=>
+    `<span><span class="d" style="background:${HEX(L.key)}"></span>${L.name} ${tot[L.key].toLocaleString()}</span>`).join('');
+  // 2) top vendors by active
+  const hbar=(rows,max,col)=>rows.map(r=>{
+    const w=max>0?(r.v/max*100):0;
+    return `<div class="hbar"><span class="nm" title="${r.n}">${r.n}</span>`+
+      `<span class="track"><span class="fill" style="width:${w}%;background:${col}"></span></span>`+
+      `<span class="ct">${r.v.toLocaleString()}</span></div>`;
+  }).join('');
+  const topA=[...DATA].filter(d=>d.active>0).sort((a,b)=>b.active-a.active).slice(0,12).map(d=>({n:d.vendor,v:d.active}));
+  document.getElementById('chTopVendors').innerHTML=hbar(topA,topA.length?topA[0].v:0,HEX('active'));
+  // 3) staged pipeline split β€” vendors with the most staged
+  const topS=[...DATA].filter(d=>d.staged>0).sort((a,b)=>b.staged-a.staged).slice(0,12).map(d=>({n:d.vendor,v:d.staged}));
+  document.getElementById('chStaged').innerHTML=topS.length
+    ? hbar(topS,topS[0].v,HEX('staged'))
+    : '<div style="color:var(--muted);font-size:13px">No staged products in the catalog right now.</div>';
+}
+const chartsEl=document.getElementById('charts');
+function toggleCharts(on){ const show=on==null?!chartsEl.classList.contains('show'):on;
+  if(show){ renderCharts(); chartsEl.classList.add('show'); } else chartsEl.classList.remove('show'); }
+document.getElementById('chartsBtn').onclick=()=>toggleCharts();
+document.getElementById('chartsClose').onclick=()=>toggleCharts(false);
+addEventListener('keydown',e=>{ if(e.key==='Escape'&&chartsEl.classList.contains('show')) toggleCharts(false); });
+
 const __initialHash=location.hash;   // capture before renderVendors() clears it via syncHash
 fetch('scene.json?_='+Date.now()).then(r=>r.json()).then(d=>{
   DATA=d.sort((a,b)=>(b.active+b.archived)-(a.active+a.archived)); renderVendors(); restoreFromHash(__initialHash); });

← 320cfff D1: document daily refresh (path B β€” standalone com.steve.ca  Β·  back to Dw Catalog 3d  Β·  404-guard: exclude placeholder (-handle/-sample/empty) handl 1e317c9 β†’