From fdc8e544ae9a08bca1c8e5515f524e02d1a8a595 Mon Sep 17 00:00:00 2001 From: josh Date: Fri, 24 Apr 2026 16:53:46 -0400 Subject: [PATCH] Initial scaffold: AI Tycoon monorepo with core game loop Turborepo monorepo with three packages: - packages/shared: TypeScript types for all 14 game systems + balance constants + formatting utils - packages/game-engine: Pure TS simulation engine with tick processor, economy, infrastructure, compute, research, market, and reputation systems - apps/web: React + Vite + Tailwind + Zustand frontend with sidebar dashboard layout, new game screen, dashboard with charts, infrastructure management, and model training pages Co-Authored-By: Claude Opus 4.6 --- .gitignore | 7 + apps/web/index.html | 16 + apps/web/package.json | 32 + apps/web/postcss.config.js | 6 + apps/web/src/App.tsx | 15 + .../web/src/components/game/NewGameScreen.tsx | 81 + apps/web/src/components/layout/MainLayout.tsx | 44 + apps/web/src/components/layout/Sidebar.tsx | 65 + apps/web/src/components/layout/TopBar.tsx | 79 + apps/web/src/hooks/useGameLoop.ts | 51 + apps/web/src/index.css | 25 + apps/web/src/main.tsx | 10 + apps/web/src/pages/DashboardPage.tsx | 193 ++ apps/web/src/pages/InfrastructurePage.tsx | 173 ++ apps/web/src/pages/ModelsPage.tsx | 178 ++ apps/web/src/pages/SettingsPage.tsx | 80 + apps/web/src/store/index.ts | 243 ++ apps/web/tailwind.config.ts | 37 + apps/web/tsconfig.json | 10 + apps/web/vite.config.ts | 12 + package.json | 19 + packages/game-engine/package.json | 18 + packages/game-engine/src/engine.ts | 82 + packages/game-engine/src/index.ts | 2 + .../game-engine/src/systems/computeSystem.ts | 24 + .../game-engine/src/systems/economySystem.ts | 45 + .../src/systems/infrastructureSystem.ts | 72 + .../game-engine/src/systems/marketSystem.ts | 67 + .../src/systems/reputationSystem.ts | 27 + .../game-engine/src/systems/researchSystem.ts | 29 + packages/game-engine/src/tick.ts | 33 + packages/game-engine/tsconfig.json | 8 + packages/shared/package.json | 15 + packages/shared/src/constants/gameBalance.ts | 42 + packages/shared/src/index.ts | 15 + packages/shared/src/types/achievements.ts | 28 + packages/shared/src/types/competitors.ts | 35 + packages/shared/src/types/compute.ts | 17 + packages/shared/src/types/data.ts | 47 + packages/shared/src/types/economy.ts | 63 + packages/shared/src/types/events.ts | 74 + packages/shared/src/types/gameState.ts | 63 + packages/shared/src/types/infrastructure.ts | 84 + packages/shared/src/types/market.ts | 73 + packages/shared/src/types/models.ts | 106 + packages/shared/src/types/reputation.ts | 22 + packages/shared/src/types/research.ts | 45 + packages/shared/src/types/talent.ts | 49 + packages/shared/src/utils/formatting.ts | 33 + packages/shared/tsconfig.json | 8 + packages/tsconfig/base.json | 16 + packages/tsconfig/node.json | 9 + packages/tsconfig/package.json | 5 + packages/tsconfig/react.json | 8 + pnpm-lock.yaml | 2090 +++++++++++++++++ pnpm-workspace.yaml | 3 + turbo.json | 20 + 57 files changed, 4753 insertions(+) create mode 100644 .gitignore create mode 100644 apps/web/index.html create mode 100644 apps/web/package.json create mode 100644 apps/web/postcss.config.js create mode 100644 apps/web/src/App.tsx create mode 100644 apps/web/src/components/game/NewGameScreen.tsx create mode 100644 apps/web/src/components/layout/MainLayout.tsx create mode 100644 apps/web/src/components/layout/Sidebar.tsx create mode 100644 apps/web/src/components/layout/TopBar.tsx create mode 100644 apps/web/src/hooks/useGameLoop.ts create mode 100644 apps/web/src/index.css create mode 100644 apps/web/src/main.tsx create mode 100644 apps/web/src/pages/DashboardPage.tsx create mode 100644 apps/web/src/pages/InfrastructurePage.tsx create mode 100644 apps/web/src/pages/ModelsPage.tsx create mode 100644 apps/web/src/pages/SettingsPage.tsx create mode 100644 apps/web/src/store/index.ts create mode 100644 apps/web/tailwind.config.ts create mode 100644 apps/web/tsconfig.json create mode 100644 apps/web/vite.config.ts create mode 100644 package.json create mode 100644 packages/game-engine/package.json create mode 100644 packages/game-engine/src/engine.ts create mode 100644 packages/game-engine/src/index.ts create mode 100644 packages/game-engine/src/systems/computeSystem.ts create mode 100644 packages/game-engine/src/systems/economySystem.ts create mode 100644 packages/game-engine/src/systems/infrastructureSystem.ts create mode 100644 packages/game-engine/src/systems/marketSystem.ts create mode 100644 packages/game-engine/src/systems/reputationSystem.ts create mode 100644 packages/game-engine/src/systems/researchSystem.ts create mode 100644 packages/game-engine/src/tick.ts create mode 100644 packages/game-engine/tsconfig.json create mode 100644 packages/shared/package.json create mode 100644 packages/shared/src/constants/gameBalance.ts create mode 100644 packages/shared/src/index.ts create mode 100644 packages/shared/src/types/achievements.ts create mode 100644 packages/shared/src/types/competitors.ts create mode 100644 packages/shared/src/types/compute.ts create mode 100644 packages/shared/src/types/data.ts create mode 100644 packages/shared/src/types/economy.ts create mode 100644 packages/shared/src/types/events.ts create mode 100644 packages/shared/src/types/gameState.ts create mode 100644 packages/shared/src/types/infrastructure.ts create mode 100644 packages/shared/src/types/market.ts create mode 100644 packages/shared/src/types/models.ts create mode 100644 packages/shared/src/types/reputation.ts create mode 100644 packages/shared/src/types/research.ts create mode 100644 packages/shared/src/types/talent.ts create mode 100644 packages/shared/src/utils/formatting.ts create mode 100644 packages/shared/tsconfig.json create mode 100644 packages/tsconfig/base.json create mode 100644 packages/tsconfig/node.json create mode 100644 packages/tsconfig/package.json create mode 100644 packages/tsconfig/react.json create mode 100644 pnpm-lock.yaml create mode 100644 pnpm-workspace.yaml create mode 100644 turbo.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2b89bc5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +node_modules/ +dist/ +.turbo/ +*.tsbuildinfo +.env +.env.local +.DS_Store diff --git a/apps/web/index.html b/apps/web/index.html new file mode 100644 index 0000000..d61ff88 --- /dev/null +++ b/apps/web/index.html @@ -0,0 +1,16 @@ + + + + + + + AI Tycoon + + + + + +
+ + + diff --git a/apps/web/package.json b/apps/web/package.json new file mode 100644 index 0000000..0d29b15 --- /dev/null +++ b/apps/web/package.json @@ -0,0 +1,32 @@ +{ + "name": "@ai-tycoon/web", + "private": true, + "version": "0.0.1", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "typecheck": "tsc --noEmit", + "preview": "vite preview" + }, + "dependencies": { + "@ai-tycoon/shared": "workspace:*", + "@ai-tycoon/game-engine": "workspace:*", + "react": "^19.1.0", + "react-dom": "^19.1.0", + "recharts": "^2.15.0", + "zustand": "^5.0.0", + "lucide-react": "^0.475.0" + }, + "devDependencies": { + "@ai-tycoon/tsconfig": "workspace:*", + "@types/react": "^19.1.0", + "@types/react-dom": "^19.1.0", + "@vitejs/plugin-react": "^4.4.0", + "autoprefixer": "^10.4.20", + "postcss": "^8.5.0", + "tailwindcss": "^3.4.0", + "typescript": "^5.8.0", + "vite": "^6.3.0" + } +} diff --git a/apps/web/postcss.config.js b/apps/web/postcss.config.js new file mode 100644 index 0000000..2aa7205 --- /dev/null +++ b/apps/web/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +}; diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx new file mode 100644 index 0000000..558e0de --- /dev/null +++ b/apps/web/src/App.tsx @@ -0,0 +1,15 @@ +import { useGameStore } from '@/store'; +import { MainLayout } from '@/components/layout/MainLayout'; +import { NewGameScreen } from '@/components/game/NewGameScreen'; +import { useGameLoop } from '@/hooks/useGameLoop'; + +export function App() { + const companyName = useGameStore((s) => s.meta.companyName); + useGameLoop(); + + if (!companyName) { + return ; + } + + return ; +} diff --git a/apps/web/src/components/game/NewGameScreen.tsx b/apps/web/src/components/game/NewGameScreen.tsx new file mode 100644 index 0000000..2eb489a --- /dev/null +++ b/apps/web/src/components/game/NewGameScreen.tsx @@ -0,0 +1,81 @@ +import { useState } from 'react'; +import { Sparkles } from 'lucide-react'; +import { useGameStore } from '@/store'; + +const SUGGESTED_NAMES = [ + 'Nexus AI', 'Cortex Labs', 'Synapse Technologies', + 'Prometheus AI', 'Athena Research', 'Cognitron', + 'Neural Forge', 'DeepMind+', 'Cerebral Systems', +]; + +export function NewGameScreen() { + const [name, setName] = useState(''); + const startNewGame = useGameStore((s) => s.startNewGame); + + const handleStart = () => { + const companyName = name.trim() || SUGGESTED_NAMES[Math.floor(Math.random() * SUGGESTED_NAMES.length)]; + startNewGame(companyName); + }; + + return ( +
+
+
+
+ +

+ AI Tycoon +

+
+

+ Build the world's most powerful AI company. From startup to AGI. +

+
+ +
+
+ + setName(e.target.value)} + onKeyDown={(e) => e.key === 'Enter' && handleStart()} + placeholder={SUGGESTED_NAMES[0]} + className="w-full bg-surface-800 border border-surface-600 rounded-lg px-4 py-3 text-surface-100 placeholder:text-surface-500 focus:outline-none focus:ring-2 focus:ring-accent/50 focus:border-accent" + autoFocus + maxLength={30} + /> +
+ +
+

Or pick one:

+
+ {SUGGESTED_NAMES.slice(0, 6).map((suggestion) => ( + + ))} +
+
+ + +
+ +

+ Manage data centers, train models, serve millions of users, and achieve AGI. +

+
+
+ ); +} diff --git a/apps/web/src/components/layout/MainLayout.tsx b/apps/web/src/components/layout/MainLayout.tsx new file mode 100644 index 0000000..f1c3479 --- /dev/null +++ b/apps/web/src/components/layout/MainLayout.tsx @@ -0,0 +1,44 @@ +import { Sidebar } from './Sidebar'; +import { TopBar } from './TopBar'; +import { useGameStore } from '@/store'; +import { DashboardPage } from '@/pages/DashboardPage'; +import { InfrastructurePage } from '@/pages/InfrastructurePage'; +import { ModelsPage } from '@/pages/ModelsPage'; +import { SettingsPage } from '@/pages/SettingsPage'; + +export function MainLayout() { + const activePage = useGameStore((s) => s.activePage); + + return ( +
+ +
+ +
+ +
+
+
+ ); +} + +function PageRouter({ page }: { page: string }) { + switch (page) { + case 'dashboard': return ; + case 'infrastructure': return ; + case 'models': return ; + case 'settings': return ; + default: return ; + } +} + +function PlaceholderPage({ name }: { name: string }) { + return ( +
+
+

{name}

+

Coming soon...

+
+
+ ); +} diff --git a/apps/web/src/components/layout/Sidebar.tsx b/apps/web/src/components/layout/Sidebar.tsx new file mode 100644 index 0000000..fc7e728 --- /dev/null +++ b/apps/web/src/components/layout/Sidebar.tsx @@ -0,0 +1,65 @@ +import { + LayoutDashboard, Server, FlaskConical, Brain, + TrendingUp, Users, Database, Swords, DollarSign, Settings, +} from 'lucide-react'; +import { useGameStore, type ActivePage } from '@/store'; + +const NAV_ITEMS: { page: ActivePage; label: string; icon: typeof LayoutDashboard; era?: string }[] = [ + { page: 'dashboard', label: 'Dashboard', icon: LayoutDashboard }, + { page: 'infrastructure', label: 'Infrastructure', icon: Server }, + { page: 'research', label: 'Research', icon: FlaskConical }, + { page: 'models', label: 'Models', icon: Brain }, + { page: 'market', label: 'Market', icon: TrendingUp }, + { page: 'finance', label: 'Finance', icon: DollarSign }, + { 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: 'settings', label: 'Settings', icon: Settings }, +]; + +export function Sidebar() { + const activePage = useGameStore((s) => s.activePage); + const setActivePage = useGameStore((s) => s.setActivePage); + const companyName = useGameStore((s) => s.meta.companyName); + const era = useGameStore((s) => s.meta.currentEra); + + const eraOrder = ['startup', 'scaleup', 'bigtech', 'agi']; + const currentEraIdx = eraOrder.indexOf(era); + + return ( + + ); +} diff --git a/apps/web/src/components/layout/TopBar.tsx b/apps/web/src/components/layout/TopBar.tsx new file mode 100644 index 0000000..a9ba0cb --- /dev/null +++ b/apps/web/src/components/layout/TopBar.tsx @@ -0,0 +1,79 @@ +import { Pause, Play, Bell, Zap } from 'lucide-react'; +import { useGameStore } from '@/store'; +import { formatMoney, formatNumber, formatDuration } from '@ai-tycoon/shared'; +import type { GameSpeed } from '@ai-tycoon/shared'; + +const SPEEDS: GameSpeed[] = [1, 2, 5]; + +export function TopBar() { + const money = useGameStore((s) => s.economy.money); + const revenuePerTick = useGameStore((s) => s.economy.revenuePerTick); + const reputation = useGameStore((s) => s.reputation.score); + const totalFlops = useGameStore((s) => s.infrastructure.totalFlops); + const isPaused = useGameStore((s) => s.meta.isPaused); + const gameSpeed = useGameStore((s) => s.meta.gameSpeed); + const tickCount = useGameStore((s) => s.meta.tickCount); + const togglePause = useGameStore((s) => s.togglePause); + const setGameSpeed = useGameStore((s) => s.setGameSpeed); + const notifications = useGameStore((s) => s.notifications); + const unreadCount = notifications.filter(n => !n.read).length; + + return ( +
+
+ 0 ? 'up' : 'neutral'} /> + + + + +
+ +
+
+ + {SPEEDS.map((speed) => ( + + ))} +
+ + +
+
+ ); +} + +function KPI({ label, value, trend }: { label: string; value: string; trend?: 'up' | 'down' | 'neutral' }) { + return ( +
+ {label} + + {value} + +
+ ); +} diff --git a/apps/web/src/hooks/useGameLoop.ts b/apps/web/src/hooks/useGameLoop.ts new file mode 100644 index 0000000..53f60aa --- /dev/null +++ b/apps/web/src/hooks/useGameLoop.ts @@ -0,0 +1,51 @@ +import { useEffect, useRef } from 'react'; +import { GameEngine } from '@ai-tycoon/game-engine'; +import { useGameStore } from '@/store'; + +export function useGameLoop() { + const engineRef = useRef(null); + const companyName = useGameStore((s) => s.meta.companyName); + const gameSpeed = useGameStore((s) => s.meta.gameSpeed); + + useEffect(() => { + if (!companyName) return; + + const engine = new GameEngine({ + getState: () => { + const state = useGameStore.getState(); + return { + meta: state.meta, + economy: state.economy, + infrastructure: state.infrastructure, + compute: state.compute, + research: state.research, + models: state.models, + market: state.market, + competitors: state.competitors, + talent: state.talent, + data: state.data, + reputation: state.reputation, + events: state.events, + achievements: state.achievements, + }; + }, + setState: (partial) => { + useGameStore.getState().updateState(partial); + }, + }); + + engineRef.current = engine; + engine.start(); + + return () => { + engine.stop(); + engineRef.current = null; + }; + }, [companyName]); + + useEffect(() => { + if (engineRef.current) { + engineRef.current.setSpeed(gameSpeed); + } + }, [gameSpeed]); +} diff --git a/apps/web/src/index.css b/apps/web/src/index.css new file mode 100644 index 0000000..10bbd0d --- /dev/null +++ b/apps/web/src/index.css @@ -0,0 +1,25 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +@layer base { + * { + @apply border-surface-700; + } + + ::-webkit-scrollbar { + @apply w-2; + } + + ::-webkit-scrollbar-track { + @apply bg-surface-900; + } + + ::-webkit-scrollbar-thumb { + @apply bg-surface-600 rounded-full; + } + + ::-webkit-scrollbar-thumb:hover { + @apply bg-surface-500; + } +} diff --git a/apps/web/src/main.tsx b/apps/web/src/main.tsx new file mode 100644 index 0000000..f7c32b7 --- /dev/null +++ b/apps/web/src/main.tsx @@ -0,0 +1,10 @@ +import { StrictMode } from 'react'; +import { createRoot } from 'react-dom/client'; +import { App } from './App'; +import './index.css'; + +createRoot(document.getElementById('root')!).render( + + + , +); diff --git a/apps/web/src/pages/DashboardPage.tsx b/apps/web/src/pages/DashboardPage.tsx new file mode 100644 index 0000000..8e98f0a --- /dev/null +++ b/apps/web/src/pages/DashboardPage.tsx @@ -0,0 +1,193 @@ +import { useGameStore } from '@/store'; +import { formatMoney, formatNumber, formatPercent } from '@ai-tycoon/shared'; +import { + DollarSign, Server, Brain, Users, TrendingUp, + TrendingDown, Minus, Cpu, Zap, Shield, +} from 'lucide-react'; +import { LineChart, Line, XAxis, YAxis, Tooltip, ResponsiveContainer, Area, AreaChart } from 'recharts'; + +export function DashboardPage() { + const money = useGameStore((s) => s.economy.money); + const revenuePerTick = useGameStore((s) => s.economy.revenuePerTick); + const expensesPerTick = useGameStore((s) => s.economy.expensesPerTick); + const totalFlops = useGameStore((s) => s.infrastructure.totalFlops); + const dataCenters = useGameStore((s) => s.infrastructure.dataCenters); + const trainedModels = useGameStore((s) => s.models.trainedModels); + const activeTraining = useGameStore((s) => s.models.activeTraining); + const subscribers = useGameStore((s) => s.market.consumers.totalSubscribers); + const reputation = useGameStore((s) => s.reputation.score); + const inferenceUtil = useGameStore((s) => s.compute.inferenceUtilization); + const financialHistory = useGameStore((s) => s.economy.financialHistory); + const era = useGameStore((s) => s.meta.currentEra); + + const netIncome = revenuePerTick - expensesPerTick; + + return ( +
+

Dashboard

+ +
+ = 0 ? '+' : ''}${formatMoney(netIncome)}/s`} + trend={netIncome > 0 ? 'up' : netIncome < 0 ? 'down' : 'neutral'} + color="text-green-400" + /> + + + +
+ +
+
+

Revenue Over Time

+ {financialHistory.length > 1 ? ( + + + + + + + + + + + [formatMoney(value), 'Revenue']} + /> + + + + ) : ( +
+ No data yet — start earning revenue +
+ )} +
+ +
+

System Status

+
+ 0.9 ? 'bg-danger' : inferenceUtil > 0.7 ? 'bg-warning' : 'bg-success'} + /> + 70 ? 'bg-success' : reputation > 40 ? 'bg-warning' : 'bg-danger'} + /> + +
+
+
+ + {dataCenters.length === 0 && ( +
+

Get Started

+

+ Build your first data center to start training AI models. +

+ +
+ )} +
+ ); +} + +function StatCard({ + icon: Icon, label, value, subValue, trend, color, +}: { + icon: typeof DollarSign; + label: string; + value: string; + subValue?: string; + trend?: 'up' | 'down' | 'neutral'; + color?: string; +}) { + return ( +
+
+ + {label} +
+
{value}
+ {subValue && ( +
+ {trend === 'up' && } + {trend === 'down' && } + {trend === 'neutral' && } + {subValue} +
+ )} +
+ ); +} + +function StatusRow({ + icon: Icon, label, value, bar, barColor, +}: { + icon: typeof Cpu; + label: string; + value: string; + bar: number; + barColor: string; +}) { + return ( +
+
+
+ + {label} +
+ {value} +
+
+
+
+
+ ); +} diff --git a/apps/web/src/pages/InfrastructurePage.tsx b/apps/web/src/pages/InfrastructurePage.tsx new file mode 100644 index 0000000..45037f3 --- /dev/null +++ b/apps/web/src/pages/InfrastructurePage.tsx @@ -0,0 +1,173 @@ +import { useState } from 'react'; +import { Plus, Server, Cpu, MapPin } from 'lucide-react'; +import { useGameStore } from '@/store'; +import { formatMoney, formatNumber, formatPercent, GPU_CONFIGS, LOCATION_CONFIGS } from '@ai-tycoon/shared'; +import type { GpuType, LocationId } from '@ai-tycoon/shared'; + +export function InfrastructurePage() { + const dataCenters = useGameStore((s) => s.infrastructure.dataCenters); + const gpuPrices = useGameStore((s) => s.infrastructure.gpuMarketPrices); + const money = useGameStore((s) => s.economy.money); + const era = useGameStore((s) => s.meta.currentEra); + const buildDataCenter = useGameStore((s) => s.buildDataCenter); + const buyGpu = useGameStore((s) => s.buyGpu); + + const [showNewDC, setShowNewDC] = useState(false); + const [newDCName, setNewDCName] = useState(''); + const [newDCLocation, setNewDCLocation] = useState('us-west'); + + const eraOrder = ['startup', 'scaleup', 'bigtech', 'agi']; + const currentEraIdx = eraOrder.indexOf(era); + + const availableLocations = Object.values(LOCATION_CONFIGS).filter( + loc => eraOrder.indexOf(loc.availableAt) <= currentEraIdx, + ); + + const availableGpus = Object.values(GPU_CONFIGS).filter( + gpu => eraOrder.indexOf(gpu.availableAt) <= currentEraIdx, + ); + + const handleBuildDC = () => { + if (!newDCName.trim()) return; + buildDataCenter(newDCName.trim(), newDCLocation); + setNewDCName(''); + setShowNewDC(false); + }; + + return ( +
+
+

Infrastructure

+ +
+ + {showNewDC && ( +
+

Build New Data Center

+
+
+ + setNewDCName(e.target.value)} + placeholder="DC-West-01" + className="w-full bg-surface-800 border border-surface-600 rounded px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-accent/50" + autoFocus + /> +
+
+ + +
+
+
+ Cost: {formatMoney(10_000)} +
+ + +
+
+
+ )} + + {dataCenters.length === 0 ? ( +
+ +

No data centers yet. Build your first one to start hosting AI models.

+
+ ) : ( +
+ {dataCenters.map(dc => ( +
+
+
+

{dc.name}

+
+ + {LOCATION_CONFIGS[dc.location].name} +
+
+
+
Uptime: {formatPercent(dc.currentUptime)}
+
Cost: {formatMoney(dc.energyCostPerTick + dc.maintenanceCostPerTick)}/s
+
+
+ +
+

GPUs

+ {dc.gpus.length === 0 ? ( +

No GPUs installed

+ ) : ( +
+ {dc.gpus.map(inv => ( +
+
{GPU_CONFIGS[inv.type].name}
+
+ {inv.healthyCount}/{inv.count} healthy · {formatNumber(inv.healthyCount * GPU_CONFIGS[inv.type].flopsPerUnit)} FLOPS +
+
+ ))} +
+ )} +
+ +
+

Buy GPUs

+
+ {availableGpus.map(gpu => ( + + ))} +
+
+
+ ))} +
+ )} + +
+

GPU Market Prices

+
+ {availableGpus.map(gpu => ( +
+
+
{gpu.name}
+
{formatNumber(gpu.flopsPerUnit)} FLOPS/unit
+
+
{formatMoney(gpuPrices[gpu.type])}
+
+ ))} +
+
+
+ ); +} diff --git a/apps/web/src/pages/ModelsPage.tsx b/apps/web/src/pages/ModelsPage.tsx new file mode 100644 index 0000000..5806d0b --- /dev/null +++ b/apps/web/src/pages/ModelsPage.tsx @@ -0,0 +1,178 @@ +import { useState } from 'react'; +import { Brain, Play, Rocket, Settings2 } from 'lucide-react'; +import { useGameStore } from '@/store'; +import { formatNumber, formatPercent, formatDuration } from '@ai-tycoon/shared'; + +export function ModelsPage() { + const trainedModels = useGameStore((s) => s.models.trainedModels); + const activeTraining = useGameStore((s) => s.models.activeTraining); + const productLines = useGameStore((s) => s.models.productLines); + const totalFlops = useGameStore((s) => s.compute.totalFlops); + const trainingAlloc = useGameStore((s) => s.compute.trainingAllocation); + const totalData = useGameStore((s) => s.data.totalTrainingTokens); + const startTraining = useGameStore((s) => s.startTraining); + const deployModel = useGameStore((s) => s.deployModel); + const setTrainingAllocation = useGameStore((s) => s.setTrainingAllocation); + + const [modelName, setModelName] = useState(''); + + const trainingFlops = totalFlops * trainingAlloc; + const estimatedTicks = trainingFlops > 0 ? Math.max(30, Math.ceil(120 / (1 + trainingFlops * 0.1))) : Infinity; + const estimatedCapability = Math.min(100, Math.log(1 + trainingFlops * 0.5) * 10 + Math.log(1 + totalData / 1e9) * 5); + + const handleStartTraining = () => { + if (activeTraining || trainingFlops === 0) return; + const name = modelName.trim() || `Model v${trainedModels.length + 1}`; + startTraining({ + modelName: name, + generation: trainedModels.length + 1, + allocatedCompute: trainingFlops, + allocatedDataTokens: totalData, + totalTicks: estimatedTicks, + estimatedCapability, + }); + setModelName(''); + }; + + return ( +
+

Models

+ +
+

Compute Allocation

+
+ Training + setTrainingAllocation(Number(e.target.value) / 100)} + className="flex-1 accent-accent" + /> + Inference +
+
+ {formatPercent(trainingAlloc)} + {formatPercent(1 - trainingAlloc)} +
+
+ +
+

Train New Model

+ {activeTraining ? ( +
+
+ {activeTraining.modelName} + + {formatPercent(activeTraining.progressTicks / activeTraining.totalTicks)} complete + +
+
+
+
+
+ ETA: {formatDuration(activeTraining.totalTicks - activeTraining.progressTicks)} +
+
+ ) : ( +
+
+ + setModelName(e.target.value)} + placeholder={`Model v${trainedModels.length + 1}`} + className="w-full bg-surface-800 border border-surface-600 rounded px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-accent/50" + /> +
+
+
+
Training Compute
+
{formatNumber(trainingFlops)} FLOPS
+
+
+
Training Data
+
{formatNumber(totalData)} tokens
+
+
+
Est. Time
+
{trainingFlops > 0 ? formatDuration(estimatedTicks) : 'N/A'}
+
+
+
+ Estimated capability score: {estimatedCapability.toFixed(1)}/100 +
+ +
+ )} +
+ + {trainedModels.length > 0 && ( +
+

Trained Models

+ {trainedModels.map(model => ( +
+
+
+

{model.name}

+
+ Gen {model.generation} · Benchmark: {model.benchmarkScore.toFixed(1)}/100 · Safety: {model.safetyScore.toFixed(0)}/100 +
+
+
+ {model.isDeployed ? ( + Deployed + ) : ( + <> + {productLines.filter(pl => pl.type === 'text-api' || pl.type === 'chat-product').map(pl => ( + + ))} + + )} +
+
+
+ ))} +
+ )} + +
+

Product Lines

+ {productLines.map(pl => ( +
+
+
+

{pl.name}

+
+ {pl.modelId ? `Running: ${trainedModels.find(m => m.id === pl.modelId)?.name ?? 'Unknown'}` : 'No model deployed'} +
+
+ + {pl.isActive ? 'Active' : 'Inactive'} + +
+
+ ))} +
+
+ ); +} diff --git a/apps/web/src/pages/SettingsPage.tsx b/apps/web/src/pages/SettingsPage.tsx new file mode 100644 index 0000000..ce2ed45 --- /dev/null +++ b/apps/web/src/pages/SettingsPage.tsx @@ -0,0 +1,80 @@ +import { useGameStore } from '@/store'; + +export function SettingsPage() { + const settings = useGameStore((s) => s.meta.settings); + const companyName = useGameStore((s) => s.meta.companyName); + + const handleReset = () => { + if (confirm('Are you sure you want to reset all progress? This cannot be undone.')) { + localStorage.removeItem('ai-tycoon-save'); + window.location.reload(); + } + }; + + const handleExport = () => { + const state = useGameStore.getState(); + const { activePage, notifications, ...gameState } = state; + const blob = new Blob([JSON.stringify(gameState)], { type: 'application/json' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = `ai-tycoon-${companyName.replace(/\s+/g, '-').toLowerCase()}.json`; + a.click(); + URL.revokeObjectURL(url); + }; + + return ( +
+

Settings

+ +
+

Game

+ +
+
+
Sound Effects
+
Play UI sounds and notifications
+
+ {}} /> +
+ +
+
+
Music
+
Background music (coming soon)
+
+ {}} /> +
+
+ +
+

Save Data

+
+ + +
+
+
+ ); +} + +function ToggleSwitch({ checked, onChange }: { checked: boolean; onChange: () => void }) { + return ( + + ); +} diff --git a/apps/web/src/store/index.ts b/apps/web/src/store/index.ts new file mode 100644 index 0000000..8dd156c --- /dev/null +++ b/apps/web/src/store/index.ts @@ -0,0 +1,243 @@ +import { create } from 'zustand'; +import { persist } from 'zustand/middleware'; +import type { + GameState, Era, GameSpeed, GameSettings, + EconomyState, InfrastructureState, ComputeState, + ResearchState, ModelsState, MarketState, + CompetitorState, TalentState, DataState, + ReputationState, EventState, AchievementState, + DataCenter, GpuType, GpuInventory, TrainingJob, +} from '@ai-tycoon/shared'; +import { + INITIAL_SETTINGS, SAVE_VERSION, + INITIAL_ECONOMY, INITIAL_INFRASTRUCTURE, INITIAL_COMPUTE, + INITIAL_RESEARCH, INITIAL_MODELS, INITIAL_MARKET, + INITIAL_COMPETITORS, INITIAL_TALENT, INITIAL_DATA, + INITIAL_REPUTATION, INITIAL_EVENTS, INITIAL_ACHIEVEMENTS, + GPU_CONFIGS, +} from '@ai-tycoon/shared'; + +export type ActivePage = 'dashboard' | 'infrastructure' | 'research' | 'models' + | 'market' | 'talent' | 'data' | 'competitors' | 'finance' | 'settings'; + +interface UIState { + activePage: ActivePage; + notifications: GameNotification[]; +} + +export interface GameNotification { + id: string; + title: string; + message: string; + type: 'info' | 'success' | 'warning' | 'danger'; + tick: number; + read: boolean; +} + +interface Actions { + setActivePage: (page: ActivePage) => void; + addNotification: (n: Omit) => void; + dismissNotification: (id: string) => void; + startNewGame: (companyName: string) => void; + setGameSpeed: (speed: GameSpeed) => void; + togglePause: () => void; + setTrainingAllocation: (ratio: number) => void; + buyGpu: (dataCenterId: string, gpuType: GpuType, count: number) => void; + buildDataCenter: (name: string, location: DataCenter['location']) => void; + startTraining: (job: Omit) => void; + deployModel: (modelId: string, productLineId: string) => void; + setProductPricing: (productLineId: string, field: string, value: number) => void; + toggleProductLine: (productLineId: string) => void; + updateState: (partial: Partial) => void; +} + +type Store = GameState & UIState & Actions; + +const initialGameState: GameState = { + meta: { + saveVersion: SAVE_VERSION, + companyName: '', + currentEra: 'startup', + tickCount: 0, + lastTickTimestamp: Date.now(), + gameSpeed: 1, + isPaused: true, + createdAt: Date.now(), + totalPlayTime: 0, + settings: INITIAL_SETTINGS, + }, + economy: INITIAL_ECONOMY, + infrastructure: INITIAL_INFRASTRUCTURE, + compute: INITIAL_COMPUTE, + research: INITIAL_RESEARCH, + models: INITIAL_MODELS, + market: INITIAL_MARKET, + competitors: INITIAL_COMPETITORS, + talent: INITIAL_TALENT, + data: INITIAL_DATA, + reputation: INITIAL_REPUTATION, + events: INITIAL_EVENTS, + achievements: INITIAL_ACHIEVEMENTS, +}; + +export const useGameStore = create()( + persist( + (set, get) => ({ + ...initialGameState, + activePage: 'dashboard' as ActivePage, + notifications: [], + + setActivePage: (page) => set({ activePage: page }), + + addNotification: (n) => set((s) => ({ + notifications: [ + { ...n, id: crypto.randomUUID(), read: false }, + ...s.notifications.slice(0, 49), + ], + })), + + dismissNotification: (id) => set((s) => ({ + notifications: s.notifications.map(n => + n.id === id ? { ...n, read: true } : n, + ), + })), + + startNewGame: (companyName) => set({ + ...initialGameState, + meta: { + ...initialGameState.meta, + companyName, + isPaused: false, + createdAt: Date.now(), + lastTickTimestamp: Date.now(), + }, + activePage: 'dashboard', + notifications: [], + }), + + setGameSpeed: (speed) => set((s) => ({ + meta: { ...s.meta, gameSpeed: speed }, + })), + + togglePause: () => set((s) => ({ + meta: { ...s.meta, isPaused: !s.meta.isPaused }, + })), + + setTrainingAllocation: (ratio) => set((s) => ({ + compute: { ...s.compute, trainingAllocation: ratio, inferenceAllocation: 1 - ratio }, + })), + + buyGpu: (dataCenterId, gpuType, count) => set((s) => { + const price = s.infrastructure.gpuMarketPrices[gpuType] * count; + if (s.economy.money < price) return s; + + const dataCenters = s.infrastructure.dataCenters.map(dc => { + if (dc.id !== dataCenterId) return dc; + const existingIdx = dc.gpus.findIndex(g => g.type === gpuType); + let gpus: GpuInventory[]; + if (existingIdx >= 0) { + gpus = dc.gpus.map((g, i) => + i === existingIdx + ? { ...g, count: g.count + count, healthyCount: g.healthyCount + count } + : g, + ); + } else { + gpus = [...dc.gpus, { type: gpuType, count, healthyCount: count, failedCount: 0 }]; + } + return { ...dc, gpus }; + }); + + return { + economy: { ...s.economy, money: s.economy.money - price }, + infrastructure: { ...s.infrastructure, dataCenters }, + }; + }), + + buildDataCenter: (name, location) => set((s) => { + const buildCost = 10_000; + if (s.economy.money < buildCost) return s; + + const dc: DataCenter = { + id: crypto.randomUUID(), + name, + location, + gpus: [], + maxCapacity: 100, + coolingLevel: 0.5, + redundancyLevel: 0.3, + currentUptime: 1, + energyCostPerTick: 0, + maintenanceCostPerTick: 0, + }; + + return { + economy: { ...s.economy, money: s.economy.money - buildCost }, + infrastructure: { + ...s.infrastructure, + dataCenters: [...s.infrastructure.dataCenters, dc], + }, + }; + }), + + startTraining: (job) => set((s) => ({ + models: { + ...s.models, + activeTraining: { ...job, progressTicks: 0 }, + }, + })), + + deployModel: (modelId, productLineId) => set((s) => ({ + models: { + ...s.models, + trainedModels: s.models.trainedModels.map(m => + m.id === modelId ? { ...m, isDeployed: true } : m, + ), + productLines: s.models.productLines.map(pl => + pl.id === productLineId ? { ...pl, modelId, isActive: true } : pl, + ), + }, + })), + + setProductPricing: (productLineId, field, value) => set((s) => ({ + models: { + ...s.models, + productLines: s.models.productLines.map(pl => + pl.id === productLineId + ? { ...pl, pricing: { ...pl.pricing, [field]: value } } + : pl, + ), + }, + })), + + toggleProductLine: (productLineId) => set((s) => ({ + models: { + ...s.models, + productLines: s.models.productLines.map(pl => + pl.id === productLineId ? { ...pl, isActive: !pl.isActive } : pl, + ), + }, + })), + + updateState: (partial) => set((s) => { + const newState: Partial = {}; + for (const key of Object.keys(partial) as (keyof GameState)[]) { + const value = partial[key]; + const current = s[key]; + if (typeof value === 'object' && value !== null && !Array.isArray(value) && typeof current === 'object' && current !== null) { + (newState as Record)[key] = { ...current, ...value }; + } else { + (newState as Record)[key] = value; + } + } + return newState; + }), + }), + { + name: 'ai-tycoon-save', + partialize: (state) => { + const { activePage, notifications, ...rest } = state; + return rest; + }, + }, + ), +); diff --git a/apps/web/tailwind.config.ts b/apps/web/tailwind.config.ts new file mode 100644 index 0000000..26e1de0 --- /dev/null +++ b/apps/web/tailwind.config.ts @@ -0,0 +1,37 @@ +import type { Config } from 'tailwindcss'; + +export default { + content: ['./index.html', './src/**/*.{ts,tsx}'], + theme: { + extend: { + colors: { + surface: { + 50: '#f8fafc', + 100: '#f1f5f9', + 200: '#e2e8f0', + 300: '#cbd5e1', + 400: '#94a3b8', + 500: '#64748b', + 600: '#475569', + 700: '#334155', + 800: '#1e293b', + 900: '#0f172a', + 950: '#020617', + }, + accent: { + DEFAULT: '#6366f1', + light: '#818cf8', + dark: '#4f46e5', + }, + success: '#22c55e', + warning: '#f59e0b', + danger: '#ef4444', + }, + fontFamily: { + sans: ['Inter', 'system-ui', 'sans-serif'], + mono: ['JetBrains Mono', 'monospace'], + }, + }, + }, + plugins: [], +} satisfies Config; diff --git a/apps/web/tsconfig.json b/apps/web/tsconfig.json new file mode 100644 index 0000000..6c0574c --- /dev/null +++ b/apps/web/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "@ai-tycoon/tsconfig/react.json", + "compilerOptions": { + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"] + } + }, + "include": ["src"] +} diff --git a/apps/web/vite.config.ts b/apps/web/vite.config.ts new file mode 100644 index 0000000..95d423e --- /dev/null +++ b/apps/web/vite.config.ts @@ -0,0 +1,12 @@ +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; +import path from 'path'; + +export default defineConfig({ + plugins: [react()], + resolve: { + alias: { + '@': path.resolve(__dirname, './src'), + }, + }, +}); diff --git a/package.json b/package.json new file mode 100644 index 0000000..a6b3f8b --- /dev/null +++ b/package.json @@ -0,0 +1,19 @@ +{ + "name": "ai-tycoon", + "private": true, + "scripts": { + "dev": "turbo dev", + "build": "turbo build", + "typecheck": "turbo typecheck", + "lint": "turbo lint", + "clean": "turbo clean" + }, + "devDependencies": { + "turbo": "^2.5.0", + "typescript": "^5.8.0" + }, + "packageManager": "pnpm@10.33.0", + "pnpm": { + "onlyBuiltDependencies": ["esbuild"] + } +} diff --git a/packages/game-engine/package.json b/packages/game-engine/package.json new file mode 100644 index 0000000..831ac85 --- /dev/null +++ b/packages/game-engine/package.json @@ -0,0 +1,18 @@ +{ + "name": "@ai-tycoon/game-engine", + "private": true, + "version": "0.0.1", + "type": "module", + "main": "./src/index.ts", + "types": "./src/index.ts", + "scripts": { + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "@ai-tycoon/shared": "workspace:*" + }, + "devDependencies": { + "@ai-tycoon/tsconfig": "workspace:*", + "typescript": "^5.8.0" + } +} diff --git a/packages/game-engine/src/engine.ts b/packages/game-engine/src/engine.ts new file mode 100644 index 0000000..a933117 --- /dev/null +++ b/packages/game-engine/src/engine.ts @@ -0,0 +1,82 @@ +import type { GameState } from '@ai-tycoon/shared'; +import { processTick } from './tick'; + +export interface GameEngineCallbacks { + getState: () => GameState; + setState: (partial: Partial) => void; + onTick?: (tickCount: number) => void; + onEraChange?: (era: GameState['meta']['currentEra']) => void; +} + +export class GameEngine { + private callbacks: GameEngineCallbacks; + private lastFrameTime = 0; + private accumulator = 0; + private animFrameId: number | null = null; + private tickIntervalMs = 1000; + + constructor(callbacks: GameEngineCallbacks) { + this.callbacks = callbacks; + } + + start(): void { + if (this.animFrameId !== null) return; + this.lastFrameTime = performance.now(); + this.accumulator = 0; + this.loop(this.lastFrameTime); + } + + stop(): void { + if (this.animFrameId !== null) { + cancelAnimationFrame(this.animFrameId); + this.animFrameId = null; + } + } + + setSpeed(speed: number): void { + this.tickIntervalMs = 1000 / speed; + } + + processOfflineTicks(missedTicks: number): { revenue: number; expenses: number; ticksProcessed: number } { + let totalRevenue = 0; + let totalExpenses = 0; + + for (let i = 0; i < missedTicks; i++) { + const state = this.callbacks.getState(); + const result = processTick(state); + this.callbacks.setState(result); + totalRevenue += result.economy?.revenuePerTick ?? 0; + totalExpenses += result.economy?.expensesPerTick ?? 0; + } + + return { revenue: totalRevenue, expenses: totalExpenses, ticksProcessed: missedTicks }; + } + + private loop = (now: number): void => { + const delta = now - this.lastFrameTime; + this.lastFrameTime = now; + + const state = this.callbacks.getState(); + if (!state.meta.isPaused) { + this.accumulator += delta; + + let ticksThisFrame = 0; + const maxTicksPerFrame = 10; + + while (this.accumulator >= this.tickIntervalMs && ticksThisFrame < maxTicksPerFrame) { + const currentState = this.callbacks.getState(); + const result = processTick(currentState); + this.callbacks.setState(result); + this.accumulator -= this.tickIntervalMs; + ticksThisFrame++; + this.callbacks.onTick?.(currentState.meta.tickCount + 1); + } + + if (this.accumulator > this.tickIntervalMs * maxTicksPerFrame) { + this.accumulator = 0; + } + } + + this.animFrameId = requestAnimationFrame(this.loop); + }; +} diff --git a/packages/game-engine/src/index.ts b/packages/game-engine/src/index.ts new file mode 100644 index 0000000..f220bc3 --- /dev/null +++ b/packages/game-engine/src/index.ts @@ -0,0 +1,2 @@ +export { GameEngine } from './engine'; +export { processTick } from './tick'; diff --git a/packages/game-engine/src/systems/computeSystem.ts b/packages/game-engine/src/systems/computeSystem.ts new file mode 100644 index 0000000..4274512 --- /dev/null +++ b/packages/game-engine/src/systems/computeSystem.ts @@ -0,0 +1,24 @@ +import type { GameState, ComputeState, InfrastructureState } from '@ai-tycoon/shared'; + +export function processCompute(state: GameState, infrastructure: InfrastructureState): ComputeState { + const totalFlops = infrastructure.totalFlops; + const trainingAllocation = state.compute.trainingAllocation; + const inferenceAllocation = 1 - trainingAllocation; + + const inferenceFlops = totalFlops * inferenceAllocation; + const tokensPerSecondCapacity = inferenceFlops * 10; + + const tokensPerSecondDemand = state.compute.tokensPerSecondDemand; + const inferenceUtilization = tokensPerSecondCapacity > 0 + ? Math.min(1, tokensPerSecondDemand / tokensPerSecondCapacity) + : 0; + + return { + totalFlops, + trainingAllocation, + inferenceAllocation, + inferenceUtilization, + tokensPerSecondCapacity, + tokensPerSecondDemand, + }; +} diff --git a/packages/game-engine/src/systems/economySystem.ts b/packages/game-engine/src/systems/economySystem.ts new file mode 100644 index 0000000..e024ec4 --- /dev/null +++ b/packages/game-engine/src/systems/economySystem.ts @@ -0,0 +1,45 @@ +import type { GameState, EconomyState, InfrastructureState } from '@ai-tycoon/shared'; +import { FINANCIAL_SNAPSHOT_INTERVAL, MAX_FINANCIAL_HISTORY } from '@ai-tycoon/shared'; +import type { MarketTickResult } from './marketSystem'; + +export function processEconomy( + state: GameState, + market: MarketTickResult, + infrastructure: InfrastructureState, +): EconomyState { + const revenue = market.apiRevenue + market.subscriptionRevenue; + + const infraExpenses = infrastructure.dataCenters.reduce((sum, dc) => { + return sum + dc.energyCostPerTick + dc.maintenanceCostPerTick; + }, 0); + + const talentExpenses = state.talent.totalSalaryPerTick; + const dataExpenses = state.data.partnerships.reduce((sum, p) => sum + p.costPerTick, 0); + const expenses = infraExpenses + talentExpenses + dataExpenses; + + const money = state.economy.money + revenue - expenses; + + const financialHistory = [...state.economy.financialHistory]; + if (state.meta.tickCount % FINANCIAL_SNAPSHOT_INTERVAL === 0) { + financialHistory.push({ + tick: state.meta.tickCount, + money, + revenue, + expenses, + valuation: state.economy.funding.valuation, + }); + if (financialHistory.length > MAX_FINANCIAL_HISTORY) { + financialHistory.shift(); + } + } + + return { + ...state.economy, + money: Math.max(0, money), + totalRevenue: state.economy.totalRevenue + revenue, + totalExpenses: state.economy.totalExpenses + expenses, + revenuePerTick: revenue, + expensesPerTick: expenses, + financialHistory, + }; +} diff --git a/packages/game-engine/src/systems/infrastructureSystem.ts b/packages/game-engine/src/systems/infrastructureSystem.ts new file mode 100644 index 0000000..0ae2efd --- /dev/null +++ b/packages/game-engine/src/systems/infrastructureSystem.ts @@ -0,0 +1,72 @@ +import type { GameState, InfrastructureState } from '@ai-tycoon/shared'; +import { + GPU_CONFIGS, + LOCATION_CONFIGS, + GPU_PRICE_VOLATILITY, + GPU_FAILURE_RATE_BASE, + REDUNDANCY_FAILURE_REDUCTION, + BASE_ENERGY_COST_PER_FLOP, + BASE_MAINTENANCE_PER_GPU, +} from '@ai-tycoon/shared'; +import type { GpuType } from '@ai-tycoon/shared'; + +export function processInfrastructure(state: GameState): InfrastructureState { + const dataCenters = state.infrastructure.dataCenters.map(dc => { + const location = LOCATION_CONFIGS[dc.location]; + + const gpus = dc.gpus.map(inv => { + const failureRate = GPU_FAILURE_RATE_BASE * (1 - dc.redundancyLevel * REDUNDANCY_FAILURE_REDUCTION); + let newFailed = inv.failedCount; + for (let i = 0; i < inv.healthyCount; i++) { + if (Math.random() < failureRate) newFailed++; + } + const healthyCount = Math.max(0, inv.count - newFailed); + return { ...inv, healthyCount, failedCount: newFailed }; + }); + + let totalFlops = 0; + let totalPower = 0; + let totalGpuCount = 0; + for (const inv of gpus) { + const config = GPU_CONFIGS[inv.type]; + totalFlops += inv.healthyCount * config.flopsPerUnit; + totalPower += inv.healthyCount * config.basePowerDraw; + totalGpuCount += inv.count; + } + + const energyCostPerTick = totalPower * BASE_ENERGY_COST_PER_FLOP * location.energyCostMultiplier; + const maintenanceCostPerTick = totalGpuCount * BASE_MAINTENANCE_PER_GPU; + const currentUptime = totalGpuCount > 0 + ? gpus.reduce((s, inv) => s + inv.healthyCount, 0) / totalGpuCount + : 1; + + return { ...dc, gpus, energyCostPerTick, maintenanceCostPerTick, currentUptime }; + }); + + const gpuMarketPrices = { ...state.infrastructure.gpuMarketPrices }; + for (const gpuType of Object.keys(gpuMarketPrices) as GpuType[]) { + const basePrice = GPU_CONFIGS[gpuType].basePrice; + const variation = (Math.random() - 0.5) * 2 * GPU_PRICE_VOLATILITY; + const currentPrice = gpuMarketPrices[gpuType]; + const newPrice = currentPrice * (1 + variation); + gpuMarketPrices[gpuType] = Math.max(basePrice * 0.7, Math.min(basePrice * 1.5, newPrice)); + } + + let totalFlops = 0; + let totalUptime = 0; + let dcCount = 0; + for (const dc of dataCenters) { + for (const inv of dc.gpus) { + totalFlops += inv.healthyCount * GPU_CONFIGS[inv.type].flopsPerUnit; + } + totalUptime += dc.currentUptime; + dcCount++; + } + + return { + dataCenters, + gpuMarketPrices, + totalFlops, + totalUptime: dcCount > 0 ? totalUptime / dcCount : 1, + }; +} diff --git a/packages/game-engine/src/systems/marketSystem.ts b/packages/game-engine/src/systems/marketSystem.ts new file mode 100644 index 0000000..5598fbb --- /dev/null +++ b/packages/game-engine/src/systems/marketSystem.ts @@ -0,0 +1,67 @@ +import type { GameState, MarketState, ComputeState } from '@ai-tycoon/shared'; +import { + CONSUMER_BASE_GROWTH, + CONSUMER_QUALITY_GROWTH_MULTIPLIER, + CONSUMER_BASE_CHURN, + API_TOKENS_PER_REQUEST, +} from '@ai-tycoon/shared'; + +export interface MarketTickResult { + marketState: MarketState; + apiRevenue: number; + subscriptionRevenue: number; +} + +export function processMarket(state: GameState, compute: ComputeState): MarketTickResult { + const bestModel = state.models.trainedModels + .filter(m => m.isDeployed) + .sort((a, b) => b.benchmarkScore - a.benchmarkScore)[0]; + + const modelQuality = bestModel ? bestModel.benchmarkScore / 100 : 0; + const chatProduct = state.models.productLines.find(p => p.type === 'chat-product'); + const textApi = state.models.productLines.find(p => p.type === 'text-api'); + + const consumers = { ...state.market.consumers }; + if (chatProduct?.isActive && bestModel) { + const growthRate = CONSUMER_BASE_GROWTH + modelQuality * CONSUMER_QUALITY_GROWTH_MULTIPLIER; + const churnRate = CONSUMER_BASE_CHURN * (1 + (1 - consumers.satisfaction)); + consumers.growthRatePerTick = growthRate; + consumers.churnRatePerTick = churnRate; + const newSubs = consumers.totalSubscribers * growthRate; + const lostSubs = consumers.totalSubscribers * churnRate; + consumers.totalSubscribers = Math.max(0, consumers.totalSubscribers + newSubs - lostSubs); + + if (consumers.totalSubscribers < 10 && modelQuality > 0) { + consumers.totalSubscribers += 1; + } + + consumers.satisfaction = Math.min(1, Math.max(0, + 0.3 + modelQuality * 0.5 + (1 - compute.inferenceUtilization) * 0.2, + )); + } + + const subscriptionRevenue = chatProduct?.isActive + ? consumers.totalSubscribers * (chatProduct.pricing.subscriptionPrice / 30 / 24 / 3600) + : 0; + + const enterprise = { ...state.market.enterprise }; + let apiRevenue = 0; + if (textApi?.isActive && bestModel) { + let totalTokens = 0; + for (const contract of enterprise.activeContracts) { + totalTokens += contract.tokensPerTick; + apiRevenue += (contract.tokensPerTick / 1_000_000) * contract.pricePerMToken; + } + enterprise.totalApiCallsPerTick = totalTokens / API_TOKENS_PER_REQUEST; + } + + return { + marketState: { + ...state.market, + consumers, + enterprise, + }, + apiRevenue, + subscriptionRevenue, + }; +} diff --git a/packages/game-engine/src/systems/reputationSystem.ts b/packages/game-engine/src/systems/reputationSystem.ts new file mode 100644 index 0000000..4202f31 --- /dev/null +++ b/packages/game-engine/src/systems/reputationSystem.ts @@ -0,0 +1,27 @@ +import type { GameState, ReputationState } from '@ai-tycoon/shared'; +import { MAX_REPUTATION_HISTORY } from '@ai-tycoon/shared'; + +export function processReputation(state: GameState): ReputationState { + const { safetyRecord, publicPerception, employeeSatisfaction, regulatoryStanding } = state.reputation; + + const score = Math.round( + safetyRecord * 0.3 + + publicPerception * 0.3 + + employeeSatisfaction * 0.2 + + regulatoryStanding * 0.2, + ); + + const reputationHistory = [...state.reputation.reputationHistory]; + if (state.meta.tickCount % 120 === 0) { + reputationHistory.push({ tick: state.meta.tickCount, score }); + if (reputationHistory.length > MAX_REPUTATION_HISTORY) { + reputationHistory.shift(); + } + } + + return { + ...state.reputation, + score, + reputationHistory, + }; +} diff --git a/packages/game-engine/src/systems/researchSystem.ts b/packages/game-engine/src/systems/researchSystem.ts new file mode 100644 index 0000000..c4c7699 --- /dev/null +++ b/packages/game-engine/src/systems/researchSystem.ts @@ -0,0 +1,29 @@ +import type { GameState, ResearchState, ComputeState } from '@ai-tycoon/shared'; + +export function processResearch(state: GameState, compute: ComputeState): ResearchState { + const active = state.research.activeResearch; + if (!active) return state.research; + + const researcherBoost = state.talent.departments.research.headcount * + state.talent.departments.research.effectiveness; + const speedMultiplier = 1 + researcherBoost * 0.1; + + const newProgress = active.progressTicks + speedMultiplier; + + if (newProgress >= active.totalTicks) { + return { + ...state.research, + completedResearch: [...state.research.completedResearch, active.researchId], + activeResearch: null, + researchPoints: state.research.researchPoints + 1, + }; + } + + return { + ...state.research, + activeResearch: { + ...active, + progressTicks: newProgress, + }, + }; +} diff --git a/packages/game-engine/src/tick.ts b/packages/game-engine/src/tick.ts new file mode 100644 index 0000000..3d8ac4e --- /dev/null +++ b/packages/game-engine/src/tick.ts @@ -0,0 +1,33 @@ +import type { GameState } from '@ai-tycoon/shared'; +import { processEconomy } from './systems/economySystem'; +import { processInfrastructure } from './systems/infrastructureSystem'; +import { processCompute } from './systems/computeSystem'; +import { processResearch } from './systems/researchSystem'; +import { processMarket } from './systems/marketSystem'; +import { processReputation } from './systems/reputationSystem'; + +export function processTick(state: GameState): Partial { + const infrastructure = processInfrastructure(state); + const compute = processCompute(state, infrastructure); + const research = processResearch(state, compute); + const market = processMarket(state, compute); + const reputation = processReputation(state); + const economy = processEconomy(state, market, infrastructure); + + const tickCount = state.meta.tickCount + 1; + + return { + meta: { + ...state.meta, + tickCount, + lastTickTimestamp: Date.now(), + totalPlayTime: state.meta.totalPlayTime + 1, + }, + economy, + infrastructure, + compute, + research, + market: market.marketState, + reputation, + }; +} diff --git a/packages/game-engine/tsconfig.json b/packages/game-engine/tsconfig.json new file mode 100644 index 0000000..5664219 --- /dev/null +++ b/packages/game-engine/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "@ai-tycoon/tsconfig/base.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "src" + }, + "include": ["src"] +} diff --git a/packages/shared/package.json b/packages/shared/package.json new file mode 100644 index 0000000..ab288a8 --- /dev/null +++ b/packages/shared/package.json @@ -0,0 +1,15 @@ +{ + "name": "@ai-tycoon/shared", + "private": true, + "version": "0.0.1", + "type": "module", + "main": "./src/index.ts", + "types": "./src/index.ts", + "scripts": { + "typecheck": "tsc --noEmit" + }, + "devDependencies": { + "@ai-tycoon/tsconfig": "workspace:*", + "typescript": "^5.8.0" + } +} diff --git a/packages/shared/src/constants/gameBalance.ts b/packages/shared/src/constants/gameBalance.ts new file mode 100644 index 0000000..cddb8b2 --- /dev/null +++ b/packages/shared/src/constants/gameBalance.ts @@ -0,0 +1,42 @@ +export const TICK_INTERVAL_MS = 1000; +export const MAX_OFFLINE_TICKS = 86_400; +export const OFFLINE_EFFICIENCY = 0.8; +export const FAST_FORWARD_BATCH_SIZE = 100; +export const AUTO_SAVE_INTERVAL_TICKS = 60; +export const FINANCIAL_SNAPSHOT_INTERVAL = 60; +export const MAX_FINANCIAL_HISTORY = 1000; +export const MAX_EVENT_HISTORY = 50; +export const MAX_REPUTATION_HISTORY = 500; + +export const STARTING_MONEY = 50_000; +export const BASE_ENERGY_COST_PER_FLOP = 0.001; +export const BASE_MAINTENANCE_PER_GPU = 0.5; + +export const TRAINING_BASE_TICKS = 120; +export const TRAINING_COMPUTE_MULTIPLIER = 0.8; +export const TRAINING_DATA_QUALITY_WEIGHT = 0.3; + +export const CAPABILITY_FORMULA = { + computeWeight: 0.4, + dataWeight: 0.3, + researcherWeight: 0.2, + efficiencyWeight: 0.1, +}; + +export const CONSUMER_BASE_GROWTH = 0.002; +export const CONSUMER_QUALITY_GROWTH_MULTIPLIER = 0.01; +export const CONSUMER_PRICE_ELASTICITY = -0.5; +export const CONSUMER_BASE_CHURN = 0.001; + +export const API_TOKENS_PER_REQUEST = 500; +export const API_REVENUE_PER_MTOK = 1.0; + +export const ERA_THRESHOLDS = { + scaleup: { revenue: 10_000, capability: 15, reputation: 30 }, + bigtech: { revenue: 1_000_000, capability: 50, reputation: 60 }, + agi: { revenue: 100_000_000, capability: 90, reputation: 70 }, +}; + +export const GPU_PRICE_VOLATILITY = 0.02; +export const GPU_FAILURE_RATE_BASE = 0.0001; +export const REDUNDANCY_FAILURE_REDUCTION = 0.5; diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts new file mode 100644 index 0000000..538daf1 --- /dev/null +++ b/packages/shared/src/index.ts @@ -0,0 +1,15 @@ +export * from './types/gameState'; +export * from './types/economy'; +export * from './types/infrastructure'; +export * from './types/compute'; +export * from './types/research'; +export * from './types/models'; +export * from './types/market'; +export * from './types/competitors'; +export * from './types/talent'; +export * from './types/data'; +export * from './types/reputation'; +export * from './types/events'; +export * from './types/achievements'; +export * from './utils/formatting'; +export * from './constants/gameBalance'; diff --git a/packages/shared/src/types/achievements.ts b/packages/shared/src/types/achievements.ts new file mode 100644 index 0000000..d292503 --- /dev/null +++ b/packages/shared/src/types/achievements.ts @@ -0,0 +1,28 @@ +export interface AchievementState { + unlocked: UnlockedAchievement[]; + progress: Record; +} + +export interface UnlockedAchievement { + id: string; + unlockedAtTick: number; +} + +export interface AchievementDefinition { + id: string; + name: string; + description: string; + icon: string; + condition: AchievementCondition; +} + +export interface AchievementCondition { + field: string; + operator: 'gt' | 'gte' | 'eq'; + value: number; +} + +export const INITIAL_ACHIEVEMENTS: AchievementState = { + unlocked: [], + progress: {}, +}; diff --git a/packages/shared/src/types/competitors.ts b/packages/shared/src/types/competitors.ts new file mode 100644 index 0000000..1c30918 --- /dev/null +++ b/packages/shared/src/types/competitors.ts @@ -0,0 +1,35 @@ +export interface CompetitorState { + rivals: Competitor[]; + industryBenchmark: number; +} + +export interface Competitor { + id: string; + name: string; + archetype: CompetitorArchetype; + personality: CompetitorPersonality; + status: 'active' | 'acquired' | 'failed'; + estimatedCapability: number; + estimatedRevenue: number; + estimatedUsers: number; + reputation: number; + latestModelName: string; + completedMilestones: string[]; + nextMilestoneAtTick: number; +} + +export type CompetitorArchetype = 'safety-first' | 'move-fast' | 'big-tech' | 'open-source' | 'stealth-startup'; + +export interface CompetitorPersonality { + aggression: number; + safetyFocus: number; + openSourceTendency: number; + marketingFocus: number; + researchFocus: number; + riskTolerance: number; +} + +export const INITIAL_COMPETITORS: CompetitorState = { + rivals: [], + industryBenchmark: 0, +}; diff --git a/packages/shared/src/types/compute.ts b/packages/shared/src/types/compute.ts new file mode 100644 index 0000000..1f3c45d --- /dev/null +++ b/packages/shared/src/types/compute.ts @@ -0,0 +1,17 @@ +export interface ComputeState { + totalFlops: number; + trainingAllocation: number; + inferenceAllocation: number; + inferenceUtilization: number; + tokensPerSecondCapacity: number; + tokensPerSecondDemand: number; +} + +export const INITIAL_COMPUTE: ComputeState = { + totalFlops: 0, + trainingAllocation: 0.5, + inferenceAllocation: 0.5, + inferenceUtilization: 0, + tokensPerSecondCapacity: 0, + tokensPerSecondDemand: 0, +}; diff --git a/packages/shared/src/types/data.ts b/packages/shared/src/types/data.ts new file mode 100644 index 0000000..e42ce2e --- /dev/null +++ b/packages/shared/src/types/data.ts @@ -0,0 +1,47 @@ +export interface DataState { + ownedDatasets: OwnedDataset[]; + userDataGenerationRate: number; + totalTrainingTokens: number; + partnerships: DataPartnership[]; +} + +export interface OwnedDataset { + id: string; + name: string; + domain: DataDomain; + sizeTokens: number; + quality: number; + legalRisk: number; + acquiredAtTick: number; +} + +export type DataDomain = 'web' | 'books' | 'code' | 'scientific' | 'conversation' + | 'multilingual' | 'images' | 'video' | 'audio' | 'synthetic'; + +export interface DataPartnership { + id: string; + partnerName: string; + domain: DataDomain; + tokensPerTick: number; + costPerTick: number; + exclusivity: boolean; + durationTicks: number; + startTick: number; +} + +export const INITIAL_DATA: DataState = { + ownedDatasets: [ + { + id: 'web-crawl-basic', + name: 'Basic Web Crawl', + domain: 'web', + sizeTokens: 1_000_000_000, + quality: 0.3, + legalRisk: 0.2, + acquiredAtTick: 0, + }, + ], + userDataGenerationRate: 0, + totalTrainingTokens: 1_000_000_000, + partnerships: [], +}; diff --git a/packages/shared/src/types/economy.ts b/packages/shared/src/types/economy.ts new file mode 100644 index 0000000..95e3bcf --- /dev/null +++ b/packages/shared/src/types/economy.ts @@ -0,0 +1,63 @@ +export interface EconomyState { + money: number; + totalRevenue: number; + totalExpenses: number; + revenuePerTick: number; + expensesPerTick: number; + funding: FundingState; + financialHistory: FinancialSnapshot[]; +} + +export interface FundingState { + totalRaised: number; + currentRound: FundingRound | null; + completedRounds: CompletedFundingRound[]; + founderEquity: number; + valuation: number; + isPublic: boolean; +} + +export type FundingRoundType = 'seed' | 'seriesA' | 'seriesB' | 'seriesC' | 'seriesD' | 'ipo'; + +export interface FundingRound { + type: FundingRoundType; + targetAmount: number; + dilution: number; + requirements: { + minRevenue?: number; + minUsers?: number; + minReputation?: number; + }; +} + +export interface CompletedFundingRound { + type: FundingRoundType; + amount: number; + dilution: number; + completedAtTick: number; +} + +export interface FinancialSnapshot { + tick: number; + money: number; + revenue: number; + expenses: number; + valuation: number; +} + +export const INITIAL_ECONOMY: EconomyState = { + money: 50_000, + totalRevenue: 0, + totalExpenses: 0, + revenuePerTick: 0, + expensesPerTick: 0, + funding: { + totalRaised: 0, + currentRound: null, + completedRounds: [], + founderEquity: 1.0, + valuation: 100_000, + isPublic: false, + }, + financialHistory: [], +}; diff --git a/packages/shared/src/types/events.ts b/packages/shared/src/types/events.ts new file mode 100644 index 0000000..7245267 --- /dev/null +++ b/packages/shared/src/types/events.ts @@ -0,0 +1,74 @@ +import type { Era } from './gameState'; + +export interface EventState { + activeEvents: ActiveEvent[]; + eventHistory: EventHistoryEntry[]; + eventCooldowns: Record; + eventOccurrences: Record; +} + +export interface ActiveEvent { + eventId: string; + instanceId: string; + triggeredAtTick: number; + expiresAtTick: number; + title: string; + description: string; + category: EventCategory; + choices: EventChoice[]; + defaultChoiceIndex: number; +} + +export type EventCategory = 'industry' | 'regulatory' | 'pr' | 'internal' | 'market'; + +export interface EventChoice { + label: string; + description: string; + consequences: EventConsequence[]; +} + +export interface EventConsequence { + type: 'money' | 'reputation' | 'compute' | 'talent' | 'research_speed' + | 'regulation' | 'competitor' | 'unlock' | 'lock' | 'chain_event'; + value: number; + target?: string; + delay?: number; +} + +export interface EventHistoryEntry { + eventId: string; + instanceId: string; + title: string; + category: EventCategory; + tick: number; + chosenOptionIndex: number; +} + +export interface EventDefinition { + id: string; + title: string; + descriptionTemplate: string; + category: EventCategory; + eras: Era[]; + weight: number; + cooldownTicks: number; + maxOccurrences: number; + prerequisites: string[]; + conditions: EventCondition[]; + choices: EventChoice[]; + defaultChoiceIndex: number; + expiryTicks: number; +} + +export interface EventCondition { + field: string; + operator: 'gt' | 'lt' | 'gte' | 'lte' | 'eq'; + value: number; +} + +export const INITIAL_EVENTS: EventState = { + activeEvents: [], + eventHistory: [], + eventCooldowns: {}, + eventOccurrences: {}, +}; diff --git a/packages/shared/src/types/gameState.ts b/packages/shared/src/types/gameState.ts new file mode 100644 index 0000000..5bf90eb --- /dev/null +++ b/packages/shared/src/types/gameState.ts @@ -0,0 +1,63 @@ +import type { EconomyState } from './economy'; +import type { InfrastructureState } from './infrastructure'; +import type { ComputeState } from './compute'; +import type { ResearchState } from './research'; +import type { ModelsState } from './models'; +import type { MarketState } from './market'; +import type { CompetitorState } from './competitors'; +import type { TalentState } from './talent'; +import type { DataState } from './data'; +import type { ReputationState } from './reputation'; +import type { EventState } from './events'; +import type { AchievementState } from './achievements'; + +export interface GameState { + meta: GameMeta; + economy: EconomyState; + infrastructure: InfrastructureState; + compute: ComputeState; + research: ResearchState; + models: ModelsState; + market: MarketState; + competitors: CompetitorState; + talent: TalentState; + data: DataState; + reputation: ReputationState; + events: EventState; + achievements: AchievementState; +} + +export interface GameMeta { + saveVersion: number; + companyName: string; + currentEra: Era; + tickCount: number; + lastTickTimestamp: number; + gameSpeed: GameSpeed; + isPaused: boolean; + createdAt: number; + totalPlayTime: number; + settings: GameSettings; +} + +export type Era = 'startup' | 'scaleup' | 'bigtech' | 'agi'; + +export type GameSpeed = 1 | 2 | 5; + +export interface GameSettings { + autoSaveInterval: number; + notificationsEnabled: boolean; + soundEnabled: boolean; + musicVolume: number; + sfxVolume: number; +} + +export const INITIAL_SETTINGS: GameSettings = { + autoSaveInterval: 60, + notificationsEnabled: true, + soundEnabled: true, + musicVolume: 0.5, + sfxVolume: 0.7, +}; + +export const SAVE_VERSION = 1; diff --git a/packages/shared/src/types/infrastructure.ts b/packages/shared/src/types/infrastructure.ts new file mode 100644 index 0000000..abc9464 --- /dev/null +++ b/packages/shared/src/types/infrastructure.ts @@ -0,0 +1,84 @@ +import type { Era } from './gameState'; + +export interface InfrastructureState { + dataCenters: DataCenter[]; + gpuMarketPrices: Record; + totalFlops: number; + totalUptime: number; +} + +export interface DataCenter { + id: string; + name: string; + location: LocationId; + gpus: GpuInventory[]; + maxCapacity: number; + coolingLevel: number; + redundancyLevel: number; + currentUptime: number; + energyCostPerTick: number; + maintenanceCostPerTick: number; +} + +export interface GpuInventory { + type: GpuType; + count: number; + healthyCount: number; + failedCount: number; +} + +export type GpuType = 'consumer' | 't4' | 'a100' | 'h100' | 'b200' | 'custom'; + +export type LocationId = 'us-west' | 'us-east' | 'eu-west' | 'eu-north' | 'asia-east' | 'asia-south' | 'middle-east'; + +export interface LocationConfig { + id: LocationId; + name: string; + energyCostMultiplier: number; + latencyTier: number; + regulatoryStrictness: number; + politicalStability: number; + availableAt: Era; +} + +export interface GpuConfig { + type: GpuType; + name: string; + flopsPerUnit: number; + basePowerDraw: number; + basePrice: number; + availableAt: Era; +} + +export const GPU_CONFIGS: Record = { + consumer: { type: 'consumer', name: 'Consumer GPU', flopsPerUnit: 1, basePowerDraw: 0.3, basePrice: 800, availableAt: 'startup' }, + t4: { type: 't4', name: 'NVIDIA T4', flopsPerUnit: 8, basePowerDraw: 0.5, basePrice: 5_000, availableAt: 'startup' }, + a100: { type: 'a100', name: 'NVIDIA A100', flopsPerUnit: 40, basePowerDraw: 1.0, basePrice: 15_000, availableAt: 'scaleup' }, + h100: { type: 'h100', name: 'NVIDIA H100', flopsPerUnit: 120, basePowerDraw: 1.2, basePrice: 35_000, availableAt: 'scaleup' }, + b200: { type: 'b200', name: 'NVIDIA B200', flopsPerUnit: 400, basePowerDraw: 1.5, basePrice: 50_000, availableAt: 'bigtech' }, + custom: { type: 'custom', name: 'Custom ASIC', flopsPerUnit: 800, basePowerDraw: 1.0, basePrice: 80_000, availableAt: 'agi' }, +}; + +export const LOCATION_CONFIGS: Record = { + 'us-west': { id: 'us-west', name: 'US West (Oregon)', energyCostMultiplier: 1.0, latencyTier: 1, regulatoryStrictness: 0.3, politicalStability: 0.9, availableAt: 'startup' }, + 'us-east': { id: 'us-east', name: 'US East (Virginia)', energyCostMultiplier: 1.1, latencyTier: 1, regulatoryStrictness: 0.3, politicalStability: 0.9, availableAt: 'startup' }, + 'eu-west': { id: 'eu-west', name: 'EU West (Ireland)', energyCostMultiplier: 1.3, latencyTier: 2, regulatoryStrictness: 0.7, politicalStability: 0.85, availableAt: 'scaleup' }, + 'eu-north': { id: 'eu-north', name: 'EU North (Sweden)', energyCostMultiplier: 0.8, latencyTier: 2, regulatoryStrictness: 0.6, politicalStability: 0.95, availableAt: 'scaleup' }, + 'asia-east': { id: 'asia-east', name: 'Asia East (Tokyo)', energyCostMultiplier: 1.4, latencyTier: 3, regulatoryStrictness: 0.4, politicalStability: 0.9, availableAt: 'scaleup' }, + 'asia-south': { id: 'asia-south', name: 'Asia South (Mumbai)', energyCostMultiplier: 0.6, latencyTier: 3, regulatoryStrictness: 0.2, politicalStability: 0.7, availableAt: 'bigtech' }, + 'middle-east': { id: 'middle-east', name: 'Middle East (UAE)', energyCostMultiplier: 0.5, latencyTier: 3, regulatoryStrictness: 0.1, politicalStability: 0.6, availableAt: 'bigtech' }, +}; + +export const INITIAL_INFRASTRUCTURE: InfrastructureState = { + dataCenters: [], + gpuMarketPrices: { + consumer: 800, + t4: 5_000, + a100: 15_000, + h100: 35_000, + b200: 50_000, + custom: 80_000, + }, + totalFlops: 0, + totalUptime: 1, +}; diff --git a/packages/shared/src/types/market.ts b/packages/shared/src/types/market.ts new file mode 100644 index 0000000..49dfdd0 --- /dev/null +++ b/packages/shared/src/types/market.ts @@ -0,0 +1,73 @@ +export interface MarketState { + consumers: ConsumerMarket; + enterprise: EnterpriseMarket; + overloadPolicy: OverloadPolicy; + openSourcedModels: string[]; +} + +export interface ConsumerMarket { + totalSubscribers: number; + churnRatePerTick: number; + growthRatePerTick: number; + satisfaction: number; + viralCoefficient: number; +} + +export interface EnterpriseMarket { + activeContracts: EnterpriseContract[]; + pendingRFPs: EnterpriseRFP[]; + totalApiCallsPerTick: number; + averageTokensPerCall: number; +} + +export interface EnterpriseContract { + id: string; + customerName: string; + segment: 'startup' | 'mid-market' | 'enterprise' | 'government'; + tokensPerTick: number; + pricePerMToken: number; + slaUptime: number; + startTick: number; + durationTicks: number; + satisfaction: number; +} + +export interface EnterpriseRFP { + id: string; + customerName: string; + segment: 'startup' | 'mid-market' | 'enterprise' | 'government'; + requiredCapability: number; + offeredPricePerMToken: number; + requiredSlaUptime: number; + expiresAtTick: number; +} + +export interface OverloadPolicy { + maxQueueDepth: number; + rateLimitPerCustomer: number; + degradeQualityUnderLoad: boolean; + prioritizeEnterprise: boolean; +} + +export const INITIAL_MARKET: MarketState = { + consumers: { + totalSubscribers: 0, + churnRatePerTick: 0.001, + growthRatePerTick: 0, + satisfaction: 0.5, + viralCoefficient: 0, + }, + enterprise: { + activeContracts: [], + pendingRFPs: [], + totalApiCallsPerTick: 0, + averageTokensPerCall: 500, + }, + overloadPolicy: { + maxQueueDepth: 100, + rateLimitPerCustomer: 1000, + degradeQualityUnderLoad: false, + prioritizeEnterprise: true, + }, + openSourcedModels: [], +}; diff --git a/packages/shared/src/types/models.ts b/packages/shared/src/types/models.ts new file mode 100644 index 0000000..fba6582 --- /dev/null +++ b/packages/shared/src/types/models.ts @@ -0,0 +1,106 @@ +export interface ModelsState { + trainedModels: TrainedModel[]; + activeTraining: TrainingJob | null; + productLines: ProductLine[]; +} + +export interface TrainedModel { + id: string; + name: string; + generation: number; + parameterCount: number; + trainingDataSize: number; + capabilities: ModelCapabilities; + safetyScore: number; + benchmarkScore: number; + tuning: ModelTuning; + isDeployed: boolean; + trainedAtTick: number; +} + +export interface ModelCapabilities { + reasoning: number; + coding: number; + creative: number; + multimodal: number; + agents: number; + speed: number; +} + +export interface ModelTuning { + preset: TuningPreset; + verbosity?: number; + safetyLevel?: number; + creativity?: number; + speedQuality?: number; + refusalRate?: number; +} + +export type TuningPreset = 'helpful-safe' | 'max-capability' | 'enterprise' | 'creative'; + +export interface TrainingJob { + modelName: string; + generation: number; + allocatedCompute: number; + allocatedDataTokens: number; + progressTicks: number; + totalTicks: number; + estimatedCapability: number; +} + +export interface ProductLine { + id: string; + type: ProductLineType; + name: string; + modelId: string | null; + isActive: boolean; + pricing: ProductPricing; +} + +export type ProductLineType = 'text-api' | 'chat-product' | 'image' | 'code' | 'agents'; + +export interface ProductPricing { + inputTokenPrice: number; + outputTokenPrice: number; + thinkingTokenBudget: number; + cachingEnabled: boolean; + subscriptionPrice: number; + freeTokenAllowance: number; +} + +export const INITIAL_MODELS: ModelsState = { + trainedModels: [], + activeTraining: null, + productLines: [ + { + id: 'text-api', + type: 'text-api', + name: 'Text API', + modelId: null, + isActive: false, + pricing: { + inputTokenPrice: 1.0, + outputTokenPrice: 3.0, + thinkingTokenBudget: 0, + cachingEnabled: false, + subscriptionPrice: 0, + freeTokenAllowance: 0, + }, + }, + { + id: 'chat-product', + type: 'chat-product', + name: 'Chat Product', + modelId: null, + isActive: false, + pricing: { + inputTokenPrice: 0, + outputTokenPrice: 0, + thinkingTokenBudget: 0, + cachingEnabled: false, + subscriptionPrice: 20, + freeTokenAllowance: 10_000, + }, + }, + ], +}; diff --git a/packages/shared/src/types/reputation.ts b/packages/shared/src/types/reputation.ts new file mode 100644 index 0000000..5c31500 --- /dev/null +++ b/packages/shared/src/types/reputation.ts @@ -0,0 +1,22 @@ +export interface ReputationState { + score: number; + safetyRecord: number; + publicPerception: number; + employeeSatisfaction: number; + regulatoryStanding: number; + reputationHistory: ReputationSnapshot[]; +} + +export interface ReputationSnapshot { + tick: number; + score: number; +} + +export const INITIAL_REPUTATION: ReputationState = { + score: 50, + safetyRecord: 50, + publicPerception: 50, + employeeSatisfaction: 70, + regulatoryStanding: 50, + reputationHistory: [], +}; diff --git a/packages/shared/src/types/research.ts b/packages/shared/src/types/research.ts new file mode 100644 index 0000000..7846a34 --- /dev/null +++ b/packages/shared/src/types/research.ts @@ -0,0 +1,45 @@ +import type { Era } from './gameState'; + +export interface ResearchState { + completedResearch: string[]; + activeResearch: ActiveResearch | null; + researchPoints: number; +} + +export interface ActiveResearch { + researchId: string; + progressTicks: number; + totalTicks: number; + allocatedResearchers: number; + allocatedCompute: number; +} + +export interface ResearchNode { + id: string; + name: string; + description: string; + era: Era; + category: 'generation' | 'efficiency' | 'safety' | 'specialization' | 'infrastructure'; + branch?: 'reasoning' | 'coding' | 'creative' | 'multimodal' | 'agents'; + prerequisites: string[]; + cost: { + researchPoints: number; + compute: number; + ticks: number; + }; + effects: ResearchEffect[]; +} + +export interface ResearchEffect { + type: 'unlock_gpu' | 'unlock_model_tier' | 'efficiency_boost' + | 'capability_boost' | 'cost_reduction' | 'unlock_feature' + | 'unlock_product_line' | 'safety_boost'; + target: string; + value: number; +} + +export const INITIAL_RESEARCH: ResearchState = { + completedResearch: [], + activeResearch: null, + researchPoints: 0, +}; diff --git a/packages/shared/src/types/talent.ts b/packages/shared/src/types/talent.ts new file mode 100644 index 0000000..aca8783 --- /dev/null +++ b/packages/shared/src/types/talent.ts @@ -0,0 +1,49 @@ +export interface TalentState { + departments: Record; + keyHires: KeyHire[]; + hiringPipeline: HiringCandidate[]; + totalSalaryPerTick: number; +} + +export type DepartmentId = 'research' | 'engineering' | 'operations' | 'sales'; + +export interface Department { + id: DepartmentId; + headcount: number; + budget: number; + effectiveness: number; + morale: number; +} + +export interface KeyHire { + id: string; + name: string; + department: DepartmentId; + specialAbility: string; + effects: { type: string; value: number }[]; + salary: number; + hiredAtTick: number; + loyalty: number; +} + +export interface HiringCandidate { + id: string; + name: string; + department: DepartmentId; + quality: number; + salaryCost: number; + expiresAtTick: number; + isKeyHire: boolean; +} + +export const INITIAL_TALENT: TalentState = { + departments: { + research: { id: 'research', headcount: 2, budget: 5_000, effectiveness: 0.5, morale: 0.8 }, + engineering: { id: 'engineering', headcount: 3, budget: 7_000, effectiveness: 0.5, morale: 0.8 }, + operations: { id: 'operations', headcount: 1, budget: 3_000, effectiveness: 0.5, morale: 0.8 }, + sales: { id: 'sales', headcount: 0, budget: 0, effectiveness: 0, morale: 0.8 }, + }, + keyHires: [], + hiringPipeline: [], + totalSalaryPerTick: 0, +}; diff --git a/packages/shared/src/utils/formatting.ts b/packages/shared/src/utils/formatting.ts new file mode 100644 index 0000000..ed89dd4 --- /dev/null +++ b/packages/shared/src/utils/formatting.ts @@ -0,0 +1,33 @@ +export function formatNumber(n: number): string { + if (n < 0) return `-${formatNumber(-n)}`; + if (n < 1_000) return Math.floor(n).toString(); + if (n < 1_000_000) return `${(n / 1_000).toFixed(1)}K`; + if (n < 1_000_000_000) return `${(n / 1_000_000).toFixed(1)}M`; + if (n < 1_000_000_000_000) return `${(n / 1_000_000_000).toFixed(1)}B`; + return `${(n / 1_000_000_000_000).toFixed(1)}T`; +} + +export function formatMoney(n: number): string { + if (n < 0) return `-$${formatNumber(-n)}`; + return `$${formatNumber(n)}`; +} + +export function formatPercent(n: number, decimals = 1): string { + return `${(n * 100).toFixed(decimals)}%`; +} + +export function formatTokens(n: number): string { + return `${formatNumber(n)} tok`; +} + +export function formatFlops(n: number): string { + return `${formatNumber(n)} FLOPS`; +} + +export function formatDuration(ticks: number): string { + if (ticks < 60) return `${ticks}s`; + if (ticks < 3600) return `${Math.floor(ticks / 60)}m ${ticks % 60}s`; + const hours = Math.floor(ticks / 3600); + const minutes = Math.floor((ticks % 3600) / 60); + return `${hours}h ${minutes}m`; +} diff --git a/packages/shared/tsconfig.json b/packages/shared/tsconfig.json new file mode 100644 index 0000000..5664219 --- /dev/null +++ b/packages/shared/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "@ai-tycoon/tsconfig/base.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "src" + }, + "include": ["src"] +} diff --git a/packages/tsconfig/base.json b/packages/tsconfig/base.json new file mode 100644 index 0000000..b9fa4b7 --- /dev/null +++ b/packages/tsconfig/base.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "bundler", + "esModuleInterop": true, + "strict": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "isolatedModules": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true + } +} diff --git a/packages/tsconfig/node.json b/packages/tsconfig/node.json new file mode 100644 index 0000000..93cf857 --- /dev/null +++ b/packages/tsconfig/node.json @@ -0,0 +1,9 @@ +{ + "extends": "./base.json", + "compilerOptions": { + "lib": ["ES2022"], + "module": "ESNext", + "outDir": "dist", + "rootDir": "src" + } +} diff --git a/packages/tsconfig/package.json b/packages/tsconfig/package.json new file mode 100644 index 0000000..dcf270f --- /dev/null +++ b/packages/tsconfig/package.json @@ -0,0 +1,5 @@ +{ + "name": "@ai-tycoon/tsconfig", + "private": true, + "files": ["base.json", "react.json", "node.json"] +} diff --git a/packages/tsconfig/react.json b/packages/tsconfig/react.json new file mode 100644 index 0000000..859ffdf --- /dev/null +++ b/packages/tsconfig/react.json @@ -0,0 +1,8 @@ +{ + "extends": "./base.json", + "compilerOptions": { + "jsx": "react-jsx", + "lib": ["DOM", "DOM.Iterable", "ES2022"], + "noEmit": true + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..1e17213 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,2090 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + devDependencies: + turbo: + specifier: ^2.5.0 + version: 2.9.6 + typescript: + specifier: ^5.8.0 + version: 5.9.3 + + apps/web: + dependencies: + '@ai-tycoon/game-engine': + specifier: workspace:* + version: link:../../packages/game-engine + '@ai-tycoon/shared': + specifier: workspace:* + version: link:../../packages/shared + lucide-react: + specifier: ^0.475.0 + version: 0.475.0(react@19.2.5) + react: + specifier: ^19.1.0 + version: 19.2.5 + react-dom: + specifier: ^19.1.0 + version: 19.2.5(react@19.2.5) + recharts: + specifier: ^2.15.0 + version: 2.15.4(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + zustand: + specifier: ^5.0.0 + version: 5.0.12(@types/react@19.2.14)(react@19.2.5) + devDependencies: + '@ai-tycoon/tsconfig': + specifier: workspace:* + version: link:../../packages/tsconfig + '@types/react': + specifier: ^19.1.0 + version: 19.2.14 + '@types/react-dom': + specifier: ^19.1.0 + version: 19.2.3(@types/react@19.2.14) + '@vitejs/plugin-react': + specifier: ^4.4.0 + version: 4.7.0(vite@6.4.2(jiti@1.21.7)) + autoprefixer: + specifier: ^10.4.20 + version: 10.5.0(postcss@8.5.10) + postcss: + specifier: ^8.5.0 + version: 8.5.10 + tailwindcss: + specifier: ^3.4.0 + version: 3.4.19 + typescript: + specifier: ^5.8.0 + version: 5.9.3 + vite: + specifier: ^6.3.0 + version: 6.4.2(jiti@1.21.7) + + packages/game-engine: + dependencies: + '@ai-tycoon/shared': + specifier: workspace:* + version: link:../shared + devDependencies: + '@ai-tycoon/tsconfig': + specifier: workspace:* + version: link:../tsconfig + typescript: + specifier: ^5.8.0 + version: 5.9.3 + + packages/shared: + devDependencies: + '@ai-tycoon/tsconfig': + specifier: workspace:* + version: link:../tsconfig + typescript: + specifier: ^5.8.0 + version: 5.9.3 + + packages/tsconfig: {} + +packages: + + '@alloc/quick-lru@5.2.0': + resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} + engines: {node: '>=10'} + + '@babel/code-frame@7.29.0': + resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.29.0': + resolution: {integrity: sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.29.0': + resolution: {integrity: sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.29.1': + resolution: {integrity: sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.28.6': + resolution: {integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-globals@7.28.0': + resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.28.6': + resolution: {integrity: sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.28.6': + resolution: {integrity: sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-plugin-utils@7.28.6': + resolution: {integrity: sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.28.5': + resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.27.1': + resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.29.2': + resolution: {integrity: sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.29.2': + resolution: {integrity: sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/plugin-transform-react-jsx-self@7.27.1': + resolution: {integrity: sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx-source@7.27.1': + resolution: {integrity: sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/runtime@7.29.2': + resolution: {integrity: sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==} + engines: {node: '>=6.9.0'} + + '@babel/template@7.28.6': + resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.29.0': + resolution: {integrity: sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.29.0': + resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} + engines: {node: '>=6.9.0'} + + '@esbuild/aix-ppc64@0.25.12': + resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.25.12': + resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.25.12': + resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.25.12': + resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.25.12': + resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.25.12': + resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.25.12': + resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.25.12': + resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.25.12': + resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.25.12': + resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.25.12': + resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.25.12': + resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.25.12': + resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.25.12': + resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.25.12': + resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.25.12': + resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.25.12': + resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.25.12': + resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.25.12': + resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.25.12': + resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.25.12': + resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.25.12': + resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.25.12': + resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.25.12': + resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.25.12': + resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.25.12': + resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + + '@rolldown/pluginutils@1.0.0-beta.27': + resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==} + + '@rollup/rollup-android-arm-eabi@4.60.2': + resolution: {integrity: sha512-dnlp69efPPg6Uaw2dVqzWRfAWRnYVb1XJ8CyyhIbZeaq4CA5/mLeZ1IEt9QqQxmbdvagjLIm2ZL8BxXv5lH4Yw==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.60.2': + resolution: {integrity: sha512-OqZTwDRDchGRHHm/hwLOL7uVPB9aUvI0am/eQuWMNyFHf5PSEQmyEeYYheA0EPPKUO/l0uigCp+iaTjoLjVoHg==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.60.2': + resolution: {integrity: sha512-UwRE7CGpvSVEQS8gUMBe1uADWjNnVgP3Iusyda1nSRwNDCsRjnGc7w6El6WLQsXmZTbLZx9cecegumcitNfpmA==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.60.2': + resolution: {integrity: sha512-gjEtURKLCC5VXm1I+2i1u9OhxFsKAQJKTVB8WvDAHF+oZlq0GTVFOlTlO1q3AlCTE/DF32c16ESvfgqR7343/g==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.60.2': + resolution: {integrity: sha512-Bcl6CYDeAgE70cqZaMojOi/eK63h5Me97ZqAQoh77VPjMysA/4ORQBRGo3rRy45x4MzVlU9uZxs8Uwy7ZaKnBw==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.60.2': + resolution: {integrity: sha512-LU+TPda3mAE2QB0/Hp5VyeKJivpC6+tlOXd1VMoXV/YFMvk/MNk5iXeBfB4MQGRWyOYVJ01625vjkr0Az98OJQ==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.60.2': + resolution: {integrity: sha512-2QxQrM+KQ7DAW4o22j+XZ6RKdxjLD7BOWTP0Bv0tmjdyhXSsr2Ul1oJDQqh9Zf5qOwTuTc7Ek83mOFaKnodPjg==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm-musleabihf@4.60.2': + resolution: {integrity: sha512-TbziEu2DVsTEOPif2mKWkMeDMLoYjx95oESa9fkQQK7r/Orta0gnkcDpzwufEcAO2BLBsD7mZkXGFqEdMRRwfw==} + cpu: [arm] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-arm64-gnu@4.60.2': + resolution: {integrity: sha512-bO/rVDiDUuM2YfuCUwZ1t1cP+/yqjqz+Xf2VtkdppefuOFS2OSeAfgafaHNkFn0t02hEyXngZkxtGqXcXwO8Rg==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm64-musl@4.60.2': + resolution: {integrity: sha512-hr26p7e93Rl0Za+JwW7EAnwAvKkehh12BU1Llm9Ykiibg4uIr2rbpxG9WCf56GuvidlTG9KiiQT/TXT1yAWxTA==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-loong64-gnu@4.60.2': + resolution: {integrity: sha512-pOjB/uSIyDt+ow3k/RcLvUAOGpysT2phDn7TTUB3n75SlIgZzM6NKAqlErPhoFU+npgY3/n+2HYIQVbF70P9/A==} + cpu: [loong64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-loong64-musl@4.60.2': + resolution: {integrity: sha512-2/w+q8jszv9Ww1c+6uJT3OwqhdmGP2/4T17cu8WuwyUuuaCDDJ2ojdyYwZzCxx0GcsZBhzi3HmH+J5pZNXnd+Q==} + cpu: [loong64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-ppc64-gnu@4.60.2': + resolution: {integrity: sha512-11+aL5vKheYgczxtPVVRhdptAM2H7fcDR5Gw4/bTcteuZBlH4oP9f5s9zYO9aGZvoGeBpqXI/9TZZihZ609wKw==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-ppc64-musl@4.60.2': + resolution: {integrity: sha512-i16fokAGK46IVZuV8LIIwMdtqhin9hfYkCh8pf8iC3QU3LpwL+1FSFGej+O7l3E/AoknL6Dclh2oTdnRMpTzFQ==} + cpu: [ppc64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-riscv64-gnu@4.60.2': + resolution: {integrity: sha512-49FkKS6RGQoriDSK/6E2GkAsAuU5kETFCh7pG4yD/ylj9rKhTmO3elsnmBvRD4PgJPds5W2PkhC82aVwmUcJ7A==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-riscv64-musl@4.60.2': + resolution: {integrity: sha512-mjYNkHPfGpUR00DuM1ZZIgs64Hpf4bWcz9Z41+4Q+pgDx73UwWdAYyf6EG/lRFldmdHHzgrYyge5akFUW0D3mQ==} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-s390x-gnu@4.60.2': + resolution: {integrity: sha512-ALyvJz965BQk8E9Al/JDKKDLH2kfKFLTGMlgkAbbYtZuJt9LU8DW3ZoDMCtQpXAltZxwBHevXz5u+gf0yA0YoA==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-gnu@4.60.2': + resolution: {integrity: sha512-UQjrkIdWrKI626Du8lCQ6MJp/6V1LAo2bOK9OTu4mSn8GGXIkPXk/Vsp4bLHCd9Z9Iz2OTEaokUE90VweJgIYQ==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-musl@4.60.2': + resolution: {integrity: sha512-bTsRGj6VlSdn/XD4CGyzMnzaBs9bsRxy79eTqTCBsA8TMIEky7qg48aPkvJvFe1HyzQ5oMZdg7AnVlWQSKLTnw==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rollup/rollup-openbsd-x64@4.60.2': + resolution: {integrity: sha512-6d4Z3534xitaA1FcMWP7mQPq5zGwBmGbhphh2DwaA1aNIXUu3KTOfwrWpbwI4/Gr0uANo7NTtaykFyO2hPuFLg==} + cpu: [x64] + os: [openbsd] + + '@rollup/rollup-openharmony-arm64@4.60.2': + resolution: {integrity: sha512-NetAg5iO2uN7eB8zE5qrZ3CSil+7IJt4WDFLcC75Ymywq1VZVD6qJ6EvNLjZ3rEm6gB7XW5JdT60c6MN35Z85Q==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.60.2': + resolution: {integrity: sha512-NCYhOotpgWZ5kdxCZsv6Iudx0wX8980Q/oW4pNFNihpBKsDbEA1zpkfxJGC0yugsUuyDZ7gL37dbzwhR0VI7pQ==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.60.2': + resolution: {integrity: sha512-RXsaOqXxfoUBQoOgvmmijVxJnW2IGB0eoMO7F8FAjaj0UTywUO/luSqimWBJn04WNgUkeNhh7fs7pESXajWmkg==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.60.2': + resolution: {integrity: sha512-qdAzEULD+/hzObedtmV6iBpdL5TIbKVztGiK7O3/KYSf+HIzU257+MX1EXJcyIiDbMAqmbwaufcYPvyRryeZtA==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.60.2': + resolution: {integrity: sha512-Nd/SgG27WoA9e+/TdK74KnHz852TLa94ovOYySo/yMPuTmpckK/jIF2jSwS3g7ELSKXK13/cVdmg1Z/DaCWKxA==} + cpu: [x64] + os: [win32] + + '@turbo/darwin-64@2.9.6': + resolution: {integrity: sha512-X/56SnVXIQZBLKwniGTwEQTGmtE5brSACnKMBWpY3YafuxVYefrC2acamfjgxP7BG5w3I+6jf0UrLoSzgPcSJg==} + cpu: [x64] + os: [darwin] + + '@turbo/darwin-arm64@2.9.6': + resolution: {integrity: sha512-aalBeSl4agT/QtYGDyf/XLajedWzUC9Vg/pm/YO6QQ93vkQ91Vz5uK1ta5RbVRDozQSz4njxUNqRNmOXDzW+qw==} + cpu: [arm64] + os: [darwin] + + '@turbo/linux-64@2.9.6': + resolution: {integrity: sha512-YKi05jnNHaD7vevgYwahpzGwbsNNTwzU2c7VZdmdFm7+cGDP4oREUWSsainiMfRqjRuolQxBwRn8wf1jmu+YZA==} + cpu: [x64] + os: [linux] + + '@turbo/linux-arm64@2.9.6': + resolution: {integrity: sha512-02o/ZS69cOYEDczXvOB2xmyrtzjQ2hVFtWZK1iqxXUfzMmTjZK4UumrfNnjckSg+gqeBfnPRHa0NstA173Ik3g==} + cpu: [arm64] + os: [linux] + + '@turbo/windows-64@2.9.6': + resolution: {integrity: sha512-wVdQjvnBI15wB6JrA+43CtUtagjIMmX6XYO758oZHAsCNSxqRlJtdyujih0D8OCnwCRWiGWGI63zAxR0hO6s9g==} + cpu: [x64] + os: [win32] + + '@turbo/windows-arm64@2.9.6': + resolution: {integrity: sha512-1XUUyWW0W6FTSqGEhU8RHVqb2wP1SPkr7hIvBlMEwH9jr+sJQK5kqeosLJ/QaUv4ecSAd1ZhIrLoW7qslAzT4A==} + cpu: [arm64] + os: [win32] + + '@types/babel__core@7.20.5': + resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} + + '@types/babel__generator@7.27.0': + resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==} + + '@types/babel__template@7.4.4': + resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} + + '@types/babel__traverse@7.28.0': + resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} + + '@types/d3-array@3.2.2': + resolution: {integrity: sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==} + + '@types/d3-color@3.1.3': + resolution: {integrity: sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==} + + '@types/d3-ease@3.0.2': + resolution: {integrity: sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==} + + '@types/d3-interpolate@3.0.4': + resolution: {integrity: sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==} + + '@types/d3-path@3.1.1': + resolution: {integrity: sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==} + + '@types/d3-scale@4.0.9': + resolution: {integrity: sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==} + + '@types/d3-shape@3.1.8': + resolution: {integrity: sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==} + + '@types/d3-time@3.0.4': + resolution: {integrity: sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==} + + '@types/d3-timer@3.0.2': + resolution: {integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==} + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + + '@types/react-dom@19.2.3': + resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} + peerDependencies: + '@types/react': ^19.2.0 + + '@types/react@19.2.14': + resolution: {integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==} + + '@vitejs/plugin-react@4.7.0': + resolution: {integrity: sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 + + any-promise@1.3.0: + resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + + anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + + arg@5.0.2: + resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} + + autoprefixer@10.5.0: + resolution: {integrity: sha512-FMhOoZV4+qR6aTUALKX2rEqGG+oyATvwBt9IIzVR5rMa2HRWPkxf+P+PAJLD1I/H5/II+HuZcBJYEFBpq39ong==} + engines: {node: ^10 || ^12 || >=14} + hasBin: true + peerDependencies: + postcss: ^8.1.0 + + baseline-browser-mapping@2.10.21: + resolution: {integrity: sha512-Q+rUQ7Uz8AHM7DEaNdwvfFCTq7a43lNTzuS94eiWqwyxfV/wJv+oUivef51T91mmRY4d4A1u9rcSvkeufCVXlA==} + engines: {node: '>=6.0.0'} + hasBin: true + + binary-extensions@2.3.0: + resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} + engines: {node: '>=8'} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + browserslist@4.28.2: + resolution: {integrity: sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + camelcase-css@2.0.1: + resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} + engines: {node: '>= 6'} + + caniuse-lite@1.0.30001790: + resolution: {integrity: sha512-bOoxfJPyYo+ds6W0YfptaCWbFnJYjh2Y1Eow5lRv+vI2u8ganPZqNm1JwNh0t2ELQCqIWg4B3dWEusgAmsoyOw==} + + chokidar@3.6.0: + resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} + engines: {node: '>= 8.10.0'} + + clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} + + commander@4.1.1: + resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} + engines: {node: '>= 6'} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + cssesc@3.0.0: + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} + engines: {node: '>=4'} + hasBin: true + + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + d3-array@3.2.4: + resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==} + engines: {node: '>=12'} + + d3-color@3.1.0: + resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==} + engines: {node: '>=12'} + + d3-ease@3.0.1: + resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==} + engines: {node: '>=12'} + + d3-format@3.1.2: + resolution: {integrity: sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==} + engines: {node: '>=12'} + + d3-interpolate@3.0.1: + resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==} + engines: {node: '>=12'} + + d3-path@3.1.0: + resolution: {integrity: sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==} + engines: {node: '>=12'} + + d3-scale@4.0.2: + resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==} + engines: {node: '>=12'} + + d3-shape@3.2.0: + resolution: {integrity: sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==} + engines: {node: '>=12'} + + d3-time-format@4.1.0: + resolution: {integrity: sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==} + engines: {node: '>=12'} + + d3-time@3.1.0: + resolution: {integrity: sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==} + engines: {node: '>=12'} + + d3-timer@3.0.1: + resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==} + engines: {node: '>=12'} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + decimal.js-light@2.5.1: + resolution: {integrity: sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==} + + didyoumean@1.2.2: + resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} + + dlv@1.1.3: + resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} + + dom-helpers@5.2.1: + resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==} + + electron-to-chromium@1.5.344: + resolution: {integrity: sha512-4MxfbmNDm+KPh066EZy+eUnkcDPcZ35wNmOWzFuh/ijvHsve6kbLTLURy88uCNK5FbpN+yk2nQY6BYh1GEt+wg==} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + esbuild@0.25.12: + resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==} + engines: {node: '>=18'} + hasBin: true + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + eventemitter3@4.0.7: + resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} + + fast-equals@5.4.0: + resolution: {integrity: sha512-jt2DW/aNFNwke7AUd+Z+e6pz39KO5rzdbbFCg2sGafS4mk13MI7Z8O5z9cADNn5lhGODIgLwug6TZO2ctf7kcw==} + engines: {node: '>=6.0.0'} + + fast-glob@3.3.3: + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} + engines: {node: '>=8.6.0'} + + fastq@1.20.1: + resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + fraction.js@5.3.4: + resolution: {integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + hasown@2.0.3: + resolution: {integrity: sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==} + engines: {node: '>= 0.4'} + + internmap@2.0.3: + resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} + engines: {node: '>=12'} + + is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + + is-core-module@2.16.1: + resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} + engines: {node: '>= 0.4'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + jiti@1.21.7: + resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==} + hasBin: true + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + lilconfig@3.1.3: + resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} + engines: {node: '>=14'} + + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + + lodash@4.18.1: + resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==} + + loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + lucide-react@0.475.0: + resolution: {integrity: sha512-NJzvVu1HwFVeZ+Gwq2q00KygM1aBhy/ZrhY9FsAgJtpB+E4R7uxRk9M2iKvHa6/vNxZydIB59htha4c2vvwvVg==} + peerDependencies: + react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + mz@2.7.0: + resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} + + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + node-releases@2.0.38: + resolution: {integrity: sha512-3qT/88Y3FbH/Kx4szpQQ4HzUbVrHPKTLVpVocKiLfoYvw9XSGOX2FmD2d6DrXbVYyAQTF2HeF6My8jmzx7/CRw==} + + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + object-hash@3.0.0: + resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} + engines: {node: '>= 6'} + + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.2: + resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==} + engines: {node: '>=8.6'} + + picomatch@4.0.4: + resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} + engines: {node: '>=12'} + + pify@2.3.0: + resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} + engines: {node: '>=0.10.0'} + + pirates@4.0.7: + resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} + engines: {node: '>= 6'} + + postcss-import@15.1.0: + resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==} + engines: {node: '>=14.0.0'} + peerDependencies: + postcss: ^8.0.0 + + postcss-js@4.1.0: + resolution: {integrity: sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==} + engines: {node: ^12 || ^14 || >= 16} + peerDependencies: + postcss: ^8.4.21 + + postcss-load-config@6.0.1: + resolution: {integrity: sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==} + engines: {node: '>= 18'} + peerDependencies: + jiti: '>=1.21.0' + postcss: '>=8.0.9' + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + jiti: + optional: true + postcss: + optional: true + tsx: + optional: true + yaml: + optional: true + + postcss-nested@6.2.0: + resolution: {integrity: sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==} + engines: {node: '>=12.0'} + peerDependencies: + postcss: ^8.2.14 + + postcss-selector-parser@6.1.2: + resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==} + engines: {node: '>=4'} + + postcss-value-parser@4.2.0: + resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} + + postcss@8.5.10: + resolution: {integrity: sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ==} + engines: {node: ^10 || ^12 || >=14} + + prop-types@15.8.1: + resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + react-dom@19.2.5: + resolution: {integrity: sha512-J5bAZz+DXMMwW/wV3xzKke59Af6CHY7G4uYLN1OvBcKEsWOs4pQExj86BBKamxl/Ik5bx9whOrvBlSDfWzgSag==} + peerDependencies: + react: ^19.2.5 + + react-is@16.13.1: + resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + + react-is@18.3.1: + resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} + + react-refresh@0.17.0: + resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==} + engines: {node: '>=0.10.0'} + + react-smooth@4.0.4: + resolution: {integrity: sha512-gnGKTpYwqL0Iii09gHobNolvX4Kiq4PKx6eWBCYYix+8cdw+cGo3do906l1NBPKkSWx1DghC1dlWG9L2uGd61Q==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + react-transition-group@4.4.5: + resolution: {integrity: sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==} + peerDependencies: + react: '>=16.6.0' + react-dom: '>=16.6.0' + + react@19.2.5: + resolution: {integrity: sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA==} + engines: {node: '>=0.10.0'} + + read-cache@1.0.0: + resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} + + readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + + recharts-scale@0.4.5: + resolution: {integrity: sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==} + + recharts@2.15.4: + resolution: {integrity: sha512-UT/q6fwS3c1dHbXv2uFgYJ9BMFHu3fwnd7AYZaEQhXuYQ4hgsxLvsUXzGdKeZrW5xopzDCvuA2N41WJ88I7zIw==} + engines: {node: '>=14'} + peerDependencies: + react: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + resolve@1.22.12: + resolution: {integrity: sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==} + engines: {node: '>= 0.4'} + hasBin: true + + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + rollup@4.60.2: + resolution: {integrity: sha512-J9qZyW++QK/09NyN/zeO0dG/1GdGfyp9lV8ajHnRVLfo/uFsbji5mHnDgn/qYdUHyCkM2N+8VyspgZclfAh0eQ==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + scheduler@0.27.0: + resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + sucrase@3.35.1: + resolution: {integrity: sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==} + engines: {node: '>=16 || 14 >=14.17'} + hasBin: true + + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + + tailwindcss@3.4.19: + resolution: {integrity: sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==} + engines: {node: '>=14.0.0'} + hasBin: true + + thenify-all@1.6.0: + resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} + engines: {node: '>=0.8'} + + thenify@3.3.1: + resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + + tiny-invariant@1.3.3: + resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} + + tinyglobby@0.2.16: + resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==} + engines: {node: '>=12.0.0'} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + ts-interface-checker@0.1.13: + resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + + turbo@2.9.6: + resolution: {integrity: sha512-+v2QJey7ZUeUiuigkU+uFfklvNUyPI2VO2vBpMYJA+a1hKFLFiKtUYlRHdb3P9CrAvMzi0upbjI4WT+zKtqkBg==} + hasBin: true + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + update-browserslist-db@1.2.3: + resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + victory-vendor@36.9.2: + resolution: {integrity: sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==} + + vite@6.4.2: + resolution: {integrity: sha512-2N/55r4JDJ4gdrCvGgINMy+HH3iRpNIz8K6SFwVsA+JbQScLiC+clmAxBgwiSPgcG9U15QmvqCGWzMbqda5zGQ==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + jiti: '>=1.21.0' + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + + zustand@5.0.12: + resolution: {integrity: sha512-i77ae3aZq4dhMlRhJVCYgMLKuSiZAaUPAct2AksxQ+gOtimhGMdXljRT21P5BNpeT4kXlLIckvkPM029OljD7g==} + engines: {node: '>=12.20.0'} + peerDependencies: + '@types/react': '>=18.0.0' + immer: '>=9.0.6' + react: '>=18.0.0' + use-sync-external-store: '>=1.2.0' + peerDependenciesMeta: + '@types/react': + optional: true + immer: + optional: true + react: + optional: true + use-sync-external-store: + optional: true + +snapshots: + + '@alloc/quick-lru@5.2.0': {} + + '@babel/code-frame@7.29.0': + dependencies: + '@babel/helper-validator-identifier': 7.28.5 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.29.0': {} + + '@babel/core@7.29.0': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) + '@babel/helpers': 7.29.2 + '@babel/parser': 7.29.2 + '@babel/template': 7.28.6 + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + '@jridgewell/remapping': 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.3 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.29.1': + dependencies: + '@babel/parser': 7.29.2 + '@babel/types': 7.29.0 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + + '@babel/helper-compilation-targets@7.28.6': + dependencies: + '@babel/compat-data': 7.29.0 + '@babel/helper-validator-option': 7.27.1 + browserslist: 4.28.2 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-globals@7.28.0': {} + + '@babel/helper-module-imports@7.28.6': + dependencies: + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-module-imports': 7.28.6 + '@babel/helper-validator-identifier': 7.28.5 + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-plugin-utils@7.28.6': {} + + '@babel/helper-string-parser@7.27.1': {} + + '@babel/helper-validator-identifier@7.28.5': {} + + '@babel/helper-validator-option@7.27.1': {} + + '@babel/helpers@7.29.2': + dependencies: + '@babel/template': 7.28.6 + '@babel/types': 7.29.0 + + '@babel/parser@7.29.2': + dependencies: + '@babel/types': 7.29.0 + + '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/runtime@7.29.2': {} + + '@babel/template@7.28.6': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/parser': 7.29.2 + '@babel/types': 7.29.0 + + '@babel/traverse@7.29.0': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/helper-globals': 7.28.0 + '@babel/parser': 7.29.2 + '@babel/template': 7.28.6 + '@babel/types': 7.29.0 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.29.0': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 + + '@esbuild/aix-ppc64@0.25.12': + optional: true + + '@esbuild/android-arm64@0.25.12': + optional: true + + '@esbuild/android-arm@0.25.12': + optional: true + + '@esbuild/android-x64@0.25.12': + optional: true + + '@esbuild/darwin-arm64@0.25.12': + optional: true + + '@esbuild/darwin-x64@0.25.12': + optional: true + + '@esbuild/freebsd-arm64@0.25.12': + optional: true + + '@esbuild/freebsd-x64@0.25.12': + optional: true + + '@esbuild/linux-arm64@0.25.12': + optional: true + + '@esbuild/linux-arm@0.25.12': + optional: true + + '@esbuild/linux-ia32@0.25.12': + optional: true + + '@esbuild/linux-loong64@0.25.12': + optional: true + + '@esbuild/linux-mips64el@0.25.12': + optional: true + + '@esbuild/linux-ppc64@0.25.12': + optional: true + + '@esbuild/linux-riscv64@0.25.12': + optional: true + + '@esbuild/linux-s390x@0.25.12': + optional: true + + '@esbuild/linux-x64@0.25.12': + optional: true + + '@esbuild/netbsd-arm64@0.25.12': + optional: true + + '@esbuild/netbsd-x64@0.25.12': + optional: true + + '@esbuild/openbsd-arm64@0.25.12': + optional: true + + '@esbuild/openbsd-x64@0.25.12': + optional: true + + '@esbuild/openharmony-arm64@0.25.12': + optional: true + + '@esbuild/sunos-x64@0.25.12': + optional: true + + '@esbuild/win32-arm64@0.25.12': + optional: true + + '@esbuild/win32-ia32@0.25.12': + optional: true + + '@esbuild/win32-x64@0.25.12': + optional: true + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.20.1 + + '@rolldown/pluginutils@1.0.0-beta.27': {} + + '@rollup/rollup-android-arm-eabi@4.60.2': + optional: true + + '@rollup/rollup-android-arm64@4.60.2': + optional: true + + '@rollup/rollup-darwin-arm64@4.60.2': + optional: true + + '@rollup/rollup-darwin-x64@4.60.2': + optional: true + + '@rollup/rollup-freebsd-arm64@4.60.2': + optional: true + + '@rollup/rollup-freebsd-x64@4.60.2': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.60.2': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.60.2': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.60.2': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.60.2': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.60.2': + optional: true + + '@rollup/rollup-linux-loong64-musl@4.60.2': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.60.2': + optional: true + + '@rollup/rollup-linux-ppc64-musl@4.60.2': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.60.2': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.60.2': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.60.2': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.60.2': + optional: true + + '@rollup/rollup-linux-x64-musl@4.60.2': + optional: true + + '@rollup/rollup-openbsd-x64@4.60.2': + optional: true + + '@rollup/rollup-openharmony-arm64@4.60.2': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.60.2': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.60.2': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.60.2': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.60.2': + optional: true + + '@turbo/darwin-64@2.9.6': + optional: true + + '@turbo/darwin-arm64@2.9.6': + optional: true + + '@turbo/linux-64@2.9.6': + optional: true + + '@turbo/linux-arm64@2.9.6': + optional: true + + '@turbo/windows-64@2.9.6': + optional: true + + '@turbo/windows-arm64@2.9.6': + optional: true + + '@types/babel__core@7.20.5': + dependencies: + '@babel/parser': 7.29.2 + '@babel/types': 7.29.0 + '@types/babel__generator': 7.27.0 + '@types/babel__template': 7.4.4 + '@types/babel__traverse': 7.28.0 + + '@types/babel__generator@7.27.0': + dependencies: + '@babel/types': 7.29.0 + + '@types/babel__template@7.4.4': + dependencies: + '@babel/parser': 7.29.2 + '@babel/types': 7.29.0 + + '@types/babel__traverse@7.28.0': + dependencies: + '@babel/types': 7.29.0 + + '@types/d3-array@3.2.2': {} + + '@types/d3-color@3.1.3': {} + + '@types/d3-ease@3.0.2': {} + + '@types/d3-interpolate@3.0.4': + dependencies: + '@types/d3-color': 3.1.3 + + '@types/d3-path@3.1.1': {} + + '@types/d3-scale@4.0.9': + dependencies: + '@types/d3-time': 3.0.4 + + '@types/d3-shape@3.1.8': + dependencies: + '@types/d3-path': 3.1.1 + + '@types/d3-time@3.0.4': {} + + '@types/d3-timer@3.0.2': {} + + '@types/estree@1.0.8': {} + + '@types/react-dom@19.2.3(@types/react@19.2.14)': + dependencies: + '@types/react': 19.2.14 + + '@types/react@19.2.14': + dependencies: + csstype: 3.2.3 + + '@vitejs/plugin-react@4.7.0(vite@6.4.2(jiti@1.21.7))': + dependencies: + '@babel/core': 7.29.0 + '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.29.0) + '@rolldown/pluginutils': 1.0.0-beta.27 + '@types/babel__core': 7.20.5 + react-refresh: 0.17.0 + vite: 6.4.2(jiti@1.21.7) + transitivePeerDependencies: + - supports-color + + any-promise@1.3.0: {} + + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.2 + + arg@5.0.2: {} + + autoprefixer@10.5.0(postcss@8.5.10): + dependencies: + browserslist: 4.28.2 + caniuse-lite: 1.0.30001790 + fraction.js: 5.3.4 + picocolors: 1.1.1 + postcss: 8.5.10 + postcss-value-parser: 4.2.0 + + baseline-browser-mapping@2.10.21: {} + + binary-extensions@2.3.0: {} + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + browserslist@4.28.2: + dependencies: + baseline-browser-mapping: 2.10.21 + caniuse-lite: 1.0.30001790 + electron-to-chromium: 1.5.344 + node-releases: 2.0.38 + update-browserslist-db: 1.2.3(browserslist@4.28.2) + + camelcase-css@2.0.1: {} + + caniuse-lite@1.0.30001790: {} + + chokidar@3.6.0: + dependencies: + anymatch: 3.1.3 + braces: 3.0.3 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.3 + + clsx@2.1.1: {} + + commander@4.1.1: {} + + convert-source-map@2.0.0: {} + + cssesc@3.0.0: {} + + csstype@3.2.3: {} + + d3-array@3.2.4: + dependencies: + internmap: 2.0.3 + + d3-color@3.1.0: {} + + d3-ease@3.0.1: {} + + d3-format@3.1.2: {} + + d3-interpolate@3.0.1: + dependencies: + d3-color: 3.1.0 + + d3-path@3.1.0: {} + + d3-scale@4.0.2: + dependencies: + d3-array: 3.2.4 + d3-format: 3.1.2 + d3-interpolate: 3.0.1 + d3-time: 3.1.0 + d3-time-format: 4.1.0 + + d3-shape@3.2.0: + dependencies: + d3-path: 3.1.0 + + d3-time-format@4.1.0: + dependencies: + d3-time: 3.1.0 + + d3-time@3.1.0: + dependencies: + d3-array: 3.2.4 + + d3-timer@3.0.1: {} + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + decimal.js-light@2.5.1: {} + + didyoumean@1.2.2: {} + + dlv@1.1.3: {} + + dom-helpers@5.2.1: + dependencies: + '@babel/runtime': 7.29.2 + csstype: 3.2.3 + + electron-to-chromium@1.5.344: {} + + es-errors@1.3.0: {} + + esbuild@0.25.12: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.12 + '@esbuild/android-arm': 0.25.12 + '@esbuild/android-arm64': 0.25.12 + '@esbuild/android-x64': 0.25.12 + '@esbuild/darwin-arm64': 0.25.12 + '@esbuild/darwin-x64': 0.25.12 + '@esbuild/freebsd-arm64': 0.25.12 + '@esbuild/freebsd-x64': 0.25.12 + '@esbuild/linux-arm': 0.25.12 + '@esbuild/linux-arm64': 0.25.12 + '@esbuild/linux-ia32': 0.25.12 + '@esbuild/linux-loong64': 0.25.12 + '@esbuild/linux-mips64el': 0.25.12 + '@esbuild/linux-ppc64': 0.25.12 + '@esbuild/linux-riscv64': 0.25.12 + '@esbuild/linux-s390x': 0.25.12 + '@esbuild/linux-x64': 0.25.12 + '@esbuild/netbsd-arm64': 0.25.12 + '@esbuild/netbsd-x64': 0.25.12 + '@esbuild/openbsd-arm64': 0.25.12 + '@esbuild/openbsd-x64': 0.25.12 + '@esbuild/openharmony-arm64': 0.25.12 + '@esbuild/sunos-x64': 0.25.12 + '@esbuild/win32-arm64': 0.25.12 + '@esbuild/win32-ia32': 0.25.12 + '@esbuild/win32-x64': 0.25.12 + + escalade@3.2.0: {} + + eventemitter3@4.0.7: {} + + fast-equals@5.4.0: {} + + fast-glob@3.3.3: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + + fastq@1.20.1: + dependencies: + reusify: 1.1.0 + + fdir@6.5.0(picomatch@4.0.4): + optionalDependencies: + picomatch: 4.0.4 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + fraction.js@5.3.4: {} + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + gensync@1.0.0-beta.2: {} + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + hasown@2.0.3: + dependencies: + function-bind: 1.1.2 + + internmap@2.0.3: {} + + is-binary-path@2.1.0: + dependencies: + binary-extensions: 2.3.0 + + is-core-module@2.16.1: + dependencies: + hasown: 2.0.3 + + is-extglob@2.1.1: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-number@7.0.0: {} + + jiti@1.21.7: {} + + js-tokens@4.0.0: {} + + jsesc@3.1.0: {} + + json5@2.2.3: {} + + lilconfig@3.1.3: {} + + lines-and-columns@1.2.4: {} + + lodash@4.18.1: {} + + loose-envify@1.4.0: + dependencies: + js-tokens: 4.0.0 + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + lucide-react@0.475.0(react@19.2.5): + dependencies: + react: 19.2.5 + + merge2@1.4.1: {} + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.2 + + ms@2.1.3: {} + + mz@2.7.0: + dependencies: + any-promise: 1.3.0 + object-assign: 4.1.1 + thenify-all: 1.6.0 + + nanoid@3.3.11: {} + + node-releases@2.0.38: {} + + normalize-path@3.0.0: {} + + object-assign@4.1.1: {} + + object-hash@3.0.0: {} + + path-parse@1.0.7: {} + + picocolors@1.1.1: {} + + picomatch@2.3.2: {} + + picomatch@4.0.4: {} + + pify@2.3.0: {} + + pirates@4.0.7: {} + + postcss-import@15.1.0(postcss@8.5.10): + dependencies: + postcss: 8.5.10 + postcss-value-parser: 4.2.0 + read-cache: 1.0.0 + resolve: 1.22.12 + + postcss-js@4.1.0(postcss@8.5.10): + dependencies: + camelcase-css: 2.0.1 + postcss: 8.5.10 + + postcss-load-config@6.0.1(jiti@1.21.7)(postcss@8.5.10): + dependencies: + lilconfig: 3.1.3 + optionalDependencies: + jiti: 1.21.7 + postcss: 8.5.10 + + postcss-nested@6.2.0(postcss@8.5.10): + dependencies: + postcss: 8.5.10 + postcss-selector-parser: 6.1.2 + + postcss-selector-parser@6.1.2: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + + postcss-value-parser@4.2.0: {} + + postcss@8.5.10: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + prop-types@15.8.1: + dependencies: + loose-envify: 1.4.0 + object-assign: 4.1.1 + react-is: 16.13.1 + + queue-microtask@1.2.3: {} + + react-dom@19.2.5(react@19.2.5): + dependencies: + react: 19.2.5 + scheduler: 0.27.0 + + react-is@16.13.1: {} + + react-is@18.3.1: {} + + react-refresh@0.17.0: {} + + react-smooth@4.0.4(react-dom@19.2.5(react@19.2.5))(react@19.2.5): + dependencies: + fast-equals: 5.4.0 + prop-types: 15.8.1 + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + react-transition-group: 4.4.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + + react-transition-group@4.4.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5): + dependencies: + '@babel/runtime': 7.29.2 + dom-helpers: 5.2.1 + loose-envify: 1.4.0 + prop-types: 15.8.1 + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + + react@19.2.5: {} + + read-cache@1.0.0: + dependencies: + pify: 2.3.0 + + readdirp@3.6.0: + dependencies: + picomatch: 2.3.2 + + recharts-scale@0.4.5: + dependencies: + decimal.js-light: 2.5.1 + + recharts@2.15.4(react-dom@19.2.5(react@19.2.5))(react@19.2.5): + dependencies: + clsx: 2.1.1 + eventemitter3: 4.0.7 + lodash: 4.18.1 + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + react-is: 18.3.1 + react-smooth: 4.0.4(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + recharts-scale: 0.4.5 + tiny-invariant: 1.3.3 + victory-vendor: 36.9.2 + + resolve@1.22.12: + dependencies: + es-errors: 1.3.0 + is-core-module: 2.16.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + reusify@1.1.0: {} + + rollup@4.60.2: + dependencies: + '@types/estree': 1.0.8 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.60.2 + '@rollup/rollup-android-arm64': 4.60.2 + '@rollup/rollup-darwin-arm64': 4.60.2 + '@rollup/rollup-darwin-x64': 4.60.2 + '@rollup/rollup-freebsd-arm64': 4.60.2 + '@rollup/rollup-freebsd-x64': 4.60.2 + '@rollup/rollup-linux-arm-gnueabihf': 4.60.2 + '@rollup/rollup-linux-arm-musleabihf': 4.60.2 + '@rollup/rollup-linux-arm64-gnu': 4.60.2 + '@rollup/rollup-linux-arm64-musl': 4.60.2 + '@rollup/rollup-linux-loong64-gnu': 4.60.2 + '@rollup/rollup-linux-loong64-musl': 4.60.2 + '@rollup/rollup-linux-ppc64-gnu': 4.60.2 + '@rollup/rollup-linux-ppc64-musl': 4.60.2 + '@rollup/rollup-linux-riscv64-gnu': 4.60.2 + '@rollup/rollup-linux-riscv64-musl': 4.60.2 + '@rollup/rollup-linux-s390x-gnu': 4.60.2 + '@rollup/rollup-linux-x64-gnu': 4.60.2 + '@rollup/rollup-linux-x64-musl': 4.60.2 + '@rollup/rollup-openbsd-x64': 4.60.2 + '@rollup/rollup-openharmony-arm64': 4.60.2 + '@rollup/rollup-win32-arm64-msvc': 4.60.2 + '@rollup/rollup-win32-ia32-msvc': 4.60.2 + '@rollup/rollup-win32-x64-gnu': 4.60.2 + '@rollup/rollup-win32-x64-msvc': 4.60.2 + fsevents: 2.3.3 + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + scheduler@0.27.0: {} + + semver@6.3.1: {} + + source-map-js@1.2.1: {} + + sucrase@3.35.1: + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + commander: 4.1.1 + lines-and-columns: 1.2.4 + mz: 2.7.0 + pirates: 4.0.7 + tinyglobby: 0.2.16 + ts-interface-checker: 0.1.13 + + supports-preserve-symlinks-flag@1.0.0: {} + + tailwindcss@3.4.19: + dependencies: + '@alloc/quick-lru': 5.2.0 + arg: 5.0.2 + chokidar: 3.6.0 + didyoumean: 1.2.2 + dlv: 1.1.3 + fast-glob: 3.3.3 + glob-parent: 6.0.2 + is-glob: 4.0.3 + jiti: 1.21.7 + lilconfig: 3.1.3 + micromatch: 4.0.8 + normalize-path: 3.0.0 + object-hash: 3.0.0 + picocolors: 1.1.1 + postcss: 8.5.10 + postcss-import: 15.1.0(postcss@8.5.10) + postcss-js: 4.1.0(postcss@8.5.10) + postcss-load-config: 6.0.1(jiti@1.21.7)(postcss@8.5.10) + postcss-nested: 6.2.0(postcss@8.5.10) + postcss-selector-parser: 6.1.2 + resolve: 1.22.12 + sucrase: 3.35.1 + transitivePeerDependencies: + - tsx + - yaml + + thenify-all@1.6.0: + dependencies: + thenify: 3.3.1 + + thenify@3.3.1: + dependencies: + any-promise: 1.3.0 + + tiny-invariant@1.3.3: {} + + tinyglobby@0.2.16: + dependencies: + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + ts-interface-checker@0.1.13: {} + + turbo@2.9.6: + optionalDependencies: + '@turbo/darwin-64': 2.9.6 + '@turbo/darwin-arm64': 2.9.6 + '@turbo/linux-64': 2.9.6 + '@turbo/linux-arm64': 2.9.6 + '@turbo/windows-64': 2.9.6 + '@turbo/windows-arm64': 2.9.6 + + typescript@5.9.3: {} + + update-browserslist-db@1.2.3(browserslist@4.28.2): + dependencies: + browserslist: 4.28.2 + escalade: 3.2.0 + picocolors: 1.1.1 + + util-deprecate@1.0.2: {} + + victory-vendor@36.9.2: + dependencies: + '@types/d3-array': 3.2.2 + '@types/d3-ease': 3.0.2 + '@types/d3-interpolate': 3.0.4 + '@types/d3-scale': 4.0.9 + '@types/d3-shape': 3.1.8 + '@types/d3-time': 3.0.4 + '@types/d3-timer': 3.0.2 + d3-array: 3.2.4 + d3-ease: 3.0.1 + d3-interpolate: 3.0.1 + d3-scale: 4.0.2 + d3-shape: 3.2.0 + d3-time: 3.1.0 + d3-timer: 3.0.1 + + vite@6.4.2(jiti@1.21.7): + dependencies: + esbuild: 0.25.12 + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + postcss: 8.5.10 + rollup: 4.60.2 + tinyglobby: 0.2.16 + optionalDependencies: + fsevents: 2.3.3 + jiti: 1.21.7 + + yallist@3.1.1: {} + + zustand@5.0.12(@types/react@19.2.14)(react@19.2.5): + optionalDependencies: + '@types/react': 19.2.14 + react: 19.2.5 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 0000000..3ff5faa --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +packages: + - "apps/*" + - "packages/*" diff --git a/turbo.json b/turbo.json new file mode 100644 index 0000000..09b5086 --- /dev/null +++ b/turbo.json @@ -0,0 +1,20 @@ +{ + "$schema": "https://turbo.build/schema.json", + "tasks": { + "build": { + "dependsOn": ["^build"], + "outputs": ["dist/**"] + }, + "dev": { + "cache": false, + "persistent": true + }, + "typecheck": { + "dependsOn": ["^build"] + }, + "lint": {}, + "clean": { + "cache": false + } + } +}