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 ? '' : '') + '
'; + }).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.
- - - + + + diff --git a/public/index.html b/public/index.html index 1f637a3..056b363 100644 --- a/public/index.html +++ b/public/index.html @@ -5,7 +5,7 @@ InstantAdPay: advertise and earn, locked in code - + @@ -410,9 +410,9 @@ - - - - + + + + diff --git a/public/ledger.html b/public/ledger.html index 9601b53..101f2bd 100644 --- a/public/ledger.html +++ b/public/ledger.html @@ -5,7 +5,7 @@ Live ledger | InstantAdPay - +
@@ -25,8 +25,8 @@
InstantAdPay ยท how it works ยท contract source โ†—
- - - + + + diff --git a/public/my.html b/public/my.html index 29f9290..31e7492 100644 --- a/public/my.html +++ b/public/my.html @@ -4,7 +4,7 @@ Member area | InstantAdPay - + @@ -186,6 +186,10 @@
+

Recent activity

@@ -529,9 +533,9 @@
- - - - + + + + diff --git a/public/tx.html b/public/tx.html index aa32169..72dae31 100644 --- a/public/tx.html +++ b/public/tx.html @@ -4,7 +4,7 @@ Transaction | InstantAdPay - + @@ -33,7 +33,7 @@

โ† Back to the live ledger ยท Read the contract review

- - + + diff --git a/public/view.html b/public/view.html index f2f9e83..c29f0c4 100644 --- a/public/view.html +++ b/public/view.html @@ -4,7 +4,7 @@ Viewing ad โ€” InstantAdPay - +