{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"pixel-media","type":"registry:component","title":"Pixel Media","description":"An image or video that breaks into pixels under the cursor.","meta":{"pro":false},"docs":"Usage: https://atelier-ui.com/r/pixel-media.md\nDocs: https://atelier-ui.com/docs/components/cursor/pixel-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/pixel-media/pixel-media.tsx","type":"registry:component","target":"components/pixel-media/pixel-media.tsx","content":"\"use client\"\n\nimport { shaderMaterial } from \"@react-three/drei\"\nimport { extend, type ThreeElement, useFrame } from \"@react-three/fiber\"\nimport { useMemo, useRef } from \"react\"\nimport { DataTexture, FloatType, NearestFilter, RGBAFormat, Texture, Vector2 } 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        pixelMediaMat: ThreeElement<typeof PixelMediaMat>\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    varying vec2 vUv;\n\n    uniform sampler2D uMap;\n    uniform sampler2D uGrid;\n    uniform float uStrength;\n    uniform float uAberration;\n\n    void main() {\n        vec2 offset = texture2D(uGrid, vUv).rg * uStrength;\n        vec2 uv = vUv - offset;\n        vec2 shift = offset * uAberration;\n\n        gl_FragColor = vec4(\n            texture2D(uMap, uv - shift).r,\n            texture2D(uMap, uv).g,\n            texture2D(uMap, uv + shift).b,\n            1.0\n        );\n    }\n`\n\nconst PixelMediaMat = shaderMaterial(\n    {\n        uMap: new Texture(),\n        uGrid: new Texture(),\n        uStrength: 0.5,\n        uAberration: 0.5,\n    },\n    vertexShader,\n    fragmentShader,\n)\n\nextend({ PixelMediaMat })\n\nconst REFERENCE_FPS = 60\n\nexport type PixelEffectProps = {\n    gridSize?: number\n    interactionRadius?: number\n    strength?: number\n    aberration?: number\n    trail?: number\n    segments?: number\n    webglEnabled?: boolean\n    postProcessing?: boolean\n}\n\ntype PixelMediaMaterialProps = {\n    map: Texture\n    pointer: Pointer\n} & Required<\n    Pick<PixelEffectProps, \"gridSize\" | \"interactionRadius\" | \"strength\" | \"aberration\" | \"trail\">\n>\n\ntype PixelMediaImageProps = PixelEffectProps & {\n    type?: \"image\"\n    src: string\n    alt: string\n} & Omit<React.ComponentPropsWithoutRef<\"img\">, \"src\" | \"alt\">\n\ntype PixelMediaVideoProps = PixelEffectProps & {\n    type: \"video\"\n    src: string\n} & Omit<React.ComponentPropsWithoutRef<\"video\">, \"src\">\n\nexport type PixelMediaProps = PixelMediaImageProps | PixelMediaVideoProps\n\nfunction PixelMediaMaterial({\n    map,\n    pointer,\n    gridSize,\n    interactionRadius,\n    strength,\n    aberration,\n    trail,\n}: PixelMediaMaterialProps) {\n    const ref = useRef<InstanceType<typeof PixelMediaMat>>(null)\n    const previous = useMemo(() => new Vector2(0.5, 0.5), [])\n    const wasHovering = useRef(false)\n\n    const grid = useMemo(() => {\n        const data = new Float32Array(gridSize * gridSize * 4)\n        const texture = new DataTexture(data, gridSize, gridSize, RGBAFormat, FloatType)\n        texture.magFilter = NearestFilter\n        texture.minFilter = NearestFilter\n        texture.needsUpdate = true\n        return { texture, data }\n    }, [gridSize])\n\n    useFrame((_, delta) => {\n        const material = ref.current\n        if (!material) return\n\n        const { data } = grid\n\n        if (pointer.hover > 0 && !wasHovering.current) previous.copy(pointer.texUv)\n        wasHovering.current = pointer.hover > 0\n\n        const velocityX = (pointer.texUv.x - previous.x) * pointer.hover\n        const velocityY = (pointer.texUv.y - previous.y) * pointer.hover\n        previous.copy(pointer.texUv)\n\n        const fade = trail ** (delta * REFERENCE_FPS)\n        const cursorColumn = pointer.texUv.x * gridSize\n        const cursorRow = pointer.texUv.y * gridSize\n\n        for (let row = 0; row < gridSize; row++) {\n            for (let column = 0; column < gridSize; column++) {\n                const index = (row * gridSize + column) * 4\n                const distance = Math.hypot(column - cursorColumn, row - cursorRow)\n                const falloff = Math.max(0, 1 - distance / interactionRadius)\n                data[index] = data[index] * fade + velocityX * falloff\n                data[index + 1] = data[index + 1] * fade + velocityY * falloff\n            }\n        }\n\n        grid.texture.needsUpdate = true\n        material.uMap = map\n        material.uGrid = grid.texture\n        material.uStrength = strength\n        material.uAberration = aberration\n    })\n\n    return (\n        <pixelMediaMat\n            ref={ref}\n            key={PixelMediaMat.key}\n            uMap={map}\n            uGrid={grid.texture}\n            transparent\n        />\n    )\n}\n\nexport function PixelMedia(props: PixelMediaProps) {\n    const {\n        gridSize = 22,\n        interactionRadius = 4,\n        strength = 1.65,\n        aberration = 0.25,\n        trail = 0.93,\n        segments = 1,\n        webglEnabled = true,\n        ...rest\n    } = props\n\n    // The pixel displacement is a fragment-only shader, so the same material\n    // runs on both the image and video primitives (they share the WebGL plane).\n    const material = (map: Texture, pointer: Pointer) => (\n        <PixelMediaMaterial\n            map={map}\n            pointer={pointer}\n            gridSize={gridSize}\n            interactionRadius={interactionRadius}\n            strength={strength}\n            aberration={aberration}\n            trail={trail}\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"}]}