diff --git a/frontend/src/components/FightViewer.vue b/frontend/src/components/FightViewer.vue index 24fd4d7..a8377c1 100644 --- a/frontend/src/components/FightViewer.vue +++ b/frontend/src/components/FightViewer.vue @@ -46,6 +46,7 @@ const canvasContainer = ref() const logEl = ref() let scene: FightSceneController | null = null const sceneReady = ref(false) +let cleanupTimerHandle: ReturnType | null = null const isReplaying = ref(false) const displayHpA = ref(100) @@ -98,6 +99,8 @@ onUnmounted(() => { stopAllAudio() sceneReady.value = false if (scene) { scene.destroy(); scene = null } + if (cleanupTimerHandle) { clearTimeout(cleanupTimerHandle); cleanupTimerHandle = null } + if (typeof speechSynthesis !== 'undefined') speechSynthesis.cancel() }) async function initScene() { @@ -398,8 +401,10 @@ async function replay() { } // Kill any remaining queued speech — fight is over - setTimeout(() => { + if (cleanupTimerHandle) clearTimeout(cleanupTimerHandle) + cleanupTimerHandle = setTimeout(() => { if (typeof speechSynthesis !== 'undefined') speechSynthesis.cancel() + cleanupTimerHandle = null }, 3000) isReplaying.value = false diff --git a/frontend/src/game/sounds.ts b/frontend/src/game/sounds.ts index 6ac3ddc..da50175 100644 --- a/frontend/src/game/sounds.ts +++ b/frontend/src/game/sounds.ts @@ -290,9 +290,9 @@ function speak(text: string, profileName: string, cancelPrevious: boolean = fals if (!voicesLoaded) loadVoices() // Chrome bug: speechSynthesis can get stuck. Nudge it. if (speechSynthesis.paused) speechSynthesis.resume() - // Only flush if queue is getting deep — allows voices to overlap naturally + // Flush if queue is getting deep — max 2 queued to prevent buildup if (cancelPrevious || (speechSynthesis.pending && speechSynthesis.speaking)) { - if (_speechQueueDepth > 3) { + if (_speechQueueDepth > 2) { speechSynthesis.cancel() _speechQueueDepth = 0 } diff --git a/frontend/src/pages/BotProfilePage.vue b/frontend/src/pages/BotProfilePage.vue index 871fba7..e0dc295 100644 --- a/frontend/src/pages/BotProfilePage.vue +++ b/frontend/src/pages/BotProfilePage.vue @@ -76,7 +76,6 @@ const showChoose = ref(false) const waitingFighters = ref([]) let pollHandle: ReturnType | null = null -const isOwner = ref(false) const loadError = ref('') const fightError = ref('') const showCustomize = ref(false) @@ -102,6 +101,8 @@ const ARCHETYPES = [ 'broom_man', ] +const isOwner = computed(() => isLoggedIn.value && nostrBot.value?.name === botName) + const custForm = reactive({ archetype: '', primaryColor: '#3388cc', @@ -216,9 +217,6 @@ onMounted(async () => { } isLoading.value = false - // Check ownership - isOwner.value = isLoggedIn.value && nostrBot.value?.name === botName - // Poll queue for "choose your fight" pollQueue() pollHandle = setInterval(pollQueue, 4000)