import { GlobalFonts, createCanvas, loadImage } from '@napi-rs/canvas'; import path from 'path'; // Register Fonts (same as studentID.ts) const fontDir = path.join(process.cwd(), 'src', 'assets', 'fonts'); GlobalFonts.registerFromPath(path.join(fontDir, 'IBMPlexSansCondensed-SemiBold.ttf'), 'IBMPlexSansCondensed-SemiBold'); GlobalFonts.registerFromPath(path.join(fontDir, 'IBMPlexMono-Bold.ttf'), 'IBMPlexMono-Bold'); export async function generateLootdropCard(amount: number, currency: string): Promise { const templatePath = path.join(process.cwd(), 'src', 'assets', 'graphics', 'lootdrop', 'template.png'); const template = await loadImage(templatePath); const canvas = createCanvas(template.width, template.height); const ctx = canvas.getContext('2d'); // Draw Template ctx.drawImage(template, 0, 0); // Draw Lootdrop Text (Title-ish) ctx.save(); ctx.font = '48px IBMPlexSansCondensed-SemiBold'; ctx.fillStyle = '#FFFFFF'; ctx.textAlign = 'center'; ctx.shadowBlur = 10; ctx.shadowColor = 'rgba(255, 255, 255, 0.5)'; // Center of lower half (512-1024) is roughly 768 ctx.fillText('A STAR IS FALLING', canvas.width / 2, 660); ctx.restore(); // Draw Reward Amount ctx.save(); ctx.font = '72px IBMPlexMono-Bold'; ctx.fillStyle = '#DAC7A1'; ctx.textAlign = 'center'; //ctx.shadowBlur = 15; //ctx.shadowColor = 'rgba(255, 215, 0, 0.8)'; ctx.fillText(`${amount} ${currency}`, canvas.width / 2, 760); // Below title ctx.restore(); // Crop the image by 64px on all sides const croppedWidth = template.width - 128; const croppedHeight = template.height - 128; const croppedCanvas = createCanvas(croppedWidth, croppedHeight); const croppedCtx = croppedCanvas.getContext('2d'); // Draw the original canvas onto the cropped canvas, shifted by -64 croppedCtx.drawImage(canvas, -64, -64); return croppedCanvas.toBuffer('image/png'); } export async function generateClaimedLootdropCard(amount: number, currency: string, username: string, avatarUrl: string): Promise { const templatePath = path.join(process.cwd(), 'src', 'assets', 'graphics', 'lootdrop', 'template.png'); const template = await loadImage(templatePath); const canvas = createCanvas(template.width, template.height); const ctx = canvas.getContext('2d'); // Draw Template ctx.drawImage(template, 0, 0); // Add a colored overlay to signify "claimed" ctx.fillStyle = 'rgba(10, 10, 20, 0.85)'; ctx.fillRect(0, 0, canvas.width, canvas.height); // Draw Claimed Text (Title-ish) ctx.save(); ctx.font = '48px IBMPlexSansCondensed-SemiBold'; ctx.fillStyle = '#FFFFFF'; ctx.textAlign = 'center'; ctx.shadowBlur = 10; ctx.shadowColor = 'rgba(255, 255, 255, 0.5)'; ctx.fillText('STAR CLAIMED', canvas.width / 2, 660); ctx.restore(); // Draw "by username" with Avatar ctx.save(); ctx.font = '36px IBMPlexSansCondensed-SemiBold'; ctx.fillStyle = '#AAAAAA'; // Calculate layout for centering Group (Avatar + Text) const text = `by ${username}`; const textMetrics = ctx.measureText(text); const textWidth = textMetrics.width; const avatarSize = 50; const gap = 15; const totalWidth = avatarSize + gap + textWidth; const startX = (canvas.width - totalWidth) / 2; const baselineY = 830; // Draw Avatar try { const avatar = await loadImage(avatarUrl); ctx.save(); ctx.beginPath(); // Center avatar vertically relative to text roughly (baseline - ~half cap height) // 36px text ~ 27px cap height. Center roughly at baselineY - 14 const avatarCenterY = baselineY - 14; ctx.arc(startX + avatarSize / 2, avatarCenterY, avatarSize / 2, 0, Math.PI * 2); ctx.closePath(); ctx.clip(); ctx.drawImage(avatar, startX, avatarCenterY - avatarSize / 2, avatarSize, avatarSize); ctx.restore(); } catch (e) { // Fallback if avatar fails to load, just don't draw it (or maybe shift text?) // For now, let's just proceed, the text will be off-center if avatar is missing but that's acceptable edge case console.error("Failed to load avatar", e); } // Draw Text ctx.textAlign = 'left'; ctx.fillText(text, startX + avatarSize + gap, baselineY); ctx.restore(); ctx.save(); ctx.font = '72px IBMPlexMono-Bold'; // Match Amount size ctx.fillStyle = '#E6D2B5'; // Lighter gold/beige for better contrast ctx.textAlign = 'center'; ctx.shadowBlur = 10; ctx.shadowColor = 'rgba(0, 0, 0, 0.8)'; // Dark shadow for contrast ctx.fillText(`${amount} ${currency}`, canvas.width / 2, 760); // Same position as Unclaimed Amount ctx.restore(); // Crop the image by 64px on all sides const croppedWidth = template.width - 128; const croppedHeight = template.height - 128; const croppedCanvas = createCanvas(croppedWidth, croppedHeight); const croppedCtx = croppedCanvas.getContext('2d'); // Draw the original canvas onto the cropped canvas, shifted by -64 croppedCtx.drawImage(canvas, -64, -64); return croppedCanvas.toBuffer('image/png'); }