Badge cards: seven AI artworks for the hunting achievements, the hunter's name stamped on the ribbon server-side (ffmpeg), public share pages /b/<who>/<id> with OG cards, badge strip + share modal + unlock celebration on the board, Telegram photo post gated by OUTBOUND

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
martbost
2026-09-19 20:31:20 -05:00
parent 6338af9efe
commit 8eecc7c0cb
19 changed files with 156 additions and 14 deletions
+21
View File
@@ -32,7 +32,26 @@
+ '<div class="step"><b>Come back through the door.</b> Return to your dashboard and press <a href="' + IAP + '/api/my/polhunter">Open my board</a> again. Your missions unlock the moment a wallet is on the account.</div>'
+ '</div>';
}
// badge cards: art, lock state, share
function badgeStrip() {
const el = $('badgeStrip'); if (!el || !board.badgeCards) return;
el.innerHTML = board.badgeCards.map(b => '<div class="badge-a' + (b.earned ? '' : ' locked') + '"><img src="' + b.art + '" alt="' + esc(b.name) + '" loading="lazy"><div class="bl">' + b.icon + ' ' + esc(b.name) + '</div><div class="bs">' + esc(b.why) + '</div>' + (b.earned ? '<button class="btn pol sm" data-share="' + b.id + '">Share</button>' : '<div class="bs">\u{1F512} locked</div>') + '</div>').join('');
el.querySelectorAll('[data-share]').forEach(x => x.addEventListener('click', () => shareBadge(x.dataset.share, false)));
}
function shareBadge(id, unlocked) {
const b = (board.badgeCards || []).find(x => x.id === id); if (!b || !b.earned) return;
const text = (unlocked ? 'Achievement unlocked on PolHunter: ' : 'I earned ') + b.name + ' (' + b.why + ') on PolHunter. Visit a site, find your code, get a drip of POL to your wallet. ' + b.page;
const T = encodeURIComponent(text), U = encodeURIComponent(b.page);
$('shareBody').innerHTML = '<div class="share-hd">' + (unlocked ? '<span class="tag gold">Achievement unlocked</span>' : '') + '<h3>' + b.icon + ' ' + esc(b.name) + '</h3><p class="site">' + esc(b.why) + '</p></div>'
+ '<img class="share-img" src="' + b.image + '?t=' + Date.now() + '" alt="' + esc(b.name) + '">'
+ '<div class="sharebtns" style="justify-content:center"><a class="btn ghost sm" target="_blank" rel="noopener" href="https://twitter.com/intent/tweet?text=' + T + '">X</a><a class="btn ghost sm" target="_blank" rel="noopener" href="https://t.me/share/url?url=' + U + '&text=' + encodeURIComponent(text.replace(b.page, '').trim()) + '">Telegram</a><a class="btn ghost sm" target="_blank" rel="noopener" href="https://www.facebook.com/sharer/sharer.php?u=' + U + '">Facebook</a><a class="btn ghost sm" target="_blank" rel="noopener" href="https://wa.me/?text=' + T + '">WhatsApp</a><a class="btn ghost sm" href="' + b.image + '" download="polhunter-' + b.id + '.jpg">Save image</a><button class="btn pol sm" id="shareCopy">Copy link</button></div>'
+ '<p class="small" style="margin-top:10px;text-align:center">The card carries your name; the link carries your referral.</p>';
$('shareModal').hidden = false;
$('shareCopy').onclick = async () => { try { await navigator.clipboard.writeText(b.page); } catch (e) {} $('shareCopy').textContent = 'Copied'; setTimeout(() => { $('shareCopy').textContent = 'Copy link'; }, 1200); };
}
document.addEventListener('click', e => { if (e.target.id === 'shareClose' || e.target.id === 'shareModal') $('shareModal').hidden = true; });
function render() {
badgeStrip();
$('who').innerHTML = '<i></i> ' + esc(board.me.username ? '@' + board.me.username : '#' + board.me.memberId) + (board.me.wallet ? '' : ' · no wallet linked');
const r = board.missions[0] && board.missions[0].reward;
if (r) $('rangeLine').textContent = 'Each find pays a random drip between ' + r.minPol + ' and ' + r.maxPol + ' POL. Your codes are yours alone.';
@@ -86,6 +105,8 @@
const r = await api('/api/my/submit', { token: open.token, code });
const m = $('msg'); m.className = 'msg ' + (r.error ? 'bad' : 'ok'); m.textContent = r.error || r.message;
if (!r.error) { const a = document.createElement('a'); a.href = '#share'; a.textContent = ' Share this find \u2197'; a.style.marginLeft = '6px'; m.appendChild(a); delete $('sharetext').dataset.touched; }
// an achievement unlocked by this find: celebrate it once the board has reloaded
if (!r.error && r.unlocked && r.unlocked.length) { const id = r.unlocked[0]; setTimeout(async () => { await load(); shareBadge(id, true); }, 1200); }
$('submit').disabled = false;
if (!r.error || r.spent) { open = null; setTimeout(load, 900); }
}