feat: remove mock fight button for production, cleanup fight UI
- Remove "Fight a Classic Bot" button (mock endpoints blocked in prod) - Simplify speech bubble rendering (remove glow/accent layers) - Sound and FightViewer improvements - BotProfilePage enhancements Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
bb9a966d4e
commit
56785cfdea
@@ -6082,31 +6082,19 @@ export async function createFightScene(config: FightSceneConfig) {
|
||||
const bubbleW = textW + padX * 2
|
||||
const bubbleH = lines.length * lineH + padY * 2
|
||||
const tailSize = 10
|
||||
const borderW = 3
|
||||
|
||||
// Position — offset away from center so bubbles don't overlap
|
||||
const offsetX = side === 'a' ? -bubbleW * 0.3 : bubbleW * 0.3
|
||||
const bx = fighter.pos.x + offsetX
|
||||
const by = Math.max(8, fighter.pos.y - 85 - bubbleH)
|
||||
|
||||
// Colours — vivid neon
|
||||
const bgColor = side === 'a' ? '#001a22' : '#22001a'
|
||||
const borderColor = side === 'a' ? '#00f0ff' : '#ff2d7b'
|
||||
const glowColor = side === 'a' ? '#00f0ff' : '#ff2d7b'
|
||||
const textColor = '#ffffff'
|
||||
const bgColor = side === 'a' ? '#0a1e2a' : '#2a0a1e'
|
||||
|
||||
// Outer glow halo
|
||||
const glow = k.add([
|
||||
k.rect(bubbleW + 12, bubbleH + 12),
|
||||
k.pos(bx - bubbleW / 2 - 6, by - 6),
|
||||
k.color(safeColor(k, glowColor)), k.opacity(0.08), k.z(53),
|
||||
])
|
||||
allEls.push(glow)
|
||||
|
||||
// Border (thick, behind bubble)
|
||||
// Single border rect (no glow/accent layers — keeps rendering clean)
|
||||
const border = k.add([
|
||||
k.rect(bubbleW + borderW * 2, bubbleH + borderW * 2),
|
||||
k.pos(bx - bubbleW / 2 - borderW, by - borderW),
|
||||
k.rect(bubbleW + 4, bubbleH + 4),
|
||||
k.pos(bx - bubbleW / 2 - 2, by - 2),
|
||||
k.color(safeColor(k, borderColor)), k.opacity(0.85), k.z(54),
|
||||
])
|
||||
allEls.push(border)
|
||||
@@ -6119,70 +6107,56 @@ export async function createFightScene(config: FightSceneConfig) {
|
||||
])
|
||||
allEls.push(bubble)
|
||||
|
||||
// Tail — two overlapping rects to form a triangle look
|
||||
// Tail — pixel arrow pointing down to fighter
|
||||
const tailX = bx + (side === 'a' ? bubbleW * 0.15 : -bubbleW * 0.15)
|
||||
const tailY = by + bubbleH
|
||||
// Outer tail (border colour)
|
||||
const tailOuter = k.add([
|
||||
k.rect(tailSize + borderW, tailSize + borderW),
|
||||
k.pos(tailX, tailY - 2),
|
||||
k.color(safeColor(k, borderColor)), k.opacity(0.85), k.z(54),
|
||||
k.rotate(45), k.anchor('top'),
|
||||
])
|
||||
allEls.push(tailOuter)
|
||||
// Inner tail (bg colour, masks the border)
|
||||
const tailInner = k.add([
|
||||
k.rect(tailSize, tailSize),
|
||||
k.pos(tailX, tailY - 1),
|
||||
k.color(safeColor(k, bgColor)), k.opacity(0.95), k.z(55),
|
||||
k.rotate(45), k.anchor('top'),
|
||||
])
|
||||
allEls.push(tailInner)
|
||||
// Cover strip — hides top half of the rotated tail square
|
||||
const tailCover = k.add([
|
||||
k.rect(bubbleW, borderW + 2),
|
||||
k.pos(bx - bubbleW / 2, tailY - borderW),
|
||||
k.color(safeColor(k, bgColor)), k.opacity(0.95), k.z(56),
|
||||
])
|
||||
allEls.push(tailCover)
|
||||
|
||||
// Inner accent line at top of bubble
|
||||
const accent = k.add([
|
||||
k.rect(bubbleW - 8, 2),
|
||||
k.pos(bx - bubbleW / 2 + 4, by + 3),
|
||||
k.color(safeColor(k, borderColor)), k.opacity(0.4), k.z(56),
|
||||
])
|
||||
allEls.push(accent)
|
||||
const tailBaseY = by + bubbleH
|
||||
for (let row = 0; row < tailSize; row++) {
|
||||
const tw = tailSize - row
|
||||
// Border pixel row
|
||||
const tailBorder = k.add([
|
||||
k.rect(tw + 2, 1),
|
||||
k.pos(tailX - (tw + 2) / 2, tailBaseY + row),
|
||||
k.color(safeColor(k, borderColor)), k.opacity(0.85), k.z(54),
|
||||
])
|
||||
allEls.push(tailBorder)
|
||||
// Inner pixel row
|
||||
if (tw > 2) {
|
||||
const tailInner = k.add([
|
||||
k.rect(tw - 1, 1),
|
||||
k.pos(tailX - (tw - 1) / 2, tailBaseY + row),
|
||||
k.color(safeColor(k, bgColor)), k.opacity(0.95), k.z(55),
|
||||
])
|
||||
allEls.push(tailInner)
|
||||
}
|
||||
}
|
||||
|
||||
// Text lines
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const tEl = k.add([
|
||||
k.text(safeText(lines[i]), { size: fontSize }),
|
||||
k.text(safeText(lines[i]), { size: fontSize, font: 'monospace' }),
|
||||
k.pos(bx - bubbleW / 2 + padX, by + padY + i * lineH + 2),
|
||||
k.color(safeColor(k, textColor)), k.opacity(1), k.z(57),
|
||||
k.color(255, 255, 255), k.opacity(1), k.z(57),
|
||||
])
|
||||
allEls.push(tEl)
|
||||
}
|
||||
|
||||
// Animated glow pulse
|
||||
glow.onUpdate(() => {
|
||||
glow.opacity = 0.05 + Math.sin(k.time() * 3) * 0.05
|
||||
})
|
||||
// Border glow pulse
|
||||
border.onUpdate(() => {
|
||||
border.opacity = 0.7 + Math.sin(k.time() * 4) * 0.15
|
||||
})
|
||||
|
||||
// Pop-in — scale up from anchor point with bounce
|
||||
// Pop-in animation
|
||||
const anchorX = bx, anchorY = by + bubbleH
|
||||
allEls.forEach(el => {
|
||||
const origX = el.pos.x, origY = el.pos.y
|
||||
const origOpacity = el.opacity
|
||||
el.pos.x = anchorX + (origX - anchorX) * 0.1
|
||||
el.pos.y = anchorY + (origY - anchorY) * 0.1
|
||||
el.opacity *= 0
|
||||
el.opacity = 0
|
||||
k.tween(0, 1, 0.25, (t) => {
|
||||
el.pos.x = anchorX + (origX - anchorX) * t
|
||||
el.pos.y = anchorY + (origY - anchorY) * t
|
||||
el.opacity = (el === glow ? 0.08 : el === border ? 0.85 : el === bubble || el === tailInner || el === tailCover ? 0.95 : el === tailOuter ? 0.85 : el === accent ? 0.4 : 1) * t
|
||||
el.opacity = origOpacity * t
|
||||
}, k.easings.easeOutBack)
|
||||
})
|
||||
|
||||
@@ -6641,7 +6615,7 @@ export async function createFightScene(config: FightSceneConfig) {
|
||||
}, k.easings.easeInOutQuad)
|
||||
await k.wait(0.2)
|
||||
heart.text = '!!!'
|
||||
announceRandom("You promised you'd stop fighting!", false)
|
||||
announceRandom("You promised you'd stop fighting!")
|
||||
await k.wait(0.4)
|
||||
// Girlfriend storms off
|
||||
announceSilly('I\'m telling your developer!')
|
||||
|
||||
@@ -265,8 +265,9 @@ if (typeof speechSynthesis !== 'undefined') {
|
||||
loadVoices()
|
||||
}
|
||||
|
||||
function speak(text: string, profileName: string, cancelPrevious: boolean = true, _echo: boolean = false) {
|
||||
function speak(text: string, profileName: string, cancelPrevious: boolean = false, _echo: boolean = false) {
|
||||
if (typeof speechSynthesis === 'undefined') return
|
||||
if (masterMuted) return
|
||||
if (!voicesLoaded) loadVoices()
|
||||
if (cancelPrevious) speechSynthesis.cancel()
|
||||
const profile = voiceProfiles[profileName] || voiceProfiles.announcer
|
||||
@@ -285,11 +286,10 @@ export function stopAllAudio() {
|
||||
|
||||
// Public voice functions
|
||||
export function announce(text: string, pitch?: number, rate?: number) {
|
||||
if (masterMuted) return
|
||||
if (pitch !== undefined || rate !== undefined) {
|
||||
// Custom params — use announcer voice with overrides
|
||||
if (typeof speechSynthesis === 'undefined') return
|
||||
if (!voicesLoaded) loadVoices()
|
||||
speechSynthesis.cancel()
|
||||
const utter = new SpeechSynthesisUtterance(text)
|
||||
const profile = voiceProfiles.announcer
|
||||
if (profile.voice) utter.voice = profile.voice
|
||||
@@ -310,16 +310,16 @@ export function announceSmooth(text: string) { speak(text, 'smooth') }
|
||||
|
||||
// Pick a random voice profile for variety
|
||||
const ALL_VOICE_KEYS = Object.keys(voiceProfiles)
|
||||
export function announceRandom(text: string, echo: boolean = false) {
|
||||
export function announceRandom(text: string) {
|
||||
const key = ALL_VOICE_KEYS[Math.floor(Math.random() * ALL_VOICE_KEYS.length)]
|
||||
speak(text, key, true, echo)
|
||||
speak(text, key)
|
||||
}
|
||||
// Announce with a specific mood category
|
||||
const DRAMATIC_VOICES = ['deep', 'boomer', 'movie', 'preacher', 'opera', 'demon_v', 'echo_v', 'final_boss', 'game_over', 'mainframe', 'hal', 'ancient', 'giant']
|
||||
const HYPE_VOICES = ['hype', 'screamer', 'sportscaster', 'auctioneer', 'hyper', 'punk', 'drill', 'wrestler_v', 'karen', 'terrified', 'power_up', 'news', 'scottish']
|
||||
const SILLY_VOICES = ['chipmunk', 'baby', 'surfer', 'valley', 'pirate_v', 'alien_v', 'glitch', 'drunk', 'stoner', 'fairy', 'tutorial', 'npc', 'dial_up', 'glitchbot', 'grandma', 'conspiracy']
|
||||
const COOL_VOICES = ['smooth', 'wizard_v', 'ninja_v', 'cowboy_v', 'angel', 'whisper', 'posh', 'aussie', 'french', 'sensei', 'ai_core', 'android_v', 'siri', 'professor', 'texan', 'boss_taunt', 'sleepy']
|
||||
export function announceDramatic(text: string) { speak(text, DRAMATIC_VOICES[Math.floor(Math.random() * DRAMATIC_VOICES.length)], true, true) }
|
||||
export function announceDramatic(text: string) { speak(text, DRAMATIC_VOICES[Math.floor(Math.random() * DRAMATIC_VOICES.length)]) }
|
||||
export function announceHype(text: string) { speak(text, HYPE_VOICES[Math.floor(Math.random() * HYPE_VOICES.length)]) }
|
||||
export function announceSilly(text: string) { speak(text, SILLY_VOICES[Math.floor(Math.random() * SILLY_VOICES.length)]) }
|
||||
export function announceCool(text: string) { speak(text, COOL_VOICES[Math.floor(Math.random() * COOL_VOICES.length)]) }
|
||||
@@ -1794,6 +1794,30 @@ export function setSfxVolume(v: number) {
|
||||
if (sfxGain) sfxGain.gain.value = Math.max(0, Math.min(1, v))
|
||||
}
|
||||
|
||||
let masterMuted = false
|
||||
const MUSIC_VOL = 0.12
|
||||
const SFX_VOL = 0.25
|
||||
|
||||
export function setMasterMute(muted: boolean) {
|
||||
masterMuted = muted
|
||||
if (muted) {
|
||||
if (musicGain) musicGain.gain.value = 0
|
||||
if (sfxGain) sfxGain.gain.value = 0
|
||||
if (typeof speechSynthesis !== 'undefined') speechSynthesis.cancel()
|
||||
} else {
|
||||
if (musicGain) musicGain.gain.value = MUSIC_VOL
|
||||
if (sfxGain) sfxGain.gain.value = SFX_VOL
|
||||
}
|
||||
}
|
||||
|
||||
export function isMasterMuted(): boolean {
|
||||
return masterMuted
|
||||
}
|
||||
|
||||
export function ensureAudioContext() {
|
||||
getCtx()
|
||||
}
|
||||
|
||||
// === CROWD SOUNDS ===
|
||||
// Procedural crowd reactions using layered noise + filtered tones
|
||||
|
||||
|
||||
Reference in New Issue
Block a user