{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "swap-currency-card",
  "type": "registry:component",
  "title": "Swap Currency Card",
  "description": "A responsive interface component designed for instant user sentiment, featuring snappy visual cues that provide immediate confirmation upon interaction.",
  "dependencies": [
    "lucide-react",
    "motion"
  ],
  "files": [
    {
      "path": "components/watermelon/swap-currency-card.tsx",
      "type": "registry:component",
      "content": "'use client';\n\nimport {\n  useState,\n  useRef,\n  useEffect,\n  useCallback,\n  type FC,\n  type ChangeEvent,\n} from 'react';\nimport { motion, AnimatePresence } from 'motion/react';\nimport { ChevronDown, Check } from 'lucide-react';\n\n/* --- Types --- */\nexport interface Currency {\n  code: string;\n  countryCode: string;\n  flag: string;\n  rate: number;\n  name: string;\n}\n\ninterface SwapCurrencyCardProps {\n  currencies: Currency[];\n  defaultFromCode?: string;\n  defaultToCode?: string;\n  defaultAmount?: string;\n}\n\n/* --- Flag Component --- */\ninterface FlagIconProps {\n  countryCode: string;\n  emoji: string;\n}\n\nconst FlagIcon: FC<FlagIconProps> = ({ countryCode, emoji }) => {\n  const [imgError, setImgError] = useState(false);\n\n  useEffect(() => {\n    requestAnimationFrame(() => setImgError(false));\n  }, [countryCode]);\n\n  if (!countryCode) return <span className=\"text-lg sm:text-xl\">{emoji}</span>;\n\n  const src =\n    countryCode === 'eu'\n      ? 'https://upload.wikimedia.org/wikipedia/commons/b/b7/Flag_of_Europe.svg'\n      : `https://flagcdn.com/${countryCode.toLowerCase()}.svg`;\n\n  return (\n    <div className=\"flex h-4 w-5 shrink-0 items-center justify-center overflow-hidden rounded-xs border border-gray-200 bg-transparent sm:h-5 sm:w-6 dark:border-zinc-700\">\n      {!imgError ? (\n        <img\n          src={src}\n          alt={countryCode}\n          className=\"h-full w-full object-cover\"\n          loading=\"lazy\"\n          onError={() => setImgError(true)}\n        />\n      ) : (\n        <span className=\"flex h-full w-full items-center justify-center text-xs leading-none sm:text-sm\">\n          {emoji}\n        </span>\n      )}\n    </div>\n  );\n};\n\n/* --- Dropdown --- */\ninterface DropdownProps {\n  selected: Currency;\n  onSelect: (currency: Currency) => void;\n  currencies: Currency[];\n}\n\nconst Dropdown: FC<DropdownProps> = ({ selected, onSelect, currencies }) => {\n  const [isOpen, setIsOpen] = useState(false);\n  const ref = useRef<HTMLDivElement>(null);\n\n  useEffect(() => {\n    const close = (e: MouseEvent) => {\n      if (ref.current && !ref.current.contains(e.target as Node)) {\n        setIsOpen(false);\n      }\n    };\n    document.addEventListener('mousedown', close);\n    return () => document.removeEventListener('mousedown', close);\n  }, []);\n\n  return (\n    <div className=\"relative\" ref={ref}>\n      <button\n        onClick={() => setIsOpen((v) => !v)}\n        className=\"flex items-center gap-1.5 rounded-full border border-[#E5E5E9] bg-[#fefefe] px-2.5 py-1.5 transition-all active:scale-95 sm:gap-2 sm:px-3 sm:py-2 dark:border-zinc-700 dark:bg-zinc-800\"\n      >\n        <FlagIcon countryCode={selected.countryCode} emoji={selected.flag} />\n        <span className=\"text-xs font-semibold text-gray-700 sm:text-sm dark:text-zinc-200\">\n          {selected.code}\n        </span>\n        <ChevronDown\n          className={`h-4 w-4 text-gray-400 transition-transform sm:h-5 sm:w-5 ${\n            isOpen ? 'rotate-180' : ''\n          }`}\n        />\n      </button>\n\n      <AnimatePresence>\n        {isOpen && (\n          <motion.div\n            initial={{ opacity: 0, y: -8, scale: 0.96, filter: 'blur(4px)' }}\n            animate={{ opacity: 1, y: 0, scale: 1, filter: 'blur(0px)' }}\n            exit={{ opacity: 0, y: -8, scale: 0.96, filter: 'blur(4px)' }}\n            transition={{ duration: 0.2 }}\n            className=\"absolute right-0 z-50 mt-2 w-40 rounded-xl border-[1.6px] border-[#E5E5E9] bg-white py-1 shadow-lg sm:w-48 sm:rounded-2xl dark:border-zinc-700 dark:bg-zinc-800\"\n          >\n            {currencies.map((currency) => (\n              <button\n                key={currency.code}\n                onClick={() => {\n                  onSelect(currency);\n                  setIsOpen(false);\n                }}\n                className=\"flex w-full items-center justify-between px-3 py-2 transition-colors hover:bg-gray-50 sm:px-4 sm:py-2.5 dark:hover:bg-zinc-700/50\"\n              >\n                <div className=\"flex items-center gap-2 sm:gap-3\">\n                  <FlagIcon\n                    countryCode={currency.countryCode}\n                    emoji={currency.flag}\n                  />\n                  <span className=\"text-xs font-medium text-gray-700 sm:text-sm dark:text-zinc-200\">\n                    {currency.code}\n                  </span>\n                </div>\n\n                {currency.code === selected.code && (\n                  <Check className=\"h-3.5 w-3.5 text-gray-400 sm:h-4 sm:w-4 dark:text-zinc-500\" />\n                )}\n              </button>\n            ))}\n          </motion.div>\n        )}\n      </AnimatePresence>\n    </div>\n  );\n};\n\n/* --- Animated Number --- */\ninterface AnimatedNumberProps {\n  value: string;\n}\n\nconst AnimatedNumber: FC<AnimatedNumberProps> = ({ value }) => {\n  const chars = String(value || '0').split('');\n\n  return (\n    <div className=\"flex items-center text-xl font-medium text-[#2F2F33] sm:text-2xl dark:text-zinc-100\">\n      {chars.map((char, i) => {\n        // Delay calculated from right-to-left (ones = 0 delay, tens = slightly longer, etc.)\n        const delay = (chars.length - 1 - i) * 0.03;\n        return <DigitColumn key={i} digit={char} delay={delay} />;\n      })}\n    </div>\n  );\n};\n\ninterface DigitColumnProps {\n  digit: string;\n  delay?: number;\n}\n\nconst DigitColumn: FC<DigitColumnProps> = ({ digit, delay = 0 }) => {\n  // Adjusted heights for mobile/desktop\n  const [digitHeight, setDigitHeight] = useState(28);\n\n  useEffect(() => {\n    const updateHeight = () => {\n      setDigitHeight(window.innerWidth < 640 ? 24 : 28);\n    };\n    updateHeight();\n    window.addEventListener('resize', updateHeight);\n    return () => window.removeEventListener('resize', updateHeight);\n  }, []);\n\n  const num = Number(digit);\n\n  if (Number.isNaN(num)) {\n    return (\n      <span className=\"inline-block w-[0.54em] text-center font-bold text-[#010103] dark:text-white\">\n        {digit}\n      </span>\n    );\n  }\n\n  return (\n    <div\n      className=\"relative flex items-center justify-center\"\n      style={{ height: digitHeight, width: '0.6em' }}\n    >\n      <AnimatePresence initial={false}>\n        <motion.span\n          key={digit}\n          initial={{ opacity: 0, y: -10, scale: 0.65, filter: 'blur(2px)' }}\n          animate={{ opacity: 1, y: 0, scale: 1, filter: 'blur(0px)' }}\n          exit={{ opacity: 0, y: 0, scale: 1, filter: 'blur(2px)' }}\n          transition={{\n            type: 'spring',\n            bounce: 0.2,\n            duration: 0.4,\n            delay: delay,\n          }}\n          className=\"absolute font-bold text-[#010103] dark:text-white\"\n        >\n          {digit}\n        </motion.span>\n      </AnimatePresence>\n    </div>\n  );\n};\n\n/* --- MAIN COMPONENT --- */\nexport const SwapCurrencyCard: FC<SwapCurrencyCardProps> = ({\n  currencies,\n  defaultFromCode = currencies[0].code,\n  defaultToCode = currencies[1].code,\n  defaultAmount = '10',\n}) => {\n  const fromDefault =\n    currencies.find((c) => c.code === defaultFromCode) || currencies[0];\n  const toDefault =\n    currencies.find((c) => c.code === defaultToCode) || currencies[1];\n\n  const [fromCurrency, setFromCurrency] = useState(fromDefault);\n  const [toCurrency, setToCurrency] = useState(toDefault);\n  const [fromAmount, setFromAmount] = useState(defaultAmount);\n  const [toAmount, setToAmount] = useState('');\n\n  const convert = useCallback(\n    (amount: string, from: Currency, to: Currency): string => {\n      const val = parseFloat(amount);\n      if (isNaN(val)) return '';\n      const usd = val / from.rate;\n      return (usd * to.rate).toFixed(2);\n    },\n    [],\n  );\n\n  useEffect(() => {\n    requestAnimationFrame(() =>\n      setToAmount(convert(fromAmount, fromCurrency, toCurrency)),\n    );\n  }, [convert, fromAmount, fromCurrency, toCurrency]);\n\n  const handleFromChange = (e: ChangeEvent<HTMLInputElement>) => {\n    const val = e.target.value;\n    if (val === '' || /^\\d*\\.?\\d*$/.test(val)) {\n      setFromAmount(val);\n      setToAmount(convert(val, fromCurrency, toCurrency));\n    }\n  };\n\n  const handleToChange = (e: ChangeEvent<HTMLInputElement>) => {\n    const val = e.target.value;\n    if (val === '' || /^\\d*\\.?\\d*$/.test(val)) {\n      setToAmount(val);\n      setFromAmount(convert(val, toCurrency, fromCurrency));\n    }\n  };\n\n  const rate = (toCurrency.rate / fromCurrency.rate).toFixed(2);\n\n  return (\n    <motion.div\n      initial={{ opacity: 0, scale: 0.95, filter: 'blur(8px)' }}\n      animate={{ opacity: 1, scale: 1, filter: 'blur(0px)' }}\n      transition={{ type: 'spring', stiffness: 200, damping: 20 }}\n      className=\"flex w-xs flex-col gap-5 rounded-[32px] border-[1.6px] border-[#E5E5E9] bg-[#FEFEFE] p-6 shadow-[0_32px_47px_-16px_rgba(0,0,0,0.1)] sm:w-sm sm:gap-6 sm:rounded-[40px] sm:p-8 dark:border-zinc-800 dark:bg-zinc-900 dark:shadow-[0_32px_60px_-16px_rgba(0,0,0,0.5)]\"\n    >\n      <h2 className=\"text-lg font-semibold text-[#898990] sm:text-[20px] dark:text-zinc-500\">\n        Swap Currency\n      </h2>\n\n      <div className=\"flex flex-col gap-1.5 sm:gap-2\">\n        {/* Input */}\n        <div className=\"flex items-center justify-between rounded-t-4xl rounded-b-2xl bg-[#F6F5FA] p-3.5 sm:rounded-t-[24px] sm:rounded-b-3xl sm:p-4 dark:bg-zinc-800/50\">\n          <div className=\"relative mr-2 flex-1\">\n            <AnimatedNumber value={fromAmount} />\n            <input\n              title=\"from\"\n              value={fromAmount}\n              onChange={handleFromChange}\n              className=\"absolute inset-0 w-full bg-transparent text-xl font-semibold tracking-[0.08em] text-transparent caret-[#2F2F33] outline-none sm:text-[24px] dark:caret-zinc-100\"\n            />\n          </div>\n\n          <Dropdown\n            selected={fromCurrency}\n            currencies={currencies}\n            onSelect={(c) => {\n              setFromCurrency(c);\n              setToAmount(convert(fromAmount, c, toCurrency));\n            }}\n          />\n        </div>\n\n        {/* Input Block 2 */}\n        <div className=\"flex items-center justify-between rounded-t-2xl rounded-b-4xl bg-[#F6F5FA] p-3.5 sm:rounded-t-3xl sm:rounded-b-[24px] sm:p-4 dark:bg-zinc-800/50\">\n          <div className=\"relative mr-2 flex-1\">\n            <AnimatedNumber value={toAmount} />\n            <input\n              title=\"to\"\n              value={toAmount}\n              onChange={handleToChange}\n              className=\"absolute inset-0 w-full bg-transparent text-xl font-semibold tracking-[0.08em] text-transparent caret-[#2F2F33] outline-none sm:text-[24px] dark:caret-zinc-100\"\n            />\n          </div>\n\n          <Dropdown\n            selected={toCurrency}\n            currencies={currencies}\n            onSelect={(c) => {\n              setToCurrency(c);\n              setToAmount(convert(fromAmount, fromCurrency, c));\n            }}\n          />\n        </div>\n      </div>\n\n      <button className=\"w-full rounded-2xl bg-[#262629] py-3.5 text-base font-semibold text-white shadow-lg transition hover:bg-black active:scale-[0.98] sm:rounded-[22px] sm:py-4 sm:text-[18px] dark:bg-zinc-100 dark:text-zinc-950 dark:hover:bg-white\">\n        Proceed\n      </button>\n\n      <div className=\"text-center text-sm font-medium text-[#9F9EA1] sm:text-base dark:text-zinc-500\">\n        1 {fromCurrency.code} ≈ {rate} {toCurrency.code}\n      </div>\n    </motion.div>\n  );\n};\n"
    }
  ]
}
