diff --git a/ads.js b/ads.js
index a610cf9..4fe759a 100644
Binary files a/ads.js and b/ads.js differ
diff --git a/public/assets/my.js b/public/assets/my.js
index 8ab9fee..88e190f 100644
--- a/public/assets/my.js
+++ b/public/assets/my.js
@@ -80,12 +80,19 @@
const c = document.createElement('canvas'); c.width = img.width; c.height = img.height;
const x = c.getContext('2d');
x.drawImage(img, 0, 0);
- if (who) { // the blank ribbon sits at ~76% down; name it in dark ink on the mint banner
+ if (who) { // name it on the blank ribbon: gold with a dark outline so it reads on any badge color
x.textAlign = 'center';
- x.fillStyle = '#04231b';
- x.font = 'bold ' + Math.round(c.width * 0.052) + 'px Sora, "Segoe UI", sans-serif';
- x.fillText(who, c.width / 2, c.height * 0.78);
+ x.textBaseline = 'middle';
+ x.font = 'bold ' + Math.round(c.width * 0.055) + 'px Sora, "Segoe UI", sans-serif';
+ const y = c.height * 0.82;
+ x.lineJoin = 'round';
+ x.lineWidth = Math.max(3, Math.round(c.width * 0.008));
+ x.strokeStyle = 'rgba(4,20,15,.9)';
+ x.strokeText(who, c.width / 2, y);
+ x.fillStyle = '#ffd15c';
+ x.fillText(who, c.width / 2, y);
x.textAlign = 'left';
+ x.textBaseline = 'alphabetic';
}
try {
c.toBlob(bl => {
@@ -563,6 +570,13 @@
document.addEventListener('change', e => { if (e.target && e.target.id === 'cWatchSecs') videoHint(); });
$('cBudget').addEventListener('input', () => { if ($('cType').value === 'video') videoHint(); });
$('cVideoUploadBtn').addEventListener('click', () => $('cVideoFile').click());
+ $('cVideoUrl').addEventListener('change', async () => {
+ const url = $('cVideoUrl').value.trim();
+ $('cVideoInfo').textContent = url ? 'checking video…' : '';
+ cVidDims = url ? await probeVideoDims(url) : null;
+ if (url && !cVidDims) $('cVideoInfo').textContent = 'could not read that video';
+ else { $('cVideoInfo').textContent = ''; showVidOrient(); }
+ });
$('cVideoFile').addEventListener('change', async () => {
const f = $('cVideoFile').files[0];
if (!f) return;
@@ -574,6 +588,7 @@
$('cVideoInfo').textContent = f.name + ' uploaded';
$('cVideoPrev').hidden = false;
$('cVideoPrev').innerHTML = '';
+ cVidDims = await probeVideoDims(r.url); showVidOrient();
} catch (e) { $('cVideoInfo').textContent = 'Upload failed. Try again.'; }
$('cVideoFile').value = '';
});
@@ -583,11 +598,13 @@
if ($('cSoloRaw') && !$('cSoloRaw').hidden) soloBody = $('cSoloRaw').value;
const t = $('cType').value;
const isVideo = t === 'video', isFeat = t === 'featured';
+ if (isVideo && !cVidDims && $('cVideoUrl').value) cVidDims = await probeVideoDims($('cVideoUrl').value);
await api('/api/my/campaigns', { type: t, name: $('cName').value,
targetUrl: $('cTarget').value, imageUrl: $('cImage').value, size: $('cSize').value,
title: isVideo ? $('cVideoTitle').value : isFeat ? $('cFeatTitle').value : t === 'visits' ? $('cVisitTitle').value : $('cTitle').value,
body: t === 'solo' ? soloBody : $('cBody').value,
videoUrl: $('cVideoUrl').value, watchSecs: Number($('cWatchSecs').value),
+ videoW: cVidDims ? cVidDims.w : null, videoH: cVidDims ? cVidDims.h : null,
days: Number($('cFeatDays').value), startDay: featStartDay,
count: Number($('cVisitCount').value),
ctaLabel: isVideo ? $('cVideoCta').value : $('cCtaLabel').value,
@@ -672,9 +689,27 @@
// ── watch-to-earn videos: escape-proof player, server-clock reward ──
const vidState = { token: null, secs: 0, maxSeen: 0, done: false, credited: false };
+ // detected dimensions of the video being created — portrait goes to Shorts, landscape to Watch videos
+ let cVidDims = null;
+ function probeVideoDims(url) {
+ return new Promise(resolve => {
+ if (!url) return resolve(null);
+ const v = document.createElement('video'); v.preload = 'metadata'; v.muted = true;
+ let done = false; const fin = r => { if (!done) { done = true; resolve(r); } };
+ v.onloadedmetadata = () => fin(v.videoWidth && v.videoHeight ? { w: v.videoWidth, h: v.videoHeight } : null);
+ v.onerror = () => fin(null); setTimeout(() => fin(null), 8000); v.src = url;
+ });
+ }
+ function showVidOrient() {
+ const el = $('cVideoInfo'); if (!el) return;
+ if (!cVidDims) return;
+ const portrait = cVidDims.h > cVidDims.w;
+ el.textContent = (el.textContent ? el.textContent + ' · ' : '')
+ + (portrait ? 'portrait — shows in the Shorts reel' : 'landscape — shows in the Watch videos tab');
+ }
async function loadVideoStatus() {
try {
- const st = await (await fetch('/api/my/videos')).json();
+ const st = await (await fetch('/api/my/videos?orientation=landscape')).json();
if (st.error) return;
$('vidProgress').textContent = 'today: ' + (st.status.count || 0) + ' / ' + st.status.cap + ' videos watched';
if (st.status.left <= 0) {
@@ -687,7 +722,7 @@
}
async function loadVideoAd() {
let r = null;
- try { r = await (await fetch('/api/my/videos')).json(); } catch (e) {}
+ try { r = await (await fetch('/api/my/videos?orientation=landscape')).json(); } catch (e) {}
if (!r || !r.ad) {
$('vidBox').innerHTML = '' + (r && r.status && r.status.left <= 0
? 'That is today\'s video set. Come back tomorrow.'
diff --git a/public/assets/shorts.js b/public/assets/shorts.js
index a749df7..ff69b7e 100644
--- a/public/assets/shorts.js
+++ b/public/assets/shorts.js
@@ -3,7 +3,7 @@
// 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 };
+ 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) }
@@ -13,7 +13,7 @@
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) {}
+ 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) {
@@ -25,6 +25,13 @@
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;
diff --git a/public/my.html b/public/my.html
index c92a38e..172ddcd 100644
--- a/public/my.html
+++ b/public/my.html
@@ -4,7 +4,7 @@
Member area | InstantAdPay
-
+
@@ -627,9 +627,9 @@
-
-
-
-
+
+
+
+