diff --git a/apps/web/src/components/game/TutorialHint.tsx b/apps/web/src/components/game/TutorialHint.tsx new file mode 100644 index 0000000..3cdeffc --- /dev/null +++ b/apps/web/src/components/game/TutorialHint.tsx @@ -0,0 +1,37 @@ +import { useState } from 'react'; +import { X, Lightbulb } from 'lucide-react'; + +const DISMISSED_KEY = 'ai-tycoon-dismissed-hints'; + +function getDismissed(): Set { + try { + return new Set(JSON.parse(localStorage.getItem(DISMISSED_KEY) || '[]')); + } catch { + return new Set(); + } +} + +function dismiss(id: string) { + const d = getDismissed(); + d.add(id); + localStorage.setItem(DISMISSED_KEY, JSON.stringify([...d])); +} + +export function TutorialHint({ id, children }: { id: string; children: React.ReactNode }) { + const [visible, setVisible] = useState(!getDismissed().has(id)); + + if (!visible) return null; + + return ( +
+ +
{children}
+ +
+ ); +} diff --git a/apps/web/src/components/layout/MainLayout.tsx b/apps/web/src/components/layout/MainLayout.tsx index 5b76928..c4eaa25 100644 --- a/apps/web/src/components/layout/MainLayout.tsx +++ b/apps/web/src/components/layout/MainLayout.tsx @@ -13,6 +13,7 @@ import { FinancePage } from '@/pages/FinancePage'; import { TalentPage } from '@/pages/TalentPage'; import { DataPage } from '@/pages/DataPage'; import { CompetitorsPage } from '@/pages/CompetitorsPage'; +import { AchievementsPage } from '@/pages/AchievementsPage'; export function MainLayout() { const activePage = useGameStore((s) => s.activePage); @@ -43,6 +44,7 @@ function PageRouter({ page }: { page: string }) { case 'talent': return ; case 'data': return ; case 'competitors': return ; + case 'achievements': return ; case 'settings': return ; default: return ; } diff --git a/apps/web/src/components/layout/Sidebar.tsx b/apps/web/src/components/layout/Sidebar.tsx index fc7e728..ec06cb0 100644 --- a/apps/web/src/components/layout/Sidebar.tsx +++ b/apps/web/src/components/layout/Sidebar.tsx @@ -1,6 +1,7 @@ +import { useState, useEffect, useRef } from 'react'; import { LayoutDashboard, Server, FlaskConical, Brain, - TrendingUp, Users, Database, Swords, DollarSign, Settings, + TrendingUp, Users, Database, Swords, DollarSign, Settings, Trophy, } from 'lucide-react'; import { useGameStore, type ActivePage } from '@/store'; @@ -14,6 +15,7 @@ const NAV_ITEMS: { page: ActivePage; label: string; icon: typeof LayoutDashboard { page: 'talent', label: 'Talent', icon: Users, era: 'scaleup' }, { page: 'data', label: 'Data', icon: Database, era: 'scaleup' }, { page: 'competitors', label: 'Competitors', icon: Swords, era: 'scaleup' }, + { page: 'achievements', label: 'Achievements', icon: Trophy }, { page: 'settings', label: 'Settings', icon: Settings }, ]; @@ -26,6 +28,32 @@ export function Sidebar() { const eraOrder = ['startup', 'scaleup', 'bigtech', 'agi']; const currentEraIdx = eraOrder.indexOf(era); + const seenEraRef = useRef(era); + const [newPages, setNewPages] = useState>(new Set()); + + useEffect(() => { + if (era !== seenEraRef.current) { + const oldIdx = eraOrder.indexOf(seenEraRef.current); + const newIdx = eraOrder.indexOf(era); + if (newIdx > oldIdx) { + const newlyVisible = NAV_ITEMS + .filter(item => item.era && eraOrder.indexOf(item.era) > oldIdx && eraOrder.indexOf(item.era) <= newIdx) + .map(item => item.page); + setNewPages(prev => new Set([...prev, ...newlyVisible])); + } + seenEraRef.current = era; + } + }, [era]); + + const handleNavClick = (page: ActivePage) => { + setActivePage(page); + setNewPages(prev => { + const next = new Set(prev); + next.delete(page); + return next; + }); + }; + return (