134 lines
4.0 KiB
TypeScript
134 lines
4.0 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useState } from "react";
|
|
|
|
interface PlayerStatsProps {
|
|
username?: string;
|
|
avatar?: string;
|
|
hp?: number;
|
|
maxHp?: number;
|
|
xp?: number;
|
|
maxXp?: number;
|
|
level?: number;
|
|
}
|
|
|
|
// Format number with consistent locale to avoid hydration mismatch
|
|
const formatNumber = (num: number): string => {
|
|
return num.toLocaleString("en-US");
|
|
};
|
|
|
|
export default function PlayerStats({
|
|
username = "DragonSlayer99",
|
|
avatar = "/got-2.jpg",
|
|
hp = 750,
|
|
maxHp = 1000,
|
|
xp = 3250,
|
|
maxXp = 5000,
|
|
level = 42,
|
|
}: PlayerStatsProps) {
|
|
const [hpPercentage, setHpPercentage] = useState(0);
|
|
const [xpPercentage, setXpPercentage] = useState(0);
|
|
|
|
useEffect(() => {
|
|
// Animate HP bar
|
|
const hpTimer = setTimeout(() => {
|
|
setHpPercentage((hp / maxHp) * 100);
|
|
}, 100);
|
|
|
|
// Animate XP bar
|
|
const xpTimer = setTimeout(() => {
|
|
setXpPercentage((xp / maxXp) * 100);
|
|
}, 200);
|
|
|
|
return () => {
|
|
clearTimeout(hpTimer);
|
|
clearTimeout(xpTimer);
|
|
};
|
|
}, [hp, maxHp, xp, maxXp]);
|
|
|
|
const hpColor =
|
|
hpPercentage > 60
|
|
? "from-green-600 to-green-700"
|
|
: hpPercentage > 30
|
|
? "from-yellow-600 to-orange-700"
|
|
: "from-red-700 to-red-900";
|
|
|
|
return (
|
|
<div className="flex items-center gap-3">
|
|
{/* Avatar */}
|
|
<div className="w-10 h-10 rounded-full border border-pixel-gold/20 overflow-hidden bg-gray-900">
|
|
<img
|
|
src={avatar}
|
|
alt={username}
|
|
className="w-full h-full object-cover"
|
|
/>
|
|
</div>
|
|
|
|
{/* Stats */}
|
|
<div className="flex flex-col gap-1.5 min-w-[200px]">
|
|
{/* Username & Level */}
|
|
<div className="flex items-center gap-2">
|
|
<div className="text-pixel-gold font-gaming font-bold text-sm tracking-wider">
|
|
{username}
|
|
</div>
|
|
<div className="text-gray-400 font-pixel text-xs uppercase border border-pixel-gold/30 px-1.5 py-0.5 bg-black/40">
|
|
Lv.{level}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Bars side by side */}
|
|
<div className="flex flex-col gap-1">
|
|
<div className="flex items-center gap-2">
|
|
{/* HP Bar */}
|
|
<div className="relative h-2 flex-1 bg-gray-900 border border-gray-700 rounded overflow-hidden">
|
|
<div
|
|
className={`absolute inset-0 bg-gradient-to-r ${hpColor} transition-all duration-1000 ease-out`}
|
|
style={{ width: `${hpPercentage}%` }}
|
|
>
|
|
<div className="absolute inset-0 bg-gradient-to-r from-transparent via-white/10 to-transparent animate-shimmer"></div>
|
|
</div>
|
|
{hpPercentage < 30 && (
|
|
<div className="absolute inset-0 border border-red-500 rounded animate-pulse"></div>
|
|
)}
|
|
</div>
|
|
|
|
{/* XP Bar */}
|
|
<div className="relative h-2 flex-1 bg-gray-900 border border-pixel-gold/30 rounded overflow-hidden">
|
|
<div
|
|
className="absolute inset-0 bg-gradient-to-r from-pixel-gold/80 via-pixel-gold/70 to-pixel-gold/80 transition-all duration-1000 ease-out"
|
|
style={{ width: `${xpPercentage}%` }}
|
|
>
|
|
<div className="absolute inset-0 bg-gradient-to-r from-transparent via-white/10 to-transparent animate-shimmer"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{/* Labels */}
|
|
<div className="flex items-center gap-2 text-[8px] font-pixel text-gray-400">
|
|
<div className="flex-1 text-left">
|
|
HP {hp} / {maxHp}
|
|
</div>
|
|
<div className="flex-1 text-right">
|
|
XP {formatNumber(xp)} / {formatNumber(maxXp)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<style jsx>{`
|
|
@keyframes shimmer {
|
|
0% {
|
|
transform: translateX(-100%);
|
|
}
|
|
100% {
|
|
transform: translateX(100%);
|
|
}
|
|
}
|
|
.animate-shimmer {
|
|
animation: shimmer 2s infinite;
|
|
}
|
|
`}</style>
|
|
</div>
|
|
);
|
|
}
|
|
|