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');
+15
View File
@@ -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;
+3 -1
View File
@@ -221,7 +221,9 @@
<div class="platecard">
<div class="plate"><svg viewBox="0 0 24 24"><circle cx="12" cy="9" r="3.5"/><path d="M5 20c1.5-3.5 4-5 7-5s5.5 1.5 7 5"/><rect x="3" y="3" width="18" height="18" rx="3"/></svg></div>
<h3>Login ads <span class="badge">live</span></h3>
<p>Full attention at sign-in, priced per day. The member is already engaged before they do anything else.</p>
<p>The moment a member signs in, your ad is the toll gate: they open your page in a fresh tab
while a countdown holds their dashboard. Just a link is enough — add a banner if you have
one. Priced per day.</p>
</div>
<div class="platecard">
<div class="plate"><svg viewBox="0 0 24 24"><path d="M3 7l9 6 9-6"/><rect x="3" y="5" width="18" height="14" rx="2"/></svg></div>
+26 -6
View File
@@ -4,7 +4,7 @@
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>Member area | InstantAdPay</title>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Sora:wght@600;700;800&display=swap">
<link rel="stylesheet" href="/assets/site.css?v=20260905o">
<link rel="stylesheet" href="/assets/site.css?v=20260905q">
</head>
<body class="bo-body">
@@ -48,6 +48,25 @@
</div>
</div>
<!-- ── login ad interstitial: sponsor opens in a new tab, timer runs here ── -->
<div id="loginGate" class="lgate" hidden>
<div class="lgate-bar">
<span class="lg-brand">Instant<em>AdPay</em></span>
<span class="lgate-note small muted" id="lgStatus">Login ad sponsor — click "Open Ad" to begin.</span>
<span class="lg-timer mono" id="lgTimer" hidden>…</span>
<button class="btn small" id="lgContinue" type="button" hidden>Go to dashboard</button>
</div>
<div class="lgate-body">
<div class="lgate-card">
<p class="eyebrow">Today's login sponsor</p>
<div id="lgCreative"></div>
<p style="margin-top:18px"><button class="btn" id="lgOpen" type="button">Open Ad</button></p>
<p class="small muted">The ad opens in a new tab. The timer counts down right here —
when it hits zero, your dashboard button appears above.</p>
</div>
</div>
</div>
<!-- ── signed-in: back-office shell ──────────────────────── -->
<div id="memberArea" class="bo" hidden>
<aside class="bo-side" id="boSide">
@@ -218,7 +237,8 @@
</div>
<p><input id="cTarget" placeholder="Target URL (https://…)" style="width:100%"></p>
<p class="small muted" style="margin-top:-6px">Banner and text targets are shown full screen in the
ad viewer, so the URL must allow framing — we check it the moment you submit.</p>
ad viewer, so those URLs must allow framing — we check them the moment you submit. Login and
solo links open in a fresh tab, so any working page is fine.</p>
<p id="cImageRow"><input id="cImage" placeholder="Image URL (banner/login ads)" style="width:100%"></p>
<p id="cTitleRow" hidden><input id="cTitle" placeholder="Headline (max 60)" style="width:100%"></p>
<p id="cBodyRow" hidden><input id="cBody" placeholder="Ad text (max 140)" style="width:100%"></p>
@@ -379,9 +399,9 @@
</div>
</div>
<script src="/assets/common.js?v=20260905o"></script>
<script src="/assets/wallet.js?v=20260905o"></script>
<script src="/assets/my.js?v=20260905o"></script>
<script src="/assets/chat.js?v=20260905o"></script>
<script src="/assets/common.js?v=20260905q"></script>
<script src="/assets/wallet.js?v=20260905q"></script>
<script src="/assets/my.js?v=20260905q"></script>
<script src="/assets/chat.js?v=20260905q"></script>
</body>
</html>
+2 -2
View File
@@ -4,7 +4,7 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Viewing ad — InstantAdPay</title>
<link rel="stylesheet" href="/assets/site.css?v=20260905o">
<link rel="stylesheet" href="/assets/site.css?v=20260905q">
<style>
html,body{height:100%;margin:0;overflow:hidden}
.vw{display:flex;flex-direction:column;height:100vh;height:100dvh;background:var(--bg,#04110c);color:var(--ink,#e8fff7)}
@@ -43,6 +43,6 @@
</div>
<iframe class="vframe" id="vFrame" sandbox="allow-scripts allow-same-origin allow-forms allow-popups" referrerpolicy="no-referrer" title="Advertiser site"></iframe>
</div>
<script src="/assets/view.js?v=20260905o"></script>
<script src="/assets/view.js?v=20260905q"></script>
</body>
</html>