import { useEffect, useState } from 'react' export type ThemeMode = 'midnight' | 'garden' | 'forest' | 'arctic' export interface ThemeColors { bg: number bgStr: string bgPanel: string border: string text: string textMuted: string player1: number player1Str: string player2: number player2Str: string gridMinor: number gridMajor: number gridBorder: number win: string draw: string waiting: string playing: string error: string errorBg: string hoverFill: number hoverFillAlpha: number hoverStroke: number hoverStrokeAlpha: number rematchBg: string rematchBgHover: string effectGlow: number effectSpark: number effectGold: number winLineColor: number particleGold: number particleSpark: number screenFlash: number cellFill: number cellFillAlt: number } export interface ThemeInfo { label: string emoji: string colors: ThemeColors backgroundImage: string | null } const midnight: ThemeColors = { bg: 0x0d1117, bgStr: '#0d1117', bgPanel: 'rgba(22,27,34,0.92)', border: '#21262d', text: '#e6edf3', textMuted: '#8b949e', player1: 0x58a6ff, player1Str: '#58a6ff', player2: 0xf0883e, player2Str: '#f0883e', gridMinor: 0x1c2128, gridMajor: 0x2d333b, gridBorder: 0x444c5e, win: '#3fb950', draw: '#d29922', waiting: '#8b949e', playing: '#58a6ff', error: '#f85149', errorBg: 'rgba(35,10,10,0.92)', hoverFill: 0x58a6ff, hoverFillAlpha: 0.10, hoverStroke: 0x58a6ff, hoverStrokeAlpha: 0.25, rematchBg: '#238636', rematchBgHover: '#2ea043', effectGlow: 0x58a6ff, effectSpark: 0x58a6ff, effectGold: 0xffd700, winLineColor: 0x3fb950, particleGold: 0xffd700, particleSpark: 0xffffff, screenFlash: 0xffffff, cellFill: 0x0d1117, cellFillAlt: 0x0f141c, } const garden: ThemeColors = { bg: 0xf5f0e8, bgStr: '#f5f0e8', bgPanel: 'rgba(245,240,232,0.92)', border: '#e0d8c8', text: '#3a3a2e', textMuted: '#9a9a82', player1: 0x6a9e6a, player1Str: '#6a9e6a', player2: 0xd4869a, player2Str: '#d4869a', gridMinor: 0xd8d0ba, gridMajor: 0xc8c0aa, gridBorder: 0xb8b098, win: '#6a9e6a', draw: '#c9a84c', waiting: '#9a9a82', playing: '#6a9e6a', error: '#d47878', errorBg: 'rgba(200,80,80,0.12)', hoverFill: 0x6a9e6a, hoverFillAlpha: 0.10, hoverStroke: 0x6a9e6a, hoverStrokeAlpha: 0.25, rematchBg: '#6a9e6a', rematchBgHover: '#7ab07a', effectGlow: 0x6a9e6a, effectSpark: 0x6a9e6a, effectGold: 0xffd700, winLineColor: 0x6a9e6a, particleGold: 0xffd700, particleSpark: 0xd4869a, screenFlash: 0xffffff, cellFill: 0xf5f0e8, cellFillAlt: 0xf2ece0, } const forest: ThemeColors = { bg: 0x0a1f0d, bgStr: '#0a1f0d', bgPanel: 'rgba(10,31,13,0.92)', border: '#1a3d1f', text: '#d4edda', textMuted: '#8bb99a', player1: 0x8bde6d, player1Str: '#8bde6d', player2: 0xd4a54d, player2Str: '#d4a54d', gridMinor: 0x122a16, gridMajor: 0x1a3d1f, gridBorder: 0x2d5234, win: '#fbbf24', draw: '#f59e0b', waiting: '#8bb99a', playing: '#8bde6d', error: '#f87171', errorBg: 'rgba(35,10,10,0.92)', hoverFill: 0x8bde6d, hoverFillAlpha: 0.15, hoverStroke: 0x8bde6d, hoverStrokeAlpha: 0.30, rematchBg: '#2d7d3a', rematchBgHover: '#3a9c4a', effectGlow: 0x8bde6d, effectSpark: 0x8bde6d, effectGold: 0xffd700, winLineColor: 0xfbbf24, particleGold: 0xffd700, particleSpark: 0xd4a54d, screenFlash: 0xffffff, cellFill: 0x2a1c0c, cellFillAlt: 0x26190a, } const arctic: ThemeColors = { bg: 0x0b1620, bgStr: '#0b1620', bgPanel: 'rgba(11,22,32,0.92)', border: '#1a3348', text: '#deeaf5', textMuted: '#8aaac4', player1: 0x7ec8e3, player1Str: '#7ec8e3', player2: 0xe8d5b7, player2Str: '#e8d5b7', gridMinor: 0x102838, gridMajor: 0x1a3348, gridBorder: 0x2a4a62, win: '#a3d9ff', draw: '#d4a574', waiting: '#8aaac4', playing: '#7ec8e3', error: '#f87171', errorBg: 'rgba(35,10,10,0.92)', hoverFill: 0x7ec8e3, hoverFillAlpha: 0.15, hoverStroke: 0x7ec8e3, hoverStrokeAlpha: 0.30, rematchBg: '#2a7a9a', rematchBgHover: '#3a8aaa', effectGlow: 0x7ec8e3, effectSpark: 0x7ec8e3, effectGold: 0xffd700, winLineColor: 0xa3d9ff, particleGold: 0xffd700, particleSpark: 0xe8d5b7, screenFlash: 0xffffff, cellFill: 0x0c1e2c, cellFillAlt: 0x0e2232, } export const THEMES: Record = { midnight: { label: 'Midnight', emoji: '🌙', colors: midnight, backgroundImage: 'bg/night-bg.jpg?v=3' }, garden: { label: 'Garden', emoji: '🌷', colors: garden, backgroundImage: null }, forest: { label: 'Forest', emoji: '🌲', colors: forest, backgroundImage: 'bg/forest_bg.jpg?v=3' }, arctic: { label: 'Arctic', emoji: '🧊', colors: arctic, backgroundImage: null }, } const listeners = new Set<() => void>() const stored: string | null = null let currentMode: ThemeMode = stored !== null && stored in THEMES ? stored as ThemeMode : 'forest' let currentTheme: ThemeColors = THEMES[currentMode].colors export const theme = new Proxy({} as ThemeColors, { get(_, prop) { return currentTheme[prop as keyof ThemeColors] }, ownKeys() { return Reflect.ownKeys(currentTheme) as (keyof ThemeColors)[] }, getOwnPropertyDescriptor() { return { configurable: true, enumerable: true } }, }) export function setThemeMode(mode: ThemeMode): void { if (mode === currentMode) return currentMode = mode currentTheme = THEMES[mode].colors document.body.style.background = currentTheme.bgStr document.body.style.color = currentTheme.text listeners.forEach((fn) => fn()) } export function getThemeMode(): ThemeMode { return currentMode } export function getThemeBackgroundImage(mode: ThemeMode = currentMode): string | null { return THEMES[mode].backgroundImage } export function onThemeChange(fn: () => void): () => void { listeners.add(fn) return () => listeners.delete(fn) } export function useThemeMode(): [ThemeMode, (mode: ThemeMode) => void] { const [mode, setMode] = useState(getThemeMode) useEffect(() => { return onThemeChange(() => setMode(getThemeMode())) }, []) return [mode, setThemeMode] } document.body.style.background = currentTheme.bgStr document.body.style.color = currentTheme.text let _playerName: string | null = null let _player2Name: string | null = null let _playerSymbol: string = 'X' let _player2Symbol: string = 'O' let _playerColor: string | null = null let _player2Color: string | null = null let _playerDrawTower: boolean = true let _player2DrawTower: boolean = true export function setPlayerName(name: string): void { _playerName = name } export function getPlayerName(): string { return _playerName || 'Caro 1' } export function setPlayer2Name(name: string): void { _player2Name = name } export function getPlayer2Name(): string { return _player2Name || 'Caro 2' } export function setPlayerSymbol(symbol: string): void { _playerSymbol = symbol } export function getPlayerSymbol(): string { return _playerSymbol } export function setPlayer1Symbol(symbol: string): void { setPlayerSymbol(symbol) } export function getPlayer1Symbol(): string { return getPlayerSymbol() } export function setPlayer2Symbol(symbol: string): void { _player2Symbol = symbol } export function getPlayer2Symbol(): string { return _player2Symbol } export function setPlayerColor(color: string): void { _playerColor = color } export function getPlayerColor(): string | null { return _playerColor } export function setPlayer2Color(color: string): void { _player2Color = color } export function getPlayer2Color(): string | null { return _player2Color } export function setPlayerDrawTower(draw: boolean): void { _playerDrawTower = draw } export function getPlayerDrawTower(): boolean { return _playerDrawTower } export function setPlayer2DrawTower(draw: boolean): void { _player2DrawTower = draw } export function getPlayer2DrawTower(): boolean { return _player2DrawTower }