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
+5 -4
View File
@@ -8,6 +8,7 @@ const props = defineProps<{
tier?: number
size?: number
customization?: SpriteCustomization
pose?: keyof typeof ANIMATIONS
}>()
const canvasRef = ref<HTMLCanvasElement>()
@@ -24,11 +25,11 @@ function render() {
ctx.clearRect(0, 0, displaySize, displaySize)
ctx.imageSmoothingEnabled = false
const idleAnim = ANIMATIONS.idle
const f = frame % idleAnim.frames
const anim = ANIMATIONS[props.pose || 'idle']
const f = frame % anim.frames
ctx.drawImage(
img,
f * FRAME_SIZE, idleAnim.row * FRAME_SIZE, FRAME_SIZE, FRAME_SIZE,
f * FRAME_SIZE, anim.row * FRAME_SIZE, FRAME_SIZE, FRAME_SIZE,
0, 0, displaySize, displaySize,
)
@@ -46,7 +47,7 @@ function loadSprite() {
onMounted(() => loadSprite())
watch(() => [props.seed, props.archetype, props.customization], () => {
watch(() => [props.seed, props.archetype, props.customization, props.pose], () => {
if (animHandle) clearTimeout(animHandle)
frame = 0
loadSprite()