{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "demo-animate-digits",
  "title": "Animate Digits Demo",
  "description": "Timer demo showcasing per-digit blur-slide animation.",
  "registryDependencies": [
    "@unlumen-ui/animate-digits"
  ],
  "files": [
    {
      "path": "registry/demo/components/unlumen/animate-digits/index.tsx",
      "content": "\"use client\";\n\nimport { useEffect, useRef, useState } from \"react\";\nimport { motion } from \"motion/react\";\nimport { AnimateDigits } from \"@/components/unlumen-ui/animate-digits\";\nimport { Button } from \"@/components/ui/button\";\n\nfunction formatTime(seconds: number): string {\n  const m = Math.floor(seconds / 60);\n  const s = seconds % 60;\n  return String(m).padStart(2, \"0\") + \":\" + String(s).padStart(2, \"0\");\n}\n\ninterface AnimateDigitsDemoProps {\n  gap?: number;\n  enterStiffness?: number;\n  enterDamping?: number;\n  exitStiffness?: number;\n  exitDamping?: number;\n  direction?: \"dynamic\" | \"up\" | \"down\";\n  enterY?: number;\n  enterBlur?: number;\n  enterScale?: number;\n  animationDelay?: number;\n}\n\nconst SCROLL_THRESHOLD = 80;\n\nexport default function AnimateDigitsDemo({\n  gap = 2,\n  enterStiffness,\n  enterDamping,\n  exitStiffness,\n  exitDamping,\n  direction,\n  enterY,\n  enterBlur,\n  enterScale,\n  animationDelay,\n}: AnimateDigitsDemoProps) {\n  const [minutes, setMinutes] = useState(5);\n  const [remaining, setRemaining] = useState<number | null>(null);\n  const [running, setRunning] = useState(false);\n  const [scrollDir, setScrollDir] = useState<\"up\" | \"down\" | null>(null);\n  const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null);\n  const scrollAccum = useRef(0);\n  const scrollIdleTimer = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n  const idle = !running && remaining === null;\n\n  function start() {\n    setRemaining(minutes * 60);\n    setRunning(true);\n  }\n\n  function reset() {\n    if (intervalRef.current) clearInterval(intervalRef.current);\n    setRunning(false);\n    setRemaining(null);\n  }\n\n  function handleWheel(e: React.WheelEvent) {\n    if (!idle) return;\n    e.preventDefault();\n    setScrollDir(e.deltaY > 0 ? \"down\" : \"up\");\n    if (scrollIdleTimer.current) clearTimeout(scrollIdleTimer.current);\n    scrollIdleTimer.current = setTimeout(() => setScrollDir(null), 400);\n    scrollAccum.current += e.deltaY;\n    if (Math.abs(scrollAccum.current) >= SCROLL_THRESHOLD) {\n      const steps = Math.trunc(scrollAccum.current / SCROLL_THRESHOLD);\n      scrollAccum.current -= steps * SCROLL_THRESHOLD;\n      setMinutes((m) => Math.max(1, Math.min(99, m - steps)));\n    }\n  }\n\n  useEffect(() => {\n    if (!running || remaining === null) return;\n    if (remaining <= 0) {\n      setRunning(false);\n      return;\n    }\n    intervalRef.current = setInterval(() => {\n      setRemaining((r) => {\n        if (r === null || r <= 1) {\n          clearInterval(intervalRef.current!);\n          setRunning(false);\n          return 0;\n        }\n        return r - 1;\n      });\n    }, 1000);\n    return () => clearInterval(intervalRef.current!);\n  }, [running, remaining]);\n\n  const display =\n    remaining !== null ? formatTime(remaining) : formatTime(minutes * 60);\n\n  return (\n    <div className=\"flex flex-col items-center justify-center gap-8 p-8\">\n      <span className=\"text-lg text-muted-foreground flex flex-col items-center gap-4 \">\n        {idle ? \"scroll to set time\" : \"Concentration\"}\n        <div className=\"h-25 w-px bg-gradient-to-b from-border\" />\n      </span>\n      <div\n        className=\"flex relative items-center gap-4\"\n        onWheel={handleWheel}\n        style={{ cursor: idle ? \"ns-resize\" : \"default\" }}\n      >\n        <div className=\"flex absolute left-55 flex-col items-center gap-[5px]\">\n          {[0, 1, 2].map((i) => {\n            const activeIndex =\n              scrollDir === \"up\" ? 0 : scrollDir === \"down\" ? 2 : 1;\n            const isActive = idle && activeIndex === i;\n            return (\n              <motion.div\n                key={i}\n                animate={{\n                  height: isActive ? 18 : 8,\n                  opacity: isActive ? 1 : 0.2,\n                  y:\n                    isActive && scrollDir === \"up\"\n                      ? -2\n                      : isActive && scrollDir === \"down\"\n                        ? 2\n                        : 0,\n                }}\n                transition={{ type: \"spring\", stiffness: 400, damping: 28 }}\n                className=\"w-[3px] rounded-full bg-foreground\"\n              />\n            );\n          })}\n        </div>\n        <div className=\"flex flex-col items-center gap-3\">\n          <AnimateDigits\n            value={display}\n            gap={gap}\n            className=\"text-7xl font-bold font-medium tracking-tighter\"\n            enterStiffness={enterStiffness}\n            enterDamping={enterDamping}\n            exitStiffness={exitStiffness}\n            exitDamping={exitDamping}\n            direction={direction}\n            enterY={enterY}\n            enterBlur={enterBlur}\n            enterScale={enterScale}\n            animationDelay={animationDelay}\n          />\n        </div>\n      </div>\n\n      {idle && (\n        <Button variant=\"outline\" size=\"md\" onClick={start}>\n          Start\n        </Button>\n      )}\n\n      {(running || remaining !== null) && (\n        <Button variant=\"destructive\" size=\"md\" onClick={reset}>\n          Reset\n        </Button>\n      )}\n    </div>\n  );\n}\n",
      "type": "registry:example"
    }
  ],
  "type": "registry:example"
}