{
	"$schema": "https://ui.shadcn.com/schema/registry-item.json",
	"name": "Ferrofluid-TS-TW",
	"title": "Ferrofluid",
	"description": "A churning magnetic fluid traced by glowing contour lines, with a cursor magnet.",
	"type": "registry:component",
	"files": [
		{
			"type": "registry:component",
			"path": "Ferrofluid/Ferrofluid.tsx",
			"content": "import React, { useEffect, useRef } from 'react';\nimport { Renderer, Program, Mesh, Triangle } from 'ogl';\n\nexport interface FerrofluidProps {\n  className?: string;\n  dpr?: number;\n  paused?: boolean;\n  colors?: string[];\n  speed?: number;\n  scale?: number;\n  turbulence?: number;\n  fluidity?: number;\n  rimWidth?: number;\n  sharpness?: number;\n  shimmer?: number;\n  glow?: number;\n  flowDirection?: 'up' | 'down' | 'left' | 'right';\n  opacity?: number;\n  mouseInteraction?: boolean;\n  mouseStrength?: number;\n  mouseRadius?: number;\n  mouseDampening?: number;\n  mixBlendMode?: string;\n}\n\ntype RGB = [number, number, number];\n\nconst MAX_COLORS = 8;\n\nconst hexToRGB = (hex: string): RGB => {\n  const c = hex.replace('#', '').padEnd(6, '0');\n  const r = parseInt(c.slice(0, 2), 16) / 255;\n  const g = parseInt(c.slice(2, 4), 16) / 255;\n  const b = parseInt(c.slice(4, 6), 16) / 255;\n  return [r, g, b];\n};\n\nconst prepColors = (input?: string[]) => {\n  const base = (input && input.length ? input : ['#4F46E5', '#06B6D4', '#E0F2FE']).slice(0, MAX_COLORS);\n  const count = base.length;\n  const arr: RGB[] = [];\n  for (let i = 0; i < MAX_COLORS; i++) arr.push(hexToRGB(base[Math.min(i, base.length - 1)]));\n  const avg: RGB = [0, 0, 0];\n  for (let i = 0; i < count; i++) {\n    avg[0] += arr[i][0];\n    avg[1] += arr[i][1];\n    avg[2] += arr[i][2];\n  }\n  avg[0] /= count;\n  avg[1] /= count;\n  avg[2] /= count;\n  return { arr, count, avg };\n};\n\nconst flowVec = (d?: string): [number, number] => {\n  switch (d) {\n    case 'up':\n      return [0, 1];\n    case 'down':\n      return [0, -1];\n    case 'left':\n      return [-1, 0];\n    case 'right':\n      return [1, 0];\n    default:\n      return [0, -1];\n  }\n};\n\nconst vertex = `\nattribute vec2 position;\nattribute vec2 uv;\nvarying vec2 vUv;\nvoid main() {\n  vUv = uv;\n  gl_Position = vec4(position, 0.0, 1.0);\n}\n`;\n\nconst fragment = `\nprecision highp float;\n\nuniform vec3  iResolution;\nuniform vec2  iMouse;\nuniform float iTime;\n\nuniform vec3  uColor0;\nuniform vec3  uColor1;\nuniform vec3  uColor2;\nuniform vec3  uColor3;\nuniform vec3  uColor4;\nuniform vec3  uColor5;\nuniform vec3  uColor6;\nuniform vec3  uColor7;\nuniform int   uColorCount;\n\nuniform vec3  uMouseColor;\nuniform vec2  uFlow;\nuniform float uSpeed;\nuniform float uScale;\nuniform float uTurbulence;\nuniform float uFluidity;\nuniform float uRimWidth;\nuniform float uSharpness;\nuniform float uShimmer;\nuniform float uGlow;\nuniform float uOpacity;\nuniform float uMouseEnabled;\nuniform float uMouseStrength;\nuniform float uMouseRadius;\n\nvarying vec2 vUv;\n\n#define PI 3.14159265\n\nvec3 palette(float h) {\n  int count = uColorCount;\n  if (count < 1) count = 1;\n  int idx = int(floor(clamp(h, 0.0, 0.999999) * float(count)));\n  if (idx <= 0) return uColor0;\n  if (idx == 1) return uColor1;\n  if (idx == 2) return uColor2;\n  if (idx == 3) return uColor3;\n  if (idx == 4) return uColor4;\n  if (idx == 5) return uColor5;\n  if (idx == 6) return uColor6;\n  return uColor7;\n}\n\nfloat hash(vec3 p3) {\n  p3 = fract(p3 * 0.1031);\n  p3 += dot(p3, p3.zyx + 33.33);\n  return fract((p3.x + p3.y) * p3.z);\n}\n\nfloat smin(float a, float b, float k) {\n  float r = exp2(-a / k) + exp2(-b / k);\n  return -k * log2(r);\n}\n\nfloat sinlerp(float a, float b, float w) {\n  return mix(a, b, (sin(w * PI - PI / 2.0) + 1.0) / 2.0);\n}\n\nfloat vn(vec2 p, float s, float seed) {\n  vec2 cellp = floor(p / s);\n  vec2 relp = mod(p, s);\n  float g1 = hash(vec3(cellp, seed));\n  float g2 = hash(vec3(cellp.x + 1.0, cellp.y, seed));\n  float g3 = hash(vec3(cellp.x + 1.0, cellp.y + 1.0, seed));\n  float g4 = hash(vec3(cellp.x, cellp.y + 1.0, seed));\n  float bx = sinlerp(g1, g2, relp.x / s);\n  float tx = sinlerp(g4, g3, relp.x / s);\n  return sinlerp(bx, tx, relp.y / s);\n}\n\nfloat dbn(vec2 p, float s, float seed) {\n  float o = s / 2.0;\n  float n0 = vn(p, s, seed);\n  float n1 = vn(p + vec2(o, o), s, seed + 0.1);\n  float n2 = vn(p + vec2(-o, o), s, seed + 0.2);\n  float n3 = vn(p + vec2(o, -o), s, seed + 0.3);\n  float n4 = vn(p + vec2(-o, -o), s, seed + 0.4);\n  return (2.0 * n0 + 1.5 * n1 + 1.25 * n2 + 1.125 * n3 + n4) / 7.0;\n}\n\nvoid mainImage(out vec4 fragColor, in vec2 fragCoord) {\n  float ref = 700.0 / max(uScale, 0.05);\n  vec2 p = fragCoord / iResolution.y * ref;\n\n  float spd = 200.0 * uSpeed;\n  float t = iTime;\n\n  vec2 dir = uFlow;\n  vec2 perp = vec2(-dir.y, dir.x);\n\n  float distort1 = vn(p + perp * (t * spd), 60.0, 10.0) * 50.0 * uTurbulence;\n  float distort2 = vn(p - perp * (t * spd), 120.0, 15.0) * 100.0 * uTurbulence;\n\n  float peaks = dbn(p + distort1 + dir * (t * spd * 0.5), 40.0, 1.0);\n  float peaks2 = dbn(p + distort2 - dir * (t * spd * 0.5), 40.0, 0.0);\n\n  float mapeaks = smin(peaks, peaks2, max(uFluidity, 0.001));\n\n  float mGlow = 0.0;\n  if (uMouseEnabled > 0.5) {\n    vec2 mp = iMouse / iResolution.y * ref;\n    float md = length(p - mp) / ref;\n    float rr = max(uMouseRadius, 0.02);\n    mGlow = exp(-md * md / (rr * rr)) * uMouseStrength;\n  }\n\n  float band = (uRimWidth - abs((mapeaks - 0.4) * 2.0)) * 5.0;\n  float ltn = clamp(band - vn(p + dir * (t * spd * 0.5), 60.0, 12.0) * uShimmer, 0.0, 1.0);\n  ltn = pow(ltn, uSharpness) * uGlow;\n  ltn *= clamp(1.0 - mGlow, 0.0, 1.0);\n\n  float h = clamp(0.5 + (peaks - peaks2) * 0.8, 0.0, 1.0);\n  vec3 col = palette(h);\n\n  vec3 outc = col * ltn;\n  float a = clamp(max(outc.r, max(outc.g, outc.b)), 0.0, 1.0);\n  fragColor = vec4(outc, a * uOpacity);\n}\n\nvoid main() {\n  vec4 color;\n  mainImage(color, vUv * iResolution.xy);\n  gl_FragColor = color;\n}\n`;\n\nconst Ferrofluid: React.FC<FerrofluidProps> = ({\n  className,\n  dpr,\n  paused = false,\n  colors = ['#ffffff', '#ffffff', '#ffffff'],\n  speed = 0.5,\n  scale = 1.6,\n  turbulence = 1,\n  fluidity = 0.1,\n  rimWidth = 0.2,\n  sharpness = 2.5,\n  shimmer = 1.5,\n  glow = 2,\n  flowDirection = 'down',\n  opacity = 1,\n  mouseInteraction = true,\n  mouseStrength = 1,\n  mouseRadius = 0.35,\n  mouseDampening = 0.15,\n  mixBlendMode\n}) => {\n  const containerRef = useRef<HTMLDivElement | null>(null);\n  const rafRef = useRef<number | null>(null);\n  const programRef = useRef<Program | null>(null);\n  const meshRef = useRef<Mesh | null>(null);\n  const geometryRef = useRef<Triangle | null>(null);\n  const rendererRef = useRef<Renderer | null>(null);\n  const mouseTargetRef = useRef<[number, number]>([0, 0]);\n  const lastTimeRef = useRef(0);\n\n  useEffect(() => {\n    const container = containerRef.current;\n    if (!container) return;\n\n    const renderer = new Renderer({\n      dpr: dpr ?? (typeof window !== 'undefined' ? window.devicePixelRatio || 1 : 1),\n      alpha: true,\n      antialias: true\n    });\n    rendererRef.current = renderer;\n    const gl = renderer.gl;\n    const canvas = gl.canvas as HTMLCanvasElement;\n    gl.clearColor(0, 0, 0, 0);\n    canvas.style.width = '100%';\n    canvas.style.height = '100%';\n    canvas.style.display = 'block';\n    container.appendChild(canvas);\n\n    const { arr, count, avg } = prepColors(colors);\n\n    const uniforms = {\n      iResolution: { value: [gl.drawingBufferWidth, gl.drawingBufferHeight, 1] },\n      iMouse: { value: [0, 0] },\n      iTime: { value: 0 },\n      uColor0: { value: arr[0] },\n      uColor1: { value: arr[1] },\n      uColor2: { value: arr[2] },\n      uColor3: { value: arr[3] },\n      uColor4: { value: arr[4] },\n      uColor5: { value: arr[5] },\n      uColor6: { value: arr[6] },\n      uColor7: { value: arr[7] },\n      uColorCount: { value: count },\n      uMouseColor: { value: avg },\n      uFlow: { value: flowVec(flowDirection) },\n      uSpeed: { value: speed },\n      uScale: { value: scale },\n      uTurbulence: { value: turbulence },\n      uFluidity: { value: fluidity },\n      uRimWidth: { value: rimWidth },\n      uSharpness: { value: sharpness },\n      uShimmer: { value: shimmer },\n      uGlow: { value: glow },\n      uOpacity: { value: opacity },\n      uMouseEnabled: { value: mouseInteraction ? 1 : 0 },\n      uMouseStrength: { value: mouseStrength },\n      uMouseRadius: { value: mouseRadius }\n    };\n\n    const program = new Program(gl, { vertex, fragment, uniforms });\n    programRef.current = program;\n\n    const geometry = new Triangle(gl);\n    geometryRef.current = geometry;\n    const mesh = new Mesh(gl, { geometry, program });\n    meshRef.current = mesh;\n\n    const resize = () => {\n      const rect = container.getBoundingClientRect();\n      renderer.setSize(rect.width, rect.height);\n      uniforms.iResolution.value = [gl.drawingBufferWidth, gl.drawingBufferHeight, 1];\n    };\n\n    resize();\n    const ro = new ResizeObserver(resize);\n    ro.observe(container);\n\n    const onPointerMove = (e: PointerEvent) => {\n      const rect = canvas.getBoundingClientRect();\n      const sc = renderer.dpr || 1;\n      const x = (e.clientX - rect.left) * sc;\n      const y = (rect.height - (e.clientY - rect.top)) * sc;\n      mouseTargetRef.current = [x, y];\n      if (mouseDampening <= 0) {\n        uniforms.iMouse.value = [x, y];\n      }\n    };\n    if (mouseInteraction) {\n      canvas.addEventListener('pointermove', onPointerMove);\n    }\n\n    const loop = (t: number) => {\n      rafRef.current = requestAnimationFrame(loop);\n      uniforms.iTime.value = t * 0.001;\n      if (mouseDampening > 0) {\n        if (!lastTimeRef.current) lastTimeRef.current = t;\n        const dt = (t - lastTimeRef.current) / 1000;\n        lastTimeRef.current = t;\n        const tau = Math.max(1e-4, mouseDampening);\n        let factor = 1 - Math.exp(-dt / tau);\n        if (factor > 1) factor = 1;\n        const target = mouseTargetRef.current;\n        const cur = uniforms.iMouse.value as number[];\n        cur[0] += (target[0] - cur[0]) * factor;\n        cur[1] += (target[1] - cur[1]) * factor;\n      } else {\n        lastTimeRef.current = t;\n      }\n      if (!paused && programRef.current && meshRef.current) {\n        try {\n          renderer.render({ scene: meshRef.current });\n        } catch (e) {\n          console.error(e);\n        }\n      }\n    };\n    rafRef.current = requestAnimationFrame(loop);\n\n    return () => {\n      if (rafRef.current) cancelAnimationFrame(rafRef.current);\n      if (mouseInteraction) canvas.removeEventListener('pointermove', onPointerMove);\n      ro.disconnect();\n      if (canvas.parentElement === container) {\n        container.removeChild(canvas);\n      }\n      const callIfFn = (obj: unknown, key: string) => {\n        const fn = obj && (obj as Record<string, unknown>)[key];\n        if (typeof fn === 'function') {\n          (fn as () => void).call(obj);\n        }\n      };\n      callIfFn(programRef.current, 'remove');\n      callIfFn(geometryRef.current, 'remove');\n      callIfFn(meshRef.current, 'remove');\n      callIfFn(rendererRef.current, 'destroy');\n      programRef.current = null;\n      geometryRef.current = null;\n      meshRef.current = null;\n      rendererRef.current = null;\n    };\n  }, [\n    dpr,\n    paused,\n    colors,\n    speed,\n    scale,\n    turbulence,\n    fluidity,\n    rimWidth,\n    sharpness,\n    shimmer,\n    glow,\n    flowDirection,\n    opacity,\n    mouseInteraction,\n    mouseStrength,\n    mouseRadius,\n    mouseDampening\n  ]);\n\n  return (\n    <div\n      ref={containerRef}\n      className={`w-full h-full overflow-hidden relative ${className ?? ''}`}\n      style={{\n        ...(mixBlendMode && { mixBlendMode: mixBlendMode as React.CSSProperties['mixBlendMode'] })\n      }}\n    />\n  );\n};\n\nexport default Ferrofluid;\n"
		}
	],
	"registryDependencies": [],
	"dependencies": [
		"ogl@^1.0.11"
	]
}