/** * useThemeGame — the Corrosion design-system theming contract. * * Drives `data-theme` and `data-game` on , the two attributes the token * system keys off (see styles/tokens/colors.css + game-themes.css): * * * Dark is primary; Rust (Oxide Orange) is the default/brand accent. * * Runtime swaps add the `cc-skin-swap` class for one frame so every * accent-consuming surface repaints immediately — without it Chrome leaves * elements that read var(--accent) AND have a color/bg transition on the old * accent until the next reflow (see styles/tokens/base.css + readme). */ import { ref, readonly } from 'vue' export type Theme = 'dark' | 'light' export type Game = | 'rust' | 'dune' | 'conan' | 'soulmask' | 'ark' | 'valheim' | 'palworld' /** The fleet filter: 'all' (every game) plus each individual game. */ export type ActiveGame = 'all' | Game const THEME_KEY = 'cc-theme' const GAME_KEY = 'cc-game' const ACTIVE_GAME_KEY = 'cc-active-game' const VALID_THEMES: readonly Theme[] = ['dark', 'light'] const VALID_GAMES: readonly Game[] = [ 'rust', 'dune', 'conan', 'soulmask', 'ark', 'valheim', 'palworld', ] // Module-scope singletons so every caller shares one reactive source. const theme = ref('dark') const game = ref('rust') // Fleet filter: 'all' shows every game and uses the neutral house skin (Oxide); // a specific game both filters the fleet AND re-skins the shell (the drill-in rule). const activeGame = ref('all') function apply(): void { const el = document.documentElement el.classList.add('cc-skin-swap') el.setAttribute('data-theme', theme.value) el.setAttribute('data-game', game.value) // Keep Tailwind's `dark` class in sync — existing views may use `dark:` utilities. el.classList.toggle('dark', theme.value === 'dark') // Drop the swap guard after the paint that picked up the new accent. requestAnimationFrame(() => requestAnimationFrame(() => el.classList.remove('cc-skin-swap')), ) } let initialized = false /** * Read persisted prefs and apply them to . Call once at app start * (after a tiny inline FOUC guard in index.html has set the initial attrs). */ export function initThemeGame(): void { if (initialized) return const t = localStorage.getItem(THEME_KEY) if (t && (VALID_THEMES as string[]).includes(t)) theme.value = t as Theme const ag = localStorage.getItem(ACTIVE_GAME_KEY) if (ag && (ag === 'all' || (VALID_GAMES as string[]).includes(ag))) { activeGame.value = ag as ActiveGame } // Skin follows the filter: 'all' -> neutral house (rust/oxide), else the game. game.value = activeGame.value === 'all' ? 'rust' : activeGame.value apply() initialized = true } export function useThemeGame() { function setTheme(t: Theme): void { theme.value = t localStorage.setItem(THEME_KEY, t) apply() } function setGame(g: Game): void { game.value = g localStorage.setItem(GAME_KEY, g) apply() } function setActiveGame(g: ActiveGame): void { activeGame.value = g localStorage.setItem(ACTIVE_GAME_KEY, g) // 'all' uses the neutral house skin (rust/oxide); a game re-skins to itself. setGame(g === 'all' ? 'rust' : g) } function toggleTheme(): void { setTheme(theme.value === 'dark' ? 'light' : 'dark') } return { theme: readonly(theme), game: readonly(game), activeGame: readonly(activeGame), setTheme, setGame, setActiveGame, toggleTheme, } }