feat: UI polish for Sprint mode (viewport, backgrounds, data attributes)
Previous session improvements: - Add data-component attributes to all major components for debugging - Fix viewport issues: set overflow:hidden, maxHeight:100vh to prevent scrolling - Make backgrounds transparent for sprint mode (sky gradient at root level) - Adjust padding for sprint mode (8px vs 20px) - Redesign race configuration screen for responsive mobile-first layout - Rebuild pressure gauge with proper 180° semicircular arc - Make complement equation more prominent (96px font) - Enlarge station names and icons for better visibility - Fix viewport clipping issues with pressure gauge and question display 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
159990489f
commit
90ad789ff1
|
|
@ -11,13 +11,14 @@ export function ComplementRaceGame() {
|
|||
const { state } = useComplementRace()
|
||||
|
||||
return (
|
||||
<div style={{
|
||||
<div data-component="game-page-root" style={{
|
||||
flex: 1,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
overflow: 'auto',
|
||||
padding: '20px 8px',
|
||||
overflow: 'hidden',
|
||||
padding: state.style === 'sprint' ? '8px' : '20px 8px',
|
||||
minHeight: '100vh',
|
||||
maxHeight: '100vh',
|
||||
background: state.style === 'sprint'
|
||||
? 'linear-gradient(to bottom, #2563eb 0%, #60a5fa 100%)'
|
||||
: 'radial-gradient(ellipse at center, #8db978 0%, #7ba565 40%, #6a9354 100%)',
|
||||
|
|
|
|||
|
|
@ -24,249 +24,284 @@ export function GameControls() {
|
|||
|
||||
return (
|
||||
<div style={{
|
||||
textAlign: 'center',
|
||||
padding: '40px 20px',
|
||||
maxWidth: '800px',
|
||||
margin: '20px auto 0'
|
||||
height: '100%',
|
||||
overflowY: 'auto',
|
||||
display: 'flex',
|
||||
flexDirection: 'column'
|
||||
}}>
|
||||
<h2 style={{
|
||||
fontSize: '36px',
|
||||
fontWeight: 'bold',
|
||||
marginBottom: '32px',
|
||||
color: '#1f2937'
|
||||
}}>
|
||||
Race Configuration
|
||||
</h2>
|
||||
|
||||
{/* Number Mode Selection */}
|
||||
<div style={{
|
||||
background: 'white',
|
||||
borderRadius: '16px',
|
||||
padding: '24px',
|
||||
marginBottom: '24px',
|
||||
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.1)',
|
||||
textAlign: 'left'
|
||||
textAlign: 'center',
|
||||
padding: '20px',
|
||||
maxWidth: '1200px',
|
||||
margin: '0 auto',
|
||||
width: '100%'
|
||||
}}>
|
||||
<h3 style={{
|
||||
fontSize: '18px',
|
||||
<h2 style={{
|
||||
fontSize: '28px',
|
||||
fontWeight: 'bold',
|
||||
marginBottom: '12px',
|
||||
marginBottom: '20px',
|
||||
color: '#1f2937'
|
||||
}}>
|
||||
Number Mode
|
||||
</h3>
|
||||
Race Configuration
|
||||
</h2>
|
||||
|
||||
{/* Grid container for all sections on wider screens */}
|
||||
<div style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'repeat(auto-fit, minmax(150px, 1fr))',
|
||||
gap: '12px'
|
||||
gridTemplateColumns: 'repeat(auto-fit, minmax(300px, 1fr))',
|
||||
gap: '16px',
|
||||
marginBottom: '20px'
|
||||
}}>
|
||||
<button
|
||||
onClick={() => handleModeSelect('friends5')}
|
||||
style={{
|
||||
padding: '16px',
|
||||
borderRadius: '8px',
|
||||
border: '2px solid',
|
||||
borderColor: state.mode === 'friends5' ? '#3b82f6' : '#e5e7eb',
|
||||
background: state.mode === 'friends5' ? '#eff6ff' : 'white',
|
||||
color: state.mode === 'friends5' ? '#1e40af' : '#6b7280',
|
||||
{/* Number Mode Selection */}
|
||||
<div style={{
|
||||
background: 'white',
|
||||
borderRadius: '12px',
|
||||
padding: '16px',
|
||||
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)',
|
||||
textAlign: 'left'
|
||||
}}>
|
||||
<h3 style={{
|
||||
fontSize: '16px',
|
||||
fontWeight: 'bold',
|
||||
cursor: 'pointer',
|
||||
transition: 'all 0.2s ease'
|
||||
}}
|
||||
>
|
||||
<div style={{ fontSize: '24px', marginBottom: '4px' }}>5️⃣</div>
|
||||
<div>Friends of 5</div>
|
||||
</button>
|
||||
marginBottom: '12px',
|
||||
color: '#1f2937'
|
||||
}}>
|
||||
Number Mode
|
||||
</h3>
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '8px'
|
||||
}}>
|
||||
<button
|
||||
onClick={() => handleModeSelect('friends5')}
|
||||
style={{
|
||||
padding: '12px',
|
||||
borderRadius: '8px',
|
||||
border: '2px solid',
|
||||
borderColor: state.mode === 'friends5' ? '#3b82f6' : '#e5e7eb',
|
||||
background: state.mode === 'friends5' ? '#eff6ff' : 'white',
|
||||
color: state.mode === 'friends5' ? '#1e40af' : '#6b7280',
|
||||
fontWeight: 'bold',
|
||||
cursor: 'pointer',
|
||||
transition: 'all 0.2s ease',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '8px'
|
||||
}}
|
||||
>
|
||||
<span style={{ fontSize: '20px' }}>5️⃣</span>
|
||||
<span>Friends of 5</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={() => handleModeSelect('friends10')}
|
||||
style={{
|
||||
padding: '16px',
|
||||
borderRadius: '8px',
|
||||
border: '2px solid',
|
||||
borderColor: state.mode === 'friends10' ? '#3b82f6' : '#e5e7eb',
|
||||
background: state.mode === 'friends10' ? '#eff6ff' : 'white',
|
||||
color: state.mode === 'friends10' ? '#1e40af' : '#6b7280',
|
||||
fontWeight: 'bold',
|
||||
cursor: 'pointer',
|
||||
transition: 'all 0.2s ease'
|
||||
}}
|
||||
>
|
||||
<div style={{ fontSize: '24px', marginBottom: '4px' }}>🔟</div>
|
||||
<div>Friends of 10</div>
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleModeSelect('friends10')}
|
||||
style={{
|
||||
padding: '12px',
|
||||
borderRadius: '8px',
|
||||
border: '2px solid',
|
||||
borderColor: state.mode === 'friends10' ? '#3b82f6' : '#e5e7eb',
|
||||
background: state.mode === 'friends10' ? '#eff6ff' : 'white',
|
||||
color: state.mode === 'friends10' ? '#1e40af' : '#6b7280',
|
||||
fontWeight: 'bold',
|
||||
cursor: 'pointer',
|
||||
transition: 'all 0.2s ease',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '8px'
|
||||
}}
|
||||
>
|
||||
<span style={{ fontSize: '20px' }}>🔟</span>
|
||||
<span>Friends of 10</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={() => handleModeSelect('mixed')}
|
||||
style={{
|
||||
padding: '16px',
|
||||
borderRadius: '8px',
|
||||
border: '2px solid',
|
||||
borderColor: state.mode === 'mixed' ? '#3b82f6' : '#e5e7eb',
|
||||
background: state.mode === 'mixed' ? '#eff6ff' : 'white',
|
||||
color: state.mode === 'mixed' ? '#1e40af' : '#6b7280',
|
||||
<button
|
||||
onClick={() => handleModeSelect('mixed')}
|
||||
style={{
|
||||
padding: '12px',
|
||||
borderRadius: '8px',
|
||||
border: '2px solid',
|
||||
borderColor: state.mode === 'mixed' ? '#3b82f6' : '#e5e7eb',
|
||||
background: state.mode === 'mixed' ? '#eff6ff' : 'white',
|
||||
color: state.mode === 'mixed' ? '#1e40af' : '#6b7280',
|
||||
fontWeight: 'bold',
|
||||
cursor: 'pointer',
|
||||
transition: 'all 0.2s ease',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '8px'
|
||||
}}
|
||||
>
|
||||
<span style={{ fontSize: '20px' }}>🎲</span>
|
||||
<span>Mixed</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Game Style Selection */}
|
||||
<div style={{
|
||||
background: 'white',
|
||||
borderRadius: '12px',
|
||||
padding: '16px',
|
||||
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)',
|
||||
textAlign: 'left'
|
||||
}}>
|
||||
<h3 style={{
|
||||
fontSize: '16px',
|
||||
fontWeight: 'bold',
|
||||
cursor: 'pointer',
|
||||
transition: 'all 0.2s ease'
|
||||
}}
|
||||
>
|
||||
<div style={{ fontSize: '24px', marginBottom: '4px' }}>🎲</div>
|
||||
<div>Mixed</div>
|
||||
</button>
|
||||
marginBottom: '12px',
|
||||
color: '#1f2937'
|
||||
}}>
|
||||
Race Type
|
||||
</h3>
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '8px'
|
||||
}}>
|
||||
<button
|
||||
onClick={() => handleStyleSelect('practice')}
|
||||
style={{
|
||||
padding: '12px',
|
||||
borderRadius: '8px',
|
||||
border: '2px solid',
|
||||
borderColor: state.style === 'practice' ? '#10b981' : '#e5e7eb',
|
||||
background: state.style === 'practice' ? '#d1fae5' : 'white',
|
||||
color: state.style === 'practice' ? '#047857' : '#6b7280',
|
||||
fontWeight: 'bold',
|
||||
cursor: 'pointer',
|
||||
transition: 'all 0.2s ease',
|
||||
textAlign: 'left'
|
||||
}}
|
||||
>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '8px', marginBottom: '4px' }}>
|
||||
<span style={{ fontSize: '20px' }}>🤖</span>
|
||||
<span>Robot Showdown</span>
|
||||
</div>
|
||||
<div style={{ fontSize: '11px', opacity: 0.8 }}>Race AI on track</div>
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={() => handleStyleSelect('sprint')}
|
||||
style={{
|
||||
padding: '12px',
|
||||
borderRadius: '8px',
|
||||
border: '2px solid',
|
||||
borderColor: state.style === 'sprint' ? '#f59e0b' : '#e5e7eb',
|
||||
background: state.style === 'sprint' ? '#fef3c7' : 'white',
|
||||
color: state.style === 'sprint' ? '#d97706' : '#6b7280',
|
||||
fontWeight: 'bold',
|
||||
cursor: 'pointer',
|
||||
transition: 'all 0.2s ease',
|
||||
textAlign: 'left'
|
||||
}}
|
||||
>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '8px', marginBottom: '4px' }}>
|
||||
<span style={{ fontSize: '20px' }}>🚂</span>
|
||||
<span>Steam Sprint</span>
|
||||
</div>
|
||||
<div style={{ fontSize: '11px', opacity: 0.8 }}>60-second journey</div>
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={() => handleStyleSelect('survival')}
|
||||
style={{
|
||||
padding: '12px',
|
||||
borderRadius: '8px',
|
||||
border: '2px solid',
|
||||
borderColor: state.style === 'survival' ? '#ef4444' : '#e5e7eb',
|
||||
background: state.style === 'survival' ? '#fee2e2' : 'white',
|
||||
color: state.style === 'survival' ? '#dc2626' : '#6b7280',
|
||||
fontWeight: 'bold',
|
||||
cursor: 'pointer',
|
||||
transition: 'all 0.2s ease',
|
||||
textAlign: 'left'
|
||||
}}
|
||||
>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '8px', marginBottom: '4px' }}>
|
||||
<span style={{ fontSize: '20px' }}>🔄</span>
|
||||
<span>Endless Circuit</span>
|
||||
</div>
|
||||
<div style={{ fontSize: '11px', opacity: 0.8 }}>Infinite laps</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Game Style Selection */}
|
||||
<div style={{
|
||||
background: 'white',
|
||||
borderRadius: '16px',
|
||||
padding: '24px',
|
||||
marginBottom: '24px',
|
||||
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.1)',
|
||||
textAlign: 'left'
|
||||
}}>
|
||||
<h3 style={{
|
||||
fontSize: '18px',
|
||||
fontWeight: 'bold',
|
||||
marginBottom: '12px',
|
||||
color: '#1f2937'
|
||||
}}>
|
||||
Race Type
|
||||
</h3>
|
||||
{/* Timeout Setting Selection - Full width below */}
|
||||
<div style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'repeat(auto-fit, minmax(150px, 1fr))',
|
||||
gap: '12px'
|
||||
}}>
|
||||
<button
|
||||
onClick={() => handleStyleSelect('practice')}
|
||||
style={{
|
||||
padding: '16px',
|
||||
borderRadius: '8px',
|
||||
border: '2px solid',
|
||||
borderColor: state.style === 'practice' ? '#10b981' : '#e5e7eb',
|
||||
background: state.style === 'practice' ? '#d1fae5' : 'white',
|
||||
color: state.style === 'practice' ? '#047857' : '#6b7280',
|
||||
fontWeight: 'bold',
|
||||
cursor: 'pointer',
|
||||
transition: 'all 0.2s ease'
|
||||
}}
|
||||
>
|
||||
<div style={{ fontSize: '24px', marginBottom: '4px' }}>🤖</div>
|
||||
<div>Robot Showdown</div>
|
||||
<div style={{ fontSize: '12px', opacity: 0.8 }}>Race AI on track</div>
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={() => handleStyleSelect('sprint')}
|
||||
style={{
|
||||
padding: '16px',
|
||||
borderRadius: '8px',
|
||||
border: '2px solid',
|
||||
borderColor: state.style === 'sprint' ? '#f59e0b' : '#e5e7eb',
|
||||
background: state.style === 'sprint' ? '#fef3c7' : 'white',
|
||||
color: state.style === 'sprint' ? '#d97706' : '#6b7280',
|
||||
fontWeight: 'bold',
|
||||
cursor: 'pointer',
|
||||
transition: 'all 0.2s ease'
|
||||
}}
|
||||
>
|
||||
<div style={{ fontSize: '24px', marginBottom: '4px' }}>🚂</div>
|
||||
<div>Steam Sprint</div>
|
||||
<div style={{ fontSize: '12px', opacity: 0.8 }}>60-second journey</div>
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={() => handleStyleSelect('survival')}
|
||||
style={{
|
||||
padding: '16px',
|
||||
borderRadius: '8px',
|
||||
border: '2px solid',
|
||||
borderColor: state.style === 'survival' ? '#ef4444' : '#e5e7eb',
|
||||
background: state.style === 'survival' ? '#fee2e2' : 'white',
|
||||
color: state.style === 'survival' ? '#dc2626' : '#6b7280',
|
||||
fontWeight: 'bold',
|
||||
cursor: 'pointer',
|
||||
transition: 'all 0.2s ease'
|
||||
}}
|
||||
>
|
||||
<div style={{ fontSize: '24px', marginBottom: '4px' }}>🔄</div>
|
||||
<div>Endless Circuit</div>
|
||||
<div style={{ fontSize: '12px', opacity: 0.8 }}>Infinite laps</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Timeout Setting Selection */}
|
||||
<div style={{
|
||||
background: 'white',
|
||||
borderRadius: '16px',
|
||||
padding: '24px',
|
||||
marginBottom: '32px',
|
||||
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.1)',
|
||||
textAlign: 'left'
|
||||
}}>
|
||||
<h3 style={{
|
||||
fontSize: '18px',
|
||||
fontWeight: 'bold',
|
||||
marginBottom: '12px',
|
||||
color: '#1f2937'
|
||||
}}>
|
||||
Difficulty Level
|
||||
</h3>
|
||||
<div style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'repeat(auto-fit, minmax(100px, 1fr))',
|
||||
gap: '8px'
|
||||
}}>
|
||||
{(['preschool', 'kindergarten', 'relaxed', 'slow', 'normal', 'fast', 'expert'] as TimeoutSetting[]).map((timeout) => (
|
||||
<button
|
||||
key={timeout}
|
||||
onClick={() => handleTimeoutSelect(timeout)}
|
||||
style={{
|
||||
padding: '12px 8px',
|
||||
borderRadius: '8px',
|
||||
border: '2px solid',
|
||||
borderColor: state.timeoutSetting === timeout ? '#8b5cf6' : '#e5e7eb',
|
||||
background: state.timeoutSetting === timeout ? '#f3e8ff' : 'white',
|
||||
color: state.timeoutSetting === timeout ? '#6b21a8' : '#6b7280',
|
||||
fontWeight: 'bold',
|
||||
cursor: 'pointer',
|
||||
transition: 'all 0.2s ease',
|
||||
fontSize: '14px'
|
||||
}}
|
||||
>
|
||||
{timeout.charAt(0).toUpperCase() + timeout.slice(1)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={handleStartRace}
|
||||
style={{
|
||||
background: 'linear-gradient(135deg, #3b82f6, #8b5cf6)',
|
||||
color: 'white',
|
||||
border: 'none',
|
||||
background: 'white',
|
||||
borderRadius: '12px',
|
||||
padding: '16px 48px',
|
||||
fontSize: '20px',
|
||||
fontWeight: 'bold',
|
||||
cursor: 'pointer',
|
||||
boxShadow: '0 4px 12px rgba(59, 130, 246, 0.3)',
|
||||
transition: 'all 0.2s ease'
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
e.currentTarget.style.transform = 'translateY(-2px)'
|
||||
e.currentTarget.style.boxShadow = '0 6px 16px rgba(59, 130, 246, 0.4)'
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
e.currentTarget.style.transform = 'translateY(0)'
|
||||
e.currentTarget.style.boxShadow = '0 4px 12px rgba(59, 130, 246, 0.3)'
|
||||
}}
|
||||
>
|
||||
Begin Race!
|
||||
</button>
|
||||
padding: '16px',
|
||||
marginBottom: '16px',
|
||||
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)',
|
||||
textAlign: 'left'
|
||||
}}>
|
||||
<h3 style={{
|
||||
fontSize: '16px',
|
||||
fontWeight: 'bold',
|
||||
marginBottom: '12px',
|
||||
color: '#1f2937'
|
||||
}}>
|
||||
Difficulty Level
|
||||
</h3>
|
||||
<div style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'repeat(auto-fit, minmax(100px, 1fr))',
|
||||
gap: '8px'
|
||||
}}>
|
||||
{(['preschool', 'kindergarten', 'relaxed', 'slow', 'normal', 'fast', 'expert'] as TimeoutSetting[]).map((timeout) => (
|
||||
<button
|
||||
key={timeout}
|
||||
onClick={() => handleTimeoutSelect(timeout)}
|
||||
style={{
|
||||
padding: '10px 8px',
|
||||
borderRadius: '8px',
|
||||
border: '2px solid',
|
||||
borderColor: state.timeoutSetting === timeout ? '#8b5cf6' : '#e5e7eb',
|
||||
background: state.timeoutSetting === timeout ? '#f3e8ff' : 'white',
|
||||
color: state.timeoutSetting === timeout ? '#6b21a8' : '#6b7280',
|
||||
fontWeight: 'bold',
|
||||
cursor: 'pointer',
|
||||
transition: 'all 0.2s ease',
|
||||
fontSize: '13px'
|
||||
}}
|
||||
>
|
||||
{timeout.charAt(0).toUpperCase() + timeout.slice(1)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={handleStartRace}
|
||||
style={{
|
||||
background: 'linear-gradient(135deg, #3b82f6, #8b5cf6)',
|
||||
color: 'white',
|
||||
border: 'none',
|
||||
borderRadius: '12px',
|
||||
padding: '14px 48px',
|
||||
fontSize: '18px',
|
||||
fontWeight: 'bold',
|
||||
cursor: 'pointer',
|
||||
boxShadow: '0 4px 12px rgba(59, 130, 246, 0.3)',
|
||||
transition: 'all 0.2s ease',
|
||||
width: '100%',
|
||||
maxWidth: '300px',
|
||||
margin: '0 auto'
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
e.currentTarget.style.transform = 'translateY(-2px)'
|
||||
e.currentTarget.style.boxShadow = '0 6px 16px rgba(59, 130, 246, 0.4)'
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
e.currentTarget.style.transform = 'translateY(0)'
|
||||
e.currentTarget.style.boxShadow = '0 4px 12px rgba(59, 130, 246, 0.3)'
|
||||
}}
|
||||
>
|
||||
Begin Race!
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -125,7 +125,7 @@ export function GameDisplay() {
|
|||
if (!state.currentQuestion) return null
|
||||
|
||||
return (
|
||||
<div style={{
|
||||
<div data-component="game-display" style={{
|
||||
flex: 1,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
|
|
@ -133,7 +133,7 @@ export function GameDisplay() {
|
|||
}}>
|
||||
{/* Adaptive Feedback */}
|
||||
{state.adaptiveFeedback && (
|
||||
<div style={{
|
||||
<div data-component="adaptive-feedback" style={{
|
||||
position: 'fixed',
|
||||
top: '80px',
|
||||
left: '50%',
|
||||
|
|
@ -154,55 +154,60 @@ export function GameDisplay() {
|
|||
</div>
|
||||
)}
|
||||
|
||||
{/* Stats Header - constrained width */}
|
||||
<div style={{
|
||||
maxWidth: '1200px',
|
||||
margin: '0 auto',
|
||||
width: '100%',
|
||||
padding: '0 20px',
|
||||
marginTop: '10px'
|
||||
}}>
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-around',
|
||||
marginBottom: '10px',
|
||||
background: 'white',
|
||||
borderRadius: '12px',
|
||||
padding: '10px',
|
||||
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)'
|
||||
{/* Stats Header - constrained width, hidden for sprint mode */}
|
||||
{state.style !== 'sprint' && (
|
||||
<div data-component="stats-container" style={{
|
||||
maxWidth: '1200px',
|
||||
margin: '0 auto',
|
||||
width: '100%',
|
||||
padding: '0 20px',
|
||||
marginTop: '10px'
|
||||
}}>
|
||||
<div style={{ textAlign: 'center' }}>
|
||||
<div data-component="stats-header" style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-around',
|
||||
marginBottom: '10px',
|
||||
background: 'white',
|
||||
borderRadius: '12px',
|
||||
padding: '10px',
|
||||
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)'
|
||||
}}>
|
||||
<div data-stat="score" style={{ textAlign: 'center' }}>
|
||||
<div style={{ color: '#6b7280', fontSize: '14px', marginBottom: '4px' }}>Score</div>
|
||||
<div style={{ fontWeight: 'bold', fontSize: '24px', color: '#3b82f6' }}>
|
||||
{state.score}
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ textAlign: 'center' }}>
|
||||
<div data-stat="streak" style={{ textAlign: 'center' }}>
|
||||
<div style={{ color: '#6b7280', fontSize: '14px', marginBottom: '4px' }}>Streak</div>
|
||||
<div style={{ fontWeight: 'bold', fontSize: '24px', color: '#10b981' }}>
|
||||
{state.streak} 🔥
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ textAlign: 'center' }}>
|
||||
<div data-stat="progress" style={{ textAlign: 'center' }}>
|
||||
<div style={{ color: '#6b7280', fontSize: '14px', marginBottom: '4px' }}>Progress</div>
|
||||
<div style={{ fontWeight: 'bold', fontSize: '24px', color: '#f59e0b' }}>
|
||||
{state.correctAnswers}/{state.raceGoal}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Race Track - full width, break out of padding */}
|
||||
<div style={{
|
||||
<div data-component="track-container" style={{
|
||||
width: '100vw',
|
||||
position: 'relative',
|
||||
left: '50%',
|
||||
right: '50%',
|
||||
marginLeft: '-50vw',
|
||||
marginRight: '-50vw',
|
||||
padding: '0 20px',
|
||||
padding: state.style === 'sprint' ? '0 8px' : '0 20px',
|
||||
display: 'flex',
|
||||
justifyContent: 'center'
|
||||
justifyContent: 'center',
|
||||
background: 'transparent',
|
||||
flex: state.style === 'sprint' ? 1 : 'initial',
|
||||
minHeight: state.style === 'sprint' ? 0 : 'initial'
|
||||
}}>
|
||||
{state.style === 'survival' ? (
|
||||
<CircularTrack
|
||||
|
|
@ -232,13 +237,13 @@ export function GameDisplay() {
|
|||
|
||||
{/* Question Display - only for non-sprint modes */}
|
||||
{state.style !== 'sprint' && (
|
||||
<div style={{
|
||||
<div data-component="question-container" style={{
|
||||
maxWidth: '1200px',
|
||||
margin: '0 auto',
|
||||
width: '100%',
|
||||
padding: '0 20px'
|
||||
}}>
|
||||
<div style={{
|
||||
<div data-component="question-display" style={{
|
||||
display: 'flex',
|
||||
gap: '20px',
|
||||
alignItems: 'center',
|
||||
|
|
@ -246,21 +251,21 @@ export function GameDisplay() {
|
|||
marginTop: '5px'
|
||||
}}>
|
||||
{/* Question */}
|
||||
<div style={{
|
||||
<div data-component="question-card" style={{
|
||||
background: 'white',
|
||||
borderRadius: '12px',
|
||||
padding: '16px 24px',
|
||||
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.1)',
|
||||
textAlign: 'center'
|
||||
}}>
|
||||
<div style={{
|
||||
<div data-element="question-equation" style={{
|
||||
fontSize: '14px',
|
||||
color: '#6b7280',
|
||||
marginBottom: '4px'
|
||||
}}>
|
||||
? + {state.currentQuestion.number} = {state.currentQuestion.targetSum}
|
||||
</div>
|
||||
<div style={{
|
||||
<div data-element="question-number" style={{
|
||||
fontSize: '60px',
|
||||
fontWeight: 'bold',
|
||||
color: '#1f2937'
|
||||
|
|
@ -270,7 +275,7 @@ export function GameDisplay() {
|
|||
</div>
|
||||
|
||||
{/* Input */}
|
||||
<div style={{
|
||||
<div data-component="answer-input" style={{
|
||||
background: 'linear-gradient(135deg, #3b82f6, #8b5cf6)',
|
||||
borderRadius: '12px',
|
||||
padding: '16px 36px',
|
||||
|
|
@ -278,7 +283,7 @@ export function GameDisplay() {
|
|||
textAlign: 'center',
|
||||
minWidth: '160px'
|
||||
}}>
|
||||
<div style={{
|
||||
<div data-element="input-value" style={{
|
||||
fontSize: '60px',
|
||||
fontWeight: 'bold',
|
||||
color: 'white',
|
||||
|
|
@ -290,7 +295,7 @@ export function GameDisplay() {
|
|||
}}>
|
||||
{state.currentInput || '_'}
|
||||
</div>
|
||||
<div style={{
|
||||
<div data-element="input-hint" style={{
|
||||
fontSize: '12px',
|
||||
color: 'rgba(255, 255, 255, 0.9)',
|
||||
marginTop: '4px'
|
||||
|
|
|
|||
|
|
@ -5,9 +5,11 @@ interface PressureGaugeProps {
|
|||
}
|
||||
|
||||
export function PressureGauge({ pressure }: PressureGaugeProps) {
|
||||
// Calculate needle angle (-90deg at 0 PSI to +90deg at 150 PSI)
|
||||
const maxPressure = 150
|
||||
const angle = ((pressure / maxPressure) * 180) - 90
|
||||
|
||||
// Calculate needle angle - sweeps 180° from left to right
|
||||
// 0 PSI = 180° (pointing left), 150 PSI = 0° (pointing right)
|
||||
const angle = 180 - (pressure / maxPressure) * 180
|
||||
|
||||
// Get pressure color
|
||||
const getPressureColor = (): string => {
|
||||
|
|
@ -40,44 +42,31 @@ export function PressureGauge({ pressure }: PressureGaugeProps) {
|
|||
|
||||
{/* SVG Gauge */}
|
||||
<svg
|
||||
viewBox="0 0 200 120"
|
||||
viewBox="0 0 210 130"
|
||||
style={{
|
||||
width: '100%',
|
||||
height: 'auto',
|
||||
marginBottom: '8px'
|
||||
}}
|
||||
>
|
||||
{/* Background arc */}
|
||||
{/* Background arc - semicircle from left to right (bottom half) */}
|
||||
<path
|
||||
d="M 20 100 A 80 80 0 0 1 180 100"
|
||||
fill="none"
|
||||
stroke="#e5e7eb"
|
||||
strokeWidth="12"
|
||||
strokeWidth="8"
|
||||
strokeLinecap="round"
|
||||
/>
|
||||
|
||||
{/* Colored arc based on pressure */}
|
||||
<path
|
||||
d="M 20 100 A 80 80 0 0 1 180 100"
|
||||
fill="none"
|
||||
stroke={color}
|
||||
strokeWidth="12"
|
||||
strokeLinecap="round"
|
||||
strokeDasharray={`${(pressure / maxPressure) * 251} 251`}
|
||||
style={{
|
||||
transition: 'stroke 0.3s ease-out, stroke-dasharray 0.2s ease-out',
|
||||
filter: pressure > 50 ? `drop-shadow(0 0 6px ${color})` : 'none'
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Tick marks */}
|
||||
{[0, 50, 100, 150].map((psi, index) => {
|
||||
const tickAngle = ((psi / maxPressure) * 180) - 90
|
||||
// Angle from 180° (left) to 0° (right)
|
||||
const tickAngle = 180 - (psi / maxPressure) * 180
|
||||
const tickRad = (tickAngle * Math.PI) / 180
|
||||
const x1 = 100 + Math.cos(tickRad) * 70
|
||||
const y1 = 100 + Math.sin(tickRad) * 70
|
||||
const y1 = 100 - Math.sin(tickRad) * 70 // Subtract for SVG coords
|
||||
const x2 = 100 + Math.cos(tickRad) * 80
|
||||
const y2 = 100 + Math.sin(tickRad) * 80
|
||||
const y2 = 100 - Math.sin(tickRad) * 80 // Subtract for SVG coords
|
||||
|
||||
return (
|
||||
<g key={`tick-${index}`}>
|
||||
|
|
@ -91,8 +80,8 @@ export function PressureGauge({ pressure }: PressureGaugeProps) {
|
|||
strokeLinecap="round"
|
||||
/>
|
||||
<text
|
||||
x={100 + Math.cos(tickRad) * 60}
|
||||
y={100 + Math.sin(tickRad) * 60 + 4}
|
||||
x={100 + Math.cos(tickRad) * 92}
|
||||
y={100 - Math.sin(tickRad) * 92 + 4} // Subtract for SVG coords
|
||||
textAnchor="middle"
|
||||
fontSize="10"
|
||||
fill="#6b7280"
|
||||
|
|
@ -112,7 +101,7 @@ export function PressureGauge({ pressure }: PressureGaugeProps) {
|
|||
x1="100"
|
||||
y1="100"
|
||||
x2={100 + Math.cos((angle * Math.PI) / 180) * 70}
|
||||
y2={100 + Math.sin((angle * Math.PI) / 180) * 70}
|
||||
y2={100 - Math.sin((angle * Math.PI) / 180) * 70} // Subtract for SVG coords
|
||||
stroke={color}
|
||||
strokeWidth="3"
|
||||
strokeLinecap="round"
|
||||
|
|
|
|||
|
|
@ -233,7 +233,7 @@ export function CircularTrack({ playerProgress, playerLap, aiRacers, aiLaps }: C
|
|||
}
|
||||
|
||||
return (
|
||||
<div style={{
|
||||
<div data-component="circular-track" style={{
|
||||
position: 'relative',
|
||||
width: `${dimensions.width}px`,
|
||||
height: `${dimensions.height}px`,
|
||||
|
|
@ -241,6 +241,7 @@ export function CircularTrack({ playerProgress, playerLap, aiRacers, aiLaps }: C
|
|||
}}>
|
||||
{/* SVG Track */}
|
||||
<svg
|
||||
data-component="track-svg"
|
||||
width={dimensions.width}
|
||||
height={dimensions.height}
|
||||
style={{
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ export function LinearTrack({ playerProgress, aiRacers, raceGoal, showFinishLine
|
|||
const playerPosition = getPosition(playerProgress)
|
||||
|
||||
return (
|
||||
<div style={{
|
||||
<div data-component="linear-track" style={{
|
||||
position: 'relative',
|
||||
width: '100%',
|
||||
height: '200px',
|
||||
|
|
|
|||
Loading…
Reference in New Issue