From 48dcd71290bee585f8e69f1af174ebd552dc19b1 Mon Sep 17 00:00:00 2001 From: martbost Date: Fri, 11 Sep 2026 07:16:54 -0500 Subject: [PATCH] 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 --- chatbot.js | 1 + public/assets/common.js | 40 +++++++++++++++++- public/assets/launch.js | 38 +++++++++++++++++ public/assets/my.js | 8 ++++ public/join.html | 2 +- public/launch.html | 91 +++++++++++++++++++++++++++++++++++++++++ public/my.html | 5 ++- server.js | 2 + 8 files changed, 183 insertions(+), 4 deletions(-) create mode 100644 public/assets/launch.js create mode 100644 public/launch.html 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.done ? '✓' : (n + 1)) + '
    ' + + '

    ' + i.title + '

    ' + i.how + '

    ' + i.why + '
    ' + (i.note ? '

    ' + i.note + '

    ' : '') + '
    ' + + '
    ' + (i.manual ? '' : '' + i.cta + '') + '
  • ').join(''); + $('chk').querySelectorAll('[data-manual]').forEach(b => b.addEventListener('click', () => { IAP.launchToggle(b.dataset.manual); render(); })); + } + render(); +})(); diff --git a/public/assets/my.js b/public/assets/my.js index 07009c5..93f1116 100644 --- a/public/assets/my.js +++ b/public/assets/my.js @@ -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 ? 'Launch ready: ' + done + ' of ' + items.length + '. ' + (at && Date.now() < at ? 'Doors open ' + new Date(at).toLocaleString([], { weekday: 'short', hour: 'numeric', minute: '2-digit' }) + '. ' : '') + 'Open the founding-week checklist' : ''; } + } catch (e) {} if (d.username) { // wall link rides the username const wl = location.origin + '/wall/' + d.username; $('wallLine').textContent = wl; diff --git a/public/join.html b/public/join.html index 1a4f070..f98a03e 100644 --- a/public/join.html +++ b/public/join.html @@ -155,7 +155,7 @@ InstantAdPay · Contract · Terms · Privacy · Disclaimer - + diff --git a/public/launch.html b/public/launch.html new file mode 100644 index 0000000..5b1e9ff --- /dev/null +++ b/public/launch.html @@ -0,0 +1,91 @@ + + + + +Founding week checklist | InstantAdPay + + + + + + + + + +
    +
    +

    Leaders · founding week

    +

    Eight things before launch day.

    +

    Get these done this week, in this order, and your first commissions land in your wallet instead of climbing past you. Everything here reads from your live account.

    +
    + +

    Members only

    Sign in to your member area to open the checklist. Sign in

    + +
    +
    +
    0 of 8 ready
    + + +
    + +
    + The one rule that makes this week matter: unqualified levels pass up. + Level 2 pays you only after two of your people have bought a $20 or more package. Level 3 only after five. If your team's team starts buying before you are qualified, those 20% and 10% payments climb to the next qualified member above you, or to the platform, and they never come back. Qualify first, then open the doors. +
    + +
      + +

      The week, day by day

      +
      +
      Day 1
      • Items 1 to 3 done in one sitting.
      • Decide your play, and whether you are going for level 2 or all three.
      +
      Days 2 to 3
      • Qualify: two real buyers, or Qualified Start. Leaders: go to five and open level 3.
      • Line banner uploaded.
      +
      Days 4 to 6
      • Place your first two personally.
      • Walk them through items 1 to 3 on their accounts.
      +
      Launch day
      • Everyone releases links at the same time.
      • Watch the ledger and the Telegram proof feed fill.
      +
      + +

      What to send your two this week

      +
      Text or DM · before launchI'm bringing a small group in early on something before it opens publicly next week. Free to join, real advertising, and every payment lands in your own wallet the second it happens. I want you positioned before the doors open. Set up takes five minutes: your link
      +
      Text or DM · after they joinThree quick things before launch so your first commission lands with you and not past you: pick your username, link your wallet, switch on payouts. All on the Wallet and Profile tabs. Then send me your two names and we'll get them placed.
      + +

      No income is guaranteed. Results depend on your effort. Crypto carries risk of loss. InstantAdPay sells advertising; it is not an investment.

      +
      + + +
      + + + + diff --git a/public/my.html b/public/my.html index d9c6c91..5e35221 100644 --- a/public/my.html +++ b/public/my.html @@ -228,6 +228,7 @@
      +

      Your line at a glance

      @@ -888,10 +889,10 @@
      - + - + diff --git a/server.js b/server.js index ddf7758..3a411d1 100644 --- a/server.js +++ b/server.js @@ -327,6 +327,7 @@ function siteConfig() { // payment-proof Telegram feed (blank = off) and the P&L pane's fixed monthly cost telegramBotToken: '', telegramChatId: '', telegramTopicId: '', telegramEvents: 'payouts', telegramCtaUrl: 'https://instantadpay.com/', telegramAdminChatId: '', // private chat for admin alerts (sign-up guard bursts); falls back to ADMIN_EMAIL + launchAt: '', // public launch moment, ISO 8601 with offset (e.g. 2026-09-18T19:00:00-05:00): countdown on /launch + dashboard mark pnlFixedMonthlyUsd: 0 }, saved); } @@ -1912,6 +1913,7 @@ const server = http.createServer(async (req, res) => { if (p === '/shorts') return sendFile(res, path.join(PUBLIC_DIR, 'shorts.html')); if (p === '/plays') return sendFile(res, path.join(PUBLIC_DIR, 'plays.html')); if (p === '/wallets') return sendFile(res, path.join(PUBLIC_DIR, 'wallets.html')); + if (p === '/launch') return sendFile(res, path.join(PUBLIC_DIR, 'launch.html')); if (/^\/view\/[a-f0-9]{32}$/.test(p)) return sendFile(res, path.join(PUBLIC_DIR, 'view.html')); m = /^\/uploads\/([a-z0-9]{24}\.(?:png|jpg|webp|gif|mp4|webm))$/.exec(p); if (m) return sendFile(res, path.join(UPLOADS_DIR, m[1]));