(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 isSpill(n){return !!(n&&treeUI.parent[n.id]&&n.referrerId&&n.referrerId!==treeUI.parent[n.id])} function card(n,focus){ if(!n)return '
open
slot
'; const badge=n.tier===2?'P':'S'; const qmark=n.directCount>=2?'✓':''; const spill=isSpill(n)?`↧ spillover · ref #${n.referrerId}`:''; 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)`:''}${isSpill(n)?` · ↧ spillover (ref #${n.referrerId})`:''}`; 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'); // cold-visitor context banner: shown unless this looks like the member's own // dashboard (their stored ID matches) or they dismissed it this session try{ const intro=document.getElementById('coldIntro'); const seenBefore=d._priorId===String(d.id); // they've viewed this position before (likely their own) const dismissed=sessionStorage.getItem('ctb.introDismissed')==='1'; if(intro){ if(!seenBefore&&!dismissed){intro.classList.remove('hidden');document.getElementById('coldIntroJoin').href='/join/'+d.id;} else intro.classList.add('hidden'); } }catch(e){} 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(); // 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'}.
    `; note.classList.remove('hidden'); }else{note.classList.add('hidden');note.innerHTML='';} } 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/join/${d.id}`; if(d.directCount>=2){ const nx=d.nextInLine; if(nx){ const nxDash=`https://rmcircle.saasy.top/join/${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 invite page (the full team-build pitch, personalized to them) or send prospects the join button, which carries #${nx.id} as sponsor. (If someone still joins through your own link, that's a bonus — the entry reward is yours and they spill down your leg — but only #${nx.id}'s own directs can qualify them.)

    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, share your personal invite page — the link and QR below. A prospect who opens it gets the full team-build story (video, strategy, live blockchain payouts) with your ID #${d.id} pinned as sponsor and your position's real results as proof. This dashboard stays yours; the invite page is what you share.

    Join under #${d.id} →

    ${esc(dashUrl)}

    When you reach 2/2 this page automatically retires your link from the team's focus and promotes the next position in your leg. Any late signup on your own link is a bonus — it pays you and spills down as depth.

    `; 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{d._priorId=localStorage.getItem('ctb.myId');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; } } const introX=document.getElementById('coldIntroClose'); if(introX)introX.addEventListener('click',()=>{try{sessionStorage.setItem('ctb.introDismissed','1')}catch(e){};document.getElementById('coldIntro').classList.add('hidden')}); document.getElementById('dJoinForm').addEventListener('submit',async e=>{ e.preventDefault(); const form=e.currentTarget,msg=document.getElementById('dJoinMsg'); const newId=form.elements.newId.value.trim(),memberName=form.elements.memberName.value.trim(); if(!/^\d{1,10}$/.test(newId)){msg.style.color='var(--danger)';msg.textContent='Numbers only — the ID shown by the RM Circle dApp.';return} if(!memberName){msg.style.color='var(--danger)';msg.textContent='Add your name or Telegram handle.';return} msg.style.color='var(--muted)';msg.textContent='Verifying on the blockchain…'; try{ const pageId=(location.pathname.match(/^\/my\/(\d+)$/)||[])[1]||'?'; const r=await fetch('/api/public/submit-id',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({newId,memberName,sponsorId:pageId,source:'dashboard-'+pageId,clickid:window.ctbGetClickId?window.ctbGetClickId():''})}); const d=await r.json(); if(!r.ok)throw new Error(d.error||'Submission failed'); form.classList.add('hidden'); msg.style.color='var(--ok)'; const dash=`
    Open your own dashboard →`; if(d.duplicate)msg.innerHTML=`✓ ID ${newId} was already submitted — you're on the list.${dash}`; else if(d.onchain&&d.onchain.registered)msg.innerHTML=`✓ Verified on the blockchain! ID ${newId} is registered under sponsor #${d.onchain.referrerId} (${d.onchain.tier}). The team has been notified — welcome aboard.${dash}`; else if(d.path==='notfound'){msg.style.color='var(--danger)';msg.innerHTML=`⚠ ID ${newId} isn't on the smart contract yet — double-check the number from the RM Circle dApp. Your submission is saved and the team will verify it manually.`;} else msg.innerHTML=`✓ Got it — submitted. The team will verify your placement.${dash}`; }catch(x){msg.style.color='var(--danger)';msg.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); })();