Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 6 additions & 8 deletions frontend/components/leaderboard/LeaderboardClient.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,25 +24,23 @@ export default function LeaderboardClient({

return (
<div className="relative min-h-screen w-full">

<div className="fixed inset-0 z-0">
<DynamicGridBackground className="w-full h-full bg-gray-50 transition-colors duration-300 dark:bg-transparent" />
</div>

<div className="relative z-10 w-full max-w-5xl mx-auto px-4 sm:px-6 lg:px-8 flex flex-col items-center pt-20 pb-10">

<div className="relative z-10 w-full max-w-5xl mx-auto px-4 sm:px-6 lg:px-8 flex flex-col items-center pt-20 pb-10">
<header className="text-center mb-16 max-w-3xl">
<motion.div
initial={{ y: 20, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
transition={{ duration: 0.6 }}
>
<h1 className="text-4xl md:text-6xl lg:text-7xl font-black tracking-tight text-[var(--accent-primary)] mb-6 uppercase drop-shadow-sm">
<h1 className="text-4xl md:text-6xl lg:text-7xl font-black tracking-tight text-[var(--accent-primary)] mb-6 drop-shadow-sm">
{t('title')}
</h1>
</motion.div>
<motion.p

<motion.p
initial={{ y: 20, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
transition={{ duration: 0.6, delay: 0.1 }}
Expand All @@ -57,7 +55,7 @@ export default function LeaderboardClient({
{hasResults ? (
<LeaderboardPodium topThree={topThree} />
) : (
<motion.div
<motion.div
initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }}
className="text-center py-20 rounded-2xl border border-gray-200 dark:border-white/10 bg-white/60 dark:bg-[#111]/60 backdrop-blur-xl shadow-xl"
Expand All @@ -80,4 +78,4 @@ export default function LeaderboardClient({
</div>
</div>
);
}
}
131 changes: 82 additions & 49 deletions frontend/components/leaderboard/LeaderboardPodium.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,36 +15,67 @@ export function LeaderboardPodium({ topThree }: { topThree: User[] }) {

return (
<div className="flex items-end justify-center gap-4 md:gap-8 h-[350px] w-full max-w-3xl mx-auto">
{podiumOrder.map((user) => {
const isFirst = user.rank === 1;
const isSecond = user.rank === 2;

const height = isFirst ? '100%' : isSecond ? '45%' : '30%';
const delay = isFirst ? 0.4 : isSecond ? 0.2 : 0.6;
{podiumOrder.map(user => {
const rank = user.rank;
const isFirst = rank === 1;

const height = rank === 1 ? '100%' : rank === 2 ? '40%' : '35%';
const delay = rank === 1 ? 0.4 : rank === 2 ? 0.2 : 0.6;

const rankStyles = {
1: {
border: 'border-yellow-400 dark:border-yellow-500',
bg: 'bg-yellow-400/20 dark:bg-yellow-500/10',
text: 'text-yellow-600 dark:text-yellow-400',
badge: 'bg-yellow-400 dark:bg-yellow-500',
ring: 'border-yellow-400 dark:border-yellow-500',
barTop: 'bg-yellow-400 dark:bg-yellow-500',
},
2: {
border: 'border-slate-300 dark:border-slate-500',
bg: 'bg-slate-300/20 dark:bg-slate-500/10',
text: 'text-slate-600 dark:text-slate-400',
badge: 'bg-slate-400 dark:bg-slate-500',
ring: 'border-slate-300 dark:border-slate-500',
barTop: 'bg-slate-300 dark:bg-slate-500',
},
3: {
border: 'border-orange-300 dark:border-orange-500',
bg: 'bg-orange-300/20 dark:bg-orange-500/10',
text: 'text-orange-600 dark:text-orange-400',
badge: 'bg-orange-400 dark:bg-orange-500',
ring: 'border-orange-300 dark:border-orange-500',
barTop: 'bg-orange-300 dark:bg-orange-500',
},
};

const style = rankStyles[rank as 1 | 2 | 3] || rankStyles[2];

return (
<div key={user.id} className="relative flex flex-col items-center justify-end w-1/3 h-full">

<motion.div
<div
key={user.id}
className="relative flex flex-col items-center justify-end w-1/3 h-full"
>
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: delay + 0.5, duration: 0.5 }}
className="mb-4 flex flex-col items-center text-center z-10"
className="mb-3 md:mb-4 flex flex-col items-center text-center z-10"
>
<div className="relative mb-2">
{isFirst && (
<Crown
className="absolute -top-8 left-1/2 -translate-x-1/2 w-6 h-6 text-[var(--accent-primary)] animate-bounce"
<Crown
className="absolute -top-6 md:-top-8 left-1/2 -translate-x-1/2 w-5 h-5 md:w-6 md:h-6 text-yellow-500 animate-bounce"
fill="currentColor"
/>
)}
<div className={cn(
"relative w-16 h-16 md:w-20 md:h-20 rounded-full p-1 transition-colors duration-300",
"border-2",
isFirst
? "border-[var(--accent-primary)]"
: "border-gray-200 dark:border-white/20"
)}>

<div
className={cn(
'relative w-14 h-14 md:w-20 md:h-20 rounded-full p-1 transition-colors duration-300 border-2',
style.ring
)}
>
<div className="relative w-full h-full rounded-full overflow-hidden bg-gray-100 dark:bg-black">
<Image
src={user.avatar}
Expand All @@ -53,56 +84,58 @@ export function LeaderboardPodium({ topThree }: { topThree: User[] }) {
className="object-cover"
/>
</div>
<div className={cn(
"absolute -bottom-2 left-1/2 -translate-x-1/2 w-6 h-6 rounded-full flex items-center justify-center text-xs font-bold text-white shadow-md transition-colors duration-300",
isFirst
? "bg-[var(--accent-primary)]"
: "bg-gray-500 dark:bg-gray-700"
)}>

<div
className={cn(
'absolute -bottom-2 left-1/2 -translate-x-1/2 w-5 h-5 md:w-6 md:h-6 rounded-full flex items-center justify-center text-[10px] md:text-xs font-bold text-white shadow-sm transition-colors duration-300',
style.badge
)}
>
{user.rank}
</div>
</div>
</div>
<div className="font-bold text-gray-900 dark:text-white text-sm md:text-base truncate max-w-[100px] md:max-w-[140px]">

<div className="font-bold text-gray-900 dark:text-white text-xs md:text-base truncate max-w-[90px] md:max-w-[140px]">
{user.username}
</div>
<div className="font-mono text-xs font-bold text-[var(--accent-primary)]">
{user.points}

<div
className={cn(
'font-mono text-[10px] md:text-xs font-bold mt-0.5',
style.text
)}
>
{user.points}
</div>
</motion.div>

<motion.div
initial={{ height: 0 }}
animate={{ height: height }}
transition={{
duration: 0.8,
delay: delay,
type: "spring",
transition={{
duration: 0.8,
delay: delay,
type: 'spring',
stiffness: 60,
damping: 15
damping: 15,
}}
className={cn(
"w-full rounded-t-2xl relative overflow-hidden backdrop-blur-xl border-x border-t transition-colors duration-300",
"bg-white/60 border-gray-100 dark:bg-[#111]/60 dark:border-white/5",

isFirst
? "shadow-[0_0_50px_-15px_var(--accent-primary)]"
: "shadow-[0_-10px_40px_-15px_rgba(0,0,0,0.1)] dark:shadow-[0_-10px_40px_-15px_rgba(0,0,0,0.5)]"
'w-full rounded-t-xl md:rounded-t-2xl relative overflow-hidden backdrop-blur-md border-x border-t transition-colors duration-300',
style.bg,
style.border
)}
>
<div className={cn(
"w-full h-1.5 absolute top-0 left-0 transition-colors duration-300",
isFirst
? "bg-gradient-to-r from-[var(--accent-primary)] to-[var(--accent-hover)]"
: "bg-gray-200 dark:bg-white/10"
)} />


<div
className={cn(
'w-full h-1 md:h-1.5 absolute top-0 left-0 opacity-80',
style.barTop
)}
/>
</motion.div>
</div>
);
})}
</div>
);
}
}
Loading