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:
martbost
2026-09-06 09:16:24 -05:00
parent c5dd5c685b
commit f30a462ca8
13 changed files with 62 additions and 38 deletions
+20 -8
View File
@@ -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');
+9
View File
@@ -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;
+4 -4
View File
@@ -5,7 +5,7 @@
<title>The contract | InstantAdPay</title>
<meta name="description" content="Plain-language review of the InstantAdPay settlement contract: what it does, what nobody can change, what the operator can and cannot touch, and how to verify all of it yourself.">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Sora:wght@600;700;800&display=swap">
<link rel="stylesheet" href="/assets/site.css?v=20260906e">
<link rel="stylesheet" href="/assets/site.css?v=20260906f">
</head>
<body>
<div class="wrap">
@@ -129,8 +129,8 @@
<div class="small">Advertising services with a performance referral program. Not an investment product; no income guarantees. Crypto transactions are irreversible. Never spend what you cannot afford.</div>
</footer>
</div>
<script src="/assets/common.js?v=20260906e"></script>
<script src="/assets/contract.js?v=20260906e"></script>
<script src="/assets/chat.js?v=20260906e"></script>
<script src="/assets/common.js?v=20260906f"></script>
<script src="/assets/contract.js?v=20260906f"></script>
<script src="/assets/chat.js?v=20260906f"></script>
</body>
</html>
+5 -5
View File
@@ -5,7 +5,7 @@
<title>InstantAdPay: advertise and earn, locked in code</title>
<meta name="description" content="Real ad packages with instant on-chain settlement. Every purchase pays the sponsor line in the same transaction, verifiable by anyone on the live ledger.">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Sora:wght@600;700;800&display=swap">
<link rel="stylesheet" href="/assets/site.css?v=20260906e">
<link rel="stylesheet" href="/assets/site.css?v=20260906f">
</head>
<body>
@@ -410,9 +410,9 @@
</div>
</section>
<script src="/assets/common.js?v=20260906e"></script>
<script src="/assets/wallet.js?v=20260906e"></script>
<script src="/assets/home.js?v=20260906e"></script>
<script src="/assets/chat.js?v=20260906e"></script>
<script src="/assets/common.js?v=20260906f"></script>
<script src="/assets/wallet.js?v=20260906f"></script>
<script src="/assets/home.js?v=20260906f"></script>
<script src="/assets/chat.js?v=20260906f"></script>
</body>
</html>
+4 -4
View File
@@ -5,7 +5,7 @@
<title>Live ledger | InstantAdPay</title>
<meta name="description" content="Every purchase, payout, and pass-up on InstantAdPay, streamed straight from the blockchain with a verify link on every line.">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Sora:wght@600;700;800&display=swap">
<link rel="stylesheet" href="/assets/site.css?v=20260906e">
<link rel="stylesheet" href="/assets/site.css?v=20260906f">
</head>
<body>
<div class="wrap">
@@ -25,8 +25,8 @@
<div>InstantAdPay · <a href="/">how it works</a> · <a id="contractLink" href="#" target="_blank" rel="noopener">contract source ↗</a></div>
</footer>
</div>
<script src="/assets/common.js?v=20260906e"></script>
<script src="/assets/ledger.js?v=20260906e"></script>
<script src="/assets/chat.js?v=20260906e"></script>
<script src="/assets/common.js?v=20260906f"></script>
<script src="/assets/ledger.js?v=20260906f"></script>
<script src="/assets/chat.js?v=20260906f"></script>
</body>
</html>
+8 -6
View File
@@ -4,7 +4,7 @@
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>Member area | InstantAdPay</title>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Sora:wght@600;700;800&display=swap">
<link rel="stylesheet" href="/assets/site.css?v=20260906e">
<link rel="stylesheet" href="/assets/site.css?v=20260906f">
</head>
<body class="bo-body">
@@ -344,7 +344,9 @@
<p class="small muted" id="cSoloHint" hidden></p>
<div id="cFeaturedRow" hidden>
<p><input id="cFeatTitle" maxlength="70" placeholder="Headline for your featured link" style="width:100%"></p>
<p><select id="cFeatDays" style="width:100%"></select></p>
<p class="small muted" style="margin-bottom:4px">Pick a start day (occupancy shown per day):</p>
<div id="cFeatDaysGrid" class="feat-days"></div>
<p style="margin-top:10px"><select id="cFeatDays" style="width:100%"></select></p>
<p class="small muted" id="cFeatHint"></p>
</div>
<div id="cVideoRow" hidden>
@@ -556,9 +558,9 @@
</div>
</div>
<script src="/assets/common.js?v=20260906e"></script>
<script src="/assets/wallet.js?v=20260906e"></script>
<script src="/assets/my.js?v=20260906e"></script>
<script src="/assets/chat.js?v=20260906e"></script>
<script src="/assets/common.js?v=20260906f"></script>
<script src="/assets/wallet.js?v=20260906f"></script>
<script src="/assets/my.js?v=20260906f"></script>
<script src="/assets/chat.js?v=20260906f"></script>
</body>
</html>
+2 -2
View File
@@ -4,7 +4,7 @@
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>Shorts | InstantAdPay</title>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Sora:wght@600;700;800&display=swap">
<link rel="stylesheet" href="/assets/site.css?v=20260906e">
<link rel="stylesheet" href="/assets/site.css?v=20260906f">
<style>
html,body{height:100%;margin:0;overflow:hidden;background:#000}
.sh{position:fixed;inset:0;display:flex;flex-direction:column;background:#000;color:var(--ink,#e8fff7)}
@@ -43,6 +43,6 @@
</div>
</div>
</div>
<script src="/assets/shorts.js?v=20260906e"></script>
<script src="/assets/shorts.js?v=20260906f"></script>
</body>
</html>
+3 -3
View File
@@ -4,7 +4,7 @@
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>Transaction | InstantAdPay</title>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Sora:wght@600;700;800&display=swap">
<link rel="stylesheet" href="/assets/site.css?v=20260906e">
<link rel="stylesheet" href="/assets/site.css?v=20260906f">
</head>
<body>
<div id="nav"></div>
@@ -33,7 +33,7 @@
<p><a href="/ledger">← Back to the live ledger</a> · <a href="/contract">Read the contract review</a></p>
</div>
</section>
<script src="/assets/common.js?v=20260906e"></script>
<script src="/assets/tx.js?v=20260906e"></script>
<script src="/assets/common.js?v=20260906f"></script>
<script src="/assets/tx.js?v=20260906f"></script>
</body>
</html>
+2 -2
View File
@@ -4,7 +4,7 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Viewing ad — InstantAdPay</title>
<link rel="stylesheet" href="/assets/site.css?v=20260906e">
<link rel="stylesheet" href="/assets/site.css?v=20260906f">
<style>
html,body{height:100%;margin:0;overflow:hidden}
.vw{display:flex;flex-direction:column;height:100vh;height:100dvh;background:var(--bg,#04110c);color:var(--ink,#e8fff7)}
@@ -43,6 +43,6 @@
</div>
<iframe class="vframe" id="vFrame" sandbox="allow-scripts allow-same-origin allow-forms allow-popups" referrerpolicy="no-referrer" title="Advertiser site"></iframe>
</div>
<script src="/assets/view.js?v=20260906e"></script>
<script src="/assets/view.js?v=20260906f"></script>
</body>
</html>
+3 -3
View File
@@ -4,7 +4,7 @@
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>Banner wall | InstantAdPay</title>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Sora:wght@600;700;800&display=swap">
<link rel="stylesheet" href="/assets/site.css?v=20260906e">
<link rel="stylesheet" href="/assets/site.css?v=20260906f">
</head>
<body>
<div id="nav"></div>
@@ -34,7 +34,7 @@
</div>
</div>
</section>
<script src="/assets/common.js?v=20260906e"></script>
<script src="/assets/wall.js?v=20260906e"></script>
<script src="/assets/common.js?v=20260906f"></script>
<script src="/assets/wall.js?v=20260906f"></script>
</body>
</html>