010e8d7ffc
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
76 lines
4.3 KiB
JavaScript
76 lines
4.3 KiB
JavaScript
// Paid shorts: a full-screen vertical feed over the video-ad inventory. You must
|
|
// watch each short's required time (server-clock enforced, no seek) to earn,
|
|
// then advance to the next. Reuses /api/my/videos + /api/my/videowatch.
|
|
(function () {
|
|
const $ = id => document.getElementById(id);
|
|
const st = { token: null, secs: 0, maxSeen: 0, credited: false, done: false, skips: 0 };
|
|
function j(url, body) {
|
|
return fetch(url, body
|
|
? { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) }
|
|
: undefined).then(r => r.json());
|
|
}
|
|
async function load() {
|
|
st.token = null; st.maxSeen = 0; st.credited = false; st.done = false;
|
|
$('shOver').hidden = true; $('shCta').hidden = true; $('shNext').hidden = true;
|
|
let r = null;
|
|
// signed-out visitors get the sign-in note without a 401 round trip
|
|
let me = null; try { me = await j('/api/me'); } catch (e) {}
|
|
if (me && me.signedIn && me.email) { try { r = await j('/api/my/videos?orientation=portrait'); } catch (e) {} }
|
|
if (!r || r.error) { $('shVideo').hidden = true; $('shMsg').hidden = false; $('shMsg').textContent = 'Sign in on the dashboard to watch shorts and earn.'; return; }
|
|
$('shStat').textContent = 'today: ' + (r.status.count || 0) + ' / ' + r.status.cap;
|
|
if (!r.ad) {
|
|
$('shVideo').hidden = true; $('shMsg').hidden = false;
|
|
$('shMsg').textContent = r.status.left <= 0 ? 'That\'s today\'s shorts. Come back tomorrow.' : 'No shorts in rotation right now. Check back soon.';
|
|
return;
|
|
}
|
|
st.token = r.token; st.secs = r.ad.watchSecs; st.adId = r.ad.id;
|
|
const v = $('shVideo');
|
|
$('shMsg').hidden = true; v.hidden = false;
|
|
v.src = r.ad.videoUrl; v.currentTime = 0;
|
|
// safety net: this reel is portrait-only. If a landscape video slips through, skip to the next.
|
|
v.onloadedmetadata = () => {
|
|
if (v.videoWidth && v.videoHeight && v.videoWidth > v.videoHeight) {
|
|
if (st.skips++ < 4) { load(); return; }
|
|
v.hidden = true; $('shMsg').hidden = false; $('shMsg').textContent = 'No shorts in rotation right now. Check back soon.';
|
|
} else { st.skips = 0; }
|
|
};
|
|
$('shTitle').textContent = r.ad.title || '';
|
|
$('shCta').href = r.ad.ctaUrl; $('shCta').textContent = r.ad.ctaLabel || 'Learn more';
|
|
$('shOver').hidden = false; $('shCta').hidden = false;
|
|
v.onseeking = () => { if (v.currentTime > st.maxSeen + 0.5) v.currentTime = st.maxSeen; };
|
|
v.ontimeupdate = () => {
|
|
if (v.currentTime > st.maxSeen) st.maxSeen = v.currentTime;
|
|
const left = Math.max(0, Math.ceil(st.secs - st.maxSeen));
|
|
$('shTimer').textContent = left > 0 ? 'Watch ' + left + 's more to earn' : 'Earned — swipe to the next';
|
|
if (!st.done && st.maxSeen >= st.secs) { st.done = true; credit(); }
|
|
};
|
|
v.play().catch(() => {});
|
|
}
|
|
async function credit() {
|
|
if (st.credited) return; st.credited = true;
|
|
try {
|
|
const r = await j('/api/my/videowatch', { token: st.token });
|
|
if (r.credited) { $('shStat').textContent = 'today: ' + (r.status.count || 0) + ' / ' + (r.status.cap || 0) + ' · +' + r.credited; }
|
|
} catch (e) {}
|
|
$('shNext').hidden = false;
|
|
}
|
|
$('shNext').addEventListener('click', load);
|
|
$('shReport').addEventListener('click', e => {
|
|
e.preventDefault(); if (!st.adId) return;
|
|
const reason = (prompt('Report this short: broken, inappropriate, spam, scam, or other', 'inappropriate') || '').trim().toLowerCase();
|
|
if (!reason) return;
|
|
fetch('/api/report-ad', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ campaignId: st.adId, reason }) })
|
|
.then(() => { $('shReport').textContent = '✓ reported — thanks'; }).catch(() => {});
|
|
});
|
|
// 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
|
|
$('shVideo').addEventListener('click', () => { const v = $('shVideo'); if (v.paused) v.play(); else v.pause(); });
|
|
load();
|
|
})();
|