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>
63 lines
2.7 KiB
Vue
63 lines
2.7 KiB
Vue
<script setup lang="ts">
|
|
/**
|
|
* StatCard — KPI tile with icon, big mono value, and optional delta + note row.
|
|
* Green delta = up/good, red = down/bad, muted = flat.
|
|
*/
|
|
import { computed } from 'vue'
|
|
import Icon from '@/components/ds/core/Icon.vue'
|
|
|
|
const props = withDefaults(
|
|
defineProps<{
|
|
label: string
|
|
value: string | number
|
|
unit?: string
|
|
icon?: string
|
|
delta?: string | number
|
|
deltaDir?: 'up' | 'down' | 'flat'
|
|
note?: string
|
|
}>(),
|
|
{ deltaDir: 'up' },
|
|
)
|
|
|
|
const deltaIcon = computed(() =>
|
|
props.deltaDir === 'up' ? 'trending-up' : props.deltaDir === 'down' ? 'trending-down' : 'minus',
|
|
)
|
|
|
|
const showFoot = computed(() => props.delta != null || !!props.note)
|
|
</script>
|
|
|
|
<template>
|
|
<div class="cc-stat">
|
|
<div class="cc-stat__top">
|
|
<div v-if="icon" class="cc-stat__ico">
|
|
<Icon :name="icon" :size="15" :stroke-width="2.25" />
|
|
</div>
|
|
<div class="cc-stat__label">{{ label }}</div>
|
|
</div>
|
|
<div class="cc-stat__value">
|
|
{{ value }}<span v-if="unit" class="cc-stat__unit">{{ unit }}</span>
|
|
</div>
|
|
<div v-if="showFoot" class="cc-stat__foot">
|
|
<span v-if="delta != null" :class="['cc-stat__delta', 'cc-stat__delta--' + deltaDir]">
|
|
<Icon :name="deltaIcon" :size="13" :stroke-width="2.5" />{{ delta }}
|
|
</span>
|
|
<span v-if="note" class="cc-stat__note">{{ note }}</span>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<style>
|
|
.cc-stat { background: var(--surface-base); border-radius: var(--radius-lg); box-shadow: var(--ring-default); padding: 14px 16px; display: flex; flex-direction: column; gap: 10px; min-width: 0; position: relative; overflow: hidden; }
|
|
.cc-stat__top { display: flex; align-items: center; gap: 8px; }
|
|
.cc-stat__ico { width: 28px; height: 28px; border-radius: var(--radius-sm); display: flex; align-items: center; justify-content: center; background: var(--accent-soft); color: var(--accent-text); flex: none; }
|
|
.cc-stat__label { font-size: var(--text-xs); font-weight: 500; color: var(--text-tertiary); letter-spacing: .01em; }
|
|
.cc-stat__value { font-family: var(--font-mono); font-weight: 600; font-size: 28px; letter-spacing: -0.02em; color: var(--text-primary); font-variant-numeric: tabular-nums; line-height: 1; display: flex; align-items: baseline; gap: 4px; }
|
|
.cc-stat__unit { font-size: 14px; color: var(--text-muted); font-weight: 500; }
|
|
.cc-stat__foot { display: flex; align-items: center; gap: 6px; font-family: var(--font-mono); font-size: var(--text-xs); }
|
|
.cc-stat__delta { display: inline-flex; align-items: center; gap: 3px; font-weight: 600; }
|
|
.cc-stat__delta--up { color: var(--status-online); }
|
|
.cc-stat__delta--down { color: var(--status-offline); }
|
|
.cc-stat__delta--flat { color: var(--text-tertiary); }
|
|
.cc-stat__note { color: var(--text-tertiary); }
|
|
</style>
|