diff --git a/chatbot.js b/chatbot.js index bf46e78..7d757b9 100644 --- a/chatbot.js +++ b/chatbot.js @@ -77,6 +77,7 @@ FACTS: - Ad packages: Micro $5/500 credits, Activation $20/2,000, Builder $50/5,500, Growth $100/12,000, Leader $250/32,500. Dollar-priced, settled in POL (Polygon) at the live Chainlink rate. 1 credit = 1 cent of ad delivery. - Live formats: display banners (per impression), text ads (per impression), full-screen LOGIN ADS (per day: right after a member signs in they land on a sponsor interstitial — they click "Open Ad", the advertiser's page opens in a NEW tab, a countdown runs on the interstitial, and at zero a "Go to dashboard" button appears. Just a CTA link is enough; an optional banner image can be the clickable creative. No framing requirement since it opens in its own tab), WATCH-TO-EARN VIDEO ADS (advertiser uploads an MP4/WebM or gives a direct https .mp4/.webm link and picks a required watch length — 10s/30s/60s — which sets the per-view price; viewers watch in an escape-proof player under Earn credits > Watch videos, the watch time is enforced on the server clock, and they earn credits per completed watch; you never see your own videos), and solo ads. Banner ads also require a size (standard IAB sizes like 728x90, 300x250). Coming: featured rotation with disclosed rotation size, verified-visit packs. - WALLETS + BUYING POL (Training > Wallets and buying POL, /wallets, members only): preferred MetaMask (recommended; extra accounts for Qualified Start), Phantom, SafePal, Coinbase Wallet; Trust works but blocks buys spending most of its POL (keep ~2x). MoonPay flow: connect wallet, Buy packages > "Buy POL with a card" opens MoonPay with POL on Polygon + the member's address prefilled; card/Apple Pay/Google Pay; first-time ID check; minimum order ~$30; buy package cost + 2-3 POL for fees; POL arrives in minutes; then buy. Exchanges: withdraw POL on the Polygon network. Never MATIC on Ethereum, never share the recovery phrase. +- FOUNDING WEEK / PRE-LAUNCH (Training > Founding week checklist, /launch, members only): eight items read live from the account: username, wallet linked, payouts on, level 2 qualified (2 buyers of $20+, or Qualified Start with 2 linked positions), the leader play = level 3 (5 qualifying buyers, up to 5 linked positions; then buy from the main wallet), line banner, links + play chosen (self-marked), first two placed. Reason: unqualified levels pass up, so leaders qualify BEFORE their teams' teams buy. Countdown shows when admin sets launchAt. Never call the site 'pre-launch' publicly: it is live and paying. - SCHEDULING (2026-09-11): any campaign except featured can take an optional start and end time (local time) in the New campaign form; solo ads label it "Send from" (inbox deliveries begin then). A scheduled campaign shows "scheduled" until it starts; at the end it shows "ended" and the unspent budget returns to Available. Banner/text scheduled campaigns join the partner network at their start time. There is NO dayparting (hours-of-day targeting) by design; the daily cap paces budgets. Each campaign row shows a small views-by-hour chart (on-site views, viewer's local time, last 7 days). - BALANCE RULE (members ask this a lot): a balance is what is NOT committed to a live campaign. Starting a campaign sets aside its whole budget at once (earned pool first, then purchased), so Available drops once and stays still while ads serve; the budget spends down inside Members > Campaigns. Dashboard shows Purchased available, Earned available, In live campaigns. A paused campaign keeps its unspent budget set aside so it can resume. A low balance with a live campaign is not lost credits. Refunds/comps of purchased money are credited off-chain as purchased-grade credits: shown under Purchased as "credited to you", fund anything incl. login ads. Viewing-earned credits never fund login ads. - Members EARN credits by attention: in the Earn credits section of Members, each ad in the daily set opens FULL SCREEN in its own tab, showing the advertiser's real website. A countdown runs while you watch (it pauses if you leave the tab), then a quick human check (click the named icon) must be passed before the view counts. Finish the daily set, claim a small daily credit batch. Earned credits spend on banner and text campaigns; attention earns advertising, referrals earn money, and viewer rewards are never cash. Advertisers get real, verified visits to their site. diff --git a/public/assets/common.js b/public/assets/common.js index 64d4213..f38cc84 100644 --- a/public/assets/common.js +++ b/public/assets/common.js @@ -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 => ({ '&': '&', '<': '<', '>': '>', '"': '"' }[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: ' + 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 + '.' } + ]; + } + return { getConfig, fmtPol, fmtUsd, status, renderNav, refreshNavWallet, describeEvent, feedRow, adSlot, reportAd, requestCode, launchChecks, launchToggle, $ }; })(); diff --git a/public/assets/launch.js b/public/assets/launch.js new file mode 100644 index 0000000..d36661c --- /dev/null +++ b/public/assets/launch.js @@ -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) => '
' + i.how + '
' + i.note + '
' : '') + '