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:
Thomas Hallock 2025-09-30 12:13:48 -05:00
parent 159990489f
commit 90ad789ff1
6 changed files with 320 additions and 289 deletions

View File

@ -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%)',

View File

@ -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>
)
}

View File

@ -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'

View File

@ -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"

View File

@ -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={{

View File

@ -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',