β 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 β