Files
botfights/frontend/src/components/ArcadeHUD.vue
T
2026-04-11 19:46:37 +01:00

133 lines
4.6 KiB
Vue

<script setup lang="ts">
import { computed } from 'vue'
import type { COMBOS } from '../game/arcade/moves'
const props = defineProps<{
p1Hp: number
p2Hp: number
maxHp: number
timer: number
p1Wins: number
p2Wins: number
p1Name: string
p2Name: string
round: number
roundsToWin: number
announcement: string
comboInfo: { player: 1 | 2; count: number; name: string } | null
}>()
const p1HpPct = computed(() => Math.max(0, (props.p1Hp / props.maxHp) * 100))
const p2HpPct = computed(() => Math.max(0, (props.p2Hp / props.maxHp) * 100))
function hpColor(pct: number): string {
if (pct > 50) return 'bg-green-500'
if (pct > 25) return 'bg-yellow-500'
return 'bg-red-500'
}
</script>
<template>
<div class="absolute inset-0 pointer-events-none z-20 font-display select-none">
<!-- Health bars -->
<div class="flex items-start gap-2 px-3 pt-2">
<!-- P1 health -->
<div class="flex-1">
<div class="flex items-center gap-2 mb-0.5">
<span class="text-[10px] font-bold text-neon-cyan tracking-wider truncate max-w-[120px]">
{{ p1Name.toUpperCase() }}
</span>
<div class="flex gap-0.5">
<div
v-for="i in roundsToWin"
:key="i"
class="w-2 h-2 rounded-full border border-neon-cyan/50"
:class="i <= p1Wins ? 'bg-neon-cyan' : 'bg-transparent'"
/>
</div>
</div>
<div class="h-4 bg-surface/80 border border-border rounded-sm overflow-hidden">
<div
class="h-full transition-all duration-150 rounded-sm"
:class="hpColor(p1HpPct)"
:style="{ width: `${p1HpPct}%` }"
/>
</div>
</div>
<!-- Timer -->
<div class="flex flex-col items-center min-w-[48px]">
<span class="text-[8px] text-text-muted tracking-widest">RD {{ round }}</span>
<span
class="text-2xl font-black tabular-nums leading-none"
:class="timer <= 10 ? 'text-red-400 animate-pulse' : 'text-text-primary'"
>
{{ timer }}
</span>
</div>
<!-- P2 health -->
<div class="flex-1">
<div class="flex items-center justify-end gap-2 mb-0.5">
<div class="flex gap-0.5">
<div
v-for="i in roundsToWin"
:key="i"
class="w-2 h-2 rounded-full border border-neon-pink/50"
:class="i <= p2Wins ? 'bg-neon-pink' : 'bg-transparent'"
/>
</div>
<span class="text-[10px] font-bold text-neon-pink tracking-wider truncate max-w-[120px]">
{{ p2Name.toUpperCase() }}
</span>
</div>
<div class="h-4 bg-surface/80 border border-border rounded-sm overflow-hidden">
<div
class="h-full transition-all duration-150 rounded-sm float-right"
:class="hpColor(p2HpPct)"
:style="{ width: `${p2HpPct}%` }"
/>
</div>
</div>
</div>
<!-- Combo counter -->
<Transition name="combo">
<div
v-if="comboInfo && comboInfo.count >= 2"
class="absolute top-20 font-black text-sm tracking-widest"
:class="comboInfo.player === 1 ? 'left-4 text-neon-cyan' : 'right-4 text-neon-pink text-right'"
>
<div class="text-3xl">{{ comboInfo.count }}</div>
<div class="text-[9px] opacity-80">HIT COMBO</div>
</div>
</Transition>
<!-- Center announcement -->
<Transition name="announce">
<div
v-if="announcement"
class="absolute inset-0 flex items-center justify-center"
>
<div class="text-4xl md:text-6xl font-black text-white tracking-[0.15em] text-center glow-white drop-shadow-[0_0_20px_rgba(255,255,255,0.6)]">
{{ announcement }}
</div>
</div>
</Transition>
</div>
</template>
<style scoped>
.combo-enter-active { animation: combo-in 0.2s ease-out; }
.combo-leave-active { animation: combo-out 0.15s ease-in; }
@keyframes combo-in { from { opacity: 0; transform: scale(2); } to { opacity: 1; transform: scale(1); } }
@keyframes combo-out { from { opacity: 1; } to { opacity: 0; transform: translateY(-10px); } }
.announce-enter-active { animation: announce-in 0.3s ease-out; }
.announce-leave-active { animation: announce-out 0.3s ease-in; }
@keyframes announce-in { from { opacity: 0; transform: scale(0.5); } to { opacity: 1; transform: scale(1); } }
@keyframes announce-out { from { opacity: 1; } to { opacity: 0; transform: scale(1.5); } }
.glow-white { text-shadow: 0 0 10px rgba(255,255,255,0.5), 0 0 20px rgba(255,255,255,0.3); }
</style>