486 lines
26 KiB
TypeScript
486 lines
26 KiB
TypeScript
'use client';
|
|
|
|
import React, { useState } from 'react';
|
|
import { RadarMapMode, RadarRiderTelemetry } from './types';
|
|
import { INITIAL_RADAR_RIDERS } from './fixtures';
|
|
|
|
interface LivePackRadarProps {
|
|
initialMode?: RadarMapMode;
|
|
}
|
|
|
|
export function LivePackRadar({ initialMode = 'tactical' }: LivePackRadarProps) {
|
|
const [mapMode, setMapMode] = useState<RadarMapMode>(initialMode);
|
|
const [selectedRiderId, setSelectedRiderId] = useState<string | null>('rider-1');
|
|
const [simulationTick, setSimulationTick] = useState(0);
|
|
|
|
// Active riders list with slight dynamic animation based on simulationTick
|
|
const riders: RadarRiderTelemetry[] = INITIAL_RADAR_RIDERS.map((r, idx) => {
|
|
// Subtle realistic wobble for demo interactivity
|
|
const speedWobble = simulationTick > 0 ? ((simulationTick + idx * 3) % 5) - 2 : 0;
|
|
return {
|
|
...r,
|
|
speedMph: Math.max(0, r.speedMph + speedWobble),
|
|
};
|
|
});
|
|
|
|
const selectedRider = riders.find((r) => r.id === selectedRiderId) || riders[0];
|
|
|
|
const handlePulseSimulation = () => {
|
|
setSimulationTick((prev) => prev + 1);
|
|
};
|
|
|
|
return (
|
|
<div className="bg-surface-container-lowest rounded-xl shadow-2xl overflow-hidden border border-outline-variant/30">
|
|
{/* Tactical Header Strip */}
|
|
<div className="bg-surface-container-low px-space-lg py-space-sm flex flex-wrap items-center justify-between gap-space-md border-b border-outline-variant/20">
|
|
<div className="flex items-center gap-space-md">
|
|
<div className="flex items-center gap-space-xs">
|
|
<span className="h-2.5 w-2.5 rounded-full bg-secondary-fixed animate-pulse"></span>
|
|
<span className="font-label-caps text-label-caps text-on-surface tracking-wide">
|
|
Swarm Recon: Skyline Blvd > Coast Hwy
|
|
</span>
|
|
</div>
|
|
<span className="font-code-mono text-code-mono text-on-surface-variant bg-surface-container px-2 py-0.5 rounded border border-outline-variant/20">
|
|
Session #CA-84-SW
|
|
</span>
|
|
</div>
|
|
|
|
<div className="flex items-center gap-space-lg font-code-mono text-code-mono text-on-surface-variant">
|
|
<span className="hidden lg:flex items-center gap-1.5 text-secondary-fixed">
|
|
<span className="material-symbols-outlined text-[18px]">cell_tower</span>
|
|
Mesh Active
|
|
</span>
|
|
<span className="flex items-center gap-1.5">
|
|
<span className="text-secondary-fixed">●</span> {riders.length} Synchronized
|
|
</span>
|
|
<span className="hidden sm:inline text-on-surface-variant">Gap Delta: ±0.4s</span>
|
|
<button
|
|
onClick={handlePulseSimulation}
|
|
className="flex items-center gap-1 px-2.5 py-1 rounded bg-surface-container hover:bg-surface-container-high text-primary font-code-mono text-code-mono transition-colors"
|
|
title="Trigger Telemetry Ping"
|
|
>
|
|
<span className="material-symbols-outlined text-[16px]">sync</span>
|
|
Ping Swarm
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Cockpit Layout: Map & Radar Feed */}
|
|
<div className="grid grid-cols-1 lg:grid-cols-12 gap-0">
|
|
{/* Tactical Canvas & Radar Screen */}
|
|
<div
|
|
className={`lg:col-span-8 relative min-h-[560px] flex flex-col justify-between p-space-md overflow-hidden transition-all duration-300 ${
|
|
mapMode === 'tactical'
|
|
? 'bg-[#0B1319]'
|
|
: mapMode === 'satellite'
|
|
? 'bg-[#071015]'
|
|
: 'bg-[#0E1520]'
|
|
}`}
|
|
data-testid="radar-canvas-container"
|
|
>
|
|
{/* Tactical Background Overlay */}
|
|
{mapMode === 'tactical' && (
|
|
<>
|
|
{/* Tactical Coordinate Grids (Inline SVG Overlay) */}
|
|
<svg className="absolute inset-0 w-full h-full pointer-events-none opacity-40" xmlns="http://www.w3.org/2000/svg">
|
|
<defs>
|
|
<pattern id="tactical-grid-pattern" width="48" height="48" patternUnits="userSpaceOnUse">
|
|
<path d="M 48 0 L 0 0 0 48" fill="none" stroke="rgba(0, 242, 254, 0.15)" strokeWidth="0.5" />
|
|
<circle cx="0" cy="0" r="1.5" fill="rgba(0, 242, 254, 0.4)" />
|
|
</pattern>
|
|
</defs>
|
|
<rect width="100%" height="100%" fill="url(#tactical-grid-pattern)" />
|
|
{/* Tactical Route Trace */}
|
|
<path
|
|
d="M 80 480 C 180 410, 240 430, 340 330 S 480 280, 560 190 S 700 170, 820 100"
|
|
fill="none"
|
|
stroke="#00F2FE"
|
|
strokeDasharray="8 6"
|
|
strokeWidth="3.5"
|
|
className="animate-pulse"
|
|
/>
|
|
{/* Safe Spacing Buffers */}
|
|
<circle cx="820" cy="100" r="32" fill="none" stroke="rgba(57, 255, 20, 0.3)" strokeWidth="1.5" />
|
|
<circle cx="700" cy="170" r="28" fill="none" stroke="rgba(57, 255, 20, 0.3)" strokeWidth="1.5" />
|
|
<circle cx="560" cy="190" r="34" fill="none" stroke="rgba(255, 107, 107, 0.4)" strokeWidth="2" strokeDasharray="4 2" />
|
|
<circle cx="340" cy="330" r="26" fill="none" stroke="rgba(57, 255, 20, 0.3)" strokeWidth="1.5" />
|
|
<circle cx="240" cy="430" r="36" fill="none" stroke="rgba(255, 68, 68, 0.6)" strokeDasharray="4 2" strokeWidth="2.5" />
|
|
<circle cx="80" cy="480" r="30" fill="none" stroke="rgba(0, 242, 254, 0.4)" strokeWidth="1.5" />
|
|
</svg>
|
|
</>
|
|
)}
|
|
|
|
{/* Satellite Mode Backdrop */}
|
|
{mapMode === 'satellite' && (
|
|
<div className="absolute inset-0 pointer-events-none" data-testid="satellite-mode-view">
|
|
{/* Satellite Dark Topography Simulation */}
|
|
<div className="absolute inset-0 bg-gradient-to-b from-[#0A1620]/90 via-[#071118]/80 to-[#040A0F] mix-blend-multiply"></div>
|
|
<svg className="absolute inset-0 w-full h-full opacity-30" xmlns="http://www.w3.org/2000/svg">
|
|
{/* Topographic Contour lines */}
|
|
<path d="M 0 100 Q 250 80 400 180 T 800 120 T 1200 220" fill="none" stroke="#00F2FE" strokeWidth="1" strokeDasharray="2 4" />
|
|
<path d="M 0 200 Q 200 150 450 260 T 900 200 T 1200 320" fill="none" stroke="#00F2FE" strokeWidth="1" strokeDasharray="2 4" />
|
|
<path d="M 0 320 Q 300 280 500 380 T 950 320 T 1200 440" fill="none" stroke="#00F2FE" strokeWidth="1" strokeDasharray="2 4" />
|
|
<path d="M 0 440 Q 350 400 600 480 T 1000 420 T 1200 540" fill="none" stroke="#00F2FE" strokeWidth="1" strokeDasharray="2 4" />
|
|
{/* Satellite Vector Track */}
|
|
<path
|
|
d="M 80 480 C 180 410, 240 430, 340 330 S 480 280, 560 190 S 700 170, 820 100"
|
|
fill="none"
|
|
stroke="#39FF14"
|
|
strokeWidth="3"
|
|
/>
|
|
</svg>
|
|
<div className="absolute top-4 left-1/2 -translate-x-1/2 px-3 py-1 rounded-full bg-surface-container/90 border border-outline-variant/30 text-on-surface font-code-mono text-label-caps text-center">
|
|
High-Resolution Orbit Imagery • Multi-Band GNSS Overlaid
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Elevation 3D Mode Backdrop */}
|
|
{mapMode === 'elevation' && (
|
|
<div className="absolute inset-0 pointer-events-none" data-testid="elevation-mode-view">
|
|
<svg className="absolute inset-0 w-full h-full opacity-40" xmlns="http://www.w3.org/2000/svg">
|
|
{/* Elevation isometric grid lines */}
|
|
<defs>
|
|
<linearGradient id="elevationGrad" x1="0%" y1="100%" x2="100%" y2="0%">
|
|
<stop offset="0%" stopColor="#39FF14" stopOpacity="0.8" />
|
|
<stop offset="50%" stopColor="#00F2FE" stopOpacity="0.8" />
|
|
<stop offset="100%" stopColor="#FF9F1C" stopOpacity="0.9" />
|
|
</linearGradient>
|
|
</defs>
|
|
{/* Mountain elevation profile ribbon */}
|
|
<polygon
|
|
points="80,500 180,430 340,350 560,210 700,180 820,110 820,530 80,530"
|
|
fill="url(#elevationGrad)"
|
|
opacity="0.15"
|
|
/>
|
|
<path
|
|
d="M 80 500 L 180 430 L 340 350 L 560 210 L 700 180 L 820 110"
|
|
fill="none"
|
|
stroke="url(#elevationGrad)"
|
|
strokeWidth="4"
|
|
/>
|
|
</svg>
|
|
{/* Elevation Altitude Badges */}
|
|
<div className="absolute bottom-28 left-16 bg-surface/90 px-2 py-1 rounded text-primary font-code-mono text-label-caps border border-primary/30">
|
|
Start: 1,480 ft
|
|
</div>
|
|
<div className="absolute top-28 right-24 bg-surface/90 px-2 py-1 rounded text-secondary-fixed font-code-mono text-label-caps border border-secondary-fixed/30">
|
|
Summit: 2,210 ft (+730 ft gain)
|
|
</div>
|
|
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 bg-surface/90 px-2.5 py-1 rounded text-on-surface font-code-mono text-label-caps border border-outline-variant/30">
|
|
Grade: 8.4% Apex Switchback
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Map HUD Top Left: Sector Status */}
|
|
<div className="relative z-10 flex items-start justify-between">
|
|
<div className="bg-surface/90 backdrop-blur-md px-space-md py-2.5 rounded-xl shadow-lg border border-outline-variant/30 flex flex-col gap-0.5">
|
|
<span className="font-label-caps text-label-caps text-on-surface-variant">Formation Profile</span>
|
|
<span className="font-headline-sm text-headline-sm text-primary font-bold">Staggered Dual Column [B]</span>
|
|
<span className="font-code-mono text-code-mono text-secondary-fixed flex items-center gap-1">
|
|
<span className="material-symbols-outlined text-[14px]">shield</span> Paceline Lock: Optimal
|
|
</span>
|
|
</div>
|
|
|
|
{/* Map HUD Top Right: Telemetry & Mesh Lock (GPS Frequency label removed as requested) */}
|
|
<div className="bg-surface/90 backdrop-blur-md px-space-md py-2 rounded-xl shadow-lg border border-outline-variant/30 flex items-center gap-space-sm">
|
|
<div className="text-right">
|
|
<span className="font-label-caps text-label-caps text-secondary-fixed block font-bold">
|
|
GNSS Multi-Band Mesh
|
|
</span>
|
|
<span className="font-code-mono text-code-mono text-on-surface">18 Satellites Locked</span>
|
|
</div>
|
|
<span className="material-symbols-outlined text-secondary-fixed text-[24px]">satellite_alt</span>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Interactive Rider Markers across the canvas */}
|
|
<div className="relative z-10 my-auto h-72 w-full">
|
|
{riders.map((rider) => {
|
|
const isSelected = selectedRiderId === rider.id;
|
|
return (
|
|
<div
|
|
key={rider.id}
|
|
onClick={() => setSelectedRiderId(rider.id)}
|
|
style={{
|
|
left: `${rider.coordinates.x}%`,
|
|
top: `${rider.coordinates.y}%`,
|
|
}}
|
|
className={`absolute -translate-x-1/2 -translate-y-1/2 cursor-pointer transition-all duration-200 group ${
|
|
isSelected ? 'z-30 scale-110' : 'z-20 hover:scale-105'
|
|
}`}
|
|
data-testid={`radar-marker-${rider.callsign}`}
|
|
>
|
|
{/* Outer targeting reticle if selected */}
|
|
{isSelected && (
|
|
<div className="absolute -inset-2 rounded-full border-2 border-primary-container animate-ping pointer-events-none opacity-60"></div>
|
|
)}
|
|
|
|
{/* Marker Badge */}
|
|
<div
|
|
className={`flex items-center gap-1.5 px-2.5 py-1 rounded shadow-xl backdrop-blur-md transition-all ${
|
|
isSelected
|
|
? 'bg-primary-container text-on-primary-container ring-2 ring-primary-fixed shadow-[0_0_16px_rgba(0,242,254,0.6)]'
|
|
: rider.status === 'alert'
|
|
? 'bg-error-container/90 text-on-error-container border border-error'
|
|
: rider.status === 'warning'
|
|
? 'bg-surface-container-high/90 text-on-surface border border-error/50'
|
|
: 'bg-surface-container-high/90 text-on-surface border border-outline-variant/30 hover:border-primary-container'
|
|
}`}
|
|
>
|
|
<span
|
|
className={`w-2.5 h-2.5 rounded-full ${
|
|
rider.status === 'alert'
|
|
? 'bg-error animate-bounce'
|
|
: rider.status === 'warning'
|
|
? 'bg-error animate-pulse'
|
|
: isSelected
|
|
? 'bg-on-primary-container'
|
|
: 'bg-secondary-fixed animate-pulse'
|
|
}`}
|
|
></span>
|
|
<span className="font-code-mono text-code-mono font-bold">{rider.callsign}</span>
|
|
<span
|
|
className={`font-label-caps text-label-caps font-semibold ${
|
|
isSelected
|
|
? 'text-on-primary-container'
|
|
: rider.status === 'alert'
|
|
? 'text-error'
|
|
: 'text-secondary-fixed'
|
|
}`}
|
|
>
|
|
{rider.speedMph} MPH
|
|
</span>
|
|
</div>
|
|
|
|
{/* Deceleration Warning Callout */}
|
|
{rider.status === 'alert' && (
|
|
<div className="absolute -bottom-7 left-1/2 -translate-x-1/2 whitespace-nowrap bg-error text-white font-code-mono text-[11px] px-2 py-0.5 rounded shadow-lg flex items-center gap-1">
|
|
<span className="material-symbols-outlined text-[13px]">warning</span>
|
|
-1.2G Decel
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
|
|
{/* Map HUD Bottom Control Strip */}
|
|
<div className="relative z-10 flex flex-wrap items-center justify-between gap-space-sm mt-auto pt-space-lg">
|
|
{/* Radar View Mode Toggles */}
|
|
<div className="flex items-center gap-space-xs bg-surface/90 backdrop-blur-md p-1.5 rounded-xl border border-outline-variant/30">
|
|
<button
|
|
type="button"
|
|
onClick={() => setMapMode('tactical')}
|
|
className={`px-space-sm py-1 rounded-lg font-label-caps text-label-caps transition-all ${
|
|
mapMode === 'tactical'
|
|
? 'bg-primary-container text-on-primary-container font-bold shadow-md'
|
|
: 'text-on-surface-variant hover:text-on-surface hover:bg-surface-container'
|
|
}`}
|
|
>
|
|
Tactical
|
|
</button>
|
|
<button
|
|
type="button"
|
|
onClick={() => setMapMode('satellite')}
|
|
className={`px-space-sm py-1 rounded-lg font-label-caps text-label-caps transition-all ${
|
|
mapMode === 'satellite'
|
|
? 'bg-primary-container text-on-primary-container font-bold shadow-md'
|
|
: 'text-on-surface-variant hover:text-on-surface hover:bg-surface-container'
|
|
}`}
|
|
>
|
|
Satellite
|
|
</button>
|
|
<button
|
|
type="button"
|
|
onClick={() => setMapMode('elevation')}
|
|
className={`px-space-sm py-1 rounded-lg font-label-caps text-label-caps transition-all ${
|
|
mapMode === 'elevation'
|
|
? 'bg-primary-container text-on-primary-container font-bold shadow-md'
|
|
: 'text-on-surface-variant hover:text-on-surface hover:bg-surface-container'
|
|
}`}
|
|
>
|
|
Elevation 3D
|
|
</button>
|
|
</div>
|
|
|
|
{/* Regroup point readout */}
|
|
<div className="bg-surface/90 backdrop-blur-md px-space-md py-1.5 rounded-xl flex items-center gap-space-sm border border-outline-variant/30">
|
|
<span className="font-label-caps text-label-caps text-on-surface-variant">Regroup Point</span>
|
|
<span className="font-code-mono text-code-mono text-primary-container font-bold">
|
|
Alice's Restaurant (14.8 mi)
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Live Swarm Nodes Telemetry Column */}
|
|
<div className="lg:col-span-4 bg-surface-container p-space-md flex flex-col gap-space-sm overflow-y-auto max-h-[620px] border-t lg:border-t-0 lg:border-l border-outline-variant/20">
|
|
<div className="flex items-center justify-between pb-space-xs mb-space-xs border-b border-outline-variant/20">
|
|
<span className="font-label-caps text-label-caps text-on-surface-variant tracking-wide">
|
|
Pack Telemetry Stream
|
|
</span>
|
|
<span className="font-code-mono text-code-mono text-primary-fixed-dim">
|
|
{riders.length} Active Nodes
|
|
</span>
|
|
</div>
|
|
|
|
{/* Active Rider Telemetry Cards */}
|
|
{riders.map((rider) => {
|
|
const isSelected = selectedRiderId === rider.id;
|
|
return (
|
|
<div
|
|
key={rider.id}
|
|
onClick={() => setSelectedRiderId(rider.id)}
|
|
className={`p-space-sm rounded-xl cursor-pointer transition-all border ${
|
|
isSelected
|
|
? 'bg-surface-container-highest border-primary-container shadow-md ring-1 ring-primary-container/50'
|
|
: 'bg-surface-container-high border-transparent hover:bg-surface-container-highest hover:border-outline-variant/30'
|
|
}`}
|
|
data-testid={`telemetry-card-${rider.callsign}`}
|
|
>
|
|
<div className="flex items-center justify-between mb-1">
|
|
<div className="flex items-center gap-space-xs">
|
|
<span
|
|
className={`w-2 h-2 rounded-full ${
|
|
rider.status === 'alert'
|
|
? 'bg-error animate-bounce'
|
|
: rider.status === 'warning'
|
|
? 'bg-error animate-pulse'
|
|
: rider.status === 'sweeper'
|
|
? 'bg-secondary-fixed'
|
|
: 'bg-secondary-fixed'
|
|
}`}
|
|
></span>
|
|
<span className="font-headline-sm text-headline-sm text-on-surface font-bold">
|
|
{rider.callsign}
|
|
</span>
|
|
<span className="px-1.5 py-0.5 rounded bg-surface-container-lowest font-label-caps text-label-caps text-secondary-fixed">
|
|
{rider.role}
|
|
</span>
|
|
</div>
|
|
<span
|
|
className={`font-code-mono text-code-mono ${
|
|
rider.gapMeters === 0 ? 'text-primary-fixed font-bold' : 'text-secondary-fixed'
|
|
}`}
|
|
>
|
|
{rider.gapMeters === 0 ? 'Lead' : `+${rider.gapMeters}m Gap`}
|
|
</span>
|
|
</div>
|
|
|
|
{/* Squad Roster Telemetry Grid: Speed, Lean, Battery, Mesh Ping */}
|
|
<div className="grid grid-cols-4 gap-1.5 mt-2 font-code-mono text-[11px] text-on-surface-variant">
|
|
<div>
|
|
<span className="block text-[9px] text-outline">Speed</span>
|
|
<span className="text-on-surface font-bold">{rider.speedMph} MPH</span>
|
|
</div>
|
|
<div>
|
|
<span className="block text-[9px] text-outline">Lean</span>
|
|
<span className="text-primary-container font-bold">{rider.leanAngleDeg}°</span>
|
|
</div>
|
|
<div>
|
|
<span className="block text-[9px] text-outline">Battery</span>
|
|
<span className="text-secondary-fixed font-bold">{rider.batteryPct ?? 92}%</span>
|
|
</div>
|
|
<div>
|
|
<span className="block text-[9px] text-outline">Mesh Ping</span>
|
|
<span className="text-primary-fixed-dim font-bold">{rider.meshPingMs ?? 12}ms</span>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Warning / Alert Callout */}
|
|
{rider.alertText && (
|
|
<div className="mt-2 pt-1 border-t border-outline-variant/20 flex items-center gap-1 font-code-mono text-code-mono text-error">
|
|
<span className="material-symbols-outlined text-[15px]">
|
|
{rider.status === 'alert' ? 'warning' : 'tire_repair'}
|
|
</span>
|
|
<span className="truncate">{rider.alertText}</span>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
})}
|
|
|
|
{/* Selected Rider Inspector HUD Drawer */}
|
|
{selectedRider && (
|
|
<div
|
|
className="mt-auto p-space-sm rounded-xl bg-surface-container-lowest border border-primary-container/40"
|
|
data-testid="selected-node-inspector"
|
|
>
|
|
<div className="flex items-center justify-between text-label-caps font-label-caps text-primary-container mb-1">
|
|
<span>Selected Node Inspector</span>
|
|
<span className="font-code-mono">{selectedRider.callsign}</span>
|
|
</div>
|
|
<div className="flex items-center justify-between text-code-mono font-code-mono text-on-surface-variant text-[12px]">
|
|
<span>Role: {selectedRider.roleBadge}</span>
|
|
<span>Gap Delta: ±{selectedRider.gapDeltaSec}s</span>
|
|
<span className="text-secondary-fixed">Live Sync: 20Hz</span>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Formation Safety Metrics Panel Strip */}
|
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 bg-surface-container-highest p-space-lg gap-space-lg border-t border-outline-variant/20">
|
|
{/* Metric 1: Spacing Integrity */}
|
|
<div className="flex flex-col gap-1">
|
|
<div className="flex items-center justify-between">
|
|
<span className="font-label-caps text-label-caps text-on-surface-variant">Spacing Integrity</span>
|
|
<span className="material-symbols-outlined text-secondary-fixed text-[18px]">verified</span>
|
|
</div>
|
|
<div className="flex items-baseline gap-space-xs">
|
|
<span className="font-telemetry-metric-md text-telemetry-metric-md text-secondary-fixed">94%</span>
|
|
<span className="font-code-mono text-code-mono text-on-surface-variant">Safety Index</span>
|
|
</div>
|
|
<div className="w-full bg-surface-container h-1.5 rounded-full overflow-hidden mt-1">
|
|
<div className="bg-secondary-fixed h-full rounded-full w-[94%]"></div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Metric 2: Average Pack Speed */}
|
|
<div className="flex flex-col gap-1">
|
|
<div className="flex items-center justify-between">
|
|
<span className="font-label-caps text-label-caps text-on-surface-variant">Average Pack Velocity</span>
|
|
<span className="material-symbols-outlined text-primary-container text-[18px]">speed</span>
|
|
</div>
|
|
<div className="flex items-baseline gap-space-xs">
|
|
<span className="font-telemetry-metric-md text-telemetry-metric-md text-on-surface">64.2</span>
|
|
<span className="font-code-mono text-code-mono text-primary-fixed-dim">MPH</span>
|
|
</div>
|
|
<span className="font-code-mono text-code-mono text-on-surface-variant">
|
|
Delta Drift: -1.8 MPH (tightening)
|
|
</span>
|
|
</div>
|
|
|
|
{/* Metric 3: Atmospheric & Track Conditions */}
|
|
<div className="flex flex-col gap-1">
|
|
<div className="flex items-center justify-between">
|
|
<span className="font-label-caps text-label-caps text-on-surface-variant">Surface & Climate</span>
|
|
<span className="material-symbols-outlined text-on-surface-variant text-[18px]">thermostat</span>
|
|
</div>
|
|
<div className="flex items-baseline gap-space-xs">
|
|
<span className="font-telemetry-metric-md text-telemetry-metric-md text-on-surface">21°C</span>
|
|
<span className="font-code-mono text-code-mono text-on-surface-variant">Dry Asphalt</span>
|
|
</div>
|
|
<span className="font-code-mono text-code-mono text-secondary-fixed">0% Precip • Grip Optimal</span>
|
|
</div>
|
|
|
|
{/* Metric 4: Next Regroup Waypoint */}
|
|
<div className="flex flex-col gap-1">
|
|
<div className="flex items-center justify-between">
|
|
<span className="font-label-caps text-label-caps text-on-surface-variant">Regroup Point</span>
|
|
<span className="material-symbols-outlined text-primary-container text-[18px]">pin_drop</span>
|
|
</div>
|
|
<div className="flex items-baseline gap-space-xs">
|
|
<span className="font-telemetry-metric-md text-telemetry-metric-md text-primary-container">14.8</span>
|
|
<span className="font-code-mono text-code-mono text-primary-fixed-dim">Miles</span>
|
|
</div>
|
|
<span className="font-code-mono text-code-mono text-on-surface">Alice's Restaurant Checkpoint</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|