This commit is contained in:
2025-12-07 18:17:33 +00:00
parent 97be16c3c3
commit d33dbd110e
6 changed files with 472 additions and 30 deletions
+181
View File
@@ -0,0 +1,181 @@
import { motion } from 'framer-motion';
import { useAchievements } from '@/contexts/AchievementsContext';
import { Trophy, Lock, Clock } from 'lucide-react';
const Achievements = () => {
const { achievements, getUnlockedCount, getTotalCount, timeOnSite, unlockAchievement } = useAchievements();
// Unlock the achievement for finding this page
unlockAchievement('found_achievements');
const formatTime = (seconds: number) => {
const hours = Math.floor(seconds / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
const secs = seconds % 60;
if (hours > 0) {
return `${hours}h ${minutes}m ${secs}s`;
}
if (minutes > 0) {
return `${minutes}m ${secs}s`;
}
return `${secs}s`;
};
const formatUnlockDate = (timestamp: number) => {
return new Date(timestamp).toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric',
hour: '2-digit',
minute: '2-digit',
});
};
const unlockedAchievements = achievements.filter(a => a.unlocked);
const lockedAchievements = achievements.filter(a => !a.unlocked);
return (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="min-h-screen p-4 md:p-8"
>
{/* Header */}
<div className="mb-8">
<h1 className="text-2xl md:text-4xl font-minecraft text-primary text-glow mb-2 flex items-center gap-3">
<Trophy className="w-8 h-8" />
ACHIEVEMENTS
</h1>
<p className="text-primary/70 font-mono text-sm">
You found the secret achievements panel! Track your progress below.
</p>
</div>
{/* Stats */}
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 mb-8">
<div className="border-2 border-primary/50 bg-background/50 p-4 box-glow">
<div className="text-primary/60 text-xs font-mono mb-1">UNLOCKED</div>
<div className="text-primary text-2xl font-minecraft text-glow">
{getUnlockedCount()}/{getTotalCount()}
</div>
</div>
<div className="border-2 border-primary/50 bg-background/50 p-4 box-glow">
<div className="text-primary/60 text-xs font-mono mb-1">PROGRESS</div>
<div className="text-primary text-2xl font-minecraft text-glow">
{Math.round((getUnlockedCount() / getTotalCount()) * 100)}%
</div>
</div>
<div className="border-2 border-primary/50 bg-background/50 p-4 box-glow">
<div className="text-primary/60 text-xs font-mono mb-1 flex items-center gap-1">
<Clock className="w-3 h-3" /> TIME ON SITE
</div>
<div className="text-primary text-2xl font-minecraft text-glow">
{formatTime(timeOnSite)}
</div>
</div>
<div className="border-2 border-primary/50 bg-background/50 p-4 box-glow">
<div className="text-primary/60 text-xs font-mono mb-1">SECRET</div>
<div className="text-primary text-2xl font-minecraft text-glow">
{achievements.filter(a => a.secret && a.unlocked).length}/{achievements.filter(a => a.secret).length}
</div>
</div>
</div>
{/* Progress bar */}
<div className="mb-8">
<div className="h-4 border-2 border-primary/50 bg-background/50 overflow-hidden">
<motion.div
className="h-full bg-primary/50"
initial={{ width: 0 }}
animate={{ width: `${(getUnlockedCount() / getTotalCount()) * 100}%` }}
transition={{ duration: 1, ease: 'easeOut' }}
/>
</div>
</div>
{/* Unlocked Achievements */}
{unlockedAchievements.length > 0 && (
<div className="mb-8">
<h2 className="text-lg font-minecraft text-primary text-glow mb-4">
UNLOCKED ({unlockedAchievements.length})
</h2>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{unlockedAchievements
.sort((a, b) => (b.unlockedAt || 0) - (a.unlockedAt || 0))
.map((achievement, index) => (
<motion.div
key={achievement.id}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: index * 0.05 }}
className="border-2 border-primary bg-primary/10 p-4 box-glow"
>
<div className="flex items-start gap-3">
<span className="text-2xl">{achievement.icon}</span>
<div className="flex-1 min-w-0">
<div className="font-minecraft text-primary text-glow text-sm flex items-center gap-2">
{achievement.name}
{achievement.secret && (
<span className="text-xs bg-primary/20 px-1 border border-primary/50">SECRET</span>
)}
</div>
<div className="text-primary/70 text-xs font-mono mt-1">
{achievement.description}
</div>
{achievement.unlockedAt && (
<div className="text-primary/50 text-xs font-mono mt-2">
{formatUnlockDate(achievement.unlockedAt)}
</div>
)}
</div>
</div>
</motion.div>
))}
</div>
</div>
)}
{/* Locked Achievements */}
{lockedAchievements.length > 0 && (
<div>
<h2 className="text-lg font-minecraft text-primary/60 mb-4 flex items-center gap-2">
<Lock className="w-4 h-4" />
LOCKED ({lockedAchievements.length})
</h2>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{lockedAchievements.map((achievement, index) => (
<motion.div
key={achievement.id}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: index * 0.02 }}
className="border-2 border-primary/30 bg-background/30 p-4 opacity-60"
>
<div className="flex items-start gap-3">
<span className="text-2xl grayscale opacity-50">
{achievement.secret ? '❓' : achievement.icon}
</span>
<div className="flex-1 min-w-0">
<div className="font-minecraft text-primary/50 text-sm flex items-center gap-2">
{achievement.secret ? '???' : achievement.name}
{achievement.secret && (
<span className="text-xs bg-primary/10 px-1 border border-primary/30">SECRET</span>
)}
</div>
<div className="text-primary/40 text-xs font-mono mt-1">
{achievement.secret ? 'Hidden achievement' : achievement.description}
</div>
</div>
<Lock className="w-4 h-4 text-primary/30" />
</div>
</motion.div>
))}
</div>
</div>
)}
</motion.div>
);
};
export default Achievements;
+7 -1
View File
@@ -6,6 +6,7 @@ import MainLayout from '@/components/MainLayout';
import MatrixCursor from '@/components/MatrixCursor';
import { VERIFIED_KEY, BYPASS_KEY } from '@/components/HumanVerification';
import { useSettings } from '@/contexts/SettingsContext';
import { useAchievements } from '@/contexts/AchievementsContext';
import { useKonamiCode } from '@/hooks/useKonamiCode';
import { toast } from '@/hooks/use-toast';
@@ -33,6 +34,7 @@ const Index = () => {
const [showConsentModal, setShowConsentModal] = useState(false);
const [konamiActive, setKonamiActive] = useState(false);
const { crtEnabled, playSound } = useSettings();
const { unlockAchievement } = useAchievements();
const { activated: konamiActivated, reset: resetKonami } = useKonamiCode();
const location = useLocation();
@@ -45,6 +47,9 @@ const Index = () => {
if (konamiActivated) {
setKonamiActive(true);
// Unlock achievement
unlockAchievement('konami_master');
// Play special sound sequence
playSound('success');
setTimeout(() => playSound('boot'), 200);
@@ -62,7 +67,7 @@ const Index = () => {
resetKonami();
}, 3000);
}
}, [konamiActivated, playSound, resetKonami]);
}, [konamiActivated, playSound, resetKonami, unlockAchievement]);
// Persist theme to localStorage
useEffect(() => {
@@ -95,6 +100,7 @@ const Index = () => {
const toggleTheme = () => {
setIsRedTheme(!isRedTheme);
playSound('click');
unlockAchievement('theme_switcher');
};
const handleConsentClose = () => {