// SVG cable layer with animated electric flow const CableLayer = ({ sources, consumers, connections, allocations, pendingCable, mousePos, onCableClick, period, isMobile }) => { // Returns port world coords. Source out: right edge. Consumer in: left edge. // Card widths/heights differ between desktop and mobile. const srcW = isMobile ? 180 : 240; const srcH = isMobile ? 160 : 182; // approximate full card height const conW = isMobile ? 160 : 200; const conH = isMobile ? 130 : 158; const srcCenterY = srcH / 2; const conCenterY = conH / 2; const sourcePort = (s) => ({ x: s.x + srcW, y: s.y + srcCenterY }); const consumerPort = (c) => ({ x: c.x, y: c.y + conCenterY }); // Build a smooth bezier path between two points const bezier = (a, b) => { const dx = Math.abs(b.x - a.x); const sag = Math.min(60, dx * 0.15) + 30; // gravity-like sag const c1x = a.x + dx * 0.5; const c1y = a.y + sag; const c2x = b.x - dx * 0.5; const c2y = b.y + sag; return `M ${a.x} ${a.y} C ${c1x} ${c1y}, ${c2x} ${c2y}, ${b.x} ${b.y}`; }; return ( {/* glow filter */} {/* gradients */} {/* existing connections */} {connections.map((conn) => { const src = sources.find(s => s.id === conn.sourceId); const cons = consumers.find(c => c.id === conn.consumerId); if (!src || !cons) return null; const a = sourcePort(src); const b = consumerPort(cons); const path = bezier(a, b); // Determine cable state const alloc = allocations[conn.id] || 0; const sourceOverloaded = (allocations.__sourceDraw?.[src.id] || 0) > src.amount && src.amount > 0; const consumerFunded = (allocations.__consumerSupply?.[cons.id] || 0) >= cons.amount && cons.amount > 0; const inactive = alloc === 0; const stroke = sourceOverloaded ? 'url(#cable-red)' : consumerFunded ? 'url(#cable-cyan)' : inactive ? '#2a3340' : 'url(#cable-amber)'; const flowColor = sourceOverloaded ? '#ff5577' : consumerFunded ? '#4cf3ff' : inactive ? '#475061' : '#ffc36b'; return ( {/* hit area */} { e.stopPropagation(); onCableClick(conn.id); }} /> {/* outer glow */} {/* base cable */} {/* flow animation */} {!inactive && ( )} {/* end caps */} ); })} {/* pending (drag) cable */} {pendingCable && mousePos && (() => { const src = sources.find(s => s.id === pendingCable.sourceId); if (!src) return null; const a = sourcePort(src); const b = mousePos; const path = bezier(a, b); return ( ); })()} ); }; window.CableLayer = CableLayer;