Wall view interaction: click banner, countdown, then a viewed checkmark (per-position, persisted)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
martbost
2026-09-06 09:00:14 -05:00
parent 37a2b97bfd
commit b7e9fdf656
9 changed files with 64 additions and 31 deletions
+5
View File
@@ -365,6 +365,11 @@ textarea{resize:vertical;font:inherit}
.wall-card{background:var(--panel);border:1px solid var(--line);border-radius:var(--radius);padding:16px;text-align:center}
.wall-card img{max-width:100%;border-radius:10px;border:1px solid var(--line-strong)}
.wall-pos{font-family:var(--mono);font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.08em;margin-bottom:8px}
.wc-creative img{max-width:100%;border-radius:10px;border:1px solid var(--line-strong)}
.wc-action{margin-top:10px;min-height:32px;display:flex;justify-content:center;align-items:center}
.wc-check{color:var(--mint);font-weight:800;border:1px solid var(--mint);border-radius:999px;padding:4px 14px;
box-shadow:0 0 14px rgba(67,232,195,.3)}
.wc-timer{color:var(--amber);font-weight:700;font-family:var(--mono);font-size:13px}
/* ── modal (sponsor message) ── */
.modal-back{position:fixed;inset:0;z-index:100;background:rgba(2,6,5,.72);display:flex;align-items:center;justify-content:center;padding:20px}
.modal-card{background:var(--panel-solid);border:1px solid var(--line-strong);border-radius:var(--radius);
+33 -5
View File
@@ -20,19 +20,47 @@
if (w.qrUrl) IAP.$('bioQr').src = w.qrUrl;
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 = {};
try { viewed = JSON.parse(localStorage.getItem(VKEY) || '{}'); } catch (e) {}
const DWELL = 10;
const grid = IAP.$('wallGrid');
grid.innerHTML = '';
w.ladder.forEach((m, i) => {
const d = document.createElement('div');
d.className = 'wall-card';
const safe = String(m.name || 'member').replace(/[&<>]/g, '');
const creative = m.bannerUrl
? '<img src="' + m.bannerUrl + '" alt="' + safe + ' banner">'
: '<b>' + safe + '</b><br><span class="muted small">' + (m.targetUrl ? 'visit their site' : 'banner slot open') + '</span>';
d.innerHTML = '<div class="wall-pos">Position ' + (i + 1) + (i === 0 ? ' · this wall' : '') + '</div>'
+ (m.bannerUrl
? '<a href="' + m.targetUrl + '" target="_blank" rel="noopener nofollow"><img src="' + m.bannerUrl + '" alt="' + safe + ' banner"></a>'
: m.targetUrl
? '<a href="' + m.targetUrl + '" target="_blank" rel="noopener nofollow"><b>' + safe + '</b><br><span class="muted small">visit their site</span></a>'
: '<b>' + safe + '</b><br><span class="muted small">banner slot open</span>')
+ '<div class="wc-creative">' + creative + '</div>'
+ '<div class="wc-action"></div>'
+ '<div class="small muted" style="margin-top:8px">' + safe + '</div>';
const act = d.querySelector('.wc-action');
const done = () => { act.innerHTML = '<span class="wc-check">✓ viewed</span>'; };
if (!m.targetUrl) { act.innerHTML = '<span class="muted small">no ad yet</span>'; }
else if (viewed[i]) { done(); }
else {
const btn = document.createElement('button');
btn.className = 'btn small'; btn.textContent = 'View this ad';
btn.addEventListener('click', () => {
window.open(m.targetUrl, '_blank'); // real visit to the advertiser
let left = DWELL;
btn.disabled = true;
act.innerHTML = '<span class="wc-timer">Viewing… ' + left + 's</span>';
const t = setInterval(() => {
left--;
if (left > 0) { act.querySelector('.wc-timer').textContent = 'Viewing… ' + left + 's'; return; }
clearInterval(t);
viewed[i] = 1;
try { localStorage.setItem(VKEY, JSON.stringify(viewed)); } catch (e) {}
done();
}, 1000);
});
act.appendChild(btn);
}
grid.appendChild(d);
});
})();