{
	"$schema": "https://ui.shadcn.com/schema/registry-item.json",
	"name": "Plasma-TS-CSS",
	"title": "Plasma",
	"description": "Organic plasma gradients swirl + morph with smooth turbulence.",
	"type": "registry:component",
	"files": [
		{
			"type": "registry:file",
			"path": "Plasma.css",
			"target": "@components/Plasma.css",
			"content": ".plasma-container {\n  position: relative;\n  width: 100%;\n  height: 100%;\n  overflow: hidden;\n}\n"
		},
		{
			"type": "registry:component",
			"path": "Plasma.tsx",
			"content": "import React, { useEffect, useRef } from 'react';\nimport { Renderer, Program, Mesh, Triangle } from 'ogl';\nimport './Plasma.css';\n\ninterface PlasmaProps {\n  color?: string;\n  speed?: number;\n  direction?: 'forward' | 'reverse' | 'pingpong';\n  scale?: number;\n  opacity?: number;\n  mouseInteractive?: boolean;\n  /** Internal render resolution multiplier. 1 = full res, 0.5 = quarter the pixels. Default 0.55. */\n  renderScale?: number;\n  /** Hard cap on devicePixelRatio used for rendering. Default 1.5. */\n  maxDpr?: number;\n  /** Target frame rate for the animation loop. Default 30. */\n  targetFps?: number;\n  /** Raymarch step count — lower is cheaper, less detailed. Default 60. */\n  iterations?: number;\n  lightMode?: boolean;\n}\n\nconst hexToRgb = (hex: string): [number, number, number] => {\n  const result = /^#?([a-f\\d]{2})([a-f\\d]{2})([a-f\\d]{2})$/i.exec(hex);\n  if (!result) return [1, 0.5, 0.2];\n  return [parseInt(result[1], 16) / 255, parseInt(result[2], 16) / 255, parseInt(result[3], 16) / 255];\n};\n\nconst vertex = `#version 300 es\nprecision highp float;\nin vec2 position;\nin vec2 uv;\nout vec2 vUv;\nvoid main() {\n  vUv = uv;\n  gl_Position = vec4(position, 0.0, 1.0);\n}\n`;\n\nconst ORIGINAL_QUALITY = 60;\n\nconst buildFragment = (iterations: number) => {\n  return `#version 300 es\nprecision highp float;\nuniform vec2 iResolution;\nuniform float iTime;\nuniform vec3 uCustomColor;\nuniform float uUseCustomColor;\nuniform float uSpeed;\nuniform float uDirection;\nuniform float uScale;\nuniform float uOpacity;\nuniform vec2 uMouse;\nuniform float uMouseInteractive;\nuniform float uQuality;\nuniform float uStepScale;\nuniform float uLightMode;\nout vec4 fragColor;\n\nvoid mainImage(out vec4 o, vec2 C) {\n  vec2 center = iResolution.xy * 0.5;\n  C = (C - center) / uScale + center;\n  \n  vec2 mouseOffset = (uMouse - center) * 0.0002;\n  C += mouseOffset * length(C - center) * step(0.5, uMouseInteractive);\n  \n  float i, d, z, T = iTime * uSpeed * uDirection;\n  vec3 O, p, S;\n\n  for (vec2 r = iResolution.xy, Q; ++i < 60.0; O += o.w/d*o.xyz) {\n    p = z*normalize(vec3(C-.5*r,r.y)); \n    p.z -= 4.; \n    S = p;\n    d = p.y-T;\n    \n    p.x += .4*(1.+p.y)*sin(d + p.x*0.1)*cos(.34*d + p.x*0.05); \n    Q = p.xz *= mat2(cos(p.y+vec4(0,11,33,0)-T)); \n    z += d = (abs(sqrt(length(Q*Q)) - .25*(5.+S.y))/3.+8e-4) * uStepScale;\n    o = 1.+sin(S.y+p.z*.5+S.z-length(S-p)+vec4(2,1,0,8));\n    if (i >= uQuality) break;\n  }\n  \n  o.xyz = tanh(O/1e4);\n}\n\nbool finite1(float x){ return !(isnan(x) || isinf(x)); }\nvec3 sanitize(vec3 c){\n  return vec3(\n    finite1(c.r) ? c.r : 0.0,\n    finite1(c.g) ? c.g : 0.0,\n    finite1(c.b) ? c.b : 0.0\n  );\n}\n\nvoid main() {\n  vec4 o = vec4(0.0);\n  mainImage(o, gl_FragCoord.xy);\n  vec3 rgb = sanitize(o.rgb);\n  \n  float intensity = (rgb.r + rgb.g + rgb.b) / 3.0;\n  vec3 customColor = intensity * uCustomColor;\n  vec3 finalColor = mix(rgb, customColor, step(0.5, uUseCustomColor));\n  \n  float alpha = length(rgb) * uOpacity;\n  if (uLightMode > 0.5) {\n    vec3 source = clamp(finalColor, 0.0, 1.0);\n    float peak = max(source.r, max(source.g, source.b));\n    float floorColor = min(source.r, min(source.g, source.b));\n    vec3 chroma = (source - vec3(floorColor)) / max(peak - floorColor, 0.0001);\n    vec3 pigment = mix(source / max(peak, 0.0001), chroma, 0.68) * 0.72;\n    float energy = clamp(length(rgb) / 1.7320508, 0.0, 1.0);\n    float coverage = pow(smoothstep(0.035, 0.72, energy), 0.76) * min(uOpacity, 1.0) * 0.9;\n    fragColor = vec4(mix(vec3(1.0), pigment, coverage), 1.0);\n  } else {\n    fragColor = vec4(finalColor, alpha);\n  }\n}`;\n};\n\nexport const Plasma: React.FC<PlasmaProps> = ({\n  color = '#ffffff',\n  speed = 1,\n  direction = 'forward',\n  scale = 1,\n  opacity = 1,\n  mouseInteractive = true,\n  renderScale = 0.55,\n  maxDpr = 1.5,\n  targetFps = 60,\n  iterations = 60,\n  lightMode = false,\n}) => {\n  const containerRef = useRef<HTMLDivElement | null>(null);\n  const mousePos = useRef({ x: 0, y: 0 });\n  const pendingMouse = useRef<{ x: number; y: number } | null>(null);\n\n  useEffect(() => {\n    if (!containerRef.current) return;\n    const containerEl = containerRef.current;\n\n    const prefersReducedMotion =\n      typeof window !== 'undefined' &&\n      window.matchMedia?.('(prefers-reduced-motion: reduce)').matches;\n\n    const useCustomColor = color ? 1.0 : 0.0;\n    const customColorRgb = color ? hexToRgb(color) : [1, 1, 1];\n\n    const directionMultiplier = direction === 'reverse' ? -1.0 : 1.0;\n\n    let renderer: Renderer;\n    try {\n      renderer = new Renderer({\n        webgl: 2,\n        alpha: true,\n        antialias: false,\n        dpr: Math.min(window.devicePixelRatio || 1, maxDpr)\n      });\n    } catch {\n      return;\n    }\n    const gl = renderer.gl;\n    if (!gl) return;\n    const canvas = gl.canvas as HTMLCanvasElement;\n    canvas.style.display = 'block';\n    canvas.style.width = '100%';\n    canvas.style.height = '100%';\n    // Rendering at renderScale internally, CSS stretches it back up.\n    containerEl.appendChild(canvas);\n\n    const geometry = new Triangle(gl);\n\n    const program = new Program(gl, {\n      vertex: vertex,\n      fragment: buildFragment(iterations),\n      uniforms: {\n        iTime: { value: 0 },\n        iResolution: { value: new Float32Array([1, 1]) },\n        uCustomColor: { value: new Float32Array(customColorRgb) },\n        uUseCustomColor: { value: useCustomColor },\n        uSpeed: { value: speed * 0.4 },\n        uDirection: { value: directionMultiplier },\n        uScale: { value: scale },\n        uOpacity: { value: opacity },\n        uMouse: { value: new Float32Array([0, 0]) },\n        uMouseInteractive: { value: mouseInteractive ? 1.0 : 0.0 },\n        uQuality: { value: iterations },\n        uStepScale: { value: ORIGINAL_QUALITY / iterations },\n        uLightMode: { value: lightMode ? 1 : 0 },\n      }\n    });\n\n    const mesh = new Mesh(gl, { geometry, program });\n\n    const handleMouseMove = (e: MouseEvent) => {\n      if (!mouseInteractive) return;\n      const rect = containerEl.getBoundingClientRect();\n      // Store the latest position but don't touch GL state here, the rAF loop picks it up once per rendered frame instead of once per mouse event.\n      pendingMouse.current = {\n        x: e.clientX - rect.left,\n        y: e.clientY - rect.top,\n      };\n    };\n\n    if (mouseInteractive) {\n      containerEl.addEventListener('mousemove', handleMouseMove, { passive: true });\n    }\n\n    let resizePending = false;\n    const setSize = () => {\n      const rect = containerEl.getBoundingClientRect();\n      const width = Math.max(1, Math.floor(rect.width * renderScale));\n      const height = Math.max(1, Math.floor(rect.height * renderScale));\n      renderer.setSize(width, height);\n\n      // renderer.setSize also sets canvas.style.width/height to match the (scaled-down) drawing buffer - override that so the canvas still stretches to fill its container via CSS while the buffer stays small.\n      canvas.style.width = '100%';\n      canvas.style.height = '100%';\n\n      const res = program.uniforms.iResolution.value as Float32Array;\n      res[0] = gl.drawingBufferWidth;\n      res[1] = gl.drawingBufferHeight;\n    };\n\n    const ro = new ResizeObserver(() => {\n      // Batch rapid resize events (ex. during a window drag) into one setSize per frame.\n      if (resizePending) return;\n      resizePending = true;\n      requestAnimationFrame(() => {\n        resizePending = false;\n        setSize();\n      });\n    });\n    ro.observe(containerEl);\n    setSize();\n\n    let raf = 0;\n    let contextLost = false;\n    let isVisible = true;\n    let tabVisible = document.visibilityState !== 'hidden';\n    const t0 = performance.now();\n    const frameInterval = 1000 / targetFps;\n    let lastFrameTime = 0;\n\n    const renderStaticFrame = () => {\n      (program.uniforms.iTime as any).value = 0;\n      renderer.render({ scene: mesh });\n    };\n\n    const loop = (t: number) => {\n      if (contextLost || !isVisible || !tabVisible) return;\n\n      if (t - lastFrameTime < frameInterval) {\n        raf = requestAnimationFrame(loop);\n        return;\n      }\n      lastFrameTime = t;\n\n      if (pendingMouse.current) {\n        mousePos.current = pendingMouse.current;\n        pendingMouse.current = null;\n        const mouseUniform = program.uniforms.uMouse.value as Float32Array;\n        mouseUniform[0] = mousePos.current.x;\n        mouseUniform[1] = mousePos.current.y;\n      }\n\n      let timeValue = (t - t0) * 0.001;\n      if (direction === 'pingpong') {\n        const pingpongDuration = 10;\n        const segmentTime = timeValue % pingpongDuration;\n        const isForward = Math.floor(timeValue / pingpongDuration) % 2 === 0;\n        const u = segmentTime / pingpongDuration;\n        const smooth = u * u * (3 - 2 * u);\n        const pingpongTime = isForward ? smooth * pingpongDuration : (1 - smooth) * pingpongDuration;\n        (program.uniforms.uDirection as any).value = 1.0;\n        (program.uniforms.iTime as any).value = pingpongTime;\n      } else {\n        (program.uniforms.iTime as any).value = timeValue;\n      }\n      renderer.render({ scene: mesh });\n      raf = requestAnimationFrame(loop);\n    };\n\n    const handleContextLost = (e: Event) => {\n      e.preventDefault();\n      contextLost = true;\n      cancelAnimationFrame(raf);\n    };\n    const handleContextRestored = () => {\n      contextLost = false;\n      if (isVisible && tabVisible && !prefersReducedMotion) {\n        cancelAnimationFrame(raf);\n        raf = requestAnimationFrame(loop);\n      }\n    };\n    canvas.addEventListener('webglcontextlost', handleContextLost);\n    canvas.addEventListener('webglcontextrestored', handleContextRestored);\n\n    const io = new IntersectionObserver(([entry]) => {\n      const wasVisible = isVisible;\n      isVisible = entry.isIntersecting;\n      if (isVisible && !wasVisible && !contextLost && tabVisible && !prefersReducedMotion) {\n        cancelAnimationFrame(raf);\n        raf = requestAnimationFrame(loop);\n      }\n    }, { threshold: 0 });\n    io.observe(containerEl);\n\n    const handleVisibilityChange = () => {\n      tabVisible = document.visibilityState !== 'hidden';\n      if (tabVisible && isVisible && !contextLost && !prefersReducedMotion) {\n        cancelAnimationFrame(raf);\n        lastFrameTime = 0;\n        raf = requestAnimationFrame(loop);\n      } else {\n        cancelAnimationFrame(raf);\n      }\n    };\n    document.addEventListener('visibilitychange', handleVisibilityChange);\n\n    // Respect prefers-reduced-motion: paint one frame and stop, rather than running a perpetual animation loop for users who've asked not to see motion.\n    if (prefersReducedMotion) {\n      renderStaticFrame();\n    } else {\n      raf = requestAnimationFrame(loop);\n    }\n\n    return () => {\n      cancelAnimationFrame(raf);\n      ro.disconnect();\n      io.disconnect();\n      document.removeEventListener('visibilitychange', handleVisibilityChange);\n      canvas.removeEventListener('webglcontextlost', handleContextLost);\n      canvas.removeEventListener('webglcontextrestored', handleContextRestored);\n      if (mouseInteractive && containerEl) {\n        containerEl.removeEventListener('mousemove', handleMouseMove);\n      }\n      try {\n        containerEl?.removeChild(canvas);\n      } catch {}\n    };\n  }, [color, speed, direction, scale, opacity, mouseInteractive, renderScale, maxDpr, targetFps, iterations, lightMode]);\n\n  return <div ref={containerRef} className=\"plasma-container\" />;\n};\n\nexport default Plasma;\n"
		}
	],
	"registryDependencies": [],
	"dependencies": [
		"ogl@^1.0.11"
	]
}