(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'); let openDashTab=false; // which tab opens on load — set by the form/URL; default = pitch // Two-purpose page: a "Team Build" pitch tab (for cold visitors who clicked a // promoted link) and the "Position Dashboard" tab. Persistent — the pitch is // always one tap away, never dismissed. function setTab(name){ document.querySelectorAll('.mp-tab').forEach(t=>t.classList.toggle('on',t.dataset.tab===name)); const tp=document.getElementById('tabPitch'),td=document.getElementById('tabDash'); if(tp)tp.classList.toggle('on',name==='pitch'); if(td)td.classList.toggle('on',name==='dash'); } document.querySelectorAll('.mp-tab').forEach(t=>t.addEventListener('click',()=>{setTab(t.dataset.tab);window.scrollTo({top:0,behavior:'smooth'});})); function renderPitch(d){ const set=(id,v)=>{const el=document.getElementById(id);if(el)el.textContent=v;}; set('pInvId','#'+d.id); set('pProofId','#'+d.id); set('pEarned',Math.round(d.totalEarnedPol||0).toLocaleString()); set('pTeam',(d.subtree&&d.subtree.downCount!=null)?String(d.subtree.downCount):'—'); set('pLevel',d.levelName||'—'); const jb=document.getElementById('pJoinBtn'); if(jb){jb.href='/join-now?ref='+d.id;jb.textContent='Join under #'+d.id+' →';} const hj=document.getElementById('pHeroJoin'); if(hj)hj.href='/join-now?ref='+d.id; const pp=document.getElementById('pPayouts'); if(pp){ const inc=(d.income||[]).slice(0,3); pp.innerHTML=inc.length ? '
Real payouts landing on this team — verifiable on Polygonscan
'+inc.map(p=>`
${esc(p.desc)} — from #${p.fromId}+${fmt(p.pol)} POL
`).join('') : ''; } } 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 renderOrgBar(d){ const el=document.getElementById('dOrgBar'); if(!el)return; const st=d.subtree; if(!st||!st.downCount){el.innerHTML='';return} let gens=0,layer=[st]; while(layer.length&&gens<60){ const next=[]; layer.forEach(n=>{if(n.left)next.push(n.left);if(n.right)next.push(n.right);}); if(!next.length)break;gens++;layer=next; } const q=Object.values(treeUI.byId).filter(n=>n.id!==d.id&&n.directCount>=2).length; el.innerHTML=`
    `+ `
    Your organization${st.downCount.toLocaleString()} member${st.downCount===1?'':'s'}
    `+ `
    Generations deep${gens}
    `+ `
    Qualified below you${q}
    `+ `
    Earned below you${fmt(st.downPol)} POL
    `+ (d.polUsd>0?`
    ≈ US dollars (POL @ $${d.polUsd<0.1?d.polUsd.toFixed(4):d.polUsd.toFixed(2)})$${(st.downPol*d.polUsd).toLocaleString(undefined,{maximumFractionDigits:0})}
    `:'')+ `
    `; } 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'); // default tab: shared /my/ link visits open on the Team Build pitch; a // form lookup, a returning member on bare /my, or a #dash link opens the // dashboard (openDashTab is set by those paths before load()). renderPitch(d); setTab(openDashTab?'dash':'pitch'); document.getElementById('dTitle').textContent='#'+d.id; // hand the member's id to the tools page so every promo asset arrives pre-personalized document.querySelectorAll('a[href="/tools"]').forEach(function(a){a.href='/tools?id='+d.id;}); document.querySelectorAll('a[href="/fast-start"]').forEach(function(a){a.href='/fast-start?id='+d.id;}); document.querySelectorAll('a[href="/weekly-rhythm"]').forEach(function(a){a.href='/weekly-rhythm?id='+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); renderOrgBar(d); 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.
    '; // each panel renders independently — one panel's error must never blank // the ones after it (bit us: share-card QR vanished behind an earlier throw) [renderNextStep,renderUpgradeCTA,renderGens,renderPipeline,renderCoach,renderMessages,renderAlerts,renderShare].forEach(function(f){try{f(d);}catch(e){try{console.error('panel',f.name,e);}catch(_){}}}); 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']; // Team depth — members per generation below this position, with the level // whose upgrade routes each generation's payment to this position (gen D // pays on the upgrade OUT of level D, i.e. buying level D+1). function renderGens(d){ const el=document.getElementById('dGens');if(!el)return; const root=d.subtree; if(!root||(!root.left&&!root.right)){el.innerHTML='';return;} const gens=[];let layer=[root]; while(layer.length&&gens.length<60){ const next=[]; layer.forEach(n=>{if(n.left)next.push(n.left);if(n.right)next.push(n.right);}); if(!next.length)break;gens.push(next.length);layer=next; } const rows=gens.map((c,i)=>{ const cap=Math.pow(2,i+1); const pays=i<7?`pays you at their ${LEVELS[i+1]} upgrade`:'beyond the 8 pay levels'; return `
    Gen ${i+1}
    ${c}${cap<=1024?' of '+cap:''}${pays}
    `; }).join(''); el.innerHTML=`
    Team depth — members per generation
    ${rows}

    Full generations duplicate: each one can hold twice the last. A generation pays this position at exactly one level — stay qualified and at that level to catch it.

    `; } // On-page upgrade: connect the wallet that OWNS this position, read the // exact next-level cost from the contract (BigInt — never float-derived), // send upgrade(). Wrong value would simply revert; ownership is enforced by // matching the connected account to the position's on-chain account. const UPG={CONTRACT:'0x33bdaeefd6d17d80ae53816c916dfb26c4fb2daf',POLYGON:'0x89',SEL_UPGRADE:'0xd55ec697',SEL_GETCOSTS:'0x735f87b9'}; let upEth=null; function renderUpgradeCTA(d){ const el=document.getElementById('dUpgrade');if(!el)return; const ns=d.nextStep||{}; if(ns.kind!=='upgrade'||d.directCount<2){el.innerHTML='';return;} const name=LEVELS[ns.nextLevel-1]||('Level '+ns.nextLevel); el.innerHTML=`

    ⬆️ Upgrade to ${esc(name)} — right from this page

    Exact cost ${fmt(ns.cost)} POL, read live from the contract. Connect the wallet that owns position #${d.id}, confirm once, done. ${ns.funded?'Your wallet already covers it ✓':'Your wallet does not cover it yet — you can time it to your next catches, or top up below.'}

    ${ns.funded?'':``}

    Upgrades are optional and self-paced — never use funds you can't afford to lose. If anything is off, the contract simply rejects it and you keep your POL (minus tiny gas).

    `; const go=document.getElementById('upgGo');if(go)go.addEventListener('click',function(){doUpgrade(d,ns);}); const fu=document.getElementById('upgFund');if(fu)fu.addEventListener('click',async function(){ try{const need=Math.max(62,Math.ceil(ns.cost- (0))+5);const r=await(await fetch('/api/public/moonpay-url?address='+encodeURIComponent(d.account||'')+'&pol='+need)).json();if(r&&r.url)window.open(r.url,'_blank','noopener');}catch(e){} }); } function upgLog(msg,bad){const l=document.getElementById('upgLog');if(l){l.innerHTML=msg;l.style.color=bad?'var(--danger)':'var(--muted)';}} async function doUpgrade(d,ns){ try{ upgLog('Looking for your wallet…'); upEth=await window.RMCWallet.pick(); if(!upEth){ const h=document.getElementById('upgHelp');if(h){h.style.display='block'; const here=location.host+location.pathname+location.search; document.getElementById('upgMM').href='https://metamask.app.link/dapp/'+here; document.getElementById('upgTW').href='https://link.trustwallet.com/open_url?coin_id=966&url='+encodeURIComponent(location.href);} upgLog('No wallet in this browser — use the wallet-app links above, or install MetaMask on desktop.',true);return; } const accs=await upEth.request({method:'eth_requestAccounts'});const account=(accs[0]||'').toLowerCase(); if(!d.account||account!==String(d.account).toLowerCase()){ upgLog('This wallet ('+account.slice(0,6)+'…'+account.slice(-4)+') does not own position #'+d.id+' — connect the wallet that registered it ('+String(d.account||'').slice(0,6)+'…'+String(d.account||'').slice(-4)+').',true);return; } // network const cid=await upEth.request({method:'eth_chainId'}); if(cid!==UPG.POLYGON){ upgLog('Switching to Polygon…'); try{await upEth.request({method:'wallet_switchEthereumChain',params:[{chainId:UPG.POLYGON}]});} catch(e){if(e&&e.code===4902){await upEth.request({method:'wallet_addEthereumChain',params:[{chainId:UPG.POLYGON,chainName:'Polygon Mainnet',nativeCurrency:{name:'POL',symbol:'POL',decimals:18},rpcUrls:['https://polygon-rpc.com'],blockExplorerUrls:['https://polygonscan.com']}]});}else{throw e;}} } // exact cost from the contract (BigInt) upgLog('Reading the exact upgrade cost from the contract…'); const r=await upEth.request({method:'eth_call',params:[{to:UPG.CONTRACT,data:UPG.SEL_GETCOSTS},'latest']}); const words=[];for(let i=2;iview tx'); for(let i=0;i<60;i++){ try{const rc=await upEth.request({method:'eth_getTransactionReceipt',params:[tx]}); if(rc){if(rc.status==='0x0'){upgLog('⚠️ The upgrade reverted — your POL was returned (minus gas). Wait a moment and try again.',true);return;} upgLog('🎉 Upgraded! Refreshing your dashboard…');try{sessionStorage.setItem('rmc.fresh','1');}catch(e){}setTimeout(function(){location.reload();},3000);return;} }catch(e){} await new Promise(function(s){setTimeout(s,3000);}); } upgLog('Still pending — it will land shortly; refresh in a minute.'); }catch(e){upgLog('Upgrade failed / rejected: '+esc(e.message||String(e)),true);} } // Wallet-verified messaging: sign-in = one free personal_sign; identity is // the wallet that owns a position; permissions follow the matrix lines. function renderMessages(d){ const el=document.getElementById('dMsg'),bell=document.getElementById('msgBell'); if(!el)return; fetch('/api/public/msg-unread?id='+d.id).then(r=>r.json()).then(u=>{ if(bell&&u&&u.count>0){bell.textContent='🔔 '+u.count+' new';bell.classList.remove('hidden');} }).catch(()=>{}); loadMsgUI(d); } async function loadMsgUI(d){ const el=document.getElementById('dMsg'); let me=null; try{const r=await fetch('/api/public/msg-me');if(r.ok)me=await r.json();}catch(e){} if(!me){ el.innerHTML='

    Sign in once with the wallet that owns your position — one free signature; it can\'t move funds or approve anything.

    '; const b=document.getElementById('msgAuthBtn');if(b)b.addEventListener('click',function(){msgAuth(d);}); return; } let data; try{data=await(await fetch('/api/public/msg-inbox')).json();}catch(e){el.innerHTML='
    Could not load messages — refresh to retry.
    ';return;} const mine=Number(me.id)===Number(d.id); const banner=mine?'':`
    You're signed in as #${me.id} — this inbox is yours. (You're viewing #${d.id}'s page; the "to" box is pre-filled for them.)
    `; const rows=(data.inbox||[]).map(m=>`
    ${m.org?'📣':'✉️'}
    From #${m.fromId} · ${new Date(m.ts).toLocaleString()}${m.org?' · team broadcast':''}${m.read?'':' · NEW'}
    ${esc(m.body)}
    `).join('')||'
    No messages yet.
    '; const sent=(data.sent||[]).slice(0,3).map(m=>`
    → ${m.org?'whole team':'#'+m.toId} · ${new Date(m.ts).toLocaleString()}: ${esc(m.body.slice(0,90))}${m.body.length>90?'…':''}
    `).join(''); el.innerHTML=banner+rows+ (sent?`
    Recently sent${sent}
    `:'')+ `
    Send a message
    `; const unreadIds=(data.inbox||[]).filter(m=>!m.read).map(m=>m.mid); if(unreadIds.length)fetch('/api/public/msg-read',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({mids:unreadIds})}).catch(()=>{}); const sb=document.getElementById('msgSendBtn'); if(sb)sb.addEventListener('click',async function(){ const st=document.getElementById('msgStatus');st.textContent='Sending…';sb.disabled=true; try{ const payload={org:document.getElementById('msgOrg').checked,toId:(document.getElementById('msgTo').value||'').trim(),body:document.getElementById('msgBody').value}; const r=await(await fetch('/api/public/msg-send',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(payload)})).json(); if(r.error){st.textContent=r.error;sb.disabled=false;return;} st.textContent='Sent ✓';setTimeout(function(){loadMsgUI(d);},700); }catch(e){st.textContent='Send failed — try again.';sb.disabled=false;} }); // Telegram companion link — payout pings + team messages in Telegram try{ const foot=document.createElement('div'); foot.style.cssText='margin-top:12px;padding-top:10px;border-top:1px solid var(--line, #24425d)'; foot.innerHTML=''; el.appendChild(foot); const tb=document.getElementById('tgLinkBtn'); tb.addEventListener('click',async function(){ tb.disabled=true; try{ const r=await(await fetch('/api/public/tg-link',{method:'POST'})).json(); if(r.url){window.open(r.url,'_blank','noopener');document.getElementById('tgLinkNote').textContent='Tap START in Telegram to finish linking.';} else document.getElementById('tgLinkNote').textContent=r.error||'Try again shortly.'; }catch(e){document.getElementById('tgLinkNote').textContent='Network hiccup — try again.';} tb.disabled=false; }); }catch(e){} } async function msgAuth(d){ const err=document.getElementById('msgAuthErr'); try{ const eth=await window.RMCWallet.pick(); if(!eth){err.textContent='No wallet found in this browser. On a phone, open your wallet app (SafePal, Phantom, MetaMask, Trust, Coinbase…), go to its Browser or DApp tab, and type this page\'s address there — your wallet connects automatically.';return;} const accs=await eth.request({method:'eth_requestAccounts'});const account=accs[0]; const ch=await(await fetch('/api/public/msg-challenge',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({address:account})})).json(); if(!ch.message)throw new Error(ch.error||'Could not start sign-in.'); let hex='0x';for(const b of new TextEncoder().encode(ch.message))hex+=b.toString(16).padStart(2,'0'); const sig=await eth.request({method:'personal_sign',params:[hex,account]}); const v=await(await fetch('/api/public/msg-verify',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({address:account,signature:sig})})).json(); if(!v.ok)throw new Error(v.error||'Verification failed.'); loadMsgUI(d); }catch(e){if(err)err.textContent=e.message||String(e);} } // "Coach your team" — the same triage the team admin runs, scoped to THIS // position's leg: who below could use a nudge, and exactly what to tell them. // One-tap nudges: pre-written message per coach recommendation, sent over // the wallet-verified Messages rails (and bridged to Telegram if linked). const nudgeTexts={}; function nudgeBtn(key){return `
    `} function regNudge(id,kind,text){const key=id+':'+kind;nudgeTexts[key]={to:id,text};return nudgeBtn(key)} function renderCoach(d){ const card=document.getElementById('dCoachCard'),el=document.getElementById('dCoach'); if(!card||!el)return; const c=d.coach; if(!c||!c.ready||!c.scanned||((c.rollForward||[]).length+(c.atRisk||[]).length+(c.oneAway||[]).length)===0){card.style.display='none';return;} const link=id=>`#${id}`; const rows=[]; (c.rollForward||[]).forEach(r=>rows.push(`
    💬
    ${link(r.id)} is qualified but still Scintilla with ${fmt(r.earnedPol)} POL of entry rewards — their Ascensus upgrade (${fmt(r.ascensusCost)} POL) is already covered and catches their team's first payments. Tell them — then show them how you spotted it, so they can spot it for their two.
    📚 The Method play: send them Lesson 8 — Timing Upgrades to Catches${regNudge(r.id,'roll',`Great news from my coaching panel: your Ascensus upgrade (~${Math.round(r.ascensusCost)} POL) is already fully covered by the ${Math.round(r.earnedPol)} POL of entry rewards you've caught. Upgrading now means your team's first payments land on YOU instead of passing by. The how and why is Lesson 8: https://rmcircle.team/training#lesson-8 — and after you upgrade, open the "Coach your team" card on YOUR dashboard and run this same check for your two. That's the whole system.`)}
    `)); (c.atRisk||[]).filter(r=>r.qualified&&r.level>1).forEach(r=>rows.push(`
    ⚠️
    ${link(r.id)} (${esc(r.levelName)}) has ${fmt(r.atRiskPol)} POL forming below them but needs ${esc(r.neededLevelName)} to catch it — worth a heads-up before it passes them.
    📚 The Method play: send them Lesson 8 — Timing Upgrades to Catches${regNudge(r.id,'arq',`Friendly heads-up from my coaching panel: about ${Math.round(r.atRiskPol)} POL is forming below you, but it needs ${r.neededLevelName} to catch when it arrives — you have time to get ahead of it. How the timing works is Lesson 8: https://rmcircle.team/training#lesson-8. Your own dashboard (https://rmcircle.team/my/${r.id}) shows the same numbers on the pipeline panel — and show your two how to read theirs!`)}
    `)); (c.atRisk||[]).filter(r=>!r.qualified).forEach(r=>rows.push(`
    ⏰
    ${link(r.id)} has ${fmt(r.atRiskPol)} POL forming below but isn't qualified yet (${r.directCount}/2) — help them find their ${r.directCount===1?'last direct':'2 directs'}.
    📚 The Method play: send them Lesson 2 — Your Warm List${regNudge(r.id,'arn',`Money is already forming below you (about ${Math.round(r.atRiskPol)} POL) — it just can't land until you're qualified (you're at ${r.directCount}/2 directs). You're ${r.directCount===1?'one good conversation':'two good conversations'} away. Lesson 2 makes the warm list painless: https://rmcircle.team/training#lesson-2 — and I'm happy to help you work it. Reply here anytime.`)}
    `)); (c.oneAway||[]).forEach(r=>rows.push(`
    🎯
    ${link(r.id)} is one direct away from qualifying — introduce them to one good person and their whole position activates.
    📚 The Method play: send them Lesson 3 — The Conversation${regNudge(r.id,'one',`You're ONE direct away from qualifying — one person, and your whole position activates. Lesson 3 gives you the exact conversation, word for word: https://rmcircle.team/training#lesson-3. Want to pick who to talk to first together? Reply here — I've got you.`)}
    `)); if(!rows.length){card.style.display='none';return;} el.innerHTML=rows.slice(0,10).join('')+`

    The team rule: don't just tell them — teach them to coach. Everyone on this list has this same panel on their own dashboard. When you reach out, walk them through THEIR "Coach your team" card so they run this exact play for their two. You're not just coaching two people — you're teaching two coaches. That's what builds depth that pays. And remember: every upgrade below you either pays your position directly or builds the depth that will. Not sure how to walk someone through their dashboard? Video 7 in the training is the full tour — send it.

    `; card.style.display=''; if(!el.__nudgeBound){ el.__nudgeBound=true; el.addEventListener('click',async ev=>{ const btn=ev.target.closest&&ev.target.closest('button.coach-nudge'); if(!btn)return; const rec=nudgeTexts[btn.dataset.nkey];if(!rec)return; const note=btn.nextElementSibling; const say=(t,color)=>{if(note){note.style.display='';note.style.color=color||'var(--muted)';note.textContent=t;}}; btn.disabled=true;const old=btn.textContent;btn.textContent='Sending…'; try{ const me=await fetch('/api/public/msg-me'); if(me.status===401){ btn.textContent=old;btn.disabled=false; say('Sign in to Messages first (one free wallet signature) — then tap again.','var(--gold)'); const mc=document.getElementById('dMsg');if(mc&&mc.scrollIntoView)mc.scrollIntoView({behavior:'smooth',block:'center'}); return; } const r=await fetch('/api/public/msg-send',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({toId:rec.to,body:rec.text})}); const dd=await r.json().catch(()=>({})); if(r.ok&&!dd.error){btn.textContent='Sent ✓';say('Delivered to their dashboard — and straight to their Telegram if they’ve linked it.');} else{btn.textContent=old;btn.disabled=false;say(dd.error||'Could not send — try again.','var(--danger)');} }catch(e){btn.textContent=old;btn.disabled=false;say('Network hiccup — try again.','var(--danger)');} }); } } // "My Next Step" — the single clearest action + the level ladder + a funded // badge (server tells us funded true/false; it never sends the raw balance). function renderNextStep(d){ const el=document.getElementById('dNextStep');if(!el)return; const ns=d.nextStep||{},lvl=d.level||1; // Per-level upgrade cost: upgradeCosts[tier][k] = POL to go from level k+1 // to k+2, so the cost to REACH level L is index L-2. Scintilla (L1) is the // entry level, not an upgrade. const upc=(d.upgradeCosts&&d.upgradeCosts[d.tier===2?2:1])||[]; const ladder=LEVELS.map((nm,i)=>{ const L=i+1;let cls='nlv'; if(LYOU':(ns.kind==='upgrade'&&L===ns.nextLevel?'NEXT':''); const cost=L===1?null:upc[L-2]; const costTxt=L===1?'entry level':(cost?fmt(cost)+' POL':''); const usd=(cost&&d.polUsd>0)?' title="≈ $'+Math.round(cost*d.polUsd).toLocaleString()+' to reach '+nm+'"':''; const costLine=costTxt?`${costTxt}`:''; return `
    ${tag}${nm}${costLine}
    `; }).join(''); let head='',body='',badge=''; if(ns.kind==='qualify'){ head='Your next step — get qualified'; body=`You need ${ns.need} more direct${ns.need===1?'':'s'} (people who join on your link) to unlock upgrade payments. Grab your link from Share this position below and go get them.`; }else if(ns.kind==='upgrade'){ const nm=LEVELS[ns.nextLevel-1]||('Level '+ns.nextLevel); head=`Your next step — upgrade to ${esc(nm)}`; body=`Stay one level ahead of your deepest active team so the pass-ups from below land on you instead of skipping past. Upgrade with your earned POL when you can.`; if(ns.funded===true)badge='✓ Funded — you can upgrade now'; else if(ns.funded===false)badge='Keep building — not funded yet'; }else if(ns.kind==='max'){ head="You're at the top level 🏆"; body='Keep helping your team duplicate — every level they climb still pays up to you.'; }else{el.innerHTML='';return;} el.innerHTML=`
    Your plan
    ${badge}
    ${head}

    ${body}

    ${ladder}
    `; } 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+1}); }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.

    💡 Someone below you not upgrading? That's fine — upgrades are optional, and a paused member never blocks money: payments they can't catch pass straight UP to the next ready position (often yours), and everyone below them keeps flowing normally. Their pause only delays the single payment they themselves would send. Coach the movers; the pausers usually return once their own entry rewards stack up and the upgrade funds itself. Lesson 7 covers this in three minutes.

    `; 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]||'the top'; const have=d.directCount>=2&&myLevel>=g+1; 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+1)){ 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]||'the top'} yourself (next step: ${nextName}${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}`; const q2=d.directCount>=2, nx=d.nextInLine; // The personal QR is ALWAYS shown: /join/ self-rotates via the moving // link, so a scan is always placed correctly — even after 2/2. This is the // person-to-person flow: open your page, tap the QR, they scan, they see // your pitch page with the video and the join button. const guidance=q2 ?(nx?`★ Qualified Your page now routes new joins to #${nx.id} (${nx.directCount||0}/2) — the team play moving down your leg automatically. Anyone who scans still lands on YOUR pitch page; placement is handled for you. A signup on your own link is always a bonus: the entry reward is yours.` :`★ Qualified Your whole leg is qualified — your page routes new joins to the team rotation automatically.`) :`Share this with your 2 — anyone who scans or clicks lands on your personal pitch page (video, live proof, join button) and joins under you.`; el.innerHTML=`

    ${guidance}

    📲 Show this QR to anyone, anywhere.

    Tap the code to blow it up full-screen — they scan it with their phone camera and land on your invite page.

    Preview my page →

    ${esc(dashUrl)}

    `; const qr=document.getElementById('dQr');if(qr)qr.innerHTML=qrSvg(dashUrl); const qb=document.getElementById('dQrBtn'); if(qb)qb.addEventListener('click',function(){ const ov=document.createElement('div'); ov.style.cssText='position:fixed;inset:0;background:#fff;z-index:99999;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:14px;padding:24px;cursor:pointer'; const big=document.createElement('div'); big.style.cssText='width:min(78vw,78vh);height:min(78vw,78vh)'; big.innerHTML=qrSvg(dashUrl); const cap=document.createElement('div'); cap.style.cssText='color:#0a1620;font-weight:800;font-size:18px;text-align:center'; cap.textContent='Scan to see the RM Circle team build — join page for #'+d.id; const hint=document.createElement('div'); hint.style.cssText='color:#667;font-size:13px'; hint.textContent='Tap anywhere to close'; ov.appendChild(big);ov.appendChild(cap);ov.appendChild(hint); ov.addEventListener('click',function(){try{document.body.removeChild(ov);}catch(e){}}); document.body.appendChild(ov); }); 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{ // after our own join/upgrade tx, force one cache-bypassing read so the // dashboard reflects the transaction immediately instead of the cache let fresh='';try{if(sessionStorage.getItem('rmc.fresh')){fresh='&fresh=1&_t='+Date.now();sessionStorage.removeItem('rmc.fresh');}}catch(e){} const r=await fetch('/api/public/member?id='+id+fresh); 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; } } 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} openDashTab=true; // they actively looked up an ID — open the dashboard 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=''; }); try{ if(location.hash==='#dash'||new URLSearchParams(location.search).get('tab')==='dash') openDashTab=true; }catch(e){} const pathHasId=/^\/my\/\d+$/.test(location.pathname); const id=pathId(); if(id&&!pathHasId)openDashTab=true; // bare /my resolved from storage = returning to your own page if(id)load(id); })();