8c8935fc15
Mint-monochrome discipline on near-black, neon light-trail hero with falling streaks, glowing icon plates (custom SVG, no emoji), ledger mockup flanked by feature items, isometric network illustration, capsule nav, pricing tiles, ghost wordmark footer. Topo-contour ground texture. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
75 lines
3.5 KiB
JavaScript
75 lines
3.5 KiB
JavaScript
// 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.sponsorId) {
|
|
const el = IAP.$('sponsorLine');
|
|
el.hidden = false;
|
|
el.textContent = 'You were invited by member #' + sp.sponsorId + '. 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 = '<div class="name">' + (NAMES[p.id] || 'Package ' + p.id) + '</div>'
|
|
+ '<div class="price">$' + Math.round(p.priceCents / 100) + '</div>'
|
|
+ '<div class="cr">' + p.creditAmount.toLocaleString() + ' credits</div>'
|
|
+ '<div class="bonus">' + (bonus > 0 ? '+' + bonus.toLocaleString() + ' bonus credits' : ' ') + '</div>'
|
|
+ '<div class="pol">' + (p.costWei ? IAP.fmtPol(p.costWei) + ' POL right now' : 'paused') + '</div>'
|
|
+ '<button class="btn small" data-id="' + p.id + '" data-cost="' + (p.costWei || '') + '"'
|
|
+ (p.costWei ? '' : ' disabled') + '>Buy</button>';
|
|
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…');
|
|
const r = await IAPWallet.buy(Number(btn.dataset.id), sp.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 => '<span>' + IAP.describeEvent(ev, c) + '</span>').join('');
|
|
IAP.$('ticker').hidden = false;
|
|
} catch (e) {}
|
|
}
|
|
loadLadder();
|
|
loadStats();
|
|
loadTicker();
|
|
setInterval(loadStats, 60000);
|
|
})();
|