// 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;
// readable pace (Marty, 2026-09-12): about 70 px per second no matter how much text is loaded,
// instead of a fixed 42 s for the whole strip; pause while a finger or pointer rests on it
const wrap = IAP.$('ticker');
const secs = Math.max(30, Math.round((inner.scrollWidth + wrap.clientWidth) / 70));
inner.style.animationDuration = secs + 's';
const pause = on => { inner.style.animationPlayState = on ? 'paused' : 'running'; };
wrap.addEventListener('mouseenter', () => pause(true)); wrap.addEventListener('mouseleave', () => pause(false));
wrap.addEventListener('touchstart', () => pause(true), { passive: true }); wrap.addEventListener('touchend', () => pause(false), { passive: true });
} 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();
}
IAP.adSlot('banner', 'adSlotHome');
loadLadder();
loadStats();
loadTicker();
setInterval(loadStats, 60000);
})();