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
+1
View File
@@ -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. - 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. - 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. - 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). - 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. - 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. - 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.
+39 -1
View File
@@ -167,5 +167,43 @@ window.IAP = (function () {
throw new Error('Could not verify. Refresh the page and try again.'); 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])); 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 // ready-to-send share message + promo tools, personalized
const link = location.origin + '/join/' + (d.username || d.refCode || d.memberId || ''); const link = location.origin + '/join/' + (d.username || d.refCode || d.memberId || '');
fillPromo(link, d); 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 if (d.username) { // wall link rides the username
const wl = location.origin + '/wall/' + d.username; const wl = location.origin + '/wall/' + d.username;
$('wallLine').textContent = wl; $('wallLine').textContent = wl;
+1 -1
View File
@@ -155,7 +155,7 @@
InstantAdPay · <a href="/contract">Contract</a> · <a href="/terms">Terms</a> · <a href="/privacy">Privacy</a> · <a href="/disclaimer">Disclaimer</a> InstantAdPay · <a href="/contract">Contract</a> · <a href="/terms">Terms</a> · <a href="/privacy">Privacy</a> · <a href="/disclaimer">Disclaimer</a>
</div> </div>
</div> </div>
<script src="/assets/common.js?v=20260911a"></script> <script src="/assets/common.js?v=20260911b"></script>
<script src="/assets/join.js?v=20260911a"></script> <script src="/assets/join.js?v=20260911a"></script>
</body> </body>
</html> </html>
+91
View File
@@ -0,0 +1,91 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>Founding week checklist | InstantAdPay</title>
<meta name="description" content="The eight things a leader gets done before launch, and why the order matters. Member training.">
<meta name="robots" content="noindex">
<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=20260911c">
<style>
.lw-rule{border-left:4px solid #f2c94c;background:rgba(242,201,76,.08);padding:16px 20px;border-radius:0 12px 12px 0;margin:0 0 24px}
.lw-rule b{font-family:var(--disp);font-size:17px;display:block;margin-bottom:6px}
.lw-top{display:flex;justify-content:space-between;align-items:center;gap:16px;flex-wrap:wrap;margin:0 0 18px}
.lw-count{font-family:var(--disp);font-weight:800;font-size:15px}
.lw-count b{color:var(--mint);font-size:22px}
.lw-bar{height:10px;border-radius:99px;background:rgba(255,255,255,.06);overflow:hidden;margin:8px 0 0;width:100%}
.lw-bar i{display:block;height:100%;background:linear-gradient(90deg,var(--mint),#9ff2dc);width:0;transition:width .4s}
.lw-when{font-family:var(--mono);font-size:12px;color:var(--muted)}
.lw-when b{color:var(--ink);font-family:var(--disp);font-size:18px;display:block}
.chk{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:10px}
.chk li{display:grid;grid-template-columns:34px 1fr auto;gap:14px;align-items:start;background:var(--panel);border:1px solid var(--line);border-radius:14px;padding:14px 16px}
.chk li.done{border-color:rgba(67,232,195,.45)}
.chk .box{width:30px;height:30px;border-radius:9px;border:2px solid var(--line-strong);display:flex;align-items:center;justify-content:center;font-weight:800;color:var(--mint);font-size:18px}
.chk li.done .box{background:rgba(67,232,195,.14);border-color:var(--mint)}
.chk h3{margin:0 0 3px;font-size:16px}
.chk p{margin:0;color:var(--muted);font-size:14px}
.chk .why{margin-top:6px;font-size:13px;color:var(--ink)}
.chk .act{white-space:nowrap}
.chk .act .btn{margin-top:0}
.days{display:grid;grid-template-columns:repeat(4,1fr);gap:10px}
.days>div{background:rgba(4,8,7,.45);border:1px solid var(--line);border-radius:10px;padding:12px 14px;font-size:14px}
.days .k{font-family:var(--disp);font-weight:800;color:var(--mint);margin-bottom:6px}
.days ul{margin:0;padding-left:18px}
blockquote{margin:0 0 10px;background:rgba(4,8,7,.45);border:1px solid var(--line);border-radius:10px;padding:12px 16px;color:var(--muted)}
blockquote span{display:block;font-family:var(--mono);font-size:11px;letter-spacing:.08em;text-transform:uppercase;color:var(--mint);margin-bottom:6px}
h2{margin:34px 0 10px;font-size:26px}
#gate{display:none}
@media (max-width:720px){.days{grid-template-columns:1fr 1fr}.chk li{grid-template-columns:34px 1fr}.chk .act{grid-column:2}}
@media print{body{background:#fff;color:#000}.jn-nav,nav,footer,.act,#printBtn{display:none!important}.chk li{border-color:#999}.chk p,.chk .why{color:#000}}
</style>
</head>
<body>
<div class="wrap">
<section class="hero" style="padding:70px 0 20px">
<p class="eyebrow">Leaders · founding week</p>
<h1>Eight things before <em>launch day</em>.</h1>
<p class="lead">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.</p>
</section>
<div class="card" id="gate"><h3>Members only</h3><p class="muted small">Sign in to your member area to open the checklist. <a href="/my">Sign in</a></p></div>
<div id="body">
<div class="lw-top">
<div style="flex:1 1 300px"><div class="lw-count"><b id="lwDone">0</b> of 8 ready</div><div class="lw-bar"><i id="lwBar"></i></div></div>
<div class="lw-when" id="lwWhen" hidden>Launch<b id="lwWhenAt"></b><span id="lwWhenIn"></span></div>
<button class="btn small sec" id="printBtn" type="button">Print</button>
</div>
<div class="lw-rule">
<b>The one rule that makes this week matter: unqualified levels pass up.</b>
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.
</div>
<ol class="chk" id="chk"></ol>
<h2>The week, day by day</h2>
<div class="days">
<div><div class="k">Day 1</div><ul><li>Items 1 to 3 done in one sitting.</li><li>Decide your play, and whether you are going for level 2 or all three.</li></ul></div>
<div><div class="k">Days 2 to 3</div><ul><li>Qualify: two real buyers, or Qualified Start. Leaders: go to five and open level 3.</li><li>Line banner uploaded.</li></ul></div>
<div><div class="k">Days 4 to 6</div><ul><li>Place your first two personally.</li><li>Walk them through items 1 to 3 on their accounts.</li></ul></div>
<div><div class="k">Launch day</div><ul><li>Everyone releases links at the same time.</li><li>Watch the ledger and the Telegram proof feed fill.</li></ul></div>
</div>
<h2>What to send your two this week</h2>
<blockquote><span>Text or DM · before launch</span>I'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: <span data-link>your link</span></blockquote>
<blockquote><span>Text or DM · after they join</span>Three 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.</blockquote>
<p class="small muted" style="margin:26px 0 0">No income is guaranteed. Results depend on your effort. Crypto carries risk of loss. InstantAdPay sells advertising; it is not an investment.</p>
</div>
<footer>
<div>InstantAdPay · <a href="/my#training">back to Training</a> · <a href="/plays">team-building plays</a> · <a href="/wallets">wallets and buying POL</a></div>
<div class="small">Advertising services with a performance referral program. Not an investment product; no income guarantees.</div>
</footer>
</div>
<script src="/assets/common.js?v=20260911b"></script>
<script src="/assets/launch.js?v=20260911a"></script>
</body>
</html>
+3 -2
View File
@@ -228,6 +228,7 @@
</div> </div>
</div> </div>
<div class="nc-steps" id="ncSteps"></div> <div class="nc-steps" id="ncSteps"></div>
<p class="small" id="launchMark" hidden style="margin:10px 0 0"></p>
</div> </div>
<div class="card" id="lineTreeCard"> <div class="card" id="lineTreeCard">
<div class="card-head"><h3>Your line at a glance</h3><span class="sub" id="treeSub"></span></div> <div class="card-head"><h3>Your line at a glance</h3><span class="sub" id="treeSub"></span></div>
@@ -888,10 +889,10 @@
</div> </div>
</div> </div>
</div> </div>
<script src="/assets/common.js?v=20260911a"></script> <script src="/assets/common.js?v=20260911b"></script>
<script src="/assets/wallet.js?v=20260910a"></script> <script src="/assets/wallet.js?v=20260910a"></script>
<script src="/assets/promo.js?v=20260911a"></script> <script src="/assets/promo.js?v=20260911a"></script>
<script src="/assets/my.js?v=20260911b"></script> <script src="/assets/my.js?v=20260911c"></script>
<script src="/assets/chat.js?v=20260907l"></script> <script src="/assets/chat.js?v=20260907l"></script>
</body> </body>
</html> </html>
+2
View File
@@ -327,6 +327,7 @@ function siteConfig() {
// payment-proof Telegram feed (blank = off) and the P&L pane's fixed monthly cost // payment-proof Telegram feed (blank = off) and the P&L pane's fixed monthly cost
telegramBotToken: '', telegramChatId: '', telegramTopicId: '', telegramEvents: 'payouts', telegramCtaUrl: 'https://instantadpay.com/', telegramBotToken: '', telegramChatId: '', telegramTopicId: '', telegramEvents: 'payouts', telegramCtaUrl: 'https://instantadpay.com/',
telegramAdminChatId: '', // private chat for admin alerts (sign-up guard bursts); falls back to ADMIN_EMAIL 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 pnlFixedMonthlyUsd: 0
}, saved); }, 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 === '/shorts') return sendFile(res, path.join(PUBLIC_DIR, 'shorts.html'));
if (p === '/plays') return sendFile(res, path.join(PUBLIC_DIR, 'plays.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 === '/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')); 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); 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])); if (m) return sendFile(res, path.join(UPLOADS_DIR, m[1]));