{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "fluid-cursor",
  "title": "Fluid Cursor",
  "description": "A spring-lagged blend-mode cursor that trails the pointer and swells over interactive elements.",
  "registryDependencies": ["@godui/godui-theme"],
  "files": [
    {
      "path": "packages/components/src/fluid-cursor/fluid-cursor.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { createPortal } from \"react-dom\";\n\nexport type FluidCursorProps = {\n  /** Diameter of the leading dot in pixels. Defaults to `18`. */\n  size?: number;\n  /** Dot color. With `blend` on, `white` reads as a clean invert. Defaults to `white`. */\n  color?: string;\n  /** Invert whatever is underneath via `mix-blend-mode: difference`. Defaults to `true`. */\n  blend?: boolean;\n  /** Render a soft lagging smear behind the dot. Defaults to `true`. */\n  trail?: boolean;\n  /**\n   * Scope the cursor to an element instead of the whole page. The element must\n   * be positioned (e.g. `relative`). Omit for a global cursor.\n   */\n  containerRef?: React.RefObject<HTMLElement | null>;\n  /** Selector for elements that enlarge the cursor on hover. */\n  interactiveSelector?: string;\n  className?: string;\n};\n\nconst FluidCursor = ({\n  size = 18,\n  color = \"white\",\n  blend = true,\n  trail = true,\n  containerRef,\n  interactiveSelector = \"a, button, [role='button'], [data-cursor]\",\n  className,\n}: FluidCursorProps) => {\n  const dotRef = React.useRef<HTMLDivElement>(null);\n  const trailRef = React.useRef<HTMLDivElement>(null);\n  const [enabled, setEnabled] = React.useState(false);\n  const [target, setTarget] = React.useState<HTMLElement | null>(null);\n  const motion = React.useRef({\n    x: 0,\n    y: 0,\n    tx: 0,\n    ty: 0,\n    sx: 0,\n    sy: 0,\n    hover: 0,\n    thover: 0,\n    inside: 0,\n    seen: false,\n  });\n\n  React.useEffect(() => {\n    if (typeof window.matchMedia !== \"function\") return;\n    const fine = window.matchMedia(\"(pointer: fine)\").matches;\n    const reduce = window.matchMedia(\n      \"(prefers-reduced-motion: reduce)\",\n    ).matches;\n    setEnabled(fine && !reduce);\n    setTarget(containerRef ? containerRef.current : document.body);\n  }, [containerRef]);\n\n  React.useEffect(() => {\n    if (!enabled || !target) return;\n    const s = motion.current;\n    const scoped = Boolean(containerRef);\n    const host: HTMLElement | Window = scoped ? target : window;\n\n    // The follower idles once it catches up to the pointer, so stop the rAF when\n    // everything has settled (or the tab is hidden) and only restart it on real\n    // pointer activity — no forever-spinning loop writing identical transforms.\n    let raf = 0;\n    const SETTLE = 0.01;\n    const settled = () =>\n      Math.abs(s.tx - s.x) < SETTLE &&\n      Math.abs(s.ty - s.y) < SETTLE &&\n      Math.abs(s.tx - s.sx) < SETTLE &&\n      Math.abs(s.ty - s.sy) < SETTLE &&\n      Math.abs(s.thover - s.hover) < SETTLE;\n\n    const loop = () => {\n      s.x += (s.tx - s.x) * 0.25;\n      s.y += (s.ty - s.y) * 0.25;\n      s.sx += (s.tx - s.sx) * 0.12;\n      s.sy += (s.ty - s.sy) * 0.12;\n      s.hover += (s.thover - s.hover) * 0.15;\n\n      const visible = s.seen && s.inside ? \"1\" : \"0\";\n      const dot = dotRef.current;\n      if (dot) {\n        dot.style.transform = `translate3d(${s.x}px, ${s.y}px, 0) translate(-50%, -50%) scale(${1 + s.hover * 1.6})`;\n        dot.style.opacity = visible;\n      }\n      const tr = trailRef.current;\n      if (tr) {\n        tr.style.transform = `translate3d(${s.sx}px, ${s.sy}px, 0) translate(-50%, -50%) scale(${1 + s.hover * 0.8})`;\n        tr.style.opacity = visible;\n      }\n\n      if (settled() || document.hidden) {\n        raf = 0;\n        return;\n      }\n      raf = requestAnimationFrame(loop);\n    };\n    const start = () => {\n      if (!raf && !document.hidden) raf = requestAnimationFrame(loop);\n    };\n\n    const point = (e: PointerEvent) => {\n      if (scoped) {\n        const r = target.getBoundingClientRect();\n        s.tx = e.clientX - r.left;\n        s.ty = e.clientY - r.top;\n      } else {\n        s.tx = e.clientX;\n        s.ty = e.clientY;\n      }\n    };\n\n    const move = (e: PointerEvent) => {\n      point(e);\n      if (!s.seen) {\n        s.x = s.sx = s.tx;\n        s.y = s.sy = s.ty;\n        s.seen = true;\n      }\n      if (!scoped) s.inside = 1;\n      start();\n    };\n    const over = (e: PointerEvent) => {\n      const el = e.target as Element | null;\n      s.thover = el?.closest?.(interactiveSelector) ? 1 : 0;\n      start();\n    };\n    const enter = () => {\n      s.inside = 1;\n      start();\n    };\n    const leave = () => {\n      s.inside = 0;\n      start();\n    };\n\n    host.addEventListener(\"pointermove\", move as EventListener, {\n      passive: true,\n    });\n    host.addEventListener(\"pointerover\", over as EventListener, {\n      passive: true,\n    });\n    if (scoped) {\n      host.addEventListener(\"pointerenter\", enter as EventListener);\n      host.addEventListener(\"pointerleave\", leave as EventListener);\n    }\n\n    const onVisibility = () => {\n      if (!document.hidden) start();\n    };\n    document.addEventListener(\"visibilitychange\", onVisibility);\n\n    start();\n\n    return () => {\n      cancelAnimationFrame(raf);\n      document.removeEventListener(\"visibilitychange\", onVisibility);\n      host.removeEventListener(\"pointermove\", move as EventListener);\n      host.removeEventListener(\"pointerover\", over as EventListener);\n      if (scoped) {\n        host.removeEventListener(\"pointerenter\", enter as EventListener);\n        host.removeEventListener(\"pointerleave\", leave as EventListener);\n      }\n    };\n  }, [enabled, target, containerRef, interactiveSelector]);\n\n  if (!enabled || !target) return null;\n\n  const scoped = Boolean(containerRef);\n\n  return createPortal(\n    <div\n      aria-hidden\n      data-slot=\"fluid-cursor\"\n      className={`pointer-events-none ${scoped ? \"absolute\" : \"fixed\"} inset-0 z-toast overflow-hidden ${blend ? \"mix-blend-difference\" : \"\"} ${className ?? \"\"}`}\n    >\n      {trail ? (\n        <div\n          ref={trailRef}\n          className=\"absolute left-0 top-0 rounded-full opacity-0 blur-md [transition:opacity_250ms_ease]\"\n          style={{ width: size * 2.4, height: size * 2.4, background: color }}\n        />\n      ) : null}\n      <div\n        ref={dotRef}\n        className=\"absolute left-0 top-0 rounded-full opacity-0 [transition:opacity_250ms_ease]\"\n        style={{ width: size, height: size, background: color }}\n      />\n    </div>,\n    target,\n  );\n};\nFluidCursor.displayName = \"FluidCursor\";\n\nexport { FluidCursor };\n",
      "type": "registry:ui",
      "target": "components/godui/fluid-cursor.tsx"
    }
  ],
  "type": "registry:ui"
}
