{
  "name": "vortex",
  "type": "registry:ui",
  "dependencies": [
    "simplex-noise",
    "motion"
  ],
  "files": [
    {
      "path": "components/ui/vortex.tsx",
      "content": "\"use client\";\nimport { cn } from \"@/lib/utils\";\nimport React, { useEffect, useRef } from \"react\";\nimport { createNoise3D } from \"simplex-noise\";\nimport { motion } from \"motion/react\";\n\ninterface VortexProps {\n  children?: any;\n  className?: string;\n  containerClassName?: string;\n  particleCount?: number;\n  rangeY?: number;\n  baseHue?: number;\n  baseSpeed?: number;\n  rangeSpeed?: number;\n  baseRadius?: number;\n  rangeRadius?: number;\n  backgroundColor?: string;\n}\n\nexport const Vortex = (props: VortexProps) => {\n  const canvasRef = useRef<HTMLCanvasElement>(null);\n  const containerRef = useRef(null);\n  const animationFrameId = useRef<number>();\n  const particleCount = props.particleCount || 700;\n  const particlePropCount = 9;\n  const particlePropsLength = particleCount * particlePropCount;\n  const rangeY = props.rangeY || 100;\n  const baseTTL = 50;\n  const rangeTTL = 150;\n  const baseSpeed = props.baseSpeed || 0.0;\n  const rangeSpeed = props.rangeSpeed || 1.5;\n  const baseRadius = props.baseRadius || 1;\n  const rangeRadius = props.rangeRadius || 2;\n  const baseHue = props.baseHue || 220;\n  const rangeHue = 100;\n  const noiseSteps = 3;\n  const xOff = 0.00125;\n  const yOff = 0.00125;\n  const zOff = 0.0005;\n  const backgroundColor = props.backgroundColor || \"#000000\";\n  let tick = 0;\n  const noise3D = createNoise3D();\n  let particleProps = new Float32Array(particlePropsLength);\n  let center: [number, number] = [0, 0];\n\n  const HALF_PI: number = 0.5 * Math.PI;\n  const TAU: number = 2 * Math.PI;\n  const TO_RAD: number = Math.PI / 180;\n  const rand = (n: number): number => n * Math.random();\n  const randRange = (n: number): number => n - rand(2 * n);\n  const fadeInOut = (t: number, m: number): number => {\n    let hm = 0.5 * m;\n    return Math.abs(((t + hm) % m) - hm) / hm;\n  };\n  const lerp = (n1: number, n2: number, speed: number): number =>\n    (1 - speed) * n1 + speed * n2;\n\n  const setup = () => {\n    const canvas = canvasRef.current;\n    const container = containerRef.current;\n    if (canvas && container) {\n      const ctx = canvas.getContext(\"2d\");\n\n      if (ctx) {\n        resize(canvas, ctx);\n        initParticles();\n        draw(canvas, ctx);\n      }\n    }\n  };\n\n  const initParticles = () => {\n    tick = 0;\n    // simplex = new SimplexNoise();\n    particleProps = new Float32Array(particlePropsLength);\n\n    for (let i = 0; i < particlePropsLength; i += particlePropCount) {\n      initParticle(i);\n    }\n  };\n\n  const initParticle = (i: number) => {\n    const canvas = canvasRef.current;\n    if (!canvas) return;\n\n    let x, y, vx, vy, life, ttl, speed, radius, hue;\n\n    x = rand(canvas.width);\n    y = center[1] + randRange(rangeY);\n    vx = 0;\n    vy = 0;\n    life = 0;\n    ttl = baseTTL + rand(rangeTTL);\n    speed = baseSpeed + rand(rangeSpeed);\n    radius = baseRadius + rand(rangeRadius);\n    hue = baseHue + rand(rangeHue);\n\n    particleProps.set([x, y, vx, vy, life, ttl, speed, radius, hue], i);\n  };\n\n  const draw = (canvas: HTMLCanvasElement, ctx: CanvasRenderingContext2D) => {\n    tick++;\n\n    ctx.clearRect(0, 0, canvas.width, canvas.height);\n\n    ctx.fillStyle = backgroundColor;\n    ctx.fillRect(0, 0, canvas.width, canvas.height);\n\n    drawParticles(ctx);\n    renderGlow(canvas, ctx);\n    renderToScreen(canvas, ctx);\n\n    animationFrameId.current = window.requestAnimationFrame(() =>\n      draw(canvas, ctx),\n    );\n  };\n\n  const drawParticles = (ctx: CanvasRenderingContext2D) => {\n    for (let i = 0; i < particlePropsLength; i += particlePropCount) {\n      updateParticle(i, ctx);\n    }\n  };\n\n  const updateParticle = (i: number, ctx: CanvasRenderingContext2D) => {\n    const canvas = canvasRef.current;\n    if (!canvas) return;\n\n    let i2 = 1 + i,\n      i3 = 2 + i,\n      i4 = 3 + i,\n      i5 = 4 + i,\n      i6 = 5 + i,\n      i7 = 6 + i,\n      i8 = 7 + i,\n      i9 = 8 + i;\n    let n, x, y, vx, vy, life, ttl, speed, x2, y2, radius, hue;\n\n    x = particleProps[i];\n    y = particleProps[i2];\n    n = noise3D(x * xOff, y * yOff, tick * zOff) * noiseSteps * TAU;\n    vx = lerp(particleProps[i3], Math.cos(n), 0.5);\n    vy = lerp(particleProps[i4], Math.sin(n), 0.5);\n    life = particleProps[i5];\n    ttl = particleProps[i6];\n    speed = particleProps[i7];\n    x2 = x + vx * speed;\n    y2 = y + vy * speed;\n    radius = particleProps[i8];\n    hue = particleProps[i9];\n\n    drawParticle(x, y, x2, y2, life, ttl, radius, hue, ctx);\n\n    life++;\n\n    particleProps[i] = x2;\n    particleProps[i2] = y2;\n    particleProps[i3] = vx;\n    particleProps[i4] = vy;\n    particleProps[i5] = life;\n\n    (checkBounds(x, y, canvas) || life > ttl) && initParticle(i);\n  };\n\n  const drawParticle = (\n    x: number,\n    y: number,\n    x2: number,\n    y2: number,\n    life: number,\n    ttl: number,\n    radius: number,\n    hue: number,\n    ctx: CanvasRenderingContext2D,\n  ) => {\n    ctx.save();\n    ctx.lineCap = \"round\";\n    ctx.lineWidth = radius;\n    ctx.strokeStyle = `hsla(${hue},100%,60%,${fadeInOut(life, ttl)})`;\n    ctx.beginPath();\n    ctx.moveTo(x, y);\n    ctx.lineTo(x2, y2);\n    ctx.stroke();\n    ctx.closePath();\n    ctx.restore();\n  };\n\n  const checkBounds = (x: number, y: number, canvas: HTMLCanvasElement) => {\n    return x > canvas.width || x < 0 || y > canvas.height || y < 0;\n  };\n\n  const resize = (\n    canvas: HTMLCanvasElement,\n    ctx?: CanvasRenderingContext2D,\n  ) => {\n    const { innerWidth, innerHeight } = window;\n\n    canvas.width = innerWidth;\n    canvas.height = innerHeight;\n\n    center[0] = 0.5 * canvas.width;\n    center[1] = 0.5 * canvas.height;\n  };\n\n  const renderGlow = (\n    canvas: HTMLCanvasElement,\n    ctx: CanvasRenderingContext2D,\n  ) => {\n    ctx.save();\n    ctx.filter = \"blur(8px) brightness(200%)\";\n    ctx.globalCompositeOperation = \"lighter\";\n    ctx.drawImage(canvas, 0, 0);\n    ctx.restore();\n\n    ctx.save();\n    ctx.filter = \"blur(4px) brightness(200%)\";\n    ctx.globalCompositeOperation = \"lighter\";\n    ctx.drawImage(canvas, 0, 0);\n    ctx.restore();\n  };\n\n  const renderToScreen = (\n    canvas: HTMLCanvasElement,\n    ctx: CanvasRenderingContext2D,\n  ) => {\n    ctx.save();\n    ctx.globalCompositeOperation = \"lighter\";\n    ctx.drawImage(canvas, 0, 0);\n    ctx.restore();\n  };\n\n  const handleResize = () => {\n    const canvas = canvasRef.current;\n    const ctx = canvas?.getContext(\"2d\");\n    if (canvas && ctx) {\n      resize(canvas, ctx);\n    }\n  };\n\n  useEffect(() => {\n    setup();\n    window.addEventListener(\"resize\", handleResize);\n\n    return () => {\n      window.removeEventListener(\"resize\", handleResize);\n      if (animationFrameId.current) {\n        cancelAnimationFrame(animationFrameId.current);\n      }\n    };\n  }, []);\n\n  return (\n    <div className={cn(\"relative h-full w-full\", props.containerClassName)}>\n      <motion.div\n        initial={{ opacity: 0 }}\n        animate={{ opacity: 1 }}\n        ref={containerRef}\n        className=\"absolute inset-0 z-0 flex h-full w-full items-center justify-center bg-transparent\"\n      >\n        <canvas ref={canvasRef}></canvas>\n      </motion.div>\n\n      <div className={cn(\"relative z-10\", props.className)}>\n        {props.children}\n      </div>\n    </div>\n  );\n};\n",
      "type": "registry:ui",
      "target": "components/ui/vortex.tsx"
    }
  ],
  "author": "Manu Arora <hi@manuarora.in>",
  "title": "Vortex"
}