// Landing page: live ladder, buy buttons, sponsor attribution line. (async function () { await IAP.renderNav('home'); const c = await IAP.getConfig(); IAP.$('contractLink').href = c.explorer + '/address/' + c.contract; const sp = await (await fetch('/api/sponsor')).json(); if (sp.invited) { const el = IAP.$('sponsorLine'); el.hidden = false; el.textContent = (sp.sponsorId ? 'You were invited by member #' + sp.sponsorId + '.' : 'You arrived through a member’s invite.') + ' Your purchases pay their team, and your own link will do the same for you.'; } async function loadLadder() { const { products } = await (await fetch('/api/catalog')).json(); const wrap = document.getElementById('tiles'); wrap.innerHTML = ''; const NAMES = { 1: 'Micro', 2: 'Activation', 3: 'Builder', 4: 'Growth', 5: 'Leader' }; for (const p of products) { const bonus = p.creditAmount - p.priceCents; // credits above 1cr/cent = bulk bonus const div = document.createElement('div'); div.className = 'tile' + (p.priceCents === 5000 ? ' hot' : ''); div.innerHTML = '
' + (NAMES[p.id] || 'Package ' + p.id) + '
' + '
$' + Math.round(p.priceCents / 100) + '
' + '
' + p.creditAmount.toLocaleString() + ' credits
' + '
' + (bonus > 0 ? '+' + bonus.toLocaleString() + ' bonus credits' : ' ') + '
' + '
' + (p.costWei ? IAP.fmtPol(p.costWei) + ' POL right now' : 'paused') + '
' + ''; wrap.appendChild(div); } wrap.querySelectorAll('button[data-id]').forEach(b => b.addEventListener('click', () => buyPack(b))); } async function buyPack(btn) { try { btn.disabled = true; // email members get their wallet linked to the account at buy time const me = await (await fetch('/api/me')).json(); if (me.signedIn && me.email && !me.address) { IAP.status('First, a free signature links your wallet to your account…'); await IAPWallet.signIn(); } IAP.status('Confirm the purchase in your wallet…'); // resolve the sponsor at buy time: a code referrer who activated since // page load still gets locked in const spNow = await (await fetch('/api/sponsor')).json(); const r = await IAPWallet.buy(Number(btn.dataset.id), spNow.sponsorId || 0, btn.dataset.cost); if (r.receipt.status !== '0x1') throw new Error('Transaction reverted. Check the explorer.'); IAP.status('Purchase settled on-chain. Credits are yours, payouts delivered. Watch it on the ledger.', 'ok'); IAP.refreshNavWallet(); } catch (e) { IAP.status('Purchase failed: ' + (e.message || e), 'bad'); } finally { btn.disabled = false; } } async function loadStats() { try { const s = await (await fetch('/api/stats')).json(); IAP.$('stMembers').textContent = (s.onchainMembers || 0).toLocaleString(); IAP.$('stPurchases').textContent = (s.purchases || 0).toLocaleString(); IAP.$('stPaid').textContent = IAP.fmtPol(s.paidInWei || '0'); IAP.$('stPayouts').textContent = (s.payouts || 0).toLocaleString(); } catch (e) {} } async function loadTicker() { try { const { events } = await (await fetch('/api/feed?n=30')).json(); if (!events.length) return; const inner = IAP.$('tickerInner'); inner.innerHTML = events.map(ev => '' + IAP.describeEvent(ev, c) + '').join(''); IAP.$('ticker').hidden = false; } catch (e) {} } // level cycler: chips + generation highlighting + auto-advance const LVL = { 1: { pct: '50%', desc: 'Level 1 is open to every member: your direct referrals each pay you 50 percent of every package they ever buy, straight to your wallet.' }, 2: { pct: '20%', desc: 'Bring 2 buyers of $20 or more and level 2 unlocks: 20 percent of every package your referrals’ referrals buy, on every purchase, forever.' }, 3: { pct: '10%', desc: 'At 5 qualifying buyers, level 3 opens the third generation: 10 percent of everything they buy. Eight positions deep in this picture, and it keeps growing.' } }; const viz = document.getElementById('genViz'); if (viz) { const chips = [...document.querySelectorAll('.chips [data-lvl]')]; const setLvl = n => { viz.dataset.lvl = n; document.getElementById('vizPct').textContent = LVL[n].pct; document.getElementById('lvlDesc').textContent = LVL[n].desc; chips.forEach(ch => ch.classList.toggle('on', ch.dataset.lvl === String(n))); }; let cur = 1; let auto = null; if (!matchMedia('(prefers-reduced-motion: reduce)').matches) { auto = setInterval(() => { cur = cur % 3 + 1; setLvl(cur); }, 4200); } chips.forEach(ch => ch.addEventListener('click', () => { if (auto) { clearInterval(auto); auto = null; } // a click takes the wheel cur = Number(ch.dataset.lvl); setLvl(cur); })); } // what-if calculator: pure arithmetic on the locked constants const dc = document.getElementById('dcDirects'); if (dc) { const $id = x => document.getElementById(x); const usd = n => '$' + n.toLocaleString(undefined, { maximumFractionDigits: 2 }); const recalc = () => { const d = Number($id('dcDirects').value); const p = Number($id('dcPkg').value); const r = Number($id('dcSpread').value); $id('dcDirectsV').textContent = d; $id('dcSpreadV').textContent = r; const qualifies = p >= 20; // sub-$20 packages never count toward qualification const l2open = qualifies && d >= 2; const l3open = qualifies && d >= 5; const g2 = d * r, g3 = g2 * r; const e1 = d * p * 0.5; const e2 = l2open ? g2 * p * 0.2 : 0; const e3 = l3open ? g3 * p * 0.1 : 0; $id('dcN1').textContent = d; $id('dcN2').textContent = g2; $id('dcN3').textContent = g3; $id('dcE1').textContent = usd(e1); $id('dcE2').textContent = l2open ? usd(e2) : 'passes up'; $id('dcE3').textContent = l3open ? usd(e3) : 'passes up'; $id('dcTotal').textContent = usd(e1 + e2 + e3); const setB = (el, open, need) => { el.textContent = open ? 'open' : 'locked: ' + need; el.className = 'badge' + (open ? '' : ' amber'); }; setB($id('dcB1'), true, ''); setB($id('dcB2'), l2open, qualifies ? (2 - d) + ' more buyer(s)' : 'needs $20+ buyers'); setB($id('dcB3'), l3open, qualifies ? (5 - d) + ' more buyer(s)' : 'needs $20+ buyers'); $id('dcQualNote').textContent = qualifies ? 'Buyers of $20 or more count toward your qualification. 2 unlock level 2, 5 unlock level 3.' : 'Heads up: $5 packages pay your level 1 but do not qualify buyers, so levels 2 and 3 stay locked in this scenario.'; }; ['dcDirects', 'dcPkg', 'dcSpread'].forEach(x => $id(x).addEventListener('input', recalc)); recalc(); } loadLadder(); loadStats(); loadTicker(); setInterval(loadStats, 60000); })();