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:
+1
-1
@@ -17,7 +17,7 @@
|
|||||||
<section id="tabDash" class="mp-panel">
|
<section id="tabDash" class="mp-panel">
|
||||||
<div id="dFacts" class="facts" style="grid-template-columns:repeat(3,1fr);margin:18px 0"></div>
|
<div id="dFacts" class="facts" style="grid-template-columns:repeat(3,1fr);margin:18px 0"></div>
|
||||||
<div id="dNextStep"></div><div id="dUpgrade"></div>
|
<div id="dNextStep"></div><div id="dUpgrade"></div>
|
||||||
<div class="table-card" style="margin-bottom:18px"><div style="display:flex;justify-content:space-between;gap:12px;align-items:flex-start;flex-wrap:wrap"><div><h2 style="margin:0 0 4px">Your team</h2><p style="color:var(--muted);font-size:13px;margin:0 0 14px">Your position's matrix — the rollup line on each card counts everyone underneath, all the way down. Click a position to drill into that leg. Open slots are where the next placements land.</p></div><button id="dTreeToggle" class="btn btn-secondary btn-sm">List view</button></div><div id="dOrgBar"></div><div id="dTreeNav" style="display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin-bottom:12px"></div><div id="dTree"></div><div id="dGens" style="margin-top:14px"></div><p class="micro" style="margin:14px 0 0"><span class="mtp-qmark" style="position:static;display:inline-grid;vertical-align:middle">✓</span> qualified (2/2 directs) · <span class="mt-badge mt-prem">P</span> Premium · <span class="mt-badge">S</span> Standard · ⬇ everyone below that position (all generations) and the POL they've earned · <span style="color:var(--teal)">↧ spillover</span> = placed there by upline activity; only members who join with a position's own ID count toward its 2/2</p><div id="dSpillNote" class="hidden"></div></div>
|
<div class="table-card" style="margin-bottom:18px"><div style="display:flex;justify-content:space-between;gap:12px;align-items:flex-start;flex-wrap:wrap"><div><h2 style="margin:0 0 4px">Your team</h2><p style="color:var(--muted);font-size:13px;margin:0 0 14px">Your position's matrix — the rollup line on each card counts everyone underneath, all the way down. Click a position to drill into that leg. Open slots are where the next placements land.</p></div><div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap"><div id="dZoom" class="mt-zoom hidden"><button id="zOut" class="btn btn-secondary btn-sm mt-zbtn" title="Zoom out" aria-label="Zoom out">−</button><span id="zLbl" class="micro" style="min-width:40px;text-align:center;margin:0">100%</span><button id="zIn" class="btn btn-secondary btn-sm mt-zbtn" title="Zoom in" aria-label="Zoom in">+</button><button id="zFit" class="btn btn-secondary btn-sm" title="Fit the whole matrix on screen">Fit</button></div><button id="dTreeToggle" class="btn btn-secondary btn-sm">List view</button></div></div><div id="dOrgBar"></div><div id="dTreeNav" style="display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin-bottom:12px"></div><div id="dTree"></div><div id="dGens" style="margin-top:14px"></div><p class="micro" style="margin:14px 0 0"><span class="mtp-qmark" style="position:static;display:inline-grid;vertical-align:middle">✓</span> qualified (2/2 directs) · <span class="mt-badge mt-prem">P</span> Premium · <span class="mt-badge">S</span> Standard · ⬇ everyone below that position (all generations) and the POL they've earned · <span style="color:var(--teal)">↧ spillover</span> = placed there by upline activity; only members who join with a position's own ID count toward its 2/2</p><div id="dSpillNote" class="hidden"></div></div>
|
||||||
<div class="table-card" style="margin-bottom:18px;border-color:rgba(123,224,161,.4)"><h2 style="margin:0 0 4px">Your pipeline</h2><p style="color:var(--muted);font-size:13px;margin:0 0 12px">Money forming below you. Each generation in your leg pays your position at exactly one level — when a member's level catches up to their depth, their <em>next</em> upgrade comes to you.</p><div id="dPipeline"></div></div>
|
<div class="table-card" style="margin-bottom:18px;border-color:rgba(123,224,161,.4)"><h2 style="margin:0 0 4px">Your pipeline</h2><p style="color:var(--muted);font-size:13px;margin:0 0 12px">Money forming below you. Each generation in your leg pays your position at exactly one level — when a member's level catches up to their depth, their <em>next</em> upgrade comes to you.</p><div id="dPipeline"></div></div>
|
||||||
<div id="dCoachCard" class="table-card" style="margin-bottom:18px;display:none;border-color:rgba(240,197,109,.35)"><h2 style="margin:0 0 4px">Coach your team</h2><p style="color:var(--muted);font-size:13px;margin:0 0 12px">The fastest way to grow your own income is helping the people below you take their next step. Here's who in <em>your</em> team could use a nudge today — updated live from the blockchain.</p><div id="dCoach"></div></div>
|
<div id="dCoachCard" class="table-card" style="margin-bottom:18px;display:none;border-color:rgba(240,197,109,.35)"><h2 style="margin:0 0 4px">Coach your team</h2><p style="color:var(--muted);font-size:13px;margin:0 0 12px">The fastest way to grow your own income is helping the people below you take their next step. Here's who in <em>your</em> team could use a nudge today — updated live from the blockchain.</p><div id="dCoach"></div></div>
|
||||||
<div class="table-card" style="margin-bottom:18px;border-color:rgba(120,160,255,.35)"><h2 style="margin:0 0 4px">Messages <span id="msgBell" class="hidden" style="font-size:14px;background:var(--gold);color:#132;border-radius:20px;padding:2px 10px;vertical-align:middle;font-weight:800">🔔 0</span></h2><p style="color:var(--muted);font-size:13px;margin:0 0 12px">Wallet-verified messaging along your matrix lines — your upline and your team can reach you here, and you can reach them. No email needed; your wallet is your identity. <span class="micro">Messages are member-to-member; the team admin can review them for abuse.</span></p><div id="dMsg"><div class="empty">Loading…</div></div></div>
|
<div class="table-card" style="margin-bottom:18px;border-color:rgba(120,160,255,.35)"><h2 style="margin:0 0 4px">Messages <span id="msgBell" class="hidden" style="font-size:14px;background:var(--gold);color:#132;border-radius:20px;padding:2px 10px;vertical-align:middle;font-weight:800">🔔 0</span></h2><p style="color:var(--muted);font-size:13px;margin:0 0 12px">Wallet-verified messaging along your matrix lines — your upline and your team can reach you here, and you can reach them. No email needed; your wallet is your identity. <span class="micro">Messages are member-to-member; the team admin can review them for abuse.</span></p><div id="dMsg"><div class="empty">Loading…</div></div></div>
|
||||||
|
|||||||
+30
-1
@@ -13,6 +13,7 @@
|
|||||||
const tp=document.getElementById('tabPitch'),td=document.getElementById('tabDash');
|
const tp=document.getElementById('tabPitch'),td=document.getElementById('tabDash');
|
||||||
if(tp)tp.classList.toggle('on',name==='pitch');
|
if(tp)tp.classList.toggle('on',name==='pitch');
|
||||||
if(td)td.classList.toggle('on',name==='dash');
|
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'});}));
|
document.querySelectorAll('.mp-tab').forEach(t=>t.addEventListener('click',()=>{setTab(t.dataset.tab);window.scrollTo({top:0,behavior:'smooth'});}));
|
||||||
function renderPitch(d){
|
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 ''}}
|
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 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 isSpill(n){return !!(n&&treeUI.parent[n.id]&&n.referrerId&&n.referrerId!==treeUI.parent[n.id])}
|
||||||
function card(n,focus){
|
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>`:'';
|
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()}}));
|
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);
|
wire(out);wire(nav);
|
||||||
|
applyMatrixZoom();
|
||||||
}
|
}
|
||||||
function renderList(){
|
function renderList(){
|
||||||
const out=document.getElementById('dTree');
|
const out=document.getElementById('dTree');
|
||||||
document.getElementById('dTreeNav').innerHTML='';
|
document.getElementById('dTreeNav').innerHTML='';
|
||||||
|
const zb=document.getElementById('dZoom');if(zb)zb.classList.add('hidden');
|
||||||
const home=treeUI.byId[treeUI.homeId];
|
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}
|
if(!home){out.innerHTML='<div class="empty">Team view is still indexing — check back in a few minutes.</div>';return}
|
||||||
const node=(n,depth)=>{
|
const node=(n,depth)=>{
|
||||||
|
|||||||
@@ -135,6 +135,8 @@ a{color:inherit}.wrap{width:min(1160px,calc(100% - 32px));margin:auto}.nav{heigh
|
|||||||
|
|
||||||
/* Admin matrix pyramid view */
|
/* Admin matrix pyramid view */
|
||||||
.mtp{display:flex;flex-direction:column;gap:14px;overflow-x:auto;padding:6px 2px 10px}
|
.mtp{display:flex;flex-direction:column;gap:14px;overflow-x:auto;padding:6px 2px 10px}
|
||||||
|
.mt-zoom{display:inline-flex;gap:4px;align-items:center;background:rgba(13,34,54,.6);border:1px solid #2a4a66;border-radius:9px;padding:3px 4px}
|
||||||
|
.mt-zbtn{min-width:30px;padding-left:0;padding-right:0;font-weight:800;font-size:16px;line-height:1}
|
||||||
.mtp-row{display:flex;justify-content:center;gap:8px;min-width:max-content;margin:0 auto}
|
.mtp-row{display:flex;justify-content:center;gap:8px;min-width:max-content;margin:0 auto}
|
||||||
.mtp-card{appearance:none;font:inherit;cursor:pointer;text-align:center;background:#0d2236;border:1px solid #2a4a66;border-radius:12px;padding:9px 10px;min-width:104px;color:var(--text);display:flex;flex-direction:column;gap:2px;align-items:center;transition:.15s ease}
|
.mtp-card{appearance:none;font:inherit;cursor:pointer;text-align:center;background:#0d2236;border:1px solid #2a4a66;border-radius:12px;padding:9px 10px;min-width:104px;color:var(--text);display:flex;flex-direction:column;gap:2px;align-items:center;transition:.15s ease}
|
||||||
.mtp-card:hover{border-color:var(--teal);transform:translateY(-1px)}
|
.mtp-card:hover{border-color:var(--teal);transform:translateY(-1px)}
|
||||||
|
|||||||
Reference in New Issue
Block a user