{
	"$schema": "https://ui.shadcn.com/schema/registry-item.json",
	"name": "MoltenMetal-TS-CSS",
	"title": "MoltenMetal",
	"description": "Swirling caustic plasma filaments with molten, white-hot cores.",
	"type": "registry:component",
	"files": [
		{
			"type": "registry:file",
			"path": "MoltenMetal.css",
			"target": "@components/MoltenMetal.css",
			"content": ".molten-metal-container {\n  position: relative;\n  width: 100%;\n  height: 100%;\n  overflow: hidden;\n}\n"
		},
		{
			"type": "registry:component",
			"path": "MoltenMetal.tsx",
			"content": "import React, { useEffect, useRef } from 'react';\nimport { Renderer, Program, Mesh, Triangle } from 'ogl';\nimport './MoltenMetal.css';\n\nexport type MoltenMetalColorMode = 'molten' | 'ember' | 'frost';\n\nexport interface MoltenMetalProps {\n  color1?: string;\n  color2?: string;\n  color3?: string;\n  speed?: number;\n  scale?: number;\n  detail?: number;\n  glow?: number;\n  coreSize?: number;\n  swirl?: number;\n  fold?: number;\n  blackPoint?: number;\n  brightness?: number;\n  colorMode?: MoltenMetalColorMode;\n  grain?: boolean;\n  grainIntensity?: number;\n  mouseInteraction?: boolean;\n  mouseStrength?: number;\n  opacity?: number;\n  backgroundColor?: string;\n  lightMode?: boolean;\n  className?: string;\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, 1, 1];\n  return [parseInt(result[1], 16) / 255, parseInt(result[2], 16) / 255, parseInt(result[3], 16) / 255];\n};\n\nconst colorModeToFloat = (mode: MoltenMetalColorMode): number => (mode === 'ember' ? 1 : mode === 'frost' ? 2 : 0);\n\nconst vertex = `#version 300 es\nin vec2 position;\nvoid main() {\n  gl_Position = vec4(position, 0.0, 1.0);\n}\n`;\n\nconst fragment = `#version 300 es\nprecision highp float;\nuniform vec2 iResolution;\nuniform float iTime;\nuniform float uSpeed;\nuniform float uScale;\nuniform float uDetail;\nuniform float uGlow;\nuniform float uCoreSize;\nuniform float uSwirl;\nuniform float uFold;\nuniform float uBlackPoint;\nuniform float uBrightness;\nuniform float uColorMode;\nuniform float uGrain;\nuniform float uGrainIntensity;\nuniform float uOpacity;\nuniform vec2 uMouse;\nuniform float uMouseStrength;\nuniform bool uEnableMouse;\nuniform vec3 uColor1;\nuniform vec3 uColor2;\nuniform vec3 uColor3;\nuniform vec3 uBackgroundColor;\nuniform bool uLightMode;\nout vec4 fragColor;\n\nfloat hash(vec2 p) {\n  return fract(sin(dot(p, vec2(12.9898, 78.233))) * 43758.5453);\n}\n\nvoid main() {\n  float time = iTime * uSpeed;\n  vec2 p = uScale * ((gl_FragCoord.xy - 0.5 * iResolution.xy) / iResolution.y) - 0.5;\n\n  vec2 drift = vec2(0.0);\n  if (uEnableMouse) {\n    drift = (uMouse - 0.5) * uMouseStrength * 2.0;\n  }\n  p += drift;\n\n  vec2 i = p;\n  float c = 0.0;\n  float r = length(p + vec2(sin(time), sin(time * 0.3 + 5.0)) * 0.5);\n  float d = length(p);\n  float rot = d + time + p.x * uSwirl;\n\n  float cosRot = cos(rot);\n  mat2 warp = mat2(cos(rot - sin(time / 5.0)), sin(rot), -sin(cosRot - time), cosRot) * uFold;\n  float glowCore = uGlow * uCoreSize;\n\n  for (float n = 0.0; n < 8.0; n++) {\n    if (n >= uDetail) break;\n    p *= warp;\n    float t = r - time / (n + 3.0);\n    i -= p + vec2(cos(t - i.x - r) + sin(t + i.y), sin(t - i.y) + cos(t + i.x) + r);\n    c += glowCore / length(vec2(sin(i.x + t), cos(i.y + t)));\n  }\n\n  c /= 6.0;\n\n  float intensity = max(c - uBlackPoint, 0.0) * uBrightness;\n\n  float g = clamp(intensity, 0.0, 1.0);\n\n  float mid = 0.5;\n  if (uColorMode > 1.5) {\n    mid = 0.65;\n  } else if (uColorMode > 0.5) {\n    mid = 0.35;\n  }\n\n  vec3 col = mix(uColor1, uColor2, smoothstep(0.0, mid, g));\n  col = mix(col, uColor3, smoothstep(mid, 1.0, g));\n\n  float a = g;\n  if (uGrain > 0.5) {\n    float gr = hash(gl_FragCoord.xy + iTime);\n    a += (gr - 0.5) * uGrainIntensity;\n  }\n  a = clamp(a, 0.0, 1.0) * uOpacity;\n  if (uLightMode) {\n    float signal = 1.0 - exp(-max(c, 0.0) * 6.5);\n    float body = smoothstep(0.075, 0.68, signal);\n    float ridge = smoothstep(0.42, 0.92, signal);\n\n    vec3 lightCol = mix(uColor1, uColor2, smoothstep(0.08, 0.52, signal));\n    lightCol = mix(lightCol, uColor3, smoothstep(0.52, 0.96, signal));\n    lightCol = mix(lightCol, lightCol * 0.72, ridge * 0.24);\n\n    float coverage = body * mix(0.2, 0.86, signal) * uOpacity;\n    if (uGrain > 0.5) {\n      float gr = hash(gl_FragCoord.xy + iTime);\n      coverage += (gr - 0.5) * uGrainIntensity * body * 0.16;\n    }\n    fragColor = vec4(mix(uBackgroundColor, lightCol, clamp(coverage, 0.0, 0.92)), 1.0);\n  } else {\n    fragColor = vec4(col * a, a);\n  }\n}\n`;\n\ntype MoltenMetalCtx = {\n  renderer: InstanceType<typeof Renderer>;\n  program: InstanceType<typeof Program>;\n  mesh: InstanceType<typeof Mesh>;\n};\nconst ctxMap = new WeakMap<HTMLDivElement, MoltenMetalCtx>();\n\nconst MoltenMetal: React.FC<MoltenMetalProps> = ({\n  color1 = '#5227FF',\n  color2 = '#FF9FFC',\n  color3 = '#FFFFFF',\n  speed = 0.35,\n  scale = 4,\n  detail = 3,\n  glow = 1.6,\n  coreSize = 0.1,\n  swirl = 1,\n  fold = -0.2,\n  blackPoint = 0.05,\n  brightness = 1.3,\n  colorMode = 'molten',\n  grain = true,\n  grainIntensity = 0.05,\n  mouseInteraction = true,\n  mouseStrength = 0.3,\n  opacity = 1.0,\n  backgroundColor = '#FFFFFF',\n  lightMode = false,\n  className = ''\n}) => {\n  const containerRef = useRef<HTMLDivElement | null>(null);\n\n  useEffect(() => {\n    const container = containerRef.current;\n    if (!container) return;\n\n    const renderer = new Renderer({\n      webgl: 2,\n      alpha: true,\n      premultipliedAlpha: true,\n      antialias: false,\n      dpr: Math.min(window.devicePixelRatio || 1, 2)\n    });\n\n    const gl = renderer.gl;\n    gl.clearColor(0, 0, 0, 0);\n    const canvas = gl.canvas;\n    canvas.style.width = '100%';\n    canvas.style.height = '100%';\n    canvas.style.display = 'block';\n    container.appendChild(canvas);\n\n    const geometry = new Triangle(gl);\n    const program = new Program(gl, {\n      vertex,\n      fragment,\n      uniforms: {\n        iTime: { value: 0 },\n        iResolution: { value: new Float32Array([1, 1]) },\n        uSpeed: { value: 0.35 },\n        uScale: { value: 4 },\n        uDetail: { value: 3 },\n        uGlow: { value: 1.6 },\n        uCoreSize: { value: 0.1 },\n        uSwirl: { value: 1 },\n        uFold: { value: -0.2 },\n        uBlackPoint: { value: 0.05 },\n        uBrightness: { value: 1.3 },\n        uColorMode: { value: 0 },\n        uGrain: { value: 1 },\n        uGrainIntensity: { value: 0.05 },\n        uOpacity: { value: 1.0 },\n        uMouse: { value: new Float32Array([0.5, 0.5]) },\n        uMouseStrength: { value: 0.3 },\n        uEnableMouse: { value: true },\n        uColor1: { value: new Float32Array([1, 1, 1]) },\n        uColor2: { value: new Float32Array([1, 1, 1]) },\n        uColor3: { value: new Float32Array([1, 1, 1]) },\n        uBackgroundColor: { value: new Float32Array([1, 1, 1]) },\n        uLightMode: { value: false }\n      }\n    });\n\n    const mesh = new Mesh(gl, { geometry, program });\n    ctxMap.set(container, { renderer, program, mesh });\n\n    const setSize = () => {\n      const rect = container.getBoundingClientRect();\n      const w = Math.max(1, Math.floor(rect.width));\n      const h = Math.max(1, Math.floor(rect.height));\n      renderer.setSize(w, h);\n      const res = program.uniforms.iResolution.value as Float32Array;\n      res[0] = gl.drawingBufferWidth;\n      res[1] = gl.drawingBufferHeight;\n      renderer.render({ scene: mesh });\n    };\n\n    const ro = new ResizeObserver(setSize);\n    ro.observe(container);\n    setSize();\n\n    const targetMouse: [number, number] = [0.5, 0.5];\n    const currentMouse: [number, number] = [0.5, 0.5];\n\n    const handleMouseMove = (e: MouseEvent) => {\n      const rect = canvas.getBoundingClientRect();\n      targetMouse[0] = (e.clientX - rect.left) / rect.width;\n      targetMouse[1] = 1.0 - (e.clientY - rect.top) / rect.height;\n    };\n    const handleMouseLeave = () => {\n      targetMouse[0] = 0.5;\n      targetMouse[1] = 0.5;\n    };\n    canvas.addEventListener('mousemove', handleMouseMove);\n    canvas.addEventListener('mouseleave', handleMouseLeave);\n\n    let raf = 0;\n    let isVisible = true;\n    let isPageVisible = !document.hidden;\n    const t0 = performance.now();\n\n    const loop = (t: number) => {\n      program.uniforms.iTime.value = (t - t0) * 0.001;\n      currentMouse[0] += 0.05 * (targetMouse[0] - currentMouse[0]);\n      currentMouse[1] += 0.05 * (targetMouse[1] - currentMouse[1]);\n      const m = program.uniforms.uMouse.value as Float32Array;\n      m[0] = currentMouse[0];\n      m[1] = currentMouse[1];\n      renderer.render({ scene: mesh });\n      raf = requestAnimationFrame(loop);\n    };\n\n    const tryStart = () => {\n      if (isVisible && isPageVisible && raf === 0) raf = requestAnimationFrame(loop);\n    };\n    const tryStop = () => {\n      if (raf !== 0) {\n        cancelAnimationFrame(raf);\n        raf = 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(container);\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      ro.disconnect();\n      io.disconnect();\n      document.removeEventListener('visibilitychange', onVisibility);\n      canvas.removeEventListener('mousemove', handleMouseMove);\n      canvas.removeEventListener('mouseleave', handleMouseLeave);\n      ctxMap.delete(container);\n      if (canvas.parentNode === container) container.removeChild(canvas);\n      gl.getExtension('WEBGL_lose_context')?.loseContext();\n    };\n  }, []);\n\n  useEffect(() => {\n    const container = containerRef.current;\n    if (!container) return;\n    const ctx = ctxMap.get(container);\n    if (!ctx) return;\n    const u = ctx.program.uniforms;\n\n    u.uSpeed.value = speed;\n    u.uScale.value = scale;\n    u.uDetail.value = detail;\n    u.uGlow.value = glow;\n    u.uCoreSize.value = Math.max(coreSize, 0.001);\n    u.uSwirl.value = swirl;\n    u.uFold.value = fold;\n    u.uBlackPoint.value = blackPoint;\n    u.uBrightness.value = brightness;\n    u.uColorMode.value = colorModeToFloat(colorMode);\n    u.uGrain.value = grain ? 1 : 0;\n    u.uGrainIntensity.value = grainIntensity;\n    u.uOpacity.value = opacity;\n    u.uMouseStrength.value = mouseStrength;\n    u.uEnableMouse.value = mouseInteraction;\n    const c1 = hexToRgb(color1);\n    const c2 = hexToRgb(color2);\n    const c3 = hexToRgb(color3);\n    const bg = hexToRgb(backgroundColor);\n    const uc1 = u.uColor1.value as Float32Array;\n    const uc2 = u.uColor2.value as Float32Array;\n    const uc3 = u.uColor3.value as Float32Array;\n    uc1[0] = c1[0];\n    uc1[1] = c1[1];\n    uc1[2] = c1[2];\n    uc2[0] = c2[0];\n    uc2[1] = c2[1];\n    uc2[2] = c2[2];\n    uc3[0] = c3[0];\n    uc3[1] = c3[1];\n    uc3[2] = c3[2];\n    u.uBackgroundColor.value[0] = bg[0];\n    u.uBackgroundColor.value[1] = bg[1];\n    u.uBackgroundColor.value[2] = bg[2];\n  }, [\n    color1,\n    color2,\n    color3,\n    speed,\n    scale,\n    detail,\n    glow,\n    coreSize,\n    swirl,\n    fold,\n    blackPoint,\n    brightness,\n    colorMode,\n    grain,\n    grainIntensity,\n    mouseInteraction,\n    mouseStrength,\n    opacity,\n    backgroundColor,\n    lightMode\n  ]);\n\n  return <div ref={containerRef} className={`molten-metal-container ${className}`.trim()} />;\n};\n\nexport default MoltenMetal;\n"
		}
	],
	"registryDependencies": [],
	"dependencies": [
		"ogl@^1.0.11"
	]
}