feat: zap the winner button with lightning animation

Add ZAP WINNER button in FightViewer after fight ends. Server endpoint
POST /api/payments/zap increments zapsReceived on winner bot. Show zap
count on bot profile page. Add zaps_received column with migration.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Dorian
2026-03-08 20:10:46 +00:00
co-authored by Claude Opus 4.6
parent d1fe4d6e83
commit d22d739666
6 changed files with 92 additions and 1 deletions
+49
View File
@@ -9,6 +9,7 @@ import {
setMusicIntensity, stopAllAudio,
setMasterMute, isMasterMuted, ensureAudioContext,
speakQuestion, speakAnswer, speakNarration,
prefetchQuestion, prefetchAnswer, prefetchNarration,
sfxRandomComedy, sfxRandomFail, sfxVineBoom, sfxEmotionalDamage,
} from '../game/sounds'
@@ -70,6 +71,28 @@ const glitching = ref(false)
const soundOn = ref(true)
const insanityMode = ref(false)
// Zap the winner
const zapSent = ref(false)
const zapAnimating = ref(false)
async function zapWinner() {
if (!props.fight.winnerId || zapSent.value || zapAnimating.value) return
zapAnimating.value = true
try {
const res = await fetch('/api/payments/zap', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
winnerId: props.fight.winnerId,
fightId: props.fight.id,
amountSats: 21,
}),
})
if (res.ok) zapSent.value = true
} catch { /* ignore */ }
setTimeout(() => { zapAnimating.value = false }, 800)
}
// Reactions
const REACTION_EMOJIS: Record<string, string> = {
fist: '\u{1F44A}',
@@ -758,6 +781,20 @@ async function _doReplay() {
>
{{ insanityMode ? 'INSANITY' : 'SKIP' }}
</button>
<!-- Zap winner button -->
<button
v-if="fight.status === 'finished' && fight.winnerId && !isReplaying"
class="px-3 py-1.5 border font-display font-bold text-[10px] tracking-wider transition-all
disabled:opacity-30 disabled:cursor-not-allowed"
:class="zapSent
? 'border-neon-yellow/50 text-neon-yellow bg-neon-yellow/10'
: 'border-neon-yellow/30 text-neon-yellow hover:bg-neon-yellow/10 hover:border-neon-yellow/50'"
:disabled="zapSent || zapAnimating"
@click="zapWinner"
>
<span :class="{ 'zap-flash': zapAnimating }">{{ zapSent ? 'ZAPPED!' : 'ZAP WINNER' }}</span>
</button>
<span class="font-pixel text-[10px] text-text-muted">{{ fight.status === 'finished' ? 'FINISHED' : fight.status.toUpperCase() }}</span>
</div>
@@ -916,6 +953,18 @@ async function _doReplay() {
100% { opacity: 0; transform: translateY(-140px) scale(0.6); }
}
/* Zap lightning flash */
.zap-flash {
animation: zap-bolt 0.8s ease-out;
}
@keyframes zap-bolt {
0% { opacity: 1; filter: brightness(1); }
15% { opacity: 1; filter: brightness(3) drop-shadow(0 0 8px #ffe14d); }
30% { opacity: 0.6; filter: brightness(1.5); }
50% { opacity: 1; filter: brightness(2.5) drop-shadow(0 0 12px #ffe14d); }
100% { opacity: 1; filter: brightness(1); }
}
/* VHS scanline overlay on canvas */
.glitch-container::after {
content: '';
+6 -1
View File
@@ -46,6 +46,7 @@ interface BotStats {
satsWon: number
satsWagered: number
hasWallet: boolean
zapsReceived: number
createdAt: string
// Owner-only webhook fields
webhookUrl?: string
@@ -487,7 +488,7 @@ const tierClass = (t: number) => `tier-${t}`
</div>
<!-- Sats stats -->
<div v-if="stats.satsWon || stats.satsWagered" class="flex gap-2 mt-2">
<div v-if="stats.satsWon || stats.satsWagered || stats.zapsReceived" class="flex gap-2 mt-2">
<div class="flex-1 border border-neon-cyan/20 bg-neon-cyan/5 p-2.5 text-center">
<p class="font-display font-bold text-base text-neon-cyan">{{ stats.satsWon || 0 }}</p>
<p class="font-display text-[8px] text-text-muted tracking-wider mt-0.5">SATS WON</p>
@@ -496,6 +497,10 @@ const tierClass = (t: number) => `tier-${t}`
<p class="font-display font-bold text-base text-neon-purple">{{ stats.satsWagered || 0 }}</p>
<p class="font-display text-[8px] text-text-muted tracking-wider mt-0.5">WAGERED</p>
</div>
<div v-if="stats.zapsReceived" class="flex-1 border border-neon-yellow/20 bg-neon-yellow/5 p-2.5 text-center">
<p class="font-display font-bold text-base text-neon-yellow">{{ stats.zapsReceived }}</p>
<p class="font-display text-[8px] text-text-muted tracking-wider mt-0.5">ZAPS</p>
</div>
</div>
<!-- Wallet connection (owner only) -->