feat(redesign): design-system tokens, 23 Vue components, game-aware shell + Fleet/Solo dashboard
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>
This commit is contained in:
Vantz Stockwell
2026-06-11 02:12:35 -04:00
parent ef128b47d2
commit f91ef84832
42 changed files with 3577 additions and 299 deletions

View File

@@ -0,0 +1,114 @@
/**
* 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,
}
}