diff --git a/ads.js b/ads.js
index a372a21..928ee09 100644
Binary files a/ads.js and b/ads.js differ
diff --git a/db.js b/db.js
index ae6a31f..8911338 100644
--- a/db.js
+++ b/db.js
@@ -70,6 +70,8 @@ async function bootstrap() {
granted_welcome TINYINT NOT NULL DEFAULT 0,
updated BIGINT NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`);
+ const alterSafe0 = async sql => { try { await q(sql); } catch (e) { if (!['ER_DUP_FIELDNAME', 'ER_DUP_KEYNAME'].includes(e.code)) throw e; } };
+ await alterSafe0('ALTER TABLE earned_credits ADD COLUMN milestones VARCHAR(255) NULL'); // comma-joined granted milestone keys
// additive columns (MySQL 8 has no IF NOT EXISTS for columns)
const alterSafe = async sql => {
try { await q(sql); }
diff --git a/public/assets/my.js b/public/assets/my.js
index ba8c2a1..450b472 100644
--- a/public/assets/my.js
+++ b/public/assets/my.js
@@ -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: ' ' },
+ { key: 'firstBuyer', label: 'First Buyer', sub: 'first qualified referral', icon: ' ' },
+ { key: 'level2', label: 'Level 2', sub: '2 qualifying buyers', icon: ' ' },
+ { key: 'level3', label: 'Level 3', sub: 'fully qualified', icon: ' ' }
+ ];
+ 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 '
'
+ + '
' + b.icon + '
'
+ + '
' + b.label + '
' + b.sub + '
'
+ + (got ? '
Share ' : '') + '
';
+ }).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);
diff --git a/public/assets/site.css b/public/assets/site.css
index 237df02..ad51ca8 100644
--- a/public/assets/site.css
+++ b/public/assets/site.css
@@ -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:
diff --git a/public/contract.html b/public/contract.html
index cbdde74..378b898 100644
--- a/public/contract.html
+++ b/public/contract.html
@@ -5,7 +5,7 @@
The contract | InstantAdPay
-
+
@@ -129,8 +129,8 @@
Advertising services with a performance referral program. Not an investment product; no income guarantees. Crypto transactions are irreversible. Never spend what you cannot afford.
-
-
-
+
+
+