diff --git a/src/i18n/en.ts b/src/i18n/en.ts index 92c13f8..a991ba8 100644 --- a/src/i18n/en.ts +++ b/src/i18n/en.ts @@ -6385,6 +6385,8 @@ walletUi: { derived: 'From your Skater profile', noSkaterProfile: 'No Skater profile yet —', editSkaterProfile: 'add your styles & level', + noPhoto: 'No profile photo yet —', + addPhoto: 'add one in your profile', accent: 'Accent colour', accentHint: 'Overrides the theme accent', accentClear: 'Use theme colour', diff --git a/src/i18n/es.ts b/src/i18n/es.ts index b369fda..3437138 100644 --- a/src/i18n/es.ts +++ b/src/i18n/es.ts @@ -6385,6 +6385,8 @@ walletUi: { derived: 'Desde tu perfil de skater', noSkaterProfile: 'Aún no tienes perfil de skater —', editSkaterProfile: 'añade tus estilos y nivel', + noPhoto: 'Aún no tienes foto de perfil —', + addPhoto: 'añádela en tu perfil', accent: 'Color de acento', accentHint: 'Sustituye el acento del tema', accentClear: 'Usar color del tema', diff --git a/src/lib/riderCardCanvas.ts b/src/lib/riderCardCanvas.ts index b45bc7d..5c9be32 100644 --- a/src/lib/riderCardCanvas.ts +++ b/src/lib/riderCardCanvas.ts @@ -73,10 +73,10 @@ export interface CanvasCardData { // ─── geometry ───────────────────────────────────────────────────────────────── const X = 44; // left text margin -const NAME_MAX = 560; // text column width (QR sits to the right) -const QR_SIZE = 150; -const QR_X = CARD_W - X - QR_SIZE; // 662 -const QR_Y = 322; +const NAME_MAX = 548; // text column width (QR sits to the right) +const QR_SIZE = 178; +const QR_X = CARD_W - X - QR_SIZE; // 634 +const QR_Y = 232; // ─── helpers ────────────────────────────────────────────────────────────────── async function loadImage(url: string): Promise { @@ -104,6 +104,15 @@ function roundRect(ctx: CanvasRenderingContext2D, x: number, y: number, w: numbe ctx.closePath(); } +/** '#rrggbb' (or '#rgb') → 'rgba(r, g, b, a)'. */ +function hexToRgba(hex: string, a: number): string { + const h = hex.replace('#', ''); + const v = h.length === 3 ? h.split('').map((c) => c + c).join('') : h; + const n = parseInt(v.slice(0, 6), 16); + if (Number.isNaN(n)) return `rgba(0,0,0,${a})`; + return `rgba(${(n >> 16) & 255}, ${(n >> 8) & 255}, ${n & 255}, ${a})`; +} + function fitText(ctx: CanvasRenderingContext2D, text: string, maxWidth: number): string { let t = text; while (ctx.measureText(t).width > maxWidth && t.length > 2) t = t.slice(0, -1); @@ -421,12 +430,20 @@ export async function drawCardToCanvas(data: CanvasCardData, side: 'front' | 'ba ctx.fillStyle = top; ctx.fillRect(0, 0, CARD_W, 170); - const bot = ctx.createLinearGradient(0, 210, 0, CARD_H); + // Bottom scrim — tints with the THEME (not plain black) so the card feels + // branded: fades into the theme's dark background with an accent glow. + const bot = ctx.createLinearGradient(0, 200, 0, CARD_H); bot.addColorStop(0, 'rgba(0,0,0,0)'); - bot.addColorStop(0.55, 'rgba(0,0,0,0.45)'); - bot.addColorStop(1, 'rgba(0,0,0,0.86)'); + bot.addColorStop(0.5, hexToRgba(p.bg2, 0.55)); + bot.addColorStop(1, hexToRgba(p.bg1, 0.97)); ctx.fillStyle = bot; - ctx.fillRect(0, 210, CARD_W, CARD_H - 210); + ctx.fillRect(0, 200, CARD_W, CARD_H - 200); + + const glow = ctx.createLinearGradient(0, CARD_H - 190, 0, CARD_H); + glow.addColorStop(0, 'rgba(0,0,0,0)'); + glow.addColorStop(1, hexToRgba(p.accent, 0.20)); + ctx.fillStyle = glow; + ctx.fillRect(0, CARD_H - 190, CARD_W, 190); ctx.restore(); const logo = data.type === 'rider' && data.logoUrl ? await loadImage(data.logoUrl) : null; diff --git a/src/pages/RiderCardPage.tsx b/src/pages/RiderCardPage.tsx index c33cfa3..9e4f619 100644 --- a/src/pages/RiderCardPage.tsx +++ b/src/pages/RiderCardPage.tsx @@ -807,6 +807,13 @@ export default function RiderCardPage() { + {!metadata?.picture && ( +

+ {t('cardUi.studio.noPhoto')}{' '} + {t('cardUi.studio.addPhoto')} +

+ )} +