{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "like-burst",
  "type": "registry:component",
  "title": "Like Burst",
  "description": "A heart that squeezes and pops full with a thin ring and a flick of sparks, while the count rolls over digit by digit like an odometer; unliking fades back quietly.",
  "categories": [
    "motion",
    "Celebration"
  ],
  "dependencies": [
    "lucide-react",
    "motion"
  ],
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "registry/devl/like-burst.tsx",
      "type": "registry:component",
      "content": "\"use client\";\n\nimport { HeartIcon } from \"lucide-react\";\nimport { AnimatePresence, motion, useReducedMotion } from \"motion/react\";\nimport { type ComponentProps, useEffect, useState } from \"react\";\nimport { cn } from \"@/lib/utils\";\n\nfunction useReduce() {\n  const reduce = useReducedMotion();\n  const [mounted, setMounted] = useState(false);\n  useEffect(() => setMounted(true), []);\n  return mounted && !!reduce;\n}\n\nconst SPARKS = Array.from({ length: 8 }, (_, i) => i * 45);\nconst out = [0.22, 1, 0.36, 1] as const;\n\nfunction Burst() {\n  return (\n    <span aria-hidden className=\"pointer-events-none absolute inset-0\">\n      <motion.span\n        animate={{ scale: 2.1, opacity: [0, 0.7, 0] }}\n        className=\"absolute inset-0 rounded-full border-[1.5px] border-rose-500 dark:border-rose-400\"\n        initial={{ scale: 1, opacity: 0 }}\n        transition={{ duration: 0.5, delay: 0.08, ease: out, opacity: { duration: 0.5, delay: 0.08, times: [0, 0.1, 1] } }}\n      />\n      {SPARKS.map((angle, i) => (\n        <span className=\"absolute top-1/2 left-1/2\" key={angle} style={{ transform: `rotate(${angle}deg)` }}>\n          <motion.span\n            animate={{ y: i % 2 ? -16 : -19, opacity: [0, 1, 0], scaleY: 0.3 }}\n            className={cn(\n              \"absolute -mt-0.5 -ml-px block w-0.5 rounded-full\",\n              i % 2 ? \"h-1 bg-amber-400 dark:bg-amber-300\" : \"h-1.5 bg-rose-500 dark:bg-rose-400\",\n            )}\n            initial={{ y: -10, opacity: 0, scaleY: 1 }}\n            transition={{ duration: 0.4, delay: 0.1, ease: out, opacity: { duration: 0.4, delay: 0.1, times: [0, 0.12, 1] } }}\n          />\n        </span>\n      ))}\n    </span>\n  );\n}\n\nfunction Digit({ char, up, place, reduce }: { char: string; up: boolean; place: number; reduce: boolean }) {\n  return (\n    <span className=\"relative inline-flex overflow-hidden\">\n      <AnimatePresence custom={up} initial={false} mode=\"popLayout\">\n        <motion.span\n          animate={{ y: 0, opacity: 1 }}\n          custom={up}\n          exit=\"exit\"\n          initial={reduce ? { opacity: 0 } : { y: up ? \"100%\" : \"-100%\", opacity: 0 }}\n          key={char}\n          transition={{ duration: 0.22, delay: reduce ? 0 : place * 0.035, ease: out }}\n          variants={{ exit: (rising: boolean) => (reduce ? { opacity: 0 } : { y: rising ? \"-100%\" : \"100%\", opacity: 0 }) }}\n        >\n          {char}\n        </motion.span>\n      </AnimatePresence>\n    </span>\n  );\n}\n\nfunction RollingCount({ value, up, reduce }: { value: number; up: boolean; reduce: boolean }) {\n  const chars = [...value.toLocaleString(\"en-US\")];\n  return (\n    <span className=\"inline-flex\">\n      {chars.map((char, i) => {\n        const place = chars.length - 1 - i;\n        return <Digit char={char} key={place} place={place} reduce={reduce} up={up} />;\n      })}\n    </span>\n  );\n}\n\nexport interface LikeButtonProps extends Omit<ComponentProps<\"button\">, \"onChange\"> {\n  liked?: boolean;\n  defaultLiked?: boolean;\n  onLikedChange?: (liked: boolean) => void;\n  count?: number;\n}\n\nexport function LikeButton({\n  liked: likedProp,\n  defaultLiked = false,\n  onLikedChange,\n  count,\n  className,\n  onClick,\n  ...props\n}: LikeButtonProps) {\n  const reduce = useReduce();\n  const [internal, setInternal] = useState(defaultLiked);\n  const liked = likedProp ?? internal;\n  const [bursts, setBursts] = useState(0);\n  const total = count === undefined ? undefined : count + (liked ? 1 : 0);\n\n  return (\n    <button\n      aria-label={total === undefined ? \"Like\" : `Like, ${total} likes`}\n      aria-pressed={liked}\n      className={cn(\n        \"group relative inline-flex h-8 cursor-pointer select-none items-center gap-1.5 rounded-full px-2.5 text-muted-foreground text-xs tabular-nums outline-none transition-[color,background-color,scale] duration-150 focus-visible:ring-2 focus-visible:ring-ring active:scale-[0.96] [@media(hover:hover)]:hover:bg-rose-500/8 [@media(hover:hover)]:hover:text-rose-600 dark:[@media(hover:hover)]:hover:text-rose-400\",\n        liked && \"text-rose-600 dark:text-rose-400\",\n        className,\n      )}\n      onClick={(e) => {\n        onClick?.(e);\n        const next = !liked;\n        if (likedProp === undefined) setInternal(next);\n        onLikedChange?.(next);\n        if (next && !reduce) setBursts((n) => n + 1);\n      }}\n      type=\"button\"\n      {...props}\n    >\n      <span className=\"relative grid size-[18px] place-items-center\">\n        <motion.span\n          animate={liked && !reduce && bursts ? { scale: [1, 0.74, 1.14, 1] } : { scale: 1 }}\n          className=\"grid place-items-center\"\n          key={liked ? `on-${bursts}` : \"off\"}\n          transition={\n            liked ? { duration: 0.42, times: [0, 0.22, 0.62, 1], ease: [\"easeOut\", out, \"easeInOut\"] } : { duration: 0.15 }\n          }\n        >\n          <HeartIcon className=\"size-[18px]\" strokeWidth={2} />\n          <motion.span\n            animate={liked ? { opacity: 1, scale: 1 } : { opacity: 0, scale: reduce ? 1 : 0.9 }}\n            className=\"absolute inset-0 grid place-items-center text-rose-500 dark:text-rose-400\"\n            initial={false}\n            transition={liked ? { duration: 0.2, delay: reduce ? 0 : 0.08, ease: out } : { duration: 0.18, ease: \"easeOut\" }}\n          >\n            <HeartIcon className=\"size-[18px] fill-current\" strokeWidth={2} />\n          </motion.span>\n        </motion.span>\n        {liked && bursts > 0 && !reduce && <Burst key={bursts} />}\n      </span>\n      {total !== undefined && <RollingCount reduce={reduce} up={liked} value={total} />}\n    </button>\n  );\n}\n"
    }
  ]
}