feat: mobile human vs AI — multiple choice, two-row VS bar, responsive arena
- Server-side choice generation for all 22 challenge types (factual + creative) - Mobile: 3 multiple-choice buttons replace keyboard input (4s timer) - Two-row VS bar on mobile (names row + HP bars row) in FightPage and FightViewer - Battle log hidden on mobile to maximize canvas visibility - Arena page responsive: stacking header, tighter fight cards, no name overflow - Profile page two-column desktop layout with contained sprite display - Human fighter sprites with baby growth system Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
1906821452
commit
4b46aaf643
@@ -116,8 +116,8 @@ async function initScene() {
|
||||
|
||||
scene = await createFightScene({
|
||||
canvas: canvasRef.value,
|
||||
botA: { name: props.fight.botA.name, seed: props.fight.botA.avatarSeed || props.fight.botA.name, tier: props.fight.botA.tier, archetype: props.fight.botA.archetype, customization: props.fight.botA.customization as any },
|
||||
botB: { name: props.fight.botB.name, seed: props.fight.botB.avatarSeed || props.fight.botB.name, tier: props.fight.botB.tier, archetype: props.fight.botB.archetype, customization: props.fight.botB.customization as any },
|
||||
botA: { name: props.fight.botA.name, seed: props.fight.botA.avatarSeed || props.fight.botA.name, tier: props.fight.botA.tier, archetype: props.fight.botA.archetype, customization: props.fight.botA.customization as any, wins: props.fight.botA.wins, losses: props.fight.botA.losses },
|
||||
botB: { name: props.fight.botB.name, seed: props.fight.botB.avatarSeed || props.fight.botB.name, tier: props.fight.botB.tier, archetype: props.fight.botB.archetype, customization: props.fight.botB.customization as any, wins: props.fight.botB.wins, losses: props.fight.botB.losses },
|
||||
arena: props.fight.arena,
|
||||
})
|
||||
}
|
||||
@@ -401,8 +401,8 @@ async function replay() {
|
||||
<template>
|
||||
<div class="h-full flex flex-col lg:flex-row gap-1 sm:gap-2">
|
||||
|
||||
<!-- LEFT: Battle Log (below on mobile) -->
|
||||
<div class="h-[30vh] sm:h-auto lg:w-[38%] flex flex-col min-h-0 border border-border rounded-lg bg-black/90 overflow-hidden order-2 lg:order-1">
|
||||
<!-- LEFT: Battle Log (hidden on mobile, shown on desktop) -->
|
||||
<div class="hidden lg:flex lg:w-[38%] flex-col min-h-0 border border-border rounded-lg bg-black/90 overflow-hidden order-2 lg:order-1">
|
||||
<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" />
|
||||
@@ -440,48 +440,62 @@ async function replay() {
|
||||
</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">
|
||||
<div class="flex-1 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 -->
|
||||
<!-- Health bars — mobile: two-row (names then bars), desktop: single row -->
|
||||
<div class="px-2 sm:px-3 py-1.5 sm:py-2 bg-surface-raised/80 border-b border-border flex-shrink-0">
|
||||
<div class="flex items-center gap-1 sm:gap-2">
|
||||
<div class="flex-shrink-0 flex items-center gap-1 min-w-0 max-w-[25%] sm:max-w-none">
|
||||
<img
|
||||
v-if="fight.botA?.profilePicUrl"
|
||||
:src="fight.botA.profilePicUrl"
|
||||
alt=""
|
||||
class="w-5 h-5 sm:w-7 sm:h-7 rounded-full border border-neon-cyan/40 flex-shrink-0"
|
||||
/>
|
||||
<p class="font-marker text-[10px] sm:text-sm tracking-wider truncate"
|
||||
:class="fight.winnerId === fight.botA?.id ? 'text-neon-cyan glow-cyan' : 'text-text-primary'">
|
||||
{{ fight.botA?.name }}
|
||||
</p>
|
||||
|
||||
<!-- Mobile layout (< lg) -->
|
||||
<div class="lg:hidden space-y-1">
|
||||
<!-- Names row -->
|
||||
<div class="flex items-center gap-1">
|
||||
<div class="flex-1 flex items-center gap-1 min-w-0">
|
||||
<img v-if="fight.botA?.profilePicUrl" :src="fight.botA.profilePicUrl" alt="" class="w-5 h-5 rounded-full border border-neon-cyan/40 flex-shrink-0" />
|
||||
<p class="font-marker text-xs tracking-wider truncate" :class="fight.winnerId === fight.botA?.id ? 'text-neon-cyan glow-cyan' : 'text-text-primary'">{{ fight.botA?.name }}</p>
|
||||
</div>
|
||||
<span class="font-funky text-neon-purple text-base px-1 flex-shrink-0">VS</span>
|
||||
<div class="flex-1 flex items-center gap-1 min-w-0 justify-end">
|
||||
<p class="font-marker 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>
|
||||
<img v-if="fight.botB?.profilePicUrl" :src="fight.botB.profilePicUrl" alt="" class="w-5 h-5 rounded-full border border-neon-pink/40 flex-shrink-0" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-1 h-4 sm: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-[10px] sm:text-sm w-6 sm:w-8 text-right" :class="displayHpA > 50 ? 'text-neon-cyan' : displayHpA > 20 ? 'text-neon-yellow' : 'text-ko'">{{ displayHpA }}</span>
|
||||
<span class="font-funky text-neon-purple text-base sm:text-xl px-0.5 sm:px-1">VS</span>
|
||||
<span class="font-mono font-bold text-[10px] sm:text-sm w-6 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-4 sm: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 flex items-center gap-1 min-w-0 max-w-[25%] sm:max-w-none">
|
||||
<p class="font-marker text-[10px] sm:text-sm tracking-wider truncate text-right"
|
||||
:class="fight.winnerId === fight.botB?.id ? 'text-neon-pink glow-pink' : 'text-text-primary'">
|
||||
{{ fight.botB?.name }}
|
||||
</p>
|
||||
<img
|
||||
v-if="fight.botB?.profilePicUrl"
|
||||
:src="fight.botB.profilePicUrl"
|
||||
alt=""
|
||||
class="w-5 h-5 sm:w-7 sm:h-7 rounded-full border border-neon-pink/40 flex-shrink-0"
|
||||
/>
|
||||
<!-- HP bars row -->
|
||||
<div class="flex items-center gap-1">
|
||||
<span class="font-mono font-bold text-[10px] w-6 text-left" :class="displayHpA > 50 ? 'text-neon-cyan' : displayHpA > 20 ? 'text-neon-yellow' : 'text-ko'">{{ displayHpA }}</span>
|
||||
<div class="flex-1 h-4 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>
|
||||
<div class="flex-1 h-4 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>
|
||||
<span class="font-mono font-bold text-[10px] w-6 text-right" :class="displayHpB > 50 ? 'text-neon-pink' : displayHpB > 20 ? 'text-neon-yellow' : 'text-ko'">{{ displayHpB }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Desktop layout (>= lg) -->
|
||||
<div class="hidden lg:flex items-center gap-2">
|
||||
<div class="flex-shrink-0 flex items-center gap-1 min-w-0">
|
||||
<img v-if="fight.botA?.profilePicUrl" :src="fight.botA.profilePicUrl" alt="" class="w-7 h-7 rounded-full border border-neon-cyan/40 flex-shrink-0" />
|
||||
<p class="font-marker text-sm 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-funky text-neon-purple text-xl 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 flex items-center gap-1 min-w-0">
|
||||
<p class="font-marker text-sm tracking-wider truncate text-right" :class="fight.winnerId === fight.botB?.id ? 'text-neon-pink glow-pink' : 'text-text-primary'">{{ fight.botB?.name }}</p>
|
||||
<img v-if="fight.botB?.profilePicUrl" :src="fight.botB.profilePicUrl" alt="" class="w-7 h-7 rounded-full border border-neon-pink/40 flex-shrink-0" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between mt-0.5 sm:mt-1">
|
||||
<span class="font-pixel text-[8px] sm:text-[9px]" :class="tierClass(fight.botA?.tier || 0)">{{ Math.round(fight.botA?.eloRating || 0) }}</span>
|
||||
<span class="font-pixel text-[8px] sm:text-[9px] text-text-muted">{{ fight.arenaInfo?.name }} | R{{ currentRound || fight.totalRounds }}/{{ fight.totalRounds }}</span>
|
||||
<span class="font-pixel text-[8px] sm:text-[9px] text-text-muted truncate mx-1">{{ fight.arenaInfo?.name }} | R{{ currentRound || fight.totalRounds }}/{{ fight.totalRounds }}</span>
|
||||
<span class="font-pixel text-[8px] sm:text-[9px]" :class="tierClass(fight.botB?.tier || 0)">{{ Math.round(fight.botB?.eloRating || 0) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user