{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "linear-modal-standalone",
  "dependencies": [
    "motion",
    "lucide-react"
  ],
  "devDependencies": [],
  "registryDependencies": [],
  "files": [
    {
      "path": "./registry/components/linear-modal/standalone-version.tsx",
      "content": "'use client';\nimport { Plus, X } from 'lucide-react';\nimport { AnimatePresence, MotionConfig, type Transition, motion } from 'motion/react';\nimport type React from 'react';\nimport { useEffect, useRef, useState } from 'react';\nimport { createPortal } from 'react-dom';\n\n// Card item type\ninterface CardItem {\n  id: number;\n  url: string;\n  title: string;\n  description: string;\n  tags: string[];\n}\n\n// Animation config\nconst transition: Transition = {\n  type: 'spring',\n  bounce: 0.05,\n  duration: 0.3,\n};\n\nconst LinearCardDialog: React.FC = () => {\n  const [index, setIndex] = useState<number>(0);\n  const [isOpen, setIsOpen] = useState<boolean>(false);\n  const [carouselWidth, setCarouselWidth] = useState<number>(0);\n  const carousel = useRef<HTMLDivElement>(null);\n\n  // Default items (standalone, fixed)\n  const items: CardItem[] = [\n    {\n      id: 1,\n      url: 'https://images.unsplash.com/photo-1757672242146-a6a7897bcc80?q=80&w=1171&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D',\n      title: 'Accordion',\n      description: 'Immerse yourself in our cutting-edge interactive gallery...',\n      tags: ['Sunrise', 'Mountains', 'Golden', 'Scenic', 'Inspiring'],\n    },\n    {\n      id: 2,\n      url: 'https://images.unsplash.com/photo-1756806983725-977bb2308d4e?q=80&w=1170&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D',\n      title: 'Globe Section',\n      description:\n        'Embark on a virtual journey around the world with our state-of-the-art 3D globe feature...',\n      tags: ['Misty', 'Path', 'Mysterious', 'Serene', 'Rugged'],\n    },\n    {\n      id: 3,\n      url: 'https://images.unsplash.com/photo-1756806983832-1f056cf24182?q=80&w=1170&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D',\n      title: 'Image Mouse Trail',\n      description:\n        'Transform your browsing experience with our mesmerizing Image Mouse Trail feature...',\n      tags: ['Pathway', 'Adventure', 'Peaks', 'Challenging', 'Breathtaking'],\n    },\n  ];\n\n  useEffect(() => {\n    if (isOpen) {\n      document.body.classList.add('overflow-hidden');\n    } else {\n      document.body.classList.remove('overflow-hidden');\n    }\n\n    const handleKeyDown = (event: KeyboardEvent): void => {\n      if (event.key === 'Escape' && isOpen) {\n        handleCloseDialog();\n      }\n    };\n\n    document.addEventListener('keydown', handleKeyDown);\n    return () => {\n      document.removeEventListener('keydown', handleKeyDown);\n      document.body.classList.remove('overflow-hidden');\n    };\n  }, [isOpen]);\n\n  useEffect(() => {\n    if (carousel.current) {\n      const scrollWidth = carousel.current.scrollWidth;\n      const offsetWidth = carousel.current.offsetWidth;\n      setCarouselWidth(scrollWidth - offsetWidth);\n    }\n  }, [items]);\n\n  const handleCardClick = (itemIndex: number): void => {\n    setIndex(itemIndex);\n    setIsOpen(true);\n  };\n\n  const handleCloseDialog = (): void => {\n    setIsOpen(false);\n  };\n\n  const handleBackdropClick = (e: React.MouseEvent<HTMLDivElement>): void => {\n    if (e.target === e.currentTarget) {\n      handleCloseDialog();\n    }\n  };\n\n  const currentItem = items[index];\n\n  return (\n    <div className='relative h-full p-4'>\n      <MotionConfig transition={transition}>\n        <motion.div\n          ref={carousel}\n          drag='x'\n          dragElastic={0.2}\n          dragConstraints={{ right: 0, left: -carouselWidth }}\n          dragTransition={{ bounceDamping: 30 }}\n          className='flex w-full gap-4 py-10'\n        >\n          {items.map((item, i) => (\n            <motion.div\n              key={item.id}\n              className='shrink-0 flex relative flex-col overflow-hidden border dark:bg-black bg-neutral-100 hover:bg-neutral-200 dark:hover:bg-neutral-950 cursor-pointer transition-colors'\n              layoutId={`dialog-${item.id}`}\n              style={{ width: '250px', borderRadius: '12px' }}\n              tabIndex={i}\n              onClick={() => handleCardClick(i)}\n              onKeyDown={(e: React.KeyboardEvent) => {\n                if (e.key === 'Enter' || e.key === ' ') {\n                  e.preventDefault();\n                  handleCardClick(i);\n                }\n              }}\n              role='button'\n              aria-label={`Open ${item.title} details`}\n            >\n              <motion.div layoutId={`dialog-img-${item.id}`}>\n                <img\n                  src={item.url}\n                  alt={item.title}\n                  className='w-full h-48 object-cover'\n                  loading='lazy'\n                />\n              </motion.div>\n              <div className='flex grow flex-row items-end justify-between p-4'>\n                <div className='flex-1'>\n                  <motion.h3\n                    layoutId={`dialog-title-${item.id}`}\n                    className='dark:text-white text-black font-semibold text-sm truncate'\n                  >\n                    {item.title}\n                  </motion.h3>\n                </div>\n                <button\n                  className='absolute bottom-2 right-2 p-2 dark:bg-neutral-800 bg-neutral-300 hover:bg-neutral-400 dark:hover:bg-neutral-700 rounded-xl transition-colors'\n                  aria-label={`Open ${item.title}`}\n                  tabIndex={-1}\n                >\n                  <Plus className='w-4 h-4' />\n                </button>\n              </div>\n            </motion.div>\n          ))}\n        </motion.div>\n\n        {isOpen &&\n          currentItem &&\n          createPortal(\n            <AnimatePresence initial={false} mode='sync'>\n              <motion.div\n                key={`backdrop-${currentItem.id}`}\n                className='fixed inset-0 h-full w-full dark:bg-black/50 bg-black/25 backdrop-blur-xs z-40'\n                variants={{ open: { opacity: 1 }, closed: { opacity: 0 } }}\n                initial='closed'\n                animate='open'\n                exit='closed'\n                onClick={handleBackdropClick}\n              />\n              <motion.div\n                key='dialog'\n                className='pointer-events-none fixed inset-0 flex items-center justify-center z-50 p-4'\n              >\n                <motion.div\n                  className='pointer-events-auto relative flex h-auto w-full max-w-[500px] flex-col overflow-hidden dark:bg-neutral-900 bg-white border shadow-2xl max-h-[90vh] overflow-y-auto'\n                  layoutId={`dialog-${currentItem.id}`}\n                  tabIndex={-1}\n                  style={{ borderRadius: '24px' }}\n                  role='dialog'\n                  aria-labelledby={`dialog-title-${currentItem.id}`}\n                  aria-describedby={`dialog-description-${currentItem.id}`}\n                >\n                  <motion.div layoutId={`dialog-img-${currentItem.id}`}>\n                    <img\n                      src={currentItem.url}\n                      alt={currentItem.title}\n                      className='h-64 w-full object-cover'\n                    />\n                  </motion.div>\n                  <div className='p-6'>\n                    <motion.h2\n                      layoutId={`dialog-title-${currentItem.id}`}\n                      className='text-2xl font-bold text-zinc-950 dark:text-zinc-50 mb-4'\n                      id={`dialog-title-${currentItem.id}`}\n                    >\n                      {currentItem.title}\n                    </motion.h2>\n                    <motion.div\n                      initial={{ opacity: 0, y: 20 }}\n                      animate={{ opacity: 1, y: 0 }}\n                      exit={{ opacity: 0, y: 20 }}\n                      transition={{ delay: 0.1 }}\n                      className='text-zinc-700 dark:text-zinc-300 leading-relaxed'\n                      id={`dialog-description-${currentItem.id}`}\n                    >\n                      {currentItem.description}\n                    </motion.div>\n                    {currentItem.tags.length > 0 && (\n                      <div className='flex flex-wrap gap-2 mt-4'>\n                        {currentItem.tags.map((tag, tagIndex) => (\n                          <span\n                            key={`${tag}-${tagIndex}`}\n                            className='px-2 py-1 text-xs bg-neutral-200 dark:bg-neutral-800 text-neutral-700 dark:text-neutral-300 rounded-full'\n                          >\n                            {tag}\n                          </span>\n                        ))}\n                      </div>\n                    )}\n                  </div>\n                  <button\n                    onClick={handleCloseDialog}\n                    className='absolute right-4 top-4 p-2 bg-black dark:bg-white rounded-lg text-primary-foreground border cursor-pointer transition-colors'\n                    type='button'\n                    aria-label={`Close ${currentItem.title} dialog`}\n                  >\n                    <X size={20} />\n                  </button>\n                </motion.div>\n              </motion.div>\n            </AnimatePresence>,\n            document.body\n          )}\n      </MotionConfig>\n    </div>\n  );\n};\n\nexport default LinearCardDialog;\n",
      "type": "registry:component"
    },
    {
      "path": "./components/ui/linear-modal.tsx",
      "content": "'use client';\n\nimport { cn } from '@/lib/utils';\nimport { XIcon } from 'lucide-react';\nimport { AnimatePresence, MotionConfig, type Transition, type Variant, motion } from 'motion/react';\nimport React, { useCallback, useContext, useEffect, useId, useMemo, useRef, useState } from 'react';\nimport { createPortal } from 'react-dom';\n\ninterface DialogContextType {\n  isOpen: boolean;\n  setIsOpen: React.Dispatch<React.SetStateAction<boolean>>;\n  uniqueId: string;\n  triggerRef: React.RefObject<HTMLDivElement>;\n}\n\nconst DialogContext = React.createContext<DialogContextType | null>(null);\n\nfunction useDialog() {\n  const context = useContext(DialogContext);\n  if (!context) {\n    throw new Error('useDialog must be used within a DialogProvider');\n  }\n  return context;\n}\n\ntype DialogProviderProps = {\n  children: React.ReactNode;\n  transition?: Transition;\n};\n\nfunction DialogProvider({ children, transition }: DialogProviderProps) {\n  const [isOpen, setIsOpen] = useState(false);\n  const uniqueId = useId();\n  const triggerRef = useRef<HTMLDivElement>(null);\n\n  const contextValue = useMemo(\n    () => ({ isOpen, setIsOpen, uniqueId, triggerRef }),\n    [isOpen, uniqueId]\n  );\n\n  return (\n    <DialogContext.Provider\n      //@ts-expect-error\n      value={contextValue}\n    >\n      <MotionConfig transition={transition}>{children}</MotionConfig>\n    </DialogContext.Provider>\n  );\n}\n\ntype DialogProps = {\n  children: React.ReactNode;\n  transition?: Transition;\n};\n\nfunction Dialog({ children, transition }: DialogProps) {\n  return (\n    <DialogProvider>\n      <MotionConfig transition={transition}>{children}</MotionConfig>\n    </DialogProvider>\n  );\n}\n\ntype DialogTriggerProps = {\n  children: React.ReactNode;\n  className?: string;\n  style?: React.CSSProperties;\n  triggerRef?: React.RefObject<HTMLDivElement>;\n};\n\nfunction DialogTrigger({ children, className, style, triggerRef }: DialogTriggerProps) {\n  const { setIsOpen, isOpen, uniqueId } = useDialog();\n\n  const handleClick = useCallback(() => {\n    setIsOpen(!isOpen);\n  }, [isOpen, setIsOpen]);\n\n  const handleKeyDown = useCallback(\n    (event: React.KeyboardEvent) => {\n      if (event.key === 'Enter' || event.key === ' ') {\n        event.preventDefault();\n        setIsOpen(!isOpen);\n      }\n    },\n    [isOpen, setIsOpen]\n  );\n\n  return (\n    <motion.div\n      ref={triggerRef}\n      layoutId={`dialog-${uniqueId}`}\n      className={cn('relative cursor-pointer', className)}\n      onClick={handleClick}\n      onKeyDown={handleKeyDown}\n      style={style}\n      role='button'\n      aria-haspopup='dialog'\n      aria-expanded={isOpen}\n      aria-controls={`dialog-content-${uniqueId}`}\n    >\n      {children}\n    </motion.div>\n  );\n}\n\ntype DialogContent = {\n  children: React.ReactNode;\n  className?: string;\n  style?: React.CSSProperties;\n};\n\nfunction DialogContent({ children, className, style }: DialogContent) {\n  const { setIsOpen, isOpen, uniqueId, triggerRef } = useDialog();\n  const containerRef = useRef<HTMLDivElement>(null);\n  const [firstFocusableElement, setFirstFocusableElement] = useState<HTMLElement | null>(null);\n  const [lastFocusableElement, setLastFocusableElement] = useState<HTMLElement | null>(null);\n\n  useEffect(() => {\n    const handleKeyDown = (event: KeyboardEvent) => {\n      if (event.key === 'Escape') {\n        setIsOpen(false);\n      }\n      if (event.key === 'Tab') {\n        if (!firstFocusableElement || !lastFocusableElement) return;\n\n        if (event.shiftKey) {\n          if (document.activeElement === firstFocusableElement) {\n            event.preventDefault();\n            lastFocusableElement.focus();\n          }\n        } else {\n          if (document.activeElement === lastFocusableElement) {\n            event.preventDefault();\n            firstFocusableElement.focus();\n          }\n        }\n      }\n    };\n\n    document.addEventListener('keydown', handleKeyDown);\n\n    return () => {\n      document.removeEventListener('keydown', handleKeyDown);\n    };\n  }, [setIsOpen, firstFocusableElement, lastFocusableElement]);\n\n  useEffect(() => {\n    if (isOpen) {\n      document.body.style.overflow = 'hidden';\n\n      const focusableElements = containerRef.current?.querySelectorAll(\n        'button, [href], input, select, textarea, [tabindex]:not([tabindex=\"-1\"])'\n      );\n      if (focusableElements && focusableElements.length > 0) {\n        setFirstFocusableElement(focusableElements[0] as HTMLElement);\n        setLastFocusableElement(focusableElements[focusableElements.length - 1] as HTMLElement);\n        // Delay focus slightly to allow animation to start\n        requestAnimationFrame(() => {\n          (focusableElements[0] as HTMLElement).focus();\n        });\n      }\n\n      if (containerRef.current) {\n        containerRef.current.scrollTop = 0;\n      }\n    } else {\n      document.body.style.overflow = '';\n      triggerRef.current?.focus();\n    }\n  }, [isOpen, triggerRef]);\n\n  return (\n    <>\n      <motion.div\n        ref={containerRef}\n        layoutId={`dialog-${uniqueId}`}\n        className={cn('overflow-hidden', className)}\n        style={{\n          ...style,\n          willChange: 'transform, opacity', // GPU acceleration\n        }}\n        role='dialog'\n        aria-modal='true'\n        aria-labelledby={`dialog-title-${uniqueId}`}\n        aria-describedby={`dialog-description-${uniqueId}`}\n        initial={{ scale: 0.95, opacity: 0 }}\n        animate={{ scale: 1, opacity: 1 }}\n        exit={{ scale: 0.95, opacity: 0 }}\n        transition={{\n          type: 'spring',\n          damping: 25,\n          stiffness: 300,\n          mass: 0.8,\n        }}\n      >\n        {children}\n      </motion.div>\n    </>\n  );\n}\ntype DialogContainerProps = {\n  children: React.ReactNode;\n  className?: string;\n  overlayClassName?: string;\n  style?: React.CSSProperties;\n};\n\nfunction DialogContainer({ children, className, overlayClassName }: DialogContainerProps) {\n  const { isOpen, setIsOpen, uniqueId } = useDialog();\n  const [mounted, setMounted] = useState(false);\n\n  useEffect(() => {\n    const drawerWrapper = document.querySelectorAll('[drawer-wrapper]');\n\n    if (isOpen) {\n      document.body.classList.add('overflow-hidden');\n      drawerWrapper.forEach((wrapper) => wrapper?.classList.add('open'));\n    } else {\n      document.body.classList.remove('overflow-hidden');\n      drawerWrapper.forEach((wrapper) => wrapper?.classList.remove('open'));\n    }\n    const handleKeyDown = (event: KeyboardEvent) => {\n      if (event.key === 'Escape') {\n        setIsOpen(false);\n      }\n    };\n\n    document.addEventListener('keydown', handleKeyDown);\n    return () => {\n      document.removeEventListener('keydown', handleKeyDown);\n    };\n  }, [isOpen]);\n\n  useEffect(() => {\n    setMounted(true);\n    return () => {\n      setMounted(false);\n    };\n  }, []);\n\n  if (!mounted) return null;\n\n  return createPortal(\n    <AnimatePresence initial={false} mode='wait'>\n      {isOpen && (\n        <>\n          <motion.div\n            key={`backdrop-${uniqueId}`}\n            data-lenis-prevent\n            className={cn(\n              'fixed inset-0 h-full z-50 w-full backdrop-blur-xl dark:bg-[radial-gradient(125%_125%_at_50%_10%,#050505_40%,#243aff_100%)] bg-[radial-gradient(125%_125%_at_50%_10%,#ffffff_40%,#243aff_100%)]',\n              overlayClassName\n            )}\n            initial={{ opacity: 0 }}\n            animate={{ opacity: 1 }}\n            exit={{ opacity: 0 }}\n            transition={{\n              duration: 0.2,\n              ease: [0.4, 0.0, 0.4, 1],\n            }}\n            onClick={() => setIsOpen(false)}\n          ></motion.div>\n          <motion.div\n            className={cn(`fixed inset-0 z-50 w-fit mx-auto`, className)}\n            style={{ willChange: 'transform' }} // GPU acceleration for transforms\n          >\n            {children}\n          </motion.div>\n        </>\n      )}\n    </AnimatePresence>,\n    document.body\n  );\n}\ntype DialogTitleProps = {\n  children: React.ReactNode;\n  className?: string;\n  style?: React.CSSProperties;\n};\n\nfunction DialogTitle({ children, className, style }: DialogTitleProps) {\n  const { uniqueId } = useDialog();\n\n  return (\n    <motion.h1\n      layoutId={`dialog-title-container-${uniqueId}`}\n      className={className}\n      style={style}\n      layout\n    >\n      {children}\n    </motion.h1>\n  );\n}\n\ntype DialogSubtitleProps = {\n  children: React.ReactNode;\n  className?: string;\n  style?: React.CSSProperties;\n};\n\nfunction DialogSubtitle({ children, className, style }: DialogSubtitleProps) {\n  const { uniqueId } = useDialog();\n\n  return (\n    <motion.div\n      layoutId={`dialog-subtitle-container-${uniqueId}`}\n      className={className}\n      style={style}\n    >\n      {children}\n    </motion.div>\n  );\n}\n\ntype DialogDescriptionProps = {\n  children: React.ReactNode;\n  className?: string;\n  disableLayoutAnimation?: boolean;\n  variants?: {\n    initial: Variant;\n    animate: Variant;\n    exit: Variant;\n  };\n};\n\nfunction DialogDescription({\n  children,\n  className,\n  variants,\n  disableLayoutAnimation,\n}: DialogDescriptionProps) {\n  const { uniqueId } = useDialog();\n\n  return (\n    <motion.div\n      key={`dialog-description-${uniqueId}`}\n      layoutId={disableLayoutAnimation ? undefined : `dialog-description-content-${uniqueId}`}\n      variants={variants}\n      className={className}\n      initial='initial'\n      animate='animate'\n      exit='exit'\n      id={`dialog-description-${uniqueId}`}\n    >\n      {children}\n    </motion.div>\n  );\n}\n\ntype DialogImageProps = {\n  src: string;\n  alt: string;\n  className?: string;\n  style?: React.CSSProperties;\n};\n\nfunction DialogImage({ src, alt, className, style }: DialogImageProps) {\n  const { uniqueId } = useDialog();\n\n  return (\n    <motion.img\n      src={src}\n      alt={alt}\n      className={cn(className)}\n      layoutId={`dialog-img-${uniqueId}`}\n      style={style}\n    />\n  );\n}\n\ntype DialogCloseProps = {\n  children?: React.ReactNode;\n  className?: string;\n  variants?: {\n    initial: Variant;\n    animate: Variant;\n    exit: Variant;\n  };\n};\n\nfunction DialogClose({ children, className, variants }: DialogCloseProps) {\n  const { setIsOpen, uniqueId } = useDialog();\n\n  const handleClose = useCallback(() => {\n    setIsOpen(false);\n  }, [setIsOpen]);\n\n  return (\n    <motion.button\n      onClick={handleClose}\n      type='button'\n      aria-label='Close dialog'\n      key={`dialog-close-${uniqueId}`}\n      className={cn('absolute right-6 top-6 text-white', className)}\n      initial='initial'\n      animate='animate'\n      exit='exit'\n      variants={variants}\n    >\n      {children || <XIcon size={24} />}\n    </motion.button>\n  );\n}\n\nexport {\n  Dialog,\n  DialogClose,\n  DialogContainer,\n  DialogContent,\n  DialogDescription,\n  DialogImage,\n  DialogSubtitle,\n  DialogTitle,\n  DialogTrigger,\n};\n",
      "type": "registry:component"
    }
  ],
  "type": "registry:component"
}