All checks were successful
Test Asgard Runner / test (push) Successful in 4s
Tokens ported 1:1 from the Claude Design bundle (colors/game-themes/type/spacing/elevation/motion/fonts) with the data-theme/data-game theming contract via useThemeGame (+ cc-skin-swap repaint guard). 23 design-system components reimplemented as Vue SFCs (core/forms/data/navigation/feedback/brand). DashboardLayout rebuilt as the game-aware shell (GameSwitcher, grouped nav with permission gating preserved, agent-health footer, topbar). DashboardView: Fleet + Solo with per-game GAME_FIELDS rows and the themed ECharts PlayersChart; Solo wired to the real server store, Fleet on representative data pending the multi-instance backend. All four game skins (Rust/Dune/Conan/Soulmask). vue-tsc + vite build green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
115 lines
3.5 KiB
TypeScript
115 lines
3.5 KiB
TypeScript
/**
|
|
* useThemeGame — the Corrosion design-system theming contract.
|
|
*
|
|
* Drives `data-theme` and `data-game` on <html>, the two attributes the token
|
|
* system keys off (see styles/tokens/colors.css + game-themes.css):
|
|
* <html data-theme="dark|light" data-game="rust|dune|conan|soulmask|...">
|
|
*
|
|
* 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<Theme>('dark')
|
|
const game = ref<Game>('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<ActiveGame>('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 <html>. 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,
|
|
}
|
|
}
|