{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "data-flow-pipes",
  "title": "Data Flow Pipes",
  "description": "Glowing data packets travel along SVG bezier pipes between system nodes, leaving fading trails.",
  "dependencies": [
    "remotion"
  ],
  "files": [
    {
      "path": "registry/bases/editframe/components/data-flow-pipes/index.tsx",
      "content": "\"use client\";\n\nimport { Timegroup } from \"@editframe/react\";\nimport type { CSSProperties } from \"react\";\n\nconst FONT_FAMILY =\n  \"var(--font-geist-sans), -apple-system, BlinkMacSystemFont, sans-serif\";\n\nexport interface DataFlowNode {\n  id: string;\n  x: number;\n  y: number;\n  label?: string;\n}\n\nexport interface DataFlowEdge {\n  from: string;\n  to: string;\n  startFrame?: number;\n}\n\nexport interface DataFlowPipesProps {\n  nodes?: DataFlowNode[];\n  edges?: DataFlowEdge[];\n  pipeColor?: string;\n  pulseColor?: string;\n  pulseLength?: number;\n  pulseDuration?: number;\n  background?: string;\n  nodeColor?: string;\n  textColor?: string;\n  speed?: number;\n  fps?: number;\n  durationInFrames?: number;\n  width?: number;\n  height?: number;\n  className?: string;\n}\n\nconst DEFAULT_NODES: DataFlowNode[] = [\n  { id: \"client\", label: \"Client\", x: 140, y: 360 },\n  { id: \"api\", label: \"API\", x: 460, y: 200 },\n  { id: \"queue\", label: \"Queue\", x: 460, y: 520 },\n  { id: \"db\", label: \"Database\", x: 820, y: 360 },\n  { id: \"cdn\", label: \"CDN\", x: 1140, y: 200 },\n  { id: \"log\", label: \"Logs\", x: 1140, y: 520 },\n];\n\nconst DEFAULT_EDGES: DataFlowEdge[] = [\n  { from: \"client\", startFrame: 0, to: \"api\" },\n  { from: \"client\", startFrame: 12, to: \"queue\" },\n  { from: \"api\", startFrame: 24, to: \"db\" },\n  { from: \"queue\", startFrame: 36, to: \"db\" },\n  { from: \"db\", startFrame: 48, to: \"cdn\" },\n  { from: \"db\", startFrame: 56, to: \"log\" },\n];\n\nconst bezierPath = (\n  a: { x: number; y: number },\n  b: { x: number; y: number }\n) => {\n  const handle = Math.max(60, Math.abs(b.x - a.x) * 0.5);\n  return `M ${a.x} ${a.y} C ${a.x + handle} ${a.y}, ${b.x - handle} ${b.y}, ${b.x} ${b.y}`;\n};\n\nconst bezierLength = (\n  a: { x: number; y: number },\n  b: { x: number; y: number }\n) => {\n  const handle = Math.max(60, Math.abs(b.x - a.x) * 0.5);\n  let len = 0;\n  let prev = a;\n  for (let i = 1; i <= 32; i += 1) {\n    const t = i / 32;\n    const u = 1 - t;\n    const p = {\n      x:\n        u * u * u * a.x +\n        3 * u * u * t * (a.x + handle) +\n        3 * u * t * t * (b.x - handle) +\n        t * t * t * b.x,\n      y:\n        u * u * u * a.y +\n        3 * u * u * t * a.y +\n        3 * u * t * t * b.y +\n        t * t * t * b.y,\n    };\n    len += Math.hypot(p.x - prev.x, p.y - prev.y);\n    prev = p;\n  }\n  return len;\n};\n\nexport const DataFlowPipes = ({\n  nodes = DEFAULT_NODES,\n  edges = DEFAULT_EDGES,\n  pipeColor = \"#1f1f23\",\n  pulseColor = \"#22d3ee\",\n  pulseLength = 60,\n  pulseDuration = 36,\n  background = \"#050505\",\n  nodeColor = \"#0a0a0a\",\n  textColor = \"#fafafa\",\n  speed = 1,\n  fps = 30,\n  durationInFrames = 180,\n  width = 1280,\n  height = 720,\n  className,\n}: DataFlowPipesProps) => {\n  const safeSpeed = Math.max(0.01, speed);\n  const durationMs = ((durationInFrames / fps) * 1000) / safeSpeed;\n  const frameMs = 1000 / fps;\n\n  const nodeMap = new Map(nodes.map((n) => [n.id, n]));\n\n  const containerStyle: CSSProperties = {\n    background,\n    fontFamily: FONT_FAMILY,\n    height,\n    overflow: \"hidden\",\n    position: \"relative\",\n    width,\n  };\n\n  // Build path lengths for animation\n  const edgesWithLength = edges.map((edge) => {\n    const a = nodeMap.get(edge.from);\n    const b = nodeMap.get(edge.to);\n    const path = a && b ? bezierPath(a, b) : \"\";\n    const len = a && b ? bezierLength(a, b) : 100;\n    return { ...edge, a, b, len, path };\n  });\n\n  return (\n    <Timegroup\n      className={className}\n      duration={`${durationMs}ms`}\n      mode=\"fixed\"\n      style={containerStyle}\n    >\n      <>\n        <style>{`\n          @keyframes framecn-dataflow-pulse {\n            from { stroke-dashoffset: ${pulseLength + 100}; }\n            to { stroke-dashoffset: -${pulseLength}; }\n          }\n        `}</style>\n        <svg\n          width=\"100%\"\n          height=\"100%\"\n          viewBox=\"0 0 1280 720\"\n          style={{ inset: 0, position: \"absolute\" }}\n        >\n          {/* Static pipes */}\n          {edgesWithLength.map((edge, i) => (\n            <path\n              key={`pipe-${i}`}\n              d={edge.path}\n              fill=\"none\"\n              stroke={pipeColor}\n              strokeWidth={3}\n              strokeLinecap=\"round\"\n            />\n          ))}\n\n          {/* Pulses */}\n          {edgesWithLength.map((edge, i) => {\n            if (!edge.a || !edge.b) {\n              return null;\n            }\n            const startMs = (edge.startFrame ?? 0) * frameMs;\n            return (\n              <g key={`pulse-${i}`}>\n                {/* Trail copies */}\n                {[0.15, 0.3, 0.55].map((alpha, idx) => (\n                  <path\n                    key={`trail-${idx}`}\n                    d={edge.path}\n                    fill=\"none\"\n                    stroke={pulseColor}\n                    strokeWidth={3}\n                    strokeLinecap=\"round\"\n                    strokeDasharray={`${pulseLength} 9999`}\n                    style={{\n                      animation: `framecn-dataflow-pulse ${pulseDuration * frameMs}ms linear ${startMs + idx * 100}ms backwards`,\n                      opacity: alpha,\n                    }}\n                  />\n                ))}\n                {/* Bright head */}\n                <path\n                  d={edge.path}\n                  fill=\"none\"\n                  stroke={pulseColor}\n                  strokeWidth={3.5}\n                  strokeLinecap=\"round\"\n                  strokeDasharray={`${pulseLength} 9999`}\n                  style={{\n                    animation: `framecn-dataflow-pulse ${pulseDuration * frameMs}ms linear ${startMs}ms backwards`,\n                    filter: `drop-shadow(0 0 8px ${pulseColor})`,\n                  }}\n                />\n              </g>\n            );\n          })}\n        </svg>\n\n        {/* Nodes */}\n        {nodes.map((node) => (\n          <div\n            key={node.id}\n            style={{\n              alignItems: \"center\",\n              background: nodeColor,\n              border: \"1px solid rgba(255,255,255,0.08)\",\n              borderRadius: 10,\n              boxShadow: \"0 12px 30px rgba(0,0,0,0.4)\",\n              color: textColor,\n              display: \"flex\",\n              fontSize: 14,\n              fontWeight: 600,\n              height: 48,\n              justifyContent: \"center\",\n              left: node.x - 60,\n              letterSpacing: \"-0.01em\",\n              position: \"absolute\",\n              top: node.y - 24,\n              width: 120,\n            }}\n          >\n            {node.label ?? node.id}\n          </div>\n        ))}\n      </>\n    </Timegroup>\n  );\n};\n",
      "type": "registry:component",
      "target": "components/framecn/data-flow-pipes.tsx"
    }
  ],
  "type": "registry:component"
}