Team below you${d.subtree?`${d.subtree.downCount} member${d.subtree.downCount===1?'':'s'}`:'—'}
`+
`
Member since${date(d.joinedAt)}
`;
treeUI.byId={};treeUI.parent={};treeUI.homeId=d.id;treeUI.rootId=d.id;
indexTree(d.subtree,null);
renderTree();
// spillover explainer — shown exactly when the confusing state exists:
// positions sitting under you while you're not yet qualified
const note=document.getElementById('dSpillNote');
if(note){
const kids=d.subtree?[d.subtree.left,d.subtree.right].filter(Boolean):[];
const spills=kids.filter(k=>k.referrerId&&k.referrerId!==d.id).length;
if(d.directCount<2&&kids.length&&spills){
note.innerHTML=`
Why am I not qualified when people sit under me? ${spills===kids.length?'The positions':'Some positions'} under you arrived by spillover — when your upline recruits, the contract fills the next open slot downward, which can land members in your matrix. Spillover grows your team and your future level payments, but qualification only counts your directs — people who join using your ID. You have ${d.directCount}/2 directs; share your link above to get ${2-d.directCount===1?'your last one':'your 2'}.
No payments yet — they appear here the moment they land on-chain.
';
renderShare(d);
document.getElementById('dLineage').innerHTML=d.uplineChain&&d.uplineChain.length
?`#${d.id} → ${d.uplineChain.map(i=>'#'+i).join(' → ')} (root)`
:'You are at the top of your line.';
}
function qrSvg(url){
try{
const q=qrcode(0,'M');q.addData(url);q.make();
return q.createSvgTag({cellSize:4,margin:3,scalable:true});
}catch(e){return ''}
}
function renderShare(d){
const el=document.getElementById('dShare');
if(!el)return;
const dashUrl=`https://rmcircle.saasy.top/my/${d.id}`;
if(d.directCount>=2){
const nx=d.nextInLine;
if(nx){
const nxDash=`https://rmcircle.saasy.top/my/${nx.id}`;
el.innerHTML=`
★ Qualified The team play now moves down. Next in line: #${nx.id} (${nx.directCount}/2 directs). Help them get their 2 — share their page or send prospects the join button, which carries #${nx.id} as sponsor. (Your own link still works too — extra signups pay you the entry reward and spill down your leg as depth — but only #${nx.id}'s own directs can qualify them.)
When #${nx.id} reaches 2/2 this rotates to the next position in your leg — the qualification wave keeps moving down. General traffic can still go through the team rotation.
★ Qualified This position has its 2 directs and everyone in your leg is qualified too — outstanding. Send new members through the team rotation — the current sponsor is always live on the start page.
You need ${need} more direct${need===1?'':'s'} to qualify. Until then this is your personal recruiting page — share the link or let a prospect scan the code. They'll see this position's live results, and the join button below carries your ID #${d.id} as sponsor.
When you reach 2/2 this page automatically shifts to promoting the next position in your leg — your own link keeps working (extra signups pay you and spill down as depth), it just stops being the team's focus.
`;
const qr=document.getElementById('dQr');if(qr)qr.innerHTML=qrSvg(dashUrl);
const cp=document.getElementById('copyShare');
if(cp)cp.addEventListener('click',async()=>{try{await navigator.clipboard.writeText(dashUrl);cp.textContent='Copied ✓';setTimeout(()=>cp.textContent='Copy my page link',1400)}catch(e){}});
}
async function load(id){
try{
const r=await fetch('/api/public/member?id='+id);
const d=await r.json();
if(!r.ok)throw new Error(d.error||'Lookup failed');
if(!d.registered)throw new Error(`ID ${id} isn't registered on the smart contract — double-check the number.`);
try{localStorage.setItem('ctb.myId',String(id))}catch(e){}
if(!/^\/my\/\d+$/.test(location.pathname))history.replaceState(null,'','/my/'+id);
render(d);
}catch(x){
prompt.classList.remove('hidden');dash.classList.add('hidden');
document.getElementById('idError').textContent=x.message;
}
}
document.getElementById('idForm').addEventListener('submit',e=>{
e.preventDefault();
const v=document.getElementById('memberId').value.trim();
if(!/^\d{1,15}$/.test(v)){document.getElementById('idError').textContent='Numbers only — the ID from the RM Circle dApp.';return}
load(v);
});
document.getElementById('switchId').addEventListener('click',()=>{
try{localStorage.removeItem('ctb.myId')}catch(e){}
history.replaceState(null,'','/my');
dash.classList.add('hidden');prompt.classList.remove('hidden');
document.getElementById('memberId').value='';
});
const id=pathId();
if(id)load(id);
})();