diff --git a/chain.js b/chain.js
index edc675a..6c68fd0 100644
--- a/chain.js
+++ b/chain.js
@@ -332,8 +332,8 @@ function getMatrixTree() {
id, tier: m.tier, tierName: tierName(m.tier), level: m.level, levelName: levelName(m.level),
directCount: m.directCount || 0, earnedPol: m.earnedPol || 0, referrerId: m.referrerId, joinedAt: m.joinedAt
};
- const l = node(m.l, depth + 1), r = node(m.r, depth + 1);
- if (l || r) n.children = [l, r].filter(Boolean);
+ n.left = node(m.l, depth + 1);
+ n.right = node(m.r, depth + 1);
return n;
}
const root = node(1, 0);
diff --git a/public/admin.html b/public/admin.html
index 29c4821..99ef2fb 100644
--- a/public/admin.html
+++ b/public/admin.html
@@ -5,7 +5,7 @@
Traffic & Conversions
First-touch source per visitor session (referring domain or utm_source). Funnel: bridge page → start page → join click.
| Source | Bridge views | Start views | Training views | Join clicks | Start → Join |
|---|
Member ID Submissions
New members who confirmed their purchase on the start page. Each one was posted to your Hermes Telegram chat — add them to the rotation.
| When | Name / Handle | New ID | Joined under | Source | On-chain |
|---|
On-Chain Member Lookup
Enter an RM Circle ID to read its registration, lineage, and every payment it has received — live from the smart contract.
-Matrix Tree
The entire on-chain matrix from the root down — who landed where, with tier, level, directs, and earnings per position.
+Matrix View
The entire on-chain matrix — who landed where, with tier, level, directs, and earnings per position. Click a position to drill down.
Add Sponsor
AI Chat
Paste an OpenRouter API key to switch the help chat from canned answers to AI (). Clear it to switch back.
diff --git a/public/admin.js b/public/admin.js
index 18fc000..02eb1ad 100644
--- a/public/admin.js
+++ b/public/admin.js
@@ -66,24 +66,70 @@ document.getElementById('lookupForm').addEventListener('submit',async e=>{
out.innerHTML=facts+chain+`Payments received by #${d.id} — ${d.income?d.income.length:0} total, newest first:
`+income;
}catch(x){out.innerHTML=`${esc(x.message)}
`}
});
+const matrixUI={data:null,byId:{},parent:{},rootId:1,mode:'pyramid'};
+function mtIndex(n,par){if(!n)return;matrixUI.byId[n.id]=n;if(par)matrixUI.parent[n.id]=par.id;mtIndex(n.left,n);mtIndex(n.right,n)}
+function mtBadge(n){return n.tier===2?'P':'S'}
+const mtFmt=n=>Number(n||0).toLocaleString(undefined,{maximumFractionDigits:0});
+function mtCard(n){
+ if(!n)return 'open
slot
';
+ return ``;
+}
+function renderPyramid(){
+ const out=document.getElementById('matrixTree'),nav=document.getElementById('matrixNav');
+ const root=matrixUI.byId[matrixUI.rootId];
+ if(!root){out.innerHTML='Position not found.
';return}
+ const GEN=4; // root + 3 generations = up to 15 cards
+ const rows=[[root]];
+ for(let g=1;gn?[n.left||null,n.right||null]:[null,null]));
+ }
+ out.innerHTML=`${rows.map(r=>`
${r.map(mtCard).join('')}
`).join('')}
`;
+ // breadcrumb: path from tree root down to current focus
+ const path=[];let cur=matrixUI.rootId;
+ while(cur){path.unshift(cur);cur=matrixUI.parent[cur]}
+ nav.classList.remove('hidden');
+ nav.innerHTML=`Viewing: `+path.map((id,i)=>i===path.length-1?`#${id}`:``).join(' › ')+
+ (matrixUI.parent[matrixUI.rootId]?` `:'');
+ out.querySelectorAll('[data-mid]').forEach(b=>b.addEventListener('click',()=>{matrixUI.rootId=Number(b.dataset.mid);renderPyramid()}));
+ nav.querySelectorAll('[data-mid]').forEach(b=>b.addEventListener('click',()=>{matrixUI.rootId=Number(b.dataset.mid);renderPyramid()}));
+}
+function renderList(){
+ const out=document.getElementById('matrixTree'),d=matrixUI.data;
+ document.getElementById('matrixNav').classList.add('hidden');
+ const node=(n,depth)=>{
+ if(!n)return '';
+ if(n.missing)return `#${n.id} (no data)`;
+ const ch=[n.left,n.right].filter(Boolean);
+ const kids=ch.length?`${ch.map(c=>node(c,depth+1)).join('')}
`:'';
+ const open=depth<4?' open':'';
+ const label=`${mtBadge(n)} #${n.id} ${esc(n.levelName)} · ${n.directCount}/2 directs · ${mtFmt(n.earnedPol)} POL earned${n.referrerId?` · ref #${n.referrerId}`:''}`;
+ return ch.length?`${label}
${kids} `:`${label}`;
+ };
+ const unplaced=d.unplaced?`Not reachable from root: ${d.unplaced.map(i=>'#'+i).join(', ')}
`:'';
+ out.innerHTML=`${unplaced}`;
+}
+function renderMatrix(){
+ const d=matrixUI.data;
+ const head=`${d.memberCount} positions · snapshot ${new Date(d.snapshotAt).toISOString().replace('T',' ').slice(0,16)} UTC · P = Premium, S = Standard`;
+ document.getElementById('treeToggleBtn').classList.remove('hidden');
+ document.getElementById('treeToggleBtn').textContent=matrixUI.mode==='pyramid'?'List view':'Pyramid view';
+ if(matrixUI.mode==='pyramid')renderPyramid();else renderList();
+ const out=document.getElementById('matrixTree');
+ out.insertAdjacentHTML('afterbegin',`${head}
`);
+}
document.getElementById('treeLoadBtn').addEventListener('click',async()=>{
const out=document.getElementById('matrixTree');
- out.innerHTML='Building tree from the on-chain snapshot…
';
+ out.innerHTML='Building matrix from the on-chain snapshot…
';
try{
const d=await api('/api/admin/matrix-tree');
if(!d.ready||!d.root){out.innerHTML='Snapshot not ready yet — try again in a minute.
';return}
- const fmt=n=>Number(n).toLocaleString(undefined,{maximumFractionDigits:0});
- const node=(n,depth)=>{
- if(!n)return '';
- if(n.missing)return `#${n.id} (no data)`;
- const badge=n.tier===2?'P':'S';
- const kids=n.children&&n.children.length?`${n.children.map(c=>node(c,depth+1)).join('')}
`:'';
- const open=depth<4?' open':'';
- const label=`${badge} #${n.id} ${esc(n.levelName)} · ${n.directCount}/2 directs · ${fmt(n.earnedPol)} POL earned${n.referrerId?` · ref #${n.referrerId}`:''}`;
- return n.children&&n.children.length?`${label}
${kids} `:`${label}`;
- };
- const unplaced=d.unplaced?`Not reachable from root: ${d.unplaced.map(i=>'#'+i).join(', ')}
`:'';
- out.innerHTML=`${d.memberCount} positions · snapshot ${new Date(d.snapshotAt).toISOString().replace('T',' ').slice(0,16)} UTC · P = Premium, S = Standard
${unplaced}`;
+ matrixUI.data=d;matrixUI.byId={};matrixUI.parent={};matrixUI.rootId=d.root.id;
+ mtIndex(d.root,null);
+ renderMatrix();
}catch(x){out.innerHTML=`${esc(x.message)}
`}
});
+document.getElementById('treeToggleBtn').addEventListener('click',()=>{
+ matrixUI.mode=matrixUI.mode==='pyramid'?'list':'pyramid';
+ if(matrixUI.data)renderMatrix();
+});
loadState();
diff --git a/public/styles.css b/public/styles.css
index 2f7d8fe..9c85f41 100644
--- a/public/styles.css
+++ b/public/styles.css
@@ -82,3 +82,13 @@ a{color:inherit}.wrap{width:min(1160px,calc(100% - 32px));margin:auto}.nav{heigh
.mt-meta{color:var(--muted);font-size:12px}
.mt-badge{display:inline-block;width:16px;height:16px;line-height:16px;text-align:center;border-radius:5px;background:#183952;color:var(--teal);font-size:10px;font-weight:900}
.mt-badge.mt-prem{background:rgba(243,190,67,.15);color:var(--gold)}
+
+/* Admin matrix pyramid view */
+.mtp{display:flex;flex-direction:column;gap:14px;overflow-x:auto;padding:6px 2px 10px}
+.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:hover{border-color:var(--teal);transform:translateY(-1px)}
+.mtp-focus{border-color:var(--gold);box-shadow:0 0 0 1px var(--gold),0 8px 22px rgba(243,190,67,.12)}
+.mtp-open{background:transparent;border:1px dashed #27445e;color:#5f7891;font-size:11px;justify-content:center;cursor:default;line-height:1.3}
+.mtp-lvl{font-size:11px;color:var(--teal);text-transform:uppercase;letter-spacing:.5px;font-weight:800}
+.mtp-sub{font-size:11px;color:var(--muted)}