import GlassSurface from './GlassSurface'; /** * StatCard — dashboard stat surface from the redesign-v2 mockup. * * Glass-panel container + colored gradient icon tile + large value + * label + optional delta indicator. Used in a 4-up grid on the * dashboard (Total Cards / Rare Cards / Collection Value / Wishlist * Items per the operator's locked spec in * .convoys/redesign-v2-from-mockups.md § 7.1). * * Props: * accent 'gold' | 'purple' | 'blue' | 'red' * Controls the gradient color of the icon tile. Each accent * reads from a :root token (--stat-accent-) so dark/ * light variants stay consistent. * icon ReactNode rendered inside the gradient tile, white-tinted. * Typically an inline SVG set; pass `null` for an * icon-less variant (useful in the "Collection Value" card * which leads with a dollar glyph rendered inline). * label string — small label above the value ("Rare Cards"). * value ReactNode — the large primary metric ("317", "$1,248"). * delta string | null — optional metric like "+12.5%" or "-2.1%". * Sign drives the color (green positive, red negative). * When null/undefined, the delta row is omitted. * subtitle string — extra context below the delta ("12.9% of * collection", "7 new price drops"). Optional. * className passes through to the GlassSurface root. * * Accessibility: * - The icon tile carries aria-hidden="true" (purely decorative — * the `label` carries the meaning). * - Delta uses semantic color from the design tokens * (--accent-success / --accent-danger) — the sign-glyph (▲/▼) * and the explicit "+" / "-" in the value text are the * non-color cues for AA compliance. */ const STAT_ACCENT_GRADIENTS = { gold: 'linear-gradient(135deg, rgb(255, 200, 70) 0%, rgb(217, 142, 0) 100%)', purple: 'linear-gradient(135deg, rgb(178, 102, 255) 0%, rgb(124, 58, 237) 100%)', blue: 'linear-gradient(135deg, rgb(70, 162, 255) 0%, rgb(29, 110, 211) 100%)', red: 'linear-gradient(135deg, rgb(255, 110, 90) 0%, rgb(216, 60, 60) 100%)', }; const STAT_ACCENT_SHADOWS = { gold: '0 4px 14px -2px rgba(217, 142, 0, 0.45), inset 0 1px 0 rgba(255,255,255,0.25)', purple: '0 4px 14px -2px rgba(124, 58, 237, 0.45), inset 0 1px 0 rgba(255,255,255,0.25)', blue: '0 4px 14px -2px rgba(29, 110, 211, 0.45), inset 0 1px 0 rgba(255,255,255,0.25)', red: '0 4px 14px -2px rgba(216, 60, 60, 0.45), inset 0 1px 0 rgba(255,255,255,0.25)', }; function parseDeltaSign(delta) { if (delta == null) return null; const trimmed = String(delta).trim(); if (trimmed.startsWith('+')) return 'positive'; if (trimmed.startsWith('-')) return 'negative'; return 'neutral'; } export default function StatCard({ accent = 'gold', icon = null, label, value, delta = null, subtitle = null, className = '', }) { const gradient = STAT_ACCENT_GRADIENTS[accent] ?? STAT_ACCENT_GRADIENTS.gold; const shadow = STAT_ACCENT_SHADOWS[accent] ?? STAT_ACCENT_SHADOWS.gold; const deltaSign = parseDeltaSign(delta); const deltaColor = deltaSign === 'positive' ? 'rgb(34, 197, 94)' : deltaSign === 'negative' ? 'rgb(239, 68, 68)' : 'var(--text-secondary)'; const deltaGlyph = deltaSign === 'positive' ? '▲' : deltaSign === 'negative' ? '▼' : ''; return (
{label}
{value}
{(delta || subtitle) && (
{delta && ( {deltaGlyph && ( )} {delta} )} {subtitle && {subtitle}}
)}
); }