{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "Ferrofluid-JS-CSS", "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.css", "content": ".ferrofluid-container {\n position: relative;\n width: 100%;\n height: 100%;\n overflow: hidden;\n}\n" }, { "type": "registry:component", "path": "Ferrofluid/Ferrofluid.jsx", "content": "import React, { useEffect, useRef } from 'react';\nimport { Renderer, Program, Mesh, Triangle } from 'ogl';\nimport './Ferrofluid.css';\n\nconst MAX_COLORS = 8;\n\nconst hexToRGB = hex => {\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 => {\n const base = (input && input.length ? input : ['#4F46E5', '#06B6D4', '#E0F2FE']).slice(0, MAX_COLORS);\n const count = base.length;\n const arr = [];\n for (let i = 0; i < MAX_COLORS; i++) arr.push(hexToRGB(base[Math.min(i, base.length - 1)]));\n const avg = [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 => {\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 = ({\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(null);\n const rafRef = useRef(null);\n const programRef = useRef(null);\n const meshRef = useRef(null);\n const geometryRef = useRef(null);\n const rendererRef = useRef(null);\n const mouseTargetRef = useRef([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;\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 => {\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 => {\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;\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, key) => {\n const fn = obj && obj[key];\n if (typeof fn === 'function') {\n fn.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
\n );\n};\n\nexport default Ferrofluid;\n" } ], "registryDependencies": [], "dependencies": [ "ogl@^1.0.11" ] }