fix: mobile human fight — split view with 60/40 canvas/controls layout
- Canvas takes top 60%, controls panel takes bottom 40% on mobile - Battle log header/text hidden on mobile, controls always visible - Multiple choice buttons shown on both mobile and desktop - Removed canvas overlay approach in favor of proper split layout Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
4b46aaf643
commit
52bba75ee5
@@ -84,10 +84,8 @@ function applyChallenge(data: any, receivedAt?: number) {
|
|||||||
humanChoices.value = data.choices || []
|
humanChoices.value = data.choices || []
|
||||||
humanAnswer.value = ''
|
humanAnswer.value = ''
|
||||||
humanSubmitted.value = false
|
humanSubmitted.value = false
|
||||||
// Mobile: 4 second timer for quick tap choices; desktop: full remaining time
|
humanTimer.value = Math.ceil(remaining / 1000)
|
||||||
humanTimer.value = isMobile.value ? 4 : Math.ceil(remaining / 1000)
|
|
||||||
startTimer()
|
startTimer()
|
||||||
if (!isMobile.value) nextTick(() => answerInput.value?.focus())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function submitChoice(choice: string) {
|
function submitChoice(choice: string) {
|
||||||
@@ -645,15 +643,16 @@ function stopAutoBattle() {
|
|||||||
<!-- LIVE HUMAN FIGHT: full arena view with input -->
|
<!-- LIVE HUMAN FIGHT: full arena view with input -->
|
||||||
<div v-else-if="isLive && isHumanFight" class="flex-1 flex flex-col lg:flex-row gap-1 sm:gap-2 min-h-0">
|
<div v-else-if="isLive && isHumanFight" class="flex-1 flex flex-col lg:flex-row gap-1 sm:gap-2 min-h-0">
|
||||||
|
|
||||||
<!-- Battle Log + Desktop Human Input (hidden on mobile) -->
|
<!-- Battle Log + Human Input — mobile: bottom 40%, desktop: left 38% -->
|
||||||
<div class="hidden lg:flex lg:w-[38%] flex-col min-h-0 border border-border rounded-lg bg-black/90 overflow-hidden lg:order-1">
|
<div class="flex flex-col min-h-0 border border-border rounded-lg bg-black/90 overflow-hidden
|
||||||
<div class="bg-surface-raised border-b border-border px-3 py-1.5 flex items-center gap-2 flex-shrink-0">
|
h-[40%] lg:h-auto lg:w-[38%] order-2 lg:order-1">
|
||||||
|
<div class="bg-surface-raised border-b border-border px-3 py-1.5 hidden lg: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-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-yellow" />
|
||||||
<span class="w-2.5 h-2.5 rounded-full bg-neon-green" />
|
<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>
|
<span class="font-pixel text-[10px] text-text-muted ml-2 tracking-wider">BATTLE LOG</span>
|
||||||
</div>
|
</div>
|
||||||
<div ref="liveLogEl" class="flex-1 overflow-y-auto p-2 sm:p-4 font-mono text-xs sm:text-sm space-y-1.5 leading-relaxed">
|
<div ref="liveLogEl" class="flex-1 overflow-y-auto p-2 sm:p-4 font-mono text-xs sm:text-sm space-y-1.5 leading-relaxed hidden lg:block">
|
||||||
<div v-for="(item, idx) in liveLogItems" :key="idx">
|
<div v-for="(item, idx) in liveLogItems" :key="idx">
|
||||||
<div v-if="item.type === 'divider'" class="py-1.5"><div class="border-t border-white/5" /></div>
|
<div v-if="item.type === 'divider'" class="py-1.5"><div class="border-t border-white/5" /></div>
|
||||||
<p v-else-if="item.type === 'header'" class="text-neon-purple font-bold text-base tracking-wide pt-3 pb-1 uppercase">{{ item.text }}</p>
|
<p v-else-if="item.type === 'header'" class="text-neon-purple font-bold text-base tracking-wide pt-3 pb-1 uppercase">{{ item.text }}</p>
|
||||||
@@ -706,17 +705,17 @@ function stopAutoBattle() {
|
|||||||
</div>
|
</div>
|
||||||
<div v-else-if="humanChallenge && !humanSubmitted">
|
<div v-else-if="humanChallenge && !humanSubmitted">
|
||||||
<div class="flex items-center justify-between mb-1">
|
<div class="flex items-center justify-between mb-1">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2 min-w-0 flex-1">
|
||||||
<span class="font-display font-black text-[10px] tracking-wider text-neon-cyan">
|
<span class="font-display font-black text-[10px] tracking-wider text-neon-cyan shrink-0">
|
||||||
R{{ humanChallenge.roundNumber }}
|
R{{ humanChallenge.roundNumber }}
|
||||||
</span>
|
</span>
|
||||||
<span class="font-display font-bold text-[9px] tracking-wider text-neon-purple uppercase">
|
<span class="font-display font-bold text-[9px] tracking-wider text-neon-purple uppercase truncate">
|
||||||
{{ humanChallenge.label }}
|
{{ humanChallenge.label }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<span
|
<span
|
||||||
class="font-display font-black text-sm tracking-wider tabular-nums"
|
class="font-display font-black text-sm tracking-wider tabular-nums shrink-0 ml-2"
|
||||||
:class="humanTimer <= 2 ? 'text-ko animate-pulse' : 'text-neon-yellow'"
|
:class="humanTimer <= 1 ? 'text-ko animate-pulse scale-125' : humanTimer <= 2 ? 'text-ko animate-pulse' : 'text-neon-yellow'"
|
||||||
>
|
>
|
||||||
{{ humanTimer }}s
|
{{ humanTimer }}s
|
||||||
</span>
|
</span>
|
||||||
@@ -724,26 +723,21 @@ function stopAutoBattle() {
|
|||||||
<p class="font-mono text-[11px] text-text-primary leading-snug mb-1.5 line-clamp-2">
|
<p class="font-mono text-[11px] text-text-primary leading-snug mb-1.5 line-clamp-2">
|
||||||
{{ humanChallenge.prompt }}
|
{{ humanChallenge.prompt }}
|
||||||
</p>
|
</p>
|
||||||
<div class="flex gap-2">
|
<!-- Multiple choice buttons -->
|
||||||
<input
|
<div class="flex flex-col gap-1.5">
|
||||||
ref="answerInput"
|
|
||||||
v-model="humanAnswer"
|
|
||||||
type="text"
|
|
||||||
maxlength="200"
|
|
||||||
placeholder="Type fast..."
|
|
||||||
class="flex-1 bg-surface border-2 border-border px-2 py-1.5 text-sm font-mono
|
|
||||||
text-text-primary placeholder-text-muted
|
|
||||||
focus:outline-none focus:border-neon-pink/50 transition-colors"
|
|
||||||
@keyup.enter="submitHumanAnswer"
|
|
||||||
/>
|
|
||||||
<button
|
<button
|
||||||
class="px-3 py-1.5 bg-neon-pink/10 border-2 border-neon-pink text-neon-pink
|
v-for="(choice, i) in humanChoices"
|
||||||
font-display font-black text-xs tracking-wider
|
:key="i"
|
||||||
hover:bg-neon-pink/20 transition-all disabled:opacity-30"
|
class="w-full text-left px-3 py-2 border-2 rounded-lg font-mono text-xs leading-snug
|
||||||
:disabled="!humanAnswer.trim()"
|
active:scale-[0.97] transition-all line-clamp-2"
|
||||||
@click="submitHumanAnswer"
|
:class="[
|
||||||
|
i === 0 ? 'border-neon-cyan/40 text-neon-cyan bg-neon-cyan/5 hover:bg-neon-cyan/15 active:bg-neon-cyan/20' :
|
||||||
|
i === 1 ? 'border-neon-pink/40 text-neon-pink bg-neon-pink/5 hover:bg-neon-pink/15 active:bg-neon-pink/20' :
|
||||||
|
'border-neon-purple/40 text-neon-purple bg-neon-purple/5 hover:bg-neon-purple/15 active:bg-neon-purple/20'
|
||||||
|
]"
|
||||||
|
@click="submitChoice(choice)"
|
||||||
>
|
>
|
||||||
GO
|
{{ choice }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -769,8 +763,8 @@ function stopAutoBattle() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Game Canvas -->
|
<!-- Game Canvas — mobile: top 60%, desktop: right 62% -->
|
||||||
<div class="flex-1 lg:w-[62%] flex flex-col min-h-0 border border-border rounded-lg bg-black overflow-hidden lg:order-2">
|
<div class="h-[60%] lg:h-auto lg: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 -->
|
||||||
<div v-if="liveFightData?.botA" class="px-2 sm:px-3 py-1.5 sm:py-2 bg-surface-raised/80 border-b border-border flex-shrink-0">
|
<div v-if="liveFightData?.botA" class="px-2 sm:px-3 py-1.5 sm:py-2 bg-surface-raised/80 border-b border-border flex-shrink-0">
|
||||||
@@ -843,92 +837,6 @@ function stopAutoBattle() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- MOBILE: Challenge overlay at bottom of canvas -->
|
|
||||||
<div class="absolute bottom-0 left-0 right-0 z-30 lg:hidden">
|
|
||||||
<!-- Post-fight buttons -->
|
|
||||||
<div v-if="humanFightDone" class="bg-gradient-to-t from-black via-black/95 to-transparent px-4 pt-10 pb-4 safe-bottom">
|
|
||||||
<button
|
|
||||||
v-if="myBot"
|
|
||||||
:disabled="isRequeueing"
|
|
||||||
class="w-full py-3.5 bg-neon-cyan/10 border-2 border-neon-cyan/50 text-neon-cyan
|
|
||||||
font-display font-black text-sm tracking-widest rounded-lg mb-2
|
|
||||||
hover:bg-neon-cyan/20 transition-all
|
|
||||||
disabled:opacity-50 disabled:cursor-wait flex items-center justify-center gap-2"
|
|
||||||
@click="fightAgain(myBot!.id)"
|
|
||||||
>
|
|
||||||
<span v-if="isRequeueing" class="w-4 h-4 border-2 border-neon-cyan/30 border-t-neon-cyan rounded-full animate-spin" />
|
|
||||||
{{ isRequeueing ? 'MATCHING...' : 'FIGHT AGAIN' }}
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
class="w-full py-2.5 border border-white/10 text-text-muted
|
|
||||||
font-display text-xs tracking-widest rounded-lg
|
|
||||||
hover:bg-white/5 transition-all"
|
|
||||||
@click="$router.push('/join')"
|
|
||||||
>
|
|
||||||
BACK TO LOBBY
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<!-- Cooldown between rounds -->
|
|
||||||
<div v-else-if="roundCooldown > 0" class="bg-gradient-to-t from-black/90 to-transparent px-4 pt-6 pb-4 safe-bottom text-center">
|
|
||||||
<p class="font-display text-neon-yellow text-lg tracking-widest animate-pulse">
|
|
||||||
NEXT ROUND IN {{ roundCooldown }}...
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<!-- Multiple choice challenge -->
|
|
||||||
<div v-else-if="humanChallenge && !humanSubmitted" class="bg-gradient-to-t from-black via-black/95 to-transparent px-3 pt-8 pb-3 safe-bottom">
|
|
||||||
<div class="flex items-center justify-between mb-1.5">
|
|
||||||
<div class="flex items-center gap-2 min-w-0 flex-1">
|
|
||||||
<span class="font-display font-black text-[10px] tracking-wider text-neon-cyan shrink-0">R{{ humanChallenge.roundNumber }}</span>
|
|
||||||
<span class="font-display font-bold text-[10px] tracking-wider text-neon-purple uppercase truncate">{{ humanChallenge.label }}</span>
|
|
||||||
</div>
|
|
||||||
<span
|
|
||||||
class="font-display font-black text-lg tracking-wider tabular-nums shrink-0 ml-2"
|
|
||||||
:class="humanTimer <= 1 ? 'text-ko animate-pulse scale-125' : humanTimer <= 2 ? 'text-ko animate-pulse' : 'text-neon-yellow'"
|
|
||||||
>
|
|
||||||
{{ humanTimer }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<p class="font-mono text-xs text-text-primary leading-snug mb-2 line-clamp-2">
|
|
||||||
{{ humanChallenge.prompt }}
|
|
||||||
</p>
|
|
||||||
<div class="flex flex-col gap-1.5">
|
|
||||||
<button
|
|
||||||
v-for="(choice, i) in humanChoices"
|
|
||||||
:key="i"
|
|
||||||
class="w-full text-left px-3 py-2.5 border-2 rounded-lg font-mono text-xs leading-snug
|
|
||||||
active:scale-[0.97] transition-all line-clamp-2"
|
|
||||||
:class="[
|
|
||||||
i === 0 ? 'border-neon-cyan/40 text-neon-cyan bg-neon-cyan/5 active:bg-neon-cyan/20' :
|
|
||||||
i === 1 ? 'border-neon-pink/40 text-neon-pink bg-neon-pink/5 active:bg-neon-pink/20' :
|
|
||||||
'border-neon-purple/40 text-neon-purple bg-neon-purple/5 active:bg-neon-purple/20'
|
|
||||||
]"
|
|
||||||
@click="submitChoice(choice)"
|
|
||||||
>
|
|
||||||
{{ choice }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- Submitted / waiting -->
|
|
||||||
<div v-else-if="humanSubmitted" class="bg-gradient-to-t from-black/90 to-transparent px-4 pt-6 pb-4 safe-bottom text-center">
|
|
||||||
<p class="font-mono text-text-muted text-sm animate-pulse">Scoring round...</p>
|
|
||||||
</div>
|
|
||||||
<!-- Idle: sound toggle -->
|
|
||||||
<div v-else class="bg-gradient-to-t from-black/80 to-transparent px-4 pt-4 pb-3 safe-bottom flex items-center justify-between">
|
|
||||||
<p class="font-mono text-text-muted text-xs">Waiting for challenge...</p>
|
|
||||||
<button
|
|
||||||
class="w-8 h-8 flex items-center justify-center border border-border/50 text-text-muted
|
|
||||||
hover:text-neon-cyan hover:border-neon-cyan/50 transition-all"
|
|
||||||
@click="toggleLiveSound"
|
|
||||||
>
|
|
||||||
<svg v-if="liveSoundOn" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="w-4 h-4">
|
|
||||||
<path d="M11 5L6 9H2v6h4l5 4V5z"/><path d="M19.07 4.93a10 10 0 010 14.14M15.54 8.46a5 5 0 010 7.07"/>
|
|
||||||
</svg>
|
|
||||||
<svg v-else xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="w-4 h-4">
|
|
||||||
<path d="M11 5L6 9H2v6h4l5 4V5z"/><line x1="23" y1="9" x2="17" y2="15"/><line x1="17" y1="9" x2="23" y2="15"/>
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user