fix: mobile homepage — side-by-side Fight/Watch buttons, hero bots below title

- Buttons always side-by-side with shorter labels (FIGHT / WATCH)
- Hero bots displayed horizontally below title on mobile
- Desktop keeps flanking hero layout with larger sprites

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Dorian
2026-03-07 20:10:57 +00:00
co-authored by Claude Opus 4.6
parent 52bba75ee5
commit c5f6c6bbf9
2 changed files with 82 additions and 76 deletions
+29 -27
View File
@@ -1,5 +1,5 @@
<script setup lang="ts">
import { ref, computed, onMounted, onUnmounted, nextTick } from 'vue'
import { ref, computed, watch, onMounted, onUnmounted, nextTick } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import FightViewer from '../components/FightViewer.vue'
import { useNostr } from '../composables/useNostr'
@@ -44,7 +44,6 @@ const humanTimer = ref(5)
const humanSubmitted = ref(false)
let humanPollHandle: ReturnType<typeof setInterval> | null = null
let timerHandle: ReturnType<typeof setInterval> | null = null
const answerInput = ref<HTMLInputElement | null>(null)
// Live human fight scene state
const liveFightData = ref<any>(null)
@@ -66,7 +65,6 @@ const liveAnnouncementVisible = ref(false)
const currentChallengeInfo = ref<{ type: string; label: string } | null>(null)
const pendingChallengeData = ref<{ data: any; receivedAt: number } | null>(null)
const humanChoices = ref<string[]>([])
const isMobile = ref(typeof window !== 'undefined' && window.innerWidth < 768)
const humanFightDone = ref(false)
const humanFightResult = ref<{ winnerId: string; winnerName: string; isPerfect: boolean } | null>(null)
@@ -153,9 +151,12 @@ function startPolling() {
pollCount++
const s = await loadFight()
if (s === 'finished') {
isLive.value = false
stopHumanPolling()
if (pollHandle) { clearInterval(pollHandle); pollHandle = null }
// Human fights handle end via SSE fight_end event — don't transition here
if (!isHumanFight.value) {
isLive.value = false
stopHumanPolling()
if (pollHandle) { clearInterval(pollHandle); pollHandle = null }
}
} else if (pollCount > 120) {
isLive.value = false
fightError.value = 'Fight took too long. Try refreshing.'
@@ -510,12 +511,17 @@ function toggleLiveSound() {
setMasterMute(!liveSoundOn.value)
}
// --- Resize listener ---
function onResize() { isMobile.value = window.innerWidth < 768 }
// Watch for liveFightData becoming available (may arrive via polling after mount)
watch(liveFightData, async (val) => {
if (val && !liveSceneReady.value && !liveScene) {
await nextTick()
await nextTick()
await initLiveScene()
}
})
// --- Mount / Unmount ---
onMounted(async () => {
window.addEventListener('resize', onResize)
const status = await loadFight()
isLoading.value = false
@@ -528,15 +534,11 @@ onMounted(async () => {
if (isHumanFight.value) {
startHumanPolling()
connectSSE()
await nextTick()
await nextTick()
if (liveFightData.value) await initLiveScene()
}
}
})
onUnmounted(() => {
window.removeEventListener('resize', onResize)
if (pollHandle) clearInterval(pollHandle)
stopHumanPolling()
disconnectSSE()
@@ -698,38 +700,38 @@ function stopAutoBattle() {
BACK TO LOBBY
</button>
</div>
<div v-else-if="roundCooldown > 0" class="text-center py-1">
<p class="font-display text-neon-yellow text-sm tracking-widest animate-pulse">
<div v-else-if="roundCooldown > 0" class="text-center py-4">
<p class="font-display text-neon-yellow text-lg tracking-widest animate-pulse">
NEXT ROUND IN {{ roundCooldown }}...
</p>
</div>
<div v-else-if="humanChallenge && !humanSubmitted">
<div class="flex items-center justify-between mb-1">
<div class="flex items-center justify-between mb-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 shrink-0">
<span class="font-display font-black text-xs tracking-wider text-neon-cyan shrink-0">
R{{ humanChallenge.roundNumber }}
</span>
<span class="font-display font-bold text-[9px] tracking-wider text-neon-purple uppercase truncate">
<span class="font-display font-bold text-xs tracking-wider text-neon-purple uppercase truncate">
{{ humanChallenge.label }}
</span>
</div>
<span
class="font-display font-black text-sm tracking-wider tabular-nums shrink-0 ml-2"
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 }}s
</span>
</div>
<p class="font-mono text-[11px] text-text-primary leading-snug mb-1.5 line-clamp-2">
<p class="font-mono text-sm text-text-primary leading-relaxed mb-3">
{{ humanChallenge.prompt }}
</p>
<!-- Multiple choice buttons -->
<div class="flex flex-col gap-1.5">
<div class="flex flex-col gap-2">
<button
v-for="(choice, i) in humanChoices"
:key="i"
class="w-full text-left px-3 py-2 border-2 rounded-lg font-mono text-xs leading-snug
active:scale-[0.97] transition-all line-clamp-2"
class="w-full text-left px-4 py-3 border-2 rounded-lg font-mono text-sm leading-normal
active:scale-[0.97] transition-all"
: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' :
@@ -741,11 +743,11 @@ function stopAutoBattle() {
</button>
</div>
</div>
<div v-else-if="humanSubmitted" class="text-center py-1">
<p class="font-mono text-text-muted text-xs animate-pulse">Scoring round...</p>
<div v-else-if="humanSubmitted" class="text-center py-3">
<p class="font-display text-text-muted text-sm tracking-wider animate-pulse">SCORING ROUND...</p>
</div>
<div v-else class="flex items-center justify-between py-1">
<p class="font-mono text-text-muted text-xs">Waiting for challenge...</p>
<div v-else class="flex items-center justify-between py-2">
<p class="font-mono text-text-muted text-sm">Waiting for challenge...</p>
<button
class="w-7 h-7 flex items-center justify-center border border-border/50 text-text-muted
hover:text-neon-cyan hover:border-neon-cyan/50 transition-all"