133 lines
4.6 KiB
Vue
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>
|