2025-12-17 02:03:33 +07:00
|
|
|
import { type ReactNode, type HTMLAttributes } from 'react'
|
|
|
|
|
import { clsx } from 'clsx'
|
|
|
|
|
|
|
|
|
|
interface GlassCardProps extends HTMLAttributes<HTMLDivElement> {
|
|
|
|
|
children: ReactNode
|
|
|
|
|
variant?: 'default' | 'dark' | 'neon' | 'gradient'
|
|
|
|
|
hover?: boolean
|
|
|
|
|
glow?: boolean
|
|
|
|
|
className?: string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function GlassCard({
|
|
|
|
|
children,
|
|
|
|
|
variant = 'default',
|
|
|
|
|
hover = false,
|
|
|
|
|
glow = false,
|
|
|
|
|
className,
|
|
|
|
|
...props
|
|
|
|
|
}: GlassCardProps) {
|
|
|
|
|
const variantClasses = {
|
|
|
|
|
default: 'glass',
|
|
|
|
|
dark: 'glass-dark',
|
|
|
|
|
neon: 'glass-neon',
|
|
|
|
|
gradient: 'gradient-border',
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div
|
|
|
|
|
className={clsx(
|
|
|
|
|
'rounded-xl p-6',
|
|
|
|
|
variantClasses[variant],
|
|
|
|
|
hover && 'card-hover cursor-pointer',
|
|
|
|
|
glow && 'neon-glow-pulse',
|
|
|
|
|
className
|
|
|
|
|
)}
|
|
|
|
|
{...props}
|
|
|
|
|
>
|
|
|
|
|
{children}
|
|
|
|
|
</div>
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Stats card variant
|
|
|
|
|
interface StatsCardProps {
|
|
|
|
|
label: string
|
|
|
|
|
value: string | number
|
|
|
|
|
icon?: ReactNode
|
|
|
|
|
trend?: {
|
|
|
|
|
value: number
|
|
|
|
|
isPositive: boolean
|
|
|
|
|
}
|
|
|
|
|
color?: 'neon' | 'purple' | 'pink' | 'default'
|
|
|
|
|
className?: string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function StatsCard({
|
|
|
|
|
label,
|
|
|
|
|
value,
|
|
|
|
|
icon,
|
|
|
|
|
trend,
|
|
|
|
|
color = 'default',
|
|
|
|
|
className,
|
|
|
|
|
}: StatsCardProps) {
|
|
|
|
|
const colorClasses = {
|
|
|
|
|
neon: 'border-neon-500/30 hover:border-neon-500/50',
|
|
|
|
|
purple: 'border-accent-500/30 hover:border-accent-500/50',
|
|
|
|
|
pink: 'border-pink-500/30 hover:border-pink-500/50',
|
|
|
|
|
default: 'border-dark-600 hover:border-dark-500',
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const iconColorClasses = {
|
|
|
|
|
neon: 'text-neon-500 bg-neon-500/10',
|
|
|
|
|
purple: 'text-accent-500 bg-accent-500/10',
|
|
|
|
|
pink: 'text-pink-500 bg-pink-500/10',
|
|
|
|
|
default: 'text-gray-400 bg-dark-700',
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const valueColorClasses = {
|
|
|
|
|
neon: 'text-neon-400',
|
|
|
|
|
purple: 'text-accent-400',
|
|
|
|
|
pink: 'text-pink-400',
|
|
|
|
|
default: 'text-white',
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div
|
|
|
|
|
className={clsx(
|
|
|
|
|
'glass rounded-xl p-4 border transition-all duration-300',
|
|
|
|
|
colorClasses[color],
|
|
|
|
|
'hover:-translate-y-0.5',
|
|
|
|
|
className
|
|
|
|
|
)}
|
|
|
|
|
>
|
2025-12-17 14:53:56 +07:00
|
|
|
<div className="flex items-center justify-between gap-3">
|
|
|
|
|
<div className="min-w-0 flex-1">
|
2025-12-17 02:03:33 +07:00
|
|
|
<p className="text-sm text-gray-400 mb-1">{label}</p>
|
2025-12-17 14:53:56 +07:00
|
|
|
<p className={clsx('text-2xl font-bold truncate', valueColorClasses[color])}>
|
2025-12-17 02:03:33 +07:00
|
|
|
{value}
|
|
|
|
|
</p>
|
|
|
|
|
{trend && (
|
|
|
|
|
<p
|
|
|
|
|
className={clsx(
|
|
|
|
|
'text-xs mt-1',
|
|
|
|
|
trend.isPositive ? 'text-green-400' : 'text-red-400'
|
|
|
|
|
)}
|
|
|
|
|
>
|
|
|
|
|
{trend.isPositive ? '+' : ''}{trend.value}%
|
|
|
|
|
</p>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
{icon && (
|
|
|
|
|
<div
|
|
|
|
|
className={clsx(
|
2025-12-17 14:53:56 +07:00
|
|
|
'w-12 h-12 rounded-lg flex items-center justify-center flex-shrink-0',
|
2025-12-17 02:03:33 +07:00
|
|
|
iconColorClasses[color]
|
|
|
|
|
)}
|
|
|
|
|
>
|
|
|
|
|
{icon}
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Feature card variant
|
|
|
|
|
interface FeatureCardProps {
|
|
|
|
|
title: string
|
|
|
|
|
description: string
|
|
|
|
|
icon: ReactNode
|
|
|
|
|
color?: 'neon' | 'purple' | 'pink'
|
|
|
|
|
className?: string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function FeatureCard({
|
|
|
|
|
title,
|
|
|
|
|
description,
|
|
|
|
|
icon,
|
|
|
|
|
color = 'neon',
|
|
|
|
|
className,
|
|
|
|
|
}: FeatureCardProps) {
|
|
|
|
|
const colorClasses = {
|
|
|
|
|
neon: {
|
|
|
|
|
icon: 'text-neon-500 bg-neon-500/10 group-hover:bg-neon-500/20',
|
|
|
|
|
border: 'group-hover:border-neon-500/50',
|
2025-12-17 14:53:56 +07:00
|
|
|
glow: 'group-hover:shadow-[0_0_20px_rgba(34,211,238,0.12)]',
|
2025-12-17 02:03:33 +07:00
|
|
|
},
|
|
|
|
|
purple: {
|
|
|
|
|
icon: 'text-accent-500 bg-accent-500/10 group-hover:bg-accent-500/20',
|
|
|
|
|
border: 'group-hover:border-accent-500/50',
|
2025-12-17 14:53:56 +07:00
|
|
|
glow: 'group-hover:shadow-[0_0_20px_rgba(139,92,246,0.12)]',
|
2025-12-17 02:03:33 +07:00
|
|
|
},
|
|
|
|
|
pink: {
|
|
|
|
|
icon: 'text-pink-500 bg-pink-500/10 group-hover:bg-pink-500/20',
|
|
|
|
|
border: 'group-hover:border-pink-500/50',
|
2025-12-17 14:53:56 +07:00
|
|
|
glow: 'group-hover:shadow-[0_0_20px_rgba(244,114,182,0.12)]',
|
2025-12-17 02:03:33 +07:00
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const colors = colorClasses[color]
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div
|
|
|
|
|
className={clsx(
|
|
|
|
|
'group glass rounded-xl p-6 border border-dark-600 transition-all duration-300',
|
|
|
|
|
'hover:-translate-y-1',
|
|
|
|
|
colors.border,
|
|
|
|
|
colors.glow,
|
|
|
|
|
className
|
|
|
|
|
)}
|
|
|
|
|
>
|
|
|
|
|
<div
|
|
|
|
|
className={clsx(
|
|
|
|
|
'w-14 h-14 rounded-xl flex items-center justify-center mb-4 transition-colors',
|
|
|
|
|
colors.icon
|
|
|
|
|
)}
|
|
|
|
|
>
|
|
|
|
|
{icon}
|
|
|
|
|
</div>
|
|
|
|
|
<h3 className="text-lg font-semibold text-white mb-2">{title}</h3>
|
|
|
|
|
<p className="text-gray-400 text-sm">{description}</p>
|
|
|
|
|
</div>
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Interactive card with animated border
|
|
|
|
|
interface AnimatedBorderCardProps extends HTMLAttributes<HTMLDivElement> {
|
|
|
|
|
children: ReactNode
|
|
|
|
|
className?: string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function AnimatedBorderCard({
|
|
|
|
|
children,
|
|
|
|
|
className,
|
|
|
|
|
...props
|
|
|
|
|
}: AnimatedBorderCardProps) {
|
|
|
|
|
return (
|
|
|
|
|
<div className={clsx('relative group', className)} {...props}>
|
|
|
|
|
{/* Animated gradient border */}
|
|
|
|
|
<div
|
|
|
|
|
className="absolute -inset-0.5 bg-gradient-to-r from-neon-500 via-accent-500 to-pink-500 rounded-xl opacity-30 group-hover:opacity-60 blur transition-opacity duration-300"
|
|
|
|
|
style={{
|
|
|
|
|
backgroundSize: '200% 200%',
|
|
|
|
|
animation: 'gradient-flow 3s linear infinite',
|
|
|
|
|
}}
|
|
|
|
|
/>
|
|
|
|
|
{/* Card content */}
|
|
|
|
|
<div className="relative glass-dark rounded-xl p-6">{children}</div>
|
|
|
|
|
</div>
|
|
|
|
|
)
|
|
|
|
|
}
|