146 lines
5.0 KiB
TypeScript
146 lines
5.0 KiB
TypeScript
// components/live/LiveTrackMap.tsx
|
|
'use client';
|
|
|
|
import { useEffect, useState } from 'react';
|
|
import { getTrackMapUrl, cleanTrackName, cleanTrackConfig } from '@/lib/trackUtils';
|
|
|
|
interface Car {
|
|
carID: number;
|
|
driver_name: string;
|
|
car_model: string;
|
|
normalizedSplinePos: number;
|
|
position: number;
|
|
lap_time?: number;
|
|
best_lap_time?: number;
|
|
}
|
|
|
|
interface LiveTrackMapProps {
|
|
track: string;
|
|
trackConfig: string;
|
|
cars: Car[];
|
|
}
|
|
|
|
export default function LiveTrackMap({ track, trackConfig, cars }: LiveTrackMapProps) {
|
|
const [imageError, setImageError] = useState(false);
|
|
|
|
// Get cleaned track map URL
|
|
const trackMapUrl = getTrackMapUrl(track, trackConfig);
|
|
const displayTrackName = cleanTrackName(track);
|
|
const displayTrackConfig = cleanTrackConfig(trackConfig);
|
|
|
|
// Calculate position on track (circular approximation)
|
|
const getCarPosition = (normalizedPos: number) => {
|
|
// normalizedPos is 0.0 to 1.0 around the track
|
|
// We'll place cars in a circular path for now
|
|
const angle = normalizedPos * Math.PI * 2 - Math.PI / 2; // Start at top
|
|
|
|
// Position relative to center (percentage)
|
|
const centerX = 50;
|
|
const centerY = 50;
|
|
const radius = 40; // 40% from center
|
|
|
|
const x = centerX + Math.cos(angle) * radius;
|
|
const y = centerY + Math.sin(angle) * radius;
|
|
|
|
return { x, y };
|
|
};
|
|
|
|
// Get color based on position
|
|
const getPositionColor = (position: number) => {
|
|
if (position === 1) return '#ffffff'; // P1 - White
|
|
if (position === 2) return '#d1d5db'; // P2 - Light gray
|
|
if (position === 3) return '#9ca3af'; // P3 - Gray
|
|
return '#6b7280'; // Others - Dark gray
|
|
};
|
|
|
|
return (
|
|
<div className="relative w-full aspect-square bg-black border border-white/10 overflow-hidden">
|
|
{/* Track Map Background */}
|
|
{!imageError ? (
|
|
<img
|
|
src={trackMapUrl}
|
|
alt={`${track} track map`}
|
|
className="absolute inset-0 w-full h-full object-contain opacity-60"
|
|
onError={() => setImageError(true)}
|
|
/>
|
|
) : (
|
|
<div className="absolute inset-0 flex items-center justify-center text-white/40 text-sm">
|
|
Track map not available
|
|
</div>
|
|
)}
|
|
|
|
{/* Grid overlay for reference */}
|
|
<div className="absolute inset-0 opacity-10">
|
|
<svg width="100%" height="100%" className="text-white">
|
|
<defs>
|
|
<pattern id="grid" width="40" height="40" patternUnits="userSpaceOnUse">
|
|
<path d="M 40 0 L 0 0 0 40" fill="none" stroke="currentColor" strokeWidth="0.5"/>
|
|
</pattern>
|
|
</defs>
|
|
<rect width="100%" height="100%" fill="url(#grid)" />
|
|
</svg>
|
|
</div>
|
|
|
|
{/* Car Positions */}
|
|
<div className="absolute inset-0">
|
|
{cars.map((car) => {
|
|
const pos = getCarPosition(car.normalizedSplinePos);
|
|
const color = getPositionColor(car.position);
|
|
|
|
return (
|
|
<div
|
|
key={car.carID}
|
|
className="absolute transition-all duration-300 ease-linear"
|
|
style={{
|
|
left: `${pos.x}%`,
|
|
top: `${pos.y}%`,
|
|
transform: 'translate(-50%, -50%)',
|
|
}}
|
|
>
|
|
{/* Car dot */}
|
|
<div
|
|
className="w-4 h-4 rounded-full border-2 animate-pulse"
|
|
style={{
|
|
backgroundColor: color,
|
|
borderColor: color,
|
|
boxShadow: `0 0 10px ${color}`,
|
|
}}
|
|
/>
|
|
|
|
{/* Position number */}
|
|
<div
|
|
className="absolute -top-6 left-1/2 transform -translate-x-1/2 text-xs font-bold whitespace-nowrap px-2 py-1 bg-black/80 border"
|
|
style={{ borderColor: color, color: color }}
|
|
>
|
|
P{car.position}
|
|
</div>
|
|
|
|
{/* Driver name on hover */}
|
|
<div className="absolute top-6 left-1/2 transform -translate-x-1/2 opacity-0 hover:opacity-100 transition-opacity whitespace-nowrap">
|
|
<div className="px-2 py-1 bg-black/90 border border-white/20 text-xs">
|
|
{car.driver_name}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
|
|
{/* Track info overlay */}
|
|
<div className="absolute top-4 left-4 bg-black/80 border border-white/20 px-3 py-2">
|
|
<div className="text-xs font-bold tracking-wider text-white/60">TRACK</div>
|
|
<div className="text-sm font-mono">{track}</div>
|
|
{trackConfig && trackConfig !== 'default' && (
|
|
<div className="text-xs text-white/60">{trackConfig}</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Live indicator */}
|
|
<div className="absolute top-4 right-4 flex items-center space-x-2 bg-black/80 border border-white/20 px-3 py-2">
|
|
<div className="w-2 h-2 bg-red-500 rounded-full animate-pulse"></div>
|
|
<span className="text-xs font-bold tracking-wider">LIVE</span>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|