Files
personal_website/src/pages/Leaderboard.tsx
T

147 lines
4.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { motion } from 'framer-motion';
import { Link } from 'react-router-dom';
import GlitchText from '@/components/GlitchText';
import { useState, useEffect } from 'react';
const HIGHSCORE_KEYS = {
tetris: 'tetris-highscore',
pacman: 'pacman-highscore',
snake: 'snake-highscore',
breakout: 'breakout-highscore',
};
const MAX_SCORE = 4294967296;
const Leaderboard = () => {
const [scores, setScores] = useState({
tetris: 0,
pacman: 0,
snake: 0,
breakout: 0,
});
useEffect(() => {
const tetrisScore = localStorage.getItem(HIGHSCORE_KEYS.tetris);
const pacmanScore = localStorage.getItem(HIGHSCORE_KEYS.pacman);
const snakeScore = localStorage.getItem(HIGHSCORE_KEYS.snake);
const breakoutScore = localStorage.getItem(HIGHSCORE_KEYS.breakout);
setScores({
tetris: tetrisScore ? Math.min(parseInt(tetrisScore, 10), MAX_SCORE) : 0,
pacman: pacmanScore ? Math.min(parseInt(pacmanScore, 10), MAX_SCORE) : 0,
snake: snakeScore ? Math.min(parseInt(snakeScore, 10), MAX_SCORE) : 0,
breakout: breakoutScore ? Math.min(parseInt(breakoutScore, 10), MAX_SCORE) : 0,
});
}, []);
const totalScore = scores.tetris + scores.pacman + scores.snake + scores.breakout;
const gameCards = [
{
id: 'tetris',
name: 'Tetris',
score: scores.tetris,
icon: `▓▓
▓▓██
████`,
},
{
id: 'pacman',
name: 'Pac-Man',
score: scores.pacman,
icon: `◗ ᗣ
· ·`,
},
{
id: 'snake',
name: 'Snake',
score: scores.snake,
icon: `●■■
■◆`,
},
{
id: 'breakout',
name: 'Breakout',
score: scores.breakout,
icon: `████
═══`,
},
];
return (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.5 }}
className="flex flex-col h-full"
>
<div className="flex items-center gap-4 mb-4">
<Link to="/games" className="font-pixel text-sm text-foreground/50 hover:text-primary transition-colors">
{'<'} Back
</Link>
<h1 className="font-minecraft text-3xl md:text-4xl text-primary text-glow-strong">
<GlitchText text="Leaderboard" />
</h1>
</div>
{/* Total Score */}
<div className="border-2 border-primary box-glow p-4 bg-background/80 mb-4">
<p className="font-pixel text-sm text-foreground/60 mb-1">COMBINED TOTAL</p>
<p className="font-minecraft text-4xl text-primary text-glow-strong">
{totalScore.toLocaleString()}
</p>
<p className="font-pixel text-xs text-foreground/40 mt-1">
Target: 4,294,967,296 × 4 = 17,179,869,184
</p>
</div>
{/* Game Scores */}
<div className="grid grid-cols-2 md:grid-cols-4 gap-3">
{gameCards.map((game, index) => (
<motion.div
key={game.id}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: index * 0.1 }}
>
<Link to={`/games/${game.id}`} className="block">
<div className="border border-primary/50 hover:border-primary bg-background/50 hover:bg-primary/10 p-4 transition-all duration-300">
<pre className="font-mono text-lg text-primary/70 mb-3 leading-tight whitespace-pre">
{game.icon}
</pre>
<h2 className="font-minecraft text-xl text-primary text-glow mb-2">
{game.name}
</h2>
<div>
<p className="font-pixel text-xs text-foreground/60">HIGH SCORE</p>
<p className="font-minecraft text-2xl text-primary text-glow">
{game.score.toLocaleString()}
</p>
<div className="mt-2 h-2 bg-background/50 border border-primary/30">
<div
className="h-full bg-primary/60 transition-all duration-500"
style={{ width: `${Math.min((game.score / MAX_SCORE) * 100, 100)}%` }}
/>
</div>
<p className="font-pixel text-[10px] text-foreground/40 mt-1">
{((game.score / MAX_SCORE) * 100).toFixed(6)}% to max
</p>
</div>
</div>
</Link>
</motion.div>
))}
</div>
<div className="border border-primary/30 p-2 bg-background/30 mt-4">
<p className="font-pixel text-xs text-foreground/50">
<span className="text-primary">{'>'}</span> Click a game to play and improve your score
</p>
</div>
</motion.div>
);
};
export default Leaderboard;