Badge emblems, profile social links, and SEO/OG social previews
- Achievement badge share-image now draws a milestone emblem (⚡🎯⭐🏆) in the medal - Profile: Facebook/X/YouTube/Instagram/TikTok/Telegram/LinkedIn/Website links, shown on the public bio page - OG + Twitter Card tags on homepage/ledger/contract (hero banner as share image) - Per-member OG tags server-injected into /wall/<username> so shared bio links preview with name/bio/avatar - robots.txt + sitemap.xml; .txt/.xml MIME types Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+20
-13
@@ -42,10 +42,10 @@
|
||||
|
||||
// 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"/>' }
|
||||
{ key: 'payouts', label: 'Payouts On', sub: 'wallet linked', emblem: '⚡', icon: '<circle cx="12" cy="12" r="8"/><path d="M9 12l2 2 4-4"/>' },
|
||||
{ key: 'firstBuyer', label: 'First Buyer', sub: 'first qualified referral', emblem: '🎯', 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', emblem: '⭐', 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', emblem: '🏆', 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) {
|
||||
@@ -81,16 +81,19 @@
|
||||
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();
|
||||
// medal with the milestone emblem
|
||||
x.beginPath(); x.arc(600, 300, 112, 0, Math.PI * 2);
|
||||
const mg = x.createRadialGradient(600, 270, 20, 600, 300, 112);
|
||||
mg.addColorStop(0, 'rgba(67,232,195,.5)'); mg.addColorStop(1, 'rgba(9,26,19,.92)');
|
||||
x.fillStyle = mg; x.fill(); x.strokeStyle = '#43e8c3'; x.lineWidth = 6; x.stroke();
|
||||
x.save(); x.shadowColor = 'rgba(67,232,195,.6)'; x.shadowBlur = 24;
|
||||
x.font = '90px "Segoe UI Emoji","Noto Color Emoji",sans-serif'; x.textAlign = 'center'; x.textBaseline = 'middle';
|
||||
x.fillText(b.emblem || '', 600, 300); x.restore(); x.textBaseline = 'alphabetic';
|
||||
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);
|
||||
x.font = 'bold 60px Sora, sans-serif'; x.fillText(b.label, 600, 485);
|
||||
x.font = '25px Sora, sans-serif'; x.fillStyle = '#8ba69c'; x.fillText(b.sub, 600, 527);
|
||||
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); }
|
||||
if (who) { x.fillStyle = '#43e8c3'; x.font = 'bold 30px Sora, sans-serif'; x.fillText(who, 600, 575); }
|
||||
x.textAlign = 'left';
|
||||
try {
|
||||
c.toBlob(bl => {
|
||||
@@ -1228,10 +1231,13 @@
|
||||
fillProfileDetails(a);
|
||||
} catch (e) {}
|
||||
}
|
||||
const SOCIALS = ['facebook', 'twitter', 'youtube', 'instagram', 'tiktok', 'telegram', 'linkedin', 'website'];
|
||||
function fillProfileDetails(a) {
|
||||
if (!a) return;
|
||||
if (a.bio) $('pfBio').value = a.bio;
|
||||
if (a.avatarUrl) { const p = $('pfAvatarPrev'); p.src = a.avatarUrl; p.hidden = false; }
|
||||
let soc = {}; try { soc = a.socials ? JSON.parse(a.socials) : {}; } catch (e) {}
|
||||
for (const p of SOCIALS) if ($('soc-' + p)) $('soc-' + p).value = soc[p] || '';
|
||||
if (a.username) {
|
||||
const link = location.origin + '/wall/' + a.username;
|
||||
$('pfBioLink').textContent = link;
|
||||
@@ -1255,7 +1261,8 @@
|
||||
$('pfAvatarFile').value = '';
|
||||
});
|
||||
$('pfDetailsSave').addEventListener('click', busy2($('pfDetailsSave'), async () => {
|
||||
const body = { bio: $('pfBio').value };
|
||||
const body = { bio: $('pfBio').value, socials: {} };
|
||||
for (const p of SOCIALS) body.socials[p] = ($('soc-' + p) && $('soc-' + p).value.trim()) || '';
|
||||
if (pfAvatar) body.avatarUrl = pfAvatar;
|
||||
const r = await api('/api/my/profile-details', body);
|
||||
IAP.status('Profile saved.', 'ok');
|
||||
|
||||
Reference in New Issue
Block a user