Unblock testing: message-your-sponsor entries, video pause-on-blur, tour polish

- Sponsor messaging is now reachable everywhere a new referral looks: a
  "Message your sponsor" card on My line, the Overview quick action, and a
  pinned "Message your sponsor" row atop the Messages drawer (even with no
  thread yet). Chat stores the sponsor from the dashboard.
- Watch-to-earn video + Shorts now PAUSE when the tab/window loses focus and
  resume on return, so a viewer must stay on the page for the watch to count.
- Welcome tour hides its frame on completion (no white about:blank panel).
  NOTE: rmcircle.team allows framing (frame-ancestors *); the blank was just
  the post-dwell blank state.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
martbost
2026-09-07 06:34:49 -05:00
parent 5b1a60f653
commit 7caff0eea2
7 changed files with 64 additions and 10 deletions
+5
View File
@@ -0,0 +1,5 @@
# Netscape HTTP Cookie File
# https://curl.se/docs/http-cookies.html
# This file was generated by libcurl! Edit at your own risk.
#HttpOnly_127.0.0.1 FALSE / FALSE 1788867258 iap.sid ef5bf189318d5c20a0d3c252de07f6c5d3b15d05c4749e71344c5e279ff8f35f
+5
View File
@@ -0,0 +1,5 @@
# Netscape HTTP Cookie File
# https://curl.se/docs/http-cookies.html
# This file was generated by libcurl! Edit at your own risk.
#HttpOnly_127.0.0.1 FALSE / FALSE 1788867258 iap.sid 83b4332fb0ddfa471e537298108a022c1c214444846494075bfa4d432ac7a683
+5
View File
@@ -0,0 +1,5 @@
# Netscape HTTP Cookie File
# https://curl.se/docs/http-cookies.html
# This file was generated by libcurl! Edit at your own risk.
#HttpOnly_127.0.0.1 FALSE / FALSE 1788866028 iap.sid 6ee49f05f7b3b754792fda5f81d92f5d79f8e560853a982c2234ceae281d552c
+30 -4
View File
@@ -769,6 +769,14 @@
$('vidStartBtn').textContent = 'Next video'; $('vidStartBtn').textContent = 'Next video';
} }
$('vidStartBtn').addEventListener('click', () => loadVideoAd()); $('vidStartBtn').addEventListener('click', () => loadVideoAd());
// presence enforcement: pause the watch-to-earn video when the tab/window loses focus,
// resume when it comes back — the viewer must stay on the page for the watch to complete
document.addEventListener('visibilitychange', () => {
const p = $('vidPlayer'); if (!p || !p.src) return;
if (document.hidden) p.pause(); else if (!vidState.done) p.play().catch(() => {});
});
window.addEventListener('blur', () => { const p = $('vidPlayer'); if (p && p.src) p.pause(); });
window.addEventListener('focus', () => { const p = $('vidPlayer'); if (p && p.src && !vidState.done) p.play().catch(() => {}); });
// ── unmissable sponsor-message modal on sign-in ── // ── unmissable sponsor-message modal on sign-in ──
function showSponsorModal(msg) { function showSponsorModal(msg) {
@@ -1215,6 +1223,7 @@
const s = g.slides[i]; const s = g.slides[i];
$('ggWho').textContent = 'Position ' + (i + 1) + ': ' + s.name + (i === 0 ? ' — the person who invited you' : ''); $('ggWho').textContent = 'Position ' + (i + 1) + ': ' + s.name + (i === 0 ? ' — the person who invited you' : '');
$('ggProgress').textContent = 'Meeting your line: ' + (i + 1) + ' of ' + g.slides.length; $('ggProgress').textContent = 'Meeting your line: ' + (i + 1) + ' of ' + g.slides.length;
$('ggFrame').hidden = false;
$('ggFrame').src = s.targetUrl; $('ggFrame').src = s.targetUrl;
let left = g.dwell || 10; let left = g.dwell || 10;
$('ggTimer').textContent = left + 's'; $('ggTimer').textContent = left + 's';
@@ -1228,6 +1237,7 @@
}); });
} }
$('ggFrame').src = 'about:blank'; $('ggFrame').src = 'about:blank';
$('ggFrame').hidden = true; // avoid a white about:blank panel once the tour is done
$('ggTimer').textContent = '✓'; $('ggTimer').textContent = '✓';
$('ggWho').textContent = 'That is your line. When you grow, they earn — and yours starts the day you share.'; $('ggWho').textContent = 'That is your line. When you grow, they earn — and yours starts the day you share.';
$('ggClaim').hidden = false; $('ggClaim').hidden = false;
@@ -1371,17 +1381,24 @@
} }
// ── SPONSOR CHAT: two-way threads, presence-aware, with a slide-in drawer ── // ── SPONSOR CHAT: two-way threads, presence-aware, with a slide-in drawer ──
let CHAT_ME = null, CHAT_OTHER = null, CHAT_LASTID = 0, CHAT_POLL = null, CHAT_CANMUTE = false, CHAT_IMUTE = false; let CHAT_ME = null, CHAT_OTHER = null, CHAT_LASTID = 0, CHAT_POLL = null, CHAT_CANMUTE = false, CHAT_IMUTE = false, CHAT_SPONSOR = null;
function chatSync(d) { function chatSync(d) {
CHAT_ME = d.email || CHAT_ME; CHAT_ME = d.email || CHAT_ME;
CHAT_SPONSOR = (d.sponsor && d.sponsor.email) ? d.sponsor : null;
const link = $('chatMenuBtn'); if (link && link.closest('.bo-links')) link.closest('.bo-links').hidden = !d.email; const link = $('chatMenuBtn'); if (link && link.closest('.bo-links')) link.closest('.bo-links').hidden = !d.email;
setChatBadge(d.chatUnread || 0); setChatBadge(d.chatUnread || 0);
const tog = $('chatAvailToggle'); if (tog) tog.checked = d.chatAvailable !== false; const tog = $('chatAvailToggle'); if (tog) tog.checked = d.chatAvailable !== false;
// Overview quick action + My line card: message-your-sponsor entry points
const qs = $('qaMsgSponsor'); const qs = $('qaMsgSponsor');
if (qs) { if (qs) {
if (d.sponsor && d.sponsor.email) { qs.hidden = false; qs.dataset.email = d.sponsor.email; qs.dataset.name = d.sponsor.name || 'your sponsor'; } if (CHAT_SPONSOR) { qs.hidden = false; qs.dataset.email = CHAT_SPONSOR.email; qs.dataset.name = CHAT_SPONSOR.name || 'your sponsor'; }
else qs.hidden = true; else qs.hidden = true;
} }
const card = $('sponsorMsgCard');
if (card) {
card.hidden = !CHAT_SPONSOR;
if (CHAT_SPONSOR && $('sponsorMsgName')) $('sponsorMsgName').textContent = CHAT_SPONSOR.name || 'your sponsor';
}
} }
function setChatBadge(n) { const b = $('chatNavBadge'); if (b) { b.hidden = !n; b.textContent = n > 9 ? '9+' : n; } } function setChatBadge(n) { const b = $('chatNavBadge'); if (b) { b.hidden = !n; b.textContent = n > 9 ? '9+' : n; } }
function stopPoll() { if (CHAT_POLL) { clearInterval(CHAT_POLL); CHAT_POLL = null; } } function stopPoll() { if (CHAT_POLL) { clearInterval(CHAT_POLL); CHAT_POLL = null; } }
@@ -1399,8 +1416,16 @@
try { try {
const r = await (await fetch('/api/my/chat/threads')).json(); const r = await (await fetch('/api/my/chat/threads')).json();
const list = r.threads || []; const list = r.threads || [];
if (!list.length) { $('chatThreads').innerHTML = '<div class="chat-empty">No conversations yet. Message your sponsor from Overview, or anyone in your line from “My line”.</div>'; return; } // always offer "message your sponsor" up top when they have one and no thread yet
$('chatThreads').innerHTML = list.map(t => const hasSponsorThread = CHAT_SPONSOR && list.some(t => t.email === CHAT_SPONSOR.email);
const sponsorRow = (CHAT_SPONSOR && !hasSponsorThread)
? '<div class="chat-thread" data-email="' + esc(CHAT_SPONSOR.email) + '" data-name="' + esc(CHAT_SPONSOR.name) + '">'
+ '<span class="pres-dot' + (CHAT_SPONSOR.online ? ' on' : '') + '"></span>'
+ '<div class="ct-main"><div class="ct-name">' + esc(CHAT_SPONSOR.name) + '</div>'
+ '<div class="ct-last">Your sponsor · tap to message</div></div></div>'
: '';
if (!list.length && !sponsorRow) { $('chatThreads').innerHTML = '<div class="chat-empty">No conversations yet. You can message anyone in your line from “My line”.</div>'; return; }
$('chatThreads').innerHTML = sponsorRow + list.map(t =>
'<div class="chat-thread" data-email="' + esc(t.email) + '" data-name="' + esc(t.name) + '">' '<div class="chat-thread" data-email="' + esc(t.email) + '" data-name="' + esc(t.name) + '">'
+ '<span class="pres-dot' + (t.online ? ' on' : '') + '"></span>' + '<span class="pres-dot' + (t.online ? ' on' : '') + '"></span>'
+ '<div class="ct-main"><div class="ct-name">' + esc(t.name) + '</div>' + '<div class="ct-main"><div class="ct-name">' + esc(t.name) + '</div>'
@@ -1491,6 +1516,7 @@
inp.addEventListener('keydown', e => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); chatSend(); } }); inp.addEventListener('keydown', e => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); chatSend(); } });
} }
on('qaMsgSponsor', 'click', () => { const q = $('qaMsgSponsor'); if (q.dataset.email) openConvo(q.dataset.email, q.dataset.name || 'your sponsor'); }); on('qaMsgSponsor', 'click', () => { const q = $('qaMsgSponsor'); if (q.dataset.email) openConvo(q.dataset.email, q.dataset.name || 'your sponsor'); });
on('lineMsgSponsor', 'click', () => { if (CHAT_SPONSOR) openConvo(CHAT_SPONSOR.email, CHAT_SPONSOR.name || 'your sponsor'); });
on('chatAvailToggle', 'change', async () => { on('chatAvailToggle', 'change', async () => {
const t = $('chatAvailToggle'); const t = $('chatAvailToggle');
try { await api('/api/my/chat/available', { available: t.checked }); IAP.status(t.checked ? 'You are available to chat with your line.' : 'Live chat off. People can still leave you a note.', 'ok'); } try { await api('/api/my/chat/available', { available: t.checked }); IAP.status(t.checked ? 'You are available to chat with your line.' : 'Live chat off. People can still leave you a note.', 'ok'); }
+7
View File
@@ -53,6 +53,13 @@
$('shNext').hidden = false; $('shNext').hidden = false;
} }
$('shNext').addEventListener('click', load); $('shNext').addEventListener('click', load);
// presence enforcement: pause when the tab/window loses focus, resume on return
document.addEventListener('visibilitychange', () => {
const v = $('shVideo'); if (!v || !v.src) return;
if (document.hidden) v.pause(); else if (!st.done) v.play().catch(() => {});
});
window.addEventListener('blur', () => { const v = $('shVideo'); if (v && v.src) v.pause(); });
window.addEventListener('focus', () => { const v = $('shVideo'); if (v && v.src && !st.done) v.play().catch(() => {}); });
// tap the video to pause/resume // tap the video to pause/resume
$('shVideo').addEventListener('click', () => { const v = $('shVideo'); if (v.paused) v.play(); else v.pause(); }); $('shVideo').addEventListener('click', () => { const v = $('shVideo'); if (v.paused) v.play(); else v.pause(); });
load(); load();
+11 -5
View File
@@ -4,7 +4,7 @@
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"> <meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>Member area | InstantAdPay</title> <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="https://fonts.googleapis.com/css2?family=Sora:wght@600;700;800&display=swap">
<link rel="stylesheet" href="/assets/site.css?v=20260907d"> <link rel="stylesheet" href="/assets/site.css?v=20260907e">
</head> </head>
<body class="bo-body"> <body class="bo-body">
@@ -222,6 +222,12 @@
</div> </div>
<div class="pane" id="pane-line" hidden> <div class="pane" id="pane-line" hidden>
<div class="card" id="sponsorMsgCard" hidden>
<h3>Your sponsor has your back</h3>
<p class="muted small">Questions getting started? Message your sponsor <b id="sponsorMsgName"></b> directly.
If they are online you chat live; if not, it lands in their inbox and they are notified.</p>
<p><button class="btn" id="lineMsgSponsor" type="button">Message your sponsor</button></p>
</div>
<div class="card"> <div class="card">
<h3>Your invite link, live from day one</h3> <h3>Your invite link, live from day one</h3>
<p class="muted small">Share it anywhere, starting now. Everyone who joins through it becomes part of <p class="muted small">Share it anywhere, starting now. Everyone who joins through it becomes part of
@@ -627,9 +633,9 @@
</div> </div>
</div> </div>
</div> </div>
<script src="/assets/common.js?v=20260907d"></script> <script src="/assets/common.js?v=20260907e"></script>
<script src="/assets/wallet.js?v=20260907d"></script> <script src="/assets/wallet.js?v=20260907e"></script>
<script src="/assets/my.js?v=20260907d"></script> <script src="/assets/my.js?v=20260907e"></script>
<script src="/assets/chat.js?v=20260907d"></script> <script src="/assets/chat.js?v=20260907e"></script>
</body> </body>
</html> </html>
+1 -1
View File
@@ -43,6 +43,6 @@
</div> </div>
</div> </div>
</div> </div>
<script src="/assets/shorts.js?v=20260907c"></script> <script src="/assets/shorts.js?v=20260907e"></script>
</body> </body>
</html> </html>