{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "otp-input",
  "type": "registry:ui",
  "title": "OTP Input",
  "description": "Ultra-smooth physics-based one-time password input with rolling character tumblers, sliding focus halo, kinetic caret stretching, and celebration choreography.",
  "dependencies": [
    "clsx",
    "tailwind-merge",
    "motion"
  ],
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "src/registry/ui/otp-input.tsx",
      "content": "\"use client\";\n\nimport React, {\n  useRef,\n  useState,\n  useId,\n  useEffect,\n  type ComponentProps,\n  type KeyboardEvent,\n  type ClipboardEvent,\n  type PointerEvent,\n} from \"react\";\nimport { AnimatePresence, motion, useReducedMotion } from \"motion/react\";\nimport { cn } from \"@/lib/utils\";\n\nconst PATTERNS = {\n  numbers: /^[0-9]$/,\n  letters: /^[a-zA-Z]$/,\n  both: /^[a-zA-Z0-9]$/,\n} as const;\n\nexport type OtpStatus = \"idle\" | \"success\" | \"error\" | \"loading\";\nexport type OtpSize = \"sm\" | \"md\" | \"lg\" | \"xl\";\nexport type OtpVariant = \"default\" | \"glass\" | \"neon\" | \"underlined\";\n\nexport interface OtpInputProps extends Omit<\n  ComponentProps<\"div\">,\n  \"onChange\" | \"value\" | \"defaultValue\"\n> {\n  length?: number;\n  value?: string;\n  defaultValue?: string;\n  onChange?: (value: string) => void;\n  onComplete?: (value: string) => void;\n  type?: keyof typeof PATTERNS;\n  size?: OtpSize;\n  variant?: OtpVariant;\n  status?: OtpStatus;\n  mask?: boolean;\n  disabled?: boolean;\n  autoFocus?: boolean;\n  separator?: React.ReactNode;\n  groupSize?: number;\n  slotClassName?: string;\n}\n\nconst SIZES = {\n  sm: {\n    box: \"size-10 rounded-lg\",\n    text: \"text-base\",\n    caret: \"h-5\",\n    caretHeight: 20,\n    gap: \"gap-2\",\n    px: 40,\n    radius: 8,\n  },\n  md: {\n    box: \"size-12 rounded-xl\",\n    text: \"text-lg\",\n    caret: \"h-6\",\n    caretHeight: 24,\n    gap: \"gap-2.5\",\n    px: 48,\n    radius: 12,\n  },\n  lg: {\n    box: \"size-14 rounded-2xl\",\n    text: \"text-xl\",\n    caret: \"h-7\",\n    caretHeight: 28,\n    gap: \"gap-3\",\n    px: 56,\n    radius: 16,\n  },\n  xl: {\n    box: \"size-16 rounded-2xl\",\n    text: \"text-2xl font-semibold\",\n    caret: \"h-8\",\n    caretHeight: 32,\n    gap: \"gap-3.5\",\n    px: 64,\n    radius: 16,\n  },\n} as const;\n\nconst SIZE_SPRING = {\n  type: \"spring\",\n  stiffness: 380,\n  damping: 28,\n  mass: 0.7,\n} as const;\n\nconst ROLL_SPRING = {\n  type: \"spring\",\n  stiffness: 520,\n  damping: 30,\n  mass: 0.75,\n} as const;\n\nconst CARET_SPRING = {\n  type: \"spring\",\n  stiffness: 600,\n  damping: 36,\n  mass: 0.6,\n} as const;\n\nconst SHAKE_KEYFRAMES = [0, -12, 10, -8, 6, -3, 0];\n\nconst toSlots = (code: string, length: number) =>\n  Array.from({ length }, (_, i) => code[i] ?? \"\");\n\nexport function OtpInput({\n  length = 6,\n  value,\n  defaultValue = \"\",\n  onChange,\n  onComplete,\n  type = \"numbers\",\n  size = \"md\",\n  variant = \"default\",\n  status = \"idle\",\n  mask = false,\n  disabled = false,\n  autoFocus = false,\n  separator,\n  groupSize,\n  className,\n  slotClassName,\n  ...props\n}: OtpInputProps) {\n  const instanceId = useId();\n  const [uncontrolled, setUncontrolled] = useState(() =>\n    toSlots(defaultValue, length),\n  );\n  const [clearedSlot, setClearedSlot] = useState<number | null>(null);\n  const [focusedIndex, setFocusedIndex] = useState<number | null>(null);\n  const [caretX, setCaretX] = useState<number>(0);\n  const [isCaretMoving, setIsCaretMoving] = useState(false);\n  const [lastPunchedIndex, setLastPunchedIndex] = useState<number | null>(null);\n  const [prevStatus, setPrevStatus] = useState(status);\n  const [isCelebrating, setIsCelebrating] = useState(false);\n\n  if (prevStatus !== status) {\n    setPrevStatus(status);\n    if (status === \"success\") {\n      setIsCelebrating(true);\n    } else {\n      setIsCelebrating(false);\n    }\n  }\n\n  const inputs = useRef<(HTMLInputElement | null)[]>([]);\n  const cells = useRef<(HTMLDivElement | null)[]>([]);\n  const rowRef = useRef<HTMLDivElement | null>(null);\n  const editingAt = useRef<number | null>(null);\n  const caretTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n  const reduceMotion = useReducedMotion();\n\n  useEffect(() => {\n    if (isCelebrating) {\n      const timer = setTimeout(\n        () => {\n          setIsCelebrating(false);\n        },\n        500 + length * 50,\n      );\n      return () => clearTimeout(timer);\n    }\n  }, [isCelebrating, length]);\n\n  const slots =\n    value === undefined\n      ? Array.from({ length }, (_, i) => uncontrolled[i] ?? \"\")\n      : toSlots(value, length);\n\n  const numeric = type === \"numbers\";\n  const scale = SIZES[size];\n  const caretVisible =\n    focusedIndex !== null && !slots[focusedIndex] && status !== \"error\";\n\n  const updateCaretPosition = (index: number) => {\n    const cell = cells.current[index];\n    const row = rowRef.current;\n    if (cell && row) {\n      const cellRect = cell.getBoundingClientRect();\n      const rowRect = row.getBoundingClientRect();\n      const targetX =\n        cell.offsetParent === row\n          ? cell.offsetLeft + cell.offsetWidth / 2\n          : cellRect.left - rowRect.left + cellRect.width / 2;\n\n      setIsCaretMoving(true);\n      setCaretX(targetX);\n\n      if (caretTimerRef.current) clearTimeout(caretTimerRef.current);\n      caretTimerRef.current = setTimeout(() => {\n        setIsCaretMoving(false);\n      }, 240);\n    }\n  };\n\n  useEffect(() => {\n    if (focusedIndex !== null) {\n      updateCaretPosition(focusedIndex);\n      const frame = requestAnimationFrame(() => {\n        updateCaretPosition(focusedIndex);\n      });\n      return () => cancelAnimationFrame(frame);\n    }\n  }, [focusedIndex, size, length]);\n\n  const commit = (next: string[]) => {\n    if (value === undefined) setUncontrolled(next);\n    const code = next.join(\"\");\n    onChange?.(code);\n    if (next.every(Boolean)) onComplete?.(code);\n  };\n\n  const focusAt = (index: number) => {\n    const target = Math.min(Math.max(index, 0), length - 1);\n    const input = inputs.current[target];\n    input?.focus();\n    input?.select();\n    setFocusedIndex(target);\n    updateCaretPosition(target);\n  };\n\n  const setCharAt = (index: number, char: string) => {\n    if (!char) {\n      setClearedSlot(index);\n    } else {\n      setClearedSlot(null);\n      setLastPunchedIndex(index);\n      setTimeout(() => setLastPunchedIndex(null), 180);\n    }\n\n    const next = slots.map((slot, i) => (i === index ? char : slot));\n    commit(next);\n  };\n\n  const fill = (startIndex: number, chars: string[]) => {\n    const availableSpace = Math.min(chars.length, length - startIndex);\n    const next = [...slots];\n    chars.slice(0, availableSpace).forEach((char, i) => {\n      next[startIndex + i] = char;\n    });\n    setClearedSlot(null);\n    commit(next);\n    editingAt.current = null;\n    focusAt(startIndex + availableSpace);\n  };\n\n  const handleChange = (index: number, raw: string) => {\n    const filtered = raw.split(\"\").filter((char) => PATTERNS[type].test(char));\n    if (!filtered.length) {\n      if (raw === \"\") {\n        setCharAt(index, \"\");\n      }\n      return;\n    }\n\n    const typedChar =\n      filtered.length === 1\n        ? filtered[0]\n        : filtered.length === 2 && filtered[0] === slots[index]\n          ? filtered[1]\n          : null;\n\n    if (typedChar === null) {\n      fill(index, filtered);\n      return;\n    }\n\n    if (slots.every(Boolean) && editingAt.current !== index) return;\n\n    setCharAt(index, typedChar);\n    editingAt.current = null;\n    focusAt(index + 1);\n  };\n\n  const handleKeyDown = (\n    index: number,\n    event: KeyboardEvent<HTMLInputElement>,\n  ) => {\n    if (event.key === \"ArrowLeft\") {\n      event.preventDefault();\n      editingAt.current = Math.max(index - 1, 0);\n      focusAt(index - 1);\n      return;\n    }\n\n    if (event.key === \"ArrowRight\") {\n      event.preventDefault();\n      editingAt.current = Math.min(index + 1, length - 1);\n      focusAt(index + 1);\n      return;\n    }\n\n    if (event.key === \"Backspace\") {\n      event.preventDefault();\n      if (slots[index]) {\n        setCharAt(index, \"\");\n      } else if (index > 0) {\n        setCharAt(index - 1, \"\");\n        focusAt(index - 1);\n      }\n      return;\n    }\n\n    if (event.key === \"Delete\") {\n      event.preventDefault();\n      setCharAt(index, \"\");\n      return;\n    }\n  };\n\n  const handlePaste = (\n    index: number,\n    event: ClipboardEvent<HTMLInputElement>,\n  ) => {\n    event.preventDefault();\n    const pasted = event.clipboardData\n      .getData(\"text\")\n      .split(\"\")\n      .filter((char) => PATTERNS[type].test(char));\n    if (pasted.length) fill(index, pasted);\n  };\n\n  const handlePointerDown = (\n    index: number,\n    event: PointerEvent<HTMLInputElement>,\n  ) => {\n    const firstEmpty = slots.findIndex((slot) => !slot);\n    const target = firstEmpty === -1 ? index : Math.min(index, firstEmpty);\n    editingAt.current = target;\n    if (target === index) return;\n    event.preventDefault();\n    focusAt(target);\n  };\n\n  const getVariantStyles = (filled: boolean, isFocused: boolean) => {\n    switch (variant) {\n      case \"glass\":\n        return cn(\n          \"bg-black/[0.03] dark:bg-white/3 backdrop-blur-md border border-black/10 dark:border-white/10 shadow-[inset_0_1px_1px_rgba(0,0,0,0.05)] dark:shadow-[inset_0_1px_1px_rgba(255,255,255,0.1)]\",\n          filled &&\n            \"border-black/25 bg-black/[0.06] dark:border-white/25 dark:bg-white/8\",\n          isFocused &&\n            \"border-black/40 shadow-[0_0_20px_rgba(0,0,0,0.1)] dark:border-white/40 dark:shadow-[0_0_20px_rgba(255,255,255,0.15)]\",\n        );\n      case \"neon\":\n        return cn(\n          \"bg-zinc-100 dark:bg-zinc-950 border border-zinc-300 dark:border-zinc-800 shadow-[0_0_12px_rgba(0,0,0,0.05)] dark:shadow-[0_0_12px_rgba(0,0,0,0.5)]\",\n          filled &&\n            \"border-zinc-500 shadow-[0_0_16px_rgba(0,0,0,0.08)] dark:border-zinc-600 dark:shadow-[0_0_16px_rgba(255,255,255,0.06)]\",\n          isFocused &&\n            \"border-zinc-900 shadow-[0_0_24px_rgba(0,0,0,0.15)] dark:border-zinc-300 dark:shadow-[0_0_24px_rgba(255,255,255,0.2)]\",\n        );\n      case \"underlined\":\n        return cn(\n          \"bg-transparent border-b-2 rounded-none! border-zinc-300 dark:border-zinc-700 shadow-none\",\n          filled && \"border-zinc-600 dark:border-zinc-400\",\n          isFocused && \"border-zinc-950 dark:border-white\",\n        );\n      default:\n        return cn(\n          \"bg-zinc-100/90 dark:bg-[#161619] border border-black/10 dark:border-white/10 shadow-[0_2px_8px_rgba(0,0,0,0.04),inset_0_1px_0_rgba(255,255,255,0.8)] dark:shadow-[0_2px_8px_rgba(0,0,0,0.04),inset_0_1px_0_rgba(255,255,255,0.08)]\",\n          filled &&\n            \"border-black/25 dark:border-white/20 bg-zinc-200/60 dark:bg-[#1a1a1e]\",\n          isFocused && \"border-black/40 dark:border-white/35\",\n        );\n    }\n  };\n\n  return (\n    <div\n      data-slot=\"otp-input\"\n      data-status={status}\n      data-variant={variant}\n      role=\"group\"\n      aria-label=\"One-time code\"\n      className={cn(\n        \"relative inline-flex flex-col items-center select-none\",\n        className,\n      )}\n      {...props}\n    >\n      <motion.div\n        ref={rowRef}\n        data-slot=\"otp-input-row\"\n        layout\n        transition={{\n          layout: reduceMotion ? { duration: 0 } : SIZE_SPRING,\n          duration: 0.42,\n          ease: [0.36, 0.66, 0.04, 1],\n        }}\n        onBlur={(event) => {\n          if (!event.currentTarget.contains(event.relatedTarget as Node)) {\n            setFocusedIndex(null);\n          }\n        }}\n        animate={{\n          x: status === \"error\" && !reduceMotion ? SHAKE_KEYFRAMES : 0,\n        }}\n        className={cn(\"relative flex items-center\", scale.gap)}\n      >\n        {slots.map((slot, index) => {\n          const isFocused = focusedIndex === index;\n          const isFilled = Boolean(slot);\n          const isLastPunched = lastPunchedIndex === index;\n          const showSeparator =\n            separator && groupSize && index > 0 && index % groupSize === 0;\n\n          return (\n            <React.Fragment key={index}>\n              {showSeparator && (\n                <motion.div\n                  layout\n                  transition={{\n                    layout: reduceMotion ? { duration: 0 } : SIZE_SPRING,\n                  }}\n                  aria-hidden=\"true\"\n                  className=\"shrink-0 flex items-center justify-center text-zinc-500 dark:text-zinc-600 px-0.5\"\n                >\n                  {separator}\n                </motion.div>\n              )}\n\n              <motion.div\n                ref={(el) => {\n                  cells.current[index] = el;\n                }}\n                data-slot=\"otp-input-cell\"\n                data-filled={isFilled}\n                data-focused={isFocused}\n                layout\n                transition={{\n                  layout: reduceMotion ? { duration: 0 } : SIZE_SPRING,\n                }}\n                whileHover={\n                  disabled ||\n                  reduceMotion ||\n                  status === \"success\" ||\n                  status === \"error\" ||\n                  isCelebrating\n                    ? undefined\n                    : { y: -1.5, scale: 1.02 }\n                }\n                animate={\n                  isCelebrating && !reduceMotion\n                    ? {\n                        y: [0, -10, 0],\n                        scale: [1, 1.07, 1],\n                        transition: {\n                          duration: 0.5,\n                          delay: index * 0.05,\n                          ease: [0.34, 1.56, 0.64, 1],\n                        },\n                      }\n                    : status === \"loading\" && !reduceMotion\n                      ? {\n                          opacity: [0.5, 1, 0.5],\n                          y: [0, -3, 0],\n                          transition: {\n                            repeat: Infinity,\n                            duration: 1.2,\n                            delay: index * 0.12,\n                            ease: \"easeInOut\",\n                          },\n                        }\n                      : isLastPunched && !reduceMotion\n                        ? {\n                            scale: [1, 1.12, 1],\n                            y: [0, -2, 0],\n                            transition: { duration: 0.22, ease: \"easeOut\" },\n                          }\n                        : isFocused && !reduceMotion && status !== \"success\"\n                          ? {\n                              scale: 1.04,\n                              y: -1.5,\n                              transition: {\n                                type: \"spring\",\n                                stiffness: 450,\n                                damping: 28,\n                              },\n                            }\n                          : {\n                              scale: 1,\n                              y: 0,\n                              transition: {\n                                type: \"spring\",\n                                stiffness: 450,\n                                damping: 28,\n                              },\n                            }\n                }\n                className={cn(\n                  \"relative flex items-center justify-center transition-colors duration-200\",\n                  scale.box,\n                  getVariantStyles(isFilled, isFocused),\n                  slotClassName,\n                )}\n              >\n                <AnimatePresence>\n                  {isFocused && !reduceMotion && status !== \"success\" && (\n                    <motion.div\n                      layoutId={\"otp-active-glow-\" + instanceId}\n                      layout\n                      transition={{\n                        layout: reduceMotion ? { duration: 0 } : SIZE_SPRING,\n                        type: \"spring\",\n                        stiffness: 450,\n                        damping: 34,\n                        mass: 0.6,\n                      }}\n                      className={cn(\n                        \"pointer-events-none absolute -inset-0.5 rounded-[inherit] border-2 border-zinc-950/60 shadow-[0_0_18px_rgba(0,0,0,0.12)] dark:border-white/50 dark:shadow-[0_0_18px_rgba(255,255,255,0.2)] z-20\",\n                        variant === \"underlined\" &&\n                          \"border-0 border-b-2 rounded-none! shadow-[0_4px_12px_rgba(0,0,0,0.15)] dark:shadow-[0_4px_12px_rgba(255,255,255,0.3)] inset-x-0 -bottom-0.5 top-auto h-0.5\",\n                        status === \"error\" &&\n                          \"border-red-500/80 shadow-[0_0_20px_rgba(239,68,68,0.4)]\",\n                      )}\n                    />\n                  )}\n                </AnimatePresence>\n\n                <input\n                  ref={(el) => {\n                    inputs.current[index] = el;\n                  }}\n                  data-slot=\"otp-input-slot\"\n                  data-filled={isFilled}\n                  value={slot}\n                  onChange={(event) => handleChange(index, event.target.value)}\n                  onKeyDown={(event) => handleKeyDown(index, event)}\n                  onPaste={(event) => handlePaste(index, event)}\n                  onPointerDown={(event) => handlePointerDown(index, event)}\n                  onFocus={() => {\n                    setFocusedIndex(index);\n                    updateCaretPosition(index);\n                  }}\n                  type={mask ? \"password\" : \"text\"}\n                  inputMode={numeric ? \"numeric\" : \"text\"}\n                  autoCapitalize={numeric ? undefined : \"characters\"}\n                  autoComplete={index === 0 ? \"one-time-code\" : \"off\"}\n                  autoFocus={autoFocus && index === 0}\n                  disabled={disabled}\n                  aria-label={\n                    (numeric ? \"Digit \" : \"Character \") +\n                    (index + 1) +\n                    \" of \" +\n                    length\n                  }\n                  className=\"absolute inset-0 size-full opacity-0 cursor-pointer disabled:cursor-not-allowed z-30\"\n                />\n\n                <AnimatePresence>\n                  {status === \"success\" && (\n                    <motion.svg\n                      aria-hidden=\"true\"\n                      data-slot=\"otp-input-ring\"\n                      viewBox={\"0 0 \" + scale.px + \" \" + scale.px}\n                      exit={{ opacity: 0 }}\n                      transition={{ duration: 0.2 }}\n                      className=\"pointer-events-none absolute inset-0 size-full z-20 overflow-visible\"\n                    >\n                      {variant === \"underlined\" ? (\n                        <motion.line\n                          x1={0}\n                          y1={scale.px - 1}\n                          x2={scale.px}\n                          y2={scale.px - 1}\n                          stroke=\"#10b981\"\n                          strokeWidth={2.5}\n                          strokeLinecap=\"round\"\n                          initial={\n                            reduceMotion ? false : { pathLength: 0, opacity: 0 }\n                          }\n                          animate={{ pathLength: 1, opacity: 1 }}\n                          transition={\n                            reduceMotion\n                              ? { duration: 0 }\n                              : {\n                                  duration: 0.45,\n                                  ease: [0.16, 1, 0.3, 1],\n                                  delay: 0.08 + index * 0.05,\n                                }\n                          }\n                          style={{\n                            filter: \"drop-shadow(0 0 6px rgba(16,185,129,0.7))\",\n                          }}\n                        />\n                      ) : (\n                        <motion.rect\n                          x={1.5}\n                          y={1.5}\n                          width={scale.px - 3}\n                          height={scale.px - 3}\n                          rx={scale.radius - 1}\n                          fill=\"none\"\n                          stroke=\"#10b981\"\n                          strokeWidth={2.5}\n                          strokeLinecap=\"round\"\n                          initial={\n                            reduceMotion ? false : { pathLength: 0, opacity: 0 }\n                          }\n                          animate={{ pathLength: 1, opacity: 1 }}\n                          transition={\n                            reduceMotion\n                              ? { duration: 0 }\n                              : {\n                                  duration: 0.5,\n                                  ease: [0.16, 1, 0.3, 1],\n                                  delay: 0.1 + index * 0.05,\n                                }\n                          }\n                          style={{\n                            filter: \"drop-shadow(0 0 6px rgba(16,185,129,0.7))\",\n                          }}\n                        />\n                      )}\n                    </motion.svg>\n                  )}\n                </AnimatePresence>\n\n                <div className=\"pointer-events-none absolute inset-0 grid place-items-center overflow-hidden z-10\">\n                  <AnimatePresence\n                    mode=\"popLayout\"\n                    initial={false}\n                    custom={clearedSlot === index}\n                  >\n                    {slot && (\n                      <motion.span\n                        key={slot + \"-\" + index}\n                        layout=\"position\"\n                        initial={\n                          reduceMotion\n                            ? { opacity: 0 }\n                            : {\n                                y: 26,\n                                opacity: 0,\n                                scale: 0.65,\n                                filter: \"blur(4px)\",\n                              }\n                        }\n                        animate={{\n                          y: 0,\n                          opacity: 1,\n                          scale: 1,\n                          filter: \"blur(0px)\",\n                        }}\n                        exit={\n                          reduceMotion\n                            ? { opacity: 0 }\n                            : {\n                                y: clearedSlot === index ? 22 : -22,\n                                opacity: 0,\n                                scale: 0.7,\n                                filter: \"blur(3px)\",\n                              }\n                        }\n                        transition={{\n                          layout: reduceMotion ? { duration: 0 } : SIZE_SPRING,\n                          ...(reduceMotion ? { duration: 0.15 } : ROLL_SPRING),\n                        }}\n                        data-slot=\"otp-input-char\"\n                        className={cn(\n                          \"font-mono font-semibold tracking-wider text-black dark:text-white select-none inline-flex items-center justify-center\",\n                          scale.text,\n                        )}\n                      >\n                        {mask ? (\n                          <motion.span\n                            initial={{ scale: 0.4 }}\n                            animate={{ scale: 1 }}\n                            transition={{\n                              type: \"spring\",\n                              stiffness: 600,\n                              damping: 28,\n                            }}\n                            className=\"inline-block size-2.5 rounded-full bg-current shadow-[0_0_8px_currentColor]\"\n                          />\n                        ) : (\n                          slot\n                        )}\n                      </motion.span>\n                    )}\n                  </AnimatePresence>\n                </div>\n              </motion.div>\n            </React.Fragment>\n          );\n        })}\n\n        {caretVisible && (\n          <motion.span\n            aria-hidden=\"true\"\n            data-slot=\"otp-input-caret\"\n            initial={false}\n            animate={{\n              x: caretX - 1,\n              y: \"-50%\",\n              height: scale.caretHeight,\n              scaleX: isCaretMoving && !reduceMotion ? 1.4 : 1,\n              scaleY: isCaretMoving && !reduceMotion ? 0.85 : 1,\n              opacity: isCaretMoving ? 1 : [1, 1, 0, 0],\n            }}\n            transition={{\n              x: reduceMotion ? { duration: 0 } : CARET_SPRING,\n              height: reduceMotion ? { duration: 0 } : SIZE_SPRING,\n              scaleX: { duration: 0.15 },\n              scaleY: { duration: 0.15 },\n              opacity: isCaretMoving\n                ? { duration: 0.05 }\n                : {\n                    duration: 1.05,\n                    times: [0, 0.48, 0.5, 1],\n                    repeat: Infinity,\n                    ease: \"linear\",\n                  },\n            }}\n            className=\"pointer-events-none absolute left-0 top-1/2 w-0.5 rounded-full bg-black dark:bg-white shadow-[0_0_8px_rgba(0,0,0,0.25)] dark:shadow-[0_0_8px_rgba(255,255,255,0.8)] z-30\"\n          />\n        )}\n      </motion.div>\n    </div>\n  );\n}\n\nexport default OtpInput;\n",
      "type": "registry:ui",
      "target": "@ui/otp-input.tsx"
    }
  ]
}
