(function(){ const esc=s=>String(s??'').replace(/[&<>'"]/g,c=>({'&':'&','<':'<','>':'>',"'":''','"':'"'}[c])); const fmt=n=>Number(n||0).toLocaleString(undefined,{maximumFractionDigits:2}); const date=ts=>ts?new Date(ts*1000).toLocaleDateString(undefined,{year:'numeric',month:'short',day:'numeric'}):'—'; const prompt=document.getElementById('idPrompt'),dash=document.getElementById('dash'); 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'}; 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 card(n,focus){ if(!n)return '
open
slot
'; const badge=n.tier===2?'P':'S'; const qmark=n.directCount>=2?'✓':''; const down=n.downCount?`⬇ ${n.downCount} below · ${fmt(n.downPol)} POL`:'no downline yet'; return ``; } function renderPyramid(){ const out=document.getElementById('dTree'),nav=document.getElementById('dTreeNav'); const root=treeUI.byId[treeUI.rootId]; if(!root){out.innerHTML='
Team view is still indexing — check back in a few minutes.
';nav.innerHTML='';return} const rows=[[root]]; for(let g=1;g<4;g++)rows.push(rows[g-1].flatMap(n=>n?[n.left||null,n.right||null]:[null,null])); if(!rows[3].some(Boolean))rows.pop(); out.innerHTML=`
${rows.map((r,i)=>`
${r.map(n=>card(n,i===0)).join('')}
`).join('')}
`; const path=[];let cur=treeUI.rootId; while(cur){path.unshift(cur);if(cur===treeUI.homeId)break;cur=treeUI.parent[cur]} nav.innerHTML=path.length>1?`Viewing: `+path.map((id,i)=>i===path.length-1?`#${id}`:``).join(' › ')+` `:''; 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); } function renderList(){ const out=document.getElementById('dTree'); document.getElementById('dTreeNav').innerHTML=''; const home=treeUI.byId[treeUI.homeId]; if(!home){out.innerHTML='
Team view is still indexing — check back in a few minutes.
';return} const node=(n,depth)=>{ if(!n)return ''; const ch=[n.left,n.right].filter(Boolean); const qmark=n.directCount>=2?'✓ ':''; const badge=n.tier===2?'P':'S'; const label=`${qmark}${badge} #${n.id} ${esc(n.levelName)} · ${n.directCount}/2 directs · ${fmt(n.earnedPol)} POL${n.downCount?` · ⬇ ${n.downCount} below (${fmt(n.downPol)} POL)`:''}`; const kids=ch.length?``:''; return ch.length?`
  • ${label}${kids}
  • `:`
  • ${label}
  • `; }; out.innerHTML=``; } function renderTree(){ const tb=document.getElementById('dTreeToggle'); if(tb)tb.textContent=treeUI.mode==='pyramid'?'List view':'Pyramid view'; if(treeUI.mode==='pyramid')renderPyramid();else renderList(); } document.getElementById('dTreeToggle').addEventListener('click',()=>{ treeUI.mode=treeUI.mode==='pyramid'?'list':'pyramid'; renderTree(); }); function render(d){ prompt.classList.add('hidden');dash.classList.remove('hidden'); document.getElementById('dTitle').textContent='#'+d.id; const qb=document.getElementById('dQualified'); if(qb)qb.outerHTML=d.directCount>=2 ?'★ Qualified' :`${2-d.directCount} more direct${2-d.directCount===1?'':'s'} to qualify`; document.getElementById('dFacts').innerHTML= `
    Tier${esc(d.tierName)}
    `+ `
    Level${esc(d.levelName)}
    `+ `
    Directs${d.directCount}/2${d.directCount>=2?' ✓ qualified':''}
    `+ `
    Total received${fmt(d.totalEarnedPol)} POL
    `+ `
    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(); const inc=d.income||[]; document.getElementById('dIncome').innerHTML=inc.length ?`
    ${inc.map(p=>``).join('')}
    WhenFrom memberForAmount
    ${date(p.ts)}#${p.fromId}${esc(p.desc)}${fmt(p.pol)} POL
    ` :'
    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 Your link is retired — now the effort 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.

    Join under #${nx.id} →

    ${esc(nxDash)}

    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.

    `; const qr=document.getElementById('dQr');if(qr)qr.innerHTML=qrSvg(nxDash); const cp=document.getElementById('copyShare'); if(cp)cp.addEventListener('click',async()=>{try{await navigator.clipboard.writeText(nxDash);cp.textContent='Copied ✓';setTimeout(()=>cp.textContent=`Copy #${nx.id}'s page link`,1400)}catch(e){}}); return; } el.innerHTML=`

    ★ 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.

    Team QR code
    Open the Team Sponsor Page →

    This QR sends prospects to the team rotation — show it or print it.

    `; return; } const need=2-d.directCount; el.innerHTML=`

    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.

    Join under #${d.id} →

    ${esc(dashUrl)}

    When you reach 2/2 this page automatically retires your link and points new members to the team rotation.

    `; 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); })();