{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "heartbeat",
  "title": "Heartbeat",
  "description": "A Motion-powered heartbeat primitive with a projected shadow layer and reduced-motion support.",
  "dependencies": [
    "motion"
  ],
  "files": [
    {
      "path": "registry/base/ui/heartbeat.tsx",
      "content": "\"use client\";\n\nimport { motion, useReducedMotion, type HTMLMotionProps } from \"motion/react\";\nimport * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst heartbeatScale = [1, 1.24, 0.93, 1.19, 0.96, 1, 1];\nconst heartbeatLift = [\"0%\", \"-4.2%\", \"1.1%\", \"-3.2%\", \"0.7%\", \"0%\", \"0%\"];\nconst heartbeatRotate = [0, -1.15, 0.62, -0.85, 0.34, 0, 0];\nconst heartbeatTimes = [0, 0.055, 0.13, 0.23, 0.34, 0.5, 1];\nconst maxHeartbeatLift = Math.max(...heartbeatScale) - 1;\nconst heartbeatProjection = heartbeatScale.map((scale) =>\n  Math.max(scale - 1, 0) / maxHeartbeatLift,\n);\nconst heartbeatShadowScale = heartbeatScale.map(\n  (scale, index) => scale + heartbeatProjection[index] * 0.055,\n);\n\nconst heartbeatAnimation = {\n  rotate: heartbeatRotate,\n  scale: heartbeatScale,\n  y: heartbeatLift,\n};\n\nconst heartbeatShadowAnimation = {\n  opacity: heartbeatProjection.map((distance) => 0.13 - distance * 0.04),\n  scale: heartbeatShadowScale,\n  x: heartbeatProjection.map((distance) => `${distance * -20}%`),\n  y: heartbeatProjection.map((distance) => `${distance * -5}%`),\n};\n\nconst heartbeatTransition = {\n  duration: 1.18,\n  times: heartbeatTimes,\n  ease: \"easeOut\" as const,\n  repeat: Infinity,\n  repeatDelay: 0.9,\n};\n\ntype HeartbeatTransition = NonNullable<HTMLMotionProps<\"span\">[\"transition\"]>;\n\n/**\n * Note: `children` is rendered twice — once as the blurred shadow layer and\n * once as the visible target. Keep it a simple icon or glyph: anything\n * carrying an `id`, or a heavy node like an `img`/`video`, would be duplicated\n * in the DOM. Set `showShadow={false}` to render it only once.\n */\nexport type HeartbeatProps = React.ComponentProps<\"span\"> & {\n  /** Classes applied to the visible animated child wrapper. */\n  targetClassName?: string;\n  /** Classes applied to the projected shadow layer. */\n  shadowClassName?: string;\n  /** Render the projected shadow layer. Defaults to true. */\n  showShadow?: boolean;\n  /** Override the default double-thump transition. */\n  transition?: HeartbeatTransition;\n};\n\nexport function Heartbeat({\n  className,\n  children,\n  shadowClassName,\n  showShadow = true,\n  targetClassName,\n  transition = heartbeatTransition,\n  ...props\n}: HeartbeatProps) {\n  const reduceMotion = useReducedMotion();\n  const resolvedTransition = reduceMotion ? { duration: 0 } : transition;\n\n  return (\n    <span\n      data-slot=\"heartbeat\"\n      className={cn(\n        \"relative inline-flex items-center justify-center\",\n        className,\n      )}\n      {...props}\n    >\n      {showShadow ? (\n        <motion.span\n          aria-hidden=\"true\"\n          data-slot=\"heartbeat-shadow\"\n          className={cn(\n            \"pointer-events-none absolute inset-0 z-0 text-current blur-[5px]\",\n            shadowClassName,\n          )}\n          style={{ transformOrigin: \"center 62%\" }}\n          animate={reduceMotion ? undefined : heartbeatShadowAnimation}\n          transition={resolvedTransition}\n        >\n          {children}\n        </motion.span>\n      ) : null}\n\n      <motion.span\n        data-slot=\"heartbeat-target\"\n        className={cn(\"relative z-10 inline-flex text-current\", targetClassName)}\n        style={{ transformOrigin: \"center 62%\" }}\n        animate={reduceMotion ? undefined : heartbeatAnimation}\n        transition={resolvedTransition}\n      >\n        {children}\n      </motion.span>\n    </span>\n  );\n}\n",
      "type": "registry:ui",
      "target": "components/ui/heartbeat.tsx"
    }
  ],
  "meta": {
    "tags": [
      "heartbeat",
      "pulse",
      "double-thump",
      "primitive"
    ],
    "effects": [
      "scale",
      "pulse",
      "projected-shadow",
      "reduced-motion"
    ]
  },
  "categories": [
    "effect"
  ],
  "type": "registry:ui"
}