caro5 / client /src /pages /LocalSetup.tsx
Pedro de Carvalho
Add ai+
ff165e0
Raw
History Blame Contribute Delete
31.7 kB
import { useState, useEffect } from 'react'
import { useNavigate, useLocation } from 'react-router-dom'
import {
theme,
useThemeMode,
THEMES,
getPlayerName,
getPlayer2Name,
setPlayerName,
setPlayer2Name,
getPlayer1Symbol,
setPlayer1Symbol,
getPlayer2Symbol,
setPlayer2Symbol,
getPlayerColor,
setPlayerColor,
getPlayer2Color,
setPlayer2Color,
getPlayerDrawTower,
setPlayerDrawTower,
getPlayer2DrawTower,
setPlayer2DrawTower
} from '../theme.ts'
import SymbolPicker from '../components/SymbolPicker.tsx'
import ThemeSelector from '../components/ThemeSelector.tsx'
import type { GameRules } from 'shared/types.ts'
import { binAssetUrl } from '../binAssets'
import { PushButton, pushButtonVars } from '../components/PushButton'
import { audioManager } from '../game/effects/AudioManager.ts'
type LocalPlayerType = 'human' | 'bot'
type BotProfile = 'normal' | 'expert' | 'ai' | 'ai+'
type LegacyBotProfile = BotProfile | 'balanced'
type RulesMode = 'tournament' | 'custom'
const TOURNAMENT_RULES: GameRules = { swap2: true, noOverlines: true, boardSize: 15 }
function normalizeBotProfile(profile?: LegacyBotProfile): BotProfile {
if (profile === 'expert' || profile === 'ai' || profile === 'ai+') return profile
return 'normal'
}
const PRESET_COLORS = [
'#58a6ff', // Bright Blue
'#f0883e', // Orange/Coral
'#ef4444', // Red
'#10b981', // Emerald Green
'#a78bfa', // Lavender Purple
'#f9a8d4', // Rose Pink
'#fbbf24', // Yellow Gold
]
const getPresetColors = (defaultColor: string) => {
const base = [...PRESET_COLORS]
const normalized = defaultColor.toLowerCase()
if (!base.some(c => c.toLowerCase() === normalized)) {
return [defaultColor, ...base]
}
return base
}
function PlayerPiecePreview({ symbol, color, drawTower }: { symbol: string; color: string; drawTower: boolean }) {
const isEmoji = symbol.length > 1 || (symbol.codePointAt(0) ?? 0) > 0xffff
const textShadow = isEmoji
? '0 2px 5px rgba(0,0,0,0.75)'
: '0 1px 3px rgba(0,0,0,0.45)'
return (
<span
aria-hidden="true"
style={{
position: 'relative',
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center',
width: 52,
height: 52,
borderRadius: '50%',
background: drawTower ? color : 'transparent',
color: drawTower ? '#fff' : color,
fontSize: isEmoji ? '2rem' : '1.7rem',
fontWeight: 800,
lineHeight: 1,
textShadow,
boxShadow: drawTower
? `0 8px 0 color-mix(in srgb, ${color} 68%, #000), 0 12px 18px rgba(0,0,0,0.35)`
: 'none',
transform: drawTower ? 'translateY(-3px)' : 'none',
}}
>
{drawTower && (
<span
style={{
position: 'absolute',
inset: 4,
borderRadius: '50%',
border: '1px solid rgba(255,255,255,0.38)',
boxShadow: 'inset 5px 5px 8px rgba(255,255,255,0.18), inset -6px -6px 10px rgba(0,0,0,0.22)',
}}
/>
)}
<span style={{ position: 'relative', zIndex: 1 }}>{symbol}</span>
</span>
)
}
export default function LocalSetup() {
const navigate = useNavigate()
const location = useLocation()
const setupState = (location.state as { rules?: GameRules; botProfile?: LegacyBotProfile } | null)
const rules = setupState?.rules || { swap2: true, noOverlines: true, boardSize: 15 }
const [name1, setName1] = useState(getPlayerName())
const [name2, setName2] = useState(getPlayer2Name())
const [symbol1, setSymbol1] = useState(getPlayer1Symbol())
const [symbol2, setSymbol2] = useState(getPlayer2Symbol())
const [color1, setColor1] = useState(getPlayerColor() || theme.player1Str)
const [color2, setColor2] = useState(getPlayer2Color() || theme.player2Str)
const [hasCustomColor1, setHasCustomColor1] = useState(!!getPlayerColor())
const [hasCustomColor2, setHasCustomColor2] = useState(!!getPlayer2Color())
const [drawTower1, setDrawTower1] = useState(getPlayerDrawTower())
const [drawTower2, setDrawTower2] = useState(getPlayer2DrawTower())
const [themeMode, setThemeMode] = useThemeMode()
const [selectorOpen, setSelectorOpen] = useState(false)
const [symbolPickerFor, setSymbolPickerFor] = useState<1 | 2 | null>(null)
const [player1Type, setPlayer1Type] = useState<LocalPlayerType>('human')
const [player2Type, setPlayer2Type] = useState<LocalPlayerType>('bot')
const [botProfile, setBotProfile] = useState<BotProfile>(normalizeBotProfile(setupState?.botProfile))
const [opponentType, setOpponentType] = useState<'streamer' | 'coach' | 'analyst' | 'taunt'>('taunt')
const [tauntStyle, setTauntStyle] = useState<'friendly' | 'funny' | 'lover' | 'serious'>('funny')
const [, setMuteTick] = useState(0)
useEffect(() => {
if (!hasCustomColor1) {
setColor1(theme.player1Str)
}
}, [themeMode, hasCustomColor1])
useEffect(() => {
if (!hasCustomColor2) {
setColor2(theme.player2Str)
}
}, [themeMode, hasCustomColor2])
const swapPlayers = () => {
setName1(name2)
setName2(name1)
setSymbol1(symbol2)
setSymbol2(symbol1)
setColor1(color2)
setColor2(color1)
setPlayer1Type(player2Type)
setPlayer2Type(player1Type)
setHasCustomColor1(hasCustomColor2)
setHasCustomColor2(hasCustomColor1)
setDrawTower1(drawTower2)
setDrawTower2(drawTower1)
}
const startGame = () => {
setPlayerName(name1.trim() || 'Caro 1')
setPlayer2Name(name2.trim() || 'Caro 2')
setPlayer1Symbol(symbol1)
setPlayer2Symbol(symbol2)
setPlayerColor(color1)
setPlayer2Color(color2)
setPlayerDrawTower(drawTower1)
setPlayer2DrawTower(drawTower2)
navigate('/play/local', {
state: {
player1Name: name1.trim() || 'Caro 1',
player2Name: name2.trim() || 'Caro 2',
player1Symbol: symbol1,
player2Symbol: symbol2,
player1Type,
player2Type,
botProfile,
opponentType,
tauntStyle,
rules: rules,
}
})
}
const setupBotVsBot = () => {
setPlayer1Type('bot')
setPlayer2Type('bot')
if (!name1.trim()) setName1('Bot 1')
if (!name2.trim()) setName2('Bot 2')
}
const typeButtonStyle = (active: boolean, accent: string) => ({
padding: '0.35rem 0.7rem',
fontSize: '0.8rem',
cursor: 'pointer',
background: active ? accent : theme.bg,
color: active ? '#fff' : theme.text,
border: `1px solid ${theme.border}`,
borderRadius: 6,
fontWeight: active ? 700 : 500,
minWidth: 60,
minHeight: 36,
})
const profileButtonStyle = (active: boolean) => ({
padding: '0.45rem 0.9rem',
fontSize: '0.85rem',
cursor: 'pointer',
background: active ? theme.player2Str : theme.bg,
color: active ? '#fff' : theme.text,
border: `1px solid ${active ? theme.player2Str : theme.border}`,
borderRadius: 6,
fontWeight: active ? 700 : 500,
minWidth: 92,
minHeight: 40,
})
return (
<div style={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
height: '100dvh',
fontFamily: 'system-ui, -apple-system, sans-serif',
gap: '1.5rem',
padding: '1rem',
background: `linear-gradient(rgba(13, 17, 23, 0.68), rgba(13, 17, 23, 0.78)), url('${binAssetUrl('bg/start-menu-bg.jpg')}') center / cover no-repeat fixed, ${theme.bgStr}`,
color: theme.text,
overflow: 'auto',
}}>
<div style={{ position: 'absolute', top: 12, right: 12, display: 'flex', gap: 6 }}>
<PushButton
label={audioManager.muted ? '🔇' : '🔊'}
onClick={() => { audioManager.toggleMute(); setMuteTick(t => t + 1) }}
title={audioManager.muted ? 'Unmute audio' : 'Mute audio'}
style={pushButtonVars({
front: theme.bgPanel,
text: theme.textMuted,
padding: '0.5rem 0.8rem',
fontSize: '0.85rem',
fontWeight: 500,
radius: '6px',
})}
/>
<PushButton
label={THEMES[themeMode].emoji}
onClick={() => setSelectorOpen(true)}
style={pushButtonVars({
front: theme.bgPanel,
text: theme.textMuted,
padding: '0.5rem 0.8rem',
fontSize: '0.85rem',
fontWeight: 500,
radius: '6px',
})}
/>
</div>
{selectorOpen && (
<ThemeSelector
current={themeMode}
onSelect={setThemeMode}
onClose={() => setSelectorOpen(false)}
/>
)}
<h1 style={{ fontSize: 'clamp(1.5rem, 6vw, 2.2rem)', fontWeight: 800, letterSpacing: '-0.03em', textAlign: 'center' }}>Local Game Setup</h1>
<div style={{ display: 'flex', gap: '2rem', marginTop: '0.5rem', flexWrap: 'wrap', justifyContent: 'center', alignItems: 'center' }}>
{/* Player 1 */}
<div style={{
display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '0.75rem',
background: theme.bgPanel, padding: '1.25rem 1.5rem', borderRadius: 12,
border: `1px solid ${theme.border}`,
}}>
<span style={{ color: color1, fontWeight: 700, fontSize: '1.05rem' }}>Player 1</span>
<input
value={name1}
onChange={e => setName1(e.target.value)}
placeholder="Enter name"
maxLength={24}
style={{
padding: '0.6rem 1rem', fontSize: '1rem', background: theme.bg,
color: theme.text, border: `1px solid ${theme.border}`, borderRadius: 6,
textAlign: 'center', outline: 'none', width: 140
}}
/>
<button
onClick={() => { audioManager.play('click'); setSymbolPickerFor(1) }}
style={{
padding: 0,
cursor: 'pointer', background: 'transparent',
color: '#fff', border: 'none', borderRadius: '50%',
fontWeight: 700, minWidth: 62, minHeight: 66,
display: 'flex', alignItems: 'center', justifyContent: 'center',
transition: 'transform 0.15s ease',
}}
onMouseEnter={(e) => e.currentTarget.style.transform = 'scale(1.05)'}
onMouseLeave={(e) => e.currentTarget.style.transform = 'scale(1)'}
title="Choose Player 1 symbol"
>
<PlayerPiecePreview symbol={symbol1} color={color1} drawTower={drawTower1} />
</button>
{/* Display Tower toggle */}
<label style={{ display: 'flex', alignItems: 'center', gap: 8, cursor: 'pointer', color: theme.text, fontSize: '0.85rem', userSelect: 'none', marginTop: '0.25rem' }}>
<div style={{
width: 32, height: 18, borderRadius: 9, position: 'relative',
background: drawTower1 ? color1 : theme.border,
transition: 'background 0.2s ease',
}}
onClick={() => setDrawTower1(!drawTower1)}
>
<div style={{
width: 14, height: 14, borderRadius: '50%', background: '#fff',
position: 'absolute', top: 2,
left: drawTower1 ? 16 : 2,
transition: 'left 0.2s ease',
}} />
</div>
Display Tower
</label>
{/* Collapsible Drawer for Player 1 details */}
<div style={{
maxHeight: drawTower1 ? '160px' : '0px',
opacity: drawTower1 ? 1 : 0,
overflow: 'hidden',
transition: 'all 0.35s cubic-bezier(0.4, 0, 0.2, 1)',
width: '100%',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
gap: '0.25rem',
}}>
{/* Color selector for Player 1 */}
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '0.25rem', marginTop: '0.5rem' }}>
<span style={{ color: theme.textMuted, fontSize: '0.75rem', fontWeight: 600 }}>COLOR</span>
<div style={{ display: 'flex', gap: 6, flexWrap: 'wrap', justifyContent: 'center', maxWidth: 150 }}>
{getPresetColors(theme.player1Str).map(c => {
const isSelected = color1.toLowerCase() === c.toLowerCase()
return (
<button
key={c}
title={c}
onClick={() => {
audioManager.play('click')
setColor1(c)
setHasCustomColor1(true)
}}
style={{
width: 20,
height: 20,
borderRadius: '50%',
background: c,
border: isSelected ? `2px solid ${theme.text}` : '1px solid rgba(255,255,255,0.15)',
cursor: 'pointer',
boxShadow: '0 1px 4px rgba(0,0,0,0.15)',
padding: 0,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
transition: 'transform 0.1s ease',
}}
onMouseEnter={(e) => e.currentTarget.style.transform = 'scale(1.2)'}
onMouseLeave={(e) => e.currentTarget.style.transform = 'scale(1)'}
>
{isSelected && (
<span style={{ color: '#fff', fontSize: '0.65rem', textShadow: '0 0.5px 1px rgba(0,0,0,0.5)' }}></span>
)}
</button>
)
})}
{/* Native color picker circle */}
<div style={{ position: 'relative', width: 20, height: 20 }}>
<input
type="color"
value={color1}
onChange={(e) => {
setColor1(e.target.value)
setHasCustomColor1(true)
}}
style={{
opacity: 0,
position: 'absolute',
inset: 0,
width: '100%',
height: '100%',
cursor: 'pointer',
zIndex: 2,
}}
/>
<div style={{
width: 20,
height: 20,
borderRadius: '50%',
background: 'linear-gradient(45deg, red, orange, yellow, green, blue, purple)',
border: '1px solid rgba(255,255,255,0.15)',
boxShadow: '0 1px 4px rgba(0,0,0,0.15)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontSize: '0.65rem',
color: '#fff',
fontWeight: 'bold',
textShadow: '0 1px 1px rgba(0,0,0,0.5)',
}}>
+
</div>
</div>
</div>
</div>
</div>
<div style={{ display: 'flex', gap: 6, marginTop: '0.25rem' }}>
<button
onClick={() => { audioManager.play('click'); setPlayer1Type('human') }}
style={typeButtonStyle(player1Type === 'human', color1)}
>
Human
</button>
<button
onClick={() => { audioManager.play('click'); setPlayer1Type('bot') }}
style={typeButtonStyle(player1Type === 'bot', color1)}
>
Bot
</button>
</div>
</div>
{/* Swap button in between P1 and P2 */}
<button
onClick={() => { audioManager.play('click'); swapPlayers() }}
title="Swap Players"
style={{
background: 'rgba(185, 255, 195, 0.38)',
color: theme.text,
border: `1px solid ${theme.border}`,
borderRadius: '50%',
width: 44,
height: 44,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
cursor: 'pointer',
fontSize: '1.25rem',
boxShadow: '0 4px 12px rgba(0,0,0,0.15)',
transition: 'transform 0.2s ease, border-color 0.2s',
zIndex: 2,
margin: '0.5rem 0',
}}
onMouseEnter={(e) => {
e.currentTarget.style.transform = 'scale(1.1) rotate(180deg)';
e.currentTarget.style.borderColor = color1;
}}
onMouseLeave={(e) => {
e.currentTarget.style.transform = 'scale(1) rotate(0deg)';
e.currentTarget.style.borderColor = theme.border;
}}
>
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" style={{ display: 'block' }}>
<path d="M20 7H7.5M20 7L15.75 3M20 7L15.75 11" stroke="currentColor" stroke-width="2.25" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M4 17H16.5M4 17L8.25 13M4 17L8.25 21" stroke="currentColor" stroke-width="2.25" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</button>
{/* Player 2 */}
<div style={{
display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '0.75rem',
background: theme.bgPanel, padding: '1.25rem 1.5rem', borderRadius: 12,
border: `1px solid ${theme.border}`,
}}>
<span style={{ color: color2, fontWeight: 700, fontSize: '1.05rem' }}>Player 2</span>
<input
value={name2}
onChange={e => setName2(e.target.value)}
placeholder="Enter name"
maxLength={24}
style={{
padding: '0.6rem 1rem', fontSize: '1rem', background: theme.bg,
color: theme.text, border: `1px solid ${theme.border}`, borderRadius: 6,
textAlign: 'center', outline: 'none', width: 140
}}
/>
<button
onClick={() => { audioManager.play('click'); setSymbolPickerFor(2) }}
style={{
padding: 0,
cursor: 'pointer', background: 'transparent',
color: '#fff', border: 'none', borderRadius: '50%',
fontWeight: 700, minWidth: 62, minHeight: 66,
display: 'flex', alignItems: 'center', justifyContent: 'center',
transition: 'transform 0.15s ease',
}}
onMouseEnter={(e) => e.currentTarget.style.transform = 'scale(1.05)'}
onMouseLeave={(e) => e.currentTarget.style.transform = 'scale(1)'}
title="Choose Player 2 symbol"
>
<PlayerPiecePreview symbol={symbol2} color={color2} drawTower={drawTower2} />
</button>
{/* Display Tower toggle */}
<label style={{ display: 'flex', alignItems: 'center', gap: 8, cursor: 'pointer', color: theme.text, fontSize: '0.85rem', userSelect: 'none', marginTop: '0.25rem' }}>
<div style={{
width: 32, height: 18, borderRadius: 9, position: 'relative',
background: drawTower2 ? color2 : theme.border,
transition: 'background 0.2s ease',
}}
onClick={() => setDrawTower2(!drawTower2)}
>
<div style={{
width: 14, height: 14, borderRadius: '50%', background: '#fff',
position: 'absolute', top: 2,
left: drawTower2 ? 16 : 2,
transition: 'left 0.2s ease',
}} />
</div>
Display Tower
</label>
{/* Collapsible Drawer for Player 2 details */}
<div style={{
maxHeight: drawTower2 ? '160px' : '0px',
opacity: drawTower2 ? 1 : 0,
overflow: 'hidden',
transition: 'all 0.35s cubic-bezier(0.4, 0, 0.2, 1)',
width: '100%',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
gap: '0.25rem',
}}>
{/* Color selector for Player 2 */}
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '0.25rem', marginTop: '0.5rem' }}>
<span style={{ color: theme.textMuted, fontSize: '0.75rem', fontWeight: 600 }}>COLOR</span>
<div style={{ display: 'flex', gap: 6, flexWrap: 'wrap', justifyContent: 'center', maxWidth: 150 }}>
{getPresetColors(theme.player2Str).map(c => {
const isSelected = color2.toLowerCase() === c.toLowerCase()
return (
<button
key={c}
title={c}
onClick={() => {
audioManager.play('click')
setColor2(c)
setHasCustomColor2(true)
}}
style={{
width: 20,
height: 20,
borderRadius: '50%',
background: c,
border: isSelected ? `2px solid ${theme.text}` : '1px solid rgba(255,255,255,0.15)',
cursor: 'pointer',
boxShadow: '0 1px 4px rgba(0,0,0,0.15)',
padding: 0,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
transition: 'transform 0.1s ease',
}}
onMouseEnter={(e) => e.currentTarget.style.transform = 'scale(1.2)'}
onMouseLeave={(e) => e.currentTarget.style.transform = 'scale(1)'}
>
{isSelected && (
<span style={{ color: '#fff', fontSize: '0.65rem', textShadow: '0 0.5px 1px rgba(0,0,0,0.5)' }}></span>
)}
</button>
)
})}
{/* Native color picker circle */}
<div style={{ position: 'relative', width: 20, height: 20 }}>
<input
type="color"
value={color2}
onChange={(e) => {
setColor2(e.target.value)
setHasCustomColor2(true)
}}
style={{
opacity: 0,
position: 'absolute',
inset: 0,
width: '100%',
height: '100%',
cursor: 'pointer',
zIndex: 2,
}}
/>
<div style={{
width: 20,
height: 20,
borderRadius: '50%',
background: 'linear-gradient(45deg, red, orange, yellow, green, blue, purple)',
border: '1px solid rgba(255,255,255,0.15)',
boxShadow: '0 1px 4px rgba(0,0,0,0.15)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontSize: '0.65rem',
color: '#fff',
fontWeight: 'bold',
textShadow: '0 1px 1px rgba(0,0,0,0.5)',
}}>
+
</div>
</div>
</div>
</div>
</div>
<div style={{ display: 'flex', gap: 6, marginTop: '0.25rem' }}>
<button
onClick={() => { audioManager.play('click'); setPlayer2Type('human') }}
style={typeButtonStyle(player2Type === 'human', color2)}
>
Human
</button>
<button
onClick={() => { audioManager.play('click'); setPlayer2Type('bot') }}
style={typeButtonStyle(player2Type === 'bot', color2)}
>
Bot
</button>
</div>
</div>
</div>
{/* Readonly Rules Summary */}
<div style={{
display: 'flex', flexDirection: 'column', gap: '0.4rem', alignItems: 'center',
background: theme.bgPanel, padding: '0.75rem 1.5rem', borderRadius: 10,
border: `1px solid ${theme.border}`, fontSize: '0.85rem', color: theme.textMuted,
textAlign: 'center', maxWidth: 320,
}}>
<strong style={{ color: theme.text, fontSize: '0.9rem', letterSpacing: '0.05em' }}>GAME RULES</strong>
<div>
<span>{rules.swap2 ? 'Swap2 Enabled' : 'No Swap2'}</span>
<span> · </span>
<span>{rules.noOverlines ? 'No overline (6+)' : 'Overline wins'}</span>
<span> · </span>
<span>Board {rules.boardSize}×{rules.boardSize}</span>
</div>
</div>
{(player1Type === 'bot' || player2Type === 'bot') && (
<div style={{
display: 'flex',
flexDirection: 'column',
gap: '1.25rem',
alignItems: 'center',
background: theme.bgPanel,
padding: '1.5rem',
borderRadius: 16,
border: `1px solid ${theme.border}`,
boxShadow: '0 8px 32px rgba(0,0,0,0.15)',
maxWidth: 400,
width: '100%',
}}>
{/* Difficulty/Profile */}
<div style={{ display: 'flex', flexDirection: 'column', gap: '0.5rem', alignItems: 'center', width: '100%' }}>
<strong style={{ color: theme.text, fontSize: '0.9rem', letterSpacing: '0.05em', textTransform: 'uppercase' }}>Difficulty</strong>
<div style={{ display: 'flex', gap: 8, flexWrap: 'wrap', justifyContent: 'center' }}>
<button
onClick={() => { audioManager.play('click'); setBotProfile('normal') }}
style={profileButtonStyle(botProfile === 'normal')}
>
Normal
</button>
<button
onClick={() => { audioManager.play('click'); setBotProfile('expert') }}
style={profileButtonStyle(botProfile === 'expert')}
>
Expert
</button>
<button
onClick={() => { audioManager.play('click'); setBotProfile('ai') }}
style={profileButtonStyle(botProfile === 'ai')}
>
AI
</button>
<button
onClick={() => { audioManager.play('click'); setBotProfile('ai+') }}
style={profileButtonStyle(botProfile === 'ai+')}
>
AI+
</button>
</div>
</div>
{/* Opponent Type */}
<div style={{ display: 'flex', flexDirection: 'column', gap: '0.5rem', alignItems: 'center', width: '100%' }}>
<strong style={{ color: theme.text, fontSize: '0.9rem', letterSpacing: '0.05em', textTransform: 'uppercase' }}>Opponent Type</strong>
<div style={{ display: 'flex', gap: 8, flexWrap: 'wrap', justifyContent: 'center' }}>
{(['streamer', 'coach', 'analyst', 'taunt'] as const).map(t => {
const isActive = opponentType === t
return (
<button
key={t}
onClick={() => { audioManager.play('click'); setOpponentType(t) }}
style={{
padding: '0.45rem 0.9rem',
fontSize: '0.85rem',
cursor: 'pointer',
background: isActive ? theme.player1Str : theme.bg,
color: isActive ? '#fff' : theme.text,
border: `1px solid ${isActive ? theme.player1Str : theme.border}`,
borderRadius: 6,
fontWeight: isActive ? 700 : 500,
minWidth: 80,
minHeight: 40,
textTransform: 'capitalize',
}}
>
{t}
</button>
)
})}
</div>
</div>
{/* Taunt Style / Personality (Conditional) */}
{opponentType === 'taunt' && (
<div style={{
display: 'flex',
flexDirection: 'column',
gap: '0.5rem',
alignItems: 'center',
width: '100%',
}}>
<strong style={{ color: theme.text, fontSize: '0.9rem', letterSpacing: '0.05em', textTransform: 'uppercase' }}>Personality</strong>
<div style={{ display: 'flex', gap: 8, flexWrap: 'wrap', justifyContent: 'center' }}>
{(['friendly', 'funny', 'lover', 'serious'] as const).map(s => {
const isActive = tauntStyle === s
return (
<button
key={s}
onClick={() => { audioManager.play('click'); setTauntStyle(s) }}
style={{
padding: '0.45rem 0.9rem',
fontSize: '0.85rem',
cursor: 'pointer',
background: isActive ? theme.player2Str : theme.bg,
color: isActive ? '#fff' : theme.text,
border: `1px solid ${isActive ? theme.player2Str : theme.border}`,
borderRadius: 6,
fontWeight: isActive ? 700 : 500,
minWidth: 80,
minHeight: 40,
textTransform: 'capitalize',
}}
>
{s}
</button>
)
})}
</div>
</div>
)}
</div>
)}
<div style={{ display: 'flex', gap: '0.75rem', flexWrap: 'wrap', justifyContent: 'center' }}>
<PushButton
label="Bot vs Bot"
onClick={setupBotVsBot}
style={pushButtonVars({
front: theme.bgPanel,
text: theme.text,
padding: '0.7rem 1.4rem',
fontSize: '1rem',
fontWeight: 600,
minWidth: '150px',
radius: '8px',
})}
/>
<PushButton
label="Start Game"
onClick={startGame}
style={pushButtonVars({
front: theme.player2Str,
padding: '0.75rem 2rem',
fontSize: '1.05rem',
fontWeight: 600,
minWidth: '180px',
radius: '8px',
})}
/>
</div>
<PushButton
label="← Back to menu"
onClick={() => navigate('/')}
style={pushButtonVars({
front: theme.bgPanel,
text: theme.textMuted,
padding: '0.5rem 1rem',
fontSize: '0.9rem',
fontWeight: 500,
radius: '8px',
})}
/>
{symbolPickerFor && (
<SymbolPicker
current={symbolPickerFor === 1 ? symbol1 : symbol2}
onSelect={(s) => {
if (symbolPickerFor === 1) setSymbol1(s)
else setSymbol2(s)
setSymbolPickerFor(null)
}}
onClose={() => setSymbolPickerFor(null)}
/>
)}
</div>
)
}