Login ads as post-signin interstitials: Open Ad in new tab, timer here, Go to Dashboard

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
martbost
2026-09-05 16:28:57 -05:00
parent 2cc2855488
commit 1c31679b6c
8 changed files with 106 additions and 16 deletions
+52 -3
View File
@@ -372,7 +372,7 @@
});
$('cType').addEventListener('change', () => {
const t = $('cType').value;
$('cImageRow').hidden = t === 'text' || t === 'solo';
$('cImageRow').hidden = t !== 'banner'; // only banners carry a creative; login frames its URL
$('cTitleRow').hidden = t !== 'text' && t !== 'solo';
$('cBodyRow').hidden = t !== 'text';
$('cSoloRow').hidden = t !== 'solo';
@@ -644,6 +644,7 @@
$('mcVerifyBtn').addEventListener('click', busy($('mcVerifyBtn'), async () => {
await api('/api/auth/email/verify', { email: $('mcEmail').value, code: $('mcCode').value });
IAP.status('You are in.', 'ok');
await showLoginAd();
await render();
}));
})();
@@ -656,6 +657,7 @@
$('loginBtn').addEventListener('click', busy($('loginBtn'), async () => {
await api('/api/login', { email: $('liEmail').value, password: $('liPass').value });
IAP.status('Logged in.', 'ok');
await showLoginAd();
await render();
}));
$('walletSigninLink').addEventListener('click', async e => {
@@ -664,6 +666,7 @@
IAP.status('Check your wallet for the free sign-in signature…');
await IAPWallet.signIn();
IAP.status('Signed in with your wallet.', 'ok');
await showLoginAd();
await render();
} catch (err) { IAP.status((err && err.message) || String(err), 'bad'); }
});
@@ -692,9 +695,55 @@
await render();
});
// ad surfaces: login ads greet the sign-in screen; members see live
// ── login ad interstitial (ClickBaitPays pattern): after a successful
// sign-in the sponsor card appears; "Open Ad" opens the CTA link in a NEW
// tab (a real, counted click) while the timer counts down on THIS page —
// deliberately not paused, the member is expected to be in the ad tab.
// At zero, "Go to dashboard" appears.
async function showLoginAd() {
try {
const { ad } = await (await fetch('/api/ads/slot?type=login')).json();
if (!ad || !ad.targetUrl) return;
const gate = $('loginGate');
const openBtn = $('lgOpen');
const goBtn = $('lgContinue');
const timer = $('lgTimer');
$('lgCreative').innerHTML = ad.imageUrl
? '<img src="' + ad.imageUrl + '" alt="sponsor ad">'
: '<span class="lg-linkcard">' + (ad.title ? String(ad.title).replace(/[&<>]/g, '') : 'Visit today\'s sponsor') + '</span>';
$('lgStatus').textContent = 'Login ad sponsor — click "Open Ad" to begin.';
timer.hidden = true;
goBtn.hidden = true;
openBtn.disabled = false;
gate.hidden = false;
await new Promise(done => {
openBtn.onclick = () => {
window.open(ad.targetUrl, '_blank'); // the click relay counts it
openBtn.disabled = true;
$('lgStatus').textContent = 'Ad open in a new tab. View it and come back — the timer runs here.';
let left = ad.dwell || 10;
timer.hidden = false;
timer.textContent = 'Time remaining: ' + left + 's';
const t = setInterval(() => {
left -= 1;
if (left > 0) { timer.textContent = 'Time remaining: ' + left + 's'; return; }
clearInterval(t);
timer.textContent = 'Time is up';
timer.classList.add('done');
$('lgStatus').textContent = 'Thanks for the look. Your dashboard is ready.';
goBtn.hidden = false;
}, 1000);
goBtn.onclick = () => done();
};
});
gate.hidden = true;
$('lgTimer').classList.remove('done');
} catch (e) {}
}
// ad surfaces: a banner greets the sign-in screen; members see live
// banner + text placements inside the back office (they ARE the audience)
IAP.adSlot('login', 'adSlotLogin');
IAP.adSlot('banner', 'adSlotLogin');
IAP.adSlot('banner', 'adSlotOverview');
IAP.adSlot('text', 'adSlotSide');