Coaching layer + tools: coach your directs (rungs, stalled, one-click nudges), automatic member nudges + weekly sponsor digest, prospects list, per-angle link stats, broadcast templates, Qualified Start calculator, Telegram proof feed, send-failed alerts, admin P&L pane, automatic credit burner (ethers), username lock, home-page comparison, printable checklist, wall link in Promo tools

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
martbost
2026-09-10 07:30:16 -05:00
parent 0c30e831eb
commit 4156b1f815
24 changed files with 1264 additions and 489 deletions
+26 -2
View File
@@ -6,7 +6,7 @@
<title>Admin | InstantAdPay</title>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Sora:wght@600;700;800&display=swap">
<link rel="icon" type="image/png" href="/logo-icon.png">
<link rel="stylesheet" href="/assets/site.css?v=20260910a">
<link rel="stylesheet" href="/assets/site.css?v=20260910b">
<style>
.adm-table th,.adm-table td{padding:8px 10px;text-align:left;vertical-align:top;border-bottom:1px solid var(--line);font-size:13.5px}
.adm-table th{color:var(--muted);font-size:11.5px;text-transform:uppercase;letter-spacing:.08em}
@@ -86,6 +86,7 @@
<button data-pane="campaigns" type="button"><svg viewBox="0 0 24 24"><path d="M4 6h16M4 12h16M4 18h10"/></svg>All campaigns</button>
<button data-pane="members" type="button"><svg viewBox="0 0 24 24"><circle cx="12" cy="7" r="3.2"/><circle cx="5" cy="17" r="2.6"/><circle cx="19" cy="17" r="2.6"/><path d="M12 10v3M12 13l-5 2M12 13l5 2"/></svg>Members</button>
<button data-pane="reports" type="button"><svg viewBox="0 0 24 24"><path d="M12 3l9 16H3z"/><path d="M12 10v4M12 17v.5"/></svg>Reports<span class="pill" id="repBadge" hidden></span></button>
<button data-pane="pnl" type="button"><svg viewBox="0 0 24 24"><path d="M4 19V5M4 19h16"/><path d="M8 15l3-4 3 2 5-6"/></svg>P&amp;L</button>
<button data-pane="settings" type="button"><svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.7 1.7 0 0 0 .3 1.8l.1.1a2 2 0 1 1-2.8 2.8l-.1-.1a1.7 1.7 0 0 0-1.8-.3 1.7 1.7 0 0 0-1 1.5V21a2 2 0 1 1-4 0v-.1a1.7 1.7 0 0 0-1-1.5 1.7 1.7 0 0 0-1.8.3l-.1.1a2 2 0 1 1-2.8-2.8l.1-.1a1.7 1.7 0 0 0 .3-1.8 1.7 1.7 0 0 0-1.5-1H3a2 2 0 1 1 0-4h.1a1.7 1.7 0 0 0 1.5-1 1.7 1.7 0 0 0-.3-1.8l-.1-.1a2 2 0 1 1 2.8-2.8l.1.1a1.7 1.7 0 0 0 1.8.3H9a1.7 1.7 0 0 0 1-1.5V3a2 2 0 1 1 4 0v.1a1.7 1.7 0 0 0 1 1.5 1.7 1.7 0 0 0 1.8-.3l.1-.1a2 2 0 1 1 2.8 2.8l-.1.1a1.7 1.7 0 0 0-.3 1.8V9a1.7 1.7 0 0 0 1.5 1H21a2 2 0 1 1 0 4h-.1a1.7 1.7 0 0 0-1.5 1z"/></svg>Settings</button>
</nav>
<div class="bo-links">
@@ -240,6 +241,29 @@
</div>
</div>
<div class="pane" id="pane-pnl" hidden>
<div class="card">
<div class="card-head"><h3>Profit and loss</h3><span class="sub">read from the chain index; periods are by block (about 43,200 Polygon blocks a day)</span></div>
<div class="chips" id="pnlPeriods"><button type="button" class="chip-t" data-days="7">7 days</button><button type="button" class="chip-t on" data-days="30">30 days</button><button type="button" class="chip-t" data-days="90">90 days</button><button type="button" class="chip-t" data-days="0">All time</button></div>
<div class="grid" id="pnlTiles" style="grid-template-columns:repeat(auto-fit,minmax(200px,1fr));margin-top:12px"></div>
</div>
<div class="grid c2">
<div class="card">
<div class="card-head"><h3>Where the money went</h3><span class="sub">per period</span></div>
<div class="tablewrap"><table class="adm-table" id="pnlSplit"></table></div>
</div>
<div class="card">
<div class="card-head"><h3>Wallets</h3><span class="sub">live balances</span></div>
<div class="tablewrap"><table class="adm-table" id="pnlWallets"></table></div>
<p class="small muted" style="margin:10px 0 0">Fixed monthly cost (USD) for the net line: <input type="number" id="pnlFixed" min="0" step="1" style="width:110px"> <button class="btn small sec" type="button" id="pnlFixedSave">Save</button></p>
</div>
</div>
<div class="card">
<div class="card-head"><h3>Automatic credit burner</h3><span class="sub">settles pending campaign spend on-chain from the engine wallet</span></div>
<p class="small" id="burnerLine">…</p>
<p><button class="btn small sec" type="button" id="burnerRun">Run now</button></p>
</div>
</div>
<div class="pane" id="pane-settings" hidden>
<div class="card">
<div class="card-head"><h3>Follow-up emails</h3><span class="sub" id="dripSub">sent to every new free member, hours after sign-up</span></div>
@@ -282,6 +306,6 @@
</div>
<script src="/assets/common.js?v=20260910a"></script>
<script src="/assets/admin.js?v=20260909d"></script>
<script src="/assets/admin.js?v=20260910a"></script>
</body>
</html>
+33 -3
View File
@@ -44,8 +44,8 @@
});
// ── panes ──
const TITLES = { overview: 'Overview', house: 'House ads', campaigns: 'All campaigns', members: 'Members', reports: 'Reports', settings: 'Settings' };
const loaders = { overview: loadOverview, house: loadHouse, campaigns: loadCampaigns, members: loadMembers, reports: loadReports, settings: loadSettings };
const TITLES = { overview: 'Overview', house: 'House ads', campaigns: 'All campaigns', members: 'Members', reports: 'Reports', pnl: 'Profit and loss', settings: 'Settings' };
const loaders = { overview: loadOverview, house: loadHouse, campaigns: loadCampaigns, members: loadMembers, reports: loadReports, pnl: loadPnl, settings: loadSettings };
function setPane(name) {
if (!TITLES[name]) name = 'overview';
document.querySelectorAll('.pane').forEach(p => { p.hidden = p.id !== 'pane-' + name; });
@@ -297,6 +297,36 @@
});
// ── reports + burns ──
// ── profit and loss ──
let pnlDays = 30;
const pol = w => { try { return (Number(BigInt(w || '0') / 10n ** 14n) / 10000).toLocaleString(undefined, { maximumFractionDigits: 2 }); } catch (e) { return '0'; } };
const usdOf = (w, px) => { try { return '$' + ((Number(BigInt(w || '0') / 10n ** 14n) / 10000) * px).toLocaleString(undefined, { maximumFractionDigits: 0 }); } catch (e) { return '$0'; } };
async function loadPnl() {
const r = await api('/api/admin/pnl?days=' + pnlDays);
const px = r.polUsd || 0;
const platUsd = (Number(BigInt(r.platformWei || '0') / 10n ** 14n) / 10000) * px;
const months = pnlDays ? pnlDays / 30 : Math.max(1, (r.latest - r.fromBlock) / 43200 / 30);
const fixed = (r.fixedMonthlyUsd || 0) * months;
$('pnlTiles').innerHTML = [
['Packages sold', r.purchases.count, Object.entries(r.purchases.byPackage || {}).map(([k, v]) => v + '×' + k).join(' · ') || '—'],
['Gross volume', pol(r.purchases.volumeWei) + ' POL', usdOf(r.purchases.volumeWei, px) + ' at today\'s rate · $' + (r.purchases.usdCents / 100).toLocaleString() + ' at sale'],
['Platform (fees + dust + unclaimed)', pol(r.platformWei) + ' POL', usdOf(r.platformWei, px)],
['Paid to members', pol(r.memberPayoutsWei) + ' POL', usdOf(r.memberPayoutsWei, px)],
['Net after fixed costs', '$' + Math.round(platUsd - fixed).toLocaleString(), 'fixed ' + Math.round(fixed).toLocaleString() + ' over ' + months.toFixed(1) + ' month(s)'],
['Pass-ups', r.passedUp.count, r.passedUp.unqualified + ' unqualified · ' + r.passedUp.sendFailed + ' send-failed']
].map(t => '<div class="statx"><div><div class="nv" style="font-size:22px">' + esc(String(t[1])) + '</div><div class="lb">' + esc(t[0]) + '</div><span class="chip flat">' + esc(t[2]) + '</span></div></div>').join('');
$('pnlSplit').innerHTML = '<tr><th>Line</th><th>POL</th><th>USD now</th></tr>'
+ [['Level 1 (50%)', r.byTier[1]], ['Level 2 (20%)', r.byTier[2]], ['Level 3 (10%)', r.byTier[3]], ['Platform (20% + pass-ups)', r.platformWei]].map(x => '<tr><td>' + x[0] + '</td><td class="mono">' + pol(x[1]) + '</td><td class="mono">' + usdOf(x[1], px) + '</td></tr>').join('');
const W = r.wallets || {}, B = r.balances || {};
$('pnlWallets').innerHTML = '<tr><th>Wallet</th><th>Address</th><th>Balance</th></tr>'
+ [['Owner / fee A (Tangem)', W.feeA, B.feeA], ['Fee B', W.feeB, B.feeB], ['Engine (gas)', W.engine, B.engine]].filter(x => x[1]).map(x => '<tr><td>' + x[0] + '</td><td class="mono small">' + esc(x[1]) + '</td><td class="mono">' + (x[2] == null ? '?' : pol(x[2]) + ' POL') + '</td></tr>').join('');
$('pnlFixed').value = r.fixedMonthlyUsd || 0;
const b = r.burner || {};
$('burnerLine').textContent = !b.hasEthers ? 'ethers is not installed in this build.' : !b.keyPresent ? 'No engine key configured (ENGINE_KEY). Burns stay pending until it is set.' : b.mismatch ? 'ENGINE_KEY does not match the contract engine signer. Disabled.' : 'Engine ' + b.address + ' · ' + pol(b.balanceWei) + ' POL for gas · burned ' + b.burned + ' since boot' + (b.lastRun ? ' · last run ' + when(b.lastRun) : '') + (b.lastError ? ' · last error: ' + b.lastError : '');
}
document.querySelectorAll('#pnlPeriods [data-days]').forEach(b => b.addEventListener('click', () => { pnlDays = Number(b.dataset.days); document.querySelectorAll('#pnlPeriods [data-days]').forEach(x => x.classList.toggle('on', x === b)); loadPnl().catch(e => IAP.status(e.message, 'bad')); }));
if ($('pnlFixedSave')) $('pnlFixedSave').addEventListener('click', async () => { try { await api('/api/admin/site', { pnlFixedMonthlyUsd: Number($('pnlFixed').value) || 0 }, 'PATCH'); IAP.status('Saved.', 'ok'); loadPnl(); } catch (e) { IAP.status(e.message, 'bad'); } });
if ($('burnerRun')) $('burnerRun').addEventListener('click', async () => { try { const r = await api('/api/admin/burner/run', {}); IAP.status('Burner ran: ' + (r.burned || 0) + ' burned.', 'ok'); loadPnl(); } catch (e) { IAP.status(e.message, 'bad'); } });
async function loadReports() {
const [r, b] = await Promise.all([api('/api/admin/reports'), api('/api/admin/burns')]);
const reps = r.reports || [];
@@ -446,7 +476,7 @@
}));
// site settings: key / value rows; booleans as checkboxes, numbers stay numbers
const SITE_META = { siteName: 'Site name', tagline: 'Tagline', rehearsal: 'Testnet rehearsal banner', defaultSponsorId: 'Fallback sponsor (member #)', walletConnectProjectId: 'WalletConnect project id', moonpayPublicKey: 'MoonPay public key' };
const SITE_META = { siteName: 'Site name', tagline: 'Tagline', rehearsal: 'Testnet rehearsal banner', defaultSponsorId: 'Fallback sponsor (member #)', walletConnectProjectId: 'WalletConnect project id', moonpayPublicKey: 'MoonPay public key', telegramBotToken: 'Telegram proof feed: bot token', telegramChatId: 'Telegram proof feed: chat id', telegramTopicId: 'Telegram proof feed: topic id (optional)', telegramEvents: 'Telegram proof feed: events (payouts | payouts+purchases | all)', telegramCtaUrl: 'Telegram proof feed: join link under each post', pnlFixedMonthlyUsd: 'P&L: fixed monthly cost (USD)' };
function drawSite() {
const wrap = $('siteForm');
wrap.innerHTML = Object.entries(siteObj).map(([k, v]) => '<div class="kv-row"><span class="k" title="' + esc(k) + '">' + esc(SITE_META[k] || humanize(k)) + '</span>'
+93 -2
View File
@@ -421,6 +421,10 @@
if (d.username) { // wall link rides the username
const wl = location.origin + '/wall/' + d.username;
$('wallLine').textContent = wl;
if ($('promoWallStrip')) { // the same wall link at the top of Promo tools, next to the invite link
$('promoWallStrip').hidden = false; $('promoWallLink').textContent = wl; $('promoWallOpen').href = '/wall/' + d.username;
$('promoWallCopy').onclick = async () => { try { await navigator.clipboard.writeText(wl); IAP.status('Wall link copied.', 'ok'); } catch (e) { IAP.status('Copy failed. Select the link and copy it.', 'bad'); } };
}
$('wallCopy').hidden = false;
$('wallOpen').hidden = false;
$('wallOpen').href = '/wall/' + d.username;
@@ -460,7 +464,7 @@
if ($('boTitle')) $('boTitle').textContent = TITLES[name];
if (name === 'earn') setEarnSub(earnSub); // refresh whichever sub-tab is active
if (name === 'profile') loadLineBanner();
if (name === 'line') { loadLineage(); loadUplineMessages(); }
if (name === 'line') { loadLineage(); loadUplineMessages(); loadCoach(); loadLinkStats(); loadProspects(); }
if (name === 'campaigns') ['cTarget', 'cImage', 'cVideoUrl'].forEach(id => { if ($(id)) $(id).value = ''; }); // no residual URL between visits
if (name === 'training') loadTraining();
document.getElementById('memberArea').classList.remove('side-open'); // close mobile drawer
@@ -525,8 +529,9 @@
loadCampaigns();
// profile pane state
$('pfCurrent').textContent = me.username ? 'Current username: @' + me.username : 'No username yet. Members see you as a number until you pick one.';
$('pfCurrent').textContent = me.username ? '@' + me.username + ' is your permanent username. Your invite link, your public page and any banners you shared carry it, so it cannot be changed.' : 'No username yet. Members see you as a number until you pick one. Choose carefully: it is permanent once saved.';
if (!$('pfUsername').value) $('pfUsername').value = me.username || '';
$('pfUsername').disabled = !!me.username; $('pfSaveBtn').hidden = !!me.username;
$('pfDetails').innerHTML = 'Email: ' + (me.email || 'none') + '<br>Wallet: '
+ (me.address ? '<span class="mono">' + me.address.slice(0, 10) + '…' + me.address.slice(-6) + '</span>' : 'not linked yet')
+ '<br>On-chain member: ' + (me.memberId ? '#' + me.memberId : 'not yet');
@@ -961,6 +966,92 @@
};
}
// ── coaching: every direct's rung, stalled flag, one-click nudge ──
async function loadCoach() {
try {
const r = await (await fetch('/api/my/coach')).json();
const el = $('coachList'); if (!el || r.error) return;
const d = r.directs || [];
$('coachSummary').innerHTML = d.length ? '<b>' + d.length + '</b> direct' + (d.length === 1 ? '' : 's') + ' · <b>' + r.stalled + '</b> quiet for 3+ days' + (r.stalled ? ' · start at the top' : '') : '';
if (!d.length) { el.innerHTML = '<p class="muted small">No directs yet. When someone joins through your link they show up here with their next step.</p>'; return; }
el.innerHTML = d.map(x => '<div class="lin-row' + (x.stalled ? ' own' : '') + '"><span class="nm">' + esc(x.name) + (x.stalled ? ' <span class="badge amber">quiet ' + x.quietDays + 'd</span>' : '') + '</span>'
+ '<span class="em">' + esc(x.label) + ' → ' + esc(x.next) + '</span>'
+ '<span class="id">rung ' + x.rung + '/6</span>'
+ '<span class="dt">' + (x.buyerCount ? x.buyerCount + ' buyer' + (x.buyerCount === 1 ? '' : 's') : '') + '</span>'
+ '<button class="btn sec small" type="button" data-nudge="' + esc(x.email) + '" data-nname="' + esc(x.name) + '" data-say="' + esc(x.say) + '">Nudge</button></div>').join('');
el.querySelectorAll('[data-nudge]').forEach(b => b.addEventListener('click', async () => {
await openConvo(b.dataset.nudge, b.dataset.nname);
const inp = $('chatInput'); if (inp) { inp.value = b.dataset.say.replace(/\{\{name\}\}/g, b.dataset.nname.replace(/^@/, '')); inp.focus(); }
}));
} catch (e) {}
}
// ── link stats: views, joins, buyers per angle ──
async function loadLinkStats() {
try {
const r = await (await fetch('/api/my/linkstats')).json();
const t = $('linkStatsTable'); if (!t || r.error) return;
const rows = (r.angles || []).filter(a => a.views || a.joins || a.buyers);
if (!rows.length) { t.innerHTML = '<tr><td class="muted small">No views yet. Share your link and the numbers start here.</td></tr>'; return; }
t.innerHTML = '<tr><th>Hook</th><th>Views (30d)</th><th>Views (all)</th><th>Joined</th><th>Qualifying buyers</th></tr>'
+ rows.map(a => '<tr><td>' + esc(a.angle === 'plain' ? 'plain link' : '?v=' + a.angle) + '</td><td class="mono">' + a.views30 + '</td><td class="mono">' + a.views + '</td><td class="mono">' + a.joins + '</td><td class="mono">' + a.buyers + '</td></tr>').join('');
} catch (e) {}
}
// ── prospects: the member's own follow-up list ──
let PP_STATUSES = ['new', 'contacted', 'interested', 'joined', 'bought', 'not now'];
async function loadProspects() {
try {
const r = await (await fetch('/api/my/prospects')).json();
if (r.error) return;
PP_STATUSES = r.statuses || PP_STATUSES;
const sel = $('ppStatus'); if (sel && !sel.options.length) sel.innerHTML = PP_STATUSES.map(s => '<option value="' + s + '">' + s + '</option>').join('');
const list = r.prospects || []; const el = $('prospectList'); if (!el) return;
if (!list.length) { el.innerHTML = '<p class="muted small">Nobody on the list yet.</p>'; return; }
const today = new Date(); today.setHours(0, 0, 0, 0);
el.innerHTML = list.map(p => { const due = p.nextTs && p.nextTs <= today.getTime() + 86399999; return '<div class="lin-row' + (due ? ' own' : '') + '" data-pid="' + p.id + '"><span class="nm">' + esc(p.name) + (due ? ' <span class="badge amber">follow up</span>' : '') + '</span>'
+ '<span class="em">' + esc(p.contact || '') + (p.note ? ' · ' + esc(p.note) : '') + '</span>'
+ '<select class="small" data-pstatus="' + p.id + '">' + PP_STATUSES.map(s => '<option' + (s === p.status ? ' selected' : '') + '>' + s + '</option>').join('') + '</select>'
+ '<input type="date" class="small" data-pnext="' + p.id + '" value="' + (p.nextTs ? new Date(p.nextTs).toISOString().slice(0, 10) : '') + '">'
+ '<button class="btn sec small" type="button" data-pdel="' + p.id + '">Remove</button></div>'; }).join('');
const save = async (id, patch) => { const p = list.find(x => x.id === Number(id)); if (!p) return; try { await api('/api/my/prospects', Object.assign({}, p, patch)); } catch (e) { IAP.status(e.message, 'bad'); } };
el.querySelectorAll('[data-pstatus]').forEach(s => s.addEventListener('change', () => save(s.dataset.pstatus, { status: s.value })));
el.querySelectorAll('[data-pnext]').forEach(i => i.addEventListener('change', () => save(i.dataset.pnext, { next: i.value, nextTs: i.value ? Date.parse(i.value + 'T12:00:00') : null })));
el.querySelectorAll('[data-pdel]').forEach(b => b.addEventListener('click', async () => { try { await api('/api/my/prospects/remove', { id: b.dataset.pdel }); loadProspects(); } catch (e) { IAP.status(e.message, 'bad'); } }));
} catch (e) {}
}
if ($('prospectForm')) $('prospectForm').addEventListener('submit', async e => {
e.preventDefault();
try {
await api('/api/my/prospects', { name: $('ppName').value, contact: $('ppContact').value, status: $('ppStatus').value, nextTs: $('ppNext').value ? Date.parse($('ppNext').value + 'T12:00:00') : null });
$('ppName').value = ''; $('ppContact').value = ''; $('ppNext').value = ''; loadProspects();
} catch (err) { IAP.status(err.message, 'bad'); }
});
// ── broadcast templates ──
const BC_TEMPLATES = [
{ label: 'Welcome', subject: 'Welcome to my line: your first three moves', html: '<p>Glad you are in. Three things today, in this order:</p><ol><li>Pick your username on the Profile tab (it becomes your link).</li><li>Wallet tab: Connect and link wallet, then Switch on payouts. Both are free.</li><li>Copy your invite link from My line and send it to one person.</li></ol><p>Reply here if you get stuck on any of them. That is what I am here for.</p>' },
{ label: 'Switch on payouts', subject: 'One free step so nothing passes you by', html: '<p>Quick reminder: if payouts are not switched on yet, do it now on the Wallet tab. One free transaction.</p><p>The contract locks each buyer to their sponsor at their first purchase, and payouts only route to wallets that are switched on. Ready early and you never miss one.</p>' },
{ label: 'The $5 test', subject: 'See a payout land in real time', html: '<p>Want to see the whole thing work? Buy the $5 Micro package on Buy packages and watch the live ledger while you do it. You will see your credits mint and the split go out in the same transaction.</p><p>When you are ready to count as a qualifying buyer for me, the $20 Activation package is the one.</p>' },
{ label: 'Qualified Start', subject: 'How to open level 2 today with your own positions', html: '<p>You can be your own first buyers, openly. On Buy packages, the Qualified Start card lets you link a second wallet you own as a position. When it buys a $20 package, it counts as a qualifying buyer, half comes straight back to your main wallet, and the credits pool with yours.</p><p>Two positions open level 2 the same day. The three Qualified Start videos in Training show every click.</p>' },
{ label: 'Share your link', subject: 'One conversation a day is the whole job', html: '<p>Promo tools has posts, texts and emails that already carry your link. Pick one and send it to one person today.</p><p>Do not wait for the perfect moment. Nobody who waited ever built a line.</p>' }
];
(function () {
const w = $('bcTemplates'); if (!w) return;
w.innerHTML = BC_TEMPLATES.map((t, i) => '<button type="button" class="chip-t" data-bct="' + i + '">' + esc(t.label) + '</button>').join('');
w.querySelectorAll('[data-bct]').forEach(b => b.addEventListener('click', () => { const t = BC_TEMPLATES[Number(b.dataset.bct)]; $('bcSubject').value = t.subject; $('bcEd').innerHTML = t.html; $('bcEd').focus(); }));
})();
// ── Qualified Start calculator ──
(function () {
const n = $('qcN'), pk = $('qcPkg'), out = $('qcOut'); if (!n || !pk || !out) return;
const CR = { 20: 2000, 50: 5500, 100: 12000, 250: 32500 };
const calc = () => {
const k = Math.max(1, Math.min(10, Number(n.value) || 1)), usd = Number(pk.value) || 20;
const gross = k * usd, back = gross / 2, net = gross - back, credits = k * (CR[usd] || 0);
const level = k >= 5 ? 'Level 3 open (and level 2): the Nexus badge, wall position 3, full 50 / 20 / 10' : k >= 2 ? 'Level 2 open: 20% on your directs\' buyers, wall position 2' : 'Counts as one qualifying buyer. One more opens level 2.';
out.innerHTML = '<div class="grid c2"><div><b>$' + gross + '</b> out across ' + k + ' position' + (k === 1 ? '' : 's') + '<br><b>$' + back + '</b> back to your main wallet in the same transactions (the 50% direct-sponsor share)<br><b>$' + net + '</b> net, plus a little POL for gas in each wallet</div>'
+ '<div><b>' + credits.toLocaleString() + ' credits</b> pooled for your own ads<br>' + level + '<br><span class="muted">The 20% and 10% shares go to your upline if they are qualified, otherwise to the platform.</span></div></div>';
};
n.addEventListener('input', calc); pk.addEventListener('change', calc); calc();
})();
// ── downline lineage + sponsor broadcast + upline messages ──
async function loadLineage() {
try {
+1
View File
@@ -7,6 +7,7 @@
document.getElementById('gate').style.display = signedIn ? 'none' : 'block';
document.getElementById('body').style.display = signedIn ? 'block' : 'none';
if (!signedIn) return;
const pb = document.getElementById('printBtn'); if (pb) pb.addEventListener('click', () => window.print());
const tok = me.username || me.refCode || me.memberId;
if (tok) document.querySelectorAll('[data-link]').forEach(el => { el.textContent = location.origin + '/join/' + tok; });
})();
+8
View File
@@ -82,6 +82,14 @@ nav .links a.active{color:var(--mint);background:rgba(67,232,195,.1)}
/* ── sections ──────────────────────────────────────── */
section{padding:64px 0 8px}
.sectionhead{text-align:center;max-width:640px;margin:0 auto 40px}
.cmp{width:100%;border-collapse:separate;border-spacing:0;font-size:15px;background:var(--panel);border:1px solid var(--line);border-radius:var(--radius);overflow:hidden}
.cmp th,.cmp td{padding:13px 16px;border-bottom:1px solid var(--line);vertical-align:top;text-align:left}
.cmp th{font-family:var(--disp);font-size:13px;letter-spacing:.06em;text-transform:uppercase;color:var(--muted)}
.cmp th:last-child,.cmp td:last-child{color:var(--ink);background:rgba(67,232,195,.06)}
.cmp td:first-child{font-weight:700;white-space:nowrap}
.cmp td:nth-child(2){color:var(--muted)}
.cmp tr:last-child td{border-bottom:0}
@media (max-width:640px){.cmp td:first-child{white-space:normal}.cmp th,.cmp td{padding:10px 10px;font-size:14px}}
h2{font-size:clamp(26px,3.2vw,36px);font-weight:700;margin:0 0 12px;text-wrap:balance}
.sectionhead p{color:var(--muted);font-size:15.5px;margin:0}
h3{font-size:16.5px;margin:0 0 8px;font-weight:700}
+1 -1
View File
@@ -17,7 +17,7 @@
<meta name="theme-color" content="#043b2f">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Sora:wght@600;700;800&display=swap">
<link rel="icon" type="image/png" href="/logo-icon.png">
<link rel="stylesheet" href="/assets/site.css?v=20260910a">
<link rel="stylesheet" href="/assets/site.css?v=20260910b">
</head>
<body>
<div class="wrap">
+1 -1
View File
@@ -5,7 +5,7 @@
<title>Disclaimer | InstantAdPay</title>
<link rel="icon" type="image/png" href="/logo-icon.png">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Sora:wght@600;700;800&display=swap">
<link rel="stylesheet" href="/assets/site.css?v=20260910a">
<link rel="stylesheet" href="/assets/site.css?v=20260910b">
</head>
<body>
<div id="nav"></div>
+23 -1
View File
@@ -20,7 +20,7 @@
<meta name="theme-color" content="#043b2f">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Sora:wght@600;700;800&display=swap">
<link rel="icon" type="image/png" href="/logo-icon.png">
<link rel="stylesheet" href="/assets/site.css?v=20260910a">
<link rel="stylesheet" href="/assets/site.css?v=20260910b">
</head>
<body>
@@ -101,6 +101,28 @@
</div>
</section>
<section id="different">
<div class="wrap">
<div class="sectionhead">
<h2>Why this is not another ad site</h2>
<p>You have seen traffic exchanges, click-to-earn sites and solo-ad sellers. Here is the honest side by side.</p>
</div>
<div class="tablewrap"><table class="cmp">
<tr><th></th><th>Typical advertising site</th><th>InstantAdPay</th></tr>
<tr><td>Referral commission</td><td>5 to 15 percent, often only after a minimum balance</td><td><b>50 percent</b> to the direct sponsor, then 20 and 10 on the next two levels</td></tr>
<tr><td>When you get paid</td><td>Request a withdrawal, wait for approval, hope</td><td>In the <b>same transaction</b> the package sells</td></tr>
<tr><td>Who holds the money</td><td>The site's balance, at the owner's discretion</td><td><b>Nobody.</b> The contract splits it to real wallets. There is no balance to hold</td></tr>
<tr><td>Can the rules change</td><td>Whenever the owner edits a setting</td><td><b>Never.</b> 50 / 20 / 10 / 20 are constants in a verified, immutable contract</td></tr>
<tr><td>Proof</td><td>A number on a dashboard</td><td>Every payout is a <b>public transaction</b> you can open yourself</td></tr>
<tr><td>Ad views</td><td>Timers that run while nobody looks</td><td>A 10-second dwell, a human check, and a <b>server-side clock</b></td></tr>
<tr><td>Credits</td><td>Points that expire or get devalued</td><td><b>1 credit = 1 cent</b> of delivery, recorded on-chain, spent only by your campaigns</td></tr>
<tr><td>Joining</td><td>Install an app, connect a wallet, then maybe read</td><td><b>Email first.</b> The wallet comes when you are ready to be paid</td></tr>
<tr><td>Advertising</td><td>Pay to be seen by people who are paid to click</td><td>Seven formats delivered to <b>members who buy ads themselves</b>, with verified visits and finished video views</td></tr>
</table></div>
<p class="small muted" style="text-align:center;margin-top:14px">No income is guaranteed. Results depend on your effort. Cryptocurrency involves risk of loss. InstantAdPay sells advertising; it is not an investment.</p>
</div>
</section>
<section>
<div class="wrap">
<div class="sectionhead">
+1 -1
View File
@@ -6,7 +6,7 @@
<title>You're invited | InstantAdPay</title>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Sora:wght@600;700;800&display=swap">
<link rel="icon" type="image/png" href="/logo-icon.png">
<link rel="stylesheet" href="/assets/site.css?v=20260910a">
<link rel="stylesheet" href="/assets/site.css?v=20260910b">
<style>
.jn-nav{display:flex;justify-content:space-between;align-items:center;gap:12px;padding:16px 0}
.jn-hero{padding:34px 0 10px;text-align:left}
+1 -1
View File
@@ -17,7 +17,7 @@
<meta name="theme-color" content="#043b2f">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Sora:wght@600;700;800&display=swap">
<link rel="icon" type="image/png" href="/logo-icon.png">
<link rel="stylesheet" href="/assets/site.css?v=20260910a">
<link rel="stylesheet" href="/assets/site.css?v=20260910b">
</head>
<body>
<div class="wrap">
+40 -2
View File
@@ -5,7 +5,7 @@
<title>Member area | InstantAdPay</title>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Sora:wght@600;700;800&display=swap">
<link rel="icon" type="image/png" href="/logo-icon.png">
<link rel="stylesheet" href="/assets/site.css?v=20260910a">
<link rel="stylesheet" href="/assets/site.css?v=20260910b">
</head>
<body class="bo-body">
@@ -275,12 +275,35 @@
</p>
<p class="muted small" id="pitchPreview" hidden></p>
</div>
<div class="card" id="linkStatsCard">
<h3>Your links: what is working</h3>
<p class="muted small">Views, joins and qualifying buyers for each hook. Add <span class="mono">?v=instant</span>, <span class="mono">?v=adspend</span>, <span class="mono">?v=free</span>, <span class="mono">?v=ledger</span> or <span class="mono">?v=two</span> to the end of your link to pick the hook the page opens with.</p>
<div class="tablewrap"><table id="linkStatsTable" class="small"></table></div>
</div>
<div class="card">
<h3>Your line</h3>
<p class="muted small">Everyone who joined through your link, newest first. When they buy their
first package of $20 or more, they count toward your qualification.</p>
<div id="rosterWrap"><p class="muted small" id="rosterEmpty">Nobody yet. Your link is ready above; share it and this list starts filling.</p></div>
</div>
<div class="card" id="coachCard">
<h3>Coach your directs</h3>
<p class="muted small">Where each person you referred sits on the ladder, who has gone quiet, and what to say next. <b>Nudge</b> opens a chat with the message already written; edit it if you like, then send. Quiet members also get one automatic reminder email per step from InstantAdPay, so your nudge is the personal one.</p>
<p class="small" id="coachSummary"></p>
<div id="coachList"><p class="muted small">Loading…</p></div>
</div>
<div class="card" id="prospectCard">
<h3>People you have talked to</h3>
<p class="muted small">Your own follow-up list. Add anyone you mentioned InstantAdPay to, give them a status and a follow-up day, and the next conversation stays in front of you. Only you can see this.</p>
<form id="prospectForm" style="display:flex;gap:8px;flex-wrap:wrap;align-items:flex-end;margin:0 0 12px">
<input id="ppName" placeholder="Name" maxlength="120" required style="flex:1;min-width:140px">
<input id="ppContact" placeholder="Phone, handle or email" maxlength="120" style="flex:1;min-width:160px">
<select id="ppStatus" style="min-width:130px"></select>
<input id="ppNext" type="date" title="Follow up on" style="min-width:150px">
<button class="btn small" type="submit">Add</button>
</form>
<div id="prospectList"><p class="muted small">Nobody on the list yet.</p></div>
</div>
<div class="card">
<h3>Your downline, three levels deep</h3>
@@ -298,6 +321,8 @@
<option value="direct">My direct referrals only (level 1)</option>
<option value="all">My whole downline (levels 1–3)</option>
</select></p>
<p class="small muted" style="margin:10px 0 4px">Start from a template:</p>
<div class="chips" id="bcTemplates" style="margin:0 0 10px"></div>
<p><input id="bcSubject" maxlength="160" placeholder="Subject" style="width:100%"></p>
<div class="ed-bar" aria-label="Formatting">
<button type="button" data-bc="bold" title="Bold"><b>B</b></button>
@@ -346,6 +371,15 @@
<div id="qsList" class="small"></div>
<p class="muted small" style="margin:12px 0 0">Your own money, your own wallets, a faster start. It is not an income promise: qualification only pays on future purchases in your line, and every purchase here is a real ad package you can spend.</p>
</div>
<div class="card" id="qsCalcCard">
<h3>Qualified Start calculator</h3>
<p class="muted small">What it costs to open each level with your own positions, what comes straight back to your main wallet, and the ad credits you end up holding.</p>
<p style="display:flex;gap:12px;flex-wrap:wrap;align-items:center">
<label class="small">Positions <input type="number" id="qcN" min="1" max="10" value="2" style="width:80px;margin-left:6px"></label>
<label class="small">Package each <select id="qcPkg" style="margin-left:6px"><option value="20">$20 · 2,000 credits</option><option value="50">$50 · 5,500 credits</option><option value="100">$100 · 12,000 credits</option><option value="250">$250 · 32,500 credits</option></select></label>
</p>
<div id="qcOut" class="small"></div>
</div>
</div>
<div class="pane" id="pane-campaigns" hidden>
@@ -560,6 +594,10 @@
<div><span class="eyebrow" style="margin:0 0 4px">Your invite link</span><div class="mono small" id="promoLink" style="overflow-wrap:anywhere">…</div></div>
<button class="btn small" id="promoLinkCopy" type="button">Copy link</button>
</div>
<div class="card promo-strip" id="promoWallStrip" hidden>
<div><span class="eyebrow" style="margin:0 0 4px">Your banner wall</span><div class="mono small" id="promoWallLink" style="overflow-wrap:anywhere">…</div></div>
<span><button class="btn small sec" id="promoWallCopy" type="button">Copy wall link</button> <a class="btn small sec" id="promoWallOpen" href="#" target="_blank" rel="noopener">Open</a></span>
</div>
<div class="chips promo-pills" role="tablist" aria-label="Promo tools sections">
<button class="chip-t on" data-promo="posts" type="button" role="tab">Social posts</button>
<button class="chip-t" data-promo="text" type="button" role="tab">Text a friend</button>
@@ -797,7 +835,7 @@
<script src="/assets/common.js?v=20260910a"></script>
<script src="/assets/wallet.js?v=20260910a"></script>
<script src="/assets/promo.js?v=20260909b"></script>
<script src="/assets/my.js?v=20260910c"></script>
<script src="/assets/my.js?v=20260910d"></script>
<script src="/assets/chat.js?v=20260907l"></script>
</body>
</html>
+25 -1
View File
@@ -8,7 +8,7 @@
<meta name="theme-color" content="#043b2f">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Sora:wght@600;700;800&display=swap">
<link rel="icon" type="image/png" href="/logo-icon.png">
<link rel="stylesheet" href="/assets/site.css?v=20260910a">
<link rel="stylesheet" href="/assets/site.css?v=20260910b">
<style>
.pl-rule{border-left:4px solid #f2c94c;background:rgba(242,201,76,.08);padding:16px 20px;border-radius:0 12px 12px 0;margin:0 0 28px}
.pl-rule b{font-family:var(--disp);font-size:17px;display:block;margin-bottom:6px}
@@ -39,6 +39,8 @@
.fit-table th{text-align:left;color:var(--muted);font-family:var(--mono);font-size:11px;letter-spacing:.08em;text-transform:uppercase;font-weight:500}
.fit-table td,.fit-table th{padding:9px 10px;border-bottom:1px solid var(--line);vertical-align:top}
#gate{display:none}
#printable{display:none}
@media print{ body{background:#fff;color:#000} .wrap>*{display:none!important} #printable{display:block!important;color:#000} #printable h2,#printable h3{color:#000;font-family:Georgia,serif} #printable li{margin:6px 0} }
@media (max-width:720px){.pl-split{grid-template-columns:repeat(2,1fr)}.pl-ladder{grid-template-columns:1fr}.pl-ladder>div{border-right:0;border-bottom:1px solid var(--line)}.meta,.cal{grid-template-columns:1fr}}
</style>
</head>
@@ -160,6 +162,28 @@
</div>
<p class="muted small">No income is guaranteed. Results depend on your effort. Cryptocurrency involves risk of loss. InstantAdPay sells advertising; it is not an investment.</p>
<p><button class="btn sec" type="button" id="printBtn">Print the Qualified Start checklist and 30-day calendar</button></p>
</div>
<div id="printable">
<h2>InstantAdPay: Qualified Start checklist</h2>
<ol>
<li>Username chosen (Profile tab). It is permanent: it becomes your invite link.</li>
<li>Main wallet linked (Wallet tab, one free signature).</li>
<li>Payouts switched on (Wallet tab, one free transaction).</li>
<li>Extra wallet accounts created in your wallet app: IAP Position 2, 3, 4, 5.</li>
<li>Each extra account funded with enough POL for a $20 package plus gas.</li>
<li>Buy packages: Add a position, tick only the new account, sign once.</li>
<li>Buy from: choose the position, Buy $20, confirm in the wallet.</li>
<li>Repeat. Two positions open level 2. Five open level 3 and the Nexus badge.</li>
</ol>
<h2>First 30 days, any play</h2>
<h3>Day 1</h3><ul><li>Username, wallet linked, payouts on, welcome tour done.</li><li>Link sent to one person.</li></ul>
<h3>Days 2 to 7</h3><ul><li>One conversation a day.</li><li>Claim the daily 5 credits.</li><li>First buyer.</li></ul>
<h3>Days 8 to 14</h3><ul><li>Second qualifying buyer. Level 2 open.</li><li>Line banner set. First broadcast sent.</li></ul>
<h3>Days 15 to 30</h3><ul><li>Coach the two to their two.</li><li>Add directs three, four, five. Level 3 open by day 30 is the stretch goal.</li></ul>
<p>My invite link: <span data-link>__________________________</span></p>
<p style="font-size:12px">No income is guaranteed. Results depend on your effort. Cryptocurrency involves risk of loss. InstantAdPay sells advertising; it is not an investment.</p>
</div>
<footer>
+1 -1
View File
@@ -5,7 +5,7 @@
<title>Privacy Policy | InstantAdPay</title>
<link rel="icon" type="image/png" href="/logo-icon.png">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Sora:wght@600;700;800&display=swap">
<link rel="stylesheet" href="/assets/site.css?v=20260910a">
<link rel="stylesheet" href="/assets/site.css?v=20260910b">
</head>
<body>
<div id="nav"></div>
+1 -1
View File
@@ -5,7 +5,7 @@
<title>Shorts | InstantAdPay</title>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Sora:wght@600;700;800&display=swap">
<link rel="icon" type="image/png" href="/logo-icon.png">
<link rel="stylesheet" href="/assets/site.css?v=20260910a">
<link rel="stylesheet" href="/assets/site.css?v=20260910b">
<style>
html,body{height:100%;margin:0;overflow:hidden;background:#000}
.sh{position:fixed;inset:0;display:flex;flex-direction:column;background:#000;color:var(--ink,#e8fff7)}
+1 -1
View File
@@ -5,7 +5,7 @@
<title>Terms of Service | InstantAdPay</title>
<link rel="icon" type="image/png" href="/logo-icon.png">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Sora:wght@600;700;800&display=swap">
<link rel="stylesheet" href="/assets/site.css?v=20260910a">
<link rel="stylesheet" href="/assets/site.css?v=20260910b">
</head>
<body>
<div id="nav"></div>
+1 -1
View File
@@ -5,7 +5,7 @@
<title>Transaction | InstantAdPay</title>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Sora:wght@600;700;800&display=swap">
<link rel="icon" type="image/png" href="/logo-icon.png">
<link rel="stylesheet" href="/assets/site.css?v=20260910a">
<link rel="stylesheet" href="/assets/site.css?v=20260910b">
</head>
<body>
<div id="nav"></div>
+1 -1
View File
@@ -5,7 +5,7 @@
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Viewing ad — InstantAdPay</title>
<link rel="icon" type="image/png" href="/logo-icon.png">
<link rel="stylesheet" href="/assets/site.css?v=20260910a">
<link rel="stylesheet" href="/assets/site.css?v=20260910b">
<style>
html,body{height:100%;margin:0;overflow:hidden}
.vw{display:flex;flex-direction:column;height:100vh;height:100dvh;background:var(--bg,#04110c);color:var(--ink,#e8fff7)}
+1 -1
View File
@@ -6,7 +6,7 @@
<!--OG-->
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Sora:wght@600;700;800&display=swap">
<link rel="icon" type="image/png" href="/logo-icon.png">
<link rel="stylesheet" href="/assets/site.css?v=20260910a">
<link rel="stylesheet" href="/assets/site.css?v=20260910b">
</head>
<body>
<div id="nav"></div>