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>
73 lines
2.0 KiB
Vue
73 lines
2.0 KiB
Vue
<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>
|