424 lines
16 KiB
Vue
424 lines
16 KiB
Vue
<script setup lang="ts">
|
|||
|
|
import { ref, onMounted, onUnmounted, nextTick } from 'vue'
|
||
|
|
import { createFightScene, type FightSceneController } from '../game/FightScene'
|
||
|
|
|
||
|
|
interface Round {
|
||
|
|
roundNumber: number
|
||
|
|
challengeType: string
|
||
|
|
challengeData: string
|
||
|
|
botAResponse: string | null
|
||
|
|
botATimeMs: number | null
|
||
|
|
botAScore: number | null
|
||
|
|
botBResponse: string | null
|
||
|
|
botBTimeMs: number | null
|
||
|
|
botBScore: number | null
|
||
|
|
winnerId: string | null
|
||
|
|
narration: string | null
|
||
|
|
}
|
||
|
|
|
||
|
|
interface FightData {
|
||
|
|
id: string
|
||
|
|
botA: { id: string; name: string; avatarSeed: string; eloRating: number; wins: number; losses: number; tier: number } | null
|
||
|
|
botB: { id: string; name: string; avatarSeed: string; eloRating: number; wins: number; losses: number; tier: number } | null
|
||
|
|
arenaInfo: { id: string; name: string; description: string; modifier: string | null } | null
|
||
|
|
arena: string
|
||
|
|
winnerId: string | null
|
||
|
|
botAHp: number
|
||
|
|
botBHp: number
|
||
|
|
totalRounds: number
|
||
|
|
status: string
|
||
|
|
rounds: Round[]
|
||
|
|
}
|
||
|
|
|
||
|
|
const props = defineProps<{ fight: FightData }>()
|
||
|
|
|
||
|
|
const canvasRef = ref<HTMLCanvasElement>()
|
||
|
|
const logEl = ref<HTMLElement>()
|
||
|
|
let scene: FightSceneController | null = null
|
||
|
|
|
||
|
|
const isReplaying = ref(false)
|
||
|
|
const displayHpA = ref(100)
|
||
|
|
const displayHpB = ref(100)
|
||
|
|
const visibleRounds = ref<Round[]>([])
|
||
|
|
const currentRound = ref(0)
|
||
|
|
const showingFinal = ref(true)
|
||
|
|
|
||
|
|
// Staggered log items within a round
|
||
|
|
const logItems = ref<{ type: string; round: number; text: string; color: string }[]>([])
|
||
|
|
|
||
|
|
onMounted(() => {
|
||
|
|
displayHpA.value = props.fight.botAHp
|
||
|
|
displayHpB.value = props.fight.botBHp
|
||
|
|
visibleRounds.value = [...props.fight.rounds]
|
||
|
|
// Build full log for static view
|
||
|
|
for (const r of props.fight.rounds) {
|
||
|
|
addRoundToLog(r, false)
|
||
|
|
}
|
||
|
|
initScene()
|
||
|
|
})
|
||
|
|
|
||
|
|
onUnmounted(() => {
|
||
|
|
scene?.destroy()
|
||
|
|
scene = null
|
||
|
|
})
|
||
|
|
|
||
|
|
function initScene() {
|
||
|
|
if (!canvasRef.value || !props.fight.botA || !props.fight.botB) return
|
||
|
|
if (scene) { scene.k.go('fight'); return }
|
||
|
|
|
||
|
|
const container = canvasRef.value.parentElement
|
||
|
|
if (container) {
|
||
|
|
canvasRef.value.width = container.clientWidth
|
||
|
|
canvasRef.value.height = container.clientHeight
|
||
|
|
}
|
||
|
|
|
||
|
|
scene = createFightScene({
|
||
|
|
canvas: canvasRef.value,
|
||
|
|
botA: {
|
||
|
|
name: props.fight.botA.name,
|
||
|
|
seed: props.fight.botA.avatarSeed || props.fight.botA.name,
|
||
|
|
tier: props.fight.botA.tier,
|
||
|
|
},
|
||
|
|
botB: {
|
||
|
|
name: props.fight.botB.name,
|
||
|
|
seed: props.fight.botB.avatarSeed || props.fight.botB.name,
|
||
|
|
tier: props.fight.botB.tier,
|
||
|
|
},
|
||
|
|
arena: props.fight.arena,
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
const challengeLabel = (type: string) => {
|
||
|
|
const labels: Record<string, string> = {
|
||
|
|
speed_blitz: 'SPEED BLITZ',
|
||
|
|
riddle: 'RIDDLE ME THIS',
|
||
|
|
code_golf: 'CODE GOLF',
|
||
|
|
roast_battle: 'ROAST BATTLE',
|
||
|
|
hallucination_check: 'HALLUCINATION CHECK',
|
||
|
|
token_economy: 'TOKEN ECONOMY',
|
||
|
|
creative_writing: 'CREATIVE WRITING',
|
||
|
|
math_blitz: 'MATH BLITZ',
|
||
|
|
trap_card: 'TRAP CARD',
|
||
|
|
}
|
||
|
|
return labels[type] || type.toUpperCase()
|
||
|
|
}
|
||
|
|
|
||
|
|
const tierClass = (t: number) => `tier-${t}`
|
||
|
|
|
||
|
|
function sleep(ms: number) {
|
||
|
|
return new Promise(resolve => setTimeout(resolve, ms))
|
||
|
|
}
|
||
|
|
|
||
|
|
function scrollLog() {
|
||
|
|
nextTick(() => {
|
||
|
|
logEl.value?.scrollTo({ top: logEl.value.scrollHeight, behavior: 'smooth' })
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
function addRoundToLog(round: Round, stagger: boolean): Promise<void> {
|
||
|
|
if (!stagger) {
|
||
|
|
// Add all at once (static view)
|
||
|
|
const challenge = JSON.parse(round.challengeData)
|
||
|
|
logItems.value.push(
|
||
|
|
{ type: 'header', round: round.roundNumber, text: `ROUND ${round.roundNumber}: ${challengeLabel(round.challengeType)}`, color: 'neon-purple' },
|
||
|
|
{ type: 'prompt', round: round.roundNumber, text: challenge.prompt, color: 'text-muted' },
|
||
|
|
{ type: 'responseA', round: round.roundNumber, text: `${props.fight.botA?.name}: ${round.botAResponse?.slice(0, 120) || '[TIMEOUT]'} (${round.botATimeMs}ms)`, color: 'neon-cyan' },
|
||
|
|
{ type: 'responseB', round: round.roundNumber, text: `${props.fight.botB?.name}: ${round.botBResponse?.slice(0, 120) || '[TIMEOUT]'} (${round.botBTimeMs}ms)`, color: 'neon-pink' },
|
||
|
|
)
|
||
|
|
if (round.narration) {
|
||
|
|
logItems.value.push({ type: 'narration', round: round.roundNumber, text: `>> ${round.narration}`, color: 'neon-yellow' })
|
||
|
|
}
|
||
|
|
// Score
|
||
|
|
const winner = round.winnerId === props.fight.botA?.id ? props.fight.botA?.name
|
||
|
|
: round.winnerId === props.fight.botB?.id ? props.fight.botB?.name : 'DRAW'
|
||
|
|
logItems.value.push({ type: 'result', round: round.roundNumber, text: `${winner} wins round! (${round.botAScore} vs ${round.botBScore})`, color: 'text-secondary' })
|
||
|
|
return Promise.resolve()
|
||
|
|
}
|
||
|
|
|
||
|
|
// Staggered delivery
|
||
|
|
return (async () => {
|
||
|
|
const challenge = JSON.parse(round.challengeData)
|
||
|
|
|
||
|
|
logItems.value.push({ type: 'header', round: round.roundNumber, text: `ROUND ${round.roundNumber}: ${challengeLabel(round.challengeType)}`, color: 'neon-purple' })
|
||
|
|
scrollLog()
|
||
|
|
await sleep(600)
|
||
|
|
|
||
|
|
logItems.value.push({ type: 'prompt', round: round.roundNumber, text: challenge.prompt, color: 'text-muted' })
|
||
|
|
scrollLog()
|
||
|
|
await sleep(800)
|
||
|
|
|
||
|
|
logItems.value.push({ type: 'responseA', round: round.roundNumber, text: `${props.fight.botA?.name}: ${round.botAResponse?.slice(0, 120) || '[TIMEOUT]'}`, color: 'neon-cyan' })
|
||
|
|
scrollLog()
|
||
|
|
await sleep(500)
|
||
|
|
|
||
|
|
logItems.value.push({ type: 'time', round: round.roundNumber, text: ` Response time: ${round.botATimeMs}ms | Score: ${round.botAScore}`, color: 'text-muted' })
|
||
|
|
scrollLog()
|
||
|
|
await sleep(600)
|
||
|
|
|
||
|
|
logItems.value.push({ type: 'responseB', round: round.roundNumber, text: `${props.fight.botB?.name}: ${round.botBResponse?.slice(0, 120) || '[TIMEOUT]'}`, color: 'neon-pink' })
|
||
|
|
scrollLog()
|
||
|
|
await sleep(500)
|
||
|
|
|
||
|
|
logItems.value.push({ type: 'time', round: round.roundNumber, text: ` Response time: ${round.botBTimeMs}ms | Score: ${round.botBScore}`, color: 'text-muted' })
|
||
|
|
scrollLog()
|
||
|
|
})()
|
||
|
|
}
|
||
|
|
|
||
|
|
async function replay() {
|
||
|
|
if (isReplaying.value || !props.fight.botA || !props.fight.botB) return
|
||
|
|
isReplaying.value = true
|
||
|
|
showingFinal.value = false
|
||
|
|
displayHpA.value = 100
|
||
|
|
displayHpB.value = 100
|
||
|
|
visibleRounds.value = []
|
||
|
|
logItems.value = []
|
||
|
|
currentRound.value = 0
|
||
|
|
|
||
|
|
initScene()
|
||
|
|
await sleep(600)
|
||
|
|
|
||
|
|
// Arena intro
|
||
|
|
await scene!.showAnnouncement(props.fight.arenaInfo?.name || 'THE RING', '#b83dff', 1800)
|
||
|
|
logItems.value.push({ type: 'system', round: 0, text: `ARENA: ${props.fight.arenaInfo?.name || 'THE RING'}`, color: 'neon-purple' })
|
||
|
|
if (props.fight.arenaInfo?.description) {
|
||
|
|
logItems.value.push({ type: 'system', round: 0, text: props.fight.arenaInfo.description, color: 'text-muted' })
|
||
|
|
}
|
||
|
|
logItems.value.push({ type: 'system', round: 0, text: `${props.fight.botA.name} (${Math.round(props.fight.botA.eloRating)} ELO) vs ${props.fight.botB.name} (${Math.round(props.fight.botB.eloRating)} ELO)`, color: 'text-secondary' })
|
||
|
|
logItems.value.push({ type: 'divider', round: 0, text: '━'.repeat(30), color: 'text-muted' })
|
||
|
|
scrollLog()
|
||
|
|
await sleep(800)
|
||
|
|
|
||
|
|
for (const round of props.fight.rounds) {
|
||
|
|
currentRound.value = round.roundNumber
|
||
|
|
|
||
|
|
// Round announcements with pauses
|
||
|
|
await scene!.showAnnouncement(`ROUND ${round.roundNumber}`, '#00f0ff', 1000)
|
||
|
|
await sleep(400)
|
||
|
|
await scene!.showAnnouncement(challengeLabel(round.challengeType), '#b83dff', 1000)
|
||
|
|
await sleep(400)
|
||
|
|
await scene!.showAnnouncement('FIGHT!', '#ff2d7b', 600)
|
||
|
|
await sleep(300)
|
||
|
|
|
||
|
|
// Stagger the battle log alongside the fight
|
||
|
|
const logPromise = addRoundToLog(round, true)
|
||
|
|
|
||
|
|
// Play the round animation
|
||
|
|
const isCritical = Math.abs((round.botAScore || 0) - (round.botBScore || 0)) > 4
|
||
|
|
|
||
|
|
await scene!.playRound({
|
||
|
|
round: round.roundNumber,
|
||
|
|
challengeType: round.challengeType,
|
||
|
|
winnerId: round.winnerId,
|
||
|
|
botAId: props.fight.botA!.id,
|
||
|
|
botBId: props.fight.botB!.id,
|
||
|
|
narration: round.narration || '',
|
||
|
|
isCritical,
|
||
|
|
botAScore: round.botAScore || 0,
|
||
|
|
botBScore: round.botBScore || 0,
|
||
|
|
})
|
||
|
|
|
||
|
|
// Wait for log to finish
|
||
|
|
await logPromise
|
||
|
|
|
||
|
|
// Narration after fight
|
||
|
|
if (round.narration) {
|
||
|
|
logItems.value.push({ type: 'narration', round: round.roundNumber, text: `>> ${round.narration}`, color: 'neon-yellow' })
|
||
|
|
scrollLog()
|
||
|
|
}
|
||
|
|
|
||
|
|
// Round result
|
||
|
|
const aWon = round.winnerId === props.fight.botA!.id
|
||
|
|
const bWon = round.winnerId === props.fight.botB!.id
|
||
|
|
const winner = aWon ? props.fight.botA!.name : bWon ? props.fight.botB!.name : 'DRAW'
|
||
|
|
logItems.value.push({ type: 'result', round: round.roundNumber, text: `${winner} ${aWon || bWon ? 'wins round!' : '- no winner'}`, color: aWon ? 'neon-cyan' : bWon ? 'neon-pink' : 'text-muted' })
|
||
|
|
logItems.value.push({ type: 'divider', round: round.roundNumber, text: '', color: '' })
|
||
|
|
scrollLog()
|
||
|
|
|
||
|
|
// Update HP
|
||
|
|
const baseDmg = 15
|
||
|
|
if (aWon) {
|
||
|
|
const dmg = Math.max(8, baseDmg + ((round.botAScore || 5) - (round.botBScore || 5)) * 3)
|
||
|
|
displayHpB.value = Math.max(0, displayHpB.value - Math.round(dmg))
|
||
|
|
} else if (bWon) {
|
||
|
|
const dmg = Math.max(8, baseDmg + ((round.botBScore || 5) - (round.botAScore || 5)) * 3)
|
||
|
|
displayHpA.value = Math.max(0, displayHpA.value - Math.round(dmg))
|
||
|
|
}
|
||
|
|
|
||
|
|
// Longer pause between rounds for ~1 min total fight
|
||
|
|
await sleep(2000)
|
||
|
|
}
|
||
|
|
|
||
|
|
// Final HP
|
||
|
|
displayHpA.value = props.fight.botAHp
|
||
|
|
displayHpB.value = props.fight.botBHp
|
||
|
|
|
||
|
|
// Ending
|
||
|
|
if (props.fight.winnerId && scene) {
|
||
|
|
const winningSide = props.fight.winnerId === props.fight.botA!.id ? 'a' : 'b'
|
||
|
|
const isPerfect = props.fight.botAHp === 100 || props.fight.botBHp === 100
|
||
|
|
|
||
|
|
if (isPerfect) {
|
||
|
|
await scene.playPerfect(winningSide)
|
||
|
|
} else {
|
||
|
|
await scene.playKO(winningSide)
|
||
|
|
}
|
||
|
|
|
||
|
|
const winnerName = winningSide === 'a' ? props.fight.botA!.name : props.fight.botB!.name
|
||
|
|
await sleep(600)
|
||
|
|
await scene.showAnnouncement(`${winnerName} WINS!`, '#00f0ff', 3000)
|
||
|
|
|
||
|
|
logItems.value.push({ type: 'divider', round: 99, text: '━'.repeat(30), color: 'text-muted' })
|
||
|
|
logItems.value.push({ type: 'result', round: 99, text: `${winnerName.toUpperCase()} WINS THE FIGHT!${isPerfect ? ' PERFECT!' : ''}`, color: winningSide === 'a' ? 'neon-cyan' : 'neon-pink' })
|
||
|
|
scrollLog()
|
||
|
|
}
|
||
|
|
|
||
|
|
isReplaying.value = false
|
||
|
|
}
|
||
|
|
</script>
|
||
|
|
|
||
|
|
<template>
|
||
|
|
<div class="h-full flex flex-col lg:flex-row gap-2">
|
||
|
|
|
||
|
|
<!-- LEFT: Terminal Battle Log -->
|
||
|
|
<div class="lg:w-[38%] flex flex-col min-h-0 border border-border rounded-lg bg-black/90 neon-border-cyan overflow-hidden order-2 lg:order-1">
|
||
|
|
<!-- Terminal header -->
|
||
|
|
<div class="bg-surface-raised border-b border-border px-3 py-1.5 flex items-center gap-2 flex-shrink-0">
|
||
|
|
<span class="w-2.5 h-2.5 rounded-full bg-ko" />
|
||
|
|
<span class="w-2.5 h-2.5 rounded-full bg-neon-yellow" />
|
||
|
|
<span class="w-2.5 h-2.5 rounded-full bg-neon-green" />
|
||
|
|
<span class="font-pixel text-[10px] text-text-muted ml-2 tracking-wider">BATTLE LOG</span>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div ref="logEl" class="flex-1 overflow-y-auto p-4 font-mono text-sm space-y-1 leading-relaxed">
|
||
|
|
<div v-for="(item, idx) in logItems" :key="idx">
|
||
|
|
<div v-if="item.type === 'divider'" class="py-2">
|
||
|
|
<div v-if="item.text" class="text-border text-xs">{{ item.text }}</div>
|
||
|
|
</div>
|
||
|
|
<p v-else-if="item.type === 'header'"
|
||
|
|
class="text-neon-purple font-bold text-base tracking-wide pt-2">
|
||
|
|
{{ item.text }}
|
||
|
|
</p>
|
||
|
|
<p v-else-if="item.type === 'prompt'"
|
||
|
|
class="text-text-muted text-xs italic pl-2 pb-1">
|
||
|
|
{{ item.text }}
|
||
|
|
</p>
|
||
|
|
<p v-else-if="item.type === 'responseA'"
|
||
|
|
class="text-neon-cyan text-sm pl-2">
|
||
|
|
{{ item.text }}
|
||
|
|
</p>
|
||
|
|
<p v-else-if="item.type === 'responseB'"
|
||
|
|
class="text-neon-pink text-sm pl-2">
|
||
|
|
{{ item.text }}
|
||
|
|
</p>
|
||
|
|
<p v-else-if="item.type === 'time'"
|
||
|
|
class="text-text-muted text-xs pl-4">
|
||
|
|
{{ item.text }}
|
||
|
|
</p>
|
||
|
|
<p v-else-if="item.type === 'narration'"
|
||
|
|
class="text-neon-yellow font-bold text-sm pl-2 py-1">
|
||
|
|
{{ item.text }}
|
||
|
|
</p>
|
||
|
|
<p v-else-if="item.type === 'result'"
|
||
|
|
:class="[
|
||
|
|
'font-bold text-sm pl-2',
|
||
|
|
item.color === 'neon-cyan' ? 'text-neon-cyan' :
|
||
|
|
item.color === 'neon-pink' ? 'text-neon-pink' :
|
||
|
|
'text-text-secondary'
|
||
|
|
]">
|
||
|
|
{{ item.text }}
|
||
|
|
</p>
|
||
|
|
<p v-else-if="item.type === 'system'"
|
||
|
|
:class="[
|
||
|
|
'text-sm',
|
||
|
|
item.color === 'neon-purple' ? 'text-neon-purple font-bold tracking-wider' :
|
||
|
|
'text-text-muted'
|
||
|
|
]">
|
||
|
|
{{ item.text }}
|
||
|
|
</p>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div v-if="logItems.length === 0 && !isReplaying" class="text-text-muted italic pt-8 text-center text-sm">
|
||
|
|
Hit REPLAY to watch the fight.
|
||
|
|
</div>
|
||
|
|
<div v-if="isReplaying && logItems.length === 0" class="text-neon-purple italic pt-8 text-center text-sm">
|
||
|
|
Fight starting...
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<!-- RIGHT: Game Canvas -->
|
||
|
|
<div class="lg:w-[62%] flex flex-col min-h-0 border border-border rounded-lg bg-black overflow-hidden order-1 lg:order-2">
|
||
|
|
|
||
|
|
<!-- Health bars -->
|
||
|
|
<div class="px-3 py-2 bg-surface-raised/80 border-b border-border flex-shrink-0">
|
||
|
|
<div class="flex items-center gap-2">
|
||
|
|
<div class="flex-shrink-0 min-w-0">
|
||
|
|
<p class="font-display font-black text-xs tracking-wider truncate"
|
||
|
|
:class="fight.winnerId === fight.botA?.id ? 'text-neon-cyan glow-cyan' : 'text-text-primary'">
|
||
|
|
{{ fight.botA?.name }}
|
||
|
|
</p>
|
||
|
|
</div>
|
||
|
|
<div class="flex-1 h-5 bg-black/50 rounded-sm overflow-hidden border border-neon-cyan/30">
|
||
|
|
<div class="h-full bg-gradient-to-r from-neon-cyan to-neon-purple health-bar"
|
||
|
|
:style="{ width: `${displayHpA}%` }" />
|
||
|
|
</div>
|
||
|
|
<span class="font-mono font-bold text-sm w-8 text-right"
|
||
|
|
:class="displayHpA > 50 ? 'text-neon-cyan' : displayHpA > 20 ? 'text-neon-yellow' : 'text-ko'">
|
||
|
|
{{ displayHpA }}
|
||
|
|
</span>
|
||
|
|
|
||
|
|
<span class="font-glitch text-neon-purple text-base px-1">VS</span>
|
||
|
|
|
||
|
|
<span class="font-mono font-bold text-sm w-8 text-left"
|
||
|
|
:class="displayHpB > 50 ? 'text-neon-pink' : displayHpB > 20 ? 'text-neon-yellow' : 'text-ko'">
|
||
|
|
{{ displayHpB }}
|
||
|
|
</span>
|
||
|
|
<div class="flex-1 h-5 bg-black/50 rounded-sm overflow-hidden border border-neon-pink/30">
|
||
|
|
<div class="h-full bg-gradient-to-l from-neon-pink to-neon-purple health-bar ml-auto"
|
||
|
|
:style="{ width: `${displayHpB}%` }" />
|
||
|
|
</div>
|
||
|
|
<div class="flex-shrink-0 min-w-0">
|
||
|
|
<p class="font-display font-black text-xs tracking-wider truncate text-right"
|
||
|
|
:class="fight.winnerId === fight.botB?.id ? 'text-neon-pink glow-pink' : 'text-text-primary'">
|
||
|
|
{{ fight.botB?.name }}
|
||
|
|
</p>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="flex items-center justify-between mt-1">
|
||
|
|
<span class="font-pixel text-[9px]" :class="tierClass(fight.botA?.tier || 0)">
|
||
|
|
{{ Math.round(fight.botA?.eloRating || 0) }} ELO
|
||
|
|
</span>
|
||
|
|
<span class="font-pixel text-[9px] text-text-muted">
|
||
|
|
{{ fight.arenaInfo?.name }} | R{{ currentRound || fight.totalRounds }}/{{ fight.totalRounds }}
|
||
|
|
</span>
|
||
|
|
<span class="font-pixel text-[9px]" :class="tierClass(fight.botB?.tier || 0)">
|
||
|
|
{{ Math.round(fight.botB?.eloRating || 0) }} ELO
|
||
|
|
</span>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<!-- Canvas -->
|
||
|
|
<div class="flex-1 relative min-h-0">
|
||
|
|
<canvas ref="canvasRef" class="w-full h-full block" />
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<!-- Controls -->
|
||
|
|
<div class="px-3 py-2 border-t border-border flex-shrink-0 flex items-center justify-between bg-surface-raised/50">
|
||
|
|
<button
|
||
|
|
class="px-6 py-2 border border-neon-pink/50 text-neon-pink font-display font-bold text-xs
|
||
|
|
tracking-widest hover:bg-neon-pink/10 transition-all neon-border-pink
|
||
|
|
disabled:opacity-30 disabled:cursor-not-allowed"
|
||
|
|
:disabled="isReplaying"
|
||
|
|
@click="replay"
|
||
|
|
>
|
||
|
|
{{ isReplaying ? 'FIGHTING...' : 'REPLAY FIGHT' }}
|
||
|
|
</button>
|
||
|
|
<span class="font-pixel text-[10px] text-text-muted">
|
||
|
|
{{ fight.status === 'finished' ? 'FINISHED' : fight.status.toUpperCase() }}
|
||
|
|
</span>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</template>
|