Featured rotation format: duration-priced link rotation with disclosed dilution

- New 'featured' type: headline + link + 1/2/7 day run, flat up-front price (40cr/day)
- Composer discloses dilution (N links share the rotation, yours makes N+1) before buying
- Overview featured strip shows the live rotation; expires automatically

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
martbost
2026-09-06 08:58:22 -05:00
parent b25b4c67fe
commit 37a2b97bfd
12 changed files with 90 additions and 31 deletions
+37 -5
View File
@@ -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 =>
'<a href="' + i.url + '" target="_blank" rel="noopener nofollow"><span>' + esc(i.title) + '</span>'
+ (i.by ? '<span class="by">' + esc(i.by) + '</span>' : '') + '</a>').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: '<circle cx="12" cy="12" r="8"/><path d="M9 12l2 2 4-4"/>' },
@@ -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 =>
'<option value="' + dys + '">' + dys + ' day' + (dys > 1 ? 's' : '') + ' — ' + (dys * lastRates.featuredPerDay) + ' credits</option>').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 = '';