';
document.body.prepend(nav);
if (c.rehearsal) {
const b = document.createElement('div');
b.className = 'rehearsal';
b.innerHTML = 'Testnet rehearsal: 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();
renderFooter();
}
function renderFooter() {
if (document.getElementById('iapFooter')) return;
const f = document.createElement('footer'); f.id = 'iapFooter';
f.style.cssText = 'border-top:1px solid var(--line);margin-top:48px;padding:26px 22px;text-align:center;color:var(--muted);font-size:13px';
f.innerHTML = '
';
host.querySelectorAll('.ic-btn').forEach(b => b.addEventListener('click', () => { host.innerHTML = ''; host.hidden = true; resolve(b.textContent); }, { once: true }));
});
}
// honeypot fields (join + sign-in): read-only until a trusted focus, so browser autofill and
// password managers leave them alone; a value that appeared without a trusted event is ignored
function armHoneypots() {
document.querySelectorAll('.hp-field').forEach(el => {
if (el.dataset.armed) return; el.dataset.armed = '1'; el.readOnly = true;
const touch = e => { if (e.isTrusted) { el.readOnly = false; el.dataset.touched = '1'; } };
el.addEventListener('focus', touch); el.addEventListener('input', touch); el.addEventListener('keydown', touch);
});
}
armHoneypots(); document.addEventListener('DOMContentLoaded', armHoneypots);
const hpValue = el => (el && el.dataset.touched === '1') ? (el.value || '') : '';
// The server refuses a code request that arrives within a couple of seconds of the page loading,
// because a script fills a form instantly and a person does not. A returning member whose email
// is already autofilled has nothing to type, taps straight away, and gets told off for being
// quick (Marty, 2026-09-23). So the page waits out the remainder itself: the person sees the
// normal sending state, and anything posting at the endpoint without a page behind it still
// fails the same check.
const MIN_FORM_MS = 2400; // the server wants 2000; leave headroom for clock skew in transit
function formSettled() {
const left = MIN_FORM_MS - (Date.now() - FORM_TS);
return left > 0 ? new Promise(r => setTimeout(r, left)) : Promise.resolve();
}
async function requestCode(email, opts) {
const o = opts || {};
let pick = null;
await formSettled();
for (let i = 0; i < 4; i++) {
const r = await (await fetch('/api/auth/email/start', { method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, fts: FORM_TS, hp_field_x9: hpValue(o.honeypot), pick }) })).json();
if (r.challenge && o.host) { pick = await iconCheck(o.host, r.challenge, r.error); continue; }
if (r.error) throw new Error(r.error);
return r;
}
throw new Error('Could not verify. Refresh the page and try again.');
}
const esc = s => String(s == null ? '' : s).replace(/[&<>"]/g, c => ({ '&': '&', '<': '<', '>': '>', '"': '"' }[c]));
// โโ founding-week checklist, read from the live account. Shared by /launch and
// the dashboard mark. Two items are the member's own call and persist locally.
const LAUNCH_KEY = 'iap.launch.manual';
const manualSet = () => { try { return new Set(JSON.parse(localStorage.getItem(LAUNCH_KEY) || '[]')); } catch (e) { return new Set(); } };
function launchToggle(key) { const s = manualSet(); if (s.has(key)) s.delete(key); else s.add(key); try { localStorage.setItem(LAUNCH_KEY, JSON.stringify([...s])); } catch (e) {} }
function launchChecks(me) {
const m = me || {}, man = manualSet();
const bc = Number(m.buyerCount || 0), refs = (m.referrals || []).length;
return [
{ key: 'username', title: 'Pick your username', done: !!m.username, href: '/my#profile', cta: 'Profile',
how: 'Profile tab. It becomes your invite link and your public page, and it is permanent.',
why: 'Every link, banner and video you hand out this week carries it. Change it later and the links you already sent die.' },
{ key: 'wallet', title: 'Link your wallet', done: !!m.address, href: '/my#wallet', cta: 'Wallet',
how: 'Wallet tab, Connect, sign the free message. MetaMask recommended. Never held crypto? The wallet guide in Training walks through buying POL with a card.',
why: 'Payouts go to this address. No wallet, nowhere to pay you.' },
{ key: 'payouts', title: 'Switch on payouts', done: !!m.memberId, href: '/my#wallet', cta: 'Wallet',
how: 'Wallet tab, one small transaction. It registers your address with the contract.',
why: 'The contract binds each buyer to their sponsor at their first purchase. If payouts are off when your first person buys, that commission is not yours.' },
{ key: 'level2', title: 'Qualify: open level 2', done: bc >= 2, href: '/my#buy', cta: 'Buy packages',
how: 'Two of your people buy a $20 or more package. Or use Qualified Start: add two positions from extra wallets in your own MetaMask and buy a $20 package from each (about $23 of POL in each wallet).',
why: 'Until you have two qualifying buyers, every level 2 payment from your team climbs past you.',
note: 'Qualifying buyers so far: ' + bc + ' of 2.' },
{ key: 'level3', title: 'The leader play: open all three levels', done: bc >= 5, href: '/my#buy', cta: 'Qualified Start',
how: 'Five qualifying buyers, real or Qualified Start, up to five linked positions. Once qualified, buy from your main wallet so your sponsor is paid in full.',
why: 'A leader whose team goes three deep this week collects level 3 from day one instead of watching those 10% payments pass upward. Optional for members, the play for leaders.',
note: 'Qualifying buyers so far: ' + bc + ' of 5.' },
{ key: 'banner', title: 'Upload your line banner', done: !!m.lineBannerUrl, href: '/my#profile', cta: 'Profile',
how: 'Profile tab, line banner. It shows on the welcome tour to everyone in your next three levels.',
why: 'Your first advertising to your own team, free, and it is live the moment they join.' },
{ key: 'links', title: 'Copy your links and pick a play', done: man.has('links'), manual: true,
how: 'Promo tools, Your links: the invite link and the five angle links, plus the matching hook videos. Then read the plays page and choose one.',
why: 'On launch day you send links, not explanations. Having them ready is the whole difference between a launch and a scramble.' },
{ key: 'two', title: 'Place your first two', done: refs >= 2, href: '/my#line', cta: 'My line',
how: 'Two people you have actually talked to, joined through your link, walked through items 1 to 3 on their own accounts.',
why: 'Your first two are the shape of your whole line. Choose them, do not wait for them.',
note: 'Joined through you so far: ' + refs + '.' }
];
}
// In-page dialogs instead of window.prompt / confirm. Mobile Safari shows a red "Suppress dialogs"
// option on the second native pop-up in a row and, once tapped, swallows every later prompt on the
// site until reload. ask() resolves the typed value (null on cancel); confirmBox() resolves true/false.
function dialog(o) {
return new Promise(resolve => {
const esc = t => String(t == null ? '' : t).replace(/[&<>"]/g, c => ({ '&': '&', '<': '<', '>': '>', '"': '"' }[c]));
const back = document.createElement('div'); back.className = 'modal-back'; back.style.zIndex = '200';
const field = o.type === 'none' ? '' : o.type === 'textarea'
? ''
: '';
back.innerHTML = '