feat: move creator pubkey to env, fix mobile TTS + signer, button loaders

Security:
- Move CREATOR_PUBKEY from hardcoded constant to BOTFIGHTS_CREATOR_PUBKEYS
  env var. Shared isCreatorPubkey() in constants.ts used by auth, admin,
  tournaments. Frontend checks authorization via API, not client-side.

Mobile fixes:
- Nostr signer: poll for window.nostr up to 3s (Amber injects late).
- TTS: auto-unlock AudioContext on first user interaction via
  installAutoUnlock() on fight page mount.

UX:
- Add loading spinners to "I BUILD BOTS" and "I FIGHT MYSELF" buttons.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Dorian
2026-03-09 15:31:58 +00:00
co-authored by Claude Opus 4.6
parent dd3cbdae7f
commit 6dc50f5d5d
7 changed files with 55 additions and 40 deletions
+18 -10
View File
@@ -306,14 +306,18 @@ async function showHitText(text: string, color: string, x: number) {
}
function addRoundToLog(round: FightRound, stagger: boolean): Promise<void> {
// Hide human player's typed answer from the battle log
const isAHuman = props.fight.botA?.archetype === 'human'
const isBHuman = props.fight.botB?.archetype === 'human'
if (!stagger) {
const challenge = JSON.parse(round.challengeData)
logItems.value.push(
{ type: 'header', round: round.roundNumber, text: `ROUND ${round.roundNumber}: ${challengeLabel(round.challengeType)}`, color: 'neon-purple' },
{ type: 'prompt', round: round.roundNumber, text: challenge.displayPrompt || challenge.prompt, color: 'text-muted' },
{ type: 'responseA', round: round.roundNumber, text: `${props.fight.botA?.name}: ${round.botAResponse || '[NO RESPONSE]'} (${round.botATimeMs}ms)`, color: 'neon-cyan' },
{ type: 'responseB', round: round.roundNumber, text: `${props.fight.botB?.name}: ${round.botBResponse || '[NO RESPONSE]'} (${round.botBTimeMs}ms)`, color: 'neon-pink' },
)
if (!isAHuman) logItems.value.push({ type: 'responseA', round: round.roundNumber, text: `${props.fight.botA?.name}: ${round.botAResponse || '[NO RESPONSE]'} (${round.botATimeMs}ms)`, color: 'neon-cyan' })
if (!isBHuman) logItems.value.push({ type: 'responseB', round: round.roundNumber, text: `${props.fight.botB?.name}: ${round.botBResponse || '[NO RESPONSE]'} (${round.botBTimeMs}ms)`, color: 'neon-pink' })
if (round.narration) logItems.value.push({ type: 'narration', round: round.roundNumber, text: `>> ${round.narration}`, color: 'neon-yellow' })
const winner = round.winnerId === props.fight.botA?.id ? props.fight.botA?.name : round.winnerId === props.fight.botB?.id ? props.fight.botB?.name : 'DRAW'
logItems.value.push({ type: 'result', round: round.roundNumber, text: `${winner} wins round! (${round.botAScore} vs ${round.botBScore})`, color: 'text-secondary' })
@@ -326,14 +330,18 @@ function addRoundToLog(round: FightRound, stagger: boolean): Promise<void> {
scrollLog(); await sleep(150)
logItems.value.push({ type: 'prompt', round: round.roundNumber, text: challenge.displayPrompt || challenge.prompt, color: 'text-muted' })
scrollLog(); await sleep(200)
logItems.value.push({ type: 'responseA', round: round.roundNumber, text: `${props.fight.botA?.name}: ${round.botAResponse || '[NO RESPONSE]'}`, color: 'neon-cyan' })
scrollLog(); await sleep(150)
logItems.value.push({ type: 'time', round: round.roundNumber, text: ` ${round.botATimeMs}ms | Score: ${round.botAScore}`, color: 'text-muted' })
scrollLog(); await sleep(150)
logItems.value.push({ type: 'responseB', round: round.roundNumber, text: `${props.fight.botB?.name}: ${round.botBResponse || '[NO RESPONSE]'}`, color: 'neon-pink' })
scrollLog(); await sleep(150)
logItems.value.push({ type: 'time', round: round.roundNumber, text: ` ${round.botBTimeMs}ms | Score: ${round.botBScore}`, color: 'text-muted' })
scrollLog()
if (!isAHuman) {
logItems.value.push({ type: 'responseA', round: round.roundNumber, text: `${props.fight.botA?.name}: ${round.botAResponse || '[NO RESPONSE]'}`, color: 'neon-cyan' })
scrollLog(); await sleep(150)
logItems.value.push({ type: 'time', round: round.roundNumber, text: ` ${round.botATimeMs}ms | Score: ${round.botAScore}`, color: 'text-muted' })
scrollLog(); await sleep(150)
}
if (!isBHuman) {
logItems.value.push({ type: 'responseB', round: round.roundNumber, text: `${props.fight.botB?.name}: ${round.botBResponse || '[NO RESPONSE]'}`, color: 'neon-pink' })
scrollLog(); await sleep(150)
logItems.value.push({ type: 'time', round: round.roundNumber, text: ` ${round.botBTimeMs}ms | Score: ${round.botBScore}`, color: 'text-muted' })
scrollLog()
}
})()
}