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:
+52
-3
@@ -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');
|
||||
|
||||
|
||||
@@ -295,6 +295,21 @@ textarea{resize:vertical;font:inherit}
|
||||
.donut-legend{display:flex;flex-direction:column;gap:8px;font-size:13px}
|
||||
.donut-legend i{display:inline-block;width:10px;height:10px;border-radius:3px;margin-right:8px}
|
||||
.donut-center{font-family:var(--disp);font-weight:800}
|
||||
/* ── login ad interstitial: sponsor opens in a new tab, timer runs here ── */
|
||||
.lgate{position:fixed;inset:0;z-index:90;display:flex;flex-direction:column;background:var(--ground)}
|
||||
.lgate-bar{display:flex;align-items:center;gap:14px;padding:10px 16px;background:var(--panel-solid);
|
||||
border-bottom:1px solid var(--line);flex-wrap:wrap}
|
||||
.lg-brand{font-family:var(--disp);font-weight:800;font-size:15px;white-space:nowrap}
|
||||
.lg-brand em{color:var(--mint);font-style:normal}
|
||||
.lgate-note{flex:1;min-width:140px}
|
||||
.lg-timer{font-weight:700;font-size:14px;border:1px solid var(--line-strong);border-radius:999px;
|
||||
padding:5px 14px;min-width:150px;text-align:center}
|
||||
.lg-timer.done{color:var(--mint);border-color:var(--mint)}
|
||||
.lgate-body{flex:1;display:grid;place-items:center;padding:24px;overflow:auto}
|
||||
.lgate-card{max-width:560px;text-align:center}
|
||||
.lgate-card img{max-width:100%;max-height:50vh;border-radius:12px;border:1px solid var(--line-strong)}
|
||||
#lgCreative .lg-linkcard{display:inline-block;background:var(--panel);border:1px solid var(--line-strong);
|
||||
border-radius:14px;padding:22px 28px;font-family:var(--disp);font-weight:700;font-size:18px;overflow-wrap:anywhere}
|
||||
/* ── solo composer: toolbar + contenteditable editor ── */
|
||||
.ed-bar{display:flex;gap:6px;flex-wrap:wrap;margin-bottom:8px}
|
||||
.ed-bar button{background:var(--panel);color:var(--ink);border:1px solid var(--line-strong);border-radius:8px;
|
||||
|
||||
Reference in New Issue
Block a user