Matrix view: zoom controls so small screens can see the whole pyramid

The team pyramid overflows on phones. Add -/+/Fit zoom controls to the matrix
(pyramid) view that scale it via CSS zoom, defaulting to auto-fit so the whole
structure fits the screen width on load. Controls show only in pyramid mode,
re-fit on resize and when the dashboard tab opens, and guard against measuring
while the tab is hidden.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
martbost
2026-09-08 19:33:11 -05:00
parent 946a9d75b5
commit 860efc4912
3 changed files with 33 additions and 2 deletions
+30 -1
View File
@@ -13,6 +13,7 @@
const tp=document.getElementById('tabPitch'),td=document.getElementById('tabDash');
if(tp)tp.classList.toggle('on',name==='pitch');
if(td)td.classList.toggle('on',name==='dash');
if(name==='dash'&&treeUI.mode==='pyramid')setTimeout(applyMatrixZoom,30); // fit once the matrix is visible
}
document.querySelectorAll('.mp-tab').forEach(t=>t.addEventListener('click',()=>{setTab(t.dataset.tab);window.scrollTo({top:0,behavior:'smooth'});}));
function renderPitch(d){
@@ -34,7 +35,33 @@
function pathId(){const m=location.pathname.match(/^\/my\/(\d+)$/);if(m)return m[1];const q=new URLSearchParams(location.search).get('id');if(q&&/^\d+$/.test(q))return q;try{return localStorage.getItem('ctb.myId')||''}catch(e){return ''}}
const treeUI={byId:{},parent:{},homeId:0,rootId:0,mode:'pyramid'};
const treeUI={byId:{},parent:{},homeId:0,rootId:0,mode:'pyramid',zoom:null,_applied:1};
// Zoom the matrix so it fits small screens. treeUI.zoom: null = auto-fit (shrink
// to fit width, never enlarge), or a manual factor. Uses CSS `zoom` so the layout
// reflows cleanly (no leftover box, centering + scroll keep working).
function applyMatrixZoom(){
const bar=document.getElementById('dZoom');
const mtp=document.querySelector('#dTree .mtp');
if(!mtp){if(bar)bar.classList.add('hidden');return}
mtp.style.zoom='1';
const availW=mtp.clientWidth;
if(!availW){if(bar)bar.classList.add('hidden');return} // in a hidden tab — apply when it opens
if(bar)bar.classList.remove('hidden');
const natW=mtp.scrollWidth||1;
let z=treeUI.zoom;
if(z==null)z=Math.min(1,availW/natW); // Fit / default: shrink only
z=Math.max(.4,Math.min(1.6,z));
mtp.style.zoom=String(z);
treeUI._applied=z;
const lbl=document.getElementById('zLbl');if(lbl)lbl.textContent=Math.round(z*100)+'%';
}
(function wireZoom(){
const out=document.getElementById('zOut'),inn=document.getElementById('zIn'),fit=document.getElementById('zFit');
if(out)out.addEventListener('click',()=>{treeUI.zoom=Math.max(.4,(treeUI._applied||1)-0.15);applyMatrixZoom()});
if(inn)inn.addEventListener('click',()=>{treeUI.zoom=Math.min(1.6,(treeUI._applied||1)+0.15);applyMatrixZoom()});
if(fit)fit.addEventListener('click',()=>{treeUI.zoom=null;applyMatrixZoom()});
let t;window.addEventListener('resize',()=>{clearTimeout(t);t=setTimeout(()=>{if(treeUI.mode==='pyramid')applyMatrixZoom()},160)});
})();
function indexTree(n,par){if(!n)return;treeUI.byId[n.id]=n;if(par)treeUI.parent[n.id]=par.id;indexTree(n.left,n);indexTree(n.right,n)}
function isSpill(n){return !!(n&&treeUI.parent[n.id]&&n.referrerId&&n.referrerId!==treeUI.parent[n.id])}
function card(n,focus){
@@ -58,10 +85,12 @@
nav.innerHTML=path.length>1?`<span class="micro" style="margin:0">Viewing:</span> `+path.map((id,i)=>i===path.length-1?`<strong class="mt-id">#${id}</strong>`:`<button class="btn btn-secondary btn-sm" data-tid="${id}">#${id}</button>`).join(' <span style="color:var(--muted)">›</span> ')+` <button class="btn btn-secondary btn-sm" data-tid="${treeUI.parent[treeUI.rootId]}">↑ Up one</button>`:'';
const wire=el=>el.querySelectorAll('[data-tid]').forEach(b=>b.addEventListener('click',()=>{const t=Number(b.dataset.tid);if(t&&treeUI.byId[t]){treeUI.rootId=t;renderTree()}}));
wire(out);wire(nav);
applyMatrixZoom();
}
function renderList(){
const out=document.getElementById('dTree');
document.getElementById('dTreeNav').innerHTML='';
const zb=document.getElementById('dZoom');if(zb)zb.classList.add('hidden');
const home=treeUI.byId[treeUI.homeId];
if(!home){out.innerHTML='<div class="empty">Team view is still indexing — check back in a few minutes.</div>';return}
const node=(n,depth)=>{