(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.
    '; renderPipeline(d); renderAlerts(d); 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 ''} } const LEVELS=['Scintilla','Ascensus','Fabrica','Culmen','Apex','Fastigium','Vertex','Corona']; function renderPipeline(d){ const el=document.getElementById('dPipeline'); if(!el)return; if(!d.subtree||(!d.subtree.left&&!d.subtree.right)){el.innerHTML='
    Your pipeline starts when your first team members are placed below you.
    ';return} if(!d.upgradeCosts){el.innerHTML='
    Pipeline data is loading — check back in a minute.
    ';return} const up=d.upgradeCosts; // walk the subtree with depth: a member at depth D pays THIS position when // they buy the upgrade OUT of level D (cost index D-1) — first in line const ready=[],building=[],passedCount={n:0}; (function walk(n,depth){ if(!n||depth>16)return; const lvl=n.level||1,tier=n.tier===2?2:1; if(depth>=1){ if(lvl===depth){ const amt=(up[tier]||[])[depth-1]; if(amt)ready.push({id:n.id,depth,tier,amt,toLevel:LEVELS[depth]||('Level '+(depth+1)),needLevel:depth}); }else if(lvla.depth-b.depth||a.id-b.id); const eligible=r=>d.directCount>=2&&(d.level||1)>=r.needLevel; const readyTotal=ready.filter(eligible).reduce((s,r)=>s+r.amt,0); let html=''; if(ready.length){ html+=`

    Ready now — one upgrade from your wallet:

    `; html+=ready.slice(0,10).map(r=>{ const warn=!eligible(r)?(d.directCount<2?' ⚠ needs you qualified (2 directs)':` ⚠ needs you at ${esc(LEVELS[r.needLevel-1])}+`):''; return `
    ⏳
    #${r.id} · gen ${r.depth} · their ${esc(r.toLevel)} upgrade pays you ${fmt(r.amt)} POL${warn}
    `; }).join(''); if(ready.length>10)html+=`

    …and ${ready.length-10} more.

    `; if(readyTotal>0){ const myNext=(up[d.tier===2?2:1]||[])[(d.level||1)-1]; const fundNote=myNext?` Your next upgrade (${esc(LEVELS[d.level]||'max')}) costs ${fmt(myNext)} — ${readyTotal>=myNext?'the pipeline above covers it':'these payments put you '+fmt(readyTotal)+' toward it'}.`:''; html+=`

    ${fmt(readyTotal)} POL is one upgrade away from your wallet.${fundNote}

    `; } }else html+='

    Nobody is at their pay-you milestone yet — the members below are still climbing toward it.

    '; if(building.length)html+=`

    ${building.length} more member${building.length===1?' is':'s are'} building toward their pay-you level${passedCount.n?`; ${passedCount.n} already passed theirs`:''}. Helping your leg upgrade IS your income.

    `; else if(passedCount.n)html+=`

    ${passedCount.n} member${passedCount.n===1?' has':'s have'} already passed their pay-you milestone.

    `; html+=`
    `; el.innerHTML=html; const cs=document.getElementById('dCopySummary'); if(cs)cs.addEventListener('click',async()=>{ try{await navigator.clipboard.writeText(pipelineSummaryText(d));document.getElementById('dCopyMsg').textContent='Copied — paste it into your chat.';setTimeout(()=>{document.getElementById('dCopyMsg').textContent=''},4000);}catch(e){document.getElementById('dCopyMsg').textContent='Copy failed — long-press to select instead.'} }); } // Plain-English, chat-ready explainer of a member's upgrade pipeline — the // "will I miss upgrades?" question answered from their real position. function pipelineSummaryText(d){ const LV=LEVELS, up=(d.upgradeCosts&&d.upgradeCosts[d.tier===2?2:1])||[]; const money=n=>Math.round(n).toLocaleString(); const gens={}; (function walk(n,depth){if(!n||depth>8)return;if(depth>=1)(gens[depth]=gens[depth]||[]).push(n);walk(n.left,depth+1);walk(n.right,depth+1);})(d.subtree,0); const myLevel=d.level||1, team=(d.subtree&&d.subtree.downCount)||0; const L=[]; L.push(`RM Circle — your position #${d.id}, in plain English 👇`); L.push(''); L.push(d.directCount>=2 ? `✅ YOUR SPOT: ${d.tierName}, ${d.levelName} level, qualified (2/2 directs). ${team} ${team===1?'person':'people'} in your team so far.` : `⚠️ YOUR SPOT: ${d.tierName}, ${d.levelName} level — NOT qualified yet (${d.directCount}/2 directs). Get ${2-d.directCount} more direct${2-d.directCount===1?'':'s'} of your own first — that's the ONE thing that unlocks everything below.`); L.push(''); L.push(`HOW UPGRADES PAY YOU (the one rule that clears up all the confusion):`); L.push(`Every layer of your team pays you ONCE, at ONE level. The people directly under you pay you when they upgrade to Ascensus. The next layer down pays you at Fabrica. The layer below that at Culmen. Each layer deeper pays you at the next level up the ladder.`); L.push(''); L.push(`To CATCH each layer's payment, you only need to be that level yourself (and stay qualified). Your ladder right now:`); const maxG=Math.min(6,Math.max(2,...Object.keys(gens).map(Number).concat([2]))); for(let g=1;g<=maxG;g++){ const amt=up[g-1]; if(!amt)continue; const buys=LV[g]||'the top'; const need=LV[g-1]; const have=d.directCount>=2&&myLevel>=g; const cnt=(gens[g]||[]).length; L.push(`• Layer ${g}${cnt?` (${cnt} ${cnt===1?'person':'people'})`:''}: their ${buys} upgrade pays you ${money(amt)} POL each — needs you at ${need}${have?' ✅ you already are':' ← get here to catch it'}`); } L.push(''); L.push(`WILL YOU MISS THEM? Only if your team climbs past a level before you do. But nobody skips levels — they go up one at a time — so you always get warning. Stay one step ahead of your team's growth and you catch every single one. Fall behind and that one payment passes you to the next person up (it doesn't come back), so the whole game is simply: keep your level ahead of your deepest active layer.`); L.push(''); // next concrete action const myNext=up[myLevel-1]; const nextName=LV[myLevel]||'max level'; // shallowest layer you can't yet catch let gap=0; for(let g=1;g<=8;g++){ if((gens[g]||[]).length && !(d.directCount>=2&&myLevel>=g)){ gap=g; break; } } // is anyone in the gap layer already close (at/near their pay-you level)? const gapClose=gap&&(gens[gap]||[]).some(n=>(n.level||1)>=gap-1); if(d.directCount<2) L.push(`YOUR NEXT MOVE: get your 2 directs. Until then none of this pipeline can pay you — spillover fills your team but only your own 2 qualify you.`); else if(gap) L.push(`YOUR NEXT MOVE: your next uncovered layer is layer ${gap} — to catch their ${LV[gap]||'top'} upgrades you'll need to be at ${LV[gap-1]} (${myNext?money(myNext)+' POL':''}). They're still climbing toward it${gapClose?', and getting close — worth upgrading soon':", so no rush — just get there before they do"}. You're already eligible for everything shallower.`); else if(myNext) L.push(`YOUR NEXT MOVE: you're currently eligible for every active layer. As your team goes deeper, upgrade to ${nextName} (${money(myNext)} POL) before that new layer climbs to its pay-you level.`); L.push(''); L.push(`See it live and get a red alert the moment you'd miss one: rmcircle.team/my/${d.id}`); return L.join('\n'); } async function renderAlerts(d){ const el=document.getElementById('dAlerts'); if(!el)return; let st={subscribed:false}; try{st=await (await fetch('/api/public/alert-status?id='+d.id)).json();}catch(e){} function subscribedView(email){ el.innerHTML=`

    Alerts ON for ${esc(email||'your email')}

    `; document.getElementById('alertOff').addEventListener('click',async()=>{ try{await fetch('/api/public/alert-signup',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({id:d.id,email:''})});renderAlerts(d);}catch(e){document.getElementById('alertMsg').textContent='Try again.';} }); } function offView(){ el.innerHTML=`
    `; document.getElementById('alertForm').addEventListener('submit',async e=>{ e.preventDefault(); const email=e.currentTarget.elements.email.value.trim(),msg=document.getElementById('alertMsg'); msg.style.color='var(--muted)';msg.textContent='Turning on…'; try{ const r=await fetch('/api/public/alert-signup',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({id:d.id,email})}); const j=await r.json(); if(!r.ok)throw new Error(j.error||'Failed'); msg.style.color='var(--ok)';msg.textContent='Done — check your inbox for a confirmation.'; setTimeout(()=>renderAlerts(d),1200); }catch(x){msg.style.color='var(--danger)';msg.textContent=x.message;} }); } if(st.subscribed)subscribedView(st.email);else offView(); } function renderShare(d){ const el=document.getElementById('dShare'); if(!el)return; const dashUrl=`https://rmcircle.team/join/${d.id}`; if(d.directCount>=2){ const nx=d.nextInLine; if(nx){ const nxDash=`https://rmcircle.team/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); })();