{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"curve-media","type":"registry:component","title":"Curve Media","description":"An image or video that bends as you scroll.","meta":{"pro":false},"docs":"Usage: https://atelier-ui.com/r/curve-media.md\nDocs: https://atelier-ui.com/docs/components/scroll/curve-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/smooth-scroll.json","https://atelier-ui.com/r/agent-rules.json"],"files":[{"path":"registry/curve-media/curve-media.tsx","type":"registry:component","target":"components/curve-media/curve-media.tsx","content":"\"use client\"\n\nimport { shaderMaterial } from \"@react-three/drei\"\nimport { extend, type ThreeElement, useFrame } from \"@react-three/fiber\"\nimport { useEffect, useRef } from \"react\"\nimport { MathUtils, Texture } from \"three\"\nimport { WebglImage } from \"../webgl-image/webgl-image\"\nimport { WebglVideo } from \"../webgl-video/webgl-video\"\n\ndeclare module \"@react-three/fiber\" {\n    interface ThreeElements {\n        curveMediaMat: ThreeElement<typeof CurveMediaMat>\n    }\n}\n\nconst vertexShader = /* glsl */ `\n    precision highp float;\n    varying vec2 vUv;\n    uniform float uVelocity;\n    uniform float uAmplitude;\n\n    const float PI = 3.14159265;\n\n    void main() {\n        vUv = uv;\n        vec3 pos = position;\n\n        float bend = sin(uv.x * PI);\n\n        pos.y += bend * uVelocity * uAmplitude;\n\n        gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0);\n    }\n`\n\nconst fragmentShader = /* glsl */ `\n    precision highp float;\n    varying vec2 vUv;\n    uniform sampler2D uMap;\n    uniform float uVelocity;\n    uniform float uAberration;\n\n    void main() {\n        float shift = uVelocity * uAberration;\n        vec2 offset = vec2(0.0, shift);\n\n        float r = texture2D(uMap, vUv + offset).r;\n        float g = texture2D(uMap, vUv).g;\n        float b = texture2D(uMap, vUv - offset).b;\n\n        gl_FragColor = vec4(r, g, b, 1.0);\n    }\n`\n\nconst CurveMediaMat = shaderMaterial(\n    {\n        uMap: new Texture(),\n        uVelocity: 0,\n        uAmplitude: 0,\n        uAberration: 0,\n    },\n    vertexShader,\n    fragmentShader,\n)\n\nextend({ CurveMediaMat })\n\nexport type CurveEffectProps = {\n    amplitude?: number\n    aberration?: number\n    smoothing?: number\n    segments?: number\n    webglEnabled?: boolean\n    postProcessing?: boolean\n}\n\ntype CurveMediaMaterialProps = {\n    map: Texture\n} & Required<Pick<CurveEffectProps, \"amplitude\" | \"aberration\" | \"smoothing\">>\n\ntype CurveMediaImageProps = CurveEffectProps & {\n    type?: \"image\"\n    src: string\n    alt: string\n} & Omit<React.ComponentPropsWithoutRef<\"img\">, \"src\" | \"alt\">\n\ntype CurveMediaVideoProps = CurveEffectProps & {\n    type: \"video\"\n    src: string\n} & Omit<React.ComponentPropsWithoutRef<\"video\">, \"src\">\n\nexport type CurveMediaProps = CurveMediaImageProps | CurveMediaVideoProps\n\nfunction CurveMediaMaterial({ map, amplitude, aberration, smoothing }: CurveMediaMaterialProps) {\n    const ref = useRef<InstanceType<typeof CurveMediaMat>>(null)\n    const lastScrollY = useRef(0)\n    const velocity = useRef(0)\n\n    useEffect(() => {\n        lastScrollY.current = window.scrollY\n    }, [])\n\n    useFrame((_, delta) => {\n        const material = ref.current\n        if (!material) return\n        const current = window.scrollY\n        const instantDelta = current - lastScrollY.current\n        lastScrollY.current = current\n\n        if (delta === 0) return\n        const target = instantDelta / delta / window.innerHeight\n        velocity.current = MathUtils.damp(velocity.current, target, smoothing, delta)\n        material.uVelocity = velocity.current\n    })\n\n    return (\n        <curveMediaMat\n            ref={ref}\n            key={CurveMediaMat.key}\n            uMap={map}\n            uAmplitude={amplitude}\n            uAberration={aberration}\n            transparent\n        />\n    )\n}\n\nexport function CurveMedia(props: CurveMediaProps) {\n    const {\n        amplitude = 0.05,\n        aberration = 0.03,\n        smoothing = 20,\n        segments = 32,\n        webglEnabled = true,\n        ...rest\n    } = props\n\n    // Scroll-velocity is read from the window each frame, so the same material\n    // runs on both the image and video primitives (they share the WebGL plane contract).\n    const material = (map: Texture) => (\n        <CurveMediaMaterial\n            map={map}\n            amplitude={amplitude}\n            aberration={aberration}\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"}]}