Featured to top, centered, capped; banner ads capped 728px centered; slot CTA

- Featured card moved to the top of Overview, centered, max 760px; the rotating
  link is capped to 728px and centered (banner-sized, not a huge block).
- Served banner creatives cap at min(100%,728px), centered — kills the oversized
  sample banner; ad slots center their creative + label.
- Featured self-sell: shows "N of C slots open today" + "Feature your link →"
  that jumps to Campaigns with Featured preselected.
- Campaign target/image/video URL fields get autocomplete=off and clear when the
  Campaigns pane opens, so no residual (e.g. massifly) URL carries over.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
martbost
2026-09-07 08:08:47 -05:00
parent 3a6d9d037c
commit bc95965814
9 changed files with 34 additions and 17 deletions
+2 -1
View File
@@ -117,8 +117,9 @@ window.IAP = (function () {
const el = $(elId); const el = $(elId);
if (!ad || !el) return; if (!ad || !el) return;
if (ad.imageUrl) { if (ad.imageUrl) {
el.style.textAlign = 'center';
el.innerHTML = '<a href="' + ad.targetUrl + '" target="_blank" rel="noopener nofollow">' el.innerHTML = '<a href="' + ad.targetUrl + '" target="_blank" rel="noopener nofollow">'
+ '<img src="' + ad.imageUrl + '" alt="advertisement" style="max-width:100%;border-radius:8px"></a>' + '<img src="' + ad.imageUrl + '" alt="advertisement" style="max-width:min(100%,728px);height:auto;display:block;margin:0 auto;border-radius:8px"></a>'
+ '<div class="small muted">member ad' + reportTag(ad) + '</div>'; + '<div class="small muted">member ad' + reportTag(ad) + '</div>';
} else { } else {
el.innerHTML = '<a href="' + ad.targetUrl + '" target="_blank" rel="noopener nofollow"><b>' + ad.title + '</b>' el.innerHTML = '<a href="' + ad.targetUrl + '" target="_blank" rel="noopener nofollow"><b>' + ad.title + '</b>'
+13
View File
@@ -46,6 +46,18 @@
renderOneFeatured(); // show ONE at a time (true rotation), cycle if more than one renderOneFeatured(); // show ONE at a time (true rotation), cycle if more than one
clearInterval(featTimer); clearInterval(featTimer);
if (featItems.length > 1) featTimer = setInterval(() => { featIdx++; renderOneFeatured(); }, 6000); if (featItems.length > 1) featTimer = setInterval(() => { featIdx++; renderOneFeatured(); }, 6000);
// self-sell: today's open slots + a CTA to feature your own link
try {
const st = await (await fetch('/api/featured/stats')).json();
const today = (st.occupancy || []).find(d => d.offset === 0);
const cta = $('featCta');
if (cta) {
cta.innerHTML = (today ? '<span class="muted">' + today.open + ' of ' + today.cap + ' featured slots open today. </span>' : '')
+ '<a href="#campaigns" id="featBuy" style="color:var(--mint);font-weight:700;text-decoration:none">Feature your link →</a>';
const fb = $('featBuy');
if (fb) fb.addEventListener('click', e => { e.preventDefault(); setPane('campaigns'); const ct = $('cType'); if (ct) { ct.value = 'featured'; ct.dispatchEvent(new Event('change')); } });
}
} catch (e) {}
} catch (e) {} } catch (e) {}
} }
@@ -376,6 +388,7 @@
if (name === 'earn') setEarnSub(earnSub); // refresh whichever sub-tab is active if (name === 'earn') setEarnSub(earnSub); // refresh whichever sub-tab is active
if (name === 'profile') loadLineBanner(); if (name === 'profile') loadLineBanner();
if (name === 'line') { loadLineage(); loadUplineMessages(); } if (name === 'line') { loadLineage(); loadUplineMessages(); }
if (name === 'campaigns') ['cTarget', 'cImage', 'cVideoUrl'].forEach(id => { if ($(id)) $(id).value = ''; }); // no residual URL between visits
document.getElementById('memberArea').classList.remove('side-open'); // close mobile drawer document.getElementById('memberArea').classList.remove('side-open'); // close mobile drawer
if (location.hash !== '#' + name) history.replaceState(null, '', '#' + name); if (location.hash !== '#' + name) history.replaceState(null, '', '#' + name);
} }
+3 -1
View File
@@ -274,7 +274,9 @@ textarea{resize:vertical;font:inherit}
.feat-day .fd-occ{font-family:var(--mono);font-size:11px;color:var(--muted)} .feat-day .fd-occ{font-family:var(--mono);font-size:11px;color:var(--muted)}
.feat-day.open2 .fd-occ{color:var(--mint)} .feat-day.open2 .fd-occ{color:var(--mint)}
/* ── featured rotation strip ── */ /* ── featured rotation strip ── */
.feat-strip{display:flex;flex-direction:column;gap:8px;margin-top:10px} .feat-top{max-width:760px;margin:0 auto 16px}
.feat-strip{display:flex;justify-content:center;margin-top:10px}
.feat-strip a{max-width:728px;width:100%}
.feat-strip a{display:flex;justify-content:space-between;gap:10px;padding:11px 14px;border-radius: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} 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 a:hover{border-color:var(--mint)}
+1 -1
View File
@@ -140,7 +140,7 @@
<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> <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> </footer>
</div> </div>
<script src="/assets/common.js?v=20260907m"></script> <script src="/assets/common.js?v=20260907n"></script>
<script src="/assets/contract.js?v=20260906m"></script> <script src="/assets/contract.js?v=20260906m"></script>
<script src="/assets/chat.js?v=20260906m"></script> <script src="/assets/chat.js?v=20260906m"></script>
</body> </body>
+1 -1
View File
@@ -437,7 +437,7 @@
</div> </div>
</section> </section>
<script src="/assets/common.js?v=20260907m"></script> <script src="/assets/common.js?v=20260907n"></script>
<script src="/assets/wallet.js?v=20260907f"></script> <script src="/assets/wallet.js?v=20260907f"></script>
<script src="/assets/home.js?v=20260906m"></script> <script src="/assets/home.js?v=20260906m"></script>
<script src="/assets/chat.js?v=20260906m"></script> <script src="/assets/chat.js?v=20260906m"></script>
+1 -1
View File
@@ -36,7 +36,7 @@
<div>InstantAdPay · <a href="/">how it works</a> · <a id="contractLink" href="#" target="_blank" rel="noopener">contract source ↗</a></div> <div>InstantAdPay · <a href="/">how it works</a> · <a id="contractLink" href="#" target="_blank" rel="noopener">contract source ↗</a></div>
</footer> </footer>
</div> </div>
<script src="/assets/common.js?v=20260907m"></script> <script src="/assets/common.js?v=20260907n"></script>
<script src="/assets/ledger.js?v=20260906m"></script> <script src="/assets/ledger.js?v=20260906m"></script>
<script src="/assets/chat.js?v=20260906m"></script> <script src="/assets/chat.js?v=20260906m"></script>
</body> </body>
+11 -10
View File
@@ -4,7 +4,7 @@
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"> <meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>Member area | InstantAdPay</title> <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="https://fonts.googleapis.com/css2?family=Sora:wght@600;700;800&display=swap">
<link rel="stylesheet" href="/assets/site.css?v=20260907m"> <link rel="stylesheet" href="/assets/site.css?v=20260907n">
</head> </head>
<body class="bo-body"> <body class="bo-body">
@@ -152,6 +152,11 @@
</div> </div>
</div> </div>
<div class="card feat-top" id="featuredCard" hidden>
<div class="card-head"><h3>Featured</h3><span class="sub" id="featSub">member links in rotation</span></div>
<div id="featStrip" class="feat-strip"></div>
<div id="featCta" class="small" style="text-align:center;margin-top:10px"></div>
</div>
<div class="grid c2" style="margin-top:16px"> <div class="grid c2" style="margin-top:16px">
<div class="card"> <div class="card">
<div class="card-head"><h3>Recent payouts</h3><span class="sub" id="chEarnSub">on-chain, newest last</span><a class="sub" href="#earnings" style="color:var(--mint);text-decoration:none;margin-left:12px">full history →</a></div> <div class="card-head"><h3>Recent payouts</h3><span class="sub" id="chEarnSub">on-chain, newest last</span><a class="sub" href="#earnings" style="color:var(--mint);text-decoration:none;margin-left:12px">full history →</a></div>
@@ -190,10 +195,6 @@
</div> </div>
<div class="nc-steps" id="ncSteps"></div> <div class="nc-steps" id="ncSteps"></div>
</div> </div>
<div class="card" id="featuredCard" hidden>
<div class="card-head"><h3>Featured links</h3><span class="sub" id="featSub">member links in rotation</span></div>
<div id="featStrip" class="feat-strip"></div>
</div>
<div class="card" id="badgeCard" hidden> <div class="card" id="badgeCard" hidden>
<div class="card-head"><h3>Your achievements</h3><span class="sub">unlock as you build</span></div> <div class="card-head"><h3>Your achievements</h3><span class="sub">unlock as you build</span></div>
<div class="badges" id="badgeStrip"></div> <div class="badges" id="badgeStrip"></div>
@@ -316,11 +317,11 @@
<p><input id="cName" placeholder="Campaign name" style="width:100%"></p> <p><input id="cName" placeholder="Campaign name" style="width:100%"></p>
<p id="cBudgetRow"><input id="cBudget" type="number" placeholder="Budget (credits)" min="10" style="width:100%"></p> <p id="cBudgetRow"><input id="cBudget" type="number" placeholder="Budget (credits)" min="10" style="width:100%"></p>
</div> </div>
<p><input id="cTarget" placeholder="Target URL (https://…)" style="width:100%"></p> <p><input id="cTarget" placeholder="Target URL (https://…)" autocomplete="off" style="width:100%"></p>
<p class="small muted" style="margin-top:-6px">Banner and text targets are shown full screen in the <p class="small muted" style="margin-top:-6px">Banner and text targets are shown full screen in the
ad viewer, so those URLs must allow framing — we check them the moment you submit. Login and ad viewer, so those URLs must allow framing — we check them the moment you submit. Login and
solo links open in a fresh tab, so any working page is fine.</p> solo links open in a fresh tab, so any working page is fine.</p>
<p id="cImageRow"><input id="cImage" placeholder="Image URL (banner/login ads)" style="width:100%"></p> <p id="cImageRow"><input id="cImage" placeholder="Image URL (banner/login ads)" autocomplete="off" style="width:100%"></p>
<p id="cSizeRow" hidden><select id="cSize" style="width:100%"></select></p> <p id="cSizeRow" hidden><select id="cSize" style="width:100%"></select></p>
<p id="cTitleRow" hidden><input id="cTitle" placeholder="Headline (max 60)" style="width:100%"></p> <p id="cTitleRow" hidden><input id="cTitle" placeholder="Headline (max 60)" style="width:100%"></p>
<p id="cBodyRow" hidden><input id="cBody" placeholder="Ad text (max 140)" style="width:100%"></p> <p id="cBodyRow" hidden><input id="cBody" placeholder="Ad text (max 140)" style="width:100%"></p>
@@ -367,7 +368,7 @@
<p class="small muted" id="cFeatHint"></p> <p class="small muted" id="cFeatHint"></p>
</div> </div>
<div id="cVideoRow" hidden> <div id="cVideoRow" hidden>
<p><input id="cVideoUrl" placeholder="Video URL (direct https link ending .mp4 or .webm)" style="width:100%"></p> <p><input id="cVideoUrl" placeholder="Video URL (direct https link ending .mp4 or .webm)" autocomplete="off" style="width:100%"></p>
<p><input type="file" id="cVideoFile" accept="video/mp4,video/webm" hidden> <p><input type="file" id="cVideoFile" accept="video/mp4,video/webm" hidden>
<button type="button" class="btn small sec" id="cVideoUploadBtn">Upload video</button> <button type="button" class="btn small sec" id="cVideoUploadBtn">Upload video</button>
<span id="cVideoInfo" class="small muted"></span></p> <span id="cVideoInfo" class="small muted"></span></p>
@@ -641,9 +642,9 @@
</div> </div>
</div> </div>
</div> </div>
<script src="/assets/common.js?v=20260907m"></script> <script src="/assets/common.js?v=20260907n"></script>
<script src="/assets/wallet.js?v=20260907f"></script> <script src="/assets/wallet.js?v=20260907f"></script>
<script src="/assets/my.js?v=20260907m"></script> <script src="/assets/my.js?v=20260907n"></script>
<script src="/assets/chat.js?v=20260907l"></script> <script src="/assets/chat.js?v=20260907l"></script>
</body> </body>
</html> </html>
+1 -1
View File
@@ -33,7 +33,7 @@
<p><a href="/ledger">← Back to the live ledger</a> · <a href="/contract">Read the contract review</a></p> <p><a href="/ledger">← Back to the live ledger</a> · <a href="/contract">Read the contract review</a></p>
</div> </div>
</section> </section>
<script src="/assets/common.js?v=20260907m"></script> <script src="/assets/common.js?v=20260907n"></script>
<script src="/assets/tx.js?v=20260906m"></script> <script src="/assets/tx.js?v=20260906m"></script>
</body> </body>
</html> </html>
+1 -1
View File
@@ -36,7 +36,7 @@
</div> </div>
</div> </div>
</section> </section>
<script src="/assets/common.js?v=20260907m"></script> <script src="/assets/common.js?v=20260907n"></script>
<script src="/assets/wall.js?v=20260906m"></script> <script src="/assets/wall.js?v=20260906m"></script>
</body> </body>
</html> </html>