{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "core-fm-algorithm-diagram",
  "type": "registry:ui",
  "title": "FM Algorithm Diagram",
  "description": "A themed, selectable operator-routing diagram for FM synthesis. It draws carriers, modulators, fan-out, the audio bus and self-feedback from the shared pure geometry core while keeping the host in control of selection, colours and descriptive copy.",
  "dependencies": [
    "maruaudio@>=0.0.3 <0.1.0"
  ],
  "registryDependencies": [],
  "files": [
    {
      "path": "components/ui/fm-algorithm-diagram.tsx",
      "content": "/**\n * Source: https://maru.audio/ui-kit/fm-algorithm-diagram\n * Copyright © [Maru](https://maru.audio)\n *\n * Maru UI Kit — https://maru.audio/ui-kit\n *\n * Installed from the Maru UI registry. This file is yours now: edit it\n * freely, it will not be overwritten unless you re-run the add command.\n *\n * Keep this item’s companion files and CSS imports together. Dependencies\n * declared by the registry are installed with the source.\n */\n/**\n * A reusable, selectable view of an FM operator routing graph.\n *\n * The component owns presentation and interaction only. The host supplies the\n * wiring, selection, operator colours and optional caption. Geometry comes\n * from the UI kit's pure `fm-algorithm-layout` core, so every synth draws the\n * same graph for the same routing.\n */\n\nimport { useState } from \"react\"\nimport type { CSSProperties, KeyboardEvent, ReactNode } from \"react\"\nimport {\n  layoutFmAlgorithm,\n  type FmLayoutOptions,\n  type FmWiring,\n} from \"maruaudio/ui/shared/fm-algorithm-layout\"\nimport { token } from \"maruaudio/ui/shared/theme\"\n\nexport interface FmAlgorithmDiagramProps {\n  /** Operator routing to draw. Operators are numbered from one. */\n  wiring: FmWiring\n  /** Controlled selected operator. Omit when the graph is only a display. */\n  selectedOperator?: number\n  /** Makes operator boxes interactive when present. */\n  onSelectOperator?: (operator: number) => void\n  /** Optional colour per operator, indexed from operator one. */\n  operatorColors?: readonly string[]\n  /** Audio bus and carrier lead colour. Defaults to the kit accent. */\n  accentColor?: string\n  /** Modulation connector colour. Defaults to the kit muted text token. */\n  connectorColor?: string\n  /** Self-feedback loop colour. Defaults to the secondary accent. */\n  feedbackColor?: string\n  /** Operator label colour. Defaults to the kit text token. */\n  textColor?: string\n  /** Unselected operator face. Defaults to the kit cap token. */\n  surfaceColor?: string\n  /** Geometry overrides passed to the pure layout core. */\n  layoutOptions?: FmLayoutOptions\n  /** Rendered below the diagram. */\n  caption?: ReactNode\n  captionStyle?: CSSProperties\n  height?: number\n  className?: string\n  style?: CSSProperties\n  \"aria-label\"?: string\n}\n\nexport function FmAlgorithmDiagram({\n  wiring,\n  selectedOperator,\n  onSelectOperator,\n  operatorColors,\n  accentColor = token.accent,\n  connectorColor = token.textMuted,\n  feedbackColor = token.accent2,\n  textColor = token.text,\n  surfaceColor = token.cap,\n  layoutOptions,\n  caption,\n  captionStyle,\n  height = 205,\n  className,\n  style,\n  \"aria-label\": ariaLabel = \"FM algorithm routing\",\n}: FmAlgorithmDiagramProps) {\n  const layout = layoutFmAlgorithm(wiring, layoutOptions)\n  const interactive = typeof onSelectOperator === \"function\"\n  const [focusedOperator, setFocusedOperator] = useState<number | null>(null)\n\n  const selectFromKeyboard =\n    (operator: number) => (event: KeyboardEvent<SVGGElement>) => {\n      if (event.key !== \"Enter\" && event.key !== \" \") return\n      event.preventDefault()\n      onSelectOperator?.(operator)\n    }\n\n  return (\n    <figure\n      className={className}\n      aria-label={ariaLabel}\n      style={{ margin: 0, fontFamily: token.font, ...style }}\n    >\n      <svg\n        viewBox={`0 0 ${layout.width} ${layout.height}`}\n        width=\"100%\"\n        height={height}\n        style={{ display: \"block\", overflow: \"visible\" }}\n        role=\"group\"\n        aria-label={ariaLabel}\n      >\n        {layout.connectors.map((connector) => (\n          <path\n            key={`${connector.from}-${connector.to}`}\n            d={connector.path}\n            fill=\"none\"\n            stroke={connectorColor}\n            strokeWidth={connector.primary ? 1.5 : 1}\n            opacity={connector.primary ? 1 : 0.46}\n            aria-hidden\n          />\n        ))}\n        {layout.outputs.map((output) => (\n          <path\n            key={`out-${output.op}`}\n            d={output.path}\n            fill=\"none\"\n            stroke={accentColor}\n            strokeWidth=\"1.7\"\n            aria-hidden\n          />\n        ))}\n        <path\n          d={layout.busPath}\n          fill=\"none\"\n          stroke={accentColor}\n          strokeWidth=\"2.2\"\n          aria-hidden\n        />\n        {layout.feedbackPath ? (\n          <path\n            d={layout.feedbackPath}\n            fill=\"none\"\n            stroke={feedbackColor}\n            strokeWidth=\"1.4\"\n            strokeDasharray=\"3 2\"\n            aria-hidden\n          />\n        ) : null}\n        {layout.boxes.map((box) => {\n          const selected = box.op === selectedOperator\n          const focused = box.op === focusedOperator\n          const operatorColor = operatorColors?.[box.op - 1] ?? accentColor\n\n          return (\n            <g\n              key={box.op}\n              role={interactive ? \"button\" : undefined}\n              tabIndex={interactive ? 0 : undefined}\n              aria-label={`Operator ${box.op}${box.carrier ? \", carrier\" : \", modulator\"}`}\n              aria-pressed={interactive ? selected : undefined}\n              data-operator={box.op}\n              data-carrier={box.carrier ? \"true\" : \"false\"}\n              data-selected={selected ? \"true\" : \"false\"}\n              onClick={interactive ? () => onSelectOperator(box.op) : undefined}\n              onKeyDown={interactive ? selectFromKeyboard(box.op) : undefined}\n              onFocus={\n                interactive ? () => setFocusedOperator(box.op) : undefined\n              }\n              onBlur={interactive ? () => setFocusedOperator(null) : undefined}\n              style={{\n                cursor: interactive ? \"pointer\" : \"default\",\n                outlineColor: token.focus,\n              }}\n            >\n              <rect\n                x={box.x}\n                y={box.y}\n                width={box.width}\n                height={box.height}\n                rx=\"3\"\n                fill={\n                  selected\n                    ? `color-mix(in srgb, ${operatorColor} 20%, transparent)`\n                    : box.carrier\n                      ? `color-mix(in srgb, ${operatorColor} 10%, ${surfaceColor})`\n                      : surfaceColor\n                }\n                stroke={operatorColor}\n                strokeWidth={selected || focused ? 2.4 : 1.25}\n                strokeDasharray={focused ? \"2 1\" : undefined}\n              />\n              <text\n                x={box.cx}\n                y={box.cy + 4}\n                textAnchor=\"middle\"\n                fill={textColor}\n                fontSize=\"10\"\n                fontWeight=\"700\"\n                fontFamily={token.fontMono}\n                aria-hidden\n              >\n                {box.op}\n              </text>\n            </g>\n          )\n        })}\n      </svg>\n      {caption ? (\n        <figcaption\n          style={{\n            borderTop: `1px solid ${token.border}`,\n            paddingTop: 8,\n            color: token.textMuted,\n            fontFamily: token.fontMono,\n            fontSize: 9,\n            lineHeight: \"16px\",\n            ...captionStyle,\n          }}\n        >\n          {caption}\n        </figcaption>\n      ) : null}\n    </figure>\n  )\n}\n",
      "type": "registry:component",
      "target": "@ui/fm-algorithm-diagram.tsx"
    }
  ]
}
