{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "dock-cursor-magnify",
  "title": "Dock Cursor Magnify",
  "description": "Cursor-proximity magnification row with Gaussian falloff: macOS-dock physics for any children.",
  "dependencies": [],
  "files": [
    {
      "path": "registry/core/dock-cursor-magnify/component.tsx",
      "content": "\"use client\";\n\nimport { useEffect, useRef, type ReactNode } from \"react\";\n\n// Gaussian-falloff magnification on a direct-DOM rAF loop — no React state\n// on the hot path, interruptible easing on enter/leave.\nexport function MagneticDock({\n  children,\n  gain = 0.55,\n  sigma = 80,\n  lift = 16,\n  className = \"\",\n}: {\n  /** the dock's icons/items */\n  children: ReactNode;\n  /** max extra scale at zero distance (0.55 → 1.55x) */\n  gain?: number;\n  /** gaussian falloff radius in px */\n  sigma?: number;\n  /** upward shift in px at full magnification */\n  lift?: number;\n  /** extra classes merged onto the rendered root element */\n  className?: string;\n}) {\n  const containerRef = useRef<HTMLDivElement>(null);\n\n  useEffect(() => {\n    const container = containerRef.current;\n    if (!container) return;\n    if (window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches) return;\n\n    let items: HTMLElement[] = [];\n    let scales: number[] = [];\n    let cursorX: number | null = null;\n    let raf = 0;\n\n    // re-sync from the live DOM whenever children are added/removed/reordered\n    // — keeps the loop from animating a stale snapshot (and stale detached nodes).\n    const sync = () => {\n      const next = Array.from(container.children) as HTMLElement[];\n      scales = next.map((el) => {\n        const i = items.indexOf(el);\n        return i === -1 ? 1 : scales[i];\n      });\n      items = next;\n    };\n    sync();\n\n    const loop = () => {\n      let settled = true;\n      // untransformed centers (offsetLeft ignores transforms — no feedback wobble)\n      const left = container.getBoundingClientRect().left;\n      const centers = items.map((el) => left + el.offsetLeft + el.offsetWidth / 2);\n      items.forEach((el, i) => {\n        let target = 1;\n        if (cursorX !== null) {\n          const d = cursorX - centers[i];\n          target = 1 + gain * Math.exp(-(d * d) / (2 * sigma * sigma));\n        }\n        scales[i] += (target - scales[i]) * 0.18;\n        if (Math.abs(target - scales[i]) > 0.002) settled = false;\n      });\n      items.forEach((el, i) => {\n        // neighbors shove outward so grown items never overlap\n        let shift = 0;\n        for (let j = 0; j < items.length; j++) {\n          if (j === i) continue;\n          const push = (scales[j] - 1) * items[j].offsetWidth * 0.55;\n          shift += j < i ? push : -push;\n        }\n        const s = scales[i];\n        el.style.transform = `translate(${shift}px, ${-(s - 1) * lift}px) scale(${s})`;\n        // magnification amount 0..1 — sells depth beyond scale/lift alone,\n        // color-mix(currentColor) so the glow adapts to each theme automatically.\n        const t = Math.max(0, (s - 1) / gain);\n        if (t > 0.002) {\n          el.style.filter = `brightness(${1 + t * 0.12})`;\n          el.style.boxShadow = `0 ${4 + t * 10}px ${10 + t * 16}px -4px color-mix(in srgb, currentColor ${Math.round(15 + t * 25)}%, transparent)`;\n        } else {\n          el.style.filter = \"\";\n          el.style.boxShadow = \"\";\n        }\n      });\n      raf = settled && cursorX === null ? 0 : requestAnimationFrame(loop);\n    };\n    const wake = () => {\n      if (!raf) raf = requestAnimationFrame(loop);\n    };\n\n    const onMove = (e: PointerEvent) => {\n      cursorX = e.clientX;\n      wake();\n    };\n    const onLeave = () => {\n      cursorX = null;\n      wake();\n    };\n\n    const observer = new MutationObserver(() => {\n      sync();\n      wake();\n    });\n    observer.observe(container, { childList: true });\n\n    container.addEventListener(\"pointermove\", onMove);\n    container.addEventListener(\"pointerleave\", onLeave);\n    return () => {\n      cancelAnimationFrame(raf);\n      observer.disconnect();\n      container.removeEventListener(\"pointermove\", onMove);\n      container.removeEventListener(\"pointerleave\", onLeave);\n    };\n  }, [gain, sigma, lift]);\n\n  return (\n    <div\n      ref={containerRef}\n      className={`flex items-end gap-2 [&>*]:origin-bottom [&>*]:will-change-transform ${className}`}\n    >\n      {children}\n    </div>\n  );\n}\n",
      "type": "registry:ui",
      "target": "components/ui/dock-cursor-magnify.tsx"
    }
  ],
  "meta": {
    "collection": "core",
    "tags": [
      "nav",
      "cursor",
      "dock",
      "micro-interaction"
    ],
    "instruction": "A horizontal dock that magnifies its children based on cursor proximity with a Gaussian distance falloff: items scale up and lift as the pointer nears, neighbors swell progressively less, everything settles back with interruptible easing on leave. Direct-DOM rAF loop with no React state on the hot path, transform-origin bottom, static under prefers-reduced-motion."
  },
  "type": "registry:ui"
}