diff --git a/ads.js b/ads.js
index 9827cda..99d511b 100644
Binary files a/ads.js and b/ads.js differ
diff --git a/db.js b/db.js
index f08b51e..07d0ce1 100644
--- a/db.js
+++ b/db.js
@@ -114,6 +114,7 @@ async function bootstrap() {
await alterSafe('ALTER TABLE campaigns ADD COLUMN height INT NULL'); // banner size (IAB) → also NAS height
await alterSafe('ALTER TABLE campaigns ADD COLUMN nas_ad_id INT NULL'); // syndicated NAS sponsorads.ID
await alterSafe('ALTER TABLE campaigns ADD COLUMN nas_served INT NOT NULL DEFAULT 0'); // NAS impressions already reconciled into spend
+ await alterSafe('ALTER TABLE campaigns ADD COLUMN expires BIGINT NULL'); // featured rotation end time
await q(`CREATE TABLE IF NOT EXISTS sponsor_messages (
id INT AUTO_INCREMENT PRIMARY KEY,
from_member INT NOT NULL,
diff --git a/public/assets/my.js b/public/assets/my.js
index e88bd5c..b009319 100644
--- a/public/assets/my.js
+++ b/public/assets/my.js
@@ -26,6 +26,20 @@
if (d.buyerCount < 5) return 'Level 2 is open. ' + (5 - d.buyerCount) + ' more qualifying buyer(s) unlock level 3 and the full three-level flow.';
return 'Fully qualified. Every level pays you, and you catch the pass-ups that under-qualified positions below you let slip. Keep sharing and keep your campaigns running.';
}
+ // featured rotation strip on the overview
+ async function loadFeatured() {
+ try {
+ const r = await (await fetch('/api/featured')).json();
+ const card = $('featuredCard'); if (!card) return;
+ if (!r.items || !r.items.length) { card.hidden = true; return; }
+ card.hidden = false;
+ $('featSub').textContent = r.items.length + ' link' + (r.items.length === 1 ? '' : 's') + ' in rotation';
+ $('featStrip').innerHTML = r.items.map(i =>
+ '' + esc(i.title) + ''
+ + (i.by ? '' + esc(i.by) + '' : '') + '').join('');
+ } catch (e) {}
+ }
+
// achievement badges: the milestone ladder as unlockable medals + share-to-image
const BADGES = [
{ key: 'payouts', label: 'Payouts On', sub: 'wallet linked', icon: '' },
@@ -212,6 +226,7 @@
setInboxBadge(d.inboxUnread || 0);
if (d.sponsorMsg) showSponsorModal(d.sponsorMsg);
renderBadges(d);
+ loadFeatured();
loadCharts(d);
$('nextMove').textContent = nextMove(d);
renderSteps(d);
@@ -493,10 +508,25 @@
$('cSoloRow').hidden = t !== 'solo';
$('cSoloHint').hidden = t !== 'solo';
$('cVideoRow').hidden = t !== 'video';
+ $('cFeaturedRow').hidden = t !== 'featured';
$('cTitle').placeholder = t === 'solo' ? 'Subject line (max 80)' : 'Headline (max 60)';
soloHint();
if (t === 'video') videoHint();
+ if (t === 'featured') featHint();
});
+ function featHint() {
+ if (!lastRates || !lastRates.featuredDurations) return;
+ if ($('cFeatDays') && !$('cFeatDays').options.length)
+ $('cFeatDays').innerHTML = lastRates.featuredDurations.map(dys =>
+ '').join('');
+ fetch('/api/featured/stats').then(r => r.json()).then(s => {
+ const dys = Number($('cFeatDays').value) || (lastRates.featuredDurations[0]);
+ $('cFeatHint').textContent = (s.activeCount || 0) + ' link' + (s.activeCount === 1 ? '' : 's')
+ + ' currently share the rotation — yours would make ' + ((s.activeCount || 0) + 1)
+ + '. Runs ' + dys + ' day' + (dys > 1 ? 's' : '') + ' for ' + (dys * lastRates.featuredPerDay) + ' credits.';
+ }).catch(() => {});
+ }
+ document.addEventListener('change', e => { if (e.target && e.target.id === 'cFeatDays') featHint(); });
// video composer: tier dropdown + upload + price hint
function videoHint() {
if (!lastRates || !lastRates.videoTiers) return;
@@ -529,14 +559,16 @@
// in raw mode the source of truth is the textarea; sync it back first
let soloBody = $('cSoloEd').innerHTML;
if ($('cSoloRaw') && !$('cSoloRaw').hidden) soloBody = $('cSoloRaw').value;
- const isVideo = $('cType').value === 'video';
- await api('/api/my/campaigns', { type: $('cType').value, name: $('cName').value,
+ const t = $('cType').value;
+ const isVideo = t === 'video', isFeat = t === 'featured';
+ await api('/api/my/campaigns', { type: t, name: $('cName').value,
targetUrl: $('cTarget').value, imageUrl: $('cImage').value, size: $('cSize').value,
- title: isVideo ? $('cVideoTitle').value : $('cTitle').value,
- body: $('cType').value === 'solo' ? soloBody : $('cBody').value,
+ title: isVideo ? $('cVideoTitle').value : isFeat ? $('cFeatTitle').value : $('cTitle').value,
+ body: t === 'solo' ? soloBody : $('cBody').value,
videoUrl: $('cVideoUrl').value, watchSecs: Number($('cWatchSecs').value),
+ days: Number($('cFeatDays').value),
ctaLabel: isVideo ? $('cVideoCta').value : $('cCtaLabel').value,
- budget: Number($('cBudget').value) });
+ budget: isFeat ? (Number($('cFeatDays').value) * (lastRates.featuredPerDay || 40)) : Number($('cBudget').value) });
IAP.status('Campaign is live. It starts serving right away.', 'ok');
$('cName').value = ''; $('cBudget').value = '';
$('cSoloEd').innerHTML = ''; $('cSoloRaw').value = ''; $('cCtaLabel').value = '';
diff --git a/public/assets/site.css b/public/assets/site.css
index c0dd4d4..df82ebd 100644
--- a/public/assets/site.css
+++ b/public/assets/site.css
@@ -259,6 +259,12 @@ textarea{resize:vertical;font:inherit}
#boBurger{display:block}
.bo-content{padding:18px}
}
+/* ── featured rotation strip ── */
+.feat-strip{display:flex;flex-direction:column;gap:8px;margin-top:10px}
+.feat-strip a{display:flex;justify-content:space-between;gap:10px;padding:11px 14px;border-radius:10px;
+ background:var(--panel);border:1px solid var(--line-strong);color:var(--ink);text-decoration:none;font-weight:600}
+.feat-strip a:hover{border-color:var(--mint)}
+.feat-strip .by{font-size:12px;color:var(--muted);font-weight:500;white-space:nowrap}
/* ── achievement badges ── */
.badges{display:flex;gap:16px;flex-wrap:wrap;margin-top:12px}
.badge-a{display:flex;flex-direction:column;align-items:center;gap:8px;width:120px;text-align:center}
diff --git a/public/contract.html b/public/contract.html
index d721155..d112fca 100644
--- a/public/contract.html
+++ b/public/contract.html
@@ -5,7 +5,7 @@
The contract | InstantAdPay
-
+
@@ -129,8 +129,8 @@
Advertising services with a performance referral program. Not an investment product; no income guarantees. Crypto transactions are irreversible. Never spend what you cannot afford.