11'use client' ;
22
33import Image from 'next/image' ;
4+ import { motion } from 'framer-motion' ;
45import { Crown } from 'lucide-react' ;
5- import { useTranslations } from 'next-intl' ;
66import { cn } from '@/lib/utils' ;
77import { User } from './types' ;
88
99export function LeaderboardPodium ( { topThree } : { topThree : User [ ] } ) {
10- const t = useTranslations ( 'leaderboard' ) ;
10+ const podiumOrder = [
11+ topThree . find ( u => u . rank === 2 ) ,
12+ topThree . find ( u => u . rank === 1 ) ,
13+ topThree . find ( u => u . rank === 3 ) ,
14+ ] . filter ( Boolean ) as User [ ] ;
1115
1216 return (
13- < ol
14- className = "flex items-end justify-center gap-4 md:gap-8 pb-4 pt-16 min-h-[400px] list-none m-0 p-0"
15- aria-label = { t ( 'topThreeLabel' ) }
16- >
17- { topThree . map ( user => {
18- if ( ! user ) return null ;
17+ < div className = "flex items-end justify-center gap-4 md:gap-8 h-[350px] w-full max-w-3xl mx-auto" >
18+ { podiumOrder . map ( ( user ) => {
1919 const isFirst = user . rank === 1 ;
2020 const isSecond = user . rank === 2 ;
21- const isThird = user . rank === 3 ;
21+
22+ const height = isFirst ? '100%' : isSecond ? '45%' : '30%' ;
23+ const delay = isFirst ? 0.4 : isSecond ? 0.2 : 0.6 ;
2224
2325 return (
24- < li
25- key = { user . id }
26- className = { cn (
27- 'flex flex-col items-center transition-all duration-500 relative z-0' ,
28- isFirst ? 'order-2 z-10 -mt-12' : '' ,
29- isSecond ? 'order-1' : '' ,
30- isThird ? 'order-3' : ''
31- ) }
32- >
33- < div className = "flex flex-col items-center group w-full" >
34- < div className = "relative mb-4 transition-transform duration-300 group-hover:scale-105" >
26+ < div key = { user . id } className = "relative flex flex-col items-center justify-end w-1/3 h-full" >
27+
28+ < motion . div
29+ initial = { { opacity : 0 , y : 20 } }
30+ animate = { { opacity : 1 , y : 0 } }
31+ transition = { { delay : delay + 0.5 , duration : 0.5 } }
32+ className = "mb-4 flex flex-col items-center text-center z-10"
33+ >
34+ < div className = "relative mb-2" >
3535 { isFirst && (
36- < Crown
37- className = "absolute -top-12 left-1/2 -translate-x-1/2 w-10 h-10 text-yellow-400 animate-bounce drop-shadow-[0_0_15px_rgba(250,204,21,0.6)]"
38- aria-hidden = "true "
36+ < Crown
37+ className = "absolute -top-8 left-1/2 -translate-x-1/2 w-6 h-6 text-[var(--accent-primary)] animate-bounce"
38+ fill = "currentColor "
3939 />
4040 ) }
41-
42- < div
43- className = { cn (
44- 'relative rounded-full p-[2px]' ,
45- isFirst
46- ? 'bg-gradient-to-b from-yellow-300 to-yellow-600'
47- : isSecond
48- ? 'bg-gradient-to-b from-slate-300 to-slate-500'
49- : 'bg-gradient-to-b from-orange-300 to-orange-600'
50- ) }
51- >
52- < div className = "relative w-20 h-20 md:w-24 md:h-24 rounded-full overflow-hidden border-4 border-white dark:border-slate-950 bg-slate-200 dark:bg-slate-900" >
41+ < div className = { cn (
42+ "relative w-16 h-16 md:w-20 md:h-20 rounded-full p-1 transition-colors duration-300" ,
43+ "border-2" ,
44+ isFirst
45+ ? "border-[var(--accent-primary)]"
46+ : "border-gray-200 dark:border-white/20"
47+ ) } >
48+ < div className = "relative w-full h-full rounded-full overflow-hidden bg-gray-100 dark:bg-black" >
5349 < Image
5450 src = { user . avatar }
55- alt = { ` ${ user . username } 's avatar` }
51+ alt = { user . username }
5652 fill
5753 className = "object-cover"
5854 />
5955 </ div >
60- </ div >
61-
62- < div
63- className = { cn (
64- 'absolute -bottom-3 left-1/2 -translate-x-1/2 flex items-center justify-center w-8 h-8 rounded-full font-bold text-sm border-4 border-white dark:border-slate-950 shadow-lg' ,
65- isFirst
66- ? 'bg-yellow-500 text-white'
67- : isSecond
68- ? 'bg-slate-400 text-slate-900'
69- : 'bg-orange-500 text-white'
70- ) }
71- >
72- { user . rank }
73- </ div >
74- </ div >
75-
76- < div className = "text-center mb-4" >
77- < div className = "font-bold text-slate-900 dark:text-white text-sm md:text-lg mb-1 truncate max-w-[120px]" >
78- { user . username }
79- </ div >
80- < div className = "inline-block px-3 py-1 rounded-full bg-white/60 dark:bg-white/5 border border-slate-300 dark:border-white/10 backdrop-blur-sm" >
81- < div className = "font-mono font-bold text-xs md:text-sm text-[#ff2d55]" >
82- { user . points } { ' ' }
56+ < div className = { cn (
57+ "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" ,
58+ isFirst
59+ ? "bg-[var(--accent-primary)]"
60+ : "bg-gray-500 dark:bg-gray-700"
61+ ) } >
62+ { user . rank }
8363 </ div >
8464 </ div >
8565 </ div >
86-
87- < div
88- aria-hidden = "true"
89- className = { cn (
90- 'w-24 md:w-40 rounded-t-2xl backdrop-blur-md transition-all duration-300 relative overflow-hidden' ,
91- 'border-x border-t border-slate-300 dark:border-white/10 bg-white/60 dark:bg-white/5' ,
92-
93- isFirst
94- ? 'h-48 md:h-64'
95- : isSecond
96- ? 'h-32 md:h-44'
97- : 'h-24 md:h-32' ,
98-
99- isFirst
100- ? 'shadow-[0_0_40px_-10px_rgba(234,179,8,0.2)] dark:shadow-[0_0_40px_-10px_rgba(234,179,8,0.3)] after:absolute after:inset-0 after:bg-gradient-to-b after:from-yellow-500/10 after:to-transparent'
101- : isSecond
102- ? 'shadow-[0_0_40px_-10px_rgba(148,163,184,0.2)] dark:shadow-[0_0_40px_-10px_rgba(148,163,184,0.2)] after:absolute after:inset-0 after:bg-gradient-to-b after:from-slate-400/10 after:to-transparent'
103- : 'shadow-[0_0_40px_-10px_rgba(249,115,22,0.2)] dark:shadow-[0_0_40px_-10px_rgba(249,115,22,0.2)] after:absolute after:inset-0 after:bg-gradient-to-b after:from-orange-500/10 after:to-transparent'
104- ) }
105- >
106- < div className = "absolute inset-0 bg-[linear-gradient(to_right,#80808012_1px,transparent_1px),linear-gradient(to_bottom,#80808012_1px,transparent_1px)] bg-[size:16px_16px] opacity-50" />
66+
67+ < div className = "font-bold text-gray-900 dark:text-white text-sm md:text-base truncate max-w-[100px] md:max-w-[140px]" >
68+ { user . username }
69+ </ div >
70+ < div className = "font-mono text-xs font-bold text-[var(--accent-primary)]" >
71+ { user . points }
10772 </ div >
108- </ div >
109- </ li >
73+ </ motion . div >
74+
75+ < motion . div
76+ initial = { { height : 0 } }
77+ animate = { { height : height } }
78+ transition = { {
79+ duration : 0.8 ,
80+ delay : delay ,
81+ type : "spring" ,
82+ stiffness : 60 ,
83+ damping : 15
84+ } }
85+ className = { cn (
86+ "w-full rounded-t-2xl relative overflow-hidden backdrop-blur-xl border-x border-t transition-colors duration-300" ,
87+ "bg-white/60 border-gray-100 dark:bg-[#111]/60 dark:border-white/5" ,
88+
89+ isFirst
90+ ? "shadow-[0_0_50px_-15px_var(--accent-primary)]"
91+ : "shadow-[0_-10px_40px_-15px_rgba(0,0,0,0.1)] dark:shadow-[0_-10px_40px_-15px_rgba(0,0,0,0.5)]"
92+ ) }
93+ >
94+ < div className = { cn (
95+ "w-full h-1.5 absolute top-0 left-0 transition-colors duration-300" ,
96+ isFirst
97+ ? "bg-gradient-to-r from-[var(--accent-primary)] to-[var(--accent-hover)]"
98+ : "bg-gray-200 dark:bg-white/10"
99+ ) } />
100+
101+
102+ </ motion . div >
103+ </ div >
110104 ) ;
111105 } ) }
112- </ ol >
106+ </ div >
113107 ) ;
114- }
108+ }
0 commit comments