export type BadgeRarity = 'common' | 'uncommon' | 'rare' export interface CloudBadgeCardInput { cloudName: string cloudNameEn: string unlockedAt: string username: string rarity: BadgeRarity } const rarityTheme = { common: { from: '#dbeafe', to: '#f8fafc', accent: '#0284c7', shadow: 'rgba(2, 132, 199, 0.18)', }, uncommon: { from: '#fef3c7', to: '#fff7ed', accent: '#d97706', shadow: 'rgba(217, 119, 6, 0.22)', }, rare: { from: '#fee2e2', to: '#fff7ed', accent: '#dc2626', shadow: 'rgba(220, 38, 38, 0.22)', }, } satisfies Record function formatDate(iso: string) { return new Date(iso).toLocaleDateString('zh-CN', { year: 'numeric', month: 'long', day: 'numeric', }) } function makeDownloadName(name: string) { return `opencloud-badge-${name}.png` } export async function downloadCloudBadgeCard(input: CloudBadgeCardInput) { const canvas = document.createElement('canvas') canvas.width = 1200 canvas.height = 630 const ctx = canvas.getContext('2d') if (!ctx) return const theme = rarityTheme[input.rarity] const gradient = ctx.createLinearGradient(0, 0, canvas.width, canvas.height) gradient.addColorStop(0, theme.from) gradient.addColorStop(1, theme.to) ctx.fillStyle = gradient ctx.fillRect(0, 0, canvas.width, canvas.height) ctx.fillStyle = 'rgba(255,255,255,0.72)' ctx.beginPath() ctx.arc(1010, 120, 140, 0, Math.PI * 2) ctx.fill() ctx.fillStyle = 'rgba(255,255,255,0.45)' ctx.beginPath() ctx.arc(160, 520, 180, 0, Math.PI * 2) ctx.fill() ctx.fillStyle = '#0f172a' ctx.font = '700 44px sans-serif' ctx.fillText('OpenCloud 云朵图鉴', 72, 88) ctx.fillStyle = 'rgba(15, 23, 42, 0.62)' ctx.font = '400 24px sans-serif' ctx.fillText('新徽章已解锁', 72, 126) ctx.shadowColor = theme.shadow ctx.shadowBlur = 28 ctx.fillStyle = '#ffffff' ctx.beginPath() ctx.roundRect(72, 170, 1056, 380, 32) ctx.fill() ctx.shadowBlur = 0 ctx.fillStyle = theme.accent ctx.beginPath() ctx.arc(210, 360, 92, 0, Math.PI * 2) ctx.fill() ctx.fillStyle = '#ffffff' ctx.font = '700 82px sans-serif' ctx.textAlign = 'center' ctx.textBaseline = 'middle' ctx.fillText(input.cloudName.slice(0, 1), 210, 360) ctx.textAlign = 'left' ctx.textBaseline = 'alphabetic' ctx.fillStyle = '#0f172a' ctx.font = '700 64px sans-serif' ctx.fillText(input.cloudName, 340, 300) ctx.fillStyle = 'rgba(15, 23, 42, 0.62)' ctx.font = '400 30px sans-serif' ctx.fillText(input.cloudNameEn, 340, 346) ctx.fillStyle = theme.accent ctx.beginPath() ctx.roundRect(340, 382, 168, 48, 24) ctx.fill() ctx.fillStyle = '#ffffff' ctx.font = '600 24px sans-serif' ctx.fillText(input.rarity === 'common' ? '常见' : input.rarity === 'uncommon' ? '少见' : '罕见', 384, 414) ctx.fillStyle = '#334155' ctx.font = '400 28px sans-serif' ctx.fillText(`解锁者:${input.username}`, 340, 476) ctx.fillText(`解锁日期:${formatDate(input.unlockedAt)}`, 340, 518) const blob = await new Promise(resolve => canvas.toBlob(resolve, 'image/png')) if (!blob) return const url = URL.createObjectURL(blob) const link = document.createElement('a') link.href = url link.download = makeDownloadName(input.cloudNameEn.toLowerCase()) link.click() URL.revokeObjectURL(url) }