{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "project-gallery",
  "title": "Project Gallery",
  "description": "A Project Gallery with an interactive modal.",
  "dependencies": [
    "motion"
  ],
  "files": [
    {
      "path": "registry/react/atlasui/project-gallery.tsx",
      "content": "\"use client\";\n\nimport React, {\n  Dispatch,\n  ReactNode,\n  SetStateAction,\n  useEffect,\n  useRef,\n  useState,\n} from \"react\";\n\nimport Image from \"next/image\";\nimport Link from \"next/link\";\n\nimport { motion, useMotionValue, useSpring } from \"motion/react\";\n\ninterface ProjectGalleryProps {\n  children: ReactNode;\n}\n\ninterface BaseProjectProps {\n  title: string;\n  subtitle: string;\n  href: string;\n  imgSrc: string;\n  color: string;\n}\n\ninterface InternalProjectProps extends BaseProjectProps {\n  index: number;\n  setModal: Dispatch<SetStateAction<{ active: boolean; index: number }>>;\n}\n\ninterface ModalProps {\n  modal: { active: boolean; index: number };\n  projects: BaseProjectProps[];\n  containerRef: React.RefObject<HTMLDivElement | null>;\n}\n\nconst ProjectGallery = ({ children }: ProjectGalleryProps) => {\n  const [modal, setModal] = useState({ active: false, index: 0 });\n  const containerRef = useRef<HTMLDivElement>(null);\n\n  return (\n    <div\n      ref={containerRef}\n      className=\"relative grid grid-cols-1 sm:grid-cols-2 md:block\"\n    >\n      {children &&\n        React.Children.map(children, (child, index) => {\n          if (React.isValidElement<BaseProjectProps>(child)) {\n            return React.cloneElement(\n              child as React.ReactElement<InternalProjectProps>,\n              {\n                index,\n                setModal,\n              }\n            );\n          }\n          return child;\n        })}\n      <Modal\n        modal={modal}\n        projects={React.Children.toArray(children)\n          .map((child) => {\n            if (React.isValidElement<BaseProjectProps>(child)) {\n              const { title, subtitle, imgSrc, color, href } = child.props;\n              return { title, subtitle, imgSrc, color, href };\n            }\n            return null;\n          })\n          .filter((project): project is BaseProjectProps => project !== null)}\n        containerRef={containerRef}\n      />\n    </div>\n  );\n};\n\nconst Project = (props: BaseProjectProps | InternalProjectProps) => {\n  const { title, subtitle, href, imgSrc, color, index, setModal } =\n    props as InternalProjectProps;\n\n  return (\n    <Link href={href}>\n      <div\n        className=\"border-primary group flex w-full cursor-pointer flex-col justify-between p-4 transition-all duration-200 ease-linear md:flex-row md:items-center md:border-t md:p-24 md:hover:opacity-40\"\n        onMouseEnter={() => setModal?.({ active: true, index })}\n        onMouseLeave={() => setModal?.({ active: false, index })}\n      >\n        <div\n          style={{ backgroundColor: color }}\n          className=\"relative mb-4 aspect-4/3 w-full overflow-hidden rounded-lg md:hidden\"\n        >\n          <Image\n            src={imgSrc}\n            alt={title}\n            fill\n            className=\"object-contain p-4\"\n            sizes=\"(max-width: 768px) 100vw\"\n          />\n        </div>\n\n        <h2 className=\"text-2xl font-bold transition-all duration-200 ease-linear md:text-6xl md:font-normal md:group-hover:-translate-x-3\">\n          {title}\n        </h2>\n        <p className=\"text-muted-foreground md:text-foreground text-xs font-light transition-all duration-200 ease-linear md:text-base md:group-hover:translate-x-3\">\n          {subtitle}\n        </p>\n      </div>\n    </Link>\n  );\n};\n\nconst Modal = ({ modal, projects, containerRef }: ModalProps) => {\n  const { index, active } = modal;\n  const modalRef = useRef(null);\n  const cursorRef = useRef(null);\n  const cursorLabelRef = useRef(null);\n\n  const mouse = {\n    x: useMotionValue(0),\n    y: useMotionValue(0),\n  };\n\n  const cursor = {\n    _x: useMotionValue(0),\n    _y: useMotionValue(0),\n  };\n\n  const mouseMove = (e: any) => {\n    if (containerRef.current) {\n      const rect = containerRef.current.getBoundingClientRect();\n      const styles = getComputedStyle(containerRef.current);\n      const paddingLeft = parseFloat(styles.paddingLeft) || 0;\n      const paddingTop = parseFloat(styles.paddingTop) || 0;\n      const borderLeftWidth = parseFloat(styles.borderLeftWidth) || 0;\n      const borderTopWidth = parseFloat(styles.borderTopWidth) || 0;\n      const x = e.clientX - rect.left - paddingLeft - borderLeftWidth;\n      const y = e.clientY - rect.top - paddingTop - borderTopWidth;\n      mouse.x.set(x);\n      mouse.y.set(y);\n    }\n  };\n\n  const cursorMove = (e: any) => {\n    if (containerRef.current) {\n      const rect = containerRef.current.getBoundingClientRect();\n      const styles = getComputedStyle(containerRef.current);\n      const paddingLeft = parseFloat(styles.paddingLeft) || 0;\n      const paddingTop = parseFloat(styles.paddingTop) || 0;\n      const borderLeftWidth = parseFloat(styles.borderLeftWidth) || 0;\n      const borderTopWidth = parseFloat(styles.borderTopWidth) || 0;\n      const x = e.clientX - rect.left - paddingLeft - borderLeftWidth;\n      const y = e.clientY - rect.top - paddingTop - borderTopWidth;\n      cursor._x.set(x);\n      cursor._y.set(y);\n    }\n  };\n\n  const smoothOptions = {\n    damping: 40,\n    stiffness: 300,\n    mass: 0.5,\n  };\n\n  const cursorMoveSmoothOptions = {\n    damping: 20,\n    stiffness: 300,\n    mass: 0.5,\n  };\n\n  const smoothMouse = {\n    x: useSpring(mouse.x, smoothOptions),\n    y: useSpring(mouse.y, smoothOptions),\n  };\n\n  const smoothCursor = {\n    _x: useSpring(cursor._x, cursorMoveSmoothOptions),\n    _y: useSpring(cursor._y, cursorMoveSmoothOptions),\n  };\n\n  useEffect(() => {\n    window.addEventListener(\"mousemove\", mouseMove);\n    window.addEventListener(\"mousemove\", cursorMove);\n\n    return () => {\n      window.removeEventListener(\"mousemove\", mouseMove);\n      window.removeEventListener(\"mousemove\", cursorMove);\n    };\n  }, []);\n\n  const scaleAnimation = {\n    initial: {\n      scale: 0,\n      x: \"-50%\",\n      y: \"-50%\",\n    },\n    open: {\n      scale: 1,\n      x: \"-50%\",\n      y: \"-50%\",\n      transition: {\n        duration: 0.4,\n        ease: [0.76, 0, 0.24, 1] as [number, number, number, number],\n      },\n    },\n    closed: {\n      scale: 0,\n      x: \"-50%\",\n      y: \"-50%\",\n      transition: {\n        duration: 0.4,\n        ease: [0.32, 0, 0.67, 0] as [number, number, number, number],\n      },\n    },\n  };\n\n  return (\n    <>\n      <motion.div\n        variants={scaleAnimation}\n        initial={\"initial\"}\n        animate={active ? \"open\" : \"closed\"}\n        ref={modalRef}\n        className=\"pointer-events-none absolute hidden h-87.5 w-100 items-center justify-center overflow-hidden md:flex\"\n        style={{\n          left: smoothMouse.x,\n          top: smoothMouse.y,\n        }}\n      >\n        <div\n          className=\"ease-[cubic-bezier(0.76, 0, 0.24, 1)] absolute h-full w-full transition-[top] duration-500\"\n          style={{\n            top: index * -100 + \"%\",\n          }}\n        >\n          {projects.map((project, i) => {\n            const { title, color, imgSrc } = project;\n            return (\n              <div\n                key={i}\n                style={{\n                  backgroundColor: color,\n                }}\n                className=\"relative flex h-full items-center justify-center\"\n              >\n                <Image\n                  src={imgSrc}\n                  width={350}\n                  height={0}\n                  alt={title}\n                  className=\"h-auto\"\n                />\n              </div>\n            );\n          })}\n        </div>\n      </motion.div>\n\n      <motion.div\n        variants={scaleAnimation}\n        initial={\"initial\"}\n        animate={active ? \"open\" : \"closed\"}\n        ref={cursorRef}\n        className=\"pointer-events-none absolute hidden h-20 w-20 items-center justify-center rounded-full bg-blue-600 md:flex\"\n        style={{\n          left: smoothCursor._x,\n          top: smoothCursor._y,\n        }}\n      />\n      <motion.div\n        variants={scaleAnimation}\n        initial={\"initial\"}\n        animate={active ? \"open\" : \"closed\"}\n        ref={cursorLabelRef}\n        className=\"text-primary pointer-events-none absolute hidden h-20 w-20 items-center justify-center rounded-full bg-transparent md:flex\"\n        style={{\n          left: smoothCursor._x,\n          top: smoothCursor._y,\n        }}\n      >\n        View\n      </motion.div>\n    </>\n  );\n};\n\nexport { ProjectGallery, Project };\n",
      "type": "registry:component"
    }
  ],
  "type": "registry:component"
}