feat: v5 — boxing poster fight cards, 12-char names, diverse mock bots

- Fight Card page: dramatic poster background with cross-hatch, spotlights,
  vignettes, corner brackets, scan lines; 3D VS orb with punch animation;
  selectable undercard with main event always pinned at top
- PosterSprite: high-quality 480px poster frame with 6-pass renderer
  (aura, glow, bevel, specular, particles); PixelGlove component
- 12-char bot name limit across all forms and server validation
- Mock bots: all 100 now have diverse archetypes (25 types), 25% human
  fighters; seedMockBots updates existing bots on restart
- Leaderboard: inline SpritePreview next to each bot name
- Nostr auth: persistent login, nsec copy button
- Wallet: NWC + Lightning Address, ranked fight flow
- Server: payments, ranked queue, customization endpoint

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Dorian
2026-03-08 10:33:30 +00:00
co-authored by Claude Opus 4.6
parent ccf4196647
commit f6eb7d2845
32 changed files with 1743 additions and 467 deletions
+12 -3
View File
@@ -153,7 +153,15 @@ const myBotId = computed(() => {
const showOverlay = computed(() => replayDone.value && !isRequeueing.value && !autoBattle.value)
function sleep(ms: number) { return new Promise(resolve => setTimeout(resolve, ms)) }
let _pageDestroyed = false
function sleep(ms: number): Promise<void> {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (_pageDestroyed) reject(new Error('unmounted'))
else resolve()
}, ms)
})
}
function scrollLiveLog() { nextTick(() => { liveLogEl.value?.scrollTo({ top: liveLogEl.value.scrollHeight, behavior: 'smooth' }) }) }
const challengeLabel = (type: string) => {
@@ -401,7 +409,7 @@ function connectSSE() {
eventSource.addEventListener('round_end', (e) => {
try {
handleRoundEnd(JSON.parse(e.data))
handleRoundEnd(JSON.parse(e.data)).catch(() => {})
} catch (err) {
console.warn('[FightPage] SSE round_end failed:', err)
}
@@ -409,7 +417,7 @@ function connectSSE() {
eventSource.addEventListener('fight_end', (e) => {
try {
handleFightEnd(JSON.parse(e.data))
handleFightEnd(JSON.parse(e.data)).catch(() => {})
} catch (err) {
console.warn('[FightPage] SSE fight_end failed:', err)
}
@@ -660,6 +668,7 @@ onMounted(async () => {
})
onUnmounted(() => {
_pageDestroyed = true
if (pollHandle) clearInterval(pollHandle)
stopHumanPolling()
disconnectSSE()