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
+30 -4
View File
@@ -769,6 +769,14 @@
$('vidStartBtn').textContent = 'Next video';
}
$('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 ──
function showSponsorModal(msg) {
@@ -1215,6 +1223,7 @@
const s = g.slides[i];
$('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;
$('ggFrame').hidden = false;
$('ggFrame').src = s.targetUrl;
let left = g.dwell || 10;
$('ggTimer').textContent = left + 's';
@@ -1228,6 +1237,7 @@
});
}
$('ggFrame').src = 'about:blank';
$('ggFrame').hidden = true; // avoid a white about:blank panel once the tour is done
$('ggTimer').textContent = '✓';
$('ggWho').textContent = 'That is your line. When you grow, they earn — and yours starts the day you share.';
$('ggClaim').hidden = false;
@@ -1371,17 +1381,24 @@
}
// ── 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) {
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;
setChatBadge(d.chatUnread || 0);
const tog = $('chatAvailToggle'); if (tog) tog.checked = d.chatAvailable !== false;
// Overview quick action + My line card: message-your-sponsor entry points
const qs = $('qaMsgSponsor');
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;
}
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 stopPoll() { if (CHAT_POLL) { clearInterval(CHAT_POLL); CHAT_POLL = null; } }
@@ -1399,8 +1416,16 @@
try {
const r = await (await fetch('/api/my/chat/threads')).json();
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; }
$('chatThreads').innerHTML = list.map(t =>
// always offer "message your sponsor" up top when they have one and no thread yet
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) + '">'
+ '<span class="pres-dot' + (t.online ? ' on' : '') + '"></span>'
+ '<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(); } });
}
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 () => {
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'); }