{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "core-eurorack",
  "type": "registry:ui",
  "title": "Eurorack hardware and Mutable panel styling",
  "description": "Physical rack panels, round metal jacks and Mutable-style hardware knobs. MutableKnobDesign composes KnobHeadlessReadout: one printed caption swaps from label to formatted value on hover or edit, with shared typography and no tooltip. Hosts own actual patching and audio.",
  "dependencies": [
    "maruaudio@>=0.0.3 <0.1.0"
  ],
  "registryDependencies": [
    "https://pluginui.com/r/core-knob.json"
  ],
  "files": [
    {
      "path": "components/ui/eurorack.tsx",
      "content": "/**\n * Source: https://maru.audio/ui-kit/eurorack\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 */\nimport { forwardRef, useId, useLayoutEffect, useRef } from \"react\"\nimport type {\n  ComponentProps,\n  CSSProperties,\n  HTMLAttributes,\n  MouseEvent,\n  PointerEvent as ReactPointerEvent,\n  ReactNode,\n} from \"react\"\nimport { audioModulePanelDimensions } from \"maruaudio/ui/shared/maru/contracts\"\nimport type {\n  AudioModuleFacePatchingSurface,\n  AudioModuleFacePatchAnchor,\n  AudioModulePanelPortAnchor,\n} from \"maruaudio/ui/shared/maru/contracts\"\nimport { Knob, type KnobDesignState } from \"@/components/ui/knob\"\nimport { KnobHeadlessReadout } from \"maruaudio/ui/shared/knob-headless\"\nimport \"@/components/ui/eurorack.css\"\n\nexport interface EurorackPanelProps extends HTMLAttributes<HTMLElement> {\n  widthHp: number\n  /** Display density, independent of physical screen DPI or browser CSS mm. */\n  pixelsPerMm?: number\n  label: string\n  /** Source-panel mounting centers in millimetres; omitted panels use corner mounts. */\n  mountingHoles?: readonly { x: number; y: number }[]\n  children?: ReactNode\n}\n\n/** Portable 3U panel. The module owns controls; the host owns placement. */\nexport const EurorackPanel = forwardRef<HTMLElement, EurorackPanelProps>(\n  function EurorackPanel(\n    { widthHp, pixelsPerMm = 3, label, mountingHoles, children, className = \"\", style, ...props },\n    ref\n  ) {\n    const screwIdentity = useId()\n    const dimensions = audioModulePanelDimensions({\n      format: \"eurorack\",\n      units: \"mm\",\n      widthHp,\n      ports: [],\n    })\n    if (!Number.isFinite(pixelsPerMm) || pixelsPerMm <= 0)\n      throw new Error(\"Eurorack display density must be positive and finite.\")\n    if (mountingHoles?.some(({ x, y }) => !Number.isFinite(x) || !Number.isFinite(y)))\n      throw new Error(\"Panel mounting centers must be finite.\")\n    return (\n      <section\n        {...props}\n        ref={ref}\n        aria-label={label}\n        data-eurorack-panel=\"\"\n        data-width-hp={widthHp}\n        className={`uikit-eurorack-panel ${className}`}\n        style={{\n          ...style,\n          \"--eurorack-mm\": `${pixelsPerMm}px`,\n          width: dimensions.width * pixelsPerMm,\n          height: dimensions.height * pixelsPerMm,\n        } as CSSProperties}\n      >\n        {(mountingHoles ?? [null, null, null, null]).map((mount, index) => {\n          // Stable per mounted panel: no random render/hydration changes while editing.\n          const seed = `${label}:${screwIdentity}:${index}`\n          const hash = Array.from(seed).reduce((hash, character) =>\n            (hash * 31 + character.charCodeAt(0)) >>> 0, 17)\n          const mixed = Math.imul(hash ^ (hash >>> 16), 0x45d9f3b)\n          const angle = ((mixed ^ (mixed >>> 16)) >>> 0) % 180\n          return <span key={index} aria-hidden\n            className={`uikit-eurorack-screw${mount ? \"\" : ` uikit-eurorack-screw--${[\"tl\", \"tr\", \"bl\", \"br\"][index]}`}`}\n            style={{\n              \"--eurorack-screw-angle\": `${angle}deg`,\n              ...(mount ? {\n                left: `calc(${mount.x} * var(--eurorack-mm))`,\n                top: `calc(${mount.y} * var(--eurorack-mm))`,\n                transform: \"translate(-50%, -50%)\",\n              } : {}),\n            } as CSSProperties} />\n        })}\n        {children}\n      </section>\n    )\n  }\n)\n\nexport interface MutableKnobCaptionSpec {\n  label: ReactNode\n  /** Caption center relative to the knob center, in panel millimetres. */\n  offsetMm: number\n  widthMm: number\n  tone?: \"plain\" | \"pink\" | \"teal\"\n  /** Attenuverters replace their printed minus/plus marks with the value. */\n  bipolar?: boolean\n}\n\n/** Hardware paint and the existing headless caption; Knob owns every gesture. */\nexport function MutableKnobDesign({ value01, size, formattedValue, caption }: KnobDesignState & {\n  caption?: MutableKnobCaptionSpec\n}) {\n  return <span className=\"uikit-mutable-knob\" data-small={size < 36 || undefined}\n    style={{ \"--mutable-knob-angle\": `${-135 + value01 * 270}deg` } as CSSProperties}>\n    <span className=\"uikit-mutable-knob__skirt\" />\n    <span className=\"uikit-mutable-knob__cap\" />\n    <span className=\"uikit-mutable-knob__index\" />\n    {caption && <span className=\"uikit-mutable-knob__caption\" data-tone={caption.tone ?? \"plain\"}\n      data-bipolar={caption.bipolar || undefined}\n      style={{\n        top: `calc(50% + ${caption.offsetMm} * var(--eurorack-mm, 4px))`,\n        width: `calc(${caption.widthMm} * var(--eurorack-mm, 4px))`,\n      }}>\n      <KnobHeadlessReadout label={caption.bipolar\n        ? <span style={{display:\"flex\",justifyContent:\"space-between\",width:\"100%\"}}><span>−</span><span>+</span></span>\n        : caption.label} value={formattedValue}\n        showLabelOnHover valueReveal=\"hover-or-edit\"\n        labelStyle={{ font: \"inherit\", color: \"inherit\", whiteSpace: \"pre\" }}\n        outputStyle={{ font: \"inherit\", color: \"inherit\",\n          ...(caption.bipolar ? { background: \"var(--uikit-surface, #efefeb)\" } : {}),\n        }} />\n    </span>}\n  </span>\n}\n\nexport interface EurorackPanelItemProps extends HTMLAttributes<HTMLDivElement> {\n  /** Centre coordinates in millimetres, relative to the panel top-left. */\n  x: number\n  y: number\n  width?: number\n  height?: number\n}\n\n/** Places existing controls/labels at authored physical panel coordinates. */\nexport const EurorackPanelItem = forwardRef<HTMLDivElement, EurorackPanelItemProps>(\n  function EurorackPanelItem(\n    { x, y, width, height, className = \"\", style, children, ...props },\n    ref\n  ) {\n    if (![x, y].every(Number.isFinite) ||\n      [width, height].some((value) => value !== undefined &&\n        (!Number.isFinite(value) || value <= 0)))\n      throw new Error(\"Eurorack item geometry must be finite with positive dimensions.\")\n    return (\n      <div\n        {...props}\n        ref={ref}\n        className={`uikit-eurorack-item ${className}`}\n        style={{\n          ...style,\n          left: `calc(${x} * var(--eurorack-mm))`,\n          top: `calc(${y} * var(--eurorack-mm))`,\n          ...(width === undefined ? {} : { width: `calc(${width} * var(--eurorack-mm))` }),\n          ...(height === undefined ? {} : { height: `calc(${height} * var(--eurorack-mm))` }),\n        }}\n      >{children}</div>\n    )\n  }\n)\n\n/** Shared knob gestures, formatting and accessibility with panel defaults. */\nexport function EurorackKnob(props: ComponentProps<typeof Knob>) {\n  return <Knob variant=\"panel\" showLabelOnHover {...props} />\n}\n\nexport interface PatchJackProps extends Omit<ComponentProps<\"button\">, \"children\"> {\n  portId: string\n  direction: \"input\" | \"output\"\n  label: string\n  /** Display hint only; the host validates actual signal compatibility. */\n  kind?: string\n  /** Panel artwork may already carry a visible label; the accessible name remains. */\n  hideLabel?: boolean\n  connected?: boolean\n  selected?: boolean\n  onActivate?: (event: MouseEvent<HTMLButtonElement>) => void\n}\n\n/** A host-controlled jack. Native button semantics provide keyboard activation. */\nexport const PatchJack = forwardRef<HTMLButtonElement, PatchJackProps>(\n  function PatchJack(\n    {\n      portId, direction, label, kind, hideLabel = false, connected = false, selected = false,\n      onActivate, onClick, className = \"\", disabled, ...props\n    },\n    ref\n  ) {\n    const metalId = `patch-jack-metal-${useId().replace(/:/g, \"\")}`\n    return (\n      <button\n        {...props}\n        ref={ref}\n        type=\"button\"\n        disabled={disabled}\n        data-patch-port-id={portId}\n        data-patch-port-direction={direction}\n        data-patch-port-kind={kind}\n        data-patch-connected={connected ? \"true\" : \"false\"}\n        data-patch-selected={selected ? \"true\" : \"false\"}\n        aria-label={props[\"aria-label\"] ??\n          `${label} ${direction}${connected ? \", connected\" : \"\"}`}\n        aria-pressed={selected}\n        className={`uikit-patch-jack ${className}`}\n        onClick={(event) => {\n          onClick?.(event)\n          if (!event.defaultPrevented) onActivate?.(event)\n        }}\n      >\n        <span className=\"uikit-patch-jack__hardware\" data-patch-port-anchor=\"\" aria-hidden>\n          <svg className=\"uikit-patch-jack__metalwork\" viewBox=\"0 0 48 48\" focusable=\"false\">\n            <defs>\n              <radialGradient id={`${metalId}-rim`} cx=\"34%\" cy=\"23%\" r=\"78%\">\n                <stop offset=\"0\" stopColor=\"#fffdf2\" />\n                <stop offset=\".2\" stopColor=\"#e5dfd0\" />\n                <stop offset=\".48\" stopColor=\"#aaa493\" />\n                <stop offset=\".7\" stopColor=\"#77776e\" />\n                <stop offset=\".86\" stopColor=\"#d9d4c5\" />\n                <stop offset=\"1\" stopColor=\"#706f66\" />\n              </radialGradient>\n              <linearGradient id={`${metalId}-bevel`} x1=\"0\" y1=\"0\" x2=\"1\" y2=\"1\">\n                <stop offset=\"0\" stopColor=\"#fffef6\" />\n                <stop offset=\".22\" stopColor=\"#c7c0af\" />\n                <stop offset=\".48\" stopColor=\"#7a796f\" />\n                <stop offset=\".68\" stopColor=\"#eee9dc\" />\n                <stop offset=\"1\" stopColor=\"#817d72\" />\n              </linearGradient>\n              <linearGradient id={`${metalId}-inner`} x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n                <stop offset=\"0\" stopColor=\"#fffdf2\" />\n                <stop offset=\".28\" stopColor=\"#9e998b\" />\n                <stop offset=\".56\" stopColor=\"#55564f\" />\n                <stop offset=\".8\" stopColor=\"#ddd8ca\" />\n                <stop offset=\"1\" stopColor=\"#706e65\" />\n              </linearGradient>\n              <linearGradient id={`${metalId}-tooth`} x1=\"0\" y1=\"0\" x2=\"1\" y2=\"0\">\n                <stop offset=\"0\" stopColor=\"#6a685f\" />\n                <stop offset=\".45\" stopColor=\"#f3eedf\" />\n                <stop offset=\"1\" stopColor=\"#8a867a\" />\n              </linearGradient>\n            </defs>\n            <circle className=\"uikit-patch-jack__rim\" cx=\"24\" cy=\"24\" r=\"22.4\" fill={`url(#${metalId}-rim)`} />\n            <circle className=\"uikit-patch-jack__step\" cx=\"24\" cy=\"24\" r=\"18.5\" fill={`url(#${metalId}-bevel)`} />\n            <circle className=\"uikit-patch-jack__step-face\" cx=\"24\" cy=\"24\" r=\"15.9\" />\n            <circle className=\"uikit-patch-jack__bore-lip\" cx=\"24\" cy=\"24\" r=\"12.1\" fill={`url(#${metalId}-inner)`} />\n            {Array.from({ length: 32 }, (_, index) => (\n              <path\n                key={index}\n                className=\"uikit-patch-jack__knurl-facet\"\n                d=\"M23.15 1.15h1.7l.7 2.5h-3.1z\"\n                transform={`rotate(${index * 11.25} 24 24)`}\n                fill={`url(#${metalId}-tooth)`}\n              />\n            ))}\n            <path className=\"uikit-patch-jack__tool-notch\" d=\"M21.4 2.9h5.2l-.9 2.7h-3.4z\" />\n            <path className=\"uikit-patch-jack__tool-notch\" d=\"M21.4 45.1h5.2l-.9-2.7h-3.4z\" />\n            <path className=\"uikit-patch-jack__specular\" d=\"M8.2 16.1a17 17 0 0 1 8.5-8.4\" />\n            <path className=\"uikit-patch-jack__wear\" d=\"M36.5 36.4a17 17 0 0 1-6.2 4.2\" />\n          </svg>\n          <span className=\"uikit-patch-jack__socket\" />\n        </span>\n        {!hideLabel && <span className=\"uikit-patch-jack__label\">{label}</span>}\n      </button>\n    )\n  }\n)\n\nexport interface FacePatchZoneProps {\n  patching: AudioModuleFacePatchingSurface | undefined\n  /** Physical mm positions inside the surrounding EurorackPanel. */\n  layout?: readonly AudioModulePanelPortAnchor[]\n  portIds?: readonly string[]\n  /** Use the labels printed in authored panel artwork instead of duplicating them. */\n  hideLabels?: boolean\n  className?: string\n  style?: CSSProperties\n}\n\n/** Same jack projection in a source face and an isolated packaged face. */\nexport function FacePatchZone({\n  patching, layout, portIds, hideLabels = false, className = \"\", style,\n}: FacePatchZoneProps) {\n  const root = useRef<HTMLDivElement>(null)\n  const buttons = useRef(new Map<string, HTMLButtonElement>())\n  const patchingRef = useRef(patching)\n  const measureRef = useRef<(() => void) | undefined>(undefined)\n  patchingRef.current = patching\n  const ports = (patching?.ports ?? []).filter((port) =>\n    (!portIds || portIds.includes(port.portId)) &&\n    (!layout || layout.some((anchor) =>\n      anchor.portId === port.portId && anchor.direction === port.direction)))\n  const endpoints = JSON.stringify(ports.map(({ portId, direction }) => [portId, direction]))\n  const layoutKey = JSON.stringify(layout ?? null)\n  const available = patching !== undefined\n  useLayoutEffect(() => {\n    const zone = root.current\n    if (!available || !zone) return\n    // Positioned zones pass through pointer input; the physical panel also\n    // includes blank print and knobs outside the jack hit targets.\n    const surface = zone.closest<HTMLElement>(\"[data-eurorack-panel]\") ?? zone\n    let hovered = false\n    const enter = (event: PointerEvent) => {\n      if (event.relatedTarget instanceof Node && surface.contains(event.relatedTarget)) return\n      if (hovered) return\n      hovered = true\n      patchingRef.current?.reportHover?.(true)\n    }\n    const leave = (event: PointerEvent) => {\n      if (event.relatedTarget instanceof Node && surface.contains(event.relatedTarget)) return\n      if (!hovered) return\n      hovered = false\n      patchingRef.current?.reportHover?.(false)\n    }\n    surface.addEventListener(\"pointerover\", enter)\n    surface.addEventListener(\"pointerout\", leave)\n    return () => {\n      surface.removeEventListener(\"pointerover\", enter)\n      surface.removeEventListener(\"pointerout\", leave)\n      patchingRef.current?.reportHover?.(false)\n    }\n    // The live callback is already read through patchingRef. Rebinding on its\n    // identity would clear hover without a new pointer entry to restore it.\n  }, [available])\n  useLayoutEffect(() => {\n    if (!available || !root.current) return\n    const mountedPatching = patchingRef.current\n    let previous = \"\"\n    let frame: number | undefined\n    const measure = () => {\n      frame = undefined\n      const current = patchingRef.current\n      if (!current) return\n      const anchors: AudioModuleFacePatchAnchor[] = []\n      for (const port of current.ports) {\n        const button = buttons.current.get(port.portId)\n        if (!button) continue\n        const rect = button.getBoundingClientRect()\n        if (rect.width <= 0 || rect.height <= 0) continue\n        anchors.push({\n          portId: port.portId, direction: port.direction,\n          x: rect.left + rect.width / 2, y: rect.top + rect.height / 2,\n        })\n      }\n      const key = JSON.stringify(anchors)\n      if (key !== previous) {\n        previous = key\n        current.reportAnchors(anchors)\n      }\n    }\n    const schedule = () => {\n      if (frame === undefined) frame = requestAnimationFrame(measure)\n    }\n    // Initial frame height can be smaller than the finished panel. Re-send on\n    // viewport resize even if local coordinates stayed identical.\n    const resize = () => { previous = \"\"; schedule() }\n    measureRef.current = schedule\n    const observer = new ResizeObserver(schedule)\n    observer.observe(root.current)\n    for (const button of buttons.current.values()) observer.observe(button)\n    window.addEventListener(\"resize\", resize)\n    window.addEventListener(\"scroll\", schedule, true)\n    measure()\n    return () => {\n      observer.disconnect()\n      window.removeEventListener(\"resize\", resize)\n      window.removeEventListener(\"scroll\", schedule, true)\n      if (frame !== undefined) cancelAnimationFrame(frame)\n      measureRef.current = undefined\n      mountedPatching?.reportAnchors([])\n    }\n  }, [available, endpoints, layoutKey, patching?.reportAnchors])\n  useLayoutEffect(() => { measureRef.current?.() })\n  if (!patching) return null\n  return (\n    <div ref={root} data-face-patch-zone=\"\"\n      className={`uikit-face-patch-zone ${layout ? \"uikit-face-patch-zone--positioned\" : \"\"} ${className}`}\n      style={style}>\n      {ports.map((port) => {\n        const jack = <PatchJack\n          {...port}\n          hideLabel={hideLabels}\n          ref={(element) => {\n            if (element) buttons.current.set(port.portId, element)\n            else buttons.current.delete(port.portId)\n          }}\n          onActivate={() => patchingRef.current?.activate(port.portId, port.direction)}\n        />\n        const anchor = layout?.find((item) =>\n          item.portId === port.portId && item.direction === port.direction)\n        return anchor\n          ? <EurorackPanelItem key={port.portId} x={anchor.x} y={anchor.y}>{jack}</EurorackPanelItem>\n          : <span key={port.portId}>{jack}</span>\n      })}\n    </div>\n  )\n}\n\nexport interface PatchCablePoint { readonly x: number; readonly y: number }\nexport interface PatchCable {\n  readonly id: string\n  readonly source: PatchCablePoint\n  readonly target: PatchCablePoint\n  readonly color?: string\n  readonly selected?: boolean\n  readonly highlighted?: boolean\n  /** A drag preview participates in motion but never intercepts input. */\n  readonly interactive?: boolean\n  /** Visual destination feedback; the host alone owns snap and drop validity. */\n  readonly snappedEnd?: PatchCableEnd\n  /** Accessible connection name supplied from the host's declared endpoints. */\n  readonly label?: string\n}\n\n/** Hanging cable geometry in host-local pixels; no routing or hit testing. */\nexport function patchCablePath(\n  source: PatchCablePoint,\n  target: PatchCablePoint,\n  bendOffset = 0\n): string | null {\n  if (![source.x, source.y, target.x, target.y, bendOffset].every(Number.isFinite)) return null\n  const dx = target.x - source.x\n  const sag = Math.min(120, 28 + Math.hypot(dx, target.y - source.y) * 0.18)\n  const controls = [source.x + dx * 0.2, source.y + sag + bendOffset,\n    target.x - dx * 0.2, target.y + sag + bendOffset]\n  if (!controls.every(Number.isFinite)) return null\n  const n = (value: number) => Math.abs(value) > Number.MAX_SAFE_INTEGER / 1_000_000\n    ? value : Math.round(value * 1_000_000) / 1_000_000\n  return `M ${n(source.x)} ${n(source.y)} C ${n(controls[0]!)} ${n(controls[1]!)}, ${n(controls[2]!)} ${n(controls[3]!)}, ${n(target.x)} ${n(target.y)}`\n}\n\nexport type PatchCableEnd = \"source\" | \"target\"\n\nexport interface PatchCableLabel {\n  readonly id: string\n  readonly point: PatchCablePoint\n  readonly label: string\n  readonly color?: string\n}\n\n/** Keep temporary captions clear of sockets and of each other in dense panels. */\nfunction cableLabelLayout(labels: readonly PatchCableLabel[], width: number) {\n  const placed: { x: number; y: number; width: number; label: PatchCableLabel }[] = []\n  for (const label of [...labels].sort((a, b) => a.point.y - b.point.y || a.point.x - b.point.x)) {\n    if (![label.point.x, label.point.y].every(Number.isFinite)) continue\n    const size = Math.min(width - 8, Math.max(36, label.label.length * 6.5 + 16))\n    const x = Math.max(4, Math.min(width - size - 4, label.point.x - size / 2))\n    let y = label.point.y - 46\n    while (placed.some((box) => x < box.x + box.width + 4 && x + size + 4 > box.x &&\n      y < box.y + 24 && y + 24 > box.y) ||\n      labels.some(({ point }) => x < point.x + 22 && x + size > point.x - 22 &&\n        y < point.y + 22 && y + 22 > point.y - 22)) y -= 26\n    placed.push({ x, y, width: size, label })\n  }\n  return placed\n}\n\nexport interface PatchCableLayerProps {\n  width: number\n  height: number\n  cables: readonly PatchCable[]\n  /** Host-derived captions paint above every cable without intercepting gestures. */\n  labels?: readonly PatchCableLabel[]\n  onCableHover?: (id: string | null) => void\n  onCableSelect?: (id: string) => void\n  onCableDoubleClick?: (id: string) => void\n  onCableEndPointerDown?: (\n    id: string, end: PatchCableEnd, event: ReactPointerEvent<SVGCircleElement>\n  ) => void\n  /** Keyboard/click equivalent of picking up a selected cable end. */\n  onCableEndActivate?: (id: string, end: PatchCableEnd) => void\n  className?: string\n  style?: CSSProperties\n}\n\n/** Controlled overlay. Endpoint measurement and connection mutations belong to the host. */\nexport function PatchCableLayer({\n  width, height, cables, labels = [], onCableHover, onCableSelect, onCableDoubleClick,\n  onCableEndPointerDown, onCableEndActivate, className = \"\", style,\n}: PatchCableLayerProps) {\n  const layerId = useId().replace(/:/g, \"\")\n  if (![width, height].every((value) => Number.isFinite(value) && value > 0)) return null\n  const interactive = Boolean(onCableSelect || onCableDoubleClick ||\n    onCableEndPointerDown || onCableEndActivate)\n  return (\n    <svg\n      className={`uikit-patch-cables ${className}`}\n      width={width}\n      height={height}\n      viewBox={`0 0 ${width} ${height}`}\n      style={style}\n      aria-hidden={interactive ? undefined : true}\n      role={interactive ? \"group\" : undefined}\n      aria-label={interactive ? \"Patch cables\" : undefined}\n    >\n      {cables.map((cable, index) => (\n        <PatchCableGraphic key={cable.id} cable={cable} paintId={`${layerId}-paint-${index}`}\n          onCableHover={onCableHover}\n          onCableSelect={onCableSelect} onCableDoubleClick={onCableDoubleClick}\n          onCableEndPointerDown={onCableEndPointerDown} onCableEndActivate={onCableEndActivate}\n        />\n      ))}\n      {/* Repaint only the visual paths. Moving interactive groups on hover can\n          change hit targets at crossings and interrupt clicks or plug drags. */}\n      <g className=\"uikit-patch-cable-foreground\" aria-hidden=\"true\" pointerEvents=\"none\">\n        {cables.map((cable, index) => ({ cable, index }))\n          .filter(({ cable }) => cable.selected || cable.highlighted)\n          .sort((a, b) => Number(Boolean(a.cable.highlighted)) - Number(Boolean(b.cable.highlighted)))\n          .map(({ cable, index }) => (\n            <use key={cable.id} href={`#${layerId}-paint-${index}`}\n              data-patch-cable-foreground={cable.id}\n              style={{ \"--patch-cable-color\": cable.color ?? \"var(--uikit-accent, #c87039)\" } as CSSProperties} />\n          ))}\n      </g>\n      <g className=\"uikit-patch-cable-labels\" aria-hidden=\"true\">\n        {cableLabelLayout(labels, width).map(({ x, y, width: labelWidth, label }) => (\n          <g key={label.id} data-patch-cable-label={label.id}\n            style={{ \"--patch-cable-color\": label.color ?? \"#555a55\" } as CSSProperties}>\n            <path className=\"uikit-patch-cable-label__guide\"\n              d={`M ${label.point.x} ${label.point.y - 17} L ${x + labelWidth / 2} ${y + 22}`} />\n            <rect x={x} y={y} width={labelWidth} height={22} />\n            <text x={x + labelWidth / 2} y={y + 15}>{label.label}</text>\n          </g>\n        ))}\n      </g>\n    </svg>\n  )\n}\n\n/** A spring only paints SVG geometry; graph state and socket positions stay fixed. */\nfunction PatchCableGraphic({\n  cable, paintId, onCableHover, onCableSelect, onCableDoubleClick, onCableEndPointerDown, onCableEndActivate,\n}: Pick<PatchCableLayerProps, \"onCableSelect\" | \"onCableDoubleClick\" |\n  \"onCableEndPointerDown\" | \"onCableEndActivate\" | \"onCableHover\"> & { cable: PatchCable; paintId: string }) {\n  const group = useRef<SVGGElement>(null)\n  const clipId = useId().replace(/:/g, \"\")\n  const current = useRef(cable)\n  current.current = cable\n  const spring = useRef({ offset: 0, velocity: 0, frame: undefined as number | undefined,\n    lastTime: undefined as number | undefined, reduced: true })\n  const previous = useRef({ source: cable.source, target: cable.target })\n  const paint = () => {\n    const path = patchCablePath(current.current.source, current.current.target, spring.current.offset)\n    if (path !== null)\n      group.current?.querySelectorAll(\"[data-patch-cable-path]\").forEach((element) =>\n        element.setAttribute(\"d\", path))\n  }\n  const stop = () => {\n    const motion = spring.current\n    if (motion.frame !== undefined) cancelAnimationFrame(motion.frame)\n    motion.frame = undefined\n    motion.lastTime = undefined\n    motion.offset = 0\n    motion.velocity = 0\n    paint()\n  }\n  const tick = (time: number) => {\n    const motion = spring.current\n    motion.frame = undefined\n    if (motion.reduced) { stop(); return }\n    const dt = motion.lastTime === undefined ? 1 / 60 : Math.min(0.032, Math.max(0, (time - motion.lastTime) / 1000))\n    motion.lastTime = time\n    motion.velocity += (-100 * motion.offset - 10 * motion.velocity) * dt\n    motion.offset = Math.max(-48, Math.min(48, motion.offset + motion.velocity * dt))\n    if (Math.abs(motion.offset) < 0.04 && Math.abs(motion.velocity) < 0.2) {\n      stop()\n      return\n    }\n    paint()\n    motion.frame = requestAnimationFrame(tick)\n  }\n  const excite = (impulse: number) => {\n    const motion = spring.current\n    if (motion.reduced || !Number.isFinite(impulse) ||\n      patchCablePath(current.current.source, current.current.target) === null) return\n    motion.velocity = Math.max(-600, Math.min(600, motion.velocity + impulse))\n    if (motion.frame === undefined) motion.frame = requestAnimationFrame(tick)\n  }\n  useLayoutEffect(() => {\n    const media = window.matchMedia?.(\"(prefers-reduced-motion: reduce)\")\n    spring.current.reduced = media?.matches ?? false\n    const change = () => {\n      spring.current.reduced = media?.matches ?? false\n      if (spring.current.reduced) stop()\n    }\n    media?.addEventListener(\"change\", change)\n    excite(110)\n    return () => {\n      media?.removeEventListener(\"change\", change)\n      stop()\n    }\n  }, [])\n  useLayoutEffect(() => {\n    const before = previous.current\n    const dx = (cable.target.x - before.target.x) - (cable.source.x - before.source.x)\n    const dy = (cable.target.y - before.target.y) - (cable.source.y - before.source.y)\n    previous.current = { source: cable.source, target: cable.target }\n    if (dx || dy) excite(Math.max(-280, Math.min(280, dy * 14 + dx * 7)))\n    paint()\n  }, [cable.source.x, cable.source.y, cable.target.x, cable.target.y])\n\n  const d = patchCablePath(cable.source, cable.target, spring.current.offset)\n  if (d === null) return null\n  const interactive = cable.interactive !== false\n  const handles = interactive && cable.selected && (onCableEndPointerDown || onCableEndActivate)\n  // Geometric holes, rather than a dashed stroke, keep the socket centers\n  // available for fan-out while the full intervening cable remains clickable.\n  const extent = Math.max(Math.abs(cable.source.x), Math.abs(cable.source.y),\n    Math.abs(cable.target.x), Math.abs(cable.target.y)) + 300\n  const hole = ({ x, y }: PatchCablePoint) =>\n    `M ${x - 20} ${y} a 20 20 0 1 0 40 0 a 20 20 0 1 0 -40 0 Z`\n  return (\n    <g ref={group} data-patch-cable-id={cable.id}\n      data-selected={cable.selected ? \"true\" : \"false\"}\n      data-highlighted={cable.highlighted ? \"true\" : \"false\"}\n      onPointerEnter={interactive ? () => onCableHover?.(cable.id) : undefined}\n      onPointerLeave={interactive ? () => onCableHover?.(null) : undefined}\n      onFocus={interactive ? () => onCableHover?.(cable.id) : undefined}\n      onBlur={interactive ? () => onCableHover?.(null) : undefined}\n      aria-hidden={!interactive || undefined}\n      style={{ \"--patch-cable-color\": cable.color ?? \"var(--uikit-accent, #c87039)\" } as CSSProperties}\n    >\n      <defs>\n        {([\"source\", \"target\"] as const).map((end) => (\n          <clipPath key={end} id={`${clipId}-${end}`}>\n            <path clipRule=\"evenodd\" d={`M ${-extent} ${-extent} H ${extent} V ${extent} H ${-extent} Z ${hole(cable[end])}`} />\n          </clipPath>\n        ))}\n      </defs>\n      <g id={paintId}>\n        <path data-patch-cable-path=\"\" className=\"uikit-patch-cable__shadow\" d={d} />\n        <path data-patch-cable-path=\"\" className=\"uikit-patch-cable__body\" d={d} />\n        <path data-patch-cable-path=\"\" className=\"uikit-patch-cable__shine\" d={d} />\n      </g>\n      {interactive && (onCableSelect || onCableDoubleClick) ? (\n        <g clipPath={`url(#${clipId}-target)`}>\n        {/* Hit-test the rest geometry: the spring repaints only the visible\n            paths, so a still pointer never sees its target sway away. */}\n        <path data-patch-cable-hit=\"\"\n          className=\"uikit-patch-cable__hit\" d={patchCablePath(cable.source, cable.target) ?? d} clipPath={`url(#${clipId}-source)`}\n          role={onCableSelect ? \"button\" : undefined}\n          tabIndex={onCableSelect ? 0 : undefined}\n          aria-label={cable.label ?? `Select cable ${cable.id}`}\n          aria-pressed={onCableSelect ? Boolean(cable.selected) : undefined}\n          onPointerEnter={() => excite(100)}\n          onClick={() => onCableSelect?.(cable.id)}\n          onDoubleClick={() => onCableDoubleClick?.(cable.id)}\n          onKeyDown={(event) => {\n            if (onCableSelect && (event.key === \"Enter\" || event.key === \" \")) {\n              event.preventDefault()\n              onCableSelect(cable.id)\n            }\n          }}\n        />\n        </g>\n      ) : null}\n      {cable.snappedEnd ? (\n        <circle className=\"uikit-patch-cable__snap-ring\"\n          data-patch-cable-snap={cable.snappedEnd}\n          cx={cable[cable.snappedEnd].x} cy={cable[cable.snappedEnd].y} r={20}\n          aria-hidden=\"true\" />\n      ) : null}\n      {handles ? ([\"source\", \"target\"] as const).map((end) => (\n        <g key={end}>\n          <circle className=\"uikit-patch-cable__end-ring\" cx={cable[end].x} cy={cable[end].y} r={14} />\n          <circle className=\"uikit-patch-cable__end-hit\"\n            data-patch-cable-end={end} cx={cable[end].x} cy={cable[end].y} r={22}\n            role={onCableEndActivate ? \"button\" : \"img\"}\n            tabIndex={onCableEndActivate ? 0 : undefined}\n            aria-label={`Reconnect ${end} of ${cable.label ?? `cable ${cable.id}`}`}\n            onPointerEnter={() => excite(80)}\n            onPointerDown={(event) => onCableEndPointerDown?.(cable.id, end, event)}\n            onClick={(event) => { event.stopPropagation(); onCableEndActivate?.(cable.id, end) }}\n            onKeyDown={(event) => {\n              if (onCableEndActivate && (event.key === \"Enter\" || event.key === \" \")) {\n                event.preventDefault()\n                event.stopPropagation()\n                onCableEndActivate(cable.id, end)\n              }\n            }}\n          />\n        </g>\n      )) : null}\n    </g>\n  )\n}\n",
      "type": "registry:component",
      "target": "@ui/eurorack.tsx"
    },
    {
      "path": "components/ui/eurorack.css",
      "content": "/**\n * Source: https://maru.audio/ui-kit/eurorack\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.uikit-eurorack-panel {\n  --uikit-surface: #dedfdb;\n  --uikit-text: #252723;\n  --uikit-text-muted: #4d534d;\n  --uikit-cap: #272a28;\n  --uikit-track: #a2a5a0;\n  --uikit-border: #8d918b;\n  --uikit-accent: #517b66;\n  position: relative;\n  flex: none;\n  box-sizing: border-box;\n  isolation: isolate;\n  overflow: hidden;\n  border: 1px solid var(--uikit-border);\n  border-radius: 0;\n  color: var(--uikit-text);\n  background:\n    repeating-linear-gradient(0deg, #ffffff12 0, #ffffff12 1px, transparent 1px, transparent 3px),\n    linear-gradient(90deg, #0000000b, #ffffff20 14%, transparent 45%, #ffffff30 82%, #00000010),\n    var(--uikit-surface);\n  box-shadow: inset 1px 0 #ffffff80, inset -1px 0 #00000018;\n  font-family: var(--uikit-font, ui-sans-serif, system-ui, sans-serif);\n}\n\n/* Mutable-style panel material shared by source-faithful module faces. */\n.uikit-eurorack-panel[data-panel-theme=\"mutable\"] {\n  --uikit-surface: #efefeb;\n  --uikit-text: #292929;\n  --uikit-text-muted: #555a55;\n  --uikit-cap: #c9ccc8;\n  --uikit-track: #a9ada7;\n  --uikit-border: #8c8b83;\n  --uikit-accent: #555a55;\n  --knob-arc: #555a55;\n  --uikit-knob-cap: linear-gradient(145deg, #e5e7e2 0%, #c9ccc8 48%, #b7bbb6 100%);\n  --uikit-knob-cap-border: #929790;\n  --uikit-knob-pointer: #292929;\n  --uikit-knob-shadow: inset 0 1px 1px #ffffffa0, 0 1px 2px #00000030;\n  --patch-jack-size: calc(8 * var(--eurorack-mm));\n  /* Do not reset a module's artwork size/position/repeat with a shorthand. */\n  background-color: #efefeb;\n  background-image: none;\n  border-color: #9a9991;\n}\n.uikit-eurorack-panel[data-panel-theme=\"mutable\"]::before {\n  content: \"\";\n  position: absolute;\n  inset: 0;\n  pointer-events: none;\n  opacity: .13;\n  background-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='grain'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Cpath fill='%23777777' filter='url(%23grain)' d='M0 0h160v160H0z'/%3E%3C/svg%3E\");\n  box-shadow: inset 1px 0 #fff, inset -1px 0 #00000030;\n}\n/* This design is paint within Knob's fixed square: values never move the cap. */\n.uikit-mutable-knob {\n  position: relative;\n  display: block;\n  width: 100%;\n  height: 100%;\n  border-radius: 50%;\n  background: radial-gradient(circle at 38% 28%, #343633, #111210 70%);\n  box-shadow: 0 1px 2px #00000070, 1px 3px 4px #00000048, 3px 6px 9px #00000032, 0 0 1px 1px #00000060;\n}\n.uikit-mutable-knob__skirt {\n  position: absolute;\n  inset: 2%;\n  border-radius: 50%;\n  background: repeating-conic-gradient(from 0deg, #131412 0deg 5deg, #383b36 7deg, #171916 11deg 15deg);\n  box-shadow: inset 0 1px 1px #ffffff36, inset 0 -2px 2px #000000c0;\n}\n.uikit-mutable-knob__cap {\n  position: absolute;\n  inset: 19%;\n  border-radius: 50%;\n  background: var(--mutable-knob-cap, radial-gradient(circle at 35% 23%, #fffefa, #e9e9e1 72%, #babcb3));\n  border: 1px solid #080a08;\n  box-shadow: 0 0 0 2px #090b08, inset 0 1px 1px #ffffff70, 0 1px 2px #000000a0;\n}\n.uikit-mutable-knob__index {\n  position: absolute;\n  inset: 0;\n  transform: rotate(var(--mutable-knob-angle));\n}\n.uikit-mutable-knob__index::after {\n  content: \"\";\n  position: absolute;\n  top: 2%;\n  left: 46%;\n  width: 8%;\n  height: 31%;\n  border-radius: 1px;\n  background: var(--mutable-knob-index, linear-gradient(90deg, #bfc1b7, #fffef6 40% 75%, #d6d8ce));\n  box-shadow: 0 0 1px #00000080;\n}\n.uikit-mutable-knob[data-small] .uikit-mutable-knob__cap {\n  inset: 10%;\n  background: radial-gradient(ellipse at 34% 22%, #494b45, #242621 48%, #121410 85%);\n  box-shadow: inset 0 1px 1px #ffffff25, 0 0 0 1px #0b0c09;\n}\n.uikit-mutable-knob[data-small] .uikit-mutable-knob__index::after {\n  left: 45%;\n  width: 10%;\n  top: 7%;\n  height: 39%;\n}\n.uikit-mutable-knob__caption {\n  position: absolute;\n  left: 50%;\n  transform: translate(-50%, -50%);\n  color: var(--uikit-text, #292929);\n  font-family: var(--mutable-caption-font, \"Arial Narrow\", \"Roboto Condensed\", Arial, sans-serif);\n  font-size: calc(2.65 * var(--eurorack-mm, 4px));\n  font-weight: 700;\n  line-height: 1.05;\n  letter-spacing: .01em;\n  text-transform: uppercase;\n  pointer-events: none;\n}\n.uikit-mutable-knob__caption[data-tone=\"pink\"],\n.uikit-mutable-knob__caption[data-tone=\"teal\"] { color: #fff; }\n.uikit-mutable-knob__caption[data-bipolar] { color: #686762; }\n.uikit-eurorack-panel[data-panel-theme=\"mutable\"] [data-eurorack-panel-button] {\n  width: calc(5.2 * var(--eurorack-mm));\n  height: calc(5.2 * var(--eurorack-mm));\n  min-width: calc(5.2 * var(--eurorack-mm));\n  min-height: calc(5.2 * var(--eurorack-mm));\n  max-width: calc(5.2 * var(--eurorack-mm));\n  max-height: calc(5.2 * var(--eurorack-mm));\n  aspect-ratio: 1;\n  padding: 0;\n  border-color: #6d706b;\n  border-radius: 50%;\n  background: radial-gradient(circle at 36% 25%, #4c4d48, #242621 70%);\n  box-shadow: inset 0 1px 1px #ffffff40, 0 1px 2px #00000050;\n  color: #242623;\n}\n.uikit-eurorack-panel[data-panel-theme=\"mutable\"] [data-eurorack-panel-button=\"7.8\"] {\n  width: calc(7.8 * var(--eurorack-mm));\n  height: calc(7.8 * var(--eurorack-mm));\n  min-width: calc(7.8 * var(--eurorack-mm));\n  min-height: calc(7.8 * var(--eurorack-mm));\n  max-width: calc(7.8 * var(--eurorack-mm));\n  max-height: calc(7.8 * var(--eurorack-mm));\n}\n.uikit-eurorack-panel[data-panel-theme=\"mutable\"] .uikit-patch-jack:disabled {\n  opacity: 1;\n}\n.uikit-eurorack-item {\n  position: absolute;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  transform: translate(-50%, -50%);\n  box-sizing: border-box;\n}\n.uikit-eurorack-screw {\n  position: absolute;\n  width: calc(2.8 * var(--eurorack-mm));\n  height: calc(2.8 * var(--eurorack-mm));\n  border: 1px solid #747874;\n  border-radius: 50%;\n  background: radial-gradient(ellipse at 33% 22%, #fafbf8 0 9%, #d9dcd6 32%, #b5bab3 65%, #777e76 100%);\n  box-shadow: inset 0 0 0 1px #ffffff8a, 0 0 0 calc(.35 * var(--eurorack-mm)) #74797145, 0 1px 2px #00000050;\n  pointer-events: none;\n}\n.uikit-eurorack-screw::after {\n  content: \"\";\n  position: absolute;\n  inset: 40% 12%;\n  border-radius: 1px;\n  transform: rotate(var(--eurorack-screw-angle, -32deg));\n  background: linear-gradient(#363c35, #646c61);\n  box-shadow: 0 1px #ffffffb0, inset 0 1px 1px #00000060;\n}\n.uikit-eurorack-screw--tl { left: calc(2 * var(--eurorack-mm)); top: calc(2 * var(--eurorack-mm)); }\n.uikit-eurorack-screw--tr { right: calc(2 * var(--eurorack-mm)); top: calc(2 * var(--eurorack-mm)); }\n.uikit-eurorack-screw--bl { left: calc(2 * var(--eurorack-mm)); bottom: calc(2 * var(--eurorack-mm)); }\n.uikit-eurorack-screw--br { right: calc(2 * var(--eurorack-mm)); bottom: calc(2 * var(--eurorack-mm)); }\n.uikit-patch-jack {\n  appearance: none;\n  position: relative;\n  display: inline-grid;\n  place-items: center;\n  width: max(36px, calc(var(--patch-jack-size, 24px) + 12px));\n  height: max(36px, calc(var(--patch-jack-size, 24px) + 12px));\n  min-width: 36px;\n  min-height: 36px;\n  box-sizing: border-box;\n  padding: 6px;\n  margin: 0;\n  border: 0;\n  border-radius: 0;\n  background: transparent;\n  color: var(--uikit-text, #252723);\n  font: inherit;\n  touch-action: none;\n  cursor: crosshair;\n}\n.uikit-patch-jack:disabled { cursor: default; opacity: 0.45; }\n.uikit-patch-jack:focus-visible {\n  outline: 2px solid var(--uikit-accent, #517b66);\n  outline-offset: 2px;\n}\n.uikit-patch-jack__hardware {\n  position: relative;\n  display: grid;\n  place-items: center;\n  width: var(--patch-jack-size, 24px);\n  height: var(--patch-jack-size, 24px);\n  flex: none;\n  border-radius: 50%;\n  background: radial-gradient(circle at 34% 24%, #fffef4 0, #d6d1c4 29%, #a9a394 63%, #6e706a 88%, #d8d5ca 100%);\n  box-shadow:\n    inset 0 1px 1px #ffffffd9,\n    inset 0 -1px 2px #3c3b344d,\n    0 0 0 1px #77766d,\n    0 1px 2px #00000055;\n}\n.uikit-patch-jack__metalwork {\n  position: absolute;\n  inset: 0;\n  width: 100%;\n  height: 100%;\n  overflow: visible;\n  pointer-events: none;\n}\n.uikit-patch-jack__rim {\n  stroke: #69675e;\n  stroke-width: .7;\n}\n.uikit-patch-jack__step {\n  stroke: #f8f3e5;\n  stroke-width: .8;\n  filter: drop-shadow(0 .6px .55px #3c393366);\n}\n.uikit-patch-jack__step-face {\n  fill: #c8c2b3;\n  stroke: #77756b;\n  stroke-width: .65;\n}\n.uikit-patch-jack__bore-lip {\n  stroke: #69675f;\n  stroke-width: .6;\n  filter: drop-shadow(0 -.45px .35px #ffffffa6);\n}\n.uikit-patch-jack__knurl-facet {\n  stroke: #6a675e;\n  stroke-width: .4;\n  stroke-linejoin: bevel;\n}\n.uikit-patch-jack__tool-notch {\n  fill: #55544d;\n  stroke: #f1ecde;\n  stroke-width: .6;\n  stroke-linejoin: round;\n}\n.uikit-patch-jack__specular {\n  fill: none;\n  stroke: #ffffffd9;\n  stroke-width: .75;\n  stroke-linecap: round;\n  filter: drop-shadow(0 .45px .45px #5d594d99);\n}\n.uikit-patch-jack__wear {\n  fill: none;\n  stroke: #f7f1e3a6;\n  stroke-width: .55;\n  stroke-linecap: round;\n}\n.uikit-patch-jack__socket {\n  position: relative;\n  width: calc(var(--patch-jack-size, 24px) * 0.56);\n  height: calc(var(--patch-jack-size, 24px) * 0.56);\n  border: 1px solid #6e6b60;\n  box-sizing: border-box;\n  border-radius: 50%;\n  background:\n    radial-gradient(ellipse at 34% 22%, #ffffff5e 0 3%, transparent 17%),\n    radial-gradient(circle at 50% 50%, #030404 0 68%, #151716 77%, #393a35 87%, #928d80 96%, #f2eee1 100%);\n  box-shadow:\n    inset 0 1.5px 2px #000000e6,\n    inset 0 -1px 1px #ffffff20,\n    0 0 0 1px #787468;\n}\n.uikit-patch-jack[data-patch-connected=\"true\"] .uikit-patch-jack__socket {\n  background: var(--patch-jack-color, var(--uikit-accent, #517b66));\n  box-shadow: inset 0 0 0 3px #00000045, 0 0 0 1px #d7dbd1;\n}\n.uikit-patch-jack[data-patch-selected=\"true\"] .uikit-patch-jack__hardware {\n  background: var(--uikit-accent, #517b66);\n}\n.uikit-patch-jack__label {\n  position: absolute;\n  top: calc(100% - 2px);\n  left: 50%;\n  transform: translateX(-50%);\n  width: max-content;\n  max-width: 70px;\n  font-size: 9px;\n  line-height: 1.1;\n  font-weight: 650;\n  text-align: center;\n}\n.uikit-patch-jack[data-patch-port-direction=\"output\"] .uikit-patch-jack__label {\n  padding: 1px 3px;\n  background: var(--uikit-text, #252723);\n  color: var(--uikit-surface, #dedfdb);\n}\n.uikit-face-patch-zone { display: flex; flex-wrap: wrap; gap: 12px; padding-bottom: 14px; }\n.uikit-face-patch-zone--positioned { position: absolute; inset: 0; padding: 0; pointer-events: none; }\n.uikit-face-patch-zone--positioned .uikit-patch-jack { pointer-events: auto; }\n.uikit-patch-cables {\n  position: absolute;\n  inset: 0;\n  overflow: visible;\n  pointer-events: none;\n  fill: none;\n}\n.uikit-patch-cables path { stroke-linecap: round; }\n.uikit-patch-cable__shadow { stroke: #00000038; stroke-width: 7; transform: translateY(2px); pointer-events: none; }\n.uikit-patch-cable__body { stroke: var(--patch-cable-color); stroke-width: 5; pointer-events: none; }\n.uikit-patch-cable__shine { stroke: #ffffff40; stroke-width: 1; transform: translateY(-1px); pointer-events: none; }\n.uikit-patch-cables .uikit-patch-cable__hit { stroke: transparent; stroke-width: 18; stroke-linecap: butt; pointer-events: stroke; cursor: pointer; }\n.uikit-patch-cable__hit:focus-visible { outline: none; stroke: #ffffff80; stroke-width: 9; }\n.uikit-patch-cables g[data-selected=\"true\"] .uikit-patch-cable__body { stroke-width: 7; }\n.uikit-patch-cables g[data-highlighted=\"true\"] .uikit-patch-cable__body { stroke-width: 7; }\n.uikit-patch-cable-foreground, .uikit-patch-cable-foreground use { pointer-events: none; }\n.uikit-patch-cable-labels { pointer-events: none; }\n.uikit-patch-cable-labels rect { fill: #efefeb; stroke: var(--patch-cable-color); stroke-width: 1; }\n.uikit-patch-cable-labels text { fill: #292929; font: 600 11px ui-sans-serif, system-ui, sans-serif; text-anchor: middle; }\n.uikit-patch-cable-label__guide { fill: none; stroke: var(--patch-cable-color); stroke-width: 1.5; }\n.uikit-patch-cable__end-ring { fill: var(--patch-cable-color); stroke: #f7f7f0; stroke-width: 2; pointer-events: none; }\n.uikit-patch-cable__end-hit { fill: transparent; stroke: none; pointer-events: all; cursor: grab; touch-action: none; }\n.uikit-patch-cable__end-hit:active { cursor: grabbing; }\n.uikit-patch-cable__end-hit:focus-visible { outline: none; stroke: #f7f7f0; stroke-width: 2; fill: #ffffff20; }\n@media (prefers-reduced-motion: reduce) {\n  .uikit-patch-jack, .uikit-patch-cables { animation: none; transition: none; }\n}\n\n.uikit-patch-cable__snap-ring { fill: none; stroke: var(--patch-cable-color); stroke-width: 4; filter: drop-shadow(0 0 2px #fff); pointer-events: none; }\n",
      "type": "registry:style",
      "target": "@ui/eurorack.css"
    }
  ]
}
