Files
instantadpay/public/assets/welcome.js
T
martbost 3f9dc3cc94 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>
2026-09-24 15:54:33 -05:00

116 lines
6.7 KiB
JavaScript

/* 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();
})();