LinkSpin test area: InstantAdPay engine fork rebranded, network registry, sponsor carry-over with engine activation and claim window, rotator with /r/ redirects, link-domain mini-sites

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
martbost
2026-09-15 16:16:52 -05:00
commit 010e8d7ffc
130 changed files with 20096 additions and 0 deletions
+104
View File
@@ -0,0 +1,104 @@
// 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');
const hostEl = IAP.$('wallHost');
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;
if (hostEl) hostEl.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 LinkSpin — 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('');
}
// intro video under the bio: YouTube / Vimeo embed, or a direct file
const bv = IAP.$('bioVideo');
const vurl = w.socials && typeof w.socials === 'object' ? String(w.socials.video || '') : '';
if (bv && vurl) {
let yt = /(?:youtube\.com\/(?:watch\?(?:.*&)?v=|shorts\/|embed\/)|youtu\.be\/)([A-Za-z0-9_-]{6,})/.exec(vurl);
let vm = /vimeo\.com\/(?:video\/)?(\d+)/.exec(vurl);
let inner = '';
if (yt) inner = '<iframe src="https://www.youtube-nocookie.com/embed/' + yt[1] + '?rel=0" title="Intro video" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen loading="lazy"></iframe>';
else if (vm) inner = '<iframe src="https://player.vimeo.com/video/' + vm[1] + '" title="Intro video" allow="autoplay; fullscreen; picture-in-picture" allowfullscreen loading="lazy"></iframe>';
else if (/\.(mp4|webm)(\?|$)/i.test(vurl)) inner = '<video src="' + vurl.replace(/"/g, '%22') + '" controls playsinline preload="metadata"></video>';
if (inner) { bv.innerHTML = '<p class="eyebrow" style="margin:0 0 8px">A word from ' + String(w.name).replace(/[&<>]/g, '') + '</p>' + inner; bv.hidden = false; }
}
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');
IAP.adSlot('text', 'adSlotWallText'); IAP.adSlot('banner', 'adSlotWallBanner');
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 ? ' · LinkSpin' : ' · 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();
})();