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,72 @@
<script setup lang="ts">
/**
* Avatar — player / operator avatar. Renders an image, or falls back to initials.
* Optional status dot (online / offline / warn / idle) sits bottom-right.
*/
import { computed } from 'vue'
const props = withDefaults(
defineProps<{
name?: string
src?: string
size?: number
shape?: 'rounded' | 'circle'
status?: 'online' | 'offline' | 'warn' | 'idle'
}>(),
{ name: '', size: 32, shape: 'rounded' },
)
const TONE: Record<string, string> = {
online: 'var(--status-online)',
offline: 'var(--status-offline)',
warn: 'var(--status-warn)',
idle: 'var(--text-muted)',
}
const initials = computed(() =>
props.name
.split(/\s+/)
.filter(Boolean)
.slice(0, 2)
.map(w => w[0] ?? '')
.join('')
.toUpperCase() || '?',
)
const dotSize = computed(() => Math.max(7, Math.round(props.size * 0.28)))
const dotColor = computed(() => (props.status ? (TONE[props.status] ?? TONE.idle) : ''))
</script>
<template>
<span
class="cc-avatar"
:class="shape === 'circle' && 'cc-avatar--circle'"
:style="{
width: size + 'px',
height: size + 'px',
fontSize: Math.round(size * 0.4) + 'px',
}"
>
<img v-if="src" :src="src" :alt="name" />
<template v-else>{{ initials }}</template>
<span
v-if="status"
class="cc-avatar__status"
:style="{ width: dotSize + 'px', height: dotSize + 'px', background: dotColor }"
/>
</span>
</template>
<style>
.cc-avatar {
position: relative; display: inline-flex; align-items: center; justify-content: center; flex: none;
border-radius: var(--radius-md); background: var(--surface-active); color: var(--text-secondary);
font-family: var(--font-mono); font-weight: 600; overflow: visible; box-shadow: var(--ring-default);
}
.cc-avatar--circle { border-radius: 50%; }
.cc-avatar img { width: 100%; height: 100%; object-fit: cover; border-radius: inherit; }
.cc-avatar__status {
position: absolute; right: -2px; bottom: -2px; border-radius: 50%;
box-shadow: 0 0 0 2px var(--surface-base);
}
</style>