// Monochrome line icons, 1.5px stroke. Lucide-style. const Icon = ({ d, size = 20, stroke = "currentColor", children, viewBox="0 0 24 24", fill="none" }) => ( ); const IconCircular = (p) => (); const IconChat = (p) => (); const IconShield = (p) => (); const IconChart = (p) => (); const IconPlug = (p) => (); const IconCheck = (p) => (); const IconMenu = (p) => (); const LogoMark = ({ size = 22 }) => ( Reexo ); /* Hero Route Visualization — ANIMATED. A single concatenated SVG path (4 Bézier legs) drives a truck via getPointAtLength. - HUD top-left updates live: active leg, km done, ETA. - Waypoints (REST / FUEL / TOLL) activate as the truck passes, then fade back down. - Cities flash when reached. Valencia (base) pulses, and flares green when loop closes. - One full loop cycles over LOOP_MS; short dwell at Valencia before restart. */ const RouteDiagram = () => { const pathRef = React.useRef(null); const [progress, setProgress] = React.useState(0); // 0..1 along full route const [dwelling, setDwelling] = React.useState(false); // true during the pause at VLC // Real-world leg distances (km). Used for HUD readout + trail tinting. // Proportions here determine where along the compound path each leg ends. const LEGS = React.useMemo(() => ([ { from: "VLC", to: "MAD", km: 357, color: "#0A0A0A" }, { from: "MAD", to: "ZGZ", km: 315, color: "#0A0A0A" }, { from: "ZGZ", to: "BCN", km: 296, color: "#0A0A0A" }, { from: "BCN", to: "VLC", km: 349, color: "#1FB58A" }, // return leg ]), []); const TOTAL_KM = LEGS.reduce((s,l)=>s+l.km, 0); // 1317 (rounded to 1,480 in the HUD label for 'km planned' including approach) // Waypoint trigger thresholds (fraction of full path where each is active). // These roughly map to: REST mid-leg1, FUEL on leg2, TOLL on leg3. const REST_AT = 0.12; const FUEL_AT = 0.35; const TOLL_AT = 0.60; const WP_WINDOW = 0.09; // how long each stays highlighted, in progress units const cityReached = (p) => { // returns which city the truck has most recently passed if (p < 0.25) return 0; // still on leg 1 (heading MAD) if (p < 0.50) return 1; // reached MAD if (p < 0.75) return 2; // reached ZGZ if (p < 1.00) return 3; // reached BCN return 4; // back at VLC }; // animation loop React.useEffect(() => { let raf, start = 0, dwellUntil = 0; const LOOP_MS = 14000; const DWELL_MS = 1500; let mounted = true; const tick = (ts) => { if (!mounted) return; if (!start) start = ts; if (dwellUntil) { if (ts >= dwellUntil) { start = ts; dwellUntil = 0; setDwelling(false); setProgress(0); } raf = requestAnimationFrame(tick); return; } const p = Math.min(1, (ts - start) / LOOP_MS); setProgress(p); if (p >= 1) { dwellUntil = ts + DWELL_MS; setDwelling(true); } raf = requestAnimationFrame(tick); }; raf = requestAnimationFrame(tick); return () => { mounted = false; cancelAnimationFrame(raf); }; }, []); // lookup current xy + tangent on the compound path const ptRef = React.useRef({ x: 412, y: 282, angle: 0, len: 0 }); const [, force] = React.useReducer(x => x+1, 0); React.useEffect(() => { const el = pathRef.current; if (!el) return; const L = el.getTotalLength(); const at = L * progress; const pt = el.getPointAtLength(at); const pt2 = el.getPointAtLength(Math.min(L, at + 1)); const angle = Math.atan2(pt2.y - pt.y, pt2.x - pt.x) * 180 / Math.PI; ptRef.current = { x: pt.x, y: pt.y, angle, len: L }; force(); }, [progress]); // HUD values const activeCity = cityReached(progress); const activeLegIdx = Math.min(3, Math.floor(progress * 4)); const activeLeg = LEGS[activeLegIdx]; const legFrac = (progress * 4) - activeLegIdx; const kmDone = Math.round( LEGS.slice(0, activeLegIdx).reduce((s,l)=>s+l.km,0) + activeLeg.km * legFrac ); const etaH = Math.max(0, Math.round((TOTAL_KM - kmDone) / 70)); // ~70km/h avg const netNow = Math.round(2840 * Math.min(1, progress * 1.05)); // fill up profit as we progress // waypoint "hot" helpers const hot = (trigger) => { const d = progress - trigger; if (d < 0 || d > WP_WINDOW) return 0; // ease in/out const t = d / WP_WINDOW; return Math.sin(t * Math.PI); // 0..1..0 }; const hotREST = hot(REST_AT); const hotFUEL = hot(FUEL_AT); const hotTOLL = hot(TOLL_AT); // city "just reached" flash: 0..1 flash intensity const reachedFlash = (atStart, atEnd) => { // active around the arrival moment (atEnd) const d = progress - atEnd; if (Math.abs(d) > 0.04) return 0; return 1 - Math.abs(d) / 0.04; }; const flashMAD = reachedFlash(0, 0.25); const flashZGZ = reachedFlash(0.25, 0.50); const flashBCN = reachedFlash(0.50, 0.75); const flashVLC = dwelling ? 1 : reachedFlash(0.75, 1.0); // compound path (must match the 4 drawn leg paths below, joined head-to-tail) const COMPOUND_D = "M 412 282 Q 340 258, 278 230 Q 305 200, 342 180 Q 395 182, 452 192 Q 445 245, 412 282"; return (
{/* HUD header */} LEG {activeLegIdx + 1}/4 · {activeLeg.from} → {activeLeg.to} · {kmDone.toLocaleString()} / {TOTAL_KM.toLocaleString()} KM {dwelling ? "LOOP CLOSED · NET PROFIT €2,840" : `ETA HOME ${etaH}H · NET PROFIT €${netNow.toLocaleString()}`} {/* LIVE dot */} LIVE {/* Iberian peninsula */} {/* full route — faint base layer (for "planned but not yet driven") */} {/* Black driven trail — legs 1..3 up to min(progress, .75) */} {/* Green driven trail — leg 4 only (return leg) */} {progress > 0.75 && ( )} {/* Waypoint: REST — under leg 1 */} 0.1 ? "#1FB58A" : "#0A0A0A"}/> 0.1 ? "#1FB58A" : "#E8E6E1"} strokeWidth={hotREST > 0.1 ? 1.2 : 1}/> REST · 45 MIN EU 561/2006 {/* Waypoint: FUEL — upper-left of leg 2 */} 0.1 ? "#1FB58A" : "#0A0A0A"}/> 0.1 ? "#1FB58A" : "#E8E6E1"} strokeWidth={hotFUEL > 0.1 ? 1.2 : 1}/> FUEL · €1.42/L A-2 · 320 L {/* Waypoint: TOLL — top of leg 3 */} 0.1 ? "#1FB58A" : "#0A0A0A"}/> 0.1 ? "#1FB58A" : "#E8E6E1"} strokeWidth={hotTOLL > 0.1 ? 1.2 : 1}/> TOLL AP-2 €47.10 {/* City: Valencia (BASE) */} Valencia BASE · 01 {/* City: Madrid */} 0.1 ? "#1FB58A" : "#0A0A0A"}/> {flashMAD > 0.1 && ( )} Madrid 02 · 22T PALLET {/* City: Zaragoza */} 0.1 ? "#1FB58A" : "#0A0A0A"}/> {flashZGZ > 0.1 && ( )} Zaragoza 03 · 18T BOX {/* City: Barcelona */} 0.1 ? "#1FB58A" : "#0A0A0A"}/> {flashBCN > 0.1 && ( )} Barcelona 04 · 24T REEFER {/* TRUCK — draws last so it sits on top of everything */} {/* subtle heading glow behind the truck */} {/* cab + trailer, oriented along x+ (tangent direction). Centered on pivot. */} {/* trailer */} {/* cab */} {/* windshield */} {/* headlight beam */} {/* Legend */} LOADED RETURN PLANNED WAYPOINT
); }; // RouteDiagram is provided by src/route_diagram.jsx (loaded after this file). Object.assign(window, { Icon, IconCircular, IconChat, IconShield, IconChart, IconPlug, IconCheck, IconMenu, LogoMark });