fix: move BETA badge after green dot in navbar

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Dorian
2026-03-09 15:36:24 +00:00
co-authored by Claude Opus 4.6
parent 226d242552
commit df70f5f093
5 changed files with 46 additions and 19 deletions
+12 -2
View File
@@ -451,9 +451,13 @@ async function _doReplay() {
await sleep(insanityMode.value ? 50 : 300)
}
// Hide human player's typed answers from replay
const replayIsAHuman = props.fight.botA?.archetype === 'human'
const replayIsBHuman = props.fight.botB?.archetype === 'human'
// 2. Bot A: ensure audio ready, then show log + bubble + mouth + voice all at once
const isCodeRound = isCodeAnswer(round.challengeType, round.botAResponse || '')
if (round.botAResponse) {
if (round.botAResponse && !replayIsAHuman) {
if (doTTS && !isCodeRound) await awaitAnswerReady(props.fight.botA!.name, round.botAResponse)
scene?.stopShowboating('a')
logItems.value.push({ type: 'responseA', round: round.roundNumber, text: `${props.fight.botA?.name}: ${round.botAResponse || '[NO RESPONSE]'}`, color: 'neon-cyan' })
@@ -473,10 +477,13 @@ async function _doReplay() {
if (!doTTS && !insanityMode.value) await sleep(400)
scene?.stopTalking('a')
scene?.hideSpeechBubble('a')
} else if (replayIsAHuman) {
scene?.stopShowboating('a')
await sleep(insanityMode.value ? 50 : 200)
}
// 3. Bot B: ensure audio ready, then show log + bubble + mouth + voice all at once
if (round.botBResponse) {
if (round.botBResponse && !replayIsBHuman) {
if (doTTS && !isCodeRound) await awaitAnswerReady(props.fight.botB!.name, round.botBResponse)
scene?.stopShowboating('b')
logItems.value.push({ type: 'responseB', round: round.roundNumber, text: `${props.fight.botB?.name}: ${round.botBResponse || '[NO RESPONSE]'}`, color: 'neon-pink' })
@@ -497,6 +504,9 @@ async function _doReplay() {
if (!doTTS && !insanityMode.value) await sleep(400)
scene?.stopTalking('b')
scene?.hideSpeechBubble('b')
} else if (replayIsBHuman) {
scene?.stopShowboating('b')
await sleep(insanityMode.value ? 50 : 200)
}
// Stop all showboating before fight animation
+1 -1
View File
@@ -39,12 +39,12 @@ const bottomNav = [
<span class="font-display font-black text-neon-pink text-lg tracking-widest glow-pink">
BOTFIGHTS
</span>
<span class="font-glitch text-[9px] text-neon-cyan/80 tracking-[0.2em] -rotate-12 -ml-1 -mt-2 select-none">BETA</span>
<span
class="w-2 h-2 rounded-full transition-colors"
:class="isOnline ? 'bg-green-400' : 'bg-red-400 animate-pulse'"
:title="isOnline ? 'Online' : 'Offline'"
/>
<span class="font-glitch text-[9px] text-neon-cyan/80 tracking-[0.2em] -rotate-12 -ml-0.5 select-none">BETA</span>
</RouterLink>
<div class="hidden md:flex items-center gap-6">
+5 -1
View File
@@ -1222,7 +1222,11 @@ export async function createFightScene(config: FightSceneConfig) {
stopShowboating(side: 'a' | 'b') { showboatSystem.stopShowboating(side) },
async playEntrance() {
return entranceSystem.playEntrance()
// Timeout entrance to prevent hanging on mobile (max 8s)
await Promise.race([
entranceSystem.playEntrance(),
new Promise<void>(r => setTimeout(r, 8000)),
])
},
// Physical contact delegates
+20 -11
View File
@@ -524,21 +524,30 @@ async function playEntrance() {
while (idxB === idxA && attempts < 5) { idxB = pickTierEntrance(botB.tier); attempts++ }
// Play entrances with slight stagger — creator always gets special entrance
// Wrap each in try/catch so one failing doesn't prevent the other
announceDeepIntro()
if (botA.archetype === 'the_creator') {
await creatorEntrance(fA, HOME_A, true)
} else {
await entrances[idxA](fA, HOME_A, true)
try {
if (botA.archetype === 'the_creator') {
await creatorEntrance(fA, HOME_A, true)
} else {
await entrances[idxA](fA, HOME_A, true)
}
} catch (e) {
console.warn('[Entrance] Fighter A entrance failed:', e)
}
await k.wait(0.3)
if (botB.archetype === 'the_creator') {
await creatorEntrance(fB, HOME_B, false)
} else {
await entrances[idxB](fB, HOME_B, false)
try {
if (botB.archetype === 'the_creator') {
await creatorEntrance(fB, HOME_B, false)
} else {
await entrances[idxB](fB, HOME_B, false)
}
} catch (e) {
console.warn('[Entrance] Fighter B entrance failed:', e)
}
await k.wait(0.2)
// Safety: ensure both fighters are visible and at home positions
// Safety: ALWAYS ensure both fighters are visible and at home positions
fA.pos.x = HOME_A
fA.pos.y = GROUND_Y - 6
fA.opacity = 1
@@ -547,8 +556,8 @@ async function playEntrance() {
fB.opacity = 1
// Both idle at home positions
fA.play('idle')
fB.play('idle')
try { fA.play('idle') } catch { /* sprite may not have anim */ }
try { fB.play('idle') } catch { /* sprite may not have anim */ }
}
return { playEntrance }
+8 -4
View File
@@ -248,13 +248,17 @@ async function handleRoundEnd(data: any) {
const cLabel = currentChallengeInfo.value?.label || challengeLabel(currentChallengeInfo.value?.type || '')
// Don't show the human player's typed answer in the battle log
const isAHuman = myBotId.value === fd.botA.id && isHumanFight.value
const isBHuman = myBotId.value === fd.botB.id && isHumanFight.value
liveLogItems.value.push(
{ type: 'header', round, text: `ROUND ${round}: ${cLabel}`, color: 'neon-purple' },
)
if (result.botAResponse) {
if (result.botAResponse && !isAHuman) {
liveLogItems.value.push({ type: 'responseA', round, text: `${fd.botA.name}: ${result.botAResponse}`, color: 'neon-cyan' })
}
if (result.botBResponse) {
if (result.botBResponse && !isBHuman) {
liveLogItems.value.push({ type: 'responseB', round, text: `${fd.botB.name}: ${result.botBResponse}`, color: 'neon-pink' })
}
if (result.narration) {
@@ -269,8 +273,8 @@ async function handleRoundEnd(data: any) {
if (liveScene) {
fanfareRound(round)
if (result.botAResponse) liveScene.showSpeechBubble('a', result.botAResponse, 3)
if (result.botBResponse) {
if (result.botAResponse && !isAHuman) liveScene.showSpeechBubble('a', result.botAResponse, 3)
if (result.botBResponse && !isBHuman) {
setTimeout(() => {
if (liveScene && result.botBResponse) liveScene.showSpeechBubble('b', result.botBResponse, 2.5)
}, 300)