diff --git a/src/layouts/AdminLayout.jsx b/src/layouts/AdminLayout.jsx index 97bd356..6a8d24e 100644 --- a/src/layouts/AdminLayout.jsx +++ b/src/layouts/AdminLayout.jsx @@ -271,6 +271,8 @@ export default function AdminLayout() { const displayName = user?.displayname || user?.authname || user?.name || 'Operator'; const isHome = location.pathname === '/doormile/home' || location.pathname === '/doormile' || location.pathname === '/'; + const isAgentsPage = location.pathname === '/doormile/agents' || location.pathname.startsWith('/doormile/agents'); + const hideAssistant = isHome || isAgentsPage; return ( /* Transparent so the ambient canvas painted behind the app reads through. @@ -519,7 +521,7 @@ export default function AdminLayout() { page runs the assistant as the page itself, so neither the docked panel nor its launcher belongs there. Splitting these two rules apart is how you end up with a button that opens nothing. */} - {!isHome && ( + {!hideAssistant && ( <> toggleAssistant((open) => !open)} /> toggleAssistant(false)} /> diff --git a/src/pages/doormile/agents/AgentFlowVisualizer.jsx b/src/pages/doormile/agents/AgentFlowVisualizer.jsx index 0c64cb0..53a81e5 100644 --- a/src/pages/doormile/agents/AgentFlowVisualizer.jsx +++ b/src/pages/doormile/agents/AgentFlowVisualizer.jsx @@ -89,6 +89,31 @@ const AGENT_NETWORK_STRUCTURE = { ], }; +// ==============================|| COLOR BLENDING HELPER ||============================== // + +/** + * Smoothly blends between two hex colors based on progress (0 to 1). + * Used for dynamic photon color transitions as events route through JARVIS. + */ +function interpolateColor(color1, color2, factor) { + try { + const c1 = color1.replace('#', ''); + const c2 = color2.replace('#', ''); + const r1 = parseInt(c1.substring(0, 2), 16); + const g1 = parseInt(c1.substring(2, 4), 16); + const b1 = parseInt(c1.substring(4, 6), 16); + const r2 = parseInt(c2.substring(0, 2), 16); + const g2 = parseInt(c2.substring(2, 4), 16); + const b2 = parseInt(c2.substring(4, 6), 16); + const r = Math.round(r1 + factor * (r2 - r1)); + const g = Math.round(g1 + factor * (g2 - g1)); + const b = Math.round(b1 + factor * (b2 - b1)); + return `rgb(${r}, ${g}, ${b})`; + } catch { + return color1; + } +} + // ==============================|| MINIMAL AGENT NODE ||============================== // /** @@ -235,10 +260,23 @@ export default function AgentFlowVisualizer({ const [particle, setParticle] = useState({ x: 0, y: 0, + color: '#059669', visible: false, trail: [], }); + // Dynamic Impact Ripples (Tactile arrival shockwaves) + const [impactRipples, setImpactRipples] = useState([]); + + const triggerImpact = (x, y, color = currentScenario.color) => { + if (!x || !y) return; + const id = `${Date.now()}-${Math.random()}`; + setImpactRipples((prev) => [...prev.slice(-4), { id, x, y, color }]); + setTimeout(() => { + setImpactRipples((prev) => prev.filter((r) => r.id !== id)); + }, 850); + }; + // Update SVG node coordinates dynamically on resize and scenario change const updateCoordinates = () => { if (!canvasRef.current) return; @@ -344,6 +382,15 @@ export default function AgentFlowVisualizer({ const currentDist = progress * totalLen; const pt = pathElement.getPointAtLength(currentDist); + // Dynamic Photon Color: Shifts towards JARVIS royal indigo (#6366f1) on Path 2, + // and transitions from JARVIS into target agent's color on Path 3 + let particleColor = currentScenario.color; + if (segmentName === 'path2') { + particleColor = interpolateColor(currentScenario.color, '#6366f1', progress); + } else if (segmentName === 'path3') { + particleColor = interpolateColor('#6366f1', currentScenario.color, progress); + } + // Calculate trailing comet micro-beads const t1 = pathElement.getPointAtLength(Math.max(0, currentDist - 12)); const t2 = pathElement.getPointAtLength(Math.max(0, currentDist - 22)); @@ -352,6 +399,7 @@ export default function AgentFlowVisualizer({ setParticle({ x: pt.x, y: pt.y, + color: particleColor, visible: true, trail: [t1, t2, t3], }); @@ -373,7 +421,8 @@ export default function AgentFlowVisualizer({ setIsProcessing(false); timeoutId = setTimeout(() => { runParticleAlongPath(path1Ref.current, 'path1', 850, () => { - // Arrives at Stage 2 + // Arrives at Stage 2 Event Bus -> tactile arrival ripple + triggerImpact(p2.x, p2.y, currentScenario.color); setCurrentEventFeed({ text: currentScenario.step1Human, tech: currentScenario.step1Tech, @@ -387,18 +436,24 @@ export default function AgentFlowVisualizer({ // Step 2: Information delivered, particle travels from Stage 2 to JARVIS timeoutId = setTimeout(() => { runParticleAlongPath(path2Ref.current, 'path2', 750, () => { + // Arrives at JARVIS Orchestrator Apex -> royal indigo routing shockwave! + triggerImpact(pJarvis.x, pJarvis.y, '#6366f1'); setStep(3); }); }, 400); } else if (step === 3) { // Step 3: JARVIS routes to Target Agent, Agent pulses & processes, then routes to Result runParticleAlongPath(path3Ref.current, 'path3', 600, () => { + // Arrives at Target Agent -> agent activation ripple! + triggerImpact(pAgent.x, pAgent.y, currentScenario.color); setIsProcessing(true); // Agent processes briefly for 800ms timeoutId = setTimeout(() => { setIsProcessing(false); // Particle travels from Agent to Result runParticleAlongPath(path4Ref.current, 'path4', 850, () => { + // Arrives at Stage 4 Result -> successful resolution ripple! + triggerImpact(p4.x, p4.y, currentScenario.color); if (onActivityLogged) { onActivityLogged({ agent: currentScenario.agentName, @@ -471,7 +526,10 @@ export default function AgentFlowVisualizer({ {/* Clean Scenario Pill Buttons */} -
+ {/* flex-wrap, because four scenario pills plus Pause measure 387px and a + phone viewport is 375 — without it the row pushed the whole page 56px + wide and every screen on the site scrolled sideways, not just this one. */} +
{SCENARIOS.map((scen, idx) => { const isSelected = scenarioIndex === idx; return ( @@ -610,6 +668,32 @@ export default function AgentFlowVisualizer({ /> )} + {/* Dynamic Tactile Arrival Ripples (Shockwaves on node contact) */} + {impactRipples.map((ripple) => ( + + {/* Inner High-Energy Ring */} + + {/* Outer Shockwave Propagation Wave */} + + + ))} + {/* The Moving Event Particle with Fading Comet Trail */} {particle.visible && !reduce && ( @@ -619,7 +703,7 @@ export default function AgentFlowVisualizer({ cx={particle.trail[2].x} cy={particle.trail[2].y} r="1.8" - fill={currentScenario.color} + fill={particle.color || currentScenario.color} opacity="0.3" /> )} @@ -629,7 +713,7 @@ export default function AgentFlowVisualizer({ cx={particle.trail[1].x} cy={particle.trail[1].y} r="2.5" - fill={currentScenario.color} + fill={particle.color || currentScenario.color} opacity="0.55" /> )} @@ -639,7 +723,7 @@ export default function AgentFlowVisualizer({ cx={particle.trail[0].x} cy={particle.trail[0].y} r="3.2" - fill={currentScenario.color} + fill={particle.color || currentScenario.color} opacity="0.8" /> )} @@ -649,7 +733,7 @@ export default function AgentFlowVisualizer({ cy={particle.y} r="4.5" fill="#ffffff" - stroke={currentScenario.color} + stroke={particle.color || currentScenario.color} strokeWidth="2.5" filter="url(#light-particle-glow)" /> @@ -664,7 +748,7 @@ export default function AgentFlowVisualizer({ Mobile: 1-column vertical story (Step 4 has guaranteed reserved space and can never be pushed out!) ═══════════════════════════════════════════════════════════════ */} -
+
{/* ── STAGE 01: SOMETHING HAPPENS ── */}
@@ -801,8 +885,11 @@ export default function AgentFlowVisualizer({
- + {currentScenario.step4Human} - + {currentScenario.step4Sub}