polish(card): bigger/higher QR, theme-tinted scrim, profile-photo nudge
- QR 150->178px and moved up (bottom-right, above the identity block). - Bottom scrim now fades into the THEME's dark background + an accent glow (was plain black) so the card reads as branded. - Studio shows an 'add a profile photo' nudge when the account has no kind-0 picture (the card's placeholder name was the tell).
This commit is contained in:
parent
5ccc191b32
commit
00753b2769
4 changed files with 36 additions and 8 deletions
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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<HTMLImageElement | null> {
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -807,6 +807,13 @@ export default function RiderCardPage() {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{!metadata?.picture && (
|
||||
<p className="text-[11px] text-amber-400">
|
||||
{t('cardUi.studio.noPhoto')}{' '}
|
||||
<Link to="/settings?section=profile" className="underline">{t('cardUi.studio.addPhoto')}</Link>
|
||||
</p>
|
||||
)}
|
||||
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div>
|
||||
<Label className="mb-1.5 block text-[10px] uppercase tracking-widest text-gray-500">{t('labUi.rider.ridesFor')}</Label>
|
||||
|
|
|
|||
Loading…
Reference in a new issue