Wall page: mint QR, achievement badge, admin-ad backfill, join gated on views

- QR is mint-green (still high-contrast for reliable scanning).
- The wall shows the owner's achievement badge next to their avatar.
- Positions 2 & 3 fill with upline line-banners, then ADMIN ADS when there's no
  upline (configurable via data/admin-wall-ads.json; sensible default) so the
  wall is never sparse.
- "Join free through this wall" is disabled until the visitor has viewed every
  ad on the wall, with a "X/Y viewed" prompt.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
martbost
2026-09-07 08:35:44 -05:00
parent a4119d652d
commit 0dbd99f69e
11 changed files with 59 additions and 21 deletions
+2
View File
@@ -376,6 +376,8 @@ textarea{resize:vertical;font:inherit}
/* ── bio header (wall/profile page) ── */
.bio-head{display:flex;gap:22px;align-items:center;flex-wrap:wrap;padding:8px 0 4px}
.bio-avatar{width:96px;height:96px;border-radius:50%;object-fit:cover;border:2px solid var(--mint);flex:0 0 auto}
.bio-badge{width:74px;height:74px;border-radius:12px;object-fit:cover;flex:0 0 auto;border:1px solid var(--line-strong)}
.btn.disabled{opacity:.45;pointer-events:none;filter:grayscale(.3)}
.bio-meta{flex:1;min-width:220px}
.bio-socials{display:flex;gap:8px;flex-wrap:wrap;margin-top:12px}
.bio-socials a{font-size:12px;font-weight:700;color:var(--mint);border:1px solid var(--line-strong);
+13 -2
View File
@@ -16,6 +16,7 @@
title.innerHTML = safeName;
IAP.$('bioHead').hidden = false;
if (w.avatarUrl) { const av = IAP.$('bioAvatar'); av.src = w.avatarUrl; av.hidden = false; }
if (w.badge) { const bb = IAP.$('bioBadge'); if (bb) { bb.src = w.badge.img; bb.title = w.badge.label + ' badge'; bb.hidden = false; } }
IAP.$('bioText').textContent = w.bio || 'Building a team on InstantAdPay — join through this page and you\'re in my line.';
if (w.qrUrl) IAP.$('bioQr').src = w.qrUrl;
// social links
@@ -27,7 +28,6 @@
sc.innerHTML = links.join('');
}
IAP.$('wallCtaHead').textContent = 'Join ' + w.name + '’s line';
IAP.$('wallJoin').href = w.joinUrl;
// per-wall viewed-position memory (survives revisits; anonymous-friendly)
const VKEY = 'iap-wall-' + name;
let viewed = {};
@@ -35,6 +35,16 @@
const DWELL = 10;
const grid = IAP.$('wallGrid');
grid.innerHTML = '';
// join is gated: you must view every ad on the wall before you can join
const joinBtn = IAP.$('wallJoin'), gate = IAP.$('wallGate');
function updateGate() {
const viewable = w.ladder.filter(m => m.targetUrl).length;
const seen = w.ladder.filter((m, i) => m.targetUrl && viewed[i]).length;
if (viewable > 0 && seen < viewable) {
joinBtn.classList.add('disabled'); joinBtn.removeAttribute('href');
if (gate) { gate.hidden = false; gate.textContent = 'View all ' + viewable + ' ads above to unlock joining (' + seen + '/' + viewable + ' viewed).'; }
} else { joinBtn.href = w.joinUrl; joinBtn.classList.remove('disabled'); if (gate) gate.hidden = true; }
}
w.ladder.forEach((m, i) => {
const d = document.createElement('div');
d.className = 'wall-card';
@@ -64,11 +74,12 @@
clearInterval(t);
viewed[i] = 1;
try { localStorage.setItem(VKEY, JSON.stringify(viewed)); } catch (e) {}
done();
done(); updateGate();
}, 1000);
});
act.appendChild(btn);
}
grid.appendChild(d);
});
updateGate();
})();