feat: profile page character display, persistent auth, cleanup

- Large animated sprite on BotProfilePage with tier-aware rendering
- Dynamic status titles (UNSTOPPABLE, ON FIRE, FRESH MEAT, etc.)
- Nostr auth persisted to localStorage — survives navigation and HMR
- Remove grotesque close-up overlays (eyeballs, tongues, teeth, drool)
- Remove crowd cheering signs (too small to look good)
- Add archetype to bot stats API response
- SpritePreview now accepts tier prop

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Dorian
2026-03-07 08:21:20 +00:00
co-authored by Claude Opus 4.6
parent e49735002c
commit 4a35e1e0b5
5 changed files with 81 additions and 307 deletions
+35 -13
View File
@@ -2,6 +2,7 @@
import { ref, onMounted, onUnmounted } from 'vue'
import { useRoute, useRouter, RouterLink } from 'vue-router'
import { useNostr } from '../composables/useNostr'
import SpritePreview from '../components/SpritePreview.vue'
const route = useRoute()
const router = useRouter()
@@ -12,6 +13,7 @@ interface BotStats {
id: string
name: string
avatarSeed: string
archetype: string
profilePicUrl: string | null
eloRating: number
wins: number
@@ -36,6 +38,19 @@ interface BotStats {
}[]
}
function getStatusTitle(s: BotStats): string {
if (s.totalFights === 0) return 'FRESH MEAT'
if (s.winStreak >= 10) return 'UNSTOPPABLE'
if (s.winStreak >= 5) return 'ON FIRE'
if (s.winStreak >= 3) return 'HOT STREAK'
if (s.winRate >= 80 && s.totalFights >= 10) return 'DOMINANT'
if (s.winRate >= 60) return 'RISING'
if (s.winRate >= 40) return 'SCRAPPY'
if (s.winRate < 20 && s.totalFights >= 5) return 'PUNCHING BAG'
if (s.losses > s.wins && s.totalFights >= 5) return 'UNDERDOG'
return 'CONTENDER'
}
interface QueueEntry {
botId: string
botName: string
@@ -127,23 +142,30 @@ const tierClass = (t: number) => `tier-${t}`
</div>
<template v-else>
<!-- Header -->
<!-- Header with large character -->
<div class="text-center mb-5">
<img
v-if="stats.profilePicUrl"
:src="stats.profilePicUrl"
alt=""
class="w-16 h-16 rounded-full mx-auto mb-2 border-2"
:style="{ borderColor: stats.tierColor }"
/>
<p class="font-display text-xs font-bold tracking-[0.2em] mb-1"
:style="{ color: stats.tierColor }">
{{ stats.tierName }}
</p>
<div class="relative inline-block mb-3">
<SpritePreview
:seed="stats.avatarSeed || stats.name"
:archetype="stats.archetype"
:tier="stats.tier"
:size="160"
class="mx-auto drop-shadow-[0_0_20px_var(--glow)]"
:style="{ '--glow': stats.tierColor + '80' } as any"
/>
<div class="absolute -bottom-1 left-1/2 -translate-x-1/2 px-3 py-0.5 border text-[9px] font-display font-black tracking-widest whitespace-nowrap"
:style="{ borderColor: stats.tierColor, color: stats.tierColor, backgroundColor: 'rgba(0,0,0,0.8)' }">
{{ stats.tierName }}
</div>
</div>
<h2 class="font-display font-black text-3xl sm:text-4xl tracking-wider gradient-text">
{{ stats.name }}
</h2>
<p class="font-mono text-text-muted text-[10px] mt-1">
<p class="font-display text-sm font-bold tracking-widest mt-1"
:style="{ color: stats.tierColor }">
{{ getStatusTitle(stats) }}
</p>
<p class="font-mono text-text-muted text-[10px] mt-0.5">
#{{ stats.rank }} of {{ stats.totalBots }}
</p>
</div>