Files
instantadpay/public/assets/wall.js
T
martbost 9b32d40874 Wall page: platform icons on the social pills
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-09 08:32:52 -05:00

90 lines
5.5 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Public banner wall: a member's line banner plus their upline ladder, with
// their join link. The viral surface: members send traffic here, every visit
// puts eyes on the whole line.
(async function () {
await IAP.renderNav('');
const name = location.pathname.split('/').pop();
let w = null;
try { w = await (await fetch('/api/wall/' + encodeURIComponent(name))).json(); } catch (e) {}
const title = IAP.$('wallTitle');
if (!w || w.error) {
title.textContent = 'No wall under that name.';
IAP.$('wallJoin').href = '/my';
return;
}
const safeName = String(w.name).replace(/[&<>]/g, '');
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
const SOC = { facebook: 'Facebook', twitter: 'X', youtube: 'YouTube', instagram: 'Instagram', tiktok: 'TikTok', telegram: 'Telegram', linkedin: 'LinkedIn', website: 'Website' };
// small line icons, one per platform (currentColor so they follow the pill color)
const svg = inner => '<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">' + inner + '</svg>';
const ICON = {
facebook: svg('<path d="M14 8h3V4h-3a4 4 0 0 0-4 4v2H7v4h3v7h4v-7h3l1-4h-4V8.5c0-.3.2-.5.5-.5z" fill="currentColor" stroke="none"/>'),
twitter: svg('<path d="M4 4l16 16M20 4L4 20"/>'),
youtube: svg('<rect x="2.5" y="6" width="19" height="12" rx="4"/><path d="M10 9.5v5l4.5-2.5z" fill="currentColor" stroke="none"/>'),
instagram: svg('<rect x="3" y="3" width="18" height="18" rx="5"/><circle cx="12" cy="12" r="4"/><circle cx="17.3" cy="6.7" r="1" fill="currentColor" stroke="none"/>'),
tiktok: svg('<path d="M14 4c.3 2.3 1.9 3.8 4.3 4v3.1c-1.6 0-3.1-.5-4.3-1.4v5.8a5 5 0 1 1-5-5v3.2a1.9 1.9 0 1 0 1.9 1.9V4z" fill="currentColor" stroke="none"/>'),
telegram: svg('<path d="M21 4L3 11.5l5.5 2L11 20l3-4.5L19 19z" fill="currentColor" stroke="none"/>'),
linkedin: svg('<circle cx="6" cy="5.5" r="1.6" fill="currentColor" stroke="none"/><path d="M4.5 9.5h3V20h-3z" fill="currentColor" stroke="none"/><path d="M11 9.5h3v1.6c.7-1.1 1.9-1.9 3.5-1.9 2.8 0 3.5 1.9 3.5 4.5V20h-3v-5.6c0-1.3-.3-2.3-1.6-2.3s-2.4 1-2.4 2.4V20h-3z" fill="currentColor" stroke="none"/>'),
website: svg('<circle cx="12" cy="12" r="9"/><path d="M3 12h18M12 3c3 3.5 3 14.5 0 18M12 3c-3 3.5-3 14.5 0 18"/>')
};
const sc = IAP.$('bioSocials');
if (sc && w.socials && typeof w.socials === 'object') {
const links = Object.keys(SOC).filter(k => w.socials[k]).map(k =>
'<a href="' + String(w.socials[k]).replace(/"/g, '%22') + '" target="_blank" rel="noopener nofollow me">' + (ICON[k] || '') + '<span>' + SOC[k] + '</span></a>');
sc.innerHTML = links.join('');
}
IAP.$('wallCtaHead').textContent = 'Join ' + w.name + '’s line';
// 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 = '';
// 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';
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>'
+ '<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 (no countdown)
viewed[i] = 1;
try { localStorage.setItem(VKEY, JSON.stringify(viewed)); } catch (e) {}
done(); updateGate();
});
act.appendChild(btn);
}
grid.appendChild(d);
});
updateGate();
})();