2ac6549171
- accounts.wall_offers (JSON, up to 2 offers: label, https link, banner) set from Profile > Your wall; upload supported; locks show the buyer threshold. - /api/wall assembles: position 1 = own line banner; positions 2-3 = own offer when unlocked and set, else upline banners (only uplines with a live banner), else house ads. Response carries unlocked + buyerCount; wall labels show "this wall" / "their line" / "InstantAdPay". - Chatbot canned answer + facts, follow-up email 7 mention the ladder. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
90 lines
5.6 KiB
JavaScript
90 lines
5.6 KiB
JavaScript
// 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) + (m.own ? ' · this wall' : m.admin ? ' · InstantAdPay' : ' · their line') + '</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();
|
||
})();
|