{
	"$schema": "https://ui.shadcn.com/schema/registry-item.json",
	"name": "MagicRings-TS-CSS",
	"title": "MagicRings",
	"description": "Interactive magic rings effect with customizable parameters.",
	"type": "registry:component",
	"files": [
		{
			"type": "registry:file",
			"path": "MagicRings.css",
			"target": "@components/MagicRings.css",
			"content": ".magic-rings-container {\n  width: 100%;\n  height: 100%;\n}\n"
		},
		{
			"type": "registry:component",
			"path": "MagicRings.tsx",
			"content": "import { useEffect, useRef } from 'react';\nimport * as THREE from 'three';\n\nimport './MagicRings.css';\n\nconst vertexShader = `\nvoid main() {\n  gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\n}\n`;\n\nconst fragmentShader = `\nprecision highp float;\n\nuniform float uTime, uAttenuation, uLineThickness;\nuniform float uBaseRadius, uRadiusStep, uScaleRate;\nuniform float uOpacity, uNoiseAmount, uRotation, uRingGap;\nuniform float uFadeIn, uFadeOut;\nuniform float uMouseInfluence, uHoverAmount, uHoverScale, uParallax, uBurst;\nuniform float uCoverageAlpha;\nuniform vec2 uResolution, uMouse;\nuniform vec3 uColor, uColorTwo;\nuniform int uRingCount;\n\nconst float HP = 1.5707963;\nconst float CYCLE = 3.45;\n\nfloat fade(float t) {\n  return t < uFadeIn ? smoothstep(0.0, uFadeIn, t) : 1.0 - smoothstep(uFadeOut, CYCLE - 0.2, t);\n}\n\nfloat ring(vec2 p, float ri, float cut, float t0, float px) {\n  float t = mod(uTime + t0, CYCLE);\n  float r = ri + t / CYCLE * uScaleRate;\n  float d = abs(length(p) - r);\n  float a = atan(abs(p.y), abs(p.x)) / HP;\n  float th = max(1.0 - a, 0.5) * px * uLineThickness;\n  float h = (1.0 - smoothstep(th, th * 1.5, d)) + 1.0;\n  d += pow(cut * a, 3.0) * r;\n  return h * exp(-uAttenuation * d) * fade(t);\n}\n\nvoid main() {\n  float px = 1.0 / min(uResolution.x, uResolution.y);\n  vec2 p = (gl_FragCoord.xy - 0.5 * uResolution.xy) * px;\n  float cr = cos(uRotation), sr = sin(uRotation);\n  p = mat2(cr, -sr, sr, cr) * p;\n  p -= uMouse * uMouseInfluence;\n  float sc = mix(1.0, uHoverScale, uHoverAmount) + uBurst * 0.3;\n  p /= sc;\n  vec3 c = vec3(0.0);\n  float coverage = 0.0;\n  float rcf = max(float(uRingCount) - 1.0, 1.0);\n  for (int i = 0; i < 10; i++) {\n    if (i >= uRingCount) break;\n    float fi = float(i);\n    vec2 pr = p - fi * uParallax * uMouse;\n    vec3 rc = mix(uColor, uColorTwo, fi / rcf);\n    float ringAmount = ring(pr, uBaseRadius + fi * uRadiusStep, pow(uRingGap, fi), i == 0 ? 0.0 : 2.95 * fi, px);\n    c = mix(c, rc, vec3(ringAmount));\n    coverage = max(coverage, ringAmount);\n  }\n  c *= 1.0 + uBurst * 2.0;\n  float n = fract(sin(dot(gl_FragCoord.xy + uTime * 100.0, vec2(12.9898, 78.233))) * 43758.5453);\n  c += (n - 0.5) * uNoiseAmount;\n  float intensity = max(c.r, max(c.g, c.b));\n  vec3 emissiveColor = intensity > 0.0001 ? clamp(c / intensity, 0.0, 1.0) : vec3(0.0);\n  vec3 outputColor = mix(emissiveColor, clamp(c, 0.0, 1.0), uCoverageAlpha);\n  float outputAlpha = mix(intensity, coverage, uCoverageAlpha);\n  gl_FragColor = vec4(outputColor, clamp(outputAlpha * uOpacity, 0.0, 1.0));\n}\n`;\n\ninterface MagicRingsProps {\n  color?: string;\n  colorTwo?: string;\n  speed?: number;\n  ringCount?: number;\n  attenuation?: number;\n  lineThickness?: number;\n  baseRadius?: number;\n  radiusStep?: number;\n  scaleRate?: number;\n  opacity?: number;\n  blur?: number;\n  noiseAmount?: number;\n  rotation?: number;\n  ringGap?: number;\n  fadeIn?: number;\n  fadeOut?: number;\n  followMouse?: boolean;\n  mouseInfluence?: number;\n  hoverScale?: number;\n  parallax?: number;\n  clickBurst?: boolean;\n  alphaMode?: 'luminance' | 'coverage';\n}\n\nexport default function MagicRings({\n  color = '#fc42ff',\n  colorTwo = '#42fcff',\n  speed = 1,\n  ringCount = 6,\n  attenuation = 10,\n  lineThickness = 2,\n  baseRadius = 0.35,\n  radiusStep = 0.1,\n  scaleRate = 0.1,\n  opacity = 1,\n  blur = 0,\n  noiseAmount = 0.1,\n  rotation = 0,\n  ringGap = 1.5,\n  fadeIn = 0.7,\n  fadeOut = 0.5,\n  followMouse = false,\n  mouseInfluence = 0.2,\n  hoverScale = 1.2,\n  parallax = 0.05,\n  clickBurst = false,\n  alphaMode = 'luminance',\n}: MagicRingsProps) {\n  const mountRef = useRef<HTMLDivElement | null>(null);\n  const propsRef = useRef<Required<MagicRingsProps> | null>(null);\n  const mouseRef = useRef([0, 0]);\n  const smoothMouseRef = useRef([0, 0]);\n  const hoverAmountRef = useRef(0);\n  const isHoveredRef = useRef(false);\n  const burstRef = useRef(0);\n\n  propsRef.current = {\n    color, colorTwo, speed, ringCount, attenuation, lineThickness,\n    baseRadius, radiusStep, scaleRate, opacity, blur, noiseAmount,\n    rotation, ringGap, fadeIn, fadeOut, followMouse, mouseInfluence,\n    hoverScale, parallax, clickBurst, alphaMode,\n  };\n\n  useEffect(() => {\n    const mount = mountRef.current;\n    if (!mount) return;\n\n    let renderer: THREE.WebGLRenderer;\n    try {\n      renderer = new THREE.WebGLRenderer({ alpha: true });\n    } catch {\n      return;\n    }\n\n    if (!renderer.capabilities.isWebGL2) {\n      renderer.dispose();\n      return;\n    }\n\n    renderer.setClearColor(0x000000, 0);\n    mount.appendChild(renderer.domElement);\n\n    const scene = new THREE.Scene();\n    const camera = new THREE.OrthographicCamera(-0.5, 0.5, 0.5, -0.5, 0.1, 10);\n    camera.position.z = 1;\n\n    const uniforms = {\n      uTime: { value: 0 },\n      uAttenuation: { value: 0 },\n      uResolution: { value: new THREE.Vector2() },\n      uColor: { value: new THREE.Color() },\n      uColorTwo: { value: new THREE.Color() },\n      uLineThickness: { value: 0 },\n      uBaseRadius: { value: 0 },\n      uRadiusStep: { value: 0 },\n      uScaleRate: { value: 0 },\n      uRingCount: { value: 0 },\n      uOpacity: { value: 1 },\n      uNoiseAmount: { value: 0 },\n      uRotation: { value: 0 },\n      uRingGap: { value: 1.6 },\n      uFadeIn: { value: 0.5 },\n      uFadeOut: { value: 0.75 },\n      uMouse: { value: new THREE.Vector2() },\n      uMouseInfluence: { value: 0 },\n      uHoverAmount: { value: 0 },\n      uHoverScale: { value: 1 },\n      uParallax: { value: 0 },\n      uBurst: { value: 0 },\n      uCoverageAlpha: { value: 0 },\n    };\n\n    const material = new THREE.ShaderMaterial({ vertexShader, fragmentShader, uniforms, transparent: true });\n    const quad = new THREE.Mesh(new THREE.PlaneGeometry(1, 1), material);\n    scene.add(quad);\n\n    const resize = () => {\n      const w = mount.clientWidth;\n      const h = mount.clientHeight;\n      const dpr = Math.min(window.devicePixelRatio, 2);\n      renderer.setSize(w, h);\n      renderer.setPixelRatio(dpr);\n      uniforms.uResolution.value.set(w * dpr, h * dpr);\n    };\n    resize();\n    window.addEventListener('resize', resize);\n\n    const ro = new ResizeObserver(resize);\n    ro.observe(mount);\n\n    const onMouseMove = (e: MouseEvent) => {\n      const rect = mount.getBoundingClientRect();\n      mouseRef.current[0] = (e.clientX - rect.left) / rect.width - 0.5;\n      mouseRef.current[1] = -((e.clientY - rect.top) / rect.height - 0.5);\n    };\n    const onMouseEnter = () => { isHoveredRef.current = true; };\n    const onMouseLeave = () => {\n      isHoveredRef.current = false;\n      mouseRef.current[0] = 0;\n      mouseRef.current[1] = 0;\n    };\n    const onClick = () => { burstRef.current = 1; };\n\n    mount.addEventListener('mousemove', onMouseMove);\n    mount.addEventListener('mouseenter', onMouseEnter);\n    mount.addEventListener('mouseleave', onMouseLeave);\n    mount.addEventListener('click', onClick);\n\n    let frameId = 0;\n    let isVisible = false;\n    let isPageVisible = !document.hidden;\n    let elapsed = 0;\n    let lastT = 0;\n    const animate = (t: number) => {\n      frameId = requestAnimationFrame(animate);\n      const p = propsRef.current!;\n\n      const dt = lastT === 0 ? 0 : Math.min(t - lastT, 100);\n      lastT = t;\n      elapsed += dt * 0.001 * p.speed;\n\n      smoothMouseRef.current[0] += (mouseRef.current[0] - smoothMouseRef.current[0]) * 0.08;\n      smoothMouseRef.current[1] += (mouseRef.current[1] - smoothMouseRef.current[1]) * 0.08;\n      hoverAmountRef.current += ((isHoveredRef.current ? 1 : 0) - hoverAmountRef.current) * 0.08;\n      burstRef.current *= 0.95;\n      if (burstRef.current < 0.001) burstRef.current = 0;\n\n      uniforms.uTime.value = elapsed;\n      uniforms.uAttenuation.value = p.attenuation;\n      uniforms.uColor.value.set(p.color);\n      uniforms.uColorTwo.value.set(p.colorTwo);\n      uniforms.uLineThickness.value = p.lineThickness;\n      uniforms.uBaseRadius.value = p.baseRadius;\n      uniforms.uRadiusStep.value = p.radiusStep;\n      uniforms.uScaleRate.value = p.scaleRate;\n      uniforms.uRingCount.value = p.ringCount;\n      uniforms.uOpacity.value = p.opacity;\n      uniforms.uNoiseAmount.value = p.noiseAmount;\n      uniforms.uRotation.value = (p.rotation * Math.PI) / 180;\n      uniforms.uRingGap.value = p.ringGap;\n      uniforms.uFadeIn.value = p.fadeIn;\n      uniforms.uFadeOut.value = p.fadeOut;\n      uniforms.uMouse.value.set(smoothMouseRef.current[0], smoothMouseRef.current[1]);\n      uniforms.uMouseInfluence.value = p.followMouse ? p.mouseInfluence : 0;\n      uniforms.uHoverAmount.value = hoverAmountRef.current;\n      uniforms.uHoverScale.value = p.hoverScale;\n      uniforms.uParallax.value = p.parallax;\n      uniforms.uBurst.value = p.clickBurst ? burstRef.current : 0;\n      uniforms.uCoverageAlpha.value = p.alphaMode === 'coverage' ? 1 : 0;\n\n      renderer.render(scene, camera);\n    };\n    frameId = 0;\n\n    const tryStart = () => {\n      if (isVisible && isPageVisible && frameId === 0) {\n        lastT = 0;\n        frameId = requestAnimationFrame(animate);\n      }\n    };\n    const tryStop = () => {\n      if (frameId !== 0) {\n        cancelAnimationFrame(frameId);\n        frameId = 0;\n      }\n    };\n\n    const io = new IntersectionObserver(\n      ([entry]) => {\n        isVisible = entry.isIntersecting;\n        isVisible ? tryStart() : tryStop();\n      },\n      { threshold: 0 }\n    );\n    io.observe(mount);\n\n    const onVisibility = () => {\n      isPageVisible = !document.hidden;\n      isPageVisible ? tryStart() : tryStop();\n    };\n    document.addEventListener('visibilitychange', onVisibility);\n\n    tryStart();\n\n    return () => {\n      tryStop();\n      io.disconnect();\n      document.removeEventListener('visibilitychange', onVisibility);\n      window.removeEventListener('resize', resize);\n      ro.disconnect();\n      mount.removeEventListener('mousemove', onMouseMove);\n      mount.removeEventListener('mouseenter', onMouseEnter);\n      mount.removeEventListener('mouseleave', onMouseLeave);\n      mount.removeEventListener('click', onClick);\n      mount.removeChild(renderer.domElement);\n      renderer.dispose();\n      material.dispose();\n    };\n  }, []);\n\n  return <div ref={mountRef} className=\"magic-rings-container\" style={blur > 0 ? { filter: `blur(${blur}px)` } : undefined} />;\n}\n"
		}
	],
	"registryDependencies": [],
	"dependencies": [
		"three@^0.180.0"
	]
}