{
	"$schema": "https://ui.shadcn.com/schema/registry-item.json",
	"name": "CountUp-JS-CSS",
	"title": "CountUp",
	"description": "Animated number counter supporting formatting and decimals.",
	"type": "registry:component",
	"files": [
		{
			"type": "registry:component",
			"path": "CountUp/CountUp.jsx",
			"content": "import { useInView, useMotionValue, useSpring } from 'motion/react';\nimport { useCallback, useEffect, useRef } from 'react';\n\nexport default function CountUp({\n  to,\n  from = 0,\n  direction = 'up',\n  delay = 0,\n  duration = 2,\n  className = '',\n  startWhen = true,\n  separator = '',\n  onStart,\n  onEnd\n}) {\n  const ref = useRef(null);\n  const motionValue = useMotionValue(direction === 'down' ? to : from);\n\n  const damping = 20 + 40 * (1 / duration);\n  const stiffness = 100 * (1 / duration);\n\n  const springValue = useSpring(motionValue, {\n    damping,\n    stiffness\n  });\n\n  const isInView = useInView(ref, { once: true, margin: '0px' });\n\n  const getDecimalPlaces = num => {\n    const str = num.toString();\n\n    if (str.includes('.')) {\n      const decimals = str.split('.')[1];\n\n      if (parseInt(decimals) !== 0) {\n        return decimals.length;\n      }\n    }\n\n    return 0;\n  };\n\n  const maxDecimals = Math.max(getDecimalPlaces(from), getDecimalPlaces(to));\n\n  const formatValue = useCallback(\n    latest => {\n      const hasDecimals = maxDecimals > 0;\n\n      const options = {\n        useGrouping: !!separator,\n        minimumFractionDigits: hasDecimals ? maxDecimals : 0,\n        maximumFractionDigits: hasDecimals ? maxDecimals : 0\n      };\n\n      const formattedNumber = Intl.NumberFormat('en-US', options).format(latest);\n\n      return separator ? formattedNumber.replace(/,/g, separator) : formattedNumber;\n    },\n    [maxDecimals, separator]\n  );\n\n  useEffect(() => {\n    if (ref.current) {\n      ref.current.textContent = formatValue(direction === 'down' ? to : from);\n    }\n  }, [from, to, direction, formatValue]);\n\n  useEffect(() => {\n    if (isInView && startWhen) {\n      if (typeof onStart === 'function') onStart();\n\n      const timeoutId = setTimeout(() => {\n        motionValue.set(direction === 'down' ? from : to);\n      }, delay * 1000);\n\n      const durationTimeoutId = setTimeout(\n        () => {\n          if (typeof onEnd === 'function') onEnd();\n        },\n        delay * 1000 + duration * 1000\n      );\n\n      return () => {\n        clearTimeout(timeoutId);\n        clearTimeout(durationTimeoutId);\n      };\n    }\n  }, [isInView, startWhen, motionValue, direction, from, to, delay, onStart, onEnd, duration]);\n\n  useEffect(() => {\n    const unsubscribe = springValue.on('change', latest => {\n      if (ref.current) {\n        ref.current.textContent = formatValue(latest);\n      }\n    });\n\n    return () => unsubscribe();\n  }, [springValue, formatValue]);\n\n  return <span className={className} ref={ref} />;\n}\n"
		}
	],
	"registryDependencies": [],
	"dependencies": [
		"motion@^12.23.12"
	]
}