{
	"$schema": "https://ui.shadcn.com/schema/registry-item.json",
	"name": "MetallicPaint-JS-CSS",
	"title": "MetallicPaint",
	"description": "Liquid metallic paint shader which can be applied to SVG elements.",
	"type": "registry:component",
	"files": [
		{
			"type": "registry:file",
			"path": "MetallicPaint.css",
			"target": "@components/MetallicPaint.css",
			"content": ".paint-container {\n  display: block;\n  height: 100%;\n  width: 100%;\n  object-fit: contain;\n}\n"
		},
		{
			"type": "registry:component",
			"path": "MetallicPaint.jsx",
			"content": "'use client';\n\nimport { useEffect, useRef, useState, useCallback } from 'react';\nimport './MetallicPaint.css';\n\nconst vertexShader = `#version 300 es\nprecision highp float;\nin vec2 a_position;\nout vec2 vP;\nvoid main(){vP=a_position*.5+.5;gl_Position=vec4(a_position,0.,1.);}`;\n\nconst fragmentShader = `#version 300 es\nprecision highp float;\nin vec2 vP;\nout vec4 oC;\nuniform sampler2D u_tex;\nuniform float u_time,u_ratio,u_imgRatio,u_seed,u_scale,u_refract,u_blur,u_liquid;\nuniform float u_bright,u_contrast,u_angle,u_fresnel,u_sharp,u_wave,u_noise,u_chroma;\nuniform float u_distort,u_contour;\nuniform vec3 u_lightColor,u_darkColor,u_tint;\n\nvec3 sC,sM;\n\nvec3 pW(vec3 v){\n  vec3 i=floor(v),f=fract(v),s=sign(fract(v*.5)-.5),h=fract(sM*i+i.yzx),c=f*(f-1.);\n  return s*c*((h*16.-4.)*c-1.);\n}\n\nvec3 aF(vec3 b,vec3 c){return pW(b+c.zxy-pW(b.zxy+c.yzx)+pW(b.yzx+c.xyz));}\nvec3 lM(vec3 s,vec3 p){return(p+aF(s,p))*.5;}\n\nvec2 fA(){\n  vec2 c=vP-.5;\n  c.x*=u_ratio>u_imgRatio?u_ratio/u_imgRatio:1.;\n  c.y*=u_ratio>u_imgRatio?1.:u_imgRatio/u_ratio;\n  return vec2(c.x+.5,.5-c.y);\n}\n\nvec2 rot(vec2 p,float r){float c=cos(r),s=sin(r);return vec2(p.x*c+p.y*s,p.y*c-p.x*s);}\n\nfloat bM(vec2 c,float t){\n  vec2 l=smoothstep(vec2(0.),vec2(t),c),u=smoothstep(vec2(0.),vec2(t),1.-c);\n  return l.x*l.y*u.x*u.y;\n}\n\nfloat mG(float hi,float lo,float t,float sh,float cv){\n  sh*=(2.-u_sharp);\n  float ci=smoothstep(.15,.85,cv),r=lo;\n  float e1=.08/u_scale;\n  r=mix(r,hi,smoothstep(0.,sh*1.5,t));\n  r=mix(r,lo,smoothstep(e1-sh,e1+sh,t));\n  float e2=e1+.05/u_scale*(1.-ci*.35);\n  r=mix(r,hi,smoothstep(e2-sh,e2+sh,t));\n  float e3=e2+.025/u_scale*(1.-ci*.45);\n  r=mix(r,lo,smoothstep(e3-sh,e3+sh,t));\n  float e4=e1+.1/u_scale;\n  r=mix(r,hi,smoothstep(e4-sh,e4+sh,t));\n  float rm=1.-e4,gT=clamp((t-e4)/rm,0.,1.);\n  r=mix(r,mix(hi,lo,smoothstep(0.,1.,gT)),smoothstep(e4-sh*.5,e4+sh*.5,t));\n  return r;\n}\n\nvoid main(){\n  sC=fract(vec3(.7548,.5698,.4154)*(u_seed+17.31))+.5;\n  sM=fract(sC.zxy-sC.yzx*1.618);\n  vec2 sc=vec2(vP.x*u_ratio,1.-vP.y);\n  float angleRad=u_angle*3.14159/180.;\n  sc=rot(sc-.5,angleRad)+.5;\n  sc=clamp(sc,0.,1.);\n  float sl=sc.x-sc.y,an=u_time*.001;\n  vec2 iC=fA();\n  vec4 texSample=texture(u_tex,iC);\n  float dp=texSample.r;\n  float shapeMask=texSample.a;\n  vec3 hi=u_lightColor*u_bright;\n  vec3 lo=u_darkColor*(2.-u_bright);\n  lo.b+=smoothstep(.6,1.4,sc.x+sc.y)*.08;\n  vec2 fC=sc-.5;\n  float rd=length(fC+vec2(0.,sl*.15));\n  vec2 ag=rot(fC,(.22-sl*.18)*3.14159);\n  float cv=1.-pow(rd*1.65,1.15);\n  cv*=pow(sc.y,.35);\n  float vs=shapeMask;\n  vs*=bM(iC,.01);\n  float fr=pow(1.-cv,u_fresnel)*.3;\n  vs=min(vs+fr*vs,1.);\n  float mT=an*.0625;\n  vec3 wO=vec3(-1.05,1.35,1.55);\n  vec3 wA=aF(vec3(31.,73.,56.),mT+wO)*.22*u_wave;\n  vec3 wB=aF(vec3(24.,64.,42.),mT-wO.yzx)*.22*u_wave;\n  vec2 nC=sc*45.*u_noise;\n  nC+=aF(sC.zxy,an*.17*sC.yzx-sc.yxy*.35).xy*18.*u_wave;\n  vec3 tC=vec3(.00041,.00053,.00076)*mT+wB*nC.x+wA*nC.y;\n  tC=lM(sC,tC);\n  tC=lM(sC+1.618,tC);\n  float tb=sin(tC.x*3.14159)*.5+.5;\n  tb=tb*2.-1.;\n  float noiseVal=pW(vec3(sc*8.+an,an*.5)).x;\n  float edgeFactor=smoothstep(0.,.5,dp)*smoothstep(1.,.5,dp);\n  float lD=dp+(1.-dp)*u_liquid*tb;\n  lD+=noiseVal*u_distort*.15*edgeFactor;\n  float rB=clamp(1.-cv,0.,1.);\n  float fl=ag.x+sl;\n  fl+=noiseVal*sl*u_distort*edgeFactor;\n  fl*=mix(1.,1.-dp*.5,u_contour);\n  fl-=dp*u_contour*.8;\n  float eI=smoothstep(0.,1.,lD)*smoothstep(1.,0.,lD);\n  fl-=tb*sl*1.8*eI;\n  float cA=cv*clamp(pow(sc.y,.12),.25,1.);\n  fl*=.12+(1.05-lD)*cA;\n  fl*=smoothstep(1.,.65,lD);\n  float vA1=smoothstep(.08,.18,sc.y)*smoothstep(.38,.18,sc.y);\n  float vA2=smoothstep(.08,.18,1.-sc.y)*smoothstep(.38,.18,1.-sc.y);\n  fl+=vA1*.16+vA2*.025;\n  fl*=.45+pow(sc.y,2.)*.55;\n  fl*=u_scale;\n  fl-=an;\n  float rO=rB+cv*tb*.025;\n  float vM1=smoothstep(-.12,.18,sc.y)*smoothstep(.48,.08,sc.y);\n  float cM1=smoothstep(.35,.55,cv)*smoothstep(.95,.35,cv);\n  rO+=vM1*cM1*4.5;\n  rO-=sl;\n  float bO=rB*1.25;\n  float vM2=smoothstep(-.02,.35,sc.y)*smoothstep(.75,.08,sc.y);\n  float cM2=smoothstep(.35,.55,cv)*smoothstep(.75,.35,cv);\n  bO+=vM2*cM2*.9;\n  bO-=lD*.18;\n  rO*=u_refract*u_chroma;\n  bO*=u_refract*u_chroma;\n  float sf=u_blur;\n  float rP=fract(fl+rO);\n  float rC=mG(hi.r,lo.r,rP,sf+.018+u_refract*cv*.025,cv);\n  float gP=fract(fl);\n  float gC=mG(hi.g,lo.g,gP,sf+.008/max(.01,1.-sl),cv);\n  float bP=fract(fl-bO);\n  float bC=mG(hi.b,lo.b,bP,sf+.008,cv);\n  vec3 col=vec3(rC,gC,bC);\n  col=(col-.5)*u_contrast+.5;\n  col=clamp(col,0.,1.);\n  col=mix(col,1.-min(vec3(1.),(1.-col)/max(u_tint,vec3(.001))),length(u_tint-1.)*.5);\n  col=clamp(col,0.,1.);\n  oC=vec4(col*vs,vs);\n}`;\n\nfunction processImage(img) {\n  const MAX_SIZE = 1000;\n  const MIN_SIZE = 500;\n  let width = img.naturalWidth || img.width;\n  let height = img.naturalHeight || img.height;\n\n  if (width > MAX_SIZE || height > MAX_SIZE || width < MIN_SIZE || height < MIN_SIZE) {\n    const scale =\n      width > height\n        ? width > MAX_SIZE\n          ? MAX_SIZE / width\n          : width < MIN_SIZE\n            ? MIN_SIZE / width\n            : 1\n        : height > MAX_SIZE\n          ? MAX_SIZE / height\n          : height < MIN_SIZE\n            ? MIN_SIZE / height\n            : 1;\n    width = Math.round(width * scale);\n    height = Math.round(height * scale);\n  }\n\n  const canvas = document.createElement('canvas');\n  canvas.width = width;\n  canvas.height = height;\n  const ctx = canvas.getContext('2d');\n  ctx.drawImage(img, 0, 0, width, height);\n\n  const imageData = ctx.getImageData(0, 0, width, height);\n  const data = imageData.data;\n  const size = width * height;\n  const alphaValues = new Float32Array(size);\n  const shapeMask = new Uint8Array(size);\n  const boundaryMask = new Uint8Array(size);\n\n  for (let i = 0; i < size; i++) {\n    const idx = i * 4;\n    const r = data[idx],\n      g = data[idx + 1],\n      b = data[idx + 2],\n      a = data[idx + 3];\n    const isBackground = (r > 250 && g > 250 && b > 250 && a === 255) || a < 5;\n    alphaValues[i] = isBackground ? 0 : a / 255;\n    shapeMask[i] = alphaValues[i] > 0.1 ? 1 : 0;\n  }\n\n  for (let y = 0; y < height; y++) {\n    for (let x = 0; x < width; x++) {\n      const idx = y * width + x;\n      if (!shapeMask[idx]) continue;\n      if (\n        x === 0 ||\n        x === width - 1 ||\n        y === 0 ||\n        y === height - 1 ||\n        !shapeMask[idx - 1] ||\n        !shapeMask[idx + 1] ||\n        !shapeMask[idx - width] ||\n        !shapeMask[idx + width]\n      ) {\n        boundaryMask[idx] = 1;\n      }\n    }\n  }\n\n  const u = new Float32Array(size);\n  const ITERATIONS = 200;\n  const C = 0.01;\n  const omega = 1.85;\n\n  for (let iter = 0; iter < ITERATIONS; iter++) {\n    for (let y = 1; y < height - 1; y++) {\n      for (let x = 1; x < width - 1; x++) {\n        const idx = y * width + x;\n        if (!shapeMask[idx] || boundaryMask[idx]) continue;\n        const sum =\n          (shapeMask[idx + 1] ? u[idx + 1] : 0) +\n          (shapeMask[idx - 1] ? u[idx - 1] : 0) +\n          (shapeMask[idx + width] ? u[idx + width] : 0) +\n          (shapeMask[idx - width] ? u[idx - width] : 0);\n        const newVal = (C + sum) / 4;\n        u[idx] = omega * newVal + (1 - omega) * u[idx];\n      }\n    }\n  }\n\n  let maxVal = 0;\n  for (let i = 0; i < size; i++) if (u[i] > maxVal) maxVal = u[i];\n  if (maxVal === 0) maxVal = 1;\n\n  const outData = ctx.createImageData(width, height);\n  for (let i = 0; i < size; i++) {\n    const px = i * 4;\n    const depth = u[i] / maxVal;\n    const gray = Math.round(255 * (1 - depth * depth));\n    outData.data[px] = outData.data[px + 1] = outData.data[px + 2] = gray;\n    outData.data[px + 3] = Math.round(alphaValues[i] * 255);\n  }\n\n  return outData;\n}\n\nfunction hexToRgb(hex) {\n  const result = /^#?([a-f\\d]{2})([a-f\\d]{2})([a-f\\d]{2})$/i.exec(hex);\n  return result\n    ? [parseInt(result[1], 16) / 255, parseInt(result[2], 16) / 255, parseInt(result[3], 16) / 255]\n    : [1, 1, 1];\n}\n\nexport default function MetallicPaint({\n  imageSrc,\n  seed = 42,\n  scale = 4,\n  refraction = 0.01,\n  blur = 0.015,\n  liquid = 0.75,\n  speed = 0.3,\n  brightness = 2,\n  contrast = 0.5,\n  angle = 0,\n  fresnel = 1,\n  lightColor = '#ffffff',\n  darkColor = '#000000',\n  patternSharpness = 1,\n  waveAmplitude = 1,\n  noiseScale = 0.5,\n  chromaticSpread = 2,\n  mouseAnimation = false,\n  distortion = 1,\n  contour = 0.2,\n  tintColor = '#feb3ff'\n}) {\n  const canvasRef = useRef(null);\n  const glRef = useRef(null);\n  const programRef = useRef(null);\n  const uniformsRef = useRef({});\n  const textureRef = useRef(null);\n  const animTimeRef = useRef(0);\n  const lastTimeRef = useRef(0);\n  const rafRef = useRef(null);\n  const imgDataRef = useRef(null);\n  const speedRef = useRef(speed);\n  const mouseRef = useRef({ x: 0.5, y: 0.5, targetX: 0.5, targetY: 0.5 });\n  const mouseAnimRef = useRef(mouseAnimation);\n\n  const [ready, setReady] = useState(false);\n  const [textureReady, setTextureReady] = useState(false);\n\n  useEffect(() => {\n    speedRef.current = speed;\n  }, [speed]);\n  useEffect(() => {\n    mouseAnimRef.current = mouseAnimation;\n  }, [mouseAnimation]);\n\n  const initGL = useCallback(() => {\n    const canvas = canvasRef.current;\n    if (!canvas) return false;\n\n    const gl = canvas.getContext('webgl2', { antialias: true, alpha: true });\n    if (!gl) return false;\n\n    const compile = (src, type) => {\n      const s = gl.createShader(type);\n      gl.shaderSource(s, src);\n      gl.compileShader(s);\n      if (!gl.getShaderParameter(s, gl.COMPILE_STATUS)) {\n        console.error(gl.getShaderInfoLog(s));\n        return null;\n      }\n      return s;\n    };\n\n    const vs = compile(vertexShader, gl.VERTEX_SHADER);\n    const fs = compile(fragmentShader, gl.FRAGMENT_SHADER);\n    if (!vs || !fs) return false;\n\n    const prog = gl.createProgram();\n    gl.attachShader(prog, vs);\n    gl.attachShader(prog, fs);\n    gl.linkProgram(prog);\n    if (!gl.getProgramParameter(prog, gl.LINK_STATUS)) {\n      console.error(gl.getProgramInfoLog(prog));\n      return false;\n    }\n\n    const uniforms = {};\n    const count = gl.getProgramParameter(prog, gl.ACTIVE_UNIFORMS);\n    for (let i = 0; i < count; i++) {\n      const info = gl.getActiveUniform(prog, i);\n      if (info) uniforms[info.name] = gl.getUniformLocation(prog, info.name);\n    }\n\n    const verts = new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]);\n    const buf = gl.createBuffer();\n    gl.bindBuffer(gl.ARRAY_BUFFER, buf);\n    gl.bufferData(gl.ARRAY_BUFFER, verts, gl.STATIC_DRAW);\n\n    gl.useProgram(prog);\n    const pos = gl.getAttribLocation(prog, 'a_position');\n    gl.enableVertexAttribArray(pos);\n    gl.vertexAttribPointer(pos, 2, gl.FLOAT, false, 0, 0);\n\n    glRef.current = gl;\n    programRef.current = prog;\n    uniformsRef.current = uniforms;\n\n    return true;\n  }, []);\n\n  const uploadTexture = useCallback(imgData => {\n    const gl = glRef.current;\n    const uniforms = uniformsRef.current;\n    if (!gl || !imgData) return;\n\n    if (textureRef.current) gl.deleteTexture(textureRef.current);\n\n    const tex = gl.createTexture();\n    gl.activeTexture(gl.TEXTURE0);\n    gl.bindTexture(gl.TEXTURE_2D, tex);\n    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);\n    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);\n    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);\n    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);\n    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, imgData.width, imgData.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, imgData.data);\n    gl.uniform1i(uniforms.u_tex, 0);\n\n    const ratio = imgData.width / imgData.height;\n    gl.uniform1f(uniforms.u_imgRatio, ratio);\n    gl.uniform1f(uniforms.u_ratio, 1);\n\n    textureRef.current = tex;\n    imgDataRef.current = imgData;\n  }, []);\n\n  useEffect(() => {\n    if (!initGL()) return;\n\n    const canvas = canvasRef.current;\n    const gl = glRef.current;\n    const side = 1000 * devicePixelRatio;\n    canvas.width = side;\n    canvas.height = side;\n    gl.viewport(0, 0, side, side);\n\n    setReady(true);\n\n    return () => {\n      if (rafRef.current) cancelAnimationFrame(rafRef.current);\n      if (textureRef.current && glRef.current) {\n        glRef.current.deleteTexture(textureRef.current);\n      }\n    };\n  }, [initGL]);\n\n  useEffect(() => {\n    if (!ready || !imageSrc) return;\n\n    setTextureReady(false);\n    const img = new Image();\n    img.crossOrigin = 'anonymous';\n    img.onload = () => {\n      const imgData = processImage(img);\n      uploadTexture(imgData);\n      setTextureReady(true);\n    };\n    img.src = imageSrc;\n  }, [ready, imageSrc, uploadTexture]);\n\n  useEffect(() => {\n    const gl = glRef.current;\n    const u = uniformsRef.current;\n    if (!gl || !ready) return;\n\n    gl.uniform1f(u.u_seed, seed);\n    gl.uniform1f(u.u_scale, scale);\n    gl.uniform1f(u.u_refract, refraction);\n    gl.uniform1f(u.u_blur, blur);\n    gl.uniform1f(u.u_liquid, liquid);\n    gl.uniform1f(u.u_bright, brightness);\n    gl.uniform1f(u.u_contrast, contrast);\n    gl.uniform1f(u.u_angle, angle);\n    gl.uniform1f(u.u_fresnel, fresnel);\n\n    const light = hexToRgb(lightColor);\n    const dark = hexToRgb(darkColor);\n    const tint = hexToRgb(tintColor);\n    gl.uniform3f(u.u_lightColor, light[0], light[1], light[2]);\n    gl.uniform3f(u.u_darkColor, dark[0], dark[1], dark[2]);\n    gl.uniform1f(u.u_sharp, patternSharpness);\n    gl.uniform1f(u.u_wave, waveAmplitude);\n    gl.uniform1f(u.u_noise, noiseScale);\n    gl.uniform1f(u.u_chroma, chromaticSpread);\n    gl.uniform1f(u.u_distort, distortion);\n    gl.uniform1f(u.u_contour, contour);\n    gl.uniform3f(u.u_tint, tint[0], tint[1], tint[2]);\n  }, [\n    ready,\n    seed,\n    scale,\n    refraction,\n    blur,\n    liquid,\n    brightness,\n    contrast,\n    angle,\n    fresnel,\n    lightColor,\n    darkColor,\n    patternSharpness,\n    waveAmplitude,\n    noiseScale,\n    chromaticSpread,\n    distortion,\n    contour,\n    tintColor\n  ]);\n\n  useEffect(() => {\n    if (!ready || !textureReady) return;\n\n    const gl = glRef.current;\n    const u = uniformsRef.current;\n    const canvas = canvasRef.current;\n    const mouse = mouseRef.current;\n\n    const handleMouseMove = e => {\n      const rect = canvas.getBoundingClientRect();\n      mouse.targetX = (e.clientX - rect.left) / rect.width;\n      mouse.targetY = (e.clientY - rect.top) / rect.height;\n    };\n\n    canvas.addEventListener('mousemove', handleMouseMove);\n\n    const render = time => {\n      const delta = time - lastTimeRef.current;\n      lastTimeRef.current = time;\n\n      if (mouseAnimRef.current) {\n        mouse.x += (mouse.targetX - mouse.x) * 0.08;\n        mouse.y += (mouse.targetY - mouse.y) * 0.08;\n        animTimeRef.current = mouse.x * 3000 + mouse.y * 1500;\n      } else {\n        animTimeRef.current += delta * speedRef.current;\n      }\n\n      gl.uniform1f(u.u_time, animTimeRef.current);\n      gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);\n      rafRef.current = requestAnimationFrame(render);\n    };\n\n    lastTimeRef.current = performance.now();\n    rafRef.current = requestAnimationFrame(render);\n\n    return () => {\n      if (rafRef.current) cancelAnimationFrame(rafRef.current);\n      canvas.removeEventListener('mousemove', handleMouseMove);\n    };\n  }, [ready, textureReady]);\n\n  return <canvas ref={canvasRef} className=\"paint-container\" />;\n}\n"
		}
	],
	"registryDependencies": [],
	"dependencies": []
}