{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "followcursor",
  "type": "registry:ui",
  "title": "Button",
  "description": "Reusable button component with multiple variants and sizes.",
  "author": "Ahdeetai <https://aditya.is-cool.dev>",
  "registryDependencies": [],
  "dependencies": ["ogl"],
  "files": [
    {
      "type": "registry:ui",
      "path": "components/ui/followcursor.tsx",
      "content": "'use client';\nimport { useEffect, useRef, useState, useCallback } from 'react';\nimport { Renderer, Transform, Vec3, Color, Polyline, Program } from 'ogl';\n\nconst vertexShader = `\n    precision highp float;\n\n    attribute vec3 position;\n    attribute vec3 next;\n    attribute vec3 prev;\n    attribute vec2 uv;\n    attribute float side;\n\n    uniform vec2 uResolution;\n    uniform float uDPR;\n    uniform float uThickness;\n    uniform float uTime;\n\n    vec4 getPosition() {\n        vec4 current = vec4(position, 1);\n\n        vec2 aspect = vec2(uResolution.x / uResolution.y, 1);\n        vec2 nextScreen = next.xy * aspect;\n        vec2 prevScreen = prev.xy * aspect;\n\n        \n        vec2 tangent = normalize(nextScreen - prevScreen);\n\n        \n        vec2 normal = vec2(-tangent.y, tangent.x);\n        normal /= aspect;\n\n        \n        float noise = sin(position.x * 100.0 + uTime * 2.0) * 0.1;\n        normal *= 1.0 + noise;\n\n        \n        float taper = smoothstep(0.0, 0.3, uv.y) * (1.0 - smoothstep(0.7, 1.0, uv.y));\n        normal *= taper;\n\n        \n        float dist = length(nextScreen - prevScreen);\n        normal *= smoothstep(0.0, 0.02, dist);\n\n        float pixelWidthRatio = 1.0 / (uResolution.y / uDPR);\n        float pixelWidth = current.w * pixelWidthRatio;\n        normal *= pixelWidth * uThickness;\n        current.xy -= normal * side;\n\n        return current;\n    }\n\n    void main() {\n        gl_Position = getPosition();\n    }\n`;\n\nconst fragmentShader = `\n    precision highp float;\n    \n    uniform vec3 uColor;\n    uniform float uTime;\n    uniform float uIntensity;\n    \n    void main() {\n        \n        vec3 color = uColor * (0.9 + 0.1 * sin(uTime * 0.5));\n        \n        \n        float glow = uIntensity * 0.5;\n        color += glow * vec3(1.0, 1.0, 1.0);\n        \n        gl_FragColor = vec4(color, 1.0);\n    }\n`;\n\ninterface PolylineConfig {\n  color: string;\n  thickness: number;\n  count: number;\n  spring: number;\n  friction: number;\n  offset: Vec3;\n}\n\ninterface PolylineItem extends PolylineConfig {\n  points: Vec3[];\n  polyline: Polyline;\n  program: Program;\n  velocity: Vec3;\n  targetIntensity: number;\n}\n\ninterface FollowCursorProps {\n  className?: string;\n  style?: React.CSSProperties;\n  colors?: string[];\n  thickness?: { min: number; max: number };\n  count?: { min: number; max: number };\n  bgColor?: string;\n}\n\nexport default function FollowCursor({\n  className = '',\n  style = {},\n  colors = [\n    '#FF6B6B',\n    '#4ECDC4',\n    '#45B7D1',\n    '#FFBE0B',\n    '#FB5607',\n    '#8338EC',\n    '#3A86FF',\n    '#FF006E',\n    '#A5FFD6',\n    '#FF9E00',\n  ],\n  thickness = { min: 30, max: 100 },\n  count = { min: 15, max: 25 },\n  bgColor = 'rgba(26, 26, 38, 1)',\n}: FollowCursorProps) {\n  const containerRef = useRef<HTMLDivElement>(null);\n  const canvasRef = useRef<HTMLCanvasElement>(null);\n  const rendererRef = useRef<Renderer | null>(null);\n  const sceneRef = useRef<Transform | null>(null);\n  const linesRef = useRef<PolylineItem[]>([]);\n  const mouseRef = useRef(new Vec3());\n  const animationRef = useRef<number>(0);\n  const timeRef = useRef(0);\n  const resizeObserverRef = useRef<ResizeObserver | null>(null);\n\n  const isSetupCompleteRef = useRef(false);\n  const [isMounted, setIsMounted] = useState(false);\n\n  const lerp = (a: number, b: number, t: number) => a * (1 - t) + b * t;\n\n  const generateConfigs = useCallback((): PolylineConfig[] => {\n    return colors.map((color) => ({\n      color,\n      thickness:\n        thickness.min + Math.random() * (thickness.max - thickness.min),\n      count: count.min + Math.floor(Math.random() * (count.max - count.min)),\n      spring: 0.1 + Math.random() * 0.15,\n      friction: 0.85 + Math.random() * 0.1,\n      offset: new Vec3(\n        (Math.random() - 0.5) * 0.05,\n        (Math.random() - 0.5) * 0.05,\n        0,\n      ),\n    }));\n  }, [colors, thickness, count]);\n\n  useEffect(() => {\n    // eslint-disable-next-line react-hooks/set-state-in-effect\n    setIsMounted(true);\n    return () => setIsMounted(false);\n  }, []);\n\n  const getCanvasPosition = () => {\n    if (!canvasRef.current) return { left: 0, top: 0 };\n    const rect = canvasRef.current.getBoundingClientRect();\n    return { left: rect.left, top: rect.top };\n  };\n\n  useEffect(() => {\n    if (!isMounted || !canvasRef.current || !containerRef.current) return;\n\n    if (isSetupCompleteRef.current && rendererRef.current) return;\n\n    let isComponentMounted = true;\n\n    const container = containerRef.current;\n    const canvas = canvasRef.current;\n\n    canvas.width = container.clientWidth;\n    canvas.height = container.clientHeight;\n\n    const renderer = new Renderer({\n      canvas: canvas,\n      dpr: Math.min(window.devicePixelRatio, 2),\n      antialias: true,\n    });\n    const gl = renderer.gl;\n    rendererRef.current = renderer;\n\n    const bgColorObj = new Color(bgColor);\n    gl.clearColor(bgColorObj.r, bgColorObj.g, bgColorObj.b, 1);\n\n    const scene = new Transform();\n    sceneRef.current = scene;\n\n    const configs = generateConfigs();\n    const lines: PolylineItem[] = [];\n\n    try {\n      for (const config of configs) {\n        const points = Array.from({ length: config.count }, (_, i) => {\n          const offset = i * 0.01;\n          return new Vec3(offset, offset, 0);\n        });\n\n        const polyline = new Polyline(gl, {\n          points,\n          vertex: vertexShader,\n          fragment: fragmentShader,\n          uniforms: {\n            uColor: { value: new Color(config.color) },\n            uThickness: { value: config.thickness },\n            uTime: { value: 0 },\n            uIntensity: { value: 0 },\n          },\n        });\n\n        polyline.mesh.setParent(scene);\n\n        lines.push({\n          ...config,\n          points,\n          polyline,\n          program: polyline.mesh.program,\n          velocity: new Vec3(),\n          targetIntensity: 0,\n        });\n      }\n\n      linesRef.current = lines;\n    } catch (error) {\n      console.error('Failed to create polylines:', error);\n      return;\n    }\n\n    isSetupCompleteRef.current = true;\n\n    const handleResize = () => {\n      if (!isComponentMounted || !renderer || !container) return;\n\n      try {\n        renderer.setSize(container.clientWidth, container.clientHeight);\n\n        if (lines && Array.isArray(lines)) {\n          lines.forEach((line) => {\n            if (\n              line &&\n              line.polyline &&\n              typeof line.polyline.resize === 'function'\n            ) {\n              line.polyline.resize();\n            }\n          });\n        }\n      } catch (error) {\n        console.error('Failed to resize:', error);\n      }\n    };\n\n    const handlePointerMove = (e: MouseEvent | TouchEvent) => {\n      if (!isComponentMounted || !renderer || !canvas) return;\n\n      const event = 'touches' in e ? e.touches[0] : e;\n\n      const canvasPos = getCanvasPosition();\n\n      const x = event.clientX - canvasPos.left;\n      const y = event.clientY - canvasPos.top;\n\n      mouseRef.current.set(\n        (x / canvas.width) * 2 - 1,\n        (y / canvas.height) * -2 + 1,\n        0,\n      );\n\n      if (lines && Array.isArray(lines)) {\n        lines.forEach((line) => {\n          line.targetIntensity = 0.7 + Math.random() * 0.3;\n\n          if (line.points && line.points.length > 0) {\n            const firstPoint = line.points[0];\n            if (firstPoint) {\n              firstPoint.lerp(mouseRef.current.clone().add(line.offset), 0.6);\n            }\n          }\n        });\n      }\n    };\n\n    resizeObserverRef.current = new ResizeObserver(() => {\n      handleResize();\n    });\n\n    if (container) {\n      resizeObserverRef.current.observe(container);\n    }\n\n    window.addEventListener('mousemove', handlePointerMove);\n    window.addEventListener('touchmove', handlePointerMove);\n\n    mouseRef.current.set(0, 0, 0);\n\n    handleResize();\n\n    const animate = (time: number) => {\n      if (!isComponentMounted || !isSetupCompleteRef.current) {\n        return;\n      }\n\n      timeRef.current = time * 0.001;\n      const tmp = new Vec3();\n\n      try {\n        const currentLines = linesRef.current;\n        if (\n          currentLines &&\n          Array.isArray(currentLines) &&\n          currentLines.length > 0\n        ) {\n          for (const line of currentLines) {\n            for (let i = line.points.length - 1; i >= 0; i--) {\n              if (i === 0) {\n                tmp\n                  .copy(mouseRef.current)\n                  .add(line.offset)\n                  .sub(line.points[i])\n                  .multiply(line.spring * 6.0);\n\n                line.velocity.add(tmp).multiply(0.92);\n                line.points[i].add(line.velocity);\n\n                line.points[i].lerp(\n                  mouseRef.current.clone().add(line.offset),\n                  0.4,\n                );\n              } else {\n                line.points[i].lerp(\n                  line.points[i - 1],\n                  0.88 + Math.sin(timeRef.current + i) * 0.05,\n                );\n              }\n            }\n\n            if (line.program && line.program.uniforms) {\n              if (line.program.uniforms.uTime) {\n                line.program.uniforms.uTime.value = timeRef.current;\n              }\n\n              if (line.program.uniforms.uIntensity) {\n                line.program.uniforms.uIntensity.value = lerp(\n                  line.program.uniforms.uIntensity.value || 0,\n                  line.targetIntensity,\n                  0.1,\n                );\n              }\n            }\n\n            line.targetIntensity *= 0.9;\n\n            line.polyline.updateGeometry();\n          }\n        }\n\n        if (rendererRef.current && sceneRef.current) {\n          rendererRef.current.render({ scene: sceneRef.current });\n        }\n      } catch (error) {\n        console.error('Animation error:', error);\n      }\n\n      if (isComponentMounted) {\n        animationRef.current = requestAnimationFrame(animate);\n      }\n    };\n\n    animationRef.current = requestAnimationFrame(animate);\n\n    return () => {\n      isComponentMounted = false;\n      isSetupCompleteRef.current = false;\n\n      if (resizeObserverRef.current) {\n        resizeObserverRef.current.disconnect();\n      }\n\n      window.removeEventListener('mousemove', handlePointerMove);\n      window.removeEventListener('touchmove', handlePointerMove);\n\n      if (animationRef.current) {\n        cancelAnimationFrame(animationRef.current);\n      }\n\n      if (gl && typeof gl.getExtension === 'function') {\n        try {\n          const extension = gl.getExtension('WEBGL_lose_context');\n          if (extension) {\n            extension.loseContext();\n          }\n        } catch (error) {\n          console.error('Error losing WebGL context:', error);\n        }\n      }\n    };\n  }, [isMounted, bgColor]); // eslint-disable-line react-hooks/exhaustive-deps\n\n  return (\n    <div\n      ref={containerRef}\n      className={`relative w-full h-full overflow-hidden ${className}`}\n      style={style}\n    >\n      <canvas ref={canvasRef} className='block w-full h-full' />\n    </div>\n  );\n}\n"
    }
  ]
}
