147 lines
4.8 KiB
TypeScript
147 lines
4.8 KiB
TypeScript
'use client'
|
|
import { motion } from 'framer-motion'
|
|
import { cn } from '@/lib/utils'
|
|
|
|
interface Props {
|
|
scanned: number
|
|
alerts: number
|
|
keywords: number
|
|
uptime: string
|
|
}
|
|
|
|
const CARDS = [
|
|
{
|
|
id: 'scanned',
|
|
label: 'Lots Scanned',
|
|
sub: 'Processed this session',
|
|
gradFrom: '#00e87b',
|
|
gradTo: '#06b6d4',
|
|
icon: (
|
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
|
|
<circle cx="11" cy="11" r="8"/><path d="m21 21-4.35-4.35"/>
|
|
</svg>
|
|
),
|
|
},
|
|
{
|
|
id: 'alerts',
|
|
label: 'Alerts Fired',
|
|
sub: 'Qualifying matches',
|
|
gradFrom: '#fbbf24',
|
|
gradTo: '#f59e0b',
|
|
icon: (
|
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
|
|
<path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.73 21a2 2 0 0 1-3.46 0"/>
|
|
</svg>
|
|
),
|
|
},
|
|
{
|
|
id: 'keywords',
|
|
label: 'Active Targets',
|
|
sub: 'Keyword strategies',
|
|
gradFrom: '#a78bfa',
|
|
gradTo: '#3b82f6',
|
|
icon: (
|
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
|
|
<path d="M20.59 13.41l-7.17 7.17a2 2 0 0 1-2.83 0L2 12V2h10l8.59 8.59a2 2 0 0 1 0 2.82z"/><line x1="7" y1="7" x2="7.01" y2="7"/>
|
|
</svg>
|
|
),
|
|
},
|
|
{
|
|
id: 'uptime',
|
|
label: 'Engine Uptime',
|
|
sub: 'Continuous runtime',
|
|
gradFrom: '#3b82f6',
|
|
gradTo: '#06b6d4',
|
|
icon: (
|
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
|
|
<circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/>
|
|
</svg>
|
|
),
|
|
},
|
|
]
|
|
|
|
export default function StatsGrid({ scanned, alerts, keywords, uptime }: Props) {
|
|
const values: Record<string, string | number> = {
|
|
scanned: String(scanned),
|
|
alerts: alerts,
|
|
keywords: keywords,
|
|
uptime: uptime,
|
|
}
|
|
|
|
return (
|
|
<div className="grid grid-cols-2 xl:grid-cols-4 gap-4">
|
|
{CARDS.map((card, i) => {
|
|
const val = values[card.id]
|
|
const isAlert = card.id === 'alerts' && alerts > 0
|
|
const accentColor = isAlert ? card.gradFrom : undefined
|
|
|
|
return (
|
|
<motion.div
|
|
key={card.id}
|
|
initial={{ opacity: 1, y: 18, scale: 0.97 }}
|
|
animate={{ opacity: 1, y: 0, scale: 1 }}
|
|
transition={{ duration: 0.55, delay: i * 0.07, ease: [0.22, 1, 0.36, 1] }}
|
|
className="g-card-glow p-5 flex flex-col gap-3.5 group cursor-default select-none"
|
|
>
|
|
{/* Top row: label + icon */}
|
|
<div className="relative z-10 flex items-start justify-between gap-2">
|
|
<span className="text-[10px] font-bold uppercase tracking-[0.14em] text-g-faint leading-none mt-0.5">
|
|
{card.label}
|
|
</span>
|
|
<div
|
|
className="shrink-0 w-7 h-7 rounded-lg flex items-center justify-center transition-all duration-300 group-hover:scale-110"
|
|
style={{
|
|
background: `linear-gradient(135deg, ${card.gradFrom}18, ${card.gradTo}10)`,
|
|
border: `1px solid ${card.gradFrom}25`,
|
|
color: card.gradFrom,
|
|
boxShadow: `0 0 12px ${card.gradFrom}15`,
|
|
}}
|
|
>
|
|
{card.icon}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Value */}
|
|
<div className="relative z-10">
|
|
<span
|
|
className={cn(
|
|
'g-stat-num transition-colors duration-300',
|
|
isAlert ? 'text-g-amber' : 'text-g-text',
|
|
)}
|
|
style={accentColor ? {} : {}}
|
|
>
|
|
{val}
|
|
</span>
|
|
</div>
|
|
|
|
{/* Sub */}
|
|
<p className="relative z-10 text-[10px] text-g-faint/50 leading-none font-medium">
|
|
{card.sub}
|
|
</p>
|
|
|
|
{/* Bottom gradient line */}
|
|
<div
|
|
className="absolute bottom-0 left-0 right-0 h-px opacity-20 group-hover:opacity-40 transition-opacity"
|
|
style={{
|
|
background: `linear-gradient(90deg, transparent, ${card.gradFrom}80, ${card.gradTo}50, transparent)`,
|
|
}}
|
|
/>
|
|
|
|
{/* Alert pulse ring for alerts card when active */}
|
|
{isAlert && (
|
|
<div
|
|
className="absolute top-4 right-4 w-1.5 h-1.5 rounded-full"
|
|
style={{
|
|
background: card.gradFrom,
|
|
boxShadow: `0 0 6px ${card.gradFrom}`,
|
|
animation: 'pulse-ring 2s ease-out infinite',
|
|
}}
|
|
/>
|
|
)}
|
|
</motion.div>
|
|
)
|
|
})}
|
|
</div>
|
|
)
|
|
}
|