Featured rotation -> day-slot booking: 10 slots/day, pick your start day, live occupancy
- Per-day occupancy shown in composer (Today 1/10, Tomorrow 2/10, …), full days unpickable - Book a specific start day + duration; every covered day must have an open slot - Slot cap turns dilution disclosure into a hard ceiling; featured skips frame-check (new-tab) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+20
-8
@@ -514,17 +514,29 @@
|
||||
if (t === 'video') videoHint();
|
||||
if (t === 'featured') featHint();
|
||||
});
|
||||
function featHint() {
|
||||
let featStartDay = 0; // selected start-day offset (0 = today)
|
||||
async 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(() => {});
|
||||
let s = null;
|
||||
try { s = await (await fetch('/api/featured/stats')).json(); } catch (e) {}
|
||||
if (s && s.occupancy) {
|
||||
const grid = $('cFeatDaysGrid');
|
||||
grid.innerHTML = s.occupancy.map(d => {
|
||||
const label = d.offset === 0 ? 'Today' : d.offset === 1 ? 'Tomorrow' : new Date(d.day + 'T00:00:00Z').toLocaleDateString(undefined, { weekday: 'short', month: 'numeric', day: 'numeric' });
|
||||
const full = d.open <= 0;
|
||||
return '<div class="feat-day' + (d.offset === featStartDay ? ' on' : '') + (full ? ' full' : d.open > d.cap / 2 ? ' open2' : '') + '" data-off="' + d.offset + '"' + (full ? '' : '') + '>'
|
||||
+ '<div class="fd-day">' + label + '</div><div class="fd-occ">' + d.count + '/' + d.cap + (full ? ' full' : ' left ' + d.open) + '</div></div>';
|
||||
}).join('');
|
||||
grid.querySelectorAll('.feat-day:not(.full)').forEach(el =>
|
||||
el.addEventListener('click', () => { featStartDay = Number(el.dataset.off); featHint(); }));
|
||||
}
|
||||
const dys = Number($('cFeatDays').value) || (lastRates.featuredDurations[0]);
|
||||
const dayLabel = featStartDay === 0 ? 'today' : featStartDay === 1 ? 'tomorrow' : 'in ' + featStartDay + ' days';
|
||||
$('cFeatHint').textContent = 'Runs ' + dys + ' day' + (dys > 1 ? 's' : '') + ' starting ' + dayLabel
|
||||
+ ' for ' + (dys * lastRates.featuredPerDay) + ' credits. Max ' + (s ? s.slotsPerDay : 10) + ' links share any day.';
|
||||
}
|
||||
document.addEventListener('change', e => { if (e.target && e.target.id === 'cFeatDays') featHint(); });
|
||||
// video composer: tier dropdown + upload + price hint
|
||||
@@ -566,7 +578,7 @@
|
||||
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),
|
||||
days: Number($('cFeatDays').value), startDay: featStartDay,
|
||||
ctaLabel: isVideo ? $('cVideoCta').value : $('cCtaLabel').value,
|
||||
budget: isFeat ? (Number($('cFeatDays').value) * (lastRates.featuredPerDay || 40)) : Number($('cBudget').value) });
|
||||
IAP.status('Campaign is live. It starts serving right away.', 'ok');
|
||||
|
||||
@@ -259,6 +259,15 @@ textarea{resize:vertical;font:inherit}
|
||||
#boBurger{display:block}
|
||||
.bo-content{padding:18px}
|
||||
}
|
||||
/* ── featured day-slot picker ── */
|
||||
.feat-days{display:flex;gap:8px;flex-wrap:wrap}
|
||||
.feat-day{flex:0 0 auto;min-width:82px;padding:8px 10px;border:1px solid var(--line-strong);border-radius:10px;
|
||||
background:var(--panel);cursor:pointer;text-align:center}
|
||||
.feat-day.on{border-color:var(--mint);box-shadow:0 0 0 1px var(--mint)}
|
||||
.feat-day.full{opacity:.45;cursor:not-allowed}
|
||||
.feat-day .fd-day{font-weight:700;font-size:12px}
|
||||
.feat-day .fd-occ{font-family:var(--mono);font-size:11px;color:var(--muted)}
|
||||
.feat-day.open2 .fd-occ{color:var(--mint)}
|
||||
/* ── 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;
|
||||
|
||||
Reference in New Issue
Block a user