{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "aperture-mask-reveal",
  "description": "A camera-aperture mask reveal — six shutter blades twist open around the center, dilating a hexagonal iris cut from an inline SVG mask.",
  "dependencies": [
    "gsap"
  ],
  "files": [
    {
      "path": "registry/new-york/mask/aperture-mask-reveal.tsx",
      "content": "\"use client\";\n\n/**\n * A camera-aperture mask reveal — six shutter blades twist open around the center, dilating a hexagonal iris cut from an inline SVG mask.\n */\n\nimport { useEffect, useId, useRef } from \"react\";\nimport gsap from \"gsap\";\n\nconst SRC = \"/bend-image-reveal.gif\";\n\nconst CX = 320;\nconst CY = 180;\nconst PIVOT_R = 400; // blade pivots sit on a circle just outside the frame\nconst BLADE_SIZE = 820;\nconst MAX_OPEN = 72; // deg — 400·sin(72°) clears the frame corners\nconst MAX_SPIN = 33; // the whole assembly twists as it opens\n\nconst BLADES = Array.from({ length: 6 }, (_, i) => {\n  const theta = (i * Math.PI) / 3;\n  return {\n    px: +(CX + PIVOT_R * Math.cos(theta)).toFixed(2),\n    py: +(CY + PIVOT_R * Math.sin(theta)).toFixed(2),\n    base: i * 60 + 180, // blade edge points from its pivot through the center\n  };\n});\n\nconst ApertureMaskReveal = () => {\n  const bladeRefs = useRef<(SVGGElement | null)[]>([]);\n  const assemblyRef = useRef<SVGGElement>(null);\n  const imageRef = useRef<SVGImageElement>(null);\n  const tlRef = useRef<gsap.core.Timeline | null>(null);\n  const maskId = `aperture-${useId().replace(/:/g, \"\")}`;\n\n  const reveal = () => {\n    const image = imageRef.current;\n    if (!image) return;\n\n    const state = { open: 0, spin: 0 };\n    const apply = () => {\n      assemblyRef.current?.setAttribute(\n        \"transform\",\n        `rotate(${state.spin} ${CX} ${CY})`,\n      );\n      BLADES.forEach((b, i) => {\n        bladeRefs.current[i]?.setAttribute(\n          \"transform\",\n          `translate(${b.px} ${b.py}) rotate(${b.base + state.open})`,\n        );\n      });\n    };\n\n    tlRef.current?.kill();\n    const tl = gsap.timeline({ onUpdate: apply });\n    tlRef.current = tl;\n    tl.to(state, {\n      open: MAX_OPEN,\n      spin: MAX_SPIN,\n      duration: 1.5,\n      ease: \"power3.inOut\",\n    });\n    tl.fromTo(\n      image,\n      { scale: 1.35, transformOrigin: \"50% 50%\", filter: \"blur(14px)\" },\n      { scale: 1, filter: \"blur(0px)\", duration: 1.5, ease: \"power3.inOut\" },\n      \"<\",\n    );\n  };\n\n  useEffect(() => {\n    reveal();\n  }, []);\n\n  return (\n    <button\n      onClick={reveal}\n      className=\"relative aspect-video w-[40rem] max-w-[92%] cursor-pointer overflow-hidden rounded-xl\"\n      aria-label=\"Replay reveal\"\n    >\n      <svg\n        viewBox=\"0 0 640 360\"\n        preserveAspectRatio=\"xMidYMid slice\"\n        className=\"size-full\"\n        aria-hidden\n      >\n        <defs>\n          <mask id={maskId}>\n            <rect width=\"640\" height=\"360\" fill=\"#fff\" />\n            <g ref={assemblyRef}>\n              {BLADES.map((b, i) => (\n                <g\n                  key={i}\n                  ref={(el) => {\n                    bladeRefs.current[i] = el;\n                  }}\n                  transform={`translate(${b.px} ${b.py}) rotate(${b.base})`}\n                >\n                  <rect width={BLADE_SIZE} height={BLADE_SIZE} fill=\"#000\" />\n                </g>\n              ))}\n            </g>\n          </mask>\n        </defs>\n        <g mask={`url(#${maskId})`}>\n          <image\n            ref={imageRef}\n            href={SRC}\n            width=\"640\"\n            height=\"360\"\n            preserveAspectRatio=\"xMidYMid slice\"\n          />\n        </g>\n      </svg>\n    </button>\n  );\n};\n\nexport default ApertureMaskReveal;\n",
      "type": "registry:component",
      "target": "components/pixel-perfect/aperture-mask-reveal.tsx"
    }
  ],
  "type": "registry:block"
}