{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "border-glide",
  "type": "registry:ui",
  "title": "Border Glide",
  "description": "Modern UI cards with a moving border and smooth transitions.",
  "author": "Ahdeetai <https://aditya.is-cool.dev>",
  "registryDependencies": ["@scrollxui/card"],
  "dependencies": ["motion", "clsx", "tailwind-merge"],
  "files": [
    {
      "type": "registry:ui",
      "path": "components/ui/border-glide.tsx",
      "content": "'use client';\nimport React, { useRef, createContext, useContext, useCallback } from 'react';\nimport {\n  motion,\n  AnimatePresence,\n  PanInfo,\n  useSpring,\n  useMotionTemplate,\n  useTransform,\n} from 'motion/react';\nimport {\n  Card,\n  CardContent,\n  CardHeader,\n  CardFooter,\n  CardTitle,\n  CardDescription,\n} from '@/components/ui/card';\nimport { cn } from '@/lib/utils';\n\ninterface BorderGlideContextType {\n  currentIndex: number;\n  direction: number;\n  handleDragEnd: (\n    e: MouseEvent | TouchEvent | PointerEvent,\n    info: PanInfo,\n  ) => void;\n  totalItems: number;\n}\n\nconst BorderGlideContext = createContext<BorderGlideContextType | undefined>(\n  undefined,\n);\n\nconst useBorderGlideContext = () => {\n  const context = useContext(BorderGlideContext);\n  if (!context) {\n    throw new Error('BorderGlide components must be used within BorderGlide');\n  }\n  return context;\n};\n\nconst MovingBorder: React.FC<{\n  children: React.ReactNode;\n  duration?: number;\n  rx?: string;\n  ry?: string;\n  color?: string;\n  width?: string;\n  height?: string;\n  opacity?: number;\n}> = ({\n  children,\n  duration = 3000,\n  rx = '1.5rem',\n  ry = '1.5rem',\n  color = '#3b82f6',\n  width = '12rem',\n  height = '0.5rem',\n  opacity = 0.8,\n}) => {\n  const pathRef = useRef<SVGRectElement>(null);\n  const animationRef = useRef<number | null>(null);\n  const startTimeRef = useRef<number>(Date.now());\n\n  const time = useSpring(0, {\n    stiffness: 100,\n    damping: 20,\n    mass: 0.5,\n  });\n\n  const animate = useCallback(() => {\n    const elapsed = Date.now() - startTimeRef.current;\n    const speed = 1000 / duration;\n    time.set(elapsed * speed);\n    animationRef.current = requestAnimationFrame(animate);\n  }, [time, duration]);\n\n  React.useLayoutEffect(() => {\n    startTimeRef.current = Date.now();\n    animate();\n    return () => {\n      if (animationRef.current) {\n        cancelAnimationFrame(animationRef.current);\n      }\n    };\n  }, [animate]);\n\n  const progress = useTransform(time, (val) => {\n    if (!pathRef.current) return 0;\n    const length = pathRef.current.getTotalLength();\n    return val % length;\n  });\n\n  const x = useTransform(progress, (val) => {\n    if (!pathRef.current) return 0;\n    return pathRef.current.getPointAtLength(val).x;\n  });\n\n  const y = useTransform(progress, (val) => {\n    if (!pathRef.current) return 0;\n    return pathRef.current.getPointAtLength(val).y;\n  });\n\n  const angle = useTransform(progress, (val) => {\n    if (!pathRef.current) return 0;\n    const length = pathRef.current.getTotalLength();\n    const p1 = pathRef.current.getPointAtLength(val);\n    const p2 = pathRef.current.getPointAtLength((val + 1) % length);\n    return Math.atan2(p2.y - p1.y, p2.x - p1.x) * (180 / Math.PI);\n  });\n\n  const transform = useMotionTemplate`\n    translateX(${x}px) \n    translateY(${y}px) \n    translateX(-50%) \n    translateY(-50%) \n    rotate(${angle}deg)\n  `;\n\n  const getBackgroundStyle = (color: string) => {\n    if (\n      color.includes('gradient') ||\n      color.includes('linear-gradient') ||\n      color.includes('radial-gradient') ||\n      color.includes('conic-gradient')\n    ) {\n      return color;\n    }\n    return `radial-gradient(${color} 40%, transparent 60%)`;\n  };\n\n  return (\n    <>\n      <svg\n        xmlns='http://www.w3.org/2000/svg'\n        preserveAspectRatio='none'\n        className='absolute h-full w-full pointer-events-none'\n        style={{ willChange: 'auto' }}\n      >\n        <rect\n          fill='none'\n          width='100%'\n          height='100%'\n          rx={rx}\n          ry={ry}\n          ref={pathRef}\n          style={{ willChange: 'auto' }}\n        />\n      </svg>\n      <motion.div\n        style={{\n          position: 'absolute',\n          top: 0,\n          left: 0,\n          transform,\n          willChange: 'transform',\n        }}\n      >\n        <div\n          className='rounded-full'\n          style={{\n            height,\n            width,\n            opacity,\n            background: getBackgroundStyle(color),\n            borderRadius: '50%',\n          }}\n        />\n      </motion.div>\n    </>\n  );\n};\n\ninterface BorderGlideProps {\n  children: React.ReactNode;\n  className?: string;\n  autoPlayInterval?: number;\n  borderDuration?: number;\n  borderColor?: string;\n  borderWidth?: string;\n  borderHeight?: string;\n  borderOpacity?: number;\n}\n\nconst BorderGlide: React.FC<BorderGlideProps> = ({\n  children,\n  className,\n  autoPlayInterval = 5000,\n  borderDuration = 3000,\n  borderColor = '#3b82f6',\n  borderWidth = '6rem',\n  borderHeight = '6rem',\n  borderOpacity = 0.8,\n}) => {\n  const [currentIndex, setCurrentIndex] = React.useState(0);\n  const [direction, setDirection] = React.useState(0);\n  const autoPlayRef = useRef<NodeJS.Timeout | null>(null);\n\n  const childrenArray = React.Children.toArray(children);\n  const totalItems = childrenArray.length;\n\n  const swipeConfidenceThreshold = 10000;\n  const swipePower = (offset: number, velocity: number) =>\n    Math.abs(offset) * velocity;\n\n  const paginate = useCallback(\n    (newDirection: number) => {\n      setDirection(newDirection);\n      if (newDirection === 1) {\n        setCurrentIndex((prev) => (prev === totalItems - 1 ? 0 : prev + 1));\n      } else {\n        setCurrentIndex((prev) => (prev === 0 ? totalItems - 1 : prev - 1));\n      }\n    },\n    [totalItems],\n  );\n\n  const handleDragEnd = useCallback(\n    (\n      e: MouseEvent | TouchEvent | PointerEvent,\n      { offset, velocity }: PanInfo,\n    ) => {\n      const swipe = swipePower(offset.x, velocity.x);\n      if (swipe < -swipeConfidenceThreshold) {\n        paginate(1);\n      } else if (swipe > swipeConfidenceThreshold) {\n        paginate(-1);\n      }\n    },\n    [paginate],\n  );\n\n  const setupAutoPlay = useCallback(() => {\n    if (autoPlayRef.current) {\n      clearInterval(autoPlayRef.current);\n    }\n    if (autoPlayInterval > 0 && totalItems > 1) {\n      autoPlayRef.current = setInterval(() => {\n        paginate(1);\n      }, autoPlayInterval);\n    }\n  }, [autoPlayInterval, totalItems, paginate]);\n\n  React.useLayoutEffect(() => {\n    setupAutoPlay();\n    return () => {\n      if (autoPlayRef.current) {\n        clearInterval(autoPlayRef.current);\n      }\n    };\n  }, [setupAutoPlay]);\n\n  const contextValue: BorderGlideContextType = {\n    currentIndex,\n    direction,\n    handleDragEnd,\n    totalItems,\n  };\n\n  const slideVariants = {\n    enter: (direction: number) => ({\n      x: direction > 0 ? '100%' : '-100%',\n      opacity: 0,\n      scale: 0.95,\n    }),\n    center: {\n      zIndex: 1,\n      x: '0%',\n      opacity: 1,\n      scale: 1,\n    },\n    exit: (direction: number) => ({\n      zIndex: 0,\n      x: direction < 0 ? '100%' : '-100%',\n      opacity: 0,\n      scale: 0.95,\n    }),\n  };\n\n  const spring = {\n    type: 'spring' as const,\n    stiffness: 300,\n    damping: 30,\n    mass: 0.8,\n  };\n\n  return (\n    <BorderGlideContext.Provider value={contextValue}>\n      <div className={cn('relative w-full', className)}>\n        <div className='relative w-full h-full overflow-hidden rounded-xl bg-transparent p-0.5'>\n          <div className='absolute inset-0 pointer-events-none'>\n            <MovingBorder\n              duration={borderDuration}\n              rx='0.75rem'\n              ry='0.75rem'\n              color={borderColor}\n              width={borderWidth}\n              height={borderHeight}\n              opacity={borderOpacity}\n            >\n              <div />\n            </MovingBorder>\n          </div>\n          <div className='relative w-full h-full rounded-xl overflow-hidden bg-white dark:bg-[#09090b] backdrop-blur-xs'>\n            <AnimatePresence initial={false} custom={direction} mode='wait'>\n              <motion.div\n                key={currentIndex}\n                custom={direction}\n                variants={slideVariants}\n                initial='enter'\n                animate='center'\n                exit='exit'\n                transition={spring}\n                drag='x'\n                dragConstraints={{ left: 0, right: 0 }}\n                dragElastic={0.2}\n                onDragEnd={handleDragEnd}\n                className='absolute inset-0 cursor-grab active:cursor-grabbing will-change-transform'\n                style={{ willChange: 'transform' }}\n              >\n                {childrenArray[currentIndex]}\n              </motion.div>\n            </AnimatePresence>\n          </div>\n        </div>\n      </div>\n    </BorderGlideContext.Provider>\n  );\n};\n\ninterface BorderGlideCardProps {\n  children: React.ReactNode;\n  className?: string;\n}\n\nconst BorderGlideCard: React.FC<BorderGlideCardProps> = ({\n  children,\n  className,\n}) => {\n  return (\n    <Card\n      className={cn(\n        'bg-transparent border shadow-none text-foreground w-full h-full',\n        className,\n      )}\n    >\n      {children}\n    </Card>\n  );\n};\n\ninterface BorderGlideContentProps {\n  children: React.ReactNode;\n  className?: string;\n}\n\nconst BorderGlideContent: React.FC<BorderGlideContentProps> = ({\n  children,\n  className,\n}) => {\n  return (\n    <CardContent className={cn('p-0 w-full h-full', className)}>\n      {children}\n    </CardContent>\n  );\n};\n\ninterface BorderGlideHeaderProps {\n  children: React.ReactNode;\n  className?: string;\n}\n\nconst BorderGlideHeader: React.FC<BorderGlideHeaderProps> = ({\n  children,\n  className,\n}) => {\n  return (\n    <CardHeader className={cn('flex flex-col space-y-1.5 p-6', className)}>\n      {children}\n    </CardHeader>\n  );\n};\n\ninterface BorderGlideFooterProps {\n  children: React.ReactNode;\n  className?: string;\n}\n\nconst BorderGlideFooter: React.FC<BorderGlideFooterProps> = ({\n  children,\n  className,\n}) => {\n  return (\n    <CardFooter className={cn('flex items-center p-6 pt-0', className)}>\n      {children}\n    </CardFooter>\n  );\n};\n\ninterface BorderGlideTitleProps {\n  children: React.ReactNode;\n  className?: string;\n}\n\nconst BorderGlideTitle: React.FC<BorderGlideTitleProps> = ({\n  children,\n  className,\n}) => {\n  return (\n    <CardTitle\n      className={cn('font-semibold leading-none tracking-tight', className)}\n    >\n      {children}\n    </CardTitle>\n  );\n};\n\ninterface BorderGlideDescriptionProps {\n  children: React.ReactNode;\n  className?: string;\n}\n\nconst BorderGlideDescription: React.FC<BorderGlideDescriptionProps> = ({\n  children,\n  className,\n}) => {\n  return (\n    <CardDescription className={cn('text-sm text-muted-foreground', className)}>\n      {children}\n    </CardDescription>\n  );\n};\n\nexport {\n  BorderGlide,\n  BorderGlideCard,\n  BorderGlideContent,\n  BorderGlideHeader,\n  BorderGlideFooter,\n  BorderGlideTitle,\n  BorderGlideDescription,\n};\n"
    },
    {
      "type": "registry:ui",
      "path": "components/ui/card.tsx",
      "content": "import * as React from 'react';\n\nimport { cn } from '@/lib/utils';\n\nfunction Card({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot='card'\n      className={cn(\n        'bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-xs',\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction CardHeader({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot='card-header'\n      className={cn(\n        '@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-1.5 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6',\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction CardTitle({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot='card-title'\n      className={cn('leading-none font-semibold', className)}\n      {...props}\n    />\n  );\n}\n\nfunction CardDescription({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot='card-description'\n      className={cn('text-muted-foreground text-sm', className)}\n      {...props}\n    />\n  );\n}\n\nfunction CardAction({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot='card-action'\n      className={cn(\n        'col-start-2 row-span-2 row-start-1 self-start justify-self-end',\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction CardContent({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot='card-content'\n      className={cn('px-6', className)}\n      {...props}\n    />\n  );\n}\n\nfunction CardFooter({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot='card-footer'\n      className={cn('flex items-center px-6 [.border-t]:pt-6', className)}\n      {...props}\n    />\n  );\n}\n\nexport {\n  Card,\n  CardHeader,\n  CardFooter,\n  CardTitle,\n  CardAction,\n  CardDescription,\n  CardContent,\n};\n"
    },
    {
      "type": "registry:lib",
      "path": "lib/utils.ts",
      "content": "import { clsx, type ClassValue } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs));\n}"
    }
  ]
}
