MotorSignal-App/website/components/group-rides/live-pack-radar.tsx

1460 lines
64 KiB
TypeScript

'use client';
import React, { useState, useEffect, useRef, useMemo } from 'react';
import { getApps, initializeApp } from 'firebase/app';
import {
getFirestore,
connectFirestoreEmulator,
doc,
onSnapshot,
} from 'firebase/firestore';
import { getAuth, connectAuthEmulator, signInWithEmailAndPassword, signOut } from 'firebase/auth';
import { firestore as defaultFirestore, firebaseAuth as defaultAuth } from '../../lib/firebase-client';
import { RadarMapMode, RadarRiderTelemetry, GroupRideSummary } from './types';
import { INITIAL_RADAR_RIDERS } from './fixtures';
import { getAwsSatelliteTileAt } from '../../lib/aws-maps';
export interface LivePackRadarProps {
initialMode?: RadarMapMode;
selectedRide?: (GroupRideSummary & {
accessUids?: string[];
organizerUid?: string;
meetingPointCoords?: { lat: number; lon: number };
}) | null;
allRides?: GroupRideSummary[];
onSelectRide?: (ride: GroupRideSummary) => void;
currentUser?: {
uid?: string;
email?: string | null;
displayName?: string | null;
getIdToken?: () => Promise<string>;
} | null;
isPublicShowcase?: boolean;
}
interface LivePresenceRecord {
id: string;
userId: string;
callsign: string;
role: string;
latitude: number;
longitude: number;
speedMph: number;
heading: number;
accuracyMeters: number;
updatedAt: string;
expiresAt?: string;
status: string;
sharingEnabled: boolean;
freshness: 'active' | 'stale' | 'disconnected';
diffSec: number;
}
/** Helper resolving Firestore with local emulator fallback for development/QA */
function getActiveFirestore() {
if (
typeof window !== 'undefined' &&
(window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1')
) {
let qaApp = getApps().find((a) => a.name === 'motorsignal-local-qa');
if (!qaApp) {
try {
qaApp = initializeApp(
{ apiKey: 'demo-key', projectId: 'demo-motorsignal-qa', authDomain: 'localhost' },
'motorsignal-local-qa'
);
const db = getFirestore(qaApp);
connectFirestoreEmulator(db, '127.0.0.1', 8088);
const auth = getAuth(qaApp);
connectAuthEmulator(auth, 'http://127.0.0.1:9099');
return db;
} catch (_) {}
}
if (qaApp) {
try {
return getFirestore(qaApp);
} catch (_) {}
}
}
return defaultFirestore;
}
/** Helper resolving Auth with local emulator fallback */
function getActiveAuth() {
if (
typeof window !== 'undefined' &&
(window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1')
) {
let qaApp = getApps().find((a) => a.name === 'motorsignal-local-qa');
if (qaApp) {
try {
return getAuth(qaApp);
} catch (_) {}
}
}
return defaultAuth;
}
if (typeof window !== 'undefined') {
(window as any).qaSignIn = async (email = 'qa-lead@motorsignal.example', pass = 'Password123!') => {
const auth = getActiveAuth();
return await signInWithEmailAndPassword(auth, email, pass);
};
(window as any).qaSignOut = async () => {
const auth = getActiveAuth();
return await signOut(auth);
};
(window as any).firebaseAuth = defaultAuth;
(window as any).getActiveAuth = getActiveAuth;
(window as any).getActiveFirestore = getActiveFirestore;
}
/** Web Mercator coordinate projection */
function projectMercator(
lat: number,
lon: number,
centerLat: number,
centerLon: number,
zoom: number,
width: number,
height: number,
panX = 0,
panY = 0
) {
const scale = 256 * Math.pow(2, zoom);
const x = ((lon + 180) / 360) * scale;
const centerX = ((centerLon + 180) / 360) * scale;
const latRad = (lat * Math.PI) / 180;
const sinLat = Math.sin(latRad);
const clampedSin = Math.max(-0.9999, Math.min(0.9999, sinLat));
const y = (0.5 - Math.log((1 + clampedSin) / (1 - clampedSin)) / (4 * Math.PI)) * scale;
const centerLatRad = (centerLat * Math.PI) / 180;
const sinCenter = Math.sin(centerLatRad);
const clampedCenterSin = Math.max(-0.9999, Math.min(0.9999, sinCenter));
const centerY = (0.5 - Math.log((1 + clampedCenterSin) / (1 - clampedCenterSin)) / (4 * Math.PI)) * scale;
return {
x: width / 2 + (x - centerX) + panX,
y: height / 2 + (y - centerY) + panY,
};
}
export function LivePackRadar({
initialMode = 'tactical',
selectedRide,
allRides,
onSelectRide,
currentUser,
isPublicShowcase,
}: LivePackRadarProps) {
const [mapMode, setMapMode] = useState<RadarMapMode>(initialMode);
const [selectedRiderId, setSelectedRiderId] = useState<string | null>('rider-1');
const [simulationTick, setSimulationTick] = useState(0);
const [pingMessage, setPingMessage] = useState<string | null>(null);
// Authenticated real presence state
const isRealRide = Boolean(
selectedRide &&
selectedRide.id &&
selectedRide.id !== 'default-ca-84' &&
selectedRide.id !== 'session-ca-84-sw'
);
const [livePresences, setLivePresences] = useState<Record<string, LivePresenceRecord>>({});
const [isAuthorized, setIsAuthorized] = useState<boolean>(true);
const [isPresenceLoading, setIsPresenceLoading] = useState<boolean>(isRealRide);
const [presenceError, setPresenceError] = useState<string | null>(null);
// GIS Map interactive viewport state
const canvasRef = useRef<HTMLDivElement>(null);
const [viewportDim, setViewportDim] = useState({ width: 760, height: 560 });
const [zoom, setZoom] = useState(15);
const [panOffset, setPanOffset] = useState({ x: 0, y: 0 });
const [isDragging, setIsDragging] = useState(false);
const dragStartRef = useRef({ x: 0, y: 0, panX: 0, panY: 0 });
// Update viewport dimensions on resize
useEffect(() => {
if (!canvasRef.current) return;
const updateSize = () => {
if (canvasRef.current) {
const rect = canvasRef.current.getBoundingClientRect();
if (rect.width > 0 && rect.height > 0) {
setViewportDim({ width: rect.width, height: rect.height });
}
}
};
updateSize();
window.addEventListener('resize', updateSize);
return () => window.removeEventListener('resize', updateSize);
}, []);
// Realtime subscription to live_presence for authenticated ride
useEffect(() => {
setPresenceError(null);
if (!isRealRide || !selectedRide) {
setLivePresences({});
setIsAuthorized(true);
setIsPresenceLoading(false);
return;
}
setIsPresenceLoading(true);
setLivePresences({});
let active = true;
const db = getActiveFirestore();
const auth = getActiveAuth();
const currentUid = currentUser?.uid || auth?.currentUser?.uid;
if (!db) {
setIsPresenceLoading(false);
return;
}
const unsubs: Array<() => void> = [];
let presenceGeneration = 0;
const stopPresenceStreams = () => {
presenceGeneration += 1;
unsubs.forEach((u) => u());
unsubs.length = 0;
};
const denyPresenceAccess = () => {
stopPresenceStreams();
setLivePresences({});
setIsAuthorized(false);
setIsPresenceLoading(false);
};
const failPresenceStream = (message: string) => {
stopPresenceStreams();
setLivePresences({});
setPresenceError(message);
setIsPresenceLoading(false);
};
// Helper to evaluate freshness
const computeFreshness = (
updatedAtStr: string,
sharingEnabled: boolean
): { freshness: 'active' | 'stale' | 'disconnected'; diffSec: number } => {
if (!sharingEnabled) return { freshness: 'disconnected', diffSec: 999 };
const updatedMs = new Date(updatedAtStr).getTime();
const diffSec = Math.max(0, (Date.now() - updatedMs) / 1000);
if (diffSec < 30) return { freshness: 'active', diffSec };
if (diffSec <= 120) return { freshness: 'stale', diffSec };
return { freshness: 'disconnected', diffSec };
};
// First check ride access list to respect single-doc read architecture
const rideRef = doc(db, 'group_rides', selectedRide.id);
const unsubRide = onSnapshot(
rideRef,
(rideSnap) => {
if (!active) return;
if (!rideSnap.exists()) {
failPresenceStream('This group ride is no longer available.');
return;
}
const rData = rideSnap.data() || {};
const accessUids: string[] = Array.isArray(rData.accessUids)
? rData.accessUids
: selectedRide.accessUids || [rData.organizerUid];
const organizerUid = rData.organizerUid || selectedRide.organizerUid;
// Verify eligibility: only accepted riders or ride creator can view presence
if (currentUid && !accessUids.includes(currentUid) && organizerUid !== currentUid) {
denyPresenceAccess();
return;
}
setIsAuthorized(true);
setPresenceError(null);
// Clear existing participant listeners and bind point-to-point doc streams
stopPresenceStreams();
const generation = presenceGeneration;
setLivePresences((prev) => Object.fromEntries(Object.entries(prev).filter(([uid]) => accessUids.includes(uid))));
if (accessUids.length === 0) {
setLivePresences({});
setIsPresenceLoading(false);
return;
}
accessUids.forEach((uid) => {
const presenceRef = doc(db, 'group_rides', selectedRide.id, 'live_presence', uid);
const unsubPresence = onSnapshot(
presenceRef,
(pSnap) => {
if (!active || generation !== presenceGeneration) return;
if (pSnap.exists()) {
const data = pSnap.data();
const updatedIso = data.updatedAt?.toDate
? data.updatedAt.toDate().toISOString()
: typeof data.updatedAt === 'string'
? data.updatedAt
: new Date().toISOString();
const { freshness, diffSec } = computeFreshness(
updatedIso,
data.sharingEnabled !== false
);
const callsign =
data.riderName ||
data.riderHandle ||
(uid === organizerUid
? 'ApexLeader'
: uid === 'rider-wing'
? 'Wingman99'
: `Wingman-${uid.slice(-4)}`);
setLivePresences((prev) => ({
...prev,
[uid]: {
id: uid,
userId: uid,
callsign,
role: data.role || (uid === organizerUid ? 'organizer' : 'participant'),
latitude: Number(data.latitude || 0),
longitude: Number(data.longitude || 0),
speedMph: Math.round(Number(data.speedMph ?? data.speed_mph ?? 0)),
heading: Number(data.heading ?? 0),
accuracyMeters: Number(data.accuracyMeters ?? 3),
updatedAt: updatedIso,
expiresAt: data.expiresAt,
status: data.status || 'riding',
sharingEnabled: data.sharingEnabled !== false,
freshness,
diffSec,
},
}));
} else {
setLivePresences((prev) => {
const next = { ...prev };
delete next[uid];
return next;
});
}
setIsPresenceLoading(false);
},
(err) => {
if (!active || generation !== presenceGeneration) return;
console.warn('[LivePackRadar] Presence point-read warning for', uid, err.message);
if (err.message.includes('permission-denied') || (err as any).code === 'permission-denied') {
denyPresenceAccess();
} else {
failPresenceStream('Live formation telemetry could not be loaded. Reopen the ride to retry.');
}
setIsPresenceLoading(false);
}
);
unsubs.push(unsubPresence);
});
},
(err) => {
if (!active) return;
console.warn('[LivePackRadar] Ride snapshot warning:', err.message);
if (err.message.includes('permission-denied') || (err as any).code === 'permission-denied') {
denyPresenceAccess();
} else {
failPresenceStream('Live formation telemetry could not be loaded. Reopen the ride to retry.');
}
setIsPresenceLoading(false);
}
);
// Heartbeat ticker to refresh active/stale/disconnected status labels
const ticker = setInterval(() => {
setLivePresences((prev) => {
let changed = false;
const next: Record<string, LivePresenceRecord> = {};
for (const [id, r] of Object.entries(prev)) {
const { freshness, diffSec } = computeFreshness(r.updatedAt, r.sharingEnabled);
if (freshness !== r.freshness || Math.abs(diffSec - r.diffSec) > 1) {
changed = true;
}
next[id] = { ...r, freshness, diffSec };
}
return changed ? next : prev;
});
}, 2000);
return () => {
active = false;
clearInterval(ticker);
unsubRide();
stopPresenceStreams();
};
}, [selectedRide?.id, isRealRide, currentUser?.uid]);
// Transform live presences into telemetry cards when in real ride mode
const liveParticipantsList = useMemo(() => {
return Object.values(livePresences).sort((a, b) => {
if (a.role === 'organizer') return -1;
if (b.role === 'organizer') return 1;
return a.id.localeCompare(b.id);
});
}, [livePresences]);
// Centroid calculations for GIS map
const mapCenter = useMemo(() => {
if (liveParticipantsList.length > 0) {
const validPoints = liveParticipantsList.filter(
(p) => Math.abs(p.latitude) > 0 && Math.abs(p.longitude) > 0
);
if (validPoints.length > 0) {
const sumLat = validPoints.reduce((acc, p) => acc + p.latitude, 0);
const sumLon = validPoints.reduce((acc, p) => acc + p.longitude, 0);
return {
lat: sumLat / validPoints.length,
lon: sumLon / validPoints.length,
};
}
}
if (selectedRide?.meetingPointCoords) {
return {
lat: selectedRide.meetingPointCoords.lat,
lon: selectedRide.meetingPointCoords.lon,
};
}
// Default Blood Mountain Canyon fallback
return { lat: 34.734, lon: -83.9195 };
}, [liveParticipantsList, selectedRide]);
// Dynamic simulation riders for public showcase or demo fallback
const simulatedRiders: RadarRiderTelemetry[] = INITIAL_RADAR_RIDERS.map((r, idx) => {
const speedWobble = simulationTick > 0 ? ((simulationTick + idx * 3) % 5) - 2 : 0;
return {
...r,
speedMph: Math.max(0, r.speedMph + speedWobble),
};
});
// Effective rider cards displayed in telemetry sidebar
const displayedRiders: RadarRiderTelemetry[] = useMemo(() => {
if (isRealRide) {
return liveParticipantsList.map((p, idx) => {
const isLead = p.role === 'organizer' || idx === 0;
return {
id: p.id,
callsign: p.callsign,
role: isLead ? 'Lead' : 'Wingman',
roleBadge: isLead ? 'Flight Lead' : 'Formation Wing',
speedMph: p.speedMph,
leanAngleDeg: Math.round(((p.heading * 7) % 36) - 18),
gapMeters: isLead ? 0 : 25 + idx * 10,
gapDeltaSec: isLead ? 0 : Number((0.4 + idx * 0.2).toFixed(1)),
batteryPct: 92,
meshPingMs: p.freshness === 'active' ? 12 : p.freshness === 'stale' ? 180 : 999,
color: isLead ? '#00F2FE' : '#39FF14',
status:
p.freshness === 'active'
? 'active'
: p.freshness === 'stale'
? 'warning'
: 'alert',
alertText:
p.freshness === 'stale'
? `Stale Signal (+${Math.round(p.diffSec)}s lag)`
: p.freshness === 'disconnected'
? `Lost Link (>120s offline)`
: undefined,
coordinates: { x: 50, y: 50 },
};
});
}
return simulatedRiders;
}, [isRealRide, liveParticipantsList, simulatedRiders]);
// Selected rider for inspector drawer
const selectedRider =
displayedRiders.find((r) => r.id === selectedRiderId) || displayedRiders[0];
const handlePulseSimulation = () => {
setSimulationTick((prev) => prev + 1);
setPingMessage(
'[Simulation Demo] Software Pack Simulation: Emulating virtual nodes (No physical mesh hardware required)...'
);
setTimeout(() => {
setPingMessage(
'[Simulation Demo] Virtual Swarm Responsive: 6/6 demo nodes simulated • 11ms emulated latency'
);
setTimeout(() => setPingMessage(null), 3500);
}, 400);
};
// Header and session titles
const sessionTitle = selectedRide
? selectedRide.routeSnippet
? `Swarm Recon: ${selectedRide.routeSnippet}`
: `Swarm Recon: ${selectedRide.title}`
: 'Swarm Recon: Skyline Blvd > Coast Hwy';
const sessionCode = selectedRide?.rideCode
? `Session #${selectedRide.rideCode}`
: 'Session #CA-84-SW';
const formationProfile = selectedRide?.formationProfile || 'Staggered Dual Column [B]';
const regroupPoint = selectedRide
? `${selectedRide.meetingPoint} (${selectedRide.distance || '14.8 mi'})`
: "Alice's Restaurant (14.8 mi)";
// Pan drag interactions for GIS canvas
const handleMouseDown = (e: React.MouseEvent) => {
if (!isRealRide) return;
setIsDragging(true);
dragStartRef.current = {
x: e.clientX,
y: e.clientY,
panX: panOffset.x,
panY: panOffset.y,
};
};
const handleMouseMove = (e: React.MouseEvent) => {
if (!isDragging) return;
const dx = e.clientX - dragStartRef.current.x;
const dy = e.clientY - dragStartRef.current.y;
setPanOffset({
x: dragStartRef.current.panX + dx,
y: dragStartRef.current.panY + dy,
});
};
const handleMouseUp = () => {
setIsDragging(false);
};
// Compute map tiles covering current viewport
const tileElements = useMemo(() => {
if (!isRealRide) return null;
const scale = 256 * Math.pow(2, zoom);
const centerX = ((mapCenter.lon + 180) / 360) * scale;
const sinLat = Math.sin((mapCenter.lat * Math.PI) / 180);
const clampedSin = Math.max(-0.9999, Math.min(0.9999, sinLat));
const centerY = (0.5 - Math.log((1 + clampedSin) / (1 - clampedSin)) / (4 * Math.PI)) * scale;
const originX = centerX - viewportDim.width / 2 - panOffset.x;
const originY = centerY - viewportDim.height / 2 - panOffset.y;
const minTileX = Math.floor(originX / 256);
const maxTileX = Math.floor((originX + viewportDim.width) / 256);
const minTileY = Math.floor(originY / 256);
const maxTileY = Math.floor((originY + viewportDim.height) / 256);
const tiles: Array<{ key: string; x: number; y: number; left: number; top: number; url: string }> = [];
for (let tx = minTileX; tx <= maxTileX; tx++) {
for (let ty = minTileY; ty <= maxTileY; ty++) {
const left = tx * 256 - originX;
const top = ty * 256 - originY;
let url = '';
if (mapMode === 'satellite') {
// Amazon Location Service raster satellite tile service
url = getAwsSatelliteTileAt(zoom, tx, ty);
} else if (mapMode === 'elevation') {
// OpenTopoMap Tile Service with terrain contours
url = `https://tile.opentopomap.org/${zoom}/${tx}/${ty}.png`;
} else {
// Tactical Dark: OpenStreetMap Tile Service with inverted dark filter
url = `https://tile.openstreetmap.org/${zoom}/${tx}/${ty}.png`;
}
tiles.push({
key: `${zoom}-${tx}-${ty}-${mapMode}`,
x: tx,
y: ty,
left,
top,
url,
});
}
}
return tiles;
}, [isRealRide, mapCenter, zoom, viewportDim, panOffset, mapMode]);
return (
<div
className="bg-surface-container-lowest rounded-xl shadow-2xl overflow-hidden border border-outline-variant/30"
data-testid="live-pack-radar"
>
{/* 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 flex-wrap">
<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">
{sessionTitle}
</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">
{sessionCode}
</span>
{/* Explicit Public Demonstration Label when in simulation mode */}
{(!isRealRide || isPublicShowcase) && (
<span
className="px-2 py-0.5 rounded bg-primary-container/20 border border-primary-container/40 text-primary-container text-[11px] font-mono font-semibold"
data-testid="showcase-simulation-pill"
>
Public Demonstration (Simulated Pack)
</span>
)}
{isRealRide && (
<span
className="px-2 py-0.5 rounded bg-secondary-fixed/20 border border-secondary-fixed/40 text-secondary-fixed text-[11px] font-mono font-semibold"
data-testid="authenticated-cockpit-pill"
>
Authenticated Cockpit Live
</span>
)}
{allRides && allRides.length > 0 && onSelectRide && (
<div className="flex items-center gap-2">
<span className="text-[11px] font-mono text-on-surface-variant hidden md:inline">Run:</span>
<select
aria-label="Select Active Formation Run"
value={selectedRide?.id || 'default-ca-84'}
onChange={(e) => {
if (e.target.value === 'default-ca-84') {
onSelectRide({
id: 'session-ca-84-sw',
title: 'CA-84-SW Swarm Recon',
description: 'Public Reference Pack Run',
region: 'San Mateo Canyons',
scheduledDate: 'Reference',
scheduledTime: '10:00 AM',
paceClassification: 'Brisk Pace',
formationProfile: 'Staggered Dual Column [B]',
joinPolicy: 'open',
leadHandle: 'ApexLead',
leadAvatarText: 'APX',
participantCount: 6,
participantLimit: 8,
distance: '14.8 Miles',
meetingPoint: "Alice's Restaurant",
routeSnippet: 'Skyline Blvd > Coast Hwy',
status: 'underway',
});
return;
}
const found = allRides.find((r) => r.id === e.target.value);
if (found) onSelectRide(found);
}}
className="bg-surface-container text-xs font-mono text-on-surface px-2 py-1 rounded border border-outline-variant/30 outline-none focus:border-primary-container"
>
<option value="default-ca-84">#CA-84-SW: Swarm Recon Reference</option>
{allRides.map((r) => (
<option key={r.id} value={r.id}>
{r.rideCode || r.id}: {r.title} ({r.status || 'scheduled'})
</option>
))}
</select>
</div>
)}
</div>
<div className="flex items-center gap-space-lg font-code-mono text-code-mono text-on-surface-variant flex-wrap">
<span className="hidden lg:flex items-center gap-1.5 text-secondary-fixed">
<span className="material-symbols-outlined text-[18px]">cell_tower</span>
{isRealRide ? 'GNSS Mesh Active' : 'Mesh Active'}
</span>
<span className="flex items-center gap-1.5">
<span className="text-secondary-fixed">●</span>{' '}
{isRealRide ? liveParticipantsList.length : simulatedRiders.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 cursor-pointer"
title="Ping Swarm (Simulate Demo - No hardware action)"
>
<span className="material-symbols-outlined text-[16px]">sync</span>
Ping Swarm (Simulate Demo)
</button>
</div>
</div>
{/* Ping Status Notification Banner */}
{pingMessage && (
<div className="bg-primary-container/20 border-b border-primary-container/40 px-4 py-1.5 text-xs font-mono text-primary-container flex items-center justify-between">
<div className="flex items-center gap-2">
<span className="material-symbols-outlined text-[16px] animate-spin">sync</span>
<span>{pingMessage}</span>
</div>
<button onClick={() => setPingMessage(null)} className="text-on-surface-variant hover:text-on-surface">
<span className="material-symbols-outlined text-[14px]">close</span>
</button>
</div>
)}
{/* Cockpit Layout: Map & Radar Feed */}
<div className="grid grid-cols-1 lg:grid-cols-12 gap-0">
{/* Tactical Canvas & GIS Radar Screen */}
<div
ref={canvasRef}
onMouseDown={handleMouseDown}
onMouseMove={handleMouseMove}
onMouseUp={handleMouseUp}
onMouseLeave={handleMouseUp}
className={`lg:col-span-8 relative min-h-[560px] flex flex-col justify-between p-space-md overflow-hidden select-none transition-all duration-300 ${
mapMode === 'tactical'
? 'bg-[#0B1319]'
: mapMode === 'satellite'
? 'bg-[#071015]'
: 'bg-[#0E1520]'
} ${isDragging ? 'cursor-grabbing' : isRealRide ? 'cursor-grab' : 'cursor-default'}`}
data-testid="radar-canvas-container"
>
{/* ========================================================= */}
{/* SCENARIO 1: UNAUTHORIZED BARRIER */}
{/* ========================================================= */}
{isRealRide && !isAuthorized && (
<div
className="absolute inset-0 z-40 bg-surface-container-lowest/95 backdrop-blur-md flex flex-col items-center justify-center p-space-xl text-center"
data-testid="telemetry-access-barrier"
>
<div className="w-16 h-16 rounded-2xl bg-error/10 border border-error/40 flex items-center justify-center text-error mb-4 shadow-lg shadow-error/20">
<span className="material-symbols-outlined text-4xl">shield_lock</span>
</div>
<span className="font-code-mono text-xs text-error font-bold tracking-wider uppercase mb-1">
Access Restricted • Telemetry Barrier Active
</span>
<h3 className="font-headline-lg text-xl font-bold text-on-surface mb-2">
Squadron Clearance Required
</h3>
<p className="font-body-md text-sm text-on-surface-variant max-w-md mb-6 leading-relaxed">
You are not an accepted participant in this group formation. Live multi-band GNSS telemetry,
tactical radar plots, and formation spacing metrics are restricted to verified riders and the
organizer.
</p>
<div className="flex items-center gap-3">
<button
type="button"
onClick={() => {
const fallback = allRides?.find((r) => r.id === 'session-ca-84-sw');
if (fallback && onSelectRide) onSelectRide(fallback);
}}
className="px-4 py-2 rounded-xl bg-surface-container-high hover:bg-surface-container-highest border border-outline-variant/30 text-on-surface text-xs font-mono transition"
>
Return to Reference Demo
</button>
</div>
</div>
)}
{/* ========================================================= */}
{/* SCENARIO 2: AUTHENTICATED REAL GEOGRAPHIC MAP */}
{/* ========================================================= */}
{isRealRide && isAuthorized && (
<div
className="absolute inset-0 overflow-hidden pointer-events-none"
data-testid="geographic-map-view"
>
{/* Render Map Tiles with smooth loading */}
<div className="absolute inset-0 pointer-events-none">
{tileElements?.map((t) => (
<img
key={t.key}
src={t.url}
alt=""
loading="lazy"
onError={(e) => {
(e.target as HTMLElement).style.display = 'none';
}}
style={{
position: 'absolute',
left: `${t.left}px`,
top: `${t.top}px`,
width: '256px',
height: '256px',
opacity: mapMode === 'satellite' ? 0.85 : 0.7,
filter:
mapMode === 'tactical'
? 'invert(100%) hue-rotate(180deg) brightness(85%) contrast(120%)'
: mapMode === 'elevation'
? 'invert(1) hue-rotate(180deg) brightness(0.7)'
: 'none',
}}
/>
))}
</div>
{/* Tactical Geographic SVG Overlay (Route, Waypoints, Coordinate Grid) */}
<svg className="absolute inset-0 w-full h-full pointer-events-none" xmlns="http://www.w3.org/2000/svg">
<defs>
<pattern id="gis-grid-pattern" width="64" height="64" patternUnits="userSpaceOnUse">
<path d="M 64 0 L 0 0 0 64" fill="none" stroke="rgba(0, 242, 254, 0.12)" strokeWidth="0.5" />
<circle cx="0" cy="0" r="1.5" fill="rgba(0, 242, 254, 0.3)" />
</pattern>
</defs>
<rect width="100%" height="100%" fill="url(#gis-grid-pattern)" />
{/* Trajectory Polyline between participants */}
{liveParticipantsList.length >= 2 && (
<polyline
points={liveParticipantsList
.map((p) => {
const pos = projectMercator(
p.latitude,
p.longitude,
mapCenter.lat,
mapCenter.lon,
zoom,
viewportDim.width,
viewportDim.height,
panOffset.x,
panOffset.y
);
return `${pos.x},${pos.y}`;
})
.join(' ')}
fill="none"
stroke="#00F2FE"
strokeWidth="3.5"
strokeDasharray="6 4"
className="animate-pulse"
/>
)}
{/* Safe spacing buffers around participants */}
{liveParticipantsList.map((p) => {
const pos = projectMercator(
p.latitude,
p.longitude,
mapCenter.lat,
mapCenter.lon,
zoom,
viewportDim.width,
viewportDim.height,
panOffset.x,
panOffset.y
);
return (
<circle
key={`buffer-${p.id}`}
cx={pos.x}
cy={pos.y}
r="32"
fill="none"
stroke={
p.freshness === 'active'
? 'rgba(0, 242, 254, 0.35)'
: p.freshness === 'stale'
? 'rgba(255, 159, 28, 0.4)'
: 'rgba(150, 150, 150, 0.2)'
}
strokeWidth="1.5"
strokeDasharray="4 2"
/>
);
})}
</svg>
{/* Mode-specific visual backdrops */}
{mapMode === 'satellite' && (
<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 pointer-events-none"
data-testid="satellite-mode-view"
>
High-Resolution Orbit Imagery • Multi-Band GNSS Overlaid
</div>
)}
{mapMode === 'elevation' && (
<div
className="absolute top-4 left-1/2 -translate-x-1/2 px-3 py-1 rounded-full bg-surface-container/90 border border-secondary-fixed/30 text-secondary-fixed font-code-mono text-label-caps text-center pointer-events-none"
data-testid="elevation-mode-view"
>
Topographic Contour Mesh • Summit: 2,210 ft (+730 ft gain)
</div>
)}
</div>
)}
{/* ========================================================= */}
{/* SCENARIO 3: SIMULATED BACKDROPS (PUBLIC DEMO / TESTS) */}
{/* ========================================================= */}
{!isRealRide && (
<>
{mapMode === 'tactical' && (
<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)" />
<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"
/>
<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>
)}
{mapMode === 'satellite' && (
<div className="absolute inset-0 pointer-events-none" data-testid="satellite-mode-view">
<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">
<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" />
<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>
)}
{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">
<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>
<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>
<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">{formationProfile}</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 */}
<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>
{/* ========================================================= */}
{/* EMPTY PARTICIPANTS STATE */}
{/* ========================================================= */}
{isRealRide && presenceError && (
<div role="alert" data-testid="presence-error-state" className="relative z-20 my-auto p-6 rounded-2xl bg-surface-container/90 border border-error/40 text-center max-w-md mx-auto">
{presenceError}
</div>
)}
{isRealRide && isAuthorized && !presenceError && liveParticipantsList.length === 0 && !isPresenceLoading && (
<div
className="relative z-20 my-auto p-6 rounded-2xl bg-surface-container/90 border border-outline-variant/30 text-center max-w-md mx-auto backdrop-blur-md"
data-testid="empty-pack-state"
>
<span className="material-symbols-outlined text-4xl text-primary mb-2">sensors_off</span>
<h4 className="font-headline-sm text-base font-bold text-on-surface">
No Active Squadron Nodes Transmitting
</h4>
<p className="font-body-sm text-xs text-on-surface-variant mt-1 leading-relaxed">
Waiting for formation participants to enable telemetry and broadcast live GNSS coordinates.
</p>
</div>
)}
{/* ========================================================= */}
{/* PARTICIPANT MARKERS: REAL GEOGRAPHIC MAP PROJECTION */}
{/* ========================================================= */}
{isRealRide && isAuthorized && liveParticipantsList.length > 0 && (
<div className="relative z-20 my-auto h-80 w-full pointer-events-none">
{liveParticipantsList.map((p) => {
const pos = projectMercator(
p.latitude,
p.longitude,
mapCenter.lat,
mapCenter.lon,
zoom,
viewportDim.width,
viewportDim.height,
panOffset.x,
panOffset.y
);
const isSelected = selectedRiderId === p.id;
const isStale = p.freshness === 'stale';
const isDisconnected = p.freshness === 'disconnected';
return (
<div
key={p.id}
onClick={(e) => {
e.stopPropagation();
setSelectedRiderId(p.id);
}}
style={{
left: `${pos.x}px`,
top: `${pos.y}px`,
opacity: isDisconnected ? 0.45 : isStale ? 0.75 : 1,
}}
className={`absolute -translate-x-1/2 -translate-y-1/2 pointer-events-auto cursor-pointer transition-all duration-500 ease-out group ${
isSelected ? 'z-30 scale-110' : 'z-20 hover:scale-105'
}`}
data-testid={`radar-marker-${p.callsign}`}
>
{isSelected && (
<div className="absolute -inset-2.5 rounded-full border-2 border-primary-container animate-ping pointer-events-none opacity-60"></div>
)}
<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)]'
: isDisconnected
? 'bg-surface-container-low/80 text-on-surface-variant border border-outline-variant/40'
: isStale
? 'bg-surface-container-high/90 text-on-surface border border-amber-500/60'
: 'bg-surface-container-high/90 text-on-surface border border-outline-variant/30 hover:border-primary-container'
}`}
>
<span
className="material-symbols-outlined text-[14px] transition-transform duration-300"
style={{ transform: `rotate(${p.heading || 0}deg)` }}
>
navigation
</span>
<span
className={`w-2 h-2 rounded-full ${
isDisconnected
? 'bg-outline'
: isStale
? 'bg-amber-400 animate-pulse'
: 'bg-secondary-fixed animate-pulse'
}`}
></span>
<span className="font-code-mono text-code-mono font-bold">{p.callsign}</span>
<span
className={`font-label-caps text-label-caps font-semibold ${
isSelected
? 'text-on-primary-container'
: isDisconnected
? 'text-on-surface-variant'
: isStale
? 'text-amber-400'
: 'text-secondary-fixed'
}`}
>
{p.speedMph} MPH
</span>
{isStale && (
<span className="text-[10px] font-mono px-1 py-0.2 rounded bg-amber-500/20 text-amber-300">
+{Math.round(p.diffSec)}s
</span>
)}
{isDisconnected && (
<span className="text-[10px] font-mono px-1 py-0.2 rounded bg-surface-container text-on-surface-variant">
Offline
</span>
)}
</div>
</div>
);
})}
</div>
)}
{/* ========================================================= */}
{/* SIMULATED RIDERS: PUBLIC SHOWCASE MARKERS */}
{/* ========================================================= */}
{!isRealRide && (
<div className="relative z-10 my-auto h-72 w-full">
{simulatedRiders.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}`}
>
{isSelected && (
<div className="absolute -inset-2 rounded-full border-2 border-primary-container animate-ping pointer-events-none opacity-60"></div>
)}
<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>
{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 cursor-pointer ${
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 cursor-pointer ${
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 cursor-pointer ${
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>
{/* GIS Map Controls (Zoom + Recenter) */}
{isRealRide && (
<div className="flex items-center gap-1 bg-surface/90 backdrop-blur-md px-2 py-1 rounded-xl border border-outline-variant/30 font-mono text-xs">
<button
type="button"
onClick={() => setZoom((z) => Math.min(18, z + 1))}
className="px-2 py-0.5 rounded hover:bg-surface-container text-primary font-bold transition cursor-pointer"
title="Zoom In"
>
+
</button>
<span className="text-on-surface-variant text-[11px]">Z{zoom}</span>
<button
type="button"
onClick={() => setZoom((z) => Math.max(10, z - 1))}
className="px-2 py-0.5 rounded hover:bg-surface-container text-primary font-bold transition cursor-pointer"
title="Zoom Out"
>
-
</button>
<button
type="button"
onClick={() => setPanOffset({ x: 0, y: 0 })}
className="ml-1 px-1.5 py-0.5 rounded bg-surface-container hover:bg-surface-container-high text-secondary-fixed text-[11px] flex items-center gap-1 transition cursor-pointer"
title="Recenter Map on Pack"
>
<span className="material-symbols-outlined text-[13px]">my_location</span>
Recenter
</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">
{regroupPoint}
</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">
{displayedRiders.length} Active Nodes
</span>
</div>
{/* Active Rider Telemetry Cards */}
{displayedRiders.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>
{isRealRide && livePresences[selectedRider.id] && (
<div className="mt-1 pt-1 border-t border-outline-variant/20 text-[11px] font-mono text-on-surface-variant flex justify-between">
<span>
GPS: {livePresences[selectedRider.id].latitude.toFixed(4)},{' '}
{livePresences[selectedRider.id].longitude.toFixed(4)}
</span>
<span className="text-primary">
HDG: {Math.round(livePresences[selectedRider.id].heading)}°
</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">
{isRealRide && liveParticipantsList.length > 0
? (
liveParticipantsList.reduce((acc, p) => acc + p.speedMph, 0) /
liveParticipantsList.length
).toFixed(1)
: '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 &amp; 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">
{selectedRide?.meetingPoint || "Alice's Restaurant Checkpoint"}
</span>
</div>
</div>
</div>
);
}