Achievement badges + milestone credit bonuses
- Idempotent one-time credit bonus per milestone (payouts/firstBuyer/level2/level3): 10/25/50/100 cr - Overview badges strip (unlockable medals) with canvas share-to-image (username overlaid) - Newly-unlocked milestones toast the bonus on next dashboard load Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -26,6 +26,70 @@
|
||||
if (d.buyerCount < 5) return 'Level 2 is open. ' + (5 - d.buyerCount) + ' more qualifying buyer(s) unlock level 3 and the full three-level flow.';
|
||||
return 'Fully qualified. Every level pays you, and you catch the pass-ups that under-qualified positions below you let slip. Keep sharing and keep your campaigns running.';
|
||||
}
|
||||
// achievement badges: the milestone ladder as unlockable medals + share-to-image
|
||||
const BADGES = [
|
||||
{ key: 'payouts', label: 'Payouts On', sub: 'wallet linked', icon: '<circle cx="12" cy="12" r="8"/><path d="M9 12l2 2 4-4"/>' },
|
||||
{ key: 'firstBuyer', label: 'First Buyer', sub: 'first qualified referral', icon: '<path d="M6 7h12l-1 13H7z"/><path d="M9 7a3 3 0 0 1 6 0"/>' },
|
||||
{ key: 'level2', label: 'Level 2', sub: '2 qualifying buyers', icon: '<path d="M12 3l2.5 5 5.5.8-4 3.9.9 5.5L12 21l-4.9 2.6.9-5.5-4-3.9 5.5-.8z"/>' },
|
||||
{ key: 'level3', label: 'Level 3', sub: 'fully qualified', icon: '<path d="M5 4h14v5a7 7 0 0 1-14 0z"/><path d="M9 20h6M12 16v4"/><path d="M5 6H3v2a3 3 0 0 0 3 3M19 6h2v2a3 3 0 0 1-3 3"/>' }
|
||||
];
|
||||
let lastMilestones = [];
|
||||
function renderBadges(d) {
|
||||
const reached = d.milestonesReached || [];
|
||||
lastMilestones = reached;
|
||||
const strip = $('badgeStrip');
|
||||
if (!strip) return;
|
||||
$('badgeCard').hidden = false;
|
||||
strip.innerHTML = BADGES.map(b => {
|
||||
const got = reached.includes(b.key);
|
||||
return '<div class="badge-a' + (got ? '' : ' locked') + '">'
|
||||
+ '<div class="medal"><svg viewBox="0 0 24 24">' + b.icon + '</svg></div>'
|
||||
+ '<div class="bl">' + b.label + '</div><div class="bs">' + b.sub + '</div>'
|
||||
+ (got ? '<button class="share" data-badge="' + b.key + '">Share</button>' : '') + '</div>';
|
||||
}).join('');
|
||||
strip.querySelectorAll('[data-badge]').forEach(btn =>
|
||||
btn.addEventListener('click', () => shareBadge(btn.dataset.badge, d)));
|
||||
// celebrate anything newly granted this load
|
||||
if (d.milestonesGranted && d.milestonesGranted.length) {
|
||||
const total = d.milestonesGranted.reduce((s, g) => s + g.credited, 0);
|
||||
const names = d.milestonesGranted.map(g => (BADGES.find(b => b.key === g.key) || {}).label).filter(Boolean).join(', ');
|
||||
IAP.status('🏆 Achievement unlocked: ' + names + ' — +' + total + ' bonus credits!', 'ok');
|
||||
}
|
||||
}
|
||||
// compose a shareable badge image on a canvas (zero-dep) and download it
|
||||
function shareBadge(key, d) {
|
||||
const b = BADGES.find(x => x.key === key); if (!b) return;
|
||||
const c = document.createElement('canvas'); c.width = 1200; c.height = 630;
|
||||
const x = c.getContext('2d');
|
||||
const g = x.createLinearGradient(0, 0, 1200, 630);
|
||||
g.addColorStop(0, '#041109'); g.addColorStop(1, '#0b2018');
|
||||
x.fillStyle = g; x.fillRect(0, 0, 1200, 630);
|
||||
x.strokeStyle = 'rgba(67,232,195,.5)'; x.lineWidth = 6; x.strokeRect(24, 24, 1152, 582);
|
||||
x.fillStyle = '#43e8c3'; x.font = 'bold 34px Sora, sans-serif'; x.fillText('InstantAdPay', 70, 100);
|
||||
x.fillStyle = '#8ba69c'; x.font = '22px Sora, sans-serif'; x.fillText('ACHIEVEMENT UNLOCKED', 70, 150);
|
||||
// medal circle
|
||||
x.beginPath(); x.arc(600, 320, 110, 0, Math.PI * 2);
|
||||
const mg = x.createRadialGradient(600, 290, 20, 600, 320, 110);
|
||||
mg.addColorStop(0, 'rgba(67,232,195,.45)'); mg.addColorStop(1, 'rgba(9,26,19,.9)');
|
||||
x.fillStyle = mg; x.fill(); x.strokeStyle = '#43e8c3'; x.lineWidth = 5; x.stroke();
|
||||
x.fillStyle = '#eef7f3'; x.textAlign = 'center';
|
||||
x.font = 'bold 64px Sora, sans-serif'; x.fillText(b.label, 600, 500);
|
||||
x.font = '26px Sora, sans-serif'; x.fillStyle = '#8ba69c'; x.fillText(b.sub, 600, 545);
|
||||
const who = d.username ? '@' + d.username : d.memberId ? 'member #' + d.memberId : '';
|
||||
if (who) { x.fillStyle = '#43e8c3'; x.font = 'bold 30px Sora, sans-serif'; x.fillText(who, 600, 590); }
|
||||
x.textAlign = 'left';
|
||||
try {
|
||||
c.toBlob(bl => {
|
||||
const a = document.createElement('a');
|
||||
a.href = URL.createObjectURL(bl);
|
||||
a.download = 'instantadpay-' + key + '-badge.png';
|
||||
a.click();
|
||||
setTimeout(() => URL.revokeObjectURL(a.href), 5000);
|
||||
IAP.status('Badge image saved — share it anywhere.', 'ok');
|
||||
}, 'image/png');
|
||||
} catch (e) { IAP.status('Could not generate the image.', 'bad'); }
|
||||
}
|
||||
|
||||
// milestone stepper under "Your next move": lit nodes for what's done,
|
||||
// amber glow on the current target — the same ladder the contract pays
|
||||
function renderSteps(d) {
|
||||
@@ -147,6 +211,7 @@
|
||||
if (tc && wk) { tc.hidden = false; tc.textContent = '+' + wk + ' this week'; }
|
||||
setInboxBadge(d.inboxUnread || 0);
|
||||
if (d.sponsorMsg) showSponsorModal(d.sponsorMsg);
|
||||
renderBadges(d);
|
||||
loadCharts(d);
|
||||
$('nextMove').textContent = nextMove(d);
|
||||
renderSteps(d);
|
||||
|
||||
@@ -259,6 +259,18 @@ textarea{resize:vertical;font:inherit}
|
||||
#boBurger{display:block}
|
||||
.bo-content{padding:18px}
|
||||
}
|
||||
/* ── achievement badges ── */
|
||||
.badges{display:flex;gap:16px;flex-wrap:wrap;margin-top:12px}
|
||||
.badge-a{display:flex;flex-direction:column;align-items:center;gap:8px;width:120px;text-align:center}
|
||||
.badge-a .medal{width:76px;height:76px;border-radius:50%;display:grid;place-items:center;position:relative;
|
||||
background:radial-gradient(circle at 50% 35%,rgba(67,232,195,.28),rgba(9,26,19,.9));
|
||||
border:2px solid var(--mint);box-shadow:0 0 18px rgba(67,232,195,.3)}
|
||||
.badge-a.locked .medal{background:rgba(139,166,156,.08);border-color:var(--line-strong);box-shadow:none;filter:grayscale(1);opacity:.5}
|
||||
.badge-a .medal svg{width:38px;height:38px;stroke:var(--mint);fill:none;stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round}
|
||||
.badge-a.locked .medal svg{stroke:var(--muted)}
|
||||
.badge-a .bl{font-size:12px;font-weight:700;line-height:1.2}
|
||||
.badge-a .bs{font-size:10.5px;color:var(--muted)}
|
||||
.badge-a .share{font-size:11px;color:var(--mint);cursor:pointer;background:none;border:0;padding:0;text-decoration:underline}
|
||||
/* ── "Your next move": milestone stepper card ── */
|
||||
.next-card{position:relative;overflow:hidden;border-color:rgba(67,232,195,.28)}
|
||||
.next-card::before{content:"";position:absolute;inset:0;pointer-events:none;background:
|
||||
|
||||
Reference in New Issue
Block a user