Founding week: /launch leader checklist (8 live-read items incl. the level-3 leader play), dashboard launch-ready mark, launchAt setting

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
martbost
2026-09-11 07:16:54 -05:00
parent bf5371ab20
commit 48dcd71290
8 changed files with 183 additions and 4 deletions
+39 -1
View File
@@ -167,5 +167,43 @@ window.IAP = (function () {
throw new Error('Could not verify. Refresh the page and try again.');
}
const esc = s => String(s == null ? '' : s).replace(/[&<>"]/g, c => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;' }[c]));
return { getConfig, fmtPol, fmtUsd, status, renderNav, refreshNavWallet, describeEvent, feedRow, adSlot, reportAd, requestCode, $ };
// ── 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: <b>' + bc + '</b> 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: <b>' + bc + '</b> 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: <b>' + refs + '</b>.' }
];
}
return { getConfig, fmtPol, fmtUsd, status, renderNav, refreshNavWallet, describeEvent, feedRow, adSlot, reportAd, requestCode, launchChecks, launchToggle, $ };
})();
+38
View File
@@ -0,0 +1,38 @@
// Founding week checklist: members only; every item reads from the live account
// (IAP.launchChecks in common.js is shared with the dashboard's "launch ready" mark).
(async function () {
const $ = id => document.getElementById(id);
try { await IAP.renderNav('training'); } catch (e) {}
let me = null, cfg = {};
try { me = await (await fetch('/api/me')).json(); } catch (e) {}
try { cfg = await IAP.getConfig(); } catch (e) {}
const signedIn = !!(me && me.signedIn && me.email);
$('gate').style.display = signedIn ? 'none' : 'block';
$('body').style.display = signedIn ? 'block' : 'none';
if (!signedIn) return;
const pb = $('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; });
// launch moment (admin: Settings > launchAt, ISO 8601 with offset)
const at = cfg.launchAt ? new Date(cfg.launchAt) : null;
if (at && !isNaN(at)) {
$('lwWhen').hidden = false;
$('lwWhenAt').textContent = at.toLocaleString([], { weekday: 'short', month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit' });
const tick = () => { const ms = at - Date.now(); if (ms <= 0) { $('lwWhenIn').textContent = 'doors are open'; return; }
const d = Math.floor(ms / 86400000), h = Math.floor(ms % 86400000 / 3600000), m = Math.floor(ms % 3600000 / 60000);
$('lwWhenIn').textContent = (d ? d + 'd ' : '') + h + 'h ' + m + 'm to go'; };
tick(); setInterval(tick, 30000);
}
function render() {
const items = IAP.launchChecks(me);
const done = items.filter(i => i.done).length;
$('lwDone').textContent = done; $('lwBar').style.width = Math.round(done / items.length * 100) + '%';
$('chk').innerHTML = items.map((i, n) => '<li class="' + (i.done ? 'done' : '') + '"><div class="box">' + (i.done ? '✓' : (n + 1)) + '</div>'
+ '<div><h3>' + i.title + '</h3><p>' + i.how + '</p><div class="why">' + i.why + '</div>' + (i.note ? '<p class="small" style="margin-top:4px">' + i.note + '</p>' : '') + '</div>'
+ '<div class="act">' + (i.manual ? '<button class="btn small ' + (i.done ? 'sec' : '') + '" type="button" data-manual="' + i.key + '">' + (i.done ? 'Undo' : 'Mark done') + '</button>' : '<a class="btn small sec" href="' + i.href + '">' + i.cta + '</a>') + '</div></li>').join('');
$('chk').querySelectorAll('[data-manual]').forEach(b => b.addEventListener('click', () => { IAP.launchToggle(b.dataset.manual); render(); }));
}
render();
})();
+8
View File
@@ -421,6 +421,14 @@
// ready-to-send share message + promo tools, personalized
const link = location.origin + '/join/' + (d.username || d.refCode || d.memberId || '');
fillPromo(link, d);
// founding-week readiness: shown until every item is done (or the launch moment is a week past)
try {
const cfg = await IAP.getConfig(); const items = IAP.launchChecks(d); const done = items.filter(i => i.done).length;
const at = cfg.launchAt ? new Date(cfg.launchAt).getTime() : 0;
const show = done < items.length && !(at && Date.now() > at + 7 * 86400000);
const lm = $('launchMark');
if (lm) { lm.hidden = !show; lm.innerHTML = show ? '<b>Launch ready: ' + done + ' of ' + items.length + '.</b> ' + (at && Date.now() < at ? 'Doors open ' + new Date(at).toLocaleString([], { weekday: 'short', hour: 'numeric', minute: '2-digit' }) + '. ' : '') + '<a href="/launch">Open the founding-week checklist</a>' : ''; }
} catch (e) {}
if (d.username) { // wall link rides the username
const wl = location.origin + '/wall/' + d.username;
$('wallLine').textContent = wl;