fix: resolve connection graph lines stretching and flow visual bugs using ResizeObserver
This commit is contained in:
-350
File diff suppressed because one or more lines are too long
+350
File diff suppressed because one or more lines are too long
Vendored
+1
-1
@@ -7,7 +7,7 @@
|
|||||||
<link rel="manifest" href="/manifest.webmanifest" />
|
<link rel="manifest" href="/manifest.webmanifest" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||||
<title>Mission Control 2.0</title>
|
<title>Mission Control 2.0</title>
|
||||||
<script type="module" crossorigin src="/assets/index-BSlax7wE.js"></script>
|
<script type="module" crossorigin src="/assets/index-CFlBNApe.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="/assets/index-mQ1DUIFB.css">
|
<link rel="stylesheet" crossorigin href="/assets/index-mQ1DUIFB.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useState } from "react"
|
import { useEffect, useState, useRef } from "react"
|
||||||
import { Bot, ExternalLink, Activity, Cpu, Wrench, Shield, X, Check, Copy } from "lucide-react"
|
import { Bot, ExternalLink, Activity, Cpu, Wrench, Shield, X, Check, Copy } from "lucide-react"
|
||||||
import { api, type AgentStatus, type ModelInfo, type RoutingResp, type ConnectResp } from "@/lib/api"
|
import { api, type AgentStatus, type ModelInfo, type RoutingResp, type ConnectResp } from "@/lib/api"
|
||||||
import { cn, resolveExternalUrl } from "@/lib/utils"
|
import { cn, resolveExternalUrl } from "@/lib/utils"
|
||||||
@@ -48,6 +48,50 @@ export function AgentView() {
|
|||||||
const [copied, setCopied] = useState(false)
|
const [copied, setCopied] = useState(false)
|
||||||
const [hoveredNode, setHoveredNode] = useState<string | null>(null)
|
const [hoveredNode, setHoveredNode] = useState<string | null>(null)
|
||||||
|
|
||||||
|
// Canvas pixel tracking for pixel-perfect connection graph without non-uniform scaling
|
||||||
|
const [dimensions, setDimensions] = useState({ width: 800, height: 360 })
|
||||||
|
const containerRef = useRef<HTMLDivElement>(null)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!containerRef.current) return
|
||||||
|
const observer = new ResizeObserver((entries) => {
|
||||||
|
if (!entries || entries.length === 0) return
|
||||||
|
const rect = entries[0].contentRect
|
||||||
|
setDimensions({ width: rect.width, height: rect.height })
|
||||||
|
})
|
||||||
|
observer.observe(containerRef.current)
|
||||||
|
return () => observer.disconnect()
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const w = dimensions.width
|
||||||
|
const h = dimensions.height
|
||||||
|
|
||||||
|
// Helper to generate curve from client to gateway
|
||||||
|
const getClientPath = (yPercent: number) => {
|
||||||
|
const startX = w * 0.1
|
||||||
|
const startY = h * yPercent
|
||||||
|
const endX = w * 0.5
|
||||||
|
const endY = h * 0.5
|
||||||
|
const cp1X = w * 0.3
|
||||||
|
const cp1Y = startY
|
||||||
|
const cp2X = w * 0.3
|
||||||
|
const cp2Y = endY
|
||||||
|
return `M ${startX} ${startY} C ${cp1X} ${cp1Y}, ${cp2X} ${cp2Y}, ${endX} ${endY}`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper to generate curve from gateway to role
|
||||||
|
const getRolePath = (yPercent: number) => {
|
||||||
|
const startX = w * 0.5
|
||||||
|
const startY = h * 0.5
|
||||||
|
const endX = w * 0.9
|
||||||
|
const endY = h * yPercent
|
||||||
|
const cp1X = w * 0.7
|
||||||
|
const cp1Y = startY
|
||||||
|
const cp2X = w * 0.7
|
||||||
|
const cp2Y = endY
|
||||||
|
return `M ${startX} ${startY} C ${cp1X} ${cp1Y}, ${cp2X} ${cp2Y}, ${endX} ${endY}`
|
||||||
|
}
|
||||||
|
|
||||||
function loadData() {
|
function loadData() {
|
||||||
api<AgentStatus>("/api/agent/status").then(setS).catch((e) => setError(String(e)))
|
api<AgentStatus>("/api/agent/status").then(setS).catch((e) => setError(String(e)))
|
||||||
|
|
||||||
@@ -103,12 +147,12 @@ export function AgentView() {
|
|||||||
<style>{`
|
<style>{`
|
||||||
@keyframes flow-dash {
|
@keyframes flow-dash {
|
||||||
to {
|
to {
|
||||||
stroke-dashoffset: -40;
|
stroke-dashoffset: -20;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.animate-flow-cyan {
|
.animate-flow-cyan {
|
||||||
stroke-dasharray: 8 24;
|
stroke-dasharray: 4 6;
|
||||||
animation: flow-dash 1.2s linear infinite;
|
animation: flow-dash 1s linear infinite;
|
||||||
}
|
}
|
||||||
`}</style>
|
`}</style>
|
||||||
|
|
||||||
@@ -187,8 +231,8 @@ export function AgentView() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* The Graph Canvas Area */}
|
{/* The Graph Canvas Area */}
|
||||||
<div className="relative w-full h-[360px] border border-border/30 rounded-xl bg-black/25 overflow-hidden flex">
|
<div ref={containerRef} className="relative w-full h-[360px] border border-border/30 rounded-xl bg-black/25 overflow-hidden flex">
|
||||||
<svg className="absolute inset-0 pointer-events-none w-full h-full" viewBox="0 0 100 100" preserveAspectRatio="none">
|
<svg className="absolute inset-0 pointer-events-none w-full h-full">
|
||||||
<defs>
|
<defs>
|
||||||
<linearGradient id="cyan-to-teal" x1="0%" y1="0%" x2="100%" y2="0%">
|
<linearGradient id="cyan-to-teal" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||||
<stop offset="0%" stopColor="#06b6d4" stopOpacity="0.45" />
|
<stop offset="0%" stopColor="#06b6d4" stopOpacity="0.45" />
|
||||||
@@ -202,64 +246,64 @@ export function AgentView() {
|
|||||||
|
|
||||||
{/* Bezier Curves: Clients to Gateway */}
|
{/* Bezier Curves: Clients to Gateway */}
|
||||||
{/* Roo Code (y=10) */}
|
{/* Roo Code (y=10) */}
|
||||||
<path d="M 10 10 C 30 10, 30 50, 50 50" stroke="url(#cyan-to-teal)" strokeWidth="1.5" fill="none" />
|
<path d={getClientPath(0.10)} stroke="url(#cyan-to-teal)" strokeWidth="1.5" fill="none" />
|
||||||
{(hoveredNode === "roocode" || activeClient === "roocode") && (
|
{(hoveredNode === "roocode" || activeClient === "roocode") && (
|
||||||
<path d="M 10 10 C 30 10, 30 50, 50 50" stroke="#06b6d4" strokeWidth="2.5" fill="none" className="animate-flow-cyan" />
|
<path d={getClientPath(0.10)} stroke="#06b6d4" strokeWidth="2.5" fill="none" className="animate-flow-cyan" />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Cursor (y=30) */}
|
{/* Cursor (y=30) */}
|
||||||
<path d="M 10 30 C 30 30, 30 50, 50 50" stroke="url(#cyan-to-teal)" strokeWidth="1.5" fill="none" />
|
<path d={getClientPath(0.30)} stroke="url(#cyan-to-teal)" strokeWidth="1.5" fill="none" />
|
||||||
{(hoveredNode === "cursor" || activeClient === "cursor") && (
|
{(hoveredNode === "cursor" || activeClient === "cursor") && (
|
||||||
<path d="M 10 30 C 30 30, 30 50, 50 50" stroke="#06b6d4" strokeWidth="2.5" fill="none" className="animate-flow-cyan" />
|
<path d={getClientPath(0.30)} stroke="#06b6d4" strokeWidth="2.5" fill="none" className="animate-flow-cyan" />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* OpenCode (y=50) */}
|
{/* OpenCode (y=50) */}
|
||||||
<path d="M 10 50 C 30 50, 30 50, 50 50" stroke="url(#cyan-to-teal)" strokeWidth="1.5" fill="none" />
|
<path d={getClientPath(0.50)} stroke="url(#cyan-to-teal)" strokeWidth="1.5" fill="none" />
|
||||||
{(hoveredNode === "opencode" || activeClient === "opencode") && (
|
{(hoveredNode === "opencode" || activeClient === "opencode") && (
|
||||||
<path d="M 10 50 C 30 50, 30 50, 50 50" stroke="#06b6d4" strokeWidth="2.5" fill="none" className="animate-flow-cyan" />
|
<path d={getClientPath(0.50)} stroke="#06b6d4" strokeWidth="2.5" fill="none" className="animate-flow-cyan" />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Zed (y=70) */}
|
{/* Zed (y=70) */}
|
||||||
<path d="M 10 70 C 30 70, 30 50, 50 50" stroke="url(#cyan-to-teal)" strokeWidth="1.5" fill="none" />
|
<path d={getClientPath(0.70)} stroke="url(#cyan-to-teal)" strokeWidth="1.5" fill="none" />
|
||||||
{(hoveredNode === "zed" || activeClient === "zed") && (
|
{(hoveredNode === "zed" || activeClient === "zed") && (
|
||||||
<path d="M 10 70 C 30 70, 30 50, 50 50" stroke="#06b6d4" strokeWidth="2.5" fill="none" className="animate-flow-cyan" />
|
<path d={getClientPath(0.70)} stroke="#06b6d4" strokeWidth="2.5" fill="none" className="animate-flow-cyan" />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Continue (y=90) */}
|
{/* Continue (y=90) */}
|
||||||
<path d="M 10 90 C 30 90, 30 50, 50 50" stroke="url(#cyan-to-teal)" strokeWidth="1.5" fill="none" />
|
<path d={getClientPath(0.90)} stroke="url(#cyan-to-teal)" strokeWidth="1.5" fill="none" />
|
||||||
{(hoveredNode === "continue" || activeClient === "continue") && (
|
{(hoveredNode === "continue" || activeClient === "continue") && (
|
||||||
<path d="M 10 90 C 30 90, 30 50, 50 50" stroke="#06b6d4" strokeWidth="2.5" fill="none" className="animate-flow-cyan" />
|
<path d={getClientPath(0.90)} stroke="#06b6d4" strokeWidth="2.5" fill="none" className="animate-flow-cyan" />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Bezier Curves: Gateway to Roles */}
|
{/* Bezier Curves: Gateway to Roles */}
|
||||||
{/* fast (y=12) */}
|
{/* fast (y=12) */}
|
||||||
<path d="M 50 50 C 70 50, 70 12, 90 12" stroke="url(#cyan-to-teal)" strokeWidth="1.5" fill="none" />
|
<path d={getRolePath(0.12)} stroke="url(#cyan-to-teal)" strokeWidth="1.5" fill="none" />
|
||||||
{isRoleRunning("fast") && (
|
{isRoleRunning("fast") && (
|
||||||
<path d="M 50 50 C 70 50, 70 12, 90 12" stroke="url(#active-glow)" strokeWidth="2.5" fill="none" className="animate-flow-cyan" />
|
<path d={getRolePath(0.12)} stroke="url(#active-glow)" strokeWidth="2.5" fill="none" className="animate-flow-cyan" />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* heavy (y=31) */}
|
{/* heavy (y=31) */}
|
||||||
<path d="M 50 50 C 70 50, 70 31, 90 31" stroke="url(#cyan-to-teal)" strokeWidth="1.5" fill="none" />
|
<path d={getRolePath(0.31)} stroke="url(#cyan-to-teal)" strokeWidth="1.5" fill="none" />
|
||||||
{isRoleRunning("heavy") && (
|
{isRoleRunning("heavy") && (
|
||||||
<path d="M 50 50 C 70 50, 70 31, 90 31" stroke="url(#active-glow)" strokeWidth="2.5" fill="none" className="animate-flow-cyan" />
|
<path d={getRolePath(0.31)} stroke="url(#active-glow)" strokeWidth="2.5" fill="none" className="animate-flow-cyan" />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* coder (y=50) */}
|
{/* coder (y=50) */}
|
||||||
<path d="M 50 50 C 70 50, 70 50, 90 50" stroke="url(#cyan-to-teal)" strokeWidth="1.5" fill="none" />
|
<path d={getRolePath(0.50)} stroke="url(#cyan-to-teal)" strokeWidth="1.5" fill="none" />
|
||||||
{isRoleRunning("coder") && (
|
{isRoleRunning("coder") && (
|
||||||
<path d="M 50 50 C 70 50, 70 50, 90 50" stroke="url(#active-glow)" strokeWidth="2.5" fill="none" className="animate-flow-cyan" />
|
<path d={getRolePath(0.50)} stroke="url(#active-glow)" strokeWidth="2.5" fill="none" className="animate-flow-cyan" />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* vision (y=69) */}
|
{/* vision (y=69) */}
|
||||||
<path d="M 50 50 C 70 50, 70 69, 90 69" stroke="url(#cyan-to-teal)" strokeWidth="1.5" fill="none" />
|
<path d={getRolePath(0.69)} stroke="url(#cyan-to-teal)" strokeWidth="1.5" fill="none" />
|
||||||
{isRoleRunning("vision") && (
|
{isRoleRunning("vision") && (
|
||||||
<path d="M 50 50 C 70 50, 70 69, 90 69" stroke="url(#active-glow)" strokeWidth="2.5" fill="none" className="animate-flow-cyan" />
|
<path d={getRolePath(0.69)} stroke="url(#active-glow)" strokeWidth="2.5" fill="none" className="animate-flow-cyan" />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* scout (y=88) */}
|
{/* scout (y=88) */}
|
||||||
<path d="M 50 50 C 70 50, 70 88, 90 88" stroke="url(#cyan-to-teal)" strokeWidth="1.5" fill="none" />
|
<path d={getRolePath(0.88)} stroke="url(#cyan-to-teal)" strokeWidth="1.5" fill="none" />
|
||||||
{isRoleRunning("scout") && (
|
{isRoleRunning("scout") && (
|
||||||
<path d="M 50 50 C 70 50, 70 88, 90 88" stroke="url(#active-glow)" strokeWidth="2.5" fill="none" className="animate-flow-cyan" />
|
<path d={getRolePath(0.88)} stroke="url(#active-glow)" strokeWidth="2.5" fill="none" className="animate-flow-cyan" />
|
||||||
)}
|
)}
|
||||||
</svg>
|
</svg>
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useState } from "react"
|
import { useEffect, useState, useRef } from "react"
|
||||||
import { Download, Search, Trash2, Edit3, Star, Layers, Activity, HardDrive, X, Check, Copy, Bot, Eye, Code, Brain, Compass, ChevronDown, ChevronUp } from "lucide-react"
|
import { Download, Search, Trash2, Edit3, Star, Layers, Activity, HardDrive, X, Check, Copy, Bot, Eye, Code, Brain, Compass, ChevronDown, ChevronUp } from "lucide-react"
|
||||||
import { api, type DiscoverResp, type Fit, type Job, type ModelInfo, type RoutingResp, type UpdatesResp, type ConnectResp } from "@/lib/api"
|
import { api, type DiscoverResp, type Fit, type Job, type ModelInfo, type RoutingResp, type UpdatesResp, type ConnectResp } from "@/lib/api"
|
||||||
import { CapsChips } from "@/components/CapsChips"
|
import { CapsChips } from "@/components/CapsChips"
|
||||||
@@ -140,6 +140,50 @@ function Cockpit() {
|
|||||||
const [hoveredNode, setHoveredNode] = useState<string | null>(null)
|
const [hoveredNode, setHoveredNode] = useState<string | null>(null)
|
||||||
const [viewMode, setViewMode] = useState<"grid" | "list">("grid")
|
const [viewMode, setViewMode] = useState<"grid" | "list">("grid")
|
||||||
|
|
||||||
|
// Canvas pixel tracking for pixel-perfect connection graph without non-uniform scaling
|
||||||
|
const [dimensions, setDimensions] = useState({ width: 800, height: 360 })
|
||||||
|
const containerRef = useRef<HTMLDivElement>(null)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!containerRef.current) return
|
||||||
|
const observer = new ResizeObserver((entries) => {
|
||||||
|
if (!entries || entries.length === 0) return
|
||||||
|
const rect = entries[0].contentRect
|
||||||
|
setDimensions({ width: rect.width, height: rect.height })
|
||||||
|
})
|
||||||
|
observer.observe(containerRef.current)
|
||||||
|
return () => observer.disconnect()
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const w = dimensions.width
|
||||||
|
const h = dimensions.height
|
||||||
|
|
||||||
|
// Helper to generate curve from client to gateway
|
||||||
|
const getClientPath = (yPercent: number) => {
|
||||||
|
const startX = w * 0.1
|
||||||
|
const startY = h * yPercent
|
||||||
|
const endX = w * 0.5
|
||||||
|
const endY = h * 0.5
|
||||||
|
const cp1X = w * 0.3
|
||||||
|
const cp1Y = startY
|
||||||
|
const cp2X = w * 0.3
|
||||||
|
const cp2Y = endY
|
||||||
|
return `M ${startX} ${startY} C ${cp1X} ${cp1Y}, ${cp2X} ${cp2Y}, ${endX} ${endY}`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper to generate curve from gateway to role
|
||||||
|
const getRolePath = (yPercent: number) => {
|
||||||
|
const startX = w * 0.5
|
||||||
|
const startY = h * 0.5
|
||||||
|
const endX = w * 0.9
|
||||||
|
const endY = h * yPercent
|
||||||
|
const cp1X = w * 0.7
|
||||||
|
const cp1Y = startY
|
||||||
|
const cp2X = w * 0.7
|
||||||
|
const cp2Y = endY
|
||||||
|
return `M ${startX} ${startY} C ${cp1X} ${cp1Y}, ${cp2X} ${cp2Y}, ${endX} ${endY}`
|
||||||
|
}
|
||||||
|
|
||||||
function load() {
|
function load() {
|
||||||
Promise.all([
|
Promise.all([
|
||||||
api<{ models: ModelInfo[]; running?: string[] }>("/api/models"),
|
api<{ models: ModelInfo[]; running?: string[] }>("/api/models"),
|
||||||
@@ -275,12 +319,12 @@ function Cockpit() {
|
|||||||
<style>{`
|
<style>{`
|
||||||
@keyframes flow-dash {
|
@keyframes flow-dash {
|
||||||
to {
|
to {
|
||||||
stroke-dashoffset: -40;
|
stroke-dashoffset: -20;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.animate-flow-cyan {
|
.animate-flow-cyan {
|
||||||
stroke-dasharray: 8 24;
|
stroke-dasharray: 4 6;
|
||||||
animation: flow-dash 1.2s linear infinite;
|
animation: flow-dash 1s linear infinite;
|
||||||
}
|
}
|
||||||
`}</style>
|
`}</style>
|
||||||
|
|
||||||
@@ -352,9 +396,9 @@ function Cockpit() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* The Graph Canvas Area */}
|
{/* The Graph Canvas Area */}
|
||||||
<div className="relative w-full h-[360px] border border-border/30 rounded-xl bg-black/25 overflow-hidden flex">
|
<div ref={containerRef} className="relative w-full h-[360px] border border-border/30 rounded-xl bg-black/25 overflow-hidden flex">
|
||||||
{/* SVG Overlay behind HTML Nodes */}
|
{/* SVG Overlay behind HTML Nodes */}
|
||||||
<svg className="absolute inset-0 pointer-events-none w-full h-full" viewBox="0 0 100 100" preserveAspectRatio="none">
|
<svg className="absolute inset-0 pointer-events-none w-full h-full">
|
||||||
{/* Gradients */}
|
{/* Gradients */}
|
||||||
<defs>
|
<defs>
|
||||||
<linearGradient id="cyan-to-teal" x1="0%" y1="0%" x2="100%" y2="0%">
|
<linearGradient id="cyan-to-teal" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||||
@@ -369,64 +413,64 @@ function Cockpit() {
|
|||||||
|
|
||||||
{/* Bezier Curves: Clients to Gateway (10% -> 50%) */}
|
{/* Bezier Curves: Clients to Gateway (10% -> 50%) */}
|
||||||
{/* Roo Code (y=10) */}
|
{/* Roo Code (y=10) */}
|
||||||
<path d="M 10 10 C 30 10, 30 50, 50 50" stroke="url(#cyan-to-teal)" strokeWidth="1.5" fill="none" />
|
<path d={getClientPath(0.10)} stroke="url(#cyan-to-teal)" strokeWidth="1.5" fill="none" />
|
||||||
{(hoveredNode === "roocode" || activeClient === "roocode") && (
|
{(hoveredNode === "roocode" || activeClient === "roocode") && (
|
||||||
<path d="M 10 10 C 30 10, 30 50, 50 50" stroke="#06b6d4" strokeWidth="2.5" fill="none" className="animate-flow-cyan" />
|
<path d={getClientPath(0.10)} stroke="#06b6d4" strokeWidth="2.5" fill="none" className="animate-flow-cyan" />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Cursor (y=30) */}
|
{/* Cursor (y=30) */}
|
||||||
<path d="M 10 30 C 30 30, 30 50, 50 50" stroke="url(#cyan-to-teal)" strokeWidth="1.5" fill="none" />
|
<path d={getClientPath(0.30)} stroke="url(#cyan-to-teal)" strokeWidth="1.5" fill="none" />
|
||||||
{(hoveredNode === "cursor" || activeClient === "cursor") && (
|
{(hoveredNode === "cursor" || activeClient === "cursor") && (
|
||||||
<path d="M 10 30 C 30 30, 30 50, 50 50" stroke="#06b6d4" strokeWidth="2.5" fill="none" className="animate-flow-cyan" />
|
<path d={getClientPath(0.30)} stroke="#06b6d4" strokeWidth="2.5" fill="none" className="animate-flow-cyan" />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* OpenCode (y=50) */}
|
{/* OpenCode (y=50) */}
|
||||||
<path d="M 10 50 C 30 50, 30 50, 50 50" stroke="url(#cyan-to-teal)" strokeWidth="1.5" fill="none" />
|
<path d={getClientPath(0.50)} stroke="url(#cyan-to-teal)" strokeWidth="1.5" fill="none" />
|
||||||
{(hoveredNode === "opencode" || activeClient === "opencode") && (
|
{(hoveredNode === "opencode" || activeClient === "opencode") && (
|
||||||
<path d="M 10 50 C 30 50, 30 50, 50 50" stroke="#06b6d4" strokeWidth="2.5" fill="none" className="animate-flow-cyan" />
|
<path d={getClientPath(0.50)} stroke="#06b6d4" strokeWidth="2.5" fill="none" className="animate-flow-cyan" />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Zed (y=70) */}
|
{/* Zed (y=70) */}
|
||||||
<path d="M 10 70 C 30 70, 30 50, 50 50" stroke="url(#cyan-to-teal)" strokeWidth="1.5" fill="none" />
|
<path d={getClientPath(0.70)} stroke="url(#cyan-to-teal)" strokeWidth="1.5" fill="none" />
|
||||||
{(hoveredNode === "zed" || activeClient === "zed") && (
|
{(hoveredNode === "zed" || activeClient === "zed") && (
|
||||||
<path d="M 10 70 C 30 70, 30 50, 50 50" stroke="#06b6d4" strokeWidth="2.5" fill="none" className="animate-flow-cyan" />
|
<path d={getClientPath(0.70)} stroke="#06b6d4" strokeWidth="2.5" fill="none" className="animate-flow-cyan" />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Continue (y=90) */}
|
{/* Continue (y=90) */}
|
||||||
<path d="M 10 90 C 30 90, 30 50, 50 50" stroke="url(#cyan-to-teal)" strokeWidth="1.5" fill="none" />
|
<path d={getClientPath(0.90)} stroke="url(#cyan-to-teal)" strokeWidth="1.5" fill="none" />
|
||||||
{(hoveredNode === "continue" || activeClient === "continue") && (
|
{(hoveredNode === "continue" || activeClient === "continue") && (
|
||||||
<path d="M 10 90 C 30 90, 30 50, 50 50" stroke="#06b6d4" strokeWidth="2.5" fill="none" className="animate-flow-cyan" />
|
<path d={getClientPath(0.90)} stroke="#06b6d4" strokeWidth="2.5" fill="none" className="animate-flow-cyan" />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Bezier Curves: Gateway to Roles (50% -> 90%) */}
|
{/* Bezier Curves: Gateway to Roles (50% -> 90%) */}
|
||||||
{/* fast (y=12) */}
|
{/* fast (y=12) */}
|
||||||
<path d="M 50 50 C 70 50, 70 12, 90 12" stroke="url(#cyan-to-teal)" strokeWidth="1.5" fill="none" />
|
<path d={getRolePath(0.12)} stroke="url(#cyan-to-teal)" strokeWidth="1.5" fill="none" />
|
||||||
{isRoleRunning("fast") && (
|
{isRoleRunning("fast") && (
|
||||||
<path d="M 50 50 C 70 50, 70 12, 90 12" stroke="url(#active-glow)" strokeWidth="2.5" fill="none" className="animate-flow-cyan" />
|
<path d={getRolePath(0.12)} stroke="url(#active-glow)" strokeWidth="2.5" fill="none" className="animate-flow-cyan" />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* heavy (y=31) */}
|
{/* heavy (y=31) */}
|
||||||
<path d="M 50 50 C 70 50, 70 31, 90 31" stroke="url(#cyan-to-teal)" strokeWidth="1.5" fill="none" />
|
<path d={getRolePath(0.31)} stroke="url(#cyan-to-teal)" strokeWidth="1.5" fill="none" />
|
||||||
{isRoleRunning("heavy") && (
|
{isRoleRunning("heavy") && (
|
||||||
<path d="M 50 50 C 70 50, 70 31, 90 31" stroke="url(#active-glow)" strokeWidth="2.5" fill="none" className="animate-flow-cyan" />
|
<path d={getRolePath(0.31)} stroke="url(#active-glow)" strokeWidth="2.5" fill="none" className="animate-flow-cyan" />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* coder (y=50) */}
|
{/* coder (y=50) */}
|
||||||
<path d="M 50 50 C 70 50, 70 50, 90 50" stroke="url(#cyan-to-teal)" strokeWidth="1.5" fill="none" />
|
<path d={getRolePath(0.50)} stroke="url(#cyan-to-teal)" strokeWidth="1.5" fill="none" />
|
||||||
{isRoleRunning("coder") && (
|
{isRoleRunning("coder") && (
|
||||||
<path d="M 50 50 C 70 50, 70 50, 90 50" stroke="url(#active-glow)" strokeWidth="2.5" fill="none" className="animate-flow-cyan" />
|
<path d={getRolePath(0.50)} stroke="url(#active-glow)" strokeWidth="2.5" fill="none" className="animate-flow-cyan" />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* vision (y=69) */}
|
{/* vision (y=69) */}
|
||||||
<path d="M 50 50 C 70 50, 70 69, 90 69" stroke="url(#cyan-to-teal)" strokeWidth="1.5" fill="none" />
|
<path d={getRolePath(0.69)} stroke="url(#cyan-to-teal)" strokeWidth="1.5" fill="none" />
|
||||||
{isRoleRunning("vision") && (
|
{isRoleRunning("vision") && (
|
||||||
<path d="M 50 50 C 70 50, 70 69, 90 69" stroke="url(#active-glow)" strokeWidth="2.5" fill="none" className="animate-flow-cyan" />
|
<path d={getRolePath(0.69)} stroke="url(#active-glow)" strokeWidth="2.5" fill="none" className="animate-flow-cyan" />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* scout (y=88) */}
|
{/* scout (y=88) */}
|
||||||
<path d="M 50 50 C 70 50, 70 88, 90 88" stroke="url(#cyan-to-teal)" strokeWidth="1.5" fill="none" />
|
<path d={getRolePath(0.88)} stroke="url(#cyan-to-teal)" strokeWidth="1.5" fill="none" />
|
||||||
{isRoleRunning("scout") && (
|
{isRoleRunning("scout") && (
|
||||||
<path d="M 50 50 C 70 50, 70 88, 90 88" stroke="url(#active-glow)" strokeWidth="2.5" fill="none" className="animate-flow-cyan" />
|
<path d={getRolePath(0.88)} stroke="url(#active-glow)" strokeWidth="2.5" fill="none" className="animate-flow-cyan" />
|
||||||
)}
|
)}
|
||||||
</svg>
|
</svg>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user