/** * Hall of Fame - React Edition v3.0 * Premium Design with SVG Icons - Uses shared CoinRushNavbar */ const { useState, useEffect, useRef, useCallback } = React; // Use shared CoinRushNavbar const CoinRushNavbar = window.CoinRushNavbar; // ============================================ // SVG ICONS - Premium Design // ============================================ const Icons = { trophy: ( ), crown: ( ), medal: ( ), star: ( ), fire: ( ), coins: ( ), chart: ( ), lightning: ( ), users: ( ), gamepad: ( ), clock: ( ), dice: ( ), arrow: ( ), chevronDown: ( ), close: ( ), check: ( ), gift: ( ), info: ( ) }; // ============================================ // UTILITIES // ============================================ const fmt = (n) => new Intl.NumberFormat().format(n || 0); const fmtMoney = (cents) => { const d = (cents || 0) / 100; return d >= 1000 ? `$${(d/1000).toFixed(1)}K` : `$${d.toFixed(2)}`; }; const countdown = (end) => { const ms = end - Date.now(); if (ms <= 0) return { days: 0, hours: 0, mins: 0, secs: 0 }; return { days: Math.floor(ms / 86400000), hours: Math.floor((ms % 86400000) / 3600000), mins: Math.floor((ms % 3600000) / 60000), secs: Math.floor((ms % 60000) / 1000) }; }; const REWARDS = { weekly: ['$200', '$100', '$50'], monthly: ['$3,000', '$1,500', '$500'], }; // ============================================ // INTERACTIVE HOOKS & COMPONENTS // ============================================ // Ripple effect for buttons function useRipple() { const createRipple = (e) => { const btn = e.currentTarget; const circle = document.createElement('span'); const rect = btn.getBoundingClientRect(); const size = Math.max(rect.width, rect.height); circle.style.cssText = ` position: absolute; width: ${size}px; height: ${size}px; left: ${e.clientX - rect.left - size/2}px; top: ${e.clientY - rect.top - size/2}px; background: rgba(255,255,255,0.3); border-radius: 50%; transform: scale(0); animation: ripple-effect 0.6s ease-out; pointer-events: none; `; btn.style.position = 'relative'; btn.style.overflow = 'hidden'; btn.appendChild(circle); setTimeout(() => circle.remove(), 600); }; return createRipple; } // Tooltip component function Tooltip({ children, text, position = 'top' }) { const [show, setShow] = useState(false); return (
setShow(true)} onMouseLeave={() => setShow(false)} > {children} {show &&
{text}
}
); } // Sparkle effect for medals function Sparkles({ active }) { if (!active) return null; return (
{[...Array(6)].map((_, i) => (
))}
); } // Progress bar with animation function ProgressBar({ value, max, label, color = 'green' }) { const [width, setWidth] = useState(0); const percent = Math.min((value / max) * 100, 100); useEffect(() => { setTimeout(() => setWidth(percent), 100); }, [percent]); return (
{label &&
{label}
}
{value.toLocaleString()} / {max.toLocaleString()}
); } // Intersection Observer hook for scroll animations function useOnScreen(ref, threshold = 0.1) { const [isVisible, setIsVisible] = useState(false); useEffect(() => { const observer = new IntersectionObserver( ([entry]) => { if (entry.isIntersecting) { setIsVisible(true); observer.disconnect(); } }, { threshold } ); if (ref.current) observer.observe(ref.current); return () => observer.disconnect(); }, [ref, threshold]); return isVisible; } // ============================================ // ANIMATED NUMBER // ============================================ function AnimatedNumber({ value, prefix = '', suffix = '', duration = 1500 }) { const [display, setDisplay] = useState(0); const animRef = useRef(null); useEffect(() => { const start = display; const target = value; const t0 = performance.now(); const tick = (t) => { const p = Math.min((t - t0) / duration, 1); const eased = 1 - Math.pow(1 - p, 4); setDisplay(Math.floor(start + (target - start) * eased)); if (p < 1) animRef.current = requestAnimationFrame(tick); }; animRef.current = requestAnimationFrame(tick); return () => cancelAnimationFrame(animRef.current); }, [value]); return {prefix}{fmt(display)}{suffix}; } // ============================================ // COUNTDOWN TIMER - Premium Design // ============================================ function CountdownTimer({ type }) { const [time, setTime] = useState({ days: 0, hours: 0, mins: 0, secs: 0 }); useEffect(() => { const update = () => { const now = new Date(); let end; if (type === 'weekly') { end = new Date(now); end.setDate(now.getDate() + (7 - now.getDay())); end.setHours(23, 59, 59); } else { end = new Date(now.getFullYear(), now.getMonth() + 1, 0, 23, 59, 59); } setTime(countdown(end)); }; update(); const interval = setInterval(update, 1000); return () => clearInterval(interval); }, [type]); return (
{Icons.clock}
{String(time.days).padStart(2, '0')} D
:
{String(time.hours).padStart(2, '0')} H
:
{String(time.mins).padStart(2, '0')} M
); } // ============================================ // HERO SECTION - Premium Centered Design with Glowing Prize // ============================================ function HeroSection({ stats }) { const [visible, setVisible] = useState(false); const [prizeHovered, setPrizeHovered] = useState(false); useEffect(() => { setTimeout(() => setVisible(true), 100); }, []); return (
{/* Floating Glowing Orbs Background */}
{/* Animated Background Particles */}
{[...Array(30)].map((_, i) => (
))}
{/* Subtle Page Title at Top */}
{Icons.trophy} Hall of Fame
{/* Main Prize Pool - Fully Centered & Prominent */}
setPrizeHovered(true)} onMouseLeave={() => setPrizeHovered(false)} > {/* Multi-layer glow effect */}
Total Prize Pool
$ 5,350
in weekly & monthly rewards
{/* Sparkle effects on hover */}
{[...Array(12)].map((_, i) => (
))}
{/* Subtitle */}

Compete in weekly & monthly competitions for real cash prizes

{/* Stats Row */}
{Icons.coins}
$350 Weekly
{Icons.crown}
$5,000 Monthly
{Icons.users}
Players
{Icons.gamepad}
Games
); } // ============================================ // MEDAL COMPONENT - SVG Based // ============================================ function Medal({ rank, size = 'medium' }) { const [hovered, setHovered] = useState(false); const colors = { 1: { primary: '#FFD700', secondary: '#FFA500', glow: 'rgba(255, 215, 0, 0.4)', name: '1st Place' }, 2: { primary: '#C0C0C0', secondary: '#A8A8A8', glow: 'rgba(192, 192, 192, 0.4)', name: '2nd Place' }, 3: { primary: '#CD7F32', secondary: '#A0522D', glow: 'rgba(205, 127, 50, 0.4)', name: '3rd Place' } }; const c = colors[rank] || colors[1]; const sizeClass = `medal-${size}`; return (
setHovered(true)} onMouseLeave={() => setHovered(false)} >
{/* Ribbon */} {/* Medal Circle */} {/* Rank Number */} {rank} {/* Gradient Definitions */}
); } // ============================================ // LEADERBOARD ROW - Premium Design // ============================================ function LeaderboardRow({ data, rank, prize, delay = 0, isCurrentUser = false }) { const [visible, setVisible] = useState(false); useEffect(() => { const timer = setTimeout(() => setVisible(true), delay); return () => clearTimeout(timer); }, [delay]); const isTop3 = rank <= 3; return (
{isTop3 ? ( ) : ( {rank} )}
{(data.username || '?')[0].toUpperCase()}
{data.username || 'Anonymous'} {isCurrentUser && YOU}
{Icons.lightning} Level {data.level || 1}
{fmtMoney(data.wagered)}
{prize ? (
{prize}
) : (
)}
); } // ============================================ // COMPETITION CARD - Premium Design // ============================================ function CompetitionCard({ type, leaders, isHot = false }) { const isMonthly = type === 'monthly'; const prizes = isMonthly ? REWARDS.monthly : REWARDS.weekly; const [currentUser, setCurrentUser] = useState(null); // Fetch current user on mount useEffect(() => { const fetchUser = async () => { try { const res = await fetch('/me', { credentials: 'include' }); if (res.ok) { const data = await res.json(); setCurrentUser(data); } } catch (e) { // Not logged in } }; fetchUser(); }, []); // Find the current user's position in the leaderboard const getUserRankData = () => { if (!currentUser || !leaders || leaders.length === 0) return null; const idx = leaders.findIndex(l => l.username === currentUser.username); if (idx === -1) return null; // User not on leaderboard if (idx < 3) return null; // User is already in top 3, don't show twice return { rank: idx + 1, data: leaders[idx], prize: idx < 3 ? prizes[idx] : null }; }; const userRankData = getUserRankData(); return (
{/* Glow Effect */}
{/* Hot Badge */} {isHot && (
{Icons.fire} HOT
)} {/* Header */}
{isMonthly ? Icons.crown : Icons.trophy}
{type.toUpperCase()} Competition
LIVE
{/* Prize Podium */}
{/* 2nd Place */}
{prizes[1]}
2nd Place
{/* 1st Place */}
{prizes[0]}
{isMonthly ? 'Champion' : '1st Place'}
{/* 3rd Place */}
{prizes[2]}
3rd Place
{/* Divider */}
Leaderboard
{/* Leaderboard */}
Rank Player Wagered Prize
{leaders && leaders.length > 0 ? ( <> {/* Top 3 players */} {leaders.slice(0, 3).map((leader, i) => ( ))} {/* Current user's position (if not in top 3) */} {userRankData && ( <>
•••
)} ) : (
{Icons.trophy}
Be the first to compete!
Start playing to climb the leaderboard
)}
); } // ============================================ // STAT CARD - Premium Design // ============================================ function StatCard({ title, icon, type, data, colorClass }) { const getValue = (item) => { if (type === 'wins') return fmtMoney(item.amount || item.profit); if (type === 'profit') return (item.net_profit >= 0 ? '+' : '') + fmtMoney(item.net_profit); return `Lvl ${item.level}`; }; return (
{icon}

{title}

{data && data.length > 0 ? ( data.slice(0, 3).map((item, i) => (
{(item.username || '?')[0].toUpperCase()}
{item.username || 'Anonymous'}
{getValue(item)}
)) ) : (
No data yet
)}
); } // ============================================ // STATS SECTION // ============================================ function StatsSection({ biggestWins, topProfit, highestLevel }) { return (
{Icons.star}

Legends & Records

Hall of fame's greatest achievements

); } // ============================================ // RULES MODAL - Hall of Fame Guide // ============================================ function RulesModal({ isOpen, onClose }) { if (!isOpen) return null; return (
e.stopPropagation()}>

{Icons.trophy} Hall of Fame

{/* Welcome Section */}

{Icons.crown} Welcome to the Legends

The Hall of Fame is where CoinRush's greatest players are immortalized. Every bet you place, every game you win, contributes to your legacy. This is where champions are made and legends are born.

Will your name be etched among the greats?

{/* Weekly Competition */}

{Icons.clock} Weekly Competition

1
Play Any Game

Every wager you place across all CoinRush games counts towards your weekly total.

2
Climb the Ranks

Your total wagered amount determines your position on the weekly leaderboard.

3
Claim Your Prize

At week's end, top players receive cash rewards directly to their accounts!

🥇 1st Place $200
🥈 2nd Place $100
🥉 3rd Place $50
{/* Monthly Competition */}

{Icons.fire} Monthly Championship

The ultimate test of skill and dedication. Monthly competitions feature MASSIVE prize pools for those who prove themselves over 30 days of intense gameplay.

🏆 Champion $3,000
🥈 Runner-up $1,500
🥉 3rd Place $500
{/* How Ranking Works */}

{Icons.chart} How Rankings Work

{Icons.check}

Total Wagered: Your ranking is based on total amount wagered, not winnings

{Icons.check}

All Games Count: Jackpot, Crash, Slice 'n Dice, Knights Arena - every bet matters

{Icons.check}

Real-Time Updates: Leaderboards update instantly as you play

{Icons.check}

Fair Play: Only real wagers count - no exploits or manipulation

{/* Other Leaderboards */}

{Icons.star} Glory Beyond Rankings

The Hall of Fame celebrates more than just volume. We honor:

{Icons.coins}

Biggest Wins: Land a massive multiplier? Your victory will be immortalized.

{Icons.chart}

Top Profit: Smart players who consistently come out ahead.

{Icons.lightning}

Highest Levels: Dedication rewarded - the most experienced players.

{/* Tips */}

{Icons.gift} Pro Tips

🎯 Consistency is Key: Regular play beats sporadic big bets for leaderboard climbing.
🎮 Diversify: Try all games - you might find your lucky one!
⏰ Timing: Competitions reset weekly (Monday) and monthly (1st). Plan accordingly!
); } // ============================================ // CTA SECTION // ============================================ function CTASection() { const ripple = useRipple(); return (
{Icons.dice}

Ready to compete?

Every bet counts towards your rank. Start playing now and climb the leaderboard!

{Icons.gamepad} Start Playing {Icons.arrow}
); } // ============================================ // AUTH MODAL - Use shared component from auth-react.jsx // ============================================ // The AuthModal is now loaded from /static/js/auth-react.jsx // which includes OAuth buttons (Discord, Google, X) // Access it via window.AuthModal // ============================================ // MAIN APP - Uses shared CoinRushNavbar // ============================================ function HallOfFameApp() { const [user, setUser] = useState(null); const [showRules, setShowRules] = useState(false); const [showAuthModal, setShowAuthModal] = useState(false); const [authModalMode, setAuthModalMode] = useState('login'); const [data, setData] = useState({ weeklyLeaders: [], monthlyLeaders: [], biggestWins: [], topProfit: [], highestLevel: [], stats: { totalUsers: 0, totalGames: 0 } }); const [loading, setLoading] = useState(true); // Expose rules modal to navbar useEffect(() => { window.showFameRules = () => setShowRules(true); return () => { delete window.showFameRules; }; }, []); // Expose auth modal to navbar useEffect(() => { window.openAuthModal = (mode = 'login') => { setAuthModalMode(mode); setShowAuthModal(true); }; return () => { delete window.openAuthModal; }; }, []); // Fetch user useEffect(() => { const fetchUser = async () => { try { const res = await fetch('/me', { credentials: 'include' }); if (res.ok) { const data = await res.json(); setUser(data); } } catch (e) { // Not logged in } }; fetchUser(); }, []); const handleLogout = async () => { try { await fetch('/auth/logout', { method: 'POST', credentials: 'include' }); setUser(null); window.location.reload(); } catch (e) { console.error('Logout failed', e); } }; const fetchData = useCallback(async () => { try { const res = await fetch('/api/hall-of-fame/stats', { credentials: 'include' }); const json = await res.json(); setData({ weeklyLeaders: json.weekly_leaders || [], monthlyLeaders: json.monthly_leaders || [], biggestWins: json.biggest_wins || [], topProfit: json.top_players || [], highestLevel: json.top_by_level || [], stats: { totalUsers: json.platform_stats?.total_users || 0, totalGames: json.platform_stats?.total_games_played || 0 } }); setLoading(false); } catch (e) { console.error('[HoF] Fetch error:', e); setLoading(false); } }, []); useEffect(() => { fetchData(); const interval = setInterval(fetchData, 60000); return () => clearInterval(interval); }, [fetchData]); return (
{CoinRushNavbar && ( )} setShowRules(false)} /> {/* Auth Modal (uses shared component from auth-react.jsx with OAuth buttons) */} {window.AuthModal && ( setShowAuthModal(false)} onSuccess={(userData) => { setUser(userData); setShowAuthModal(false); }} /> )}
{Icons.trophy}

Live Competitions

Race to the top and claim your rewards

); } // Render the app const root = ReactDOM.createRoot(document.getElementById('root')); root.render();