{
  "name": "github-star",
  "dependencies": [
    "motion"
  ],
  "files": [
    {
      "path": "ui/github-star.tsx",
      "content": "\"use client\";\n\nimport { AnimatePresence, motion } from \"motion/react\";\nimport * as React from \"react\";\nimport { cn } from \"@/lib/utils\";\n\nexport interface GitHubStarButtonProps {\n  /**\n   * The owner of the GitHub repository\n   * @example \"johuniq\"\n   */\n  owner: string;\n  /**\n   * The name of the GitHub repository\n   * @example \"jolyui\"\n   */\n  repo: string;\n  /**\n   * Manual star count override. If provided, the component will not fetch from GitHub API.\n   */\n  stars?: number;\n  /**\n   * Additional CSS classes for the button\n   */\n  className?: string;\n}\n\ninterface Particle {\n  id: number;\n  x: number;\n  y: number;\n  angle: number;\n  scale: number;\n}\n\nconst StarIcon = ({\n  className,\n  filled,\n}: {\n  className?: string;\n  filled?: boolean;\n}) => (\n  <svg\n    viewBox=\"0 0 16 16\"\n    className={className}\n    fill={filled ? \"currentColor\" : \"none\"}\n    stroke=\"currentColor\"\n    strokeWidth={filled ? 0 : 1.5}\n    strokeLinecap=\"round\"\n    strokeLinejoin=\"round\"\n  >\n    <path d=\"M8 1.5l1.85 4.1 4.65.55-3.5 3.15.95 4.6L8 11.7l-4 2.2.95-4.6-3.5-3.15 4.65-.55L8 1.5z\" />\n  </svg>\n);\n\nconst GitHubIcon = ({ className }: { className?: string }) => (\n  <svg viewBox=\"0 0 16 16\" className={className} fill=\"currentColor\">\n    <path d=\"M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z\" />\n  </svg>\n);\n\nfunction formatNumber(num: number): string {\n  if (num >= 1000000) {\n    return `${(num / 1000000).toFixed(1).replace(/\\.0$/, \"\")}M`;\n  }\n  if (num >= 1000) {\n    return `${(num / 1000).toFixed(1).replace(/\\.0$/, \"\")}k`;\n  }\n  return num.toLocaleString();\n}\n\nfunction useGitHubStars(owner: string, repo: string, manualStars?: number) {\n  const [stars, setStars] = React.useState<number>(manualStars ?? 0);\n  const [loading, setLoading] = React.useState(!manualStars);\n\n  React.useEffect(() => {\n    if (manualStars !== undefined) {\n      setStars(manualStars);\n      setLoading(false);\n      return;\n    }\n\n    fetch(`https://api.github.com/repos/${owner}/${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(() => setLoading(false));\n  }, [owner, repo, manualStars]);\n\n  return { stars, loading };\n}\n\nconst AnimatedDigit = ({ digit }: { digit: string }) => {\n  const isNumber = /\\d/.test(digit);\n\n  if (!isNumber) {\n    return <span className=\"inline-block px-0.5\">{digit}</span>;\n  }\n\n  const num = parseInt(digit, 10);\n\n  return (\n    <span className=\"relative inline-block h-[1em] w-[0.6em] overflow-hidden\">\n      <motion.span\n        className=\"absolute top-0 left-0 flex w-full flex-col items-center\"\n        initial={{ y: 0 }}\n        animate={{ y: `${-num * 10}%` }}\n        transition={{\n          type: \"spring\",\n          stiffness: 100,\n          damping: 15,\n          mass: 1,\n        }}\n      >\n        {[0, 1, 2, 3, 4, 5, 6, 7, 8, 9].map((n) => (\n          <span\n            key={n}\n            className=\"flex h-[1em] items-center justify-center leading-none\"\n          >\n            {n}\n          </span>\n        ))}\n      </motion.span>\n    </span>\n  );\n};\n\nfunction RollingNumber({ value }: { value: number }) {\n  const formatted = formatNumber(value);\n  const digits = formatted.split(\"\");\n\n  return (\n    <div className=\"flex items-center\">\n      {digits.map((digit, i) => (\n        <AnimatedDigit key={`${i}-${digit}`} digit={digit} />\n      ))}\n    </div>\n  );\n}\n\nfunction StarParticles({ particles }: { particles: Particle[] }) {\n  return (\n    <AnimatePresence>\n      {particles.map((particle) => (\n        <motion.div\n          key={particle.id}\n          className=\"pointer-events-none absolute\"\n          initial={{\n            opacity: 1,\n            scale: particle.scale,\n            x: 0,\n            y: 0,\n          }}\n          animate={{\n            opacity: 0,\n            scale: 0,\n            x: Math.cos(particle.angle) * 60,\n            y: Math.sin(particle.angle) * 60,\n          }}\n          exit={{ opacity: 0 }}\n          transition={{ duration: 0.7, ease: [0.32, 0.72, 0, 1] }}\n          style={{\n            left: particle.x,\n            top: particle.y,\n          }}\n        >\n          <StarIcon className=\"h-3 w-3 text-star\" filled />\n        </motion.div>\n      ))}\n    </AnimatePresence>\n  );\n}\n\nexport function GitHubStarButton({\n  owner,\n  repo,\n  stars: manualStars,\n  className,\n}: GitHubStarButtonProps) {\n  const { stars, loading } = useGitHubStars(owner, repo, manualStars);\n  const [localStars, setLocalStars] = React.useState(0);\n  const [isHovered, setIsHovered] = React.useState(false);\n  const [isStarred, setIsStarred] = React.useState(false);\n  const [particles, setParticles] = React.useState<Particle[]>([]);\n  const buttonRef = React.useRef<HTMLAnchorElement>(null);\n\n  React.useEffect(() => {\n    if (stars > 0) {\n      setLocalStars(stars);\n    }\n  }, [stars]);\n\n  const handleClick = (e: React.MouseEvent) => {\n    if (!isStarred) {\n      e.preventDefault();\n      setIsStarred(true);\n      setLocalStars((prev) => prev + 1);\n\n      const centerX = 20;\n      const centerY = 20;\n\n      const newParticles: Particle[] = Array.from({ length: 12 }, (_, i) => ({\n        id: Date.now() + i,\n        x: centerX,\n        y: centerY,\n        angle: (Math.PI * 2 * i) / 12 + Math.random() * 0.3,\n        scale: 0.4 + Math.random() * 0.6,\n      }));\n\n      setParticles(newParticles);\n      setTimeout(() => setParticles([]), 800);\n\n      // After simulation, navigate to the repo\n      setTimeout(() => {\n        window.open(`https://github.com/${owner}/${repo}`, \"_blank\");\n      }, 600);\n    }\n  };\n\n  const repoUrl = `https://github.com/${owner}/${repo}`;\n\n  if (loading && localStars === 0) {\n    return (\n      <div\n        className={cn(\n          \"relative inline-flex animate-pulse items-center gap-3 rounded-xl border border-border bg-card px-4 py-2.5\",\n          className,\n        )}\n      >\n        <div className=\"h-5 w-5 rounded-full bg-muted\" />\n        <div className=\"h-5 w-px bg-border\" />\n        <div className=\"h-5 w-20 rounded bg-muted\" />\n      </div>\n    );\n  }\n\n  return (\n    <motion.a\n      ref={buttonRef}\n      href={repoUrl}\n      target=\"_blank\"\n      rel=\"noopener noreferrer\"\n      className={cn(\n        \"relative inline-flex items-center gap-3 rounded-xl px-4 py-2.5\",\n        \"border border-border bg-card\",\n        \"shadow-sm transition-all duration-300 hover:shadow-lg\",\n        \"group cursor-pointer no-underline\",\n        className,\n      )}\n      onMouseEnter={() => setIsHovered(true)}\n      onMouseLeave={() => setIsHovered(false)}\n      onClick={handleClick}\n      whileHover={{ scale: 1.03, y: -2 }}\n      whileTap={{ scale: 0.97 }}\n      transition={{ type: \"spring\", stiffness: 400, damping: 17 }}\n    >\n      {/* GitHub Icon */}\n      <GitHubIcon className=\"h-5 w-5 text-foreground transition-colors\" />\n\n      {/* Divider */}\n      <div className=\"h-5 w-px bg-border\" />\n\n      {/* Star Section */}\n      <div className=\"relative flex items-center gap-2\">\n        <StarParticles particles={particles} />\n\n        <motion.div\n          className=\"relative\"\n          animate={{\n            rotate: isHovered || isStarred ? [0, -15, 15, -10, 10, 0] : 0,\n            scale: isHovered || isStarred ? 1.2 : 1,\n          }}\n          transition={{\n            rotate: { duration: 0.5, ease: \"easeInOut\" },\n            scale: { type: \"spring\", stiffness: 300, damping: 15 },\n          }}\n        >\n          <StarIcon\n            className={cn(\n              \"h-5 w-5 transition-colors duration-300\",\n              isHovered || isStarred ? \"text-star\" : \"text-muted-foreground\",\n            )}\n            filled={isHovered || isStarred}\n          />\n\n          {/* Glow effect */}\n          <motion.div\n            className=\"absolute inset-0 blur-lg\"\n            initial={{ opacity: 0 }}\n            animate={{ opacity: isHovered || isStarred ? 0.8 : 0 }}\n            transition={{ duration: 0.3 }}\n          >\n            <StarIcon className=\"h-5 w-5 text-star-glow\" filled />\n          </motion.div>\n        </motion.div>\n\n        {/* Divider */}\n\n        {/* Count */}\n        <div className=\"min-w-[3rem] font-mono font-semibold text-foreground text-sm tabular-nums\">\n          <RollingNumber value={localStars} />\n        </div>\n      </div>\n\n      {/* Hover shimmer effect */}\n      <motion.div\n        className=\"pointer-events-none absolute inset-0 overflow-hidden rounded-xl\"\n        initial={{ opacity: 0 }}\n        animate={{ opacity: isHovered ? 1 : 0 }}\n        transition={{ duration: 0.3 }}\n      >\n        <motion.div\n          className=\"absolute inset-0 bg-gradient-to-r from-transparent via-star/10 to-transparent\"\n          animate={{\n            x: isHovered ? [\"100%\", \"-100%\"] : \"100%\",\n          }}\n          transition={{\n            duration: 1.2,\n            repeat: isHovered ? Infinity : 0,\n            repeatDelay: 0.8,\n            ease: \"easeInOut\",\n          }}\n        />\n      </motion.div>\n    </motion.a>\n  );\n}\n\nexport default GitHubStarButton;\n",
      "type": "registry:ui",
      "target": ""
    }
  ],
  "type": "registry:ui"
}