{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "heroui",
  "type": "registry:ui",
  "title": "Hero Section",
  "description": "A set of hero sections ranging from simple to complex layouts.",
  "author": "Ahdeetai <https://aditya.is-cool.dev>",
  "registryDependencies": [
    "@scrollxui/badge",
    "@scrollxui/button",
    "@scrollxui/globe",
    "@scrollxui/lustretext"
  ],
  "dependencies": [
    "motion",
    "@react-spring/web",
    "cobe",
    "next-themes",
    "@radix-ui/react-slot",
    "class-variance-authority",
    "clsx",
    "tailwind-merge"
  ],
  "files": [
    {
      "type": "registry:ui",
      "path": "components/ui/heroui.tsx",
      "content": "'use client';\nimport { useState, useRef } from 'react';\nimport { motion, useTransform, useScroll, Variants } from 'motion/react';\nimport { Button } from '@/components/ui/button';\nimport { Badge } from '@/components/ui/badge';\nimport LustreText from '@/components/ui/lustretext';\nimport { useTheme } from 'next-themes';\nimport Globe from '@/components/ui/globe';\nimport { cn } from '@/lib/utils';\n\nconst containerVariants: Variants = {\n  hidden: { opacity: 0 },\n  visible: {\n    opacity: 1,\n    transition: {\n      staggerChildren: 0.2,\n      delayChildren: 0.4,\n      duration: 0.8,\n    },\n  },\n};\n\nconst textVariants: Variants = {\n  hidden: { y: 40, opacity: 0 },\n  visible: {\n    y: 0,\n    opacity: 1,\n    transition: {\n      type: 'spring' as const,\n      stiffness: 120,\n      damping: 15,\n    },\n  },\n};\n\ndeclare module '@/components/ui/badge' {\n  interface BadgeProps {\n    shiny?: boolean;\n    shinySpeed?: number;\n  }\n}\n\ninterface HeroUIProps {\n  title?: string;\n  subtitle?: string;\n  badgeText?: string;\n  primaryCTA?: string;\n  secondaryCTA?: string;\n  features?: string[];\n  className?: string;\n  globeBaseColor?: {\n    light: [number, number, number];\n    dark: [number, number, number];\n  };\n  globeMarkerColor?: {\n    light: [number, number, number];\n    dark: [number, number, number];\n  };\n  globeGlowColor?: {\n    light: [number, number, number];\n    dark: [number, number, number];\n  };\n}\n\nexport default function HeroUI({\n  title = 'ScrollX UI',\n  subtitle = 'Where Interactions Spark Joy',\n  badgeText = '✨ Now Open Source',\n  primaryCTA = 'Get Started',\n  secondaryCTA = 'Documentation',\n  features = [\n    'TypeScript First',\n    'Dark Mode',\n    '100% Customizable',\n    'MIT Licensed',\n  ],\n  className = '',\n  globeBaseColor = {\n    light: [0.98, 0.98, 0.98],\n    dark: [0.12, 0.12, 0.12],\n  },\n  globeMarkerColor = {\n    light: [0.2, 0.5, 0.9],\n    dark: [0.1, 0.8, 1],\n  },\n  globeGlowColor = {\n    light: [0.3, 0.3, 0.3],\n    dark: [1, 1, 1],\n  },\n}: HeroUIProps) {\n  const { theme } = useTheme();\n  const [mounted] = useState(true);\n  const ref = useRef<HTMLDivElement>(null);\n  const { scrollYProgress } = useScroll({\n    target: ref,\n    offset: ['start start', 'end start'],\n  });\n  const y = useTransform(scrollYProgress, [0, 1], ['0%', '30%']);\n\n  if (!mounted) return null;\n\n  return (\n    <section\n      ref={ref}\n      className={cn(\n        'relative overflow-hidden min-h-[90vh] flex items-center',\n        className,\n      )}\n    >\n      <div className='absolute inset-0 z-0'>\n        <div className='absolute inset-0 bg-[radial-gradient(#e5e7eb_1px,transparent_1px)] dark:bg-[radial-gradient(#374151_1px,transparent_1px)] bg-size-[16px_16px]' />\n        <motion.div\n          className='absolute top-20 left-1/4 w-60 h-60 sm:w-80 sm:h-80 md:w-96 md:h-96 lg:w-120 lg:h-120 bg-linear-to-r from-blue-500/20 to-cyan-500/20 rounded-full blur-[100px]'\n          animate={{\n            scale: [1, 1.2, 1],\n            rotate: [0, 180, 360],\n          }}\n          transition={{\n            duration: 20,\n            repeat: Infinity,\n            ease: 'linear',\n          }}\n        />\n      </div>\n\n      <div className='container relative z-10 mx-auto px-4 py-6 sm:py-8 md:py-16 lg:py-24'>\n        <div className='relative min-h-[60vh] md:min-h-[70vh]'>\n          <motion.div\n            className='absolute right-0 bottom-4 top-auto w-[70%] md:top-2 md:bottom-auto md:w-1/2 lg:-top-15 lg:-right-10 xl:-top-20 xl:-right-15 transition-all duration-500'\n            style={{ y }}\n            initial={{ scale: 0.8, opacity: 0 }}\n            animate={{ scale: 1, opacity: 1 }}\n            transition={{ type: 'spring', stiffness: 100, damping: 15 }}\n          >\n            <div className='w-full h-55 md:h-75 lg:h-100 xl:h-112.5'>\n              <Globe\n                baseColor={\n                  theme === 'dark'\n                    ? (globeBaseColor.dark as [number, number, number])\n                    : (globeBaseColor.light as [number, number, number])\n                }\n                markerColor={\n                  theme === 'dark'\n                    ? (globeMarkerColor.dark as [number, number, number])\n                    : (globeMarkerColor.light as [number, number, number])\n                }\n                glowColor={\n                  theme === 'dark'\n                    ? (globeGlowColor.dark as [number, number, number])\n                    : (globeGlowColor.light as [number, number, number])\n                }\n              />\n            </div>\n          </motion.div>\n\n          <motion.div\n            className='relative z-20 w-full md:w-7/12 lg:w-1/2 pt-6 sm:pt-8 md:pt-16 lg:pt-24 md:ml-8 lg:ml-16 md:-mt-6'\n            variants={containerVariants}\n            initial='hidden'\n            animate='visible'\n          >\n            <Badge\n              variant='destructive'\n              className='w-fit mb-4'\n              shiny\n              shinySpeed={3}\n            >\n              {badgeText}\n            </Badge>\n\n            <motion.h1\n              className='text-3xl sm:text-4xl md:text-5xl lg:text-6xl font-bold tracking-tight leading-tight'\n              variants={textVariants}\n            >\n              <span className='block bg-linear-to-r from-blue-600 via-cyan-500 to-blue-400 bg-clip-text text-transparent'>\n                {title}\n              </span>\n              <span className='text-foreground mt-2 block'>\n                {subtitle.includes('Interactions') ? (\n                  <>\n                    Where{' '}\n                    <LustreText\n                      text='Interactions'\n                      speed={8}\n                      className='inline'\n                    />{' '}\n                    <span className='border-b-4 border-cyan-400/50'>\n                      Spark Joy\n                    </span>\n                  </>\n                ) : (\n                  subtitle\n                )}\n              </span>\n            </motion.h1>\n\n            <motion.div\n              className='text-lg sm:text-xl md:text-2xl text-muted-foreground max-w-2xl mt-4'\n              variants={textVariants}\n            >\n              Animated, accessible, and customizable components built with{' '}\n              <Badge variant='default' className='mx-1'>\n                Framer Motion\n              </Badge>{' '}\n              and{' '}\n              <Badge variant='default' className='mx-1'>\n                Tailwind CSS\n              </Badge>\n            </motion.div>\n\n            <motion.div\n              className='flex gap-4 mt-6 max-[375px]:flex-col max-[375px]:w-full max-[375px]:gap-3'\n              variants={textVariants}\n            >\n              <Button\n                size='lg'\n                className='group relative overflow-hidden rounded-xl px-8 py-6 text-lg font-semibold shadow-xl max-[375px]:w-full max-[375px]:px-4 max-[375px]:py-4'\n              >\n                <LustreText\n                  text={primaryCTA}\n                  speed={6}\n                  className='relative z-10 bg-white dark:bg-red-950 text-[clamp(0.75rem,2vw,1.25rem)]'\n                />\n\n                <div className='absolute inset-0 bg-linear-to-r from-blue-600 to-cyan-500 opacity-0 group-hover:opacity-100 transition-opacity duration-300' />\n              </Button>\n\n              <Button\n                variant='outline'\n                size='lg'\n                className='rounded-xl px-8 py-6 text-lg font-semibold border-2 backdrop-blur-xs max-[375px]:w-full max-[375px]:px-4 max-[375px]:py-4'\n              >\n                <span className='bg-linear-to-r from-foreground to-muted-foreground bg-clip-text text-transparent'>\n                  {secondaryCTA}\n                </span>\n              </Button>\n            </motion.div>\n\n            <motion.div\n              className='flex items-center gap-8 mt-12 opacity-70'\n              variants={textVariants}\n            >\n              <div className='flex gap-4 flex-wrap'>\n                {features.map((text) => (\n                  <Badge\n                    key={text}\n                    variant='outline'\n                    className='py-1.5 px-3 text-sm border-muted-foreground/30 bg-background/50'\n                  >\n                    {text}\n                  </Badge>\n                ))}\n              </div>\n            </motion.div>\n          </motion.div>\n        </div>\n      </div>\n    </section>\n  );\n}\n"
    },
    {
      "type": "registry:ui",
      "path": "components/ui/lustretext.tsx",
      "content": "import React from \"react\";\n\ninterface LustreTextProps {\n  text: string;\n  disabled?: boolean;\n  speed?: number;\n  className?: string;\n}\n\nconst LustreText: React.FC<LustreTextProps> = ({\n  text,\n  disabled = false,\n  speed = 5,\n  className = \"\",\n}) => {\n  const animationStyle = {\n    animationDuration: `${speed}s`,\n    animationTimingFunction: \"linear\",\n    animationIterationCount: \"infinite\",\n    animationFillMode: \"forwards\",\n  };\n\n  return (\n    <span\n      className={`\n    lustre-text\n    ${!disabled ? \"animate-shine\" : \"\"}\n    dark:lustre-dark lustre-light\n    ${className}\n  `}\n      style={!disabled ? animationStyle : undefined}\n    >\n      {text}\n    </span>\n  );\n};\n\nexport default LustreText;\n"
    },
    {
      "type": "registry:ui",
      "path": "components/ui/badge.tsx",
      "content": "import * as React from 'react';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { cn } from '@/lib/utils';\n\nconst badgeVariants = cva(\n  'inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-hidden focus:ring-2 focus:ring-ring focus:ring-offset-2',\n  {\n    variants: {\n      variant: {\n        default:\n          'border-transparent bg-primary text-primary-foreground hover:bg-primary/80',\n        secondary:\n          'border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80',\n        destructive:\n          'border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80',\n        outline: 'text-foreground',\n      },\n      shiny: {\n        true: 'relative overflow-hidden',\n        false: '',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n      shiny: false,\n    },\n  },\n);\n\nexport interface BadgeProps\n  extends\n    React.HTMLAttributes<HTMLDivElement>,\n    VariantProps<typeof badgeVariants> {\n  shiny?: boolean;\n  shinySpeed?: number;\n}\n\nfunction Badge({\n  className,\n  variant,\n  shiny = false,\n  shinySpeed = 5,\n  children,\n  ...props\n}: BadgeProps) {\n  const animationDuration = `${shinySpeed}s`;\n\n  return (\n    <div\n      className={cn(badgeVariants({ variant, shiny }), className)}\n      {...props}\n    >\n      <span className={shiny ? 'relative z-10' : ''}>{children}</span>\n\n      {shiny && (\n        <span\n          className='absolute inset-0 pointer-events-none animate-shine dark:hidden'\n          style={{\n            background:\n              'linear-gradient(120deg, transparent 40%, rgba(255,255,255,0.6) 50%, transparent 60%)',\n            backgroundSize: '200% 100%',\n            animationDuration,\n            mixBlendMode: 'screen',\n          }}\n        />\n      )}\n\n      {shiny && (\n        <span\n          className='absolute inset-0 pointer-events-none animate-shine hidden dark:block'\n          style={{\n            background:\n              'linear-gradient(120deg, transparent 40%, rgba(0,0,150,0.25) 50%, transparent 60%)',\n            backgroundSize: '200% 100%',\n            animationDuration,\n            mixBlendMode: 'multiply',\n          }}\n        />\n      )}\n    </div>\n  );\n}\n\nexport { Badge, badgeVariants };\n"
    },
    {
      "type": "registry:ui",
      "path": "components/ui/button.tsx",
      "content": "import * as React from 'react';\nimport { Slot } from '@radix-ui/react-slot';\nimport { cva, type VariantProps } from 'class-variance-authority';\n\nimport { cn } from '@/lib/utils';\n\nconst buttonVariants = cva(\n  'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',\n  {\n    variants: {\n      variant: {\n        default:\n          'bg-primary text-primary-foreground shadow-sm hover:bg-primary/90',\n        destructive:\n          'bg-destructive text-destructive-foreground shadow-xs hover:bg-destructive/90',\n        outline:\n          'border border-input bg-background shadow-xs hover:bg-accent hover:text-accent-foreground',\n        secondary:\n          'bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80',\n        ghost: 'hover:bg-accent hover:text-accent-foreground',\n        link: 'text-primary underline-offset-4 hover:underline',\n        success: 'bg-green-600 text-white hover:bg-green-700',\n        warning: 'bg-yellow-500 text-black hover:bg-yellow-600',\n        info: 'bg-blue-500 text-white hover:bg-blue-600',\n        dark: 'bg-gray-800 text-white hover:bg-gray-700',\n        light: 'bg-gray-100 text-gray-800 hover:bg-gray-200',\n        gradient:\n          'bg-linear-to-r from-indigo-500 via-purple-500 to-pink-500 text-white hover:opacity-90',\n        glass:\n          'bg-white/10 backdrop-blur-md text-white border border-white/20 hover:bg-white/20',\n      },\n      size: {\n        default: 'h-9 px-4 py-2',\n        sm: 'h-8 rounded-md px-3 text-xs',\n        lg: 'h-10 rounded-md px-8',\n        icon: 'h-9 w-9',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n      size: 'default',\n    },\n  },\n);\n\nexport interface ButtonProps\n  extends\n    React.ButtonHTMLAttributes<HTMLButtonElement>,\n    VariantProps<typeof buttonVariants> {\n  asChild?: boolean;\n}\n\nconst Button = React.forwardRef<HTMLButtonElement, ButtonProps>(\n  ({ className, variant, size, asChild = false, ...props }, ref) => {\n    const Comp = asChild ? Slot : 'button';\n    return (\n      <Comp\n        className={cn(buttonVariants({ variant, size, className }))}\n        ref={ref}\n        {...props}\n      />\n    );\n  },\n);\nButton.displayName = 'Button';\n\nexport { Button, buttonVariants };\n"
    },
    {
      "type": "registry:ui",
      "path": "components/ui/globe.tsx",
      "content": "'use client';\nimport { useEffect, useRef, useState } from 'react';\nimport { useSpring } from '@react-spring/web';\nimport createGlobe from 'cobe';\n\ninterface Marker {\n  location: [number, number];\n  size: number;\n}\n\ninterface GlobeProps {\n  baseColor?: [number, number, number];\n  markerColor?: [number, number, number];\n  glowColor?: [number, number, number];\n  markers?: Marker[];\n  scale?: number;\n  className?: string;\n  rotateToLocation?: string | [number, number];\n  autoRotate?: boolean;\n  rotateCities?: string[];\n  rotationSpeed?: number;\n}\n\nconst cityCoordinates: Record<string, [number, number]> = {\n  'san francisco': [37.7749, -122.4194],\n  'new york': [40.7128, -74.006],\n  london: [51.5074, -0.1278],\n  tokyo: [35.6762, 139.6503],\n  paris: [48.8566, 2.3522],\n  moscow: [55.7558, 37.6176],\n  dubai: [25.2048, 55.2708],\n  singapore: [1.3521, 103.8198],\n};\n\nconst locationToAngles = (lat: number, long: number): [number, number] => [\n  Math.PI - ((long * Math.PI) / 180 - Math.PI / 2),\n  (lat * Math.PI) / 180,\n];\n\ninterface GlobeRenderer {\n  destroy: () => void;\n}\n\nexport default function Globe({\n  baseColor = [0.3, 0.3, 0.3],\n  markerColor = [0.1, 0.8, 1],\n  glowColor = [1, 1, 1],\n  markers = [\n    { location: [37.7595, -122.4367], size: 0.03 },\n    { location: [40.7128, -74.006], size: 0.1 },\n  ],\n  scale = 1,\n  className = 'aspect-square w-full max-w-150',\n  rotateToLocation,\n  autoRotate = true,\n  rotateCities = [],\n  rotationSpeed = 3000,\n}: GlobeProps) {\n  const canvasRef = useRef<HTMLCanvasElement>(null);\n  const containerRef = useRef<HTMLDivElement>(null);\n  const pointerInteracting = useRef<number | null>(null);\n  const pointerInteractionMovement = useRef(0);\n  const focusRef = useRef<[number, number] | null>(null);\n  const phiRef = useRef(0);\n  const rotationInterval = useRef<NodeJS.Timeout | null>(null);\n  const [currentCityIndex, setCurrentCityIndex] = useState(0);\n  const globeRef = useRef<GlobeRenderer | null>(null);\n  const [isVisible, setIsVisible] = useState(false);\n\n  const [{ r }, api] = useSpring(() => ({\n    r: 0,\n    config: {\n      mass: 1,\n      tension: 280,\n      friction: 40,\n      precision: 0.001,\n    },\n  }));\n\n  useEffect(() => {\n    if (!containerRef.current) return;\n\n    const container = containerRef.current;\n\n    const observer = new IntersectionObserver(\n      (entries) => {\n        const [entry] = entries;\n        setIsVisible(entry.isIntersecting);\n      },\n      { threshold: 0.1 },\n    );\n\n    observer.observe(container);\n\n    return () => {\n      observer.unobserve(container);\n    };\n  }, []);\n\n  useEffect(() => {\n    if (rotateCities.length === 0) return;\n\n    const rotateToNextCity = () => {\n      const nextIndex = (currentCityIndex + 1) % rotateCities.length;\n      const city = rotateCities[nextIndex].toLowerCase();\n      const coordinates = cityCoordinates[city];\n\n      if (coordinates) {\n        focusRef.current = locationToAngles(...coordinates);\n        setCurrentCityIndex(nextIndex);\n      }\n    };\n\n    if (isVisible) {\n      const city = rotateCities[currentCityIndex].toLowerCase();\n      const coordinates = cityCoordinates[city];\n      if (coordinates) {\n        focusRef.current = locationToAngles(...coordinates);\n      }\n\n      rotationInterval.current = setInterval(rotateToNextCity, rotationSpeed);\n    }\n\n    return () => {\n      if (rotationInterval.current) {\n        clearInterval(rotationInterval.current);\n      }\n    };\n  }, [rotateCities, currentCityIndex, rotationSpeed, isVisible]);\n\n  useEffect(() => {\n    if (!rotateToLocation) {\n      focusRef.current = null;\n      return;\n    }\n\n    let coordinates: [number, number];\n    if (typeof rotateToLocation === 'string') {\n      const city = rotateToLocation.toLowerCase();\n      coordinates = cityCoordinates[city] || [0, 0];\n    } else {\n      coordinates = rotateToLocation;\n    }\n\n    focusRef.current = locationToAngles(...coordinates);\n  }, [rotateToLocation]);\n\n  useEffect(() => {\n    if (!isVisible || !canvasRef.current) return;\n\n    let width = canvasRef.current.offsetWidth || 300;\n    const doublePi = Math.PI * 2;\n    let currentPhi = 0;\n    let currentTheta = 0;\n    const animationFrame: number | null = null;\n\n    const onResize = () => {\n      if (canvasRef.current) {\n        width = canvasRef.current.offsetWidth || 300;\n      }\n    };\n\n    window.addEventListener('resize', onResize);\n\n    try {\n      globeRef.current = createGlobe(canvasRef.current, {\n        devicePixelRatio: 2,\n        width: width * 2,\n        height: width * 2,\n        phi: 0,\n        theta: 0,\n        dark: 1,\n        diffuse: 1.2,\n        mapSamples: 16000,\n        mapBrightness: 6,\n        baseColor: baseColor || [0.3, 0.3, 0.3],\n        markerColor: markerColor || [0.1, 0.8, 1],\n        glowColor: glowColor || [1, 1, 1],\n        markers: markers || [],\n        scale: scale || 1,\n        onRender: (state) => {\n          if (!state) return;\n\n          if (autoRotate && !pointerInteracting.current && !focusRef.current) {\n            phiRef.current += 0.01;\n          }\n\n          if (focusRef.current) {\n            const [focusPhi, focusTheta] = focusRef.current;\n            const distPositive = (focusPhi - currentPhi + doublePi) % doublePi;\n            const distNegative = (currentPhi - focusPhi + doublePi) % doublePi;\n\n            currentPhi +=\n              distPositive < distNegative\n                ? distPositive * 0.08\n                : -distNegative * 0.08;\n            currentTheta = currentTheta * 0.92 + focusTheta * 0.08;\n          } else {\n            currentPhi = phiRef.current + r.get();\n          }\n\n          state.phi = currentPhi;\n          state.theta = focusRef.current ? currentTheta : 0;\n          state.width = width * 2;\n          state.height = width * 2;\n        },\n      });\n\n      if (canvasRef.current) {\n        setTimeout(() => {\n          if (canvasRef.current) canvasRef.current.style.opacity = '1';\n        }, 100);\n      }\n    } catch (error) {\n      console.error('Error creating globe:', error);\n    }\n\n    return () => {\n      if (globeRef.current) {\n        globeRef.current.destroy();\n        globeRef.current = null;\n      }\n      window.removeEventListener('resize', onResize);\n      if (animationFrame) {\n        cancelAnimationFrame(animationFrame);\n      }\n    };\n  }, [\n    baseColor,\n    markerColor,\n    glowColor,\n    markers,\n    scale,\n    r,\n    autoRotate,\n    isVisible,\n  ]);\n\n  return (\n    <div ref={containerRef} className={`relative ${className}`}>\n      <canvas\n        ref={canvasRef}\n        onPointerDown={(e) => {\n          pointerInteracting.current =\n            e.clientX - pointerInteractionMovement.current;\n          canvasRef.current?.style?.setProperty('cursor', 'grabbing');\n        }}\n        onPointerUp={() => {\n          pointerInteracting.current = null;\n          canvasRef.current?.style?.setProperty('cursor', 'grab');\n        }}\n        onPointerOut={() => {\n          pointerInteracting.current = null;\n          canvasRef.current?.style?.setProperty('cursor', 'grab');\n        }}\n        onMouseMove={(e) => {\n          if (pointerInteracting.current !== null) {\n            const delta = e.clientX - pointerInteracting.current;\n            pointerInteractionMovement.current = delta;\n            api.start({ r: delta / 200 });\n          }\n        }}\n        onTouchMove={(e) => {\n          if (pointerInteracting.current !== null && e.touches[0]) {\n            const delta = e.touches[0].clientX - pointerInteracting.current;\n            pointerInteractionMovement.current = delta;\n            api.start({ r: delta / 100 });\n          }\n        }}\n        className='w-full h-full cursor-grab opacity-0 transition-opacity duration-1000'\n        style={{ contain: 'layout paint size' }}\n      />\n    </div>\n  );\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}"
    }
  ]
}
