mirror of
https://github.com/JorySeverijnse/ui-fixer-supreme.git
synced 2026-01-29 17:58:38 +00:00
282 lines
14 KiB
TypeScript
282 lines
14 KiB
TypeScript
import { createContext, useContext, useState, useEffect, useCallback, ReactNode } from 'react';
|
|
import { useLocation } from 'react-router-dom';
|
|
|
|
export interface Achievement {
|
|
id: string;
|
|
name: string;
|
|
description: string;
|
|
icon: string;
|
|
unlocked: boolean;
|
|
unlockedAt?: number;
|
|
secret?: boolean;
|
|
}
|
|
|
|
// Track AI message count in localStorage
|
|
const AI_MESSAGE_COUNT_KEY = 'ai-message-count';
|
|
|
|
export const incrementAiMessageCount = (): number => {
|
|
const current = parseInt(localStorage.getItem(AI_MESSAGE_COUNT_KEY) || '0', 10);
|
|
const newCount = current + 1;
|
|
localStorage.setItem(AI_MESSAGE_COUNT_KEY, newCount.toString());
|
|
return newCount;
|
|
};
|
|
|
|
export const getAiMessageCount = (): number => {
|
|
return parseInt(localStorage.getItem(AI_MESSAGE_COUNT_KEY) || '0', 10);
|
|
};
|
|
|
|
interface AchievementsContextType {
|
|
achievements: Achievement[];
|
|
unlockAchievement: (id: string) => void;
|
|
getUnlockedCount: () => number;
|
|
getTotalCount: () => number;
|
|
timeOnSite: number;
|
|
checkAiAchievements: () => void;
|
|
}
|
|
|
|
const defaultAchievements: Achievement[] = [
|
|
// Discovery achievements
|
|
{ id: 'first_visit', name: 'Hello World', description: 'Visit the site for the first time', icon: '👋', unlocked: false },
|
|
{ id: 'found_achievements', name: 'Achievement Hunter', description: 'Discover the achievements section', icon: '🏆', unlocked: false },
|
|
{ id: 'konami_master', name: 'Konami Master', description: 'Enter the legendary cheat code', icon: '🎮', unlocked: false, secret: true },
|
|
{ id: 'terminal_user', name: 'Terminal Jockey', description: 'Use the terminal command interface', icon: '💻', unlocked: false },
|
|
{ id: 'hint_seeker', name: 'Hint Seeker', description: 'Ask for a hint in the terminal', icon: '🔍', unlocked: false, secret: true },
|
|
|
|
// Navigation achievements
|
|
{ id: 'home_visitor', name: 'Home Base', description: 'Visit the home page', icon: '🏠', unlocked: false },
|
|
{ id: 'about_visitor', name: 'Getting Personal', description: 'Learn about the site owner', icon: '👤', unlocked: false },
|
|
{ id: 'projects_visitor', name: 'Project Explorer', description: 'Check out the projects', icon: '📁', unlocked: false },
|
|
{ id: 'resources_visitor', name: 'Resource Collector', description: 'Browse the resources page', icon: '📚', unlocked: false },
|
|
{ id: 'links_visitor', name: 'Link Crawler', description: 'Visit the links page', icon: '🔗', unlocked: false },
|
|
{ id: 'faq_visitor', name: 'Question Everything', description: 'Read the FAQ', icon: '❓', unlocked: false },
|
|
{ id: 'music_visitor', name: 'DJ Mode', description: 'Open the music player', icon: '🎵', unlocked: false },
|
|
{ id: 'ai_visitor', name: 'AI Whisperer', description: 'Chat with the AI', icon: '🤖', unlocked: false },
|
|
{ id: 'arcade_visitor', name: 'Arcade Enthusiast', description: 'Visit the arcade', icon: '🕹️', unlocked: false },
|
|
{ id: 'all_pages', name: 'Completionist', description: 'Visit every page on the site', icon: '🗺️', unlocked: false, secret: true },
|
|
|
|
// Time achievements
|
|
{ id: 'time_15min', name: 'Quick Visit', description: 'Spend 15 minutes on the site', icon: '⏱️', unlocked: false },
|
|
{ id: 'time_30min', name: 'Taking Your Time', description: 'Spend 30 minutes on the site', icon: '⏰', unlocked: false },
|
|
{ id: 'time_1hour', name: 'Deep Dive', description: 'Spend 1 hour on the site', icon: '🕐', unlocked: false },
|
|
{ id: 'time_3hour', name: 'Marathon Session', description: 'Spend 3 hour on the site', icon: '🕑', unlocked: false },
|
|
{ id: 'time_24hour', name: 'No Life', description: 'Spend 24 hours on the site', icon: '💀', unlocked: false, secret: true },
|
|
|
|
// Game achievements
|
|
{ id: 'tetris_played', name: 'Block Stacker', description: 'Play Tetris', icon: '🧱', unlocked: false },
|
|
{ id: 'pacman_played', name: 'Pac-Fan', description: 'Play Pac-Man', icon: '🟡', unlocked: false },
|
|
{ id: 'snake_played', name: 'Sssssnake', description: 'Play Snake', icon: '🐍', unlocked: false },
|
|
{ id: 'breakout_played', name: 'Brick Breaker', description: 'Play Breakout', icon: '🧨', unlocked: false },
|
|
{ id: 'all_games', name: 'Arcade Master', description: 'Play all four arcade games', icon: '👾', unlocked: false },
|
|
|
|
// Score achievements
|
|
{ id: 'tetris_1000', name: 'Tetris Novice', description: 'Score 1,000 points in Tetris', icon: '🥉', unlocked: false },
|
|
{ id: 'tetris_10000', name: 'Tetris Pro', description: 'Score 10,000 points in Tetris', icon: '🥈', unlocked: false },
|
|
{ id: 'tetris_50000', name: 'Tetris Legend', description: 'Score 50,000 points in Tetris', icon: '🥇', unlocked: false, secret: true },
|
|
{ id: 'pacman_1000', name: 'Pac-Rookie', description: 'Score 1,000 points in Pac-Man', icon: '🥉', unlocked: false },
|
|
{ id: 'pacman_5000', name: 'Pac-Veteran', description: 'Score 5,000 points in Pac-Man', icon: '🥈', unlocked: false },
|
|
{ id: 'pacman_10000', name: 'Pac-Champion', description: 'Score 10,000 points in Pac-Man', icon: '🥇', unlocked: false, secret: true },
|
|
{ id: 'snake_500', name: 'Baby Snake', description: 'Score 500 points in Snake', icon: '🥉', unlocked: false },
|
|
{ id: 'snake_2000', name: 'Growing Snake', description: 'Score 2,000 points in Snake', icon: '🥈', unlocked: false },
|
|
{ id: 'snake_5000', name: 'Mega Snake', description: 'Score 5,000 points in Snake', icon: '🥇', unlocked: false, secret: true },
|
|
{ id: 'breakout_1000', name: 'Brick Novice', description: 'Score 1,000 points in Breakout', icon: '🥉', unlocked: false },
|
|
{ id: 'breakout_5000', name: 'Brick Crusher', description: 'Score 5,000 points in Breakout', icon: '🥈', unlocked: false },
|
|
{ id: 'breakout_10000', name: 'Brick Destroyer', description: 'Score 10,000 points in Breakout', icon: '🥇', unlocked: false, secret: true },
|
|
|
|
// Special achievements
|
|
{ id: 'night_owl', name: 'Night Owl', description: 'Visit the site between midnight and 4 AM', icon: '🦉', unlocked: false, secret: true },
|
|
{ id: 'early_bird', name: 'Early Bird', description: 'Visit the site between 5 AM and 7 AM', icon: '🐦', unlocked: false, secret: true },
|
|
{ id: 'theme_switcher', name: 'Indecisive', description: 'Switch between red and green themes', icon: '🎨', unlocked: false },
|
|
{ id: 'crt_toggler', name: 'Retro Purist', description: 'Toggle the CRT effect', icon: '📺', unlocked: false },
|
|
{ id: 'sound_toggler', name: 'Audio Engineer', description: 'Toggle sound effects', icon: '🔊', unlocked: false },
|
|
{ id: 'music_listener', name: 'Radio Listener', description: 'Listen to a radio station', icon: '📻', unlocked: false },
|
|
{ id: 'ai_conversation', name: 'Deep Thinker', description: 'Send 5 messages to the AI', icon: '💭', unlocked: false },
|
|
{ id: 'ai_long_chat', name: 'Chatterbox', description: 'Send 20 messages to the AI', icon: '🗣️', unlocked: false, secret: true },
|
|
{ id: 'project_detail', name: 'Project Inspector', description: 'View a project in detail', icon: '🔬', unlocked: false },
|
|
{ id: 'leaderboard_check', name: 'Competitive Spirit', description: 'Check the arcade leaderboard', icon: '📊', unlocked: false },
|
|
{ id: 'max_score', name: 'Integer Overflow', description: 'Reach the maximum score in any game', icon: '💥', unlocked: false, secret: true },
|
|
{ id: 'verified_human', name: 'Certified Human', description: 'Pass the human verification', icon: '✅', unlocked: false },
|
|
];
|
|
|
|
const AchievementsContext = createContext<AchievementsContextType | undefined>(undefined);
|
|
|
|
export const AchievementsProvider = ({ children }: { children: ReactNode }) => {
|
|
const [achievements, setAchievements] = useState<Achievement[]>(() => {
|
|
const saved = localStorage.getItem('achievements');
|
|
if (saved) {
|
|
const parsed = JSON.parse(saved);
|
|
// Merge with defaults to handle new achievements
|
|
return defaultAchievements.map(def => {
|
|
const saved = parsed.find((a: Achievement) => a.id === def.id);
|
|
return saved ? { ...def, unlocked: saved.unlocked, unlockedAt: saved.unlockedAt } : def;
|
|
});
|
|
}
|
|
return defaultAchievements;
|
|
});
|
|
|
|
const [timeOnSite, setTimeOnSite] = useState(() => {
|
|
const saved = localStorage.getItem('timeOnSite');
|
|
return saved ? parseInt(saved, 10) : 0;
|
|
});
|
|
|
|
const [visitedPages, setVisitedPages] = useState<Set<string>>(() => {
|
|
const saved = localStorage.getItem('visitedPages');
|
|
return saved ? new Set(JSON.parse(saved)) : new Set();
|
|
});
|
|
|
|
const location = useLocation();
|
|
|
|
// Track time on site
|
|
useEffect(() => {
|
|
const interval = setInterval(() => {
|
|
setTimeOnSite(prev => {
|
|
const newTime = prev + 1;
|
|
localStorage.setItem('timeOnSite', newTime.toString());
|
|
return newTime;
|
|
});
|
|
}, 1000);
|
|
|
|
return () => clearInterval(interval);
|
|
}, []);
|
|
|
|
// Check time-based achievements
|
|
useEffect(() => {
|
|
if (timeOnSite >= 900) unlockAchievement('time_15min');
|
|
if (timeOnSite >= 1800) unlockAchievement('time_30min');
|
|
if (timeOnSite >= 3600) unlockAchievement('time_1hour');
|
|
if (timeOnSite >= 10800) unlockAchievement('time_3hour');
|
|
if (timeOnSite >= 86400) unlockAchievement('time_24hour');
|
|
}, [timeOnSite]);
|
|
|
|
// Check time of day achievements
|
|
useEffect(() => {
|
|
const hour = new Date().getHours();
|
|
if (hour >= 0 && hour < 4) unlockAchievement('night_owl');
|
|
if (hour >= 5 && hour < 7) unlockAchievement('early_bird');
|
|
}, []);
|
|
|
|
// Track page visits
|
|
useEffect(() => {
|
|
const path = location.pathname;
|
|
|
|
setVisitedPages(prev => {
|
|
const newSet = new Set(prev);
|
|
newSet.add(path);
|
|
localStorage.setItem('visitedPages', JSON.stringify([...newSet]));
|
|
return newSet;
|
|
});
|
|
|
|
// Page-specific achievements
|
|
if (path === '/') unlockAchievement('home_visitor');
|
|
if (path === '/about') unlockAchievement('about_visitor');
|
|
if (path === '/projects') unlockAchievement('projects_visitor');
|
|
if (path === '/resources') unlockAchievement('resources_visitor');
|
|
if (path === '/links') unlockAchievement('links_visitor');
|
|
if (path === '/faq') unlockAchievement('faq_visitor');
|
|
if (path === '/music') unlockAchievement('music_visitor');
|
|
if (path === '/ai') unlockAchievement('ai_visitor');
|
|
if (path === '/games') unlockAchievement('arcade_visitor');
|
|
if (path.startsWith('/projects/')) unlockAchievement('project_detail');
|
|
if (path === '/games/leaderboard') unlockAchievement('leaderboard_check');
|
|
if (path === '/games/tetris') unlockAchievement('tetris_played');
|
|
if (path === '/games/pacman') unlockAchievement('pacman_played');
|
|
if (path === '/games/snake') unlockAchievement('snake_played');
|
|
if (path === '/games/breakout') unlockAchievement('breakout_played');
|
|
}, [location.pathname]);
|
|
|
|
// Check all pages visited
|
|
useEffect(() => {
|
|
const requiredPages = ['/', '/about', '/projects', '/resources', '/links', '/faq', '/music', '/ai', '/games'];
|
|
const allVisited = requiredPages.every(p => visitedPages.has(p));
|
|
if (allVisited) unlockAchievement('all_pages');
|
|
}, [visitedPages]);
|
|
|
|
// Check all games played
|
|
useEffect(() => {
|
|
const gamePages = ['/games/tetris', '/games/pacman', '/games/snake', '/games/breakout'];
|
|
const allPlayed = gamePages.every(p => visitedPages.has(p));
|
|
if (allPlayed) unlockAchievement('all_games');
|
|
}, [visitedPages]);
|
|
|
|
// First visit achievement
|
|
useEffect(() => {
|
|
unlockAchievement('first_visit');
|
|
}, []);
|
|
|
|
const [notification, setNotification] = useState<Achievement | null>(null);
|
|
|
|
const unlockAchievement = useCallback((id: string) => {
|
|
setAchievements(prev => {
|
|
const achievement = prev.find(a => a.id === id);
|
|
if (!achievement || achievement.unlocked) return prev;
|
|
|
|
// Show notification
|
|
setNotification(achievement);
|
|
setTimeout(() => setNotification(null), 4000);
|
|
|
|
const updated = prev.map(a =>
|
|
a.id === id ? { ...a, unlocked: true, unlockedAt: Date.now() } : a
|
|
);
|
|
localStorage.setItem('achievements', JSON.stringify(updated));
|
|
return updated;
|
|
});
|
|
}, []);
|
|
|
|
const getUnlockedCount = useCallback(() => {
|
|
return achievements.filter(a => a.unlocked).length;
|
|
}, [achievements]);
|
|
|
|
const getTotalCount = useCallback(() => {
|
|
return achievements.length;
|
|
}, [achievements]);
|
|
|
|
// Check AI message achievements
|
|
const checkAiAchievements = useCallback(() => {
|
|
const count = getAiMessageCount();
|
|
if (count >= 5) unlockAchievement('ai_conversation');
|
|
if (count >= 20) unlockAchievement('ai_long_chat');
|
|
}, [unlockAchievement]);
|
|
|
|
// Save achievements
|
|
useEffect(() => {
|
|
localStorage.setItem('achievements', JSON.stringify(achievements));
|
|
}, [achievements]);
|
|
|
|
return (
|
|
<AchievementsContext.Provider
|
|
value={{
|
|
achievements,
|
|
unlockAchievement,
|
|
getUnlockedCount,
|
|
getTotalCount,
|
|
timeOnSite,
|
|
checkAiAchievements,
|
|
}}
|
|
>
|
|
{children}
|
|
{/* Achievement Notification */}
|
|
{notification && (
|
|
<div className="fixed top-4 right-4 z-[300] animate-in slide-in-from-right-5 fade-in duration-300">
|
|
<div className="bg-background border-2 border-primary p-4 box-glow-strong max-w-xs">
|
|
<div className="flex items-center gap-3">
|
|
<span className="text-2xl">{notification.icon}</span>
|
|
<div>
|
|
<div className="text-primary font-minecraft text-sm text-glow">ACHIEVEMENT UNLOCKED!</div>
|
|
<div className="text-primary font-bold">{notification.name}</div>
|
|
<div className="text-primary/70 text-xs">{notification.description}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</AchievementsContext.Provider>
|
|
);
|
|
};
|
|
|
|
export const useAchievements = () => {
|
|
const context = useContext(AchievementsContext);
|
|
if (context === undefined) {
|
|
throw new Error('useAchievements must be used within an AchievementsProvider');
|
|
}
|
|
return context;
|
|
};
|