{
  "name": "circuit-board",
  "type": "registry:ui",
  "dependencies": [
    "framer-motion"
  ],
  "registryDependencies": [],
  "description": "An interactive circuit board layout component with animated electricity paths that pulse between connected nodes. Fully theme-aware with automatic light/dark mode support.",
  "files": [
    {
      "path": "components/ui/circuit-board.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { motion } from \"framer-motion\"\nimport { cn } from \"@workspace/ui/lib/utils\"\n\ninterface CircuitNode {\n  id: string\n  x: number\n  y: number\n  label?: string\n  icon?: React.ReactNode\n  status?: \"active\" | \"inactive\" | \"processing\" | \"error\"\n  size?: \"sm\" | \"md\" | \"lg\"\n}\n\ninterface CircuitConnection {\n  from: string\n  to: string\n  animated?: boolean\n  bidirectional?: boolean\n  color?: string\n  pulseColor?: string\n}\n\ninterface CircuitBoardProps extends React.HTMLAttributes<HTMLDivElement> {\n  nodes: CircuitNode[]\n  connections: CircuitConnection[]\n  width?: number\n  height?: number\n  gridSize?: number\n  showGrid?: boolean\n  gridColor?: string\n  traceColor?: string\n  pulseColor?: string\n  nodeColor?: string\n  pulseSpeed?: number\n  traceWidth?: number\n  /** Force a specific theme variant. Defaults to auto-detect from system. */\n  variant?: \"light\" | \"dark\" | \"auto\"\n}\n\nfunction CircuitBoard({\n  nodes,\n  connections,\n  width = 600,\n  height = 400,\n  gridSize = 20,\n  showGrid = true,\n  gridColor,\n  traceColor,\n  pulseColor,\n  nodeColor,\n  pulseSpeed = 2,\n  traceWidth = 2,\n  variant = \"auto\",\n  className,\n  ...props\n}: CircuitBoardProps) {\n  // Theme-aware color defaults\n  const [isDark, setIsDark] = React.useState(true)\n\n  React.useEffect(() => {\n    if (variant !== \"auto\") {\n      setIsDark(variant === \"dark\")\n      return\n    }\n\n    // Check for dark class on html/body\n    const checkTheme = () => {\n      const isDarkMode = document.documentElement.classList.contains(\"dark\") ||\n        document.body.classList.contains(\"dark\")\n      setIsDark(isDarkMode)\n    }\n\n    checkTheme()\n\n    // Listen for changes\n    const observer = new MutationObserver(checkTheme)\n    observer.observe(document.documentElement, { attributes: true, attributeFilter: [\"class\"] })\n    observer.observe(document.body, { attributes: true, attributeFilter: [\"class\"] })\n\n    const mediaQuery = window.matchMedia(\"(prefers-color-scheme: dark)\")\n    mediaQuery.addEventListener(\"change\", checkTheme)\n\n    return () => {\n      observer.disconnect()\n      mediaQuery.removeEventListener(\"change\", checkTheme)\n    }\n  }, [variant])\n\n  // Compute theme-aware colors\n  const computedGridColor = gridColor || (isDark ? \"rgba(163, 163, 163, 0.08)\" : \"rgba(64, 64, 64, 0.12)\")\n  const computedTraceColor = traceColor || (isDark ? \"rgba(163, 163, 163, 0.25)\" : \"rgba(64, 64, 64, 0.35)\")\n  const computedPulseColor = pulseColor || (isDark ? \"rgba(163, 163, 163, 0.6)\" : \"rgba(64, 64, 64, 0.7)\")\n  const computedNodeColor = nodeColor || (isDark ? \"rgba(163, 163, 163, 0.5)\" : \"rgba(64, 64, 64, 0.6)\")\n  const nodeMap = React.useMemo(() => {\n    return new Map(nodes.map((node) => [node.id, node]))\n  }, [nodes])\n\n  const getNodeSize = React.useCallback((size?: CircuitNode[\"size\"]) => {\n    switch (size) {\n      case \"sm\":\n        return 24\n      case \"lg\":\n        return 48\n      default:\n        return 36\n    }\n  }, [])\n\n  const calculatePath = React.useCallback(\n    (from: CircuitNode, to: CircuitNode): string => {\n      const fromSize = getNodeSize(from.size) / 2 + 4\n      const toSize = getNodeSize(to.size) / 2 + 4\n\n      const dx = to.x - from.x\n      const dy = to.y - from.y\n\n      // Calculate start and end points offset from node centers\n      let startX = from.x\n      let startY = from.y\n      let endX = to.x\n      let endY = to.y\n\n      // Create circuit-like paths with right angles\n      if (Math.abs(dx) > Math.abs(dy)) {\n        // Horizontal first, then vertical\n        startX = from.x + (dx > 0 ? fromSize : -fromSize)\n        endX = to.x + (dx > 0 ? -toSize : toSize)\n        const midX = from.x + dx / 2\n        return `M ${startX} ${startY} H ${midX} V ${endY} H ${endX}`\n      } else {\n        // Vertical first, then horizontal\n        startY = from.y + (dy > 0 ? fromSize : -fromSize)\n        endY = to.y + (dy > 0 ? -toSize : toSize)\n        const midY = from.y + dy / 2\n        return `M ${startX} ${startY} V ${midY} H ${endX} V ${endY}`\n      }\n    },\n    [getNodeSize]\n  )\n\n  const getStatusColor = (status?: CircuitNode[\"status\"]) => {\n    if (isDark) {\n      switch (status) {\n        case \"active\":\n          return \"rgba(163, 163, 163, 0.7)\"\n        case \"processing\":\n          return \"rgba(163, 163, 163, 0.5)\"\n        case \"error\":\n          return \"rgba(120, 113, 108, 0.6)\"\n        default:\n          return computedNodeColor\n      }\n    } else {\n      switch (status) {\n        case \"active\":\n          return \"rgba(64, 64, 64, 0.8)\"\n        case \"processing\":\n          return \"rgba(64, 64, 64, 0.6)\"\n        case \"error\":\n          return \"rgba(180, 83, 83, 0.7)\"\n        default:\n          return computedNodeColor\n      }\n    }\n  }\n\n  return (\n    <div\n      className={cn(\n        \"relative overflow-hidden\",\n        className\n      )}\n      style={{ width, height }}\n      {...props}\n    >\n      <svg\n        width={width}\n        height={height}\n        className=\"absolute inset-0\"\n        style={{ overflow: \"visible\" }}\n      >\n        <defs>\n          {/* Glow filter for the pulse effect */}\n          <filter id=\"glow\" x=\"-50%\" y=\"-50%\" width=\"200%\" height=\"200%\">\n            <feGaussianBlur stdDeviation=\"3\" result=\"coloredBlur\" />\n            <feMerge>\n              <feMergeNode in=\"coloredBlur\" />\n              <feMergeNode in=\"SourceGraphic\" />\n            </feMerge>\n          </filter>\n\n          {/* Grid pattern */}\n          {showGrid && (\n            <pattern\n              id=\"circuitGrid\"\n              width={gridSize}\n              height={gridSize}\n              patternUnits=\"userSpaceOnUse\"\n            >\n              <circle cx={gridSize / 2} cy={gridSize / 2} r=\"0.5\" fill={computedGridColor} />\n            </pattern>\n          )}\n\n          {/* Animated gradient for electricity effect */}\n          {connections.map((conn, i) => (\n            <linearGradient\n              key={`gradient-${i}`}\n              id={`electricGradient-${i}`}\n              gradientUnits=\"userSpaceOnUse\"\n            >\n              <stop offset=\"0%\" stopColor=\"transparent\" />\n              <stop offset=\"40%\" stopColor=\"transparent\" />\n              <stop offset=\"50%\" stopColor={conn.pulseColor || computedPulseColor} />\n              <stop offset=\"60%\" stopColor=\"transparent\" />\n              <stop offset=\"100%\" stopColor=\"transparent\" />\n            </linearGradient>\n          ))}\n        </defs>\n\n        {/* Grid background */}\n        {showGrid && (\n          <rect width={width} height={height} fill=\"url(#circuitGrid)\" />\n        )}\n\n        {/* Connection traces */}\n        {connections.map((conn, i) => {\n          const fromNode = nodeMap.get(conn.from)\n          const toNode = nodeMap.get(conn.to)\n          if (!fromNode || !toNode) return null\n\n          const path = calculatePath(fromNode, toNode)\n          const pathLength = 500 // Approximate path length for animation\n\n          return (\n            <g key={`connection-${i}`}>\n              {/* Base trace */}\n              <motion.path\n                d={path}\n                fill=\"none\"\n                stroke={conn.color || computedTraceColor}\n                strokeWidth={traceWidth}\n                strokeLinecap=\"round\"\n                strokeLinejoin=\"round\"\n                initial={{ pathLength: 0 }}\n                animate={{ pathLength: 1 }}\n                transition={{ duration: 1, delay: i * 0.2 }}\n              />\n\n              {/* Animated electricity pulse */}\n              {conn.animated !== false && (\n                <motion.path\n                  d={path}\n                  fill=\"none\"\n                  stroke={conn.pulseColor || computedPulseColor}\n                  strokeWidth={traceWidth + 2}\n                  strokeLinecap=\"round\"\n                  strokeLinejoin=\"round\"\n                  filter=\"url(#glow)\"\n                  strokeDasharray={`${pathLength * 0.1} ${pathLength * 0.9}`}\n                  initial={{ strokeDashoffset: pathLength }}\n                  animate={{ strokeDashoffset: -pathLength }}\n                  transition={{\n                    duration: pulseSpeed,\n                    repeat: Infinity,\n                    ease: \"linear\",\n                    delay: i * 0.3,\n                  }}\n                />\n              )}\n\n              {/* Bidirectional pulse */}\n              {conn.bidirectional && (\n                <motion.path\n                  d={path}\n                  fill=\"none\"\n                  stroke={conn.pulseColor || computedPulseColor}\n                  strokeWidth={traceWidth + 2}\n                  strokeLinecap=\"round\"\n                  strokeLinejoin=\"round\"\n                  filter=\"url(#glow)\"\n                  strokeDasharray={`${pathLength * 0.1} ${pathLength * 0.9}`}\n                  initial={{ strokeDashoffset: -pathLength }}\n                  animate={{ strokeDashoffset: pathLength }}\n                  transition={{\n                    duration: pulseSpeed,\n                    repeat: Infinity,\n                    ease: \"linear\",\n                    delay: i * 0.3 + pulseSpeed / 2,\n                  }}\n                />\n              )}\n\n\n            </g>\n          )\n        })}\n      </svg>\n\n      {/* Nodes */}\n      {nodes.map((node, i) => {\n        const size = getNodeSize(node.size)\n        const statusColor = getStatusColor(node.status)\n\n        return (\n          <motion.div\n            key={node.id}\n            className=\"absolute flex items-center justify-center\"\n            style={{\n              left: node.x - size / 2,\n              top: node.y - size / 2,\n              width: size,\n              height: size,\n            }}\n            initial={{ scale: 0, opacity: 0 }}\n            animate={{ scale: 1, opacity: 1 }}\n            transition={{ delay: i * 0.1 + 0.5, type: \"spring\" }}\n          >\n            {/* Node background with pulse */}\n            <motion.div\n              className=\"absolute inset-0 rounded-lg\"\n              style={{ backgroundColor: statusColor }}\n              animate={\n                node.status === \"processing\"\n                  ? { opacity: [0.2, 0.5, 0.2] }\n                  : { opacity: 0.2 }\n              }\n              transition={\n                node.status === \"processing\"\n                  ? { duration: 1.5, repeat: Infinity }\n                  : {}\n              }\n            />\n\n            {/* Node border */}\n            <div\n              className=\"absolute inset-0 rounded-lg border-2\"\n              style={{ borderColor: statusColor }}\n            />\n\n            {/* Inner glow for active nodes */}\n            {node.status === \"active\" && (\n              <motion.div\n                className=\"absolute inset-0 rounded-lg\"\n                style={{\n                  boxShadow: `0 0 20px ${statusColor}40, inset 0 0 10px ${statusColor}20`,\n                }}\n                animate={{ opacity: [0.5, 1, 0.5] }}\n                transition={{ duration: 2, repeat: Infinity }}\n              />\n            )}\n\n            {/* Node content */}\n            <div className=\"relative z-10 flex flex-col items-center justify-center\">\n              {node.icon && (\n                <div style={{ color: statusColor }}>{node.icon}</div>\n              )}\n            </div>\n\n            {/* Label */}\n            {node.label && (\n              <div\n                className=\"absolute -bottom-6 left-1/2 -translate-x-1/2 whitespace-nowrap text-xs font-medium\"\n                style={{ color: statusColor }}\n              >\n                {node.label}\n              </div>\n            )}\n          </motion.div>\n        )\n      })}\n    </div>\n  )\n}\n\n// Pre-built circuit patterns\ninterface CircuitPatternProps extends Omit<CircuitBoardProps, \"nodes\" | \"connections\"> {\n  pattern: \"data-flow\" | \"network\" | \"processor\" | \"tree\"\n}\n\nfunction CircuitPattern({ pattern, ...props }: CircuitPatternProps) {\n  const patterns = {\n    \"data-flow\": {\n      nodes: [\n        { id: \"input\", x: 50, y: 200, label: \"Input\", status: \"active\" as const },\n        { id: \"process1\", x: 200, y: 100, label: \"Process\", status: \"processing\" as const },\n        { id: \"process2\", x: 200, y: 300, label: \"Validate\", status: \"active\" as const },\n        { id: \"merge\", x: 400, y: 200, label: \"Merge\", status: \"active\" as const },\n        { id: \"output\", x: 550, y: 200, label: \"Output\", status: \"active\" as const },\n      ],\n      connections: [\n        { from: \"input\", to: \"process1\", animated: true },\n        { from: \"input\", to: \"process2\", animated: true },\n        { from: \"process1\", to: \"merge\", animated: true },\n        { from: \"process2\", to: \"merge\", animated: true },\n        { from: \"merge\", to: \"output\", animated: true },\n      ],\n    },\n    network: {\n      nodes: [\n        { id: \"server\", x: 300, y: 80, label: \"Server\", status: \"active\" as const, size: \"lg\" as const },\n        { id: \"client1\", x: 100, y: 200, label: \"Client 1\", status: \"active\" as const },\n        { id: \"client2\", x: 300, y: 250, label: \"Client 2\", status: \"processing\" as const },\n        { id: \"client3\", x: 500, y: 200, label: \"Client 3\", status: \"active\" as const },\n        { id: \"db\", x: 300, y: 350, label: \"Database\", status: \"active\" as const },\n      ],\n      connections: [\n        { from: \"server\", to: \"client1\", bidirectional: true },\n        { from: \"server\", to: \"client2\", bidirectional: true },\n        { from: \"server\", to: \"client3\", bidirectional: true },\n        { from: \"server\", to: \"db\", bidirectional: true },\n      ],\n    },\n    processor: {\n      nodes: [\n        { id: \"alu\", x: 300, y: 200, label: \"ALU\", status: \"processing\" as const, size: \"lg\" as const },\n        { id: \"reg1\", x: 150, y: 100, label: \"R1\", status: \"active\" as const, size: \"sm\" as const },\n        { id: \"reg2\", x: 150, y: 200, label: \"R2\", status: \"active\" as const, size: \"sm\" as const },\n        { id: \"reg3\", x: 150, y: 300, label: \"R3\", status: \"active\" as const, size: \"sm\" as const },\n        { id: \"cache\", x: 450, y: 200, label: \"Cache\", status: \"active\" as const },\n        { id: \"out\", x: 550, y: 200, label: \"Out\", status: \"active\" as const, size: \"sm\" as const },\n      ],\n      connections: [\n        { from: \"reg1\", to: \"alu\", animated: true },\n        { from: \"reg2\", to: \"alu\", animated: true },\n        { from: \"reg3\", to: \"alu\", animated: true },\n        { from: \"alu\", to: \"cache\", animated: true },\n        { from: \"cache\", to: \"out\", animated: true },\n      ],\n    },\n    tree: {\n      nodes: [\n        { id: \"root\", x: 300, y: 50, label: \"Root\", status: \"active\" as const },\n        { id: \"l1\", x: 150, y: 150, label: \"L1\", status: \"active\" as const },\n        { id: \"r1\", x: 450, y: 150, label: \"R1\", status: \"processing\" as const },\n        { id: \"l1l\", x: 80, y: 280, label: \"L1L\", status: \"active\" as const, size: \"sm\" as const },\n        { id: \"l1r\", x: 220, y: 280, label: \"L1R\", status: \"active\" as const, size: \"sm\" as const },\n        { id: \"r1l\", x: 380, y: 280, label: \"R1L\", status: \"error\" as const, size: \"sm\" as const },\n        { id: \"r1r\", x: 520, y: 280, label: \"R1R\", status: \"active\" as const, size: \"sm\" as const },\n      ],\n      connections: [\n        { from: \"root\", to: \"l1\", animated: true },\n        { from: \"root\", to: \"r1\", animated: true },\n        { from: \"l1\", to: \"l1l\", animated: true },\n        { from: \"l1\", to: \"l1r\", animated: true },\n        { from: \"r1\", to: \"r1l\", animated: true },\n        { from: \"r1\", to: \"r1r\", animated: true },\n      ],\n    },\n  }\n\n  const selectedPattern = patterns[pattern]\n  return <CircuitBoard nodes={selectedPattern.nodes} connections={selectedPattern.connections} {...props} />\n}\n\n// Interactive circuit node for building custom circuits\ninterface CircuitNodeComponentProps {\n  status?: \"active\" | \"inactive\" | \"processing\" | \"error\"\n  size?: \"sm\" | \"md\" | \"lg\"\n  glowColor?: string\n  children?: React.ReactNode\n  className?: string\n  onClick?: () => void\n}\n\nfunction CircuitNode({\n  status = \"inactive\",\n  size = \"md\",\n  glowColor,\n  children,\n  className,\n  onClick,\n}: CircuitNodeComponentProps) {\n  const [isDark, setIsDark] = React.useState(true)\n\n  React.useEffect(() => {\n    const checkTheme = () => {\n      const isDarkMode = document.documentElement.classList.contains(\"dark\") ||\n        document.body.classList.contains(\"dark\")\n      setIsDark(isDarkMode)\n    }\n\n    checkTheme()\n\n    const observer = new MutationObserver(checkTheme)\n    observer.observe(document.documentElement, { attributes: true, attributeFilter: [\"class\"] })\n    observer.observe(document.body, { attributes: true, attributeFilter: [\"class\"] })\n\n    const mediaQuery = window.matchMedia(\"(prefers-color-scheme: dark)\")\n    mediaQuery.addEventListener(\"change\", checkTheme)\n\n    return () => {\n      observer.disconnect()\n      mediaQuery.removeEventListener(\"change\", checkTheme)\n    }\n  }, [])\n\n  const sizeClasses = {\n    sm: \"w-8 h-8\",\n    md: \"w-12 h-12\",\n    lg: \"w-16 h-16\",\n  }\n\n  const statusColors = isDark\n    ? {\n      active: \"rgba(163, 163, 163, 0.7)\",\n      inactive: \"rgba(115, 115, 115, 0.4)\",\n      processing: \"rgba(163, 163, 163, 0.5)\",\n      error: \"rgba(120, 113, 108, 0.6)\",\n    }\n    : {\n      active: \"rgba(64, 64, 64, 0.8)\",\n      inactive: \"rgba(100, 100, 100, 0.5)\",\n      processing: \"rgba(64, 64, 64, 0.6)\",\n      error: \"rgba(180, 83, 83, 0.7)\",\n    }\n\n  const color = glowColor || statusColors[status]\n\n  return (\n    <motion.div\n      className={cn(\n        \"relative flex items-center justify-center rounded-lg border\",\n        isDark ? \"bg-neutral-900/50\" : \"bg-neutral-200/60\",\n        sizeClasses[size],\n        className\n      )}\n      style={{ borderColor: color }}\n      whileHover={{ scale: 1.1 }}\n      whileTap={{ scale: 0.95 }}\n      onClick={onClick}\n    >\n      {/* Pulse animation for processing state */}\n      {status === \"processing\" && (\n        <motion.div\n          className=\"absolute inset-0 rounded-lg\"\n          style={{ backgroundColor: color }}\n          animate={{ opacity: [0.1, 0.3, 0.1] }}\n          transition={{ duration: 1.5, repeat: Infinity }}\n        />\n      )}\n\n      {/* Active glow */}\n      {status === \"active\" && (\n        <div\n          className=\"absolute inset-0 rounded-lg\"\n          style={{\n            boxShadow: `0 0 20px ${color}60, 0 0 40px ${color}30`,\n          }}\n        />\n      )}\n\n      {/* Error pulse */}\n      {status === \"error\" && (\n        <motion.div\n          className=\"absolute inset-0 rounded-lg\"\n          style={{ boxShadow: `0 0 20px ${color}80` }}\n          animate={{ opacity: [0.5, 1, 0.5] }}\n          transition={{ duration: 0.5, repeat: Infinity }}\n        />\n      )}\n\n      <div className=\"relative z-10\" style={{ color }}>\n        {children}\n      </div>\n    </motion.div>\n  )\n}\n\n// Animated trace line component for custom layouts\ninterface CircuitTraceProps {\n  path: string\n  animated?: boolean\n  color?: string\n  pulseColor?: string\n  width?: number\n  pulseSpeed?: number\n}\n\nfunction CircuitTrace({\n  path,\n  animated = true,\n  color,\n  pulseColor,\n  width = 2,\n  pulseSpeed = 2,\n}: CircuitTraceProps) {\n  const [isDark, setIsDark] = React.useState(true)\n\n  React.useEffect(() => {\n    const checkTheme = () => {\n      const isDarkMode = document.documentElement.classList.contains(\"dark\") ||\n        document.body.classList.contains(\"dark\")\n      setIsDark(isDarkMode)\n    }\n\n    checkTheme()\n\n    const observer = new MutationObserver(checkTheme)\n    observer.observe(document.documentElement, { attributes: true, attributeFilter: [\"class\"] })\n    observer.observe(document.body, { attributes: true, attributeFilter: [\"class\"] })\n\n    const mediaQuery = window.matchMedia(\"(prefers-color-scheme: dark)\")\n    mediaQuery.addEventListener(\"change\", checkTheme)\n\n    return () => {\n      observer.disconnect()\n      mediaQuery.removeEventListener(\"change\", checkTheme)\n    }\n  }, [])\n\n  const computedColor = color || (isDark ? \"rgba(163, 163, 163, 0.25)\" : \"rgba(64, 64, 64, 0.35)\")\n  const computedPulseColor = pulseColor || (isDark ? \"rgba(163, 163, 163, 0.6)\" : \"rgba(64, 64, 64, 0.7)\")\n  const pathLength = 500\n\n  return (\n    <svg className=\"absolute inset-0 overflow-visible pointer-events-none\">\n      <defs>\n        <filter id=\"traceGlow\" x=\"-50%\" y=\"-50%\" width=\"200%\" height=\"200%\">\n          <feGaussianBlur stdDeviation=\"3\" result=\"coloredBlur\" />\n          <feMerge>\n            <feMergeNode in=\"coloredBlur\" />\n            <feMergeNode in=\"SourceGraphic\" />\n          </feMerge>\n        </filter>\n      </defs>\n\n      {/* Base trace */}\n      <motion.path\n        d={path}\n        fill=\"none\"\n        stroke={computedColor}\n        strokeWidth={width}\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n        initial={{ pathLength: 0 }}\n        animate={{ pathLength: 1 }}\n        transition={{ duration: 1 }}\n      />\n\n      {/* Animated pulse */}\n      {animated && (\n        <motion.path\n          d={path}\n          fill=\"none\"\n          stroke={computedPulseColor}\n          strokeWidth={width + 2}\n          strokeLinecap=\"round\"\n          strokeLinejoin=\"round\"\n          filter=\"url(#traceGlow)\"\n          strokeDasharray={`${pathLength * 0.1} ${pathLength * 0.9}`}\n          initial={{ strokeDashoffset: pathLength }}\n          animate={{ strokeDashoffset: -pathLength }}\n          transition={{\n            duration: pulseSpeed,\n            repeat: Infinity,\n            ease: \"linear\",\n          }}\n        />\n      )}\n    </svg>\n  )\n}\n\nexport {\n  CircuitBoard,\n  CircuitPattern,\n  CircuitNode,\n  CircuitTrace,\n  type CircuitNode as CircuitNodeType,\n  type CircuitConnection,\n  type CircuitBoardProps,\n}\n",
      "type": "registry:ui"
    }
  ]
}