{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"displacement-hover","type":"registry:block","title":"displacement hover","description":"\"gallery images swap with 34\" — one image liquefies into another through a noise map. The signature Codrops hover.","author":"Motion Menu","categories":["role-background","shader","glsl"],"dependencies":["three"],"registryDependencies":["@motionmenu/motion-menu-runtime"],"meta":{"number":"34","slug":"displacement-hover","framework":"react","docs":"https://motion-menu-two.vercel.app/p/displacement-hover.md"},"files":[{"path":"motion-menu/displacement-hover.html","type":"registry:file","target":"motion-menu/displacement-hover.html","content":"<article class=\"card\">\n        <header><span class=\"num\">34</span><h3>Displacement hover</h3><div class=\"tags\"><i>GLSL</i></div><a class=\"format-action\" data-format-link data-pattern-slug=\"displacement-hover\" href=\"/r/displacement-hover.json?framework=react\" title=\"Open the React registry output\">React ↗</a></header>\n        <div class=\"demo\"><canvas class=\"gl\" id=\"gl-disp\"></canvas><p class=\"demo-hint\">hover to melt between images</p></div>\n        <p class=\"order\">Say: <b>\"gallery images swap with 34\"</b> — one image liquefies into another through a noise map. The signature Codrops hover.</p>\n      </article>"},{"path":"motion-menu/displacement-hover.css","type":"registry:file","target":"motion-menu/displacement-hover.css","content":".card {\n    border: 1px solid var(--line); border-radius: 14px; background: var(--card);\n    overflow: hidden; display: flex; flex-direction: column;\n  }\n\n.card > header { display: flex; align-items: baseline; gap: .8rem; padding: 1.1rem 1.2rem .9rem; border-bottom: 1px solid var(--line); }\n\n.card .num { font-family: var(--mono); color: var(--acc); font-size: 1.05rem; }\n\n.card h3 { font-size: 1.02rem; font-weight: 600; letter-spacing: -0.01em; flex: 1; }\n\n.tags { display: flex; gap: .35rem; }\n\n.tags i {\n    font: 400 .62rem/1 var(--mono); font-style: normal; color: var(--dim);\n    border: 1px solid var(--line); border-radius: 99px; padding: .28em .6em; white-space: nowrap;\n  }\n\n.demo {\n    min-height: 250px; position: relative; display: flex; align-items: center; justify-content: center;\n    background: var(--bg2); overflow: hidden; flex: 1;\n  }\n\n.order { padding: .85rem 1.2rem; font-family: var(--mono); font-size: .72rem; color: var(--dim); border-top: 1px solid var(--line); }\n\n.order b { color: var(--txt); font-weight: 500; }\n\n.demo .btn.corner { position: absolute; top: 12px; right: 12px; padding: .55em 1em; font-size: .68rem; z-index: 5; }\n\ncanvas.gl { width: 100%; height: 100%; display: block; position: absolute; inset: 0; }\n\n.demo-hint { position: absolute; bottom: 10px; left: 0; right: 0; text-align: center; font: 400 .65rem var(--mono); color: var(--dim); pointer-events: none; z-index: 4; }\n\n.ai-card .num { color: var(--acc2) !important; }\n\n.demo:has(.hascheck input:checked) .hasbtn { border-color: var(--acc); color: var(--acc); pointer-events: auto; opacity: 1; }"},{"path":"components/motion-menu/displacement-hover.tsx","type":"registry:component","content":"// @ts-nocheck — generated wrapper; inlined pattern JS is imperative DOM code, not authored TS.\n\"use client\";\n// displacement hover — Motion Menu\n// Calls the Motion Menu shared runtime (webglDemo / canvas2dDemo / art helpers).\n// Install `motion-menu-runtime` and mount it once at the app root, or the effect\n// below throws ReferenceError on first paint.\nimport { useEffect, useRef } from \"react\";\n\nconst MARKUP = \"<article class=\\\"card\\\">\\n        <header><span class=\\\"num\\\">34</span><h3>Displacement hover</h3><div class=\\\"tags\\\"><i>GLSL</i></div><a class=\\\"format-action\\\" data-format-link data-pattern-slug=\\\"displacement-hover\\\" href=\\\"/r/displacement-hover.json?framework=react\\\" title=\\\"Open the React registry output\\\">React \\u2197</a></header>\\n        <div class=\\\"demo\\\"><canvas class=\\\"gl\\\" id=\\\"gl-disp\\\"></canvas><p class=\\\"demo-hint\\\">hover to melt between images</p></div>\\n        <p class=\\\"order\\\">Say: <b>\\\"gallery images swap with 34\\\"</b> \\u2014 one image liquefies into another through a noise map. The signature Codrops hover.</p>\\n      </article>\";\n\nexport function DisplacementHover() {\n  const hostRef = useRef<HTMLDivElement>(null);\n\n  useEffect(() => {\n    const host = hostRef.current;\n    if (!host) return;\n    const $ = (s: string) => host.querySelector(s) as HTMLElement | null;\n    let disposed = false;\n    const cleanup: Array<() => void> = [];\n\n    (async () => {\n      if (disposed) return;\n      try {\n        /* ---- 34 · displacement hover ---- */\n        webglDemo('gl-disp', ({ scene, camera, canvas }) => {\n          camera.position.z = 9.6;\n          const mat = new THREE.ShaderMaterial({\n            uniforms: {\n              tA: { value: artTexture(2) }, tB: { value: artTexture(3) },\n              tDisp: { value: noiseTexture() }, uProg: { value: 0 },\n            },\n            vertexShader: QUAD_VERT,\n            fragmentShader: `\n              precision mediump float;\n              uniform sampler2D tA; uniform sampler2D tB; uniform sampler2D tDisp;\n              uniform float uProg; varying vec2 vUv;\n              void main() {\n                float d = texture2D(tDisp, vUv).r;\n                vec2 uvA = vUv + vec2(uProg * d * 0.5, 0.0);\n                vec2 uvB = vUv - vec2((1.0 - uProg) * d * 0.5, 0.0);\n                gl_FragColor = mix(texture2D(tA, uvA), texture2D(tB, uvB), uProg);\n              }`,\n          });\n          scene.add(new THREE.Mesh(FS_QUAD, mat));\n          const p = { current: 0, target: 0 };\n          canvas.parentElement.addEventListener('mouseenter', () => (p.target = 1));\n          canvas.parentElement.addEventListener('mouseleave', () => (p.target = 0));\n          return { update(t, delta) {\n            const f = 1 - Math.pow(1 - 0.07, delta * 60);\n            p.current += (p.target - p.current) * f;\n            mat.uniforms.uProg.value = p.current;\n          }};\n        });\n      } catch (err: any) {\n        console.warn(\"[motion-menu] displacement-hover failed to start:\", err);\n      }\n    })();\n\n    return () => {\n      disposed = true;\n      cleanup.forEach((fn) => { try { fn(); } catch (_) {} });\n    };\n  }, []);\n\n  return <div ref={hostRef} dangerouslySetInnerHTML={{ __html: MARKUP }} />;\n}\n\nexport default DisplacementHover;\n"}]}