{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"lens-media","type":"registry:component","title":"Lens Media","description":"An image or video with a cursor-following lens.","meta":{"pro":false},"docs":"Usage: https://atelier-ui.com/r/lens-media.md\nDocs: https://atelier-ui.com/docs/components/cursor/lens-media","dependencies":["three","@types/three","@react-three/fiber","@react-three/drei"],"registryDependencies":["https://atelier-ui.com/r/webgl-image.json","https://atelier-ui.com/r/webgl-video.json","https://atelier-ui.com/r/agent-rules.json"],"files":[{"path":"registry/lens-media/lens-media.tsx","type":"registry:component","target":"components/lens-media/lens-media.tsx","content":"\"use client\"\n\nimport { shaderMaterial } from \"@react-three/drei\"\nimport { extend, type ThreeElement, useFrame } from \"@react-three/fiber\"\nimport { useLayoutEffect, useRef } from \"react\"\nimport { type Group, MathUtils, SRGBColorSpace, Texture, Vector2, VideoTexture } from \"three\"\nimport { type Pointer, WebglImage } from \"../webgl-image/webgl-image\"\nimport { WebglVideo } from \"../webgl-video/webgl-video\"\n\ndeclare module \"@react-three/fiber\" {\n    interface ThreeElements {\n        lensMediaMat: ThreeElement<typeof LensMediaMat>\n    }\n}\n\nconst vertexShader = /* glsl */ `\n    precision highp float;\n    varying vec2 vUv;\n\n    void main() {\n        vUv = uv;\n        gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\n    }\n`\n\nconst fragmentShader = /* glsl */ `\n    precision highp float;\n    #define MAX_DISPERSION 64\n    varying vec2 vUv;\n\n    uniform sampler2D uMap;\n    uniform vec2 uMouse;\n    uniform float uAspect;\n    uniform float uSize;\n    uniform float uSoftness;\n    uniform float uAberration;\n    uniform float uRefraction;\n    uniform float uHover;\n    uniform int uDispersion;\n    uniform float uDecodeVideo;\n\n    vec3 sampleMap(vec2 uv) {\n        vec4 texel = texture2D(uMap, uv);\n        return uDecodeVideo > 0.5 ? sRGBTransferEOTF(texel).rgb : texel.rgb;\n    }\n\n    void main() {\n        vec2 toCenter = vUv - uMouse;\n\n        vec2 aspectDistance = vec2(toCenter.x * uAspect, toCenter.y);\n        float radius = length(aspectDistance);\n        float mask = (1.0 - smoothstep(uSize, uSize + uSoftness, radius)) * uHover;\n\n        vec2 refracted = vUv - toCenter * uRefraction * mask;\n\n        vec2 shift = toCenter * uAberration * mask;\n        vec3 color = vec3(0.0);\n        vec3 total = vec3(0.0);\n\n        for (int i = 0; i < MAX_DISPERSION; i++) {\n            if (i >= uDispersion) break;\n            float t = (float(i) + 0.5) / float(uDispersion);\n            vec3 weight = clamp(1.0 - 2.0 * abs(t - vec3(0.0, 0.5, 1.0)), 0.0, 1.0);\n            color += sampleMap(refracted + shift * (1.0 - 2.0 * t)) * weight;\n            total += weight;\n        }\n\n        gl_FragColor = vec4(color / max(total, vec3(0.0001)), 1.0);\n        #include <colorspace_fragment>\n    }\n`\n\nconst LensMediaMat = shaderMaterial(\n    {\n        uMap: new Texture(),\n        uMouse: new Vector2(0.5, 0.5),\n        uAspect: 1,\n        uSize: 0.12,\n        uSoftness: 0.5,\n        uAberration: 0.18,\n        uRefraction: 0.4,\n        uHover: 0,\n        uDispersion: 35,\n        uDecodeVideo: 0,\n    },\n    vertexShader,\n    fragmentShader,\n)\n\nextend({ LensMediaMat })\n\ntype LensMediaMaterialProps = {\n    map: Texture\n    pointer: Pointer\n} & Required<\n    Pick<\n        LensEffectProps,\n        \"size\" | \"softness\" | \"aberration\" | \"refraction\" | \"dispersion\" | \"smoothing\"\n    >\n>\n\n// Effect props shared by both the image and video variants.\nexport type LensEffectProps = {\n    size?: number\n    softness?: number\n    aberration?: number\n    refraction?: number\n    dispersion?: number\n    smoothing?: number\n    segments?: number\n    webglEnabled?: boolean\n    postProcessing?: boolean\n    autoReflow?: boolean\n}\n\ntype LensMediaImageProps = LensEffectProps & {\n    type?: \"image\"\n    src: string\n    alt: string\n} & Omit<React.ComponentPropsWithoutRef<\"img\">, \"src\" | \"alt\">\n\ntype LensMediaVideoProps = LensEffectProps & {\n    type: \"video\"\n    src: string\n} & Omit<React.ComponentPropsWithoutRef<\"video\">, \"src\">\n\nexport type LensMediaProps = LensMediaImageProps | LensMediaVideoProps\n\nfunction LensMediaMaterial({\n    map,\n    pointer,\n    size,\n    softness,\n    aberration,\n    refraction,\n    dispersion,\n    smoothing,\n}: LensMediaMaterialProps) {\n    const ref = useRef<InstanceType<typeof LensMediaMat>>(null)\n    const anchorRef = useRef<Group>(null)\n\n    // The shader re-encodes to sRGB, so the image must always be decoded as sRGB,\n    // whether or not another material already flagged this cached texture.\n    useLayoutEffect(() => {\n        const texture = ref.current?.uMap\n        if (!texture || texture.colorSpace === SRGBColorSpace) return\n        texture.colorSpace = SRGBColorSpace\n        texture.needsUpdate = true\n    }, [map])\n\n    useFrame((_, delta) => {\n        const material = ref.current\n        if (!material) return\n\n        const mouse = material.uMouse\n        mouse.x = MathUtils.damp(mouse.x, pointer.texUv.x, smoothing, delta)\n        mouse.y = MathUtils.damp(mouse.y, pointer.texUv.y, smoothing, delta)\n        material.uHover = MathUtils.damp(material.uHover, pointer.hover, smoothing, delta)\n\n        const parent = anchorRef.current?.parent\n        if (parent) {\n            const aspect = parent.scale.x / parent.scale.y\n            material.uAspect = (aspect * pointer.repeat.y) / pointer.repeat.x\n        }\n    })\n\n    return (\n        <>\n            <group ref={anchorRef} />\n\n            <lensMediaMat\n                ref={ref}\n                key={LensMediaMat.key}\n                uMap={map}\n                uSize={size}\n                uSoftness={softness}\n                uAberration={aberration}\n                uRefraction={refraction}\n                uDispersion={dispersion}\n                uDecodeVideo={map instanceof VideoTexture ? 1 : 0}\n                transparent\n            />\n        </>\n    )\n}\n\nexport function LensMedia(props: LensMediaProps) {\n    const {\n        size = 0.12,\n        softness = 0.5,\n        aberration = 0.18,\n        refraction = 0.4,\n        dispersion = 35,\n        smoothing = 7,\n        segments = 1,\n        webglEnabled = true,\n        ...rest\n    } = props\n\n    // The lens is a fragment-only shader, so the same material runs on both\n    // the image and video primitives (they share the WebGL plane contract).\n    const material = (map: Texture, pointer: Pointer) => (\n        <LensMediaMaterial\n            map={map}\n            pointer={pointer}\n            size={size}\n            softness={softness}\n            aberration={aberration}\n            refraction={refraction}\n            dispersion={dispersion}\n            smoothing={smoothing}\n        />\n    )\n\n    if (rest.type === \"video\") {\n        const { type: _type, ...videoProps } = rest\n        return (\n            <WebglVideo\n                segments={segments}\n                webglEnabled={webglEnabled}\n                material={material}\n                {...videoProps}\n            />\n        )\n    }\n\n    const { type: _type, ...imageProps } = rest\n    return (\n        <WebglImage\n            segments={segments}\n            webglEnabled={webglEnabled}\n            material={material}\n            {...imageProps}\n        />\n    )\n}\n"}]}