{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"text-fluid","type":"registry:component","title":"Text Fluid","description":"Text with an animated shimmer and distortion.","meta":{"pro":false},"docs":"Usage: https://atelier-ui.com/r/text-fluid.md\nDocs: https://atelier-ui.com/docs/components/text/text-fluid","dependencies":["three","@types/three","@react-three/fiber","@react-three/drei"],"registryDependencies":["https://atelier-ui.com/r/webgl-text.json","https://atelier-ui.com/r/agent-rules.json"],"files":[{"path":"registry/text-fluid/text-fluid.tsx","type":"registry:component","target":"components/text-fluid/text-fluid.tsx","content":"\"use client\"\n\nimport { shaderMaterial } from \"@react-three/drei\"\nimport { extend, type ThreeElement, useFrame } from \"@react-three/fiber\"\nimport { useRef } from \"react\"\nimport { Texture } from \"three\"\nimport type { RenderProp } from \"../../hooks/use-render\"\nimport { WebglText } from \"../webgl-text/webgl-text\"\n\ndeclare module \"@react-three/fiber\" {\n    interface ThreeElements {\n        textFluidMat: ThreeElement<typeof TextFluidMat>\n    }\n}\n\nconst vertexShader = /* glsl */ `\nvarying vec2 vUv;\nuniform float uTime;\nuniform float uNoiseFrequency;\nuniform float uNoiseAmplitude;\n\nfloat rand(vec2 n) {\n    return fract(sin(dot(n, vec2(12.9898, 4.1414))) * 43758.5453);\n}\n\nfloat noise(vec2 p) {\n    vec2 ip = floor(p);\n    vec2 u = fract(p);\n    u = u * u * (3.0 - 2.0 * u);\n    float res = mix(\n        mix(rand(ip), rand(ip + vec2(1.0, 0.0)), u.x),\n        mix(rand(ip + vec2(0.0, 1.0)), rand(ip + vec2(1.0, 1.0)), u.x),\n        u.y\n    );\n    return res * res;\n}\n\nfloat fbm(vec2 x) {\n    float v = 0.0;\n    float a = 0.5;\n    vec2 shift = vec2(100.0);\n    mat2 rot = mat2(cos(0.5), sin(0.5), -sin(0.5), cos(0.5));\n    for (int i = 0; i < 6; ++i) {\n        v += a * noise(x);\n        x = rot * x * 2.0 + shift;\n        a *= 0.5;\n    }\n    return v;\n}\n\nvoid main() {\n    vUv = uv;\n    vec3 newPos = position;\n\n    float n = fbm(newPos.xy * uNoiseFrequency + uTime);\n    newPos.y += (n - 0.25) * uNoiseAmplitude * 50.0;\n    newPos.x += (n - 0.25) * uNoiseAmplitude * 100.0;\n\n    gl_Position = projectionMatrix * modelViewMatrix * vec4(newPos, 1.0);\n}\n`\n\nconst fragmentShader = /* glsl */ `\nprecision highp float;\n\nuniform sampler2D uTexture;\nuniform float uTime;\nuniform float uRippleStrength;\nuniform float uOpacity;\n\nvarying vec2 vUv;\n\nvoid main() {\n    vec2 uv = vUv;\n    uv.x += sin(vUv.y * 8.5 + uTime * 1.8) * uRippleStrength;\n    uv.y += cos(vUv.x * 8.5 + uTime * 1.4) * uRippleStrength;\n    uv = clamp(uv, 0.0, 1.0);\n\n    vec4 tex = texture2D(uTexture, uv);\n    if (tex.a < 0.01) discard;\n\n    gl_FragColor = vec4(tex.rgb, tex.a * uOpacity);\n}\n`\n\nconst TextFluidMat = shaderMaterial(\n    {\n        uTexture: new Texture(),\n        uTime: 0,\n        uRippleStrength: 0,\n        uNoiseFrequency: 0,\n        uNoiseAmplitude: 0,\n        uOpacity: 0,\n    },\n    vertexShader,\n    fragmentShader,\n)\n\nextend({ TextFluidMat })\n\nexport type DynamicNumber = number | (() => number)\nconst read = (v: DynamicNumber) => (typeof v === \"function\" ? v() : v)\n\nexport type TextFluidProps = {\n    children: string\n    speed?: number\n    ripple?: DynamicNumber\n    frequency?: DynamicNumber\n    amplitude?: DynamicNumber\n    opacity?: DynamicNumber\n    segments?: number\n    webglEnabled?: boolean\n    postProcessing?: boolean\n    render?: RenderProp\n}\n\ntype TextFluidMaterialProps = {\n    uTexture: Texture\n} & Required<Pick<TextFluidProps, \"speed\" | \"ripple\" | \"frequency\" | \"amplitude\" | \"opacity\">>\n\nfunction TextFluidMaterial({\n    uTexture,\n    speed,\n    ripple,\n    frequency,\n    amplitude,\n    opacity,\n}: TextFluidMaterialProps) {\n    const ref = useRef<InstanceType<typeof TextFluidMat>>(null)\n\n    useFrame(({ clock }) => {\n        const mat = ref.current\n        if (!mat) return\n        mat.uTime = clock.getElapsedTime() * speed\n        mat.uRippleStrength = read(ripple)\n        mat.uNoiseFrequency = read(frequency)\n        mat.uNoiseAmplitude = read(amplitude)\n        mat.uOpacity = read(opacity)\n    })\n\n    return (\n        <textFluidMat\n            ref={ref}\n            key={TextFluidMat.key}\n            attach=\"material\"\n            uTexture={uTexture}\n            transparent\n            depthWrite={false}\n        />\n    )\n}\n\nexport function TextFluid({\n    children,\n    speed = 0.2,\n    ripple = 0.015,\n    frequency = 1.5,\n    amplitude = 0.002,\n    opacity = 1,\n    segments = 20,\n    webglEnabled = true,\n    postProcessing = true,\n    render,\n}: TextFluidProps) {\n    return (\n        <WebglText\n            render={render}\n            segments={segments}\n            webglEnabled={webglEnabled}\n            postProcessing={postProcessing}\n            material={(uTexture) => (\n                <TextFluidMaterial\n                    uTexture={uTexture}\n                    speed={speed}\n                    ripple={ripple}\n                    frequency={frequency}\n                    amplitude={amplitude}\n                    opacity={opacity}\n                />\n            )}\n        >\n            {children}\n        </WebglText>\n    )\n}\n"},{"path":"registry/hooks/use-render.ts","type":"registry:hook","target":"hooks/use-render.ts","content":"// biome-ignore-all lint/suspicious/noExplicitAny: prop merging is inherently dynamic\n/**\n * Inspired by Base UI's `useRender` + `mergeProps`, intentionally simplified for this\n * library's scope at the moment.\n *\n * Chosen over polymorphic prop: cleaner TypeScript, integrates better with other\n * component (Next/Image, design systems, third-party UI libraries)\n *\n * @see https://base-ui.com/react/utils/use-render\n * @see https://base-ui.com/react/utils/merge-props\n */\nimport { cloneElement, isValidElement, type ReactElement, type Ref } from \"react\"\n\ntype AnyProps = Record<string, any>\n\ntype RenderFunction<S> = (props: AnyProps, state: S) => ReactElement\n\nexport type RenderProp<S = void> = ReactElement | RenderFunction<S>\n\ntype UseRenderOptions<S> = {\n    render: RenderProp<S> | undefined\n    props: AnyProps\n    state?: S\n    defaultElement: ReactElement\n}\n\nexport function useRender<S = void>(options: UseRenderOptions<S>): ReactElement<AnyProps> {\n    const { render, props, state, defaultElement } = options\n    const target = render ?? defaultElement\n\n    // Function form: consumer wires props themselves, no merging needed.\n    if (typeof target === \"function\") {\n        return target(props, state as S) as ReactElement<AnyProps>\n    }\n\n    // Element form: clone and merge our internal props with whatever the consumer set on the element.\n    const targetProps = (isValidElement(target) ? target.props : {}) as AnyProps\n    return cloneElement(target, mergeProps(props, targetProps)) as ReactElement<AnyProps>\n}\n\nfunction mergeProps(internal: AnyProps, external: AnyProps): AnyProps {\n    const merged: AnyProps = { ...internal }\n\n    for (const key in external) {\n        const internalValue = internal[key]\n        const externalValue = external[key]\n\n        if (key === \"className\" && typeof externalValue === \"string\") {\n            merged[key] = [internalValue, externalValue].filter(Boolean).join(\" \")\n        } else if (key === \"style\" && externalValue && typeof externalValue === \"object\") {\n            merged[key] = { ...internalValue, ...externalValue }\n        } else if (key === \"ref\") {\n            merged[key] = composeRefs(internalValue, externalValue)\n        } else if (\n            key.startsWith(\"on\") &&\n            typeof internalValue === \"function\" &&\n            typeof externalValue === \"function\"\n        ) {\n            // External handler runs first so consumers can stopPropagation before our logic fires.\n            merged[key] = chainFunctions(externalValue, internalValue)\n        } else {\n            merged[key] = externalValue\n        }\n    }\n\n    return merged\n}\n\nfunction chainFunctions(...fns: Array<(...args: any[]) => void>) {\n    return (...args: any[]) => {\n        for (const fn of fns) fn(...args)\n    }\n}\n\nfunction composeRefs<T>(...refs: Array<Ref<T> | undefined>) {\n    return (node: T) => {\n        for (const ref of refs) {\n            if (typeof ref === \"function\") ref(node)\n            else if (ref != null) (ref as { current: T | null }).current = node\n        }\n    }\n}\n"}]}