/** * CoinRush Lottery - React Implementation * Premium lottery with countdown, ticket purchases, and animated draws * No emoji icons - all SVG */ // ============================================ // SVG ICONS // ============================================ const Icons = { trophy: ( ), ticket: ( ), clock: ( ), star: ( ), dollar: ( ), gift: ( ), cart: ( ), info: ( ), calendar: ( ), target: ( ), sparkles: ( ), check: ( ), x: ( ), crown: ( ), minus: ( ), plus: ( ), loader: ( ), users: ( ), heart: ( ), dice: ( ), percent: ( ) }; // ============================================ // LOTTERY RULES MODAL // ============================================ function LotteryRulesModal({ onClose }) { // Prevent body scroll when modal is open React.useEffect(() => { document.body.style.overflow = 'hidden'; return () => { document.body.style.overflow = ''; }; }, []); return (
e.stopPropagation()}>
{Icons.heart}

Monthly Lottery Rules

{/* Community Message */}

The CoinRush Monthly Lottery is 100% funded by CoinRush. We created this lottery as a way to give back to our amazing community and thank you for being part of the CoinRush family. Every month, we set aside a portion of our revenue to reward our loyal players with a chance to win big!

{/* How to Get Tickets */}
{Icons.ticket}

How to Get Tickets

  • Earn FREE tickets: For every $50 wagered on any CoinRush game, you automatically receive 1 FREE lottery ticket.
  • Buy tickets: Purchase additional tickets for $10 each to increase your chances of winning.
{/* How the Draw Works */}
{Icons.calendar}

Monthly Draw

  • The lottery draw happens on the 1st of every month at midnight UTC.
  • A single winning ticket is randomly selected from all tickets in the pool.
  • The more tickets you have, the higher your chance of winning!
{/* Prize Pool */}
{Icons.trophy}

Prize Pool

  • The prize pool consists of 5% of the monthly platform revenue plus all ticket purchase proceeds.
  • One lucky winner takes the entire prize pool!
  • Winnings are instantly credited to your CoinRush balance.
{/* Win Chance */}
{Icons.target}

Your Win Chance

  • Win chance = (Your Tickets / Total Tickets in Pool) × 100%
  • Your current win chance is displayed on your ticket stats card.
  • The draw is provably fair and uses a cryptographically secure random selection.
); } // ============================================ // UTILITY FUNCTIONS // ============================================ function formatCurrency(cents) { return (cents / 100).toFixed(2); } function formatWithCommas(num) { const parts = num.toString().split('.'); parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ","); return parts.join('.'); } // ============================================ // COUNTDOWN TIMER COMPONENT // ============================================ function CountdownTimer({ drawAt }) { const [timeLeft, setTimeLeft] = React.useState({ days: 0, hours: 0, minutes: 0, seconds: 0 }); const [isDrawing, setIsDrawing] = React.useState(false); React.useEffect(() => { if (!drawAt) return; const updateTimer = () => { const now = new Date(); const drawTime = new Date(drawAt); const diff = Math.max(0, drawTime - now); if (diff <= 0) { setIsDrawing(true); setTimeLeft({ days: 0, hours: 0, minutes: 0, seconds: 0 }); return; } setIsDrawing(false); setTimeLeft({ days: Math.floor(diff / (1000 * 60 * 60 * 24)), hours: Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)), minutes: Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60)), seconds: Math.floor((diff % (1000 * 60)) / 1000) }); }; updateTimer(); const interval = setInterval(updateTimer, 1000); return () => clearInterval(interval); }, [drawAt]); return (
{Icons.clock}

Next Draw

{isDrawing ? 'DRAWING' : 'OPEN'}
{String(timeLeft.days).padStart(2, '0')}
DAYS
:
{String(timeLeft.hours).padStart(2, '0')}
HRS
:
{String(timeLeft.minutes).padStart(2, '0')}
MIN
:
{String(timeLeft.seconds).padStart(2, '0')}
SEC
); } // ============================================ // PRIZE POOL COMPONENT // ============================================ function PrizePool({ prizePool, totalTickets }) { const formattedPrize = formatWithCommas(formatCurrency(prizePool)); return (
{Icons.trophy}

Prize Pool

$ {formattedPrize}
{Icons.ticket} {formatWithCommas(totalTickets)} tickets
); } // ============================================ // USER STATS COMPONENT // ============================================ function UserStats({ stats, totalTickets }) { if (!stats) { return (
{Icons.ticket}

Your Tickets

Log in to view your lottery tickets and stats

); } const winChance = stats.win_chance || 0; return (
{Icons.ticket}

Your Tickets

{stats.total_tickets || 0} Total Tickets
{winChance.toFixed(2)}%
Win Chance
{Icons.gift}
{stats.wagered_tickets || 0} Earned (Free)
{Icons.cart}
{stats.purchased_tickets || 0} Purchased
); } // ============================================ // BUY TICKETS COMPONENT // ============================================ function BuyTickets({ ticketPrice, onPurchase, userLoggedIn }) { const [quantity, setQuantity] = React.useState(1); const [loading, setLoading] = React.useState(false); const totalCost = quantity * ticketPrice; const pricePerTicket = formatCurrency(ticketPrice); const handleQuantityChange = (delta) => { setQuantity(prev => Math.max(1, Math.min(100, prev + delta))); }; const handleInputChange = (e) => { const val = parseInt(e.target.value) || 1; setQuantity(Math.max(1, Math.min(100, val))); }; const handleBuy = async () => { if (!userLoggedIn) { if (window.showAuthModal) window.showAuthModal('login'); return; } setLoading(true); try { await onPurchase(quantity); setQuantity(1); } finally { setLoading(false); } }; return (
{Icons.cart}

Buy Tickets

${pricePerTicket} / ticket
Quantity
Total ${formatCurrency(totalCost)}
); } // ============================================ // WAGER PROGRESS COMPONENT // ============================================ function WagerProgress({ stats, wagerPerTicket }) { const targetDollars = formatCurrency(wagerPerTicket); // If not logged in, show the card with 0 progress const progressInCycle = stats ? (stats.wager_progress || 0) % wagerPerTicket : 0; const percentage = (progressInCycle / wagerPerTicket) * 100; const progressDollars = formatCurrency(progressInCycle); return (
{Icons.star}

Earn Free Tickets

Wager ${targetDollars} on any game to earn 1 FREE lottery ticket!

${progressDollars} / ${targetDollars}
); } // ============================================ // HOW IT WORKS COMPONENT // ============================================ function HowItWorks() { const steps = [ { icon: Icons.star, iconClass: 'lt-icon-star', title: 'Earn Free Tickets', desc: 'For every $50 wagered on any game, you automatically earn 1 FREE lottery ticket.' }, { icon: Icons.cart, iconClass: 'lt-icon-cart', title: 'Buy Extra Tickets', desc: 'Want more chances? Buy additional tickets for just $10 each to boost your odds!' }, { icon: Icons.calendar, iconClass: 'lt-icon-calendar', title: 'Monthly Draw', desc: 'Drawings happen on the 1st of every month at midnight UTC. Watch the countdown!' }, { icon: Icons.trophy, iconClass: 'lt-icon-trophy', title: 'Win the Prize Pool', desc: 'Prize pool is 5% of monthly revenue. One lucky ticket wins 100% of it!' } ]; return (
{Icons.info}

How It Works

{steps.map((step, i) => (
{i + 1}
{step.icon}

{step.title}

{step.desc}

))}
); } // ============================================ // RECENT WINNERS COMPONENT // ============================================ function RecentWinners({ winners }) { return (
{Icons.crown}

Recent Winners

{winners.length === 0 ? (
{Icons.sparkles}

No draws yet. Be the first winner!

) : (
{winners.map((winner, i) => (
#{i + 1}
{winner.period}
{winner.winner_username || 'Anonymous'}
Won ${formatCurrency(winner.prize_pool)}
{Icons.ticket} #{winner.winning_ticket_number}
))}
)}
); } // ============================================ // TOAST NOTIFICATION // ============================================ function Toast({ message, type, onClose }) { React.useEffect(() => { const timer = setTimeout(onClose, 4000); return () => clearTimeout(timer); }, [onClose]); const icon = type === 'success' ? Icons.check : type === 'error' ? Icons.x : Icons.info; return (
{icon} {message}
); } // ============================================ // MAIN LOTTERY APP // ============================================ function LotteryApp() { const [user, setUser] = React.useState(null); const [loading, setLoading] = React.useState(true); const [showRules, setShowRules] = React.useState(false); const [lotteryState, setLotteryState] = React.useState({ status: 'open', prizePool: 0, totalTickets: 0, ticketPrice: 1000, wagerPerTicket: 5000, drawAt: null, userStats: null }); const [winners, setWinners] = React.useState([]); const [toast, setToast] = React.useState(null); // Expose rules modal to navbar React.useEffect(() => { window.showLotteryRules = () => setShowRules(true); return () => { delete window.showLotteryRules; }; }, []); // Fetch user const fetchUser = React.useCallback(async () => { try { const res = await fetch('/me', { credentials: 'include' }); if (res.ok) { const data = await res.json(); setUser(data); } } catch (e) { console.error('Failed to fetch user:', e); } }, []); // Fetch lottery state const fetchLotteryState = React.useCallback(async () => { try { const res = await fetch('/api/lottery/state', { credentials: 'include' }); if (res.ok) { const data = await res.json(); setLotteryState({ status: data.status, prizePool: data.prize_pool, totalTickets: data.total_tickets, ticketPrice: data.ticket_price, wagerPerTicket: data.wager_per_ticket, drawAt: data.draw_at, userStats: data.user_stats }); } } catch (e) { console.error('Failed to fetch lottery state:', e); } }, []); // Fetch winners history const fetchHistory = React.useCallback(async () => { try { const res = await fetch('/api/lottery/history', { credentials: 'include' }); if (res.ok) { const data = await res.json(); setWinners(data.draws || []); } } catch (e) { console.error('Failed to fetch history:', e); } }, []); // Initial load React.useEffect(() => { Promise.all([fetchUser(), fetchLotteryState(), fetchHistory()]) .finally(() => setLoading(false)); // Refresh state every 30 seconds const interval = setInterval(fetchLotteryState, 30000); return () => clearInterval(interval); }, [fetchUser, fetchLotteryState, fetchHistory]); // Buy tickets handler const handlePurchase = async (quantity) => { try { const res = await fetch('/api/lottery/buy', { method: 'POST', headers: { 'Content-Type': 'application/json' }, credentials: 'include', body: JSON.stringify({ quantity }) }); const data = await res.json(); if (!res.ok) { throw new Error(data.detail || 'Failed to purchase tickets'); } // Update state setLotteryState(prev => ({ ...prev, prizePool: data.prize_pool, totalTickets: data.total_tickets, userStats: data.user_stats })); // Update navbar balance if (window.updateBalanceDisplay && data.balance !== undefined) { window.updateBalanceDisplay(data.balance); } // Refresh user fetchUser(); setToast({ message: `Successfully purchased ${quantity} ticket${quantity > 1 ? 's' : ''}! 🎉`, type: 'success' }); } catch (error) { setToast({ message: error.message, type: 'error' }); throw error; } }; if (loading) { return (
{Icons.loader}
Loading lottery...
); } return (
{/* Floating Glowing Orbs Background */}
{/* Animated Floating Particles */}
{[...Array(35)].map((_, i) => (
))}
{/* Rules Modal */} {showRules && setShowRules(false)} />} {/* Toast Notifications */} {toast && ( setToast(null)} /> )} {/* Navbar */} { setUser(null); fetchLotteryState(); }} />
{/* Hero Section - Compact */}

Monthly Lottery

Get tickets, win big!

{/* Top Row - Prize, Countdown, Earn Free */}
{/* Main Content Grid - Stats & Buy */}
{/* How It Works */} {/* Recent Winners */}
); } // ============================================ // RENDER APP // ============================================ const lotteryRoot = ReactDOM.createRoot(document.getElementById('root')); lotteryRoot.render();