/**
* 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
);
}
// ============================================
// 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 */}
{/* Prize Podium */}
{/* 2nd Place */}
{/* 1st Place */}
{prizes[0]}
{isMonthly ? 'Champion' : '1st Place'}
{/* 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 (
{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
{Icons.close}
{/* 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!
Let's Go!
);
}
// ============================================
// CTA SECTION
// ============================================
function CTASection() {
const ripple = useRipple();
return (
);
}
// ============================================
// 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( );