diff --git a/public/assets/shorts.js b/public/assets/shorts.js new file mode 100644 index 0000000..a749df7 --- /dev/null +++ b/public/assets/shorts.js @@ -0,0 +1,52 @@ +// 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 }; + 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; + try { r = await j('/api/my/videos'); } 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; + const v = $('shVideo'); + $('shMsg').hidden = true; v.hidden = false; + v.src = r.ad.videoUrl; v.currentTime = 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); + // tap the video to pause/resume + $('shVideo').addEventListener('click', () => { const v = $('shVideo'); if (v.paused) v.play(); else v.pause(); }); + load(); +})(); diff --git a/public/my.html b/public/my.html index 46fea02..f13d526 100644 --- a/public/my.html +++ b/public/my.html @@ -394,7 +394,8 @@

Watch videos, earn credits

Watch a member's video for its required time and earn credits. The player - can't be skipped — the timer runs on our server. You never see your own videos.

+ can't be skipped — the timer runs on our server. You never see your own videos. + Prefer a feed? Open Shorts for a full-screen, swipe-to-next experience.

…

diff --git a/public/shorts.html b/public/shorts.html new file mode 100644 index 0000000..7eb5406 --- /dev/null +++ b/public/shorts.html @@ -0,0 +1,48 @@ + + + + +Shorts | InstantAdPay + + + + + +
+
+ InstantAdPay · Shorts + … + Done +
+
+
Loading your first short…
+ + +
+
+ + + diff --git a/server.js b/server.js index 65ce34e..a217ae4 100644 --- a/server.js +++ b/server.js @@ -939,6 +939,7 @@ const server = http.createServer(async (req, res) => { if (p === '/ledger') return sendFile(res, path.join(PUBLIC_DIR, 'ledger.html')); if (p === '/contract') return sendFile(res, path.join(PUBLIC_DIR, 'contract.html')); if (p === '/my') return sendFile(res, path.join(PUBLIC_DIR, 'my.html')); + if (p === '/shorts') return sendFile(res, path.join(PUBLIC_DIR, 'shorts.html')); if (/^\/view\/[a-f0-9]{32}$/.test(p)) return sendFile(res, path.join(PUBLIC_DIR, 'view.html')); m = /^\/uploads\/([a-z0-9]{24}\.(?:png|jpg|webp|gif|mp4|webm))$/.exec(p); if (m) return sendFile(res, path.join(UPLOADS_DIR, m[1]));