{
	"$schema": "https://ui.shadcn.com/schema/registry-item.json",
	"name": "Grainient-JS-CSS",
	"title": "Grainient",
	"description": "Grainy gradient swirls with soft wave distortion.",
	"type": "registry:component",
	"files": [
		{
			"type": "registry:component",
			"path": "Grainient/Grainient.css",
			"content": ".grainient-container {\n  position: relative;\n  width: 100%;\n  height: 100%;\n  overflow: hidden;\n}\n"
		},
		{
			"type": "registry:component",
			"path": "Grainient/Grainient.jsx",
			"content": "import { useEffect, useRef } from 'react';\nimport { Renderer, Program, Mesh, Triangle } from 'ogl';\nimport './Grainient.css';\n\nconst hexToRgb = hex => {\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 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 uTimeSpeed;\nuniform float uColorBalance;\nuniform float uWarpStrength;\nuniform float uWarpFrequency;\nuniform float uWarpSpeed;\nuniform float uWarpAmplitude;\nuniform float uBlendAngle;\nuniform float uBlendSoftness;\nuniform float uRotationAmount;\nuniform float uNoiseScale;\nuniform float uGrainAmount;\nuniform float uGrainScale;\nuniform float uGrainAnimated;\nuniform float uContrast;\nuniform float uGamma;\nuniform float uSaturation;\nuniform vec2 uCenterOffset;\nuniform float uZoom;\nuniform vec3 uColor1;\nuniform vec3 uColor2;\nuniform vec3 uColor3;\nout vec4 fragColor;\n#define S(a,b,t) smoothstep(a,b,t)\nmat2 Rot(float a){float s=sin(a),c=cos(a);return mat2(c,-s,s,c);} \nvec2 hash(vec2 p){p=vec2(dot(p,vec2(2127.1,81.17)),dot(p,vec2(1269.5,283.37)));return fract(sin(p)*43758.5453);} \nfloat noise(vec2 p){vec2 i=floor(p),f=fract(p),u=f*f*(3.0-2.0*f);float n=mix(mix(dot(-1.0+2.0*hash(i+vec2(0.0,0.0)),f-vec2(0.0,0.0)),dot(-1.0+2.0*hash(i+vec2(1.0,0.0)),f-vec2(1.0,0.0)),u.x),mix(dot(-1.0+2.0*hash(i+vec2(0.0,1.0)),f-vec2(0.0,1.0)),dot(-1.0+2.0*hash(i+vec2(1.0,1.0)),f-vec2(1.0,1.0)),u.x),u.y);return 0.5+0.5*n;}\nvoid mainImage(out vec4 o, vec2 C){\n  float t=iTime*uTimeSpeed;\n  vec2 uv=C/iResolution.xy;\n  float ratio=iResolution.x/iResolution.y;\n  vec2 tuv=uv-0.5+uCenterOffset;\n  tuv/=max(uZoom,0.001);\n\n  float degree=noise(vec2(t*0.1,tuv.x*tuv.y)*uNoiseScale);\n  tuv.y*=1.0/ratio;\n  tuv*=Rot(radians((degree-0.5)*uRotationAmount+180.0));\n  tuv.y*=ratio;\n\n  float frequency=uWarpFrequency;\n  float ws=max(uWarpStrength,0.001);\n  float amplitude=uWarpAmplitude/ws;\n  float warpTime=t*uWarpSpeed;\n  tuv.x+=sin(tuv.y*frequency+warpTime)/amplitude;\n  tuv.y+=sin(tuv.x*(frequency*1.5)+warpTime)/(amplitude*0.5);\n\n  vec3 colLav=uColor1;\n  vec3 colOrg=uColor2;\n  vec3 colDark=uColor3;\n  float b=uColorBalance;\n  float s=max(uBlendSoftness,0.0);\n  mat2 blendRot=Rot(radians(uBlendAngle));\n  float blendX=(tuv*blendRot).x;\n  float edge0=-0.3-b-s;\n  float edge1=0.2-b+s;\n  float v0=0.5-b+s;\n  float v1=-0.3-b-s;\n  vec3 layer1=mix(colDark,colOrg,S(edge0,edge1,blendX));\n  vec3 layer2=mix(colOrg,colLav,S(edge0,edge1,blendX));\n  vec3 col=mix(layer1,layer2,S(v0,v1,tuv.y));\n\n  vec2 grainUv=uv*max(uGrainScale,0.001);\n  if(uGrainAnimated>0.5){grainUv+=vec2(iTime*0.05);} \n  float grain=fract(sin(dot(grainUv,vec2(12.9898,78.233)))*43758.5453);\n  col+=(grain-0.5)*uGrainAmount;\n\n  col=(col-0.5)*uContrast+0.5;\n  float luma=dot(col,vec3(0.2126,0.7152,0.0722));\n  col=mix(vec3(luma),col,uSaturation);\n  col=pow(max(col,0.0),vec3(1.0/max(uGamma,0.001)));\n  col=clamp(col,0.0,1.0);\n\n  o=vec4(col,1.0);\n}\nvoid main(){\n  vec4 o=vec4(0.0);\n  mainImage(o,gl_FragCoord.xy);\n  fragColor=o;\n}\n`;\n\n\n// Keep renderer/program alive across re-renders so Effect 2 can update\n// uniforms without ever rebuilding the WebGL context.\nconst ctxMap = new WeakMap();\n\nconst Grainient = ({\n  timeSpeed = 0.25,\n  colorBalance = 0.0,\n  warpStrength = 1.0,\n  warpFrequency = 5.0,\n  warpSpeed = 2.0,\n  warpAmplitude = 50.0,\n  blendAngle = 0.0,\n  blendSoftness = 0.05,\n  rotationAmount = 500.0,\n  noiseScale = 2.0,\n  grainAmount = 0.1,\n  grainScale = 2.0,\n  grainAnimated = false,\n  contrast = 1.5,\n  gamma = 1.0,\n  saturation = 1.0,\n  centerX = 0.0,\n  centerY = 0.0,\n  zoom = 0.9,\n  color1 = '#FF9FFC',\n  color2 = '#5227FF',\n  color3 = '#B497CF',\n  className = ''\n}) => {\n  const containerRef = useRef(null);\n\n  // Effect 1: build WebGL context once, pause when offscreen / tab hidden\n  useEffect(() => {\n    const container = containerRef.current;\n    if (!container) return;\n\n    const renderer = new Renderer({\n      webgl: 2,\n      alpha: true,\n      antialias: false,\n      dpr: Math.min(window.devicePixelRatio || 1, 2)\n    });\n\n    const gl = renderer.gl;\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        uTimeSpeed:      { value: 0.25 },\n        uColorBalance:   { value: 0.0 },\n        uWarpStrength:   { value: 1.0 },\n        uWarpFrequency:  { value: 5.0 },\n        uWarpSpeed:      { value: 2.0 },\n        uWarpAmplitude:  { value: 50.0 },\n        uBlendAngle:     { value: 0.0 },\n        uBlendSoftness:  { value: 0.05 },\n        uRotationAmount: { value: 500.0 },\n        uNoiseScale:     { value: 2.0 },\n        uGrainAmount:    { value: 0.1 },\n        uGrainScale:     { value: 2.0 },\n        uGrainAnimated:  { value: 0.0 },\n        uContrast:       { value: 1.5 },\n        uGamma:          { value: 1.0 },\n        uSaturation:     { value: 1.0 },\n        uCenterOffset:   { value: new Float32Array([0, 0]) },\n        uZoom:           { value: 0.9 },\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      }\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;\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    let raf = 0;\n    let isVisible = true;\n    let isPageVisible = !document.hidden;\n    const t0 = performance.now();\n\n    const loop = t => {\n      program.uniforms.iTime.value = (t - t0) * 0.001;\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) { cancelAnimationFrame(raf); raf = 0; }\n    };\n\n    const io = new IntersectionObserver(\n      ([entry]) => { isVisible = entry.isIntersecting; isVisible ? tryStart() : tryStop(); },\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      ctxMap.delete(container);\n      try { container.removeChild(canvas); } catch { /* ignore */ }\n    };\n  }, []); // renderer created once\n\n  // Effect 2: sync props to uniforms — zero GPU cost, no teardown\n  useEffect(() => {\n    const container = containerRef.current;\n    if (!container) return;\n    const ctx = ctxMap.get(container);\n    if (!ctx) return;\n    const { program } = ctx;\n    const u = program.uniforms;\n\n    u.uTimeSpeed.value      = timeSpeed;\n    u.uColorBalance.value   = colorBalance;\n    u.uWarpStrength.value   = warpStrength;\n    u.uWarpFrequency.value  = warpFrequency;\n    u.uWarpSpeed.value      = warpSpeed;\n    u.uWarpAmplitude.value  = warpAmplitude;\n    u.uBlendAngle.value     = blendAngle;\n    u.uBlendSoftness.value  = blendSoftness;\n    u.uRotationAmount.value = rotationAmount;\n    u.uNoiseScale.value     = noiseScale;\n    u.uGrainAmount.value    = grainAmount;\n    u.uGrainScale.value     = grainScale;\n    u.uGrainAnimated.value  = grainAnimated ? 1.0 : 0.0;\n    u.uContrast.value       = contrast;\n    u.uGamma.value          = gamma;\n    u.uSaturation.value     = saturation;\n    u.uCenterOffset.value   = new Float32Array([centerX, centerY]);\n    u.uZoom.value           = zoom;\n    u.uColor1.value         = new Float32Array(hexToRgb(color1));\n    u.uColor2.value         = new Float32Array(hexToRgb(color2));\n    u.uColor3.value         = new Float32Array(hexToRgb(color3));\n  }, [\n    timeSpeed, colorBalance, warpStrength, warpFrequency, warpSpeed,\n    warpAmplitude, blendAngle, blendSoftness, rotationAmount, noiseScale,\n    grainAmount, grainScale, grainAnimated, contrast, gamma, saturation,\n    centerX, centerY, zoom, color1, color2, color3\n  ]);\n\n\n  return <div ref={containerRef} className={`grainient-container ${className}`.trim()} />;\n};\n\nexport default Grainient;"
		}
	],
	"registryDependencies": [],
	"dependencies": [
		"ogl@^1.0.11"
	]
}