{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "three-d-carousel",
  "type": "registry:ui",
  "description": "3D carousel component with perspective effects and smooth item transitions",
  "dependencies": [
    "motion"
  ],
  "files": [
    {
      "path": "registry/default/ui/three-d-carousel.tsx",
      "content": "\"use client\"\n\nimport { memo, useEffect, useLayoutEffect, useMemo, useState } from \"react\"\nimport {\n  AnimatePresence,\n  motion,\n  useAnimation,\n  useMotionValue,\n  useTransform,\n} from \"motion/react\"\n\nexport const useIsomorphicLayoutEffect =\n  typeof window !== \"undefined\" ? useLayoutEffect : useEffect\n\ntype UseMediaQueryOptions = {\n  defaultValue?: boolean\n  initializeWithValue?: boolean\n}\n\nconst IS_SERVER = typeof window === \"undefined\"\n\nexport function useMediaQuery(\n  query: string,\n  {\n    defaultValue = false,\n    initializeWithValue = true,\n  }: UseMediaQueryOptions = {}\n): boolean {\n  const getMatches = (query: string): boolean => {\n    if (IS_SERVER) {\n      return defaultValue\n    }\n    return window.matchMedia(query).matches\n  }\n\n  const [matches, setMatches] = useState<boolean>(() => {\n    if (initializeWithValue) {\n      return getMatches(query)\n    }\n    return defaultValue\n  })\n\n  const handleChange = () => {\n    setMatches(getMatches(query))\n  }\n\n  useIsomorphicLayoutEffect(() => {\n    const matchMedia = window.matchMedia(query)\n    handleChange()\n\n    matchMedia.addEventListener(\"change\", handleChange)\n\n    return () => {\n      matchMedia.removeEventListener(\"change\", handleChange)\n    }\n  }, [query])\n\n  return matches\n}\n\nconst keywords = [\n  \"night\",\n  \"city\",\n  \"sky\",\n  \"sunset\",\n  \"sunrise\",\n  \"winter\",\n  \"skyscraper\",\n  \"building\",\n  \"cityscape\",\n  \"architecture\",\n  \"street\",\n  \"lights\",\n  \"downtown\",\n  \"bridge\",\n]\n\nconst duration = 0.15\nconst transition = {\n  duration,\n  ease: [0.32, 0.72, 0, 1] as const,\n  filter: \"blur(4px)\",\n}\nconst transitionOverlay = { duration: 0.5, ease: [0.32, 0.72, 0, 1] as const }\n\nconst Carousel = memo(\n  ({\n    handleClick,\n    controls,\n    cards,\n    isCarouselActive,\n  }: {\n    handleClick: (imgUrl: string, index: number) => void\n    controls: any\n    cards: string[]\n    isCarouselActive: boolean\n  }) => {\n    const isScreenSizeSm = useMediaQuery(\"(max-width: 640px)\")\n    const cylinderWidth = isScreenSizeSm ? 1100 : 1800\n    const faceCount = cards.length\n    const faceWidth = cylinderWidth / faceCount\n    const radius = cylinderWidth / (2 * Math.PI)\n    const rotation = useMotionValue(0)\n    const transform = useTransform(\n      rotation,\n      (value) => `rotate3d(0, 1, 0, ${value}deg)`\n    )\n\n    return (\n      <div\n        className=\"bg-mauve-dark-2 flex h-full items-center justify-center\"\n        style={{\n          perspective: \"1000px\",\n          transformStyle: \"preserve-3d\",\n          willChange: \"transform\",\n        }}\n      >\n        <motion.div\n          drag={isCarouselActive ? \"x\" : false}\n          className=\"relative flex h-full origin-center cursor-grab justify-center active:cursor-grabbing\"\n          style={{\n            transform,\n            rotateY: rotation,\n            width: cylinderWidth,\n            transformStyle: \"preserve-3d\",\n          }}\n          onDrag={(_, info) =>\n            isCarouselActive &&\n            rotation.set(rotation.get() + info.offset.x * 0.05)\n          }\n          onDragEnd={(_, info) =>\n            isCarouselActive &&\n            controls.start({\n              rotateY: rotation.get() + info.velocity.x * 0.05,\n              transition: {\n                type: \"spring\" as const,\n                stiffness: 100,\n                damping: 30,\n                mass: 0.1,\n              },\n            })\n          }\n          animate={controls}\n        >\n          {cards.map((imgUrl, i) => (\n            <motion.div\n              key={`key-${imgUrl}-${i}`}\n              className=\"bg-mauve-dark-2 absolute flex h-full origin-center items-center justify-center rounded-xl p-2\"\n              style={{\n                width: `${faceWidth}px`,\n                transform: `rotateY(${\n                  i * (360 / faceCount)\n                }deg) translateZ(${radius}px)`,\n              }}\n              onClick={() => handleClick(imgUrl, i)}\n            >\n              <motion.img\n                src={imgUrl}\n                alt={`keyword_${i} ${imgUrl}`}\n                layoutId={`img-${imgUrl}`}\n                className=\"pointer-events-none aspect-square w-full rounded-xl object-cover\"\n                initial={{ filter: \"blur(4px)\" }}\n                layout=\"position\"\n                animate={{ filter: \"blur(0px)\" }}\n                transition={transition}\n              />\n            </motion.div>\n          ))}\n        </motion.div>\n      </div>\n    )\n  }\n)\n\nconst hiddenMask = `repeating-linear-gradient(to right, rgba(0,0,0,0) 0px, rgba(0,0,0,0) 30px, rgba(0,0,0,1) 30px, rgba(0,0,0,1) 30px)`\nconst visibleMask = `repeating-linear-gradient(to right, rgba(0,0,0,0) 0px, rgba(0,0,0,0) 0px, rgba(0,0,0,1) 0px, rgba(0,0,0,1) 30px)`\nfunction ThreeDPhotoCarousel() {\n  const [activeImg, setActiveImg] = useState<string | null>(null)\n  const [isCarouselActive, setIsCarouselActive] = useState(true)\n  const controls = useAnimation()\n  const cards = useMemo(\n    () => keywords.map((keyword) => `https://picsum.photos/200/300?${keyword}`),\n    []\n  )\n\n  useEffect(() => {\n    console.log(\"Cards loaded:\", cards)\n  }, [cards])\n\n  const handleClick = (imgUrl: string) => {\n    setActiveImg(imgUrl)\n    setIsCarouselActive(false)\n    controls.stop()\n  }\n\n  const handleClose = () => {\n    setActiveImg(null)\n    setIsCarouselActive(true)\n  }\n\n  return (\n    <motion.div layout className=\"relative\">\n      <AnimatePresence mode=\"sync\">\n        {activeImg && (\n          <motion.div\n            initial={{ opacity: 0, scale: 0 }}\n            animate={{ opacity: 1, scale: 1 }}\n            exit={{ opacity: 0, scale: 0 }}\n            layoutId={`img-container-${activeImg}`}\n            layout=\"position\"\n            onClick={handleClose}\n            className=\"bg-opacity-10 fixed inset-0 z-50 m-5 flex items-center justify-center rounded-3xl bg-black md:m-36 lg:mx-[19rem]\"\n            style={{ willChange: \"opacity\" }}\n            transition={transitionOverlay}\n          >\n            <motion.img\n              layoutId={`img-${activeImg}`}\n              src={activeImg}\n              className=\"max-h-full max-w-full rounded-lg shadow-lg\"\n              initial={{ scale: 0.5 }} // Start with a smaller scale\n              animate={{ scale: 1 }} // Animate to full scale\n              transition={{\n                delay: 0.5,\n                duration: 0.5,\n                ease: [0.25, 0.1, 0.25, 1] as const,\n              }} // Clean ease-out curve\n              style={{\n                willChange: \"transform\",\n              }}\n            />\n          </motion.div>\n        )}\n      </AnimatePresence>\n      <div className=\"relative h-[500px] w-full overflow-hidden\">\n        <Carousel\n          handleClick={handleClick}\n          controls={controls}\n          cards={cards}\n          isCarouselActive={isCarouselActive}\n        />\n      </div>\n    </motion.div>\n  )\n}\n\nexport default ThreeDPhotoCarousel\n",
      "type": "registry:ui"
    }
  ]
}