A session pop-up that says what InstantAdPay actually is

The dashboard card was too easy to scroll past, so this is a real overlay: once
per browser session, on every page that loads common.js, dismissed with the X,
Escape or the backdrop.

It leads with the mechanic rather than the promotion, because the ask was to
remind people what the site is about: the ad spend in your line pays you, in the
same transaction. Five Dollar Friday rides underneath as the reason to act, and
the copy is driven by the live /api/friday, so it reads "tomorrow", "today" or
nothing at all without anyone editing text on a Friday morning.

Not shown on /join or /admin: an overlay in the middle of signing up costs
sign-ups, and staff do not need reminding what the product is.

QA: the overlay appears after an async fetch, so hiding it at load time missed
it and every click in the harness timed out. The harness now pre-sets the same
sessionStorage flag a real dismissal uses, before page scripts run. Green again,
0 bugs.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
martbost
2026-09-24 15:54:33 -05:00
parent fd08af36ba
commit 3f9dc3cc94
19 changed files with 3206 additions and 3087 deletions
+1 -1
View File
@@ -510,7 +510,7 @@
</div>
</div>
<script src="/assets/common.js?v=20260923b"></script>
<script src="/assets/common.js?v=20260923b"></script><script src="/assets/welcome.js?v=20260923b"></script>
<script src="/assets/admin.js?v=20260923b"></script>
</body>
</html>
+115
View File
@@ -0,0 +1,115 @@
/* InstantAdPay session pop-up (Marty, 2026-09-24).
One card, once per browser session, on every page that loads common.js. It exists because the
dashboard card was too easy to scroll past: this one is a proper overlay you have to dismiss.
It leads with WHAT THE SITE IS, not the promotion, because the ask was "remind them what it is
about". Five Dollar Friday rides along underneath as the reason to act this week, and the card
reads the live /api/friday so it says "tomorrow", "today" or nothing at all without anyone
editing copy.
Rules it follows:
- once per SESSION, not per day: sessionStorage, so closing the tab resets it
- a new `key` (change of promo, new campaign) shows it again even in the same session
- never on the sign-up or sign-in flow: an overlay in the middle of joining costs sign-ups
- Escape and the backdrop both close it, and focus is trapped while it is open
- if sessionStorage is unavailable (private mode) it simply shows once and does not throw
*/
(function () {
var KEY = 'iap.welcome.v1';
// Pages where an overlay would get in the way of something more important.
// /join is the sign-up flow and /admin is staff only; an overlay on either is pure friction.
var SKIP = /^\/(join|signup|register|login|auth|admin)(\/|$)/i;
function seen() { try { return sessionStorage.getItem(KEY) === '1'; } catch (e) { return false; } }
function mark() { try { sessionStorage.setItem(KEY, '1'); } catch (e) {} }
function fridayLine(f) {
if (!f || !f.on) return null;
if (f.live) return { tag: 'TODAY', text: 'It is Five Dollar Friday right now. Every package of $5 or more gets <b>20% bonus credits</b>, automatically, until midnight Central.' };
if (f.nextLabel) return { tag: f.nextLabel, text: 'Five Dollar Friday: any package of $5 or more gets <b>' + (f.pct || 20) + '% bonus credits</b>, automatically. No code, nothing to claim.' };
return null;
}
function show(f) {
if (document.querySelector('.iap-welcome')) return;
var fri = fridayLine(f);
var back = document.createElement('div');
back.className = 'iap-welcome';
back.setAttribute('role', 'dialog');
back.setAttribute('aria-modal', 'true');
back.setAttribute('aria-label', 'About InstantAdPay');
back.style.cssText = 'position:fixed;inset:0;z-index:400;background:rgba(2,6,5,.72);' +
'display:flex;align-items:center;justify-content:center;padding:18px;overflow:auto;' +
'-webkit-backdrop-filter:blur(3px);backdrop-filter:blur(3px);';
var card = document.createElement('div');
card.style.cssText = 'position:relative;width:100%;max-width:470px;background:#0b1512;color:#eef7f3;' +
'border:1px solid rgba(67,232,195,.28);border-radius:18px;padding:26px 24px 22px;' +
'box-shadow:0 26px 70px rgba(0,0,0,.6);font-size:15px;line-height:1.55;';
var html =
'<button type="button" class="iap-w-x" aria-label="Close" style="position:absolute;top:10px;right:12px;' +
'background:none;border:0;color:#8ba69c;font-size:26px;line-height:1;cursor:pointer;padding:4px 8px;">&times;</button>' +
'<div style="letter-spacing:2px;text-transform:uppercase;font-size:11px;font-weight:700;color:#43e8c3;">InstantAdPay</div>' +
'<h2 style="margin:8px 0 12px;font-size:25px;line-height:1.25;color:#fff;">The ad spend in your line pays you, in the same transaction.</h2>' +
'<p style="margin:0 0 14px;color:#c8d6e2;">You buy advertising you were going to buy anyway. When someone in your line buys a package, the contract splits it and your share lands in your wallet before the page reloads. No back office, no payday to wait for.</p>' +
'<ul style="margin:0 0 16px;padding-left:18px;color:#c8d6e2;">' +
'<li style="margin-bottom:5px;">Every payout is on the public ledger, and you can read it yourself.</li>' +
'<li style="margin-bottom:5px;">Free members earn ad credits by viewing ads, videos and visits.</li>' +
'<li>Credits buy your own advertising. They are delivery, not a balance to hoard.</li>' +
'</ul>';
if (fri) {
html += '<div style="margin:0 0 16px;padding:12px 14px;border:1px solid rgba(255,178,56,.38);' +
'background:rgba(255,178,56,.08);border-radius:12px;color:#eef7f3;">' +
'<div style="font-weight:800;color:#ffb238;font-size:12px;letter-spacing:1.4px;text-transform:uppercase;margin-bottom:4px;">' + fri.tag + '</div>' +
'<div>' + fri.text + '</div></div>';
}
html += '<div style="display:flex;gap:9px;flex-wrap:wrap;">' +
'<a class="iap-w-go" href="/my#buy" style="flex:1 1 auto;text-align:center;text-decoration:none;background:linear-gradient(180deg,#5ff0cf,#43e8c3);color:#04120e;font-weight:800;padding:12px 16px;border-radius:11px;">See the packages</a>' +
'<a class="iap-w-go" href="/ledger" style="flex:0 1 auto;text-align:center;text-decoration:none;border:1px solid rgba(67,232,195,.4);color:#43e8c3;font-weight:700;padding:12px 16px;border-radius:11px;">Live ledger</a>' +
'</div>';
card.innerHTML = html;
back.appendChild(card);
document.body.appendChild(back);
mark();
var prev = document.activeElement;
var closeBtn = card.querySelector('.iap-w-x');
function close() {
back.remove();
document.removeEventListener('keydown', onKey);
try { if (prev && prev.focus) prev.focus(); } catch (e) {}
}
function onKey(e) {
if (e.key === 'Escape') { close(); return; }
if (e.key !== 'Tab') return;
var f = card.querySelectorAll('button,a[href]');
if (!f.length) return;
var first = f[0], last = f[f.length - 1];
if (e.shiftKey && document.activeElement === first) { e.preventDefault(); last.focus(); }
else if (!e.shiftKey && document.activeElement === last) { e.preventDefault(); first.focus(); }
}
closeBtn.addEventListener('click', close);
back.addEventListener('click', function (e) { if (e.target === back) close(); });
// following a link is a deliberate exit, so let it through but tidy up
card.querySelectorAll('.iap-w-go').forEach(function (a) { a.addEventListener('click', close); });
document.addEventListener('keydown', onKey);
try { closeBtn.focus(); } catch (e) {}
}
function boot() {
if (seen() || SKIP.test(location.pathname)) return;
// the Friday state decides one paragraph; a failure there must not stop the card
fetch('/api/friday').then(function (r) { return r.ok ? r.json() : null; })
.catch(function () { return null; })
.then(function (f) { show(f); });
}
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', boot);
else boot();
})();
+1 -1
View File
@@ -141,7 +141,7 @@
<div class="small">Advertising services with a performance referral program. Not an investment product; no income guarantees. Crypto transactions are irreversible. Never spend what you cannot afford.</div>
</footer>
</div>
<script src="/assets/common.js?v=20260923b"></script>
<script src="/assets/common.js?v=20260923b"></script><script src="/assets/welcome.js?v=20260923b"></script>
<script src="/assets/contract.js?v=20260908p"></script>
<script src="/assets/chat.js?v=20260906m"></script>
</body>
+1 -1
View File
@@ -26,7 +26,7 @@
<p class="muted small">You decide whether, and how much, to spend. Never spend more than you can afford to lose.</p>
</div>
</div></section>
<script src="/assets/common.js?v=20260923b"></script>
<script src="/assets/common.js?v=20260923b"></script><script src="/assets/welcome.js?v=20260923b"></script>
<script src="/assets/legal.js?v=20260908a"></script>
</body>
</html>
+1 -1
View File
@@ -83,6 +83,6 @@
<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=20260923b"></script>
<script src="/assets/common.js?v=20260923b"></script><script src="/assets/welcome.js?v=20260923b"></script>
</body>
</html>
+1 -1
View File
@@ -495,7 +495,7 @@
</div>
</section>
<script src="/assets/common.js?v=20260923b"></script>
<script src="/assets/common.js?v=20260923b"></script><script src="/assets/welcome.js?v=20260923b"></script>
<script src="/assets/wallet.js?v=20260909c"></script>
<script src="/assets/home.js?v=20260920fdf"></script>
<script src="/assets/chat.js?v=20260906m"></script>
+1 -1
View File
@@ -157,7 +157,7 @@
InstantAdPay · <a href="/contract">Contract</a> · <a href="/terms">Terms</a> · <a href="/privacy">Privacy</a> · <a href="/disclaimer">Disclaimer</a>
</div>
</div>
<script src="/assets/common.js?v=20260923b"></script>
<script src="/assets/common.js?v=20260923b"></script><script src="/assets/welcome.js?v=20260923b"></script>
<script src="/assets/join.js?v=20260914a"></script>
</body>
</html>
+1 -1
View File
@@ -104,7 +104,7 @@
<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=20260923b"></script>
<script src="/assets/common.js?v=20260923b"></script><script src="/assets/welcome.js?v=20260923b"></script>
<script src="/assets/launch.js?v=20260915b"></script>
</body>
</html>
+1 -1
View File
@@ -39,7 +39,7 @@
<div>InstantAdPay · <a href="/">how it works</a> · <a id="contractLink" href="#" target="_blank" rel="noopener">contract source ↗</a></div>
</footer>
</div>
<script src="/assets/common.js?v=20260923b"></script>
<script src="/assets/common.js?v=20260923b"></script><script src="/assets/welcome.js?v=20260923b"></script>
<script src="/assets/ledger.js?v=20260906m"></script>
<script src="https://polhunter.com/embed.js" data-slots="1" async></script>
<script src="/assets/chat.js?v=20260906m"></script>
+1 -1
View File
@@ -1070,7 +1070,7 @@
</div>
</div>
</div>
<script src="/assets/common.js?v=20260923b"></script>
<script src="/assets/common.js?v=20260923b"></script><script src="/assets/welcome.js?v=20260923b"></script>
<script src="/assets/wallet.js?v=20260911a"></script>
<script src="/assets/promo.js?v=20260921d"></script>
<script src="/assets/my.js?v=20260923d"></script>
+1 -1
View File
@@ -128,7 +128,7 @@
<div class="small">Advertising services with a performance referral program. Not an investment product; no income guarantees. Crypto transactions are irreversible. Never spend what you cannot afford to lose.</div>
</footer>
</div>
<script src="/assets/common.js?v=20260923b"></script>
<script src="/assets/common.js?v=20260923b"></script><script src="/assets/welcome.js?v=20260923b"></script>
<script src="/assets/partners.js?v=20260914a"></script>
</body>
</html>
+1 -1
View File
@@ -192,7 +192,7 @@
<div class="small">Advertising services with a performance referral program. Not an investment product; no income guarantees. Crypto transactions are irreversible. Never spend what you cannot afford.</div>
</footer>
</div>
<script src="/assets/common.js?v=20260923b"></script>
<script src="/assets/common.js?v=20260923b"></script><script src="/assets/welcome.js?v=20260923b"></script>
<script src="/assets/plays.js?v=20260910b"></script>
</body>
</html>
+1 -1
View File
@@ -29,7 +29,7 @@
<p class="muted small">We use reasonable safeguards, but no system is perfectly secure. Protect your email and your wallet.</p>
</div>
</div></section>
<script src="/assets/common.js?v=20260923b"></script>
<script src="/assets/common.js?v=20260923b"></script><script src="/assets/welcome.js?v=20260923b"></script>
<script src="/assets/legal.js?v=20260908a"></script>
</body>
</html>
+1 -1
View File
@@ -37,7 +37,7 @@
<p class="muted small" style="margin-top:18px">See also the <a href="/disclaimer">Disclaimer</a> and <a href="/privacy">Privacy Policy</a>.</p>
</div>
</div></section>
<script src="/assets/common.js?v=20260923b"></script>
<script src="/assets/common.js?v=20260923b"></script><script src="/assets/welcome.js?v=20260923b"></script>
<script src="/assets/legal.js?v=20260908a"></script>
</body>
</html>
+1 -1
View File
@@ -34,7 +34,7 @@
<p><a href="/ledger">← Back to the live ledger</a> · <a href="/contract">Read the contract review</a></p>
</div>
</section>
<script src="/assets/common.js?v=20260923b"></script>
<script src="/assets/common.js?v=20260923b"></script><script src="/assets/welcome.js?v=20260923b"></script>
<script src="/assets/tx.js?v=20260906m"></script>
</body>
</html>
+1 -1
View File
@@ -47,7 +47,7 @@
</div>
</div>
</section>
<script src="/assets/common.js?v=20260923b"></script>
<script src="/assets/common.js?v=20260923b"></script><script src="/assets/welcome.js?v=20260923b"></script>
<script src="/assets/wall.js?v=20260911v"></script>
</body>
</html>
+1 -1
View File
@@ -121,7 +121,7 @@
<div class="small">Advertising services with a performance referral program. Not an investment product; no income guarantees. Crypto transactions are irreversible. Never share your recovery phrase.</div>
</footer>
</div>
<script src="/assets/common.js?v=20260923b"></script>
<script src="/assets/common.js?v=20260923b"></script><script src="/assets/welcome.js?v=20260923b"></script>
<script src="/assets/wallets.js?v=20260911b"></script>
</body>
</html>
+2 -1
View File
@@ -19,6 +19,7 @@ const lines = []; const log = (...a) => { const s = a.join(' '); console.log(s);
const problems = [];
const browser = await chromium.launch();
const ctx = await browser.newContext({ viewport: { width: 1280, height: 950 } });
await ctx.addInitScript(() => { try { sessionStorage.setItem('iap.welcome.v1', '1'); } catch (e) {} }); // the session pop-up would block every click in an automated run
const page = await ctx.newPage();
page.on('dialog', d => d.accept());
const api = async (p, body) => (await page.request.fetch(B + p, body ? { method: 'POST', data: body } : {})).json();
@@ -40,7 +41,7 @@ for (const sd of seeds) {
await page.goto(B + '/my', { waitUntil: 'networkidle' });
await page.fill('#mcEmail', 'qa-earn@example.com'); await page.click('#mcSendBtn'); await page.waitForSelector('#mcVerifyBtn:not([hidden])'); await page.click('#mcVerifyBtn'); await page.waitForTimeout(2000);
if (await page.$('#onboardModal:not([hidden])')) { await page.fill('#obUsername', 'qaearner'); await page.click('#obSave'); await page.waitForTimeout(1000); }
await page.evaluate(() => { document.querySelectorAll('.modal-back,.lgate').forEach(m => m.hidden = true); });
await page.evaluate(() => { document.querySelectorAll('.modal-back,.lgate,.iap-welcome').forEach(m => m.hidden = true); });
const start = await api('/api/my/earn'); log('start:', JSON.stringify(start));
// Watch ads
+4 -1
View File
@@ -51,12 +51,13 @@ async function domChecks(page, label) {
if (r.overflow) note('warn', label, 'page scrolls horizontally');
return r;
}
const hide = page => page.evaluate(() => { document.querySelectorAll('.modal-back,.lgate').forEach(m => m.hidden = true); });
const hide = page => page.evaluate(() => { document.querySelectorAll('.modal-back,.lgate,.iap-welcome').forEach(m => m.hidden = true); });
const browser = await chromium.launch();
if (MODE === 'public' || MODE === 'all') {
const ctx = await browser.newContext({ viewport: { width: 1280, height: 900 } });
await ctx.addInitScript(() => { try { sessionStorage.setItem('iap.welcome.v1', '1'); } catch (e) {} }); // the session pop-up would block every click in an automated run
const page = await ctx.newPage(); const bag = watch(page, LIVE);
const PUBLIC = ['/', '/ledger', '/contract', '/terms', '/privacy', '/disclaimer', '/wall/martbost', '/join/martbost',
'/join/martbost?v=instant', '/join/martbost?v=adspend', '/join/martbost?v=free', '/join/martbost?v=ledger', '/join/martbost?v=two',
@@ -79,6 +80,7 @@ if (MODE === 'public' || MODE === 'all') {
} catch (e) { note('bug', label, 'navigation failed: ' + e.message.slice(0, 160)); flush(bag, label); }
}
const m = await browser.newContext({ viewport: { width: 390, height: 844 }, isMobile: true });
await m.addInitScript(() => { try { sessionStorage.setItem('iap.welcome.v1', '1'); } catch (e) {} }); // the session pop-up would block every click in an automated run
const mp = await m.newPage(); const mbag = watch(mp, LIVE);
for (const p of ['/', '/join/martbost?v=instant', '/wall/martbost', '/my']) {
await mp.goto(LIVE + p, { waitUntil: 'domcontentloaded', timeout: 45000 }).catch(e => note('bug', 'LIVE mobile ' + p, e.message));
@@ -90,6 +92,7 @@ if (MODE === 'public' || MODE === 'all') {
if (MODE === 'member' || MODE === 'all') {
const ctx = await browser.newContext({ viewport: { width: 1280, height: 950 } });
await ctx.addInitScript(() => { try { sessionStorage.setItem('iap.welcome.v1', '1'); } catch (e) {} }); // the session pop-up would block every click in an automated run
const page = await ctx.newPage(); const bag = watch(page, LOCAL);
page.on('dialog', d => d.accept());
const L = 'LOCAL ';