{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "voice-transcribe",
  "type": "registry:component",
  "title": "Voice Transcribe",
  "description": "Convert speech into text instantly with smooth, responsive Voice Transcribe.",
  "dependencies": [
    "motion",
    "react-icons"
  ],
  "files": [
    {
      "path": "components/watermelon/voice-transcribe.tsx",
      "type": "registry:component",
      "content": "'use client';\n\nimport React, { useState, useEffect } from 'react';\nimport { motion, AnimatePresence } from 'motion/react';\nimport {\n  TbMessageFilled,\n  TbPlayerPauseFilled,\n  TbPlayerPlayFilled,\n} from 'react-icons/tb';\n\ninterface VoiceMessageProps {\n  duration: number;\n  transcription: string;\n  waveformHeights?: number[];\n  className?: string;\n}\n\nconst DEFAULT_WAVEFORM = [\n  8, 12, 16, 12, 10, 18, 24, 16, 14, 20, 12, 16, 22, 18, 14, 10, 16, 24, 18, 14,\n  12, 10, 8, 12, 16, 14, 10,\n];\n\nexport const TranscribeVoiceMessage: React.FC<VoiceMessageProps> = ({\n  duration: initialDuration,\n  transcription,\n  waveformHeights = DEFAULT_WAVEFORM,\n  className = '',\n}) => {\n  const [isPlaying, setIsPlaying] = useState(false);\n  const [currentTime, setCurrentTime] = useState(0);\n  const [showTranscription, setShowTranscription] = useState(false);\n  useEffect(() => {\n    if (!isPlaying) return;\n\n    let frameId: number;\n    const startTime = performance.now() - currentTime * 1000;\n\n    const tick = (now: number) => {\n      const nextTime = (now - startTime) / 1000;\n      if (nextTime >= initialDuration) {\n        setCurrentTime(initialDuration);\n        setIsPlaying(false);\n      } else {\n        setCurrentTime(nextTime);\n        frameId = requestAnimationFrame(tick);\n      }\n    };\n\n    frameId = requestAnimationFrame(tick);\n\n    return () => cancelAnimationFrame(frameId);\n  }, [isPlaying, initialDuration, currentTime]);\n\n  const handlePlayToggle = () => {\n    if (currentTime >= initialDuration) setCurrentTime(0);\n    setIsPlaying(!isPlaying);\n  };\n\n  const remainingTime = Math.ceil(initialDuration - currentTime);\n  const progressPercent = currentTime / initialDuration;\n\n  const words = transcription.split(' ');\n  const totalChars = transcription.length;\n  const isDone = currentTime >= initialDuration;\n\n  // When done, reveal everything avoiding any tricky zone calculations\n  const revealedCount = isDone\n    ? totalChars\n    : Math.floor(progressPercent * totalChars);\n\n  return (\n    <div\n      className={`flex w-full flex-col items-center justify-center p-2 antialiased select-none sm:p-4 ${className}`}\n    >\n      <div className=\"relative flex w-full max-w-fit items-center gap-2 sm:gap-4\">\n        {/* Transcription Icon Toggle */}\n        <button\n          title=\"Transcription\"\n          onClick={() => setShowTranscription(!showTranscription)}\n          className={`flex h-11 w-11 shrink-0 items-center justify-center rounded-full border-2 transition-all duration-300 sm:h-16 sm:w-16 ${showTranscription\n              ? 'border-neutral-200 bg-transparent text-neutral-900 dark:border-white/20 dark:text-white'\n              : 'border-transparent bg-neutral-100 text-neutral-900 hover:bg-neutral-200 dark:bg-neutral-800 dark:text-white dark:hover:bg-neutral-700'\n            }`}\n        >\n          <TbMessageFilled size={22} className=\"sm:hidden\" />\n          <TbMessageFilled size={28} className=\"hidden sm:block\" />\n        </button>\n\n        {/* Main Player Pill */}\n        <div className=\"flex items-center gap-2 rounded-full border border-black/5 bg-neutral-100 px-3 py-2 shadow-sm transition-colors sm:gap-3 sm:px-4 sm:py-3 dark:border-white/5 dark:bg-neutral-800\">\n          <button\n            onClick={handlePlayToggle}\n            className=\"flex h-7 w-7 items-center justify-center text-neutral-900 transition-all active:scale-90 sm:h-8 sm:w-8 dark:text-white\"\n          >\n            {isPlaying ? (\n              <motion.div\n                initial={{ opacity: 0, scale: 0, filter: 'blur(4px)' }}\n                animate={{ opacity: 1, scale: 1, filter: 'blur(0px)' }}\n                exit={{ opacity: 0, scale: 0, filter: 'blur(4px)' }}\n                transition={{ duration: 0.3 }}\n              >\n                <TbPlayerPauseFilled size={24} className=\"sm:w-5.5\" />\n              </motion.div>\n            ) : (\n              <motion.div\n                initial={{ opacity: 0, scale: 0, filter: 'blur(4px)' }}\n                animate={{ opacity: 1, scale: 1, filter: 'blur(0px)' }}\n                exit={{ opacity: 0, scale: 0, filter: 'blur(4px)' }}\n                transition={{ duration: 0.3 }}\n              >\n                <TbPlayerPlayFilled\n                  size={24}\n                  className=\"ml-0.5 sm:ml-1 sm:w-5.5\"\n                />\n              </motion.div>\n            )}\n          </button>\n\n          {/* Waveform */}\n          <div className=\"relative flex h-8 items-center gap-0.5 sm:h-10 sm:gap-[3.5px]\">\n            {/* Background (Unplayed) */}\n            {waveformHeights.map((h, i) => (\n              <motion.div\n                key={`bg-${i}`}\n                initial={{ height: h * 0.7 }}\n                animate={{ height: h }}\n                transition={{ duration: 0.1 }}\n                className=\"w-0.5 rounded-full bg-neutral-400 sm:w-1 dark:bg-neutral-700\"\n              />\n            ))}\n\n            {/* Foreground (Played) with continuous masking */}\n            <div\n              className=\"absolute inset-0 flex items-center gap-0.5 sm:gap-[3.5px]\"\n              style={{\n                clipPath: `inset(0 ${100 - progressPercent * 100}% 0 0)`,\n              }}\n            >\n              {waveformHeights.map((h, i) => (\n                <motion.div\n                  key={`fg-${i}`}\n                  initial={{ height: h * 0.7 }}\n                  animate={{ height: h }}\n                  transition={{ duration: 0.1 }}\n                  className=\"w-0.5 rounded-full bg-neutral-900 sm:w-1 dark:bg-white\"\n                />\n              ))}\n            </div>\n          </div>\n\n          {/* Timer */}\n          <div className=\"relative flex h-4 w-5 items-center justify-end text-xs font-bold text-neutral-500 sm:h-5 sm:w-[26px] sm:text-base dark:text-neutral-400\">\n            <AnimatePresence mode=\"popLayout\" initial={false}>\n              <motion.span\n                key={remainingTime}\n                initial={{ y: -15, scale: 0, filter: 'blur(8px)', opacity: 0 }}\n                animate={{ y: 0, scale: 1, filter: 'blur(0px)', opacity: 1 }}\n                exit={{ y: 15, scale: 0, filter: 'blur(8px)', opacity: 0 }}\n                transition={{ type: 'spring', bounce: 0.3, duration: 0.7 }}\n                className=\"inline-block tabular-nums\"\n              >\n                {remainingTime}\n              </motion.span>\n            </AnimatePresence>\n            <span>s</span>\n          </div>\n        </div>\n\n        {/* Transcription Bubble */}\n        <AnimatePresence>\n          {showTranscription && (\n            <motion.div\n              initial={{ opacity: 0, scale: 0.85, y: 8, filter: 'blur(8px)' }}\n              animate={{ opacity: 1, scale: 1, y: 0, filter: 'blur(0px)' }}\n              exit={{ opacity: 0, scale: 0.85, y: 8, filter: 'blur(8px)' }}\n              transition={{ type: 'spring', damping: 25, stiffness: 400 }}\n              className=\"pointer-events-none absolute bottom-[170%] left-0 z-20 origin-bottom-left\"\n            >\n              <div className=\"relative\">\n                <motion.div\n                  layout\n                  transition={{ type: 'spring', damping: 32, stiffness: 300 }}\n                  className=\"w-[calc(100vw-5rem)] max-w-60 overflow-hidden rounded-2xl border border-neutral-200 bg-neutral-50 px-4 py-4 shadow-xl sm:max-w-70 sm:rounded-[28px] sm:px-6 sm:py-5 dark:border-white/10 dark:bg-neutral-800\"\n                >\n                  <p className=\"flex flex-wrap text-sm leading-relaxed font-bold tracking-tight wrap-break-word text-neutral-900 sm:text-lg dark:text-white\">\n                    {(() => {\n                      let globalIndex = 0;\n                      return words.map((word, wIdx) => {\n                        const wordChars = word.split('');\n                        const wordNode = (\n                          <span\n                            key={wIdx}\n                            className=\"mr-[0.25em] inline-flex whitespace-nowrap\"\n                          >\n                            {wordChars.map((char, cIdx) => {\n                              const isRevealed = globalIndex < revealedCount;\n                              globalIndex++;\n                              return (\n                                <motion.span\n                                  key={cIdx}\n                                  initial={false}\n                                  animate={{\n                                    opacity: isRevealed ? 1 : 0,\n                                  }}\n                                  transition={{\n                                    ease: 'easeOut',\n                                    duration: 0.1,\n                                  }}\n                                  className=\"inline-block\"\n                                >\n                                  {char}\n                                </motion.span>\n                              );\n                            })}\n                          </span>\n                        );\n                        // Increment for the space between words\n                        globalIndex++;\n                        return wordNode;\n                      });\n                    })()}\n                  </p>\n                </motion.div>\n\n                {/* Speech Bubble Connectors */}\n                <div className=\"absolute -bottom-9 left-4 flex flex-col items-center gap-1.5\">\n                  <div className=\"ml-3 h-3.5 w-3.5 rounded-full bg-neutral-50 shadow-md sm:h-4 sm:w-4 dark:bg-neutral-800\" />\n                  <div className=\"h-1.5 w-1.5 rounded-full bg-neutral-50 shadow-md sm:h-2 sm:w-2 dark:bg-neutral-800\" />\n                </div>\n              </div>\n            </motion.div>\n          )}\n        </AnimatePresence>\n      </div>\n    </div>\n  );\n};\n"
    }
  ]
}
