{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "github-stars",
  "dependencies": [
    "class-variance-authority",
    "lucide-react",
    "motion",
    "react-use-measure"
  ],
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "src/components/watermelon-ui/github-stars.tsx",
      "content": "'use client';\n\nimport * as React from 'react';\nimport {\n  useSpring,\n  useTransform,\n  motion,\n  useMotionValue,\n  type MotionValue,\n  type SpringOptions,\n  type HTMLMotionProps,\n  type UseInViewOptions,\n  isMotionComponent,\n  useInView,\n  AnimatePresence,\n} from 'motion/react';\nimport useMeasure from 'react-use-measure';\n\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { StarIcon } from 'lucide-react';\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-[box-shadow,_color,_background-color,_border-color,_outline-color,_text-decoration-color,_fill,_stroke] disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive\",\n  {\n    variants: {\n      variant: {\n        default:\n          'bg-primary text-primary-foreground shadow-xs hover:bg-primary/90',\n        accent: 'bg-accent text-accent-foreground shadow-xs hover:bg-accent/90',\n        outline:\n          'border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50',\n        ghost:\n          'hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50',\n      },\n      size: {\n        default: 'h-9 px-4 py-2 has-[>svg]:px-3',\n        sm: 'h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5',\n        lg: 'h-10 rounded-md px-6 has-[>svg]:px-4',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n      size: 'default',\n    },\n  },\n);\n\nconst buttonStarVariants = cva('', {\n  variants: {\n    variant: {\n      default:\n        'fill-neutral-700 stroke-neutral-700 dark:fill-neutral-300 dark:stroke-neutral-300',\n      accent:\n        'fill-neutral-300 stroke-neutral-300 dark:fill-neutral-700 dark:stroke-neutral-700',\n      outline:\n        'fill-neutral-300 stroke-neutral-300 dark:fill-neutral-700 dark:stroke-neutral-700',\n      ghost:\n        'fill-neutral-300 stroke-neutral-300 dark:fill-neutral-700 dark:stroke-neutral-700',\n    },\n  },\n  defaultVariants: {\n    variant: 'default',\n  },\n});\n\ntype GitHubStarsButtonProps = Omit<\n  ButtonPrimitiveProps & GithubStarsProps,\n  'asChild' | 'children'\n> &\n  VariantProps<typeof buttonVariants>;\n\nfunction GitHubStarsButton({\n  className,\n  username,\n  repo,\n  value,\n  delay,\n  inView,\n  inViewMargin,\n  inViewOnce,\n  variant,\n  size,\n  ...props\n}: GitHubStarsButtonProps) {\n  return (\n    <GithubStars\n      asChild\n      username={username}\n      repo={repo}\n      value={value}\n      delay={delay}\n      inView={inView}\n      inViewMargin={inViewMargin}\n      inViewOnce={inViewOnce}\n    >\n      <ButtonPrimitive\n        className={cn(buttonVariants({ variant, size, className }))}\n        {...props}\n      >\n        <GithubStarsLogo />\n        <GithubStarsNumber />\n        <GithubStarsParticles className=\"text-yellow-500\">\n          <GithubStarsIcon\n            icon={StarIcon}\n            data-variant={variant}\n            className={cn(buttonStarVariants({ variant }))}\n            activeClassName=\"text-yellow-500\"\n            size={18}\n          />\n        </GithubStarsParticles>\n      </ButtonPrimitive>\n    </GithubStars>\n  );\n}\n\nexport { GitHubStarsButton, type GitHubStarsButtonProps };\n\ntype GithubStarsContextType = {\n  stars: number;\n  setStars: (stars: number) => void;\n  currentStars: number;\n  setCurrentStars: (stars: number) => void;\n  isCompleted: boolean;\n  isLoading: boolean;\n};\n\nconst [GithubStarsProvider, useGithubStars] =\n  getStrictContext<GithubStarsContextType>('GithubStarsContext');\n\ntype GithubStarsProps = WithAsChild<\n  {\n    children: React.ReactNode;\n    username?: string;\n    repo?: string;\n    value?: number;\n    delay?: number;\n  } & UseIsInViewOptions &\n  HTMLMotionProps<'div'>\n>;\n\nfunction GithubStars({\n  ref,\n  children,\n  username,\n  repo,\n  value,\n  delay = 0,\n  inView = false,\n  inViewMargin = '0px',\n  inViewOnce = true,\n  asChild = false,\n  ...props\n}: GithubStarsProps) {\n  const { ref: localRef, isInView } = useIsInView(\n    ref as React.Ref<HTMLDivElement>,\n    { inView, inViewOnce, inViewMargin },\n  );\n\n  const [stars, setStars] = React.useState(value ?? 0);\n  const [currentStars, setCurrentStars] = React.useState(0);\n  const [isLoading, setIsLoading] = React.useState(true);\n  const isCompleted = React.useMemo(\n    () => currentStars === stars,\n    [currentStars, stars],\n  );\n\n  const Component = asChild ? Slot : motion.div;\n\n  React.useEffect(() => {\n    if (value !== undefined && username && repo) return;\n    if (!isInView) {\n      setStars(0);\n      setIsLoading(true);\n      return;\n    }\n\n    const timeout = setTimeout(() => {\n      fetch(`https://api.github.com/repos/${username}/${repo}`)\n        .then((response) => response.json())\n        .then((data) => {\n          if (data && typeof data.stargazers_count === 'number') {\n            setStars(data.stargazers_count);\n          }\n        })\n        .catch(console.error)\n        .finally(() => setIsLoading(false));\n    }, delay);\n\n    return () => clearTimeout(timeout);\n  }, [username, repo, value, isInView, delay]);\n\n  return (\n    <GithubStarsProvider\n      value={{\n        stars,\n        currentStars,\n        isCompleted,\n        isLoading,\n        setStars,\n        setCurrentStars,\n      }}\n    >\n      {!isLoading && (\n        <Component ref={localRef} {...props}>\n          {children}\n        </Component>\n      )}\n    </GithubStarsProvider>\n  );\n}\n\ntype GithubStarsNumberProps = Omit<SlidingNumberProps, 'number' | 'fromNumber'>;\n\nfunction GithubStarsNumber({\n  padStart = true,\n  ...props\n}: GithubStarsNumberProps) {\n  const { stars, setCurrentStars } = useGithubStars();\n\n  return (\n    <SlidingNumber\n      number={stars}\n      fromNumber={0}\n      onNumberChange={setCurrentStars}\n      padStart={padStart}\n      {...props}\n    />\n  );\n}\n\ntype GithubStarsIconProps<T extends React.ElementType> = {\n  icon: React.ReactElement<T>;\n  color?: string;\n  activeClassName?: string;\n} & React.ComponentProps<T>;\n\nfunction GithubStarsIcon<T extends React.ElementType>({\n  icon: Icon,\n  color = 'currentColor',\n  activeClassName,\n  className,\n  ...props\n}: GithubStarsIconProps<T>) {\n  const { stars, currentStars, isCompleted } = useGithubStars();\n  const fillPercentage = (currentStars / stars) * 100;\n\n  return (\n    <div style={{ position: 'relative' }}>\n      <Icon aria-hidden=\"true\" className={cn(className)} {...props} />\n      <Icon\n        aria-hidden=\"true\"\n        style={{\n          position: 'absolute',\n          top: 0,\n          left: 0,\n          fill: color,\n          stroke: color,\n          clipPath: `inset(${100 - (isCompleted ? fillPercentage : fillPercentage - 10)}% 0 0 0)`,\n        }}\n        className={cn(className, activeClassName)}\n        {...props}\n      />\n    </div>\n  );\n}\n\ntype GithubStarsParticlesProps = ParticlesEffectProps & {\n  children: React.ReactElement;\n  size?: number;\n};\n\nfunction GithubStarsParticles({\n  children,\n  size = 4,\n  style,\n  ...props\n}: GithubStarsParticlesProps) {\n  const { isCompleted } = useGithubStars();\n\n  return (\n    <Particles animate={isCompleted}>\n      {children}\n      <ParticlesEffect\n        style={{\n          backgroundColor: 'currentcolor',\n          borderRadius: '50%',\n          width: size,\n          height: size,\n          ...style,\n        }}\n        {...props}\n      />\n    </Particles>\n  );\n}\n\ntype GithubStarsLogoProps = React.SVGProps<SVGSVGElement>;\n\nfunction GithubStarsLogo(props: GithubStarsLogoProps) {\n  return (\n    <svg\n      role=\"img\"\n      viewBox=\"0 0 24 24\"\n      fill=\"currentColor\"\n      aria-label=\"GitHub\"\n      {...props}\n    >\n      <path d=\"M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12\"></path>\n    </svg>\n  );\n}\n\ntype ButtonPrimitiveProps = WithAsChild<\n  HTMLMotionProps<'button'> & {\n    hoverScale?: number;\n    tapScale?: number;\n  }\n>;\n\nfunction ButtonPrimitive({\n  hoverScale = 1.05,\n  tapScale = 0.95,\n  asChild = false,\n  ...props\n}: ButtonPrimitiveProps) {\n  const Component = asChild ? Slot : motion.button;\n\n  return (\n    <Component\n      whileTap={{ scale: tapScale }}\n      whileHover={{ scale: hoverScale }}\n      {...props}\n    />\n  );\n}\n\ntype AnyProps = Record<string, unknown>;\n\ntype DOMMotionProps<T extends HTMLElement = HTMLElement> = Omit<\n  HTMLMotionProps<keyof HTMLElementTagNameMap>,\n  'ref'\n> & { ref?: React.Ref<T> };\n\ntype WithAsChild<Base extends object> =\n  | (Base & { asChild: true; children: React.ReactElement })\n  | (Base & { asChild?: false | undefined });\n\ntype SlotProps<T extends HTMLElement = HTMLElement> = {\n  // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  children?: any;\n} & DOMMotionProps<T>;\n\nfunction mergeRefs<T>(\n  ...refs: (React.Ref<T> | undefined)[]\n): React.RefCallback<T> {\n  return (node) => {\n    refs.forEach((ref) => {\n      if (!ref) return;\n      if (typeof ref === 'function') {\n        ref(node);\n      } else {\n        (ref as React.RefObject<T | null>).current = node;\n      }\n    });\n  };\n}\n\nfunction mergeProps<T extends HTMLElement>(\n  childProps: AnyProps,\n  slotProps: DOMMotionProps<T>,\n): AnyProps {\n  const merged: AnyProps = { ...childProps, ...slotProps };\n\n  if (childProps.className || slotProps.className) {\n    merged.className = cn(\n      childProps.className as string,\n      slotProps.className as string,\n    );\n  }\n\n  if (childProps.style || slotProps.style) {\n    merged.style = {\n      ...(childProps.style as React.CSSProperties),\n      ...(slotProps.style as React.CSSProperties),\n    };\n  }\n\n  return merged;\n}\n\nconst motionCache = new Map<React.ElementType, React.ElementType>();\n\nfunction getMotionComponent(type: React.ElementType) {\n  if (typeof type === 'object' && type !== null && isMotionComponent(type)) return type;\n  if (!motionCache.has(type)) {\n    motionCache.set(type, motion.create(type));\n  }\n  return motionCache.get(type)!;\n}\n\nfunction Slot<T extends HTMLElement = HTMLElement>({\n  children,\n  ref,\n  ...props\n}: SlotProps<T>) {\n  if (!React.isValidElement(children)) return null;\n\n  const ComponentType = getMotionComponent(children.type as React.ElementType);\n\n  const { ref: childRef, ...childProps } = children.props as AnyProps;\n\n  const mergedProps = mergeProps(childProps, props);\n\n  return (\n    // eslint-disable-next-line react-hooks/static-components\n    <ComponentType {...mergedProps} ref={mergeRefs(childRef as React.Ref<T>, ref)} />\n  );\n}\n\nexport {\n  Slot,\n  type SlotProps,\n  type WithAsChild,\n  type DOMMotionProps,\n  type AnyProps,\n};\n\ntype SlidingNumberRollerProps = {\n  prevValue: number;\n  value: number;\n  place: number;\n  transition: SpringOptions;\n  delay?: number;\n};\n\nfunction SlidingNumberRoller({\n  prevValue,\n  value,\n  place,\n  transition,\n  delay = 0,\n}: SlidingNumberRollerProps) {\n  const startNumber = Math.floor(prevValue / place) % 10;\n  const targetNumber = Math.floor(value / place) % 10;\n  const animatedValue = useSpring(startNumber, transition);\n\n  React.useEffect(() => {\n    const timeoutId = setTimeout(() => {\n      animatedValue.set(targetNumber);\n    }, delay);\n    return () => clearTimeout(timeoutId);\n  }, [targetNumber, animatedValue, delay]);\n\n  const [measureRef, { height }] = useMeasure();\n\n  return (\n    <span\n      ref={measureRef}\n      data-slot=\"sliding-number-roller\"\n      style={{\n        position: 'relative',\n        display: 'inline-block',\n        width: '1ch',\n        overflowX: 'visible',\n        overflowY: 'clip',\n        lineHeight: 1,\n        fontVariantNumeric: 'tabular-nums',\n      }}\n    >\n      <span style={{ visibility: 'hidden' }}>0</span>\n      {Array.from({ length: 10 }, (_, i) => (\n        <SlidingNumberDisplay\n          key={i}\n          motionValue={animatedValue}\n          number={i}\n          height={height}\n          transition={transition}\n        />\n      ))}\n    </span>\n  );\n}\n\ntype SlidingNumberDisplayProps = {\n  motionValue: MotionValue<number>;\n  number: number;\n  height: number;\n  transition: SpringOptions;\n};\n\nfunction SlidingNumberDisplay({\n  motionValue,\n  number,\n  height,\n  transition,\n}: SlidingNumberDisplayProps) {\n  const y = useTransform(motionValue, (latest) => {\n    if (!height) return 0;\n    const currentNumber = latest % 10;\n    const offset = (10 + number - currentNumber) % 10;\n    let translateY = offset * height;\n    if (offset > 5) translateY -= 10 * height;\n    return translateY;\n  });\n\n  if (!height) {\n    return (\n      <span style={{ visibility: 'hidden', position: 'absolute' }}>\n        {number}\n      </span>\n    );\n  }\n\n  return (\n    <motion.span\n      data-slot=\"sliding-number-display\"\n      style={{\n        y,\n        position: 'absolute',\n        inset: 0,\n        display: 'flex',\n        alignItems: 'center',\n        justifyContent: 'center',\n      }}\n      transition={{ ...transition, type: 'spring' }}\n    >\n      {number}\n    </motion.span>\n  );\n}\n\ntype SlidingNumberProps = Omit<HTMLMotionProps<'span'>, 'children'> & {\n  number: number;\n  fromNumber?: number;\n  onNumberChange?: (number: number) => void;\n  padStart?: boolean;\n  decimalSeparator?: string;\n  decimalPlaces?: number;\n  thousandSeparator?: string;\n  transition?: SpringOptions;\n  delay?: number;\n  initiallyStable?: boolean;\n} & UseIsInViewOptions;\n\nfunction SlidingNumber({\n  ref,\n  number,\n  fromNumber,\n  onNumberChange,\n  inView = false,\n  inViewMargin = '0px',\n  inViewOnce = true,\n  padStart = false,\n  decimalSeparator = '.',\n  decimalPlaces = 0,\n  thousandSeparator,\n  transition = { stiffness: 200, damping: 20, mass: 0.4 },\n  delay = 0,\n  initiallyStable = false,\n  ...props\n}: SlidingNumberProps) {\n  const { ref: localRef, isInView } = useIsInView(\n    ref as React.Ref<HTMLElement>,\n    {\n      inView,\n      inViewOnce,\n      inViewMargin,\n    },\n  );\n\n  const initialNumeric = Math.abs(Number(number));\n  const prevNumberRef = React.useRef<number>(\n    initiallyStable ? initialNumeric : 0,\n  );\n\n  const hasAnimated = fromNumber !== undefined;\n\n  const motionVal = useMotionValue(\n    initiallyStable ? initialNumeric : (fromNumber ?? 0),\n  );\n  const springVal = useSpring(motionVal, { stiffness: 90, damping: 50 });\n\n  const skippedInitialWhenStable = React.useRef(false);\n\n  React.useEffect(() => {\n    if (!hasAnimated) return;\n    if (initiallyStable && !skippedInitialWhenStable.current) {\n      skippedInitialWhenStable.current = true;\n      return;\n    }\n    const timeoutId = setTimeout(() => {\n      if (isInView) motionVal.set(number);\n    }, delay);\n    return () => clearTimeout(timeoutId);\n  }, [hasAnimated, initiallyStable, isInView, number, motionVal, delay]);\n\n  const [effectiveNumber, setEffectiveNumber] = React.useState<number>(\n    initiallyStable ? initialNumeric : 0,\n  );\n\n  React.useEffect(() => {\n    if (hasAnimated) {\n      const inferredDecimals =\n        typeof decimalPlaces === 'number' && decimalPlaces >= 0\n          ? decimalPlaces\n          : (() => {\n            const s = String(number);\n            const idx = s.indexOf('.');\n            return idx >= 0 ? s.length - idx - 1 : 0;\n          })();\n\n      const factor = Math.pow(10, inferredDecimals);\n\n      const unsubscribe = springVal.on('change', (latest: number) => {\n        const newValue =\n          inferredDecimals > 0\n            ? Math.round(latest * factor) / factor\n            : Math.round(latest);\n\n        if (effectiveNumber !== newValue) {\n          setEffectiveNumber(newValue);\n          onNumberChange?.(newValue);\n        }\n      });\n      return () => unsubscribe();\n    } else {\n      setEffectiveNumber(\n        initiallyStable ? initialNumeric : !isInView ? 0 : initialNumeric,\n      );\n    }\n  }, [\n    hasAnimated,\n    springVal,\n    isInView,\n    number,\n    decimalPlaces,\n    onNumberChange,\n    effectiveNumber,\n    initiallyStable,\n    initialNumeric,\n  ]);\n\n  const formatNumber = React.useCallback(\n    (num: number) =>\n      decimalPlaces != null ? num.toFixed(decimalPlaces) : num.toString(),\n    [decimalPlaces],\n  );\n\n  const numberStr = formatNumber(effectiveNumber);\n  const [newIntStrRaw, newDecStrRaw = ''] = numberStr.split('.');\n\n  const finalIntLength = padStart\n    ? Math.max(\n      Math.floor(Math.abs(number)).toString().length,\n      newIntStrRaw.length,\n    )\n    : newIntStrRaw.length;\n\n  const newIntStr = padStart\n    ? newIntStrRaw.padStart(finalIntLength, '0')\n    : newIntStrRaw;\n\n  const prevFormatted = formatNumber(prevNumberRef.current);\n  const [prevIntStrRaw = '', prevDecStrRaw = ''] = prevFormatted.split('.');\n  const prevIntStr = padStart\n    ? prevIntStrRaw.padStart(finalIntLength, '0')\n    : prevIntStrRaw;\n\n  const adjustedPrevInt = React.useMemo(() => {\n    return prevIntStr.length > finalIntLength\n      ? prevIntStr.slice(-finalIntLength)\n      : prevIntStr.padStart(finalIntLength, '0');\n  }, [prevIntStr, finalIntLength]);\n\n  const adjustedPrevDec = React.useMemo(() => {\n    if (!newDecStrRaw) return '';\n    return prevDecStrRaw.length > newDecStrRaw.length\n      ? prevDecStrRaw.slice(0, newDecStrRaw.length)\n      : prevDecStrRaw.padEnd(newDecStrRaw.length, '0');\n  }, [prevDecStrRaw, newDecStrRaw]);\n\n  React.useEffect(() => {\n    if (isInView || initiallyStable) {\n      prevNumberRef.current = effectiveNumber;\n    }\n  }, [effectiveNumber, isInView, initiallyStable]);\n\n  const intPlaces = React.useMemo(\n    () =>\n      Array.from({ length: finalIntLength }, (_, i) =>\n        Math.pow(10, finalIntLength - i - 1),\n      ),\n    [finalIntLength],\n  );\n  const decPlaces = React.useMemo(\n    () =>\n      newDecStrRaw\n        ? Array.from({ length: newDecStrRaw.length }, (_, i) =>\n          Math.pow(10, newDecStrRaw.length - i - 1),\n        )\n        : [],\n    [newDecStrRaw],\n  );\n\n  const newDecValue = newDecStrRaw ? parseInt(newDecStrRaw, 10) : 0;\n  const prevDecValue = adjustedPrevDec ? parseInt(adjustedPrevDec, 10) : 0;\n\n  return (\n    <motion.span\n      ref={localRef}\n      data-slot=\"sliding-number\"\n      style={{\n        display: 'inline-flex',\n        alignItems: 'center',\n      }}\n      {...props}\n    >\n      {isInView && Number(number) < 0 && (\n        <span style={{ marginRight: '0.25rem' }}>-</span>\n      )}\n\n      {intPlaces.map((place, idx) => {\n        const digitsToRight = intPlaces.length - idx - 1;\n        const isSeparatorPosition =\n          typeof thousandSeparator !== 'undefined' &&\n          digitsToRight > 0 &&\n          digitsToRight % 3 === 0;\n\n        return (\n          <React.Fragment key={`int-${place}`}>\n            <SlidingNumberRoller\n              prevValue={parseInt(adjustedPrevInt, 10)}\n              value={parseInt(newIntStr ?? '0', 10)}\n              place={place}\n              transition={transition}\n            />\n            {isSeparatorPosition && <span>{thousandSeparator}</span>}\n          </React.Fragment>\n        );\n      })}\n\n      {newDecStrRaw && (\n        <>\n          <span>{decimalSeparator}</span>\n          {decPlaces.map((place) => (\n            <SlidingNumberRoller\n              key={`dec-${place}`}\n              prevValue={prevDecValue}\n              value={newDecValue}\n              place={place}\n              transition={transition}\n              delay={delay}\n            />\n          ))}\n        </>\n      )}\n    </motion.span>\n  );\n}\n\nexport { SlidingNumber, type SlidingNumberProps };\n\ninterface UseIsInViewOptions {\n  inView?: boolean;\n  inViewOnce?: boolean;\n  inViewMargin?: UseInViewOptions['margin'];\n}\n\nfunction useIsInView<T extends HTMLElement = HTMLElement>(\n  ref: React.Ref<T>,\n  options: UseIsInViewOptions = {},\n) {\n  const { inView, inViewOnce = false, inViewMargin = '0px' } = options;\n  const localRef = React.useRef<T>(null);\n  React.useImperativeHandle(ref, () => localRef.current as T);\n  const inViewResult = useInView(localRef, {\n    once: inViewOnce,\n    margin: inViewMargin,\n  });\n  const isInView = !inView || inViewResult;\n  return { ref: localRef, isInView };\n}\n\ntype Side = 'top' | 'bottom' | 'left' | 'right';\ntype Align = 'start' | 'center' | 'end';\n\ntype ParticlesContextType = {\n  animate: boolean;\n  isInView: boolean;\n};\n\nconst [ParticlesProvider, useParticles] =\n  getStrictContext<ParticlesContextType>('ParticlesContext');\n\ntype ParticlesProps = WithAsChild<\n  Omit<HTMLMotionProps<'div'>, 'children'> & {\n    animate?: boolean;\n    children: React.ReactNode;\n  } & UseIsInViewOptions\n>;\n\nfunction Particles({\n  ref,\n  animate = true,\n  asChild = false,\n  inView = false,\n  inViewMargin = '0px',\n  inViewOnce = true,\n  children,\n  style,\n  ...props\n}: ParticlesProps) {\n  const { ref: localRef, isInView } = useIsInView(\n    ref as React.Ref<HTMLDivElement>,\n    { inView, inViewOnce, inViewMargin },\n  );\n\n  const Component = asChild ? Slot : motion.div;\n\n  return (\n    <ParticlesProvider value={{ animate, isInView }}>\n      <Component\n        ref={localRef}\n        style={{ position: 'relative', ...style }}\n        {...props}\n      >\n        {children}\n      </Component>\n    </ParticlesProvider>\n  );\n}\n\ntype ParticlesEffectProps = Omit<HTMLMotionProps<'div'>, 'children'> & {\n  side?: Side;\n  align?: Align;\n  count?: number;\n  radius?: number;\n  spread?: number;\n  duration?: number;\n  holdDelay?: number;\n  sideOffset?: number;\n  alignOffset?: number;\n  delay?: number;\n};\n\nfunction ParticlesEffect({\n  side = 'top',\n  align = 'center',\n  count = 6,\n  radius = 30,\n  spread = 360,\n  duration = 0.8,\n  holdDelay = 0.05,\n  sideOffset = 0,\n  alignOffset = 0,\n  delay = 0,\n  transition,\n  style,\n  ...props\n}: ParticlesEffectProps) {\n  const { animate, isInView } = useParticles();\n\n  const isVertical = side === 'top' || side === 'bottom';\n  const alignPct = align === 'start' ? '0%' : align === 'end' ? '100%' : '50%';\n\n  const top = isVertical\n    ? side === 'top'\n      ? `calc(0% - ${sideOffset}px)`\n      : `calc(100% + ${sideOffset}px)`\n    : `calc(${alignPct} + ${alignOffset}px)`;\n\n  const left = isVertical\n    ? `calc(${alignPct} + ${alignOffset}px)`\n    : side === 'left'\n      ? `calc(0% - ${sideOffset}px)`\n      : `calc(100% + ${sideOffset}px)`;\n\n  const containerStyle: React.CSSProperties = {\n    position: 'absolute',\n    top,\n    left,\n    transform: 'translate(-50%, -50%)',\n  };\n\n  const angleStep = (spread * (Math.PI / 180)) / Math.max(1, count - 1);\n\n  return (\n    <AnimatePresence>\n      {animate &&\n        isInView &&\n        [...Array(count)].map((_, i) => {\n          const angle = i * angleStep;\n          const x = Math.cos(angle) * radius;\n          const y = Math.sin(angle) * radius;\n\n          return (\n            <motion.div\n              key={i}\n              style={{ ...containerStyle, ...style }}\n              initial={{ scale: 0, opacity: 0 }}\n              animate={{\n                x: `${x}px`,\n                y: `${y}px`,\n                scale: [0, 1, 0],\n                opacity: [0, 1, 0],\n              }}\n              transition={{\n                duration,\n                delay: delay + i * holdDelay,\n                ease: 'easeOut',\n                ...transition,\n              }}\n              {...props}\n            />\n          );\n        })}\n    </AnimatePresence>\n  );\n}\n\nexport {\n  Particles,\n  ParticlesEffect,\n  type ParticlesProps,\n  type ParticlesEffectProps,\n};\n\nfunction getStrictContext<T>(\n  name?: string,\n): readonly [\n  ({\n    value,\n    children,\n  }: {\n    value: T;\n    children?: React.ReactNode;\n  }) => React.JSX.Element,\n  () => T,\n] {\n  const Context = React.createContext<T | undefined>(undefined);\n\n  const Provider = ({\n    value,\n    children,\n  }: {\n    value: T;\n    children?: React.ReactNode;\n  }) => <Context.Provider value={value}>{children}</Context.Provider>;\n\n  const useSafeContext = () => {\n    const ctx = React.useContext(Context);\n    if (ctx === undefined) {\n      throw new Error(`useContext must be used within ${name ?? 'a Provider'}`);\n    }\n    return ctx;\n  };\n\n  return [Provider, useSafeContext] as const;\n}\n\nexport { getStrictContext };\n",
      "type": "registry:ui",
      "target": "components/ui/github-stars.tsx"
    }
  ],
  "type": "registry:ui"
}
