Files
instantadpay/public/assets/common.js
T
martbost 8c8935fc15 Landing rebuilt to Marty's picked reference (Behance 243562211)
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>
2026-09-04 13:26:28 -05:00

106 lines
4.9 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Shared page runtime: site config, nav, formatting. Zero dependencies.
window.IAP = (function () {
let config = null;
const $ = id => document.getElementById(id);
async function getConfig() {
if (!config) config = await (await fetch('/api/config')).json();
return config;
}
function fmtPol(wei) {
const s = BigInt(wei).toString().padStart(19, '0');
const whole = s.slice(0, -18) || '0';
const frac = s.slice(-18, -12).replace(/0+$/, '');
return whole + (frac ? '.' + frac : '');
}
const fmtUsd = cents => '$' + (cents / 100).toFixed(2);
function status(msg, cls) {
let el = $('status');
if (!el) { el = document.createElement('div'); el.id = 'status'; document.body.appendChild(el); }
el.textContent = msg; el.className = cls || ''; el.hidden = false;
clearTimeout(status._t);
if (cls === 'ok') status._t = setTimeout(() => { el.hidden = true; }, 6000);
}
async function renderNav(active) {
const c = await getConfig();
const nav = document.createElement('nav');
nav.innerHTML = '<div class="wrap">'
+ '<a class="logo" href="/">Instant<b>AdPay</b></a>'
+ '<span class="links">'
+ '<a href="/" data-p="home">How it works</a>'
+ '<a href="/#ladder" data-p="pricing">Ad packages</a>'
+ '<a href="/ledger" data-p="ledger">Live ledger</a>'
+ '<a href="/my" data-p="my">Members</a>'
+ '</span><span id="navWallet" class="muted">…</span></div>';
document.body.prepend(nav);
if (c.rehearsal) {
const b = document.createElement('div');
b.className = 'rehearsal';
b.innerHTML = '<b>Testnet rehearsal</b>: running on ' + c.chainName + '. Purchases use valueless test POL while we prove every payout in public.';
document.body.prepend(b);
}
const a = nav.querySelector('[data-p="' + active + '"]');
if (a) a.className = 'active';
refreshNavWallet();
}
async function refreshNavWallet() {
try {
const me = await (await fetch('/api/me')).json();
const el = $('navWallet');
if (!el) return;
if (me.signedIn) {
el.innerHTML = (me.memberId ? '<span class="badge">member #' + me.memberId + '</span> ' : '')
+ '<span class="mono">' + me.address.slice(0, 6) + '…' + me.address.slice(-4) + '</span>';
} else {
el.innerHTML = '<a href="/my">Sign in</a>';
}
return me;
} catch (e) { return null; }
}
function describeEvent(ev, c) {
const pol = w => fmtPol(w) + ' POL';
switch (ev.type) {
case 'Purchase': return '🧾 member #' + ev.buyerId + ' bought package #' + ev.productId
+ ' (' + fmtUsd(ev.priceCents) + ') for ' + pol(ev.paidWei) + ' → +' + ev.creditAmount.toLocaleString() + ' credits';
case 'TierPaid': return '💸 level ' + ev.tier + ' payout → member #' + ev.recipientId + ': ' + pol(ev.amountWei)
+ (ev.hops ? ' (passed up ' + ev.hops + ')' : '');
case 'PassedUp': return '↷ level ' + ev.tier + ' passed over #' + ev.skippedId + ' (' + ev.reason + ')';
case 'AdminPaid': return '🏛 platform fee settled: ' + pol(ev.amountWei);
case 'BuyerCounted': return '⭐ member #' + ev.sponsorId + ' now has ' + ev.newCount + ' qualifying buyer(s)';
case 'MemberActivated': return '👤 member #' + ev.id + ' activated a payout wallet';
case 'AwardPaid': return '🎁 award: ' + pol(ev.amountWei) + ' → member #' + ev.toId;
case 'CreditsConsumed': return '📣 member #' + ev.memberId + ' ran ads: −' + ev.amount.toLocaleString() + ' credits';
case 'PriceCached': return '🔮 oracle price refreshed';
case 'FallbackPriceUsed': return '🔮 cached price bridged an oracle gap';
default: return '· ' + ev.type;
}
}
function feedRow(ev, c) {
const div = document.createElement('div');
div.className = 'row t-' + ev.type;
div.innerHTML = '<span>' + describeEvent(ev, c) + '</span>'
+ '<span class="tx"><a target="_blank" rel="noopener" href="' + c.explorer + '/tx/' + ev.tx + '">verify ↗</a></span>';
return div;
}
// Render one served ad into #<elId>. Silent if no inventory.
async function adSlot(type, elId) {
try {
const { ad } = await (await fetch('/api/ads/slot?type=' + type)).json();
const el = $(elId);
if (!ad || !el) return;
if (ad.imageUrl) {
el.innerHTML = '<a href="' + ad.targetUrl + '" target="_blank" rel="noopener nofollow">'
+ '<img src="' + ad.imageUrl + '" alt="advertisement" style="max-width:100%;border-radius:8px"></a>'
+ '<div class="small muted">member ad</div>';
} else {
el.innerHTML = '<a href="' + ad.targetUrl + '" target="_blank" rel="noopener nofollow"><b>' + ad.title + '</b>'
+ (ad.body ? ' · ' + ad.body : '') + '</a> <span class="small muted">member ad</span>';
}
el.hidden = false;
} catch (e) {}
}
return { getConfig, fmtPol, fmtUsd, status, renderNav, refreshNavWallet, describeEvent, feedRow, adSlot, $ };
})();