{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "cooldown-button",
  "type": "registry:ui",
  "title": "CooldownButton",
  "description": "A button that locks itself behind a draining radial timer after each click to stop spam and double-submits, taunting you while you wait.",
  "files": [
    {
      "path": "components/evil-buttons/cooldown-button.tsx",
      "type": "registry:ui",
      "target": "components/evil-buttons/cooldown-button.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport {\n  AnimatePresence,\n  animate,\n  motion,\n  useMotionTemplate,\n  useMotionValue,\n  useReducedMotion,\n} from \"motion/react\";\nimport { cn } from \"@/lib/utils\";\n\ntype CooldownState = \"idle\" | \"cooldown\";\n\nexport interface CooldownButtonProps\n  extends React.ButtonHTMLAttributes<HTMLButtonElement> {\n  /** Idle label. Falls back to `label`. */\n  children?: React.ReactNode;\n  /** Idle label used when no children are provided. */\n  label?: React.ReactNode;\n  /** Cooldown duration in milliseconds after each click. */\n  cooldown?: number;\n  /** Rotating taunts shown while locked. One is picked per cooldown. */\n  taunts?: string[];\n  /**\n   * When true, the remaining seconds are appended to the taunt while locked.\n   * @default true\n   */\n  showCountdown?: boolean;\n}\n\nconst DEFAULT_TAUNTS = [\n  \"Patience.\",\n  \"Again? Wait.\",\n  \"Not so fast.\",\n  \"Cool it.\",\n  \"Hold your horses.\",\n];\n\nexport const CooldownButton = React.forwardRef<\n  HTMLButtonElement,\n  CooldownButtonProps\n>(\n  (\n    {\n      children,\n      label = \"Send it\",\n      cooldown = 3000,\n      taunts = DEFAULT_TAUNTS,\n      showCountdown = true,\n      onClick,\n      className,\n      disabled,\n      ...props\n    },\n    ref,\n  ) => {\n    const reduceMotion = useReducedMotion();\n\n    const [state, setState] = React.useState<CooldownState>(\"idle\");\n    const [taunt, setTaunt] = React.useState(\"\");\n    const [remaining, setRemaining] = React.useState(0);\n\n    const timeoutRef = React.useRef<number | null>(null);\n    const intervalRef = React.useRef<number | null>(null);\n\n    // Drains 360 -> 0 over the cooldown, painted as a conic sweep.\n    const sweep = useMotionValue(360);\n    const sweepBackground = useMotionTemplate`conic-gradient(currentColor ${sweep}deg, transparent 0deg)`;\n\n    const clearTimers = React.useCallback(() => {\n      if (timeoutRef.current !== null) {\n        window.clearTimeout(timeoutRef.current);\n        timeoutRef.current = null;\n      }\n      if (intervalRef.current !== null) {\n        window.clearInterval(intervalRef.current);\n        intervalRef.current = null;\n      }\n    }, []);\n\n    React.useEffect(() => () => clearTimers(), [clearTimers]);\n\n    const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {\n      if (state === \"cooldown\") return;\n      onClick?.(e);\n      if (e.defaultPrevented || cooldown <= 0) return;\n\n      const pick =\n        taunts.length > 0\n          ? taunts[Math.floor(Math.random() * taunts.length)]\n          : \"\";\n      setTaunt(pick);\n      setRemaining(Math.ceil(cooldown / 1000));\n      setState(\"cooldown\");\n\n      clearTimers();\n      sweep.set(360);\n      if (!reduceMotion) {\n        animate(sweep, 0, { duration: cooldown / 1000, ease: \"linear\" });\n      }\n      const end = performance.now() + cooldown;\n      intervalRef.current = window.setInterval(() => {\n        setRemaining(Math.max(0, Math.ceil((end - performance.now()) / 1000)));\n      }, 200);\n      timeoutRef.current = window.setTimeout(() => {\n        clearTimers();\n        setState(\"idle\");\n      }, cooldown);\n    };\n\n    const isCooling = state === \"cooldown\";\n    const idleLabel = children ?? label;\n    const cooldownLabel =\n      showCountdown && remaining > 0 ? `${taunt} ${remaining}s` : taunt;\n\n    return (\n      <button\n        ref={ref}\n        type=\"button\"\n        onClick={handleClick}\n        disabled={disabled || isCooling}\n        aria-live=\"polite\"\n        data-state={state}\n        className={cn(\n          \"relative inline-flex h-9 min-w-40 cursor-pointer items-center justify-center overflow-hidden rounded-[10px] px-4 text-sm font-medium text-neutral-50 outline-none select-none\",\n          // Graded dark surface matching MinimalButton: dark outer hairline, faint inner ring, top highlight, soft drop.\n          \"bg-linear-to-b from-[#353535] to-[#272727] shadow-[0_0_0_1px_rgb(0_0_0/0.9),inset_0_0_0_1px_rgb(255_255_255/0.06),inset_0_1px_0_rgb(255_255_255/0.14),0_1px_2px_rgb(0_0_0/0.25),0_4px_12px_-4px_rgb(0_0_0/0.4)]\",\n          \"transition-[scale,filter,box-shadow,color] duration-300 ease-[cubic-bezier(0.34,1.35,0.64,1)] hover:brightness-110\",\n          \"active:scale-[0.97] active:brightness-95 active:duration-100 active:ease-out active:shadow-[0_0_0_1px_rgb(0_0_0/0.9),inset_0_0_0_1px_rgb(255_255_255/0.05),inset_0_1px_0_rgb(255_255_255/0.08),0_0_1px_rgb(0_0_0/0.2),0_1px_3px_-2px_rgb(0_0_0/0.3)] motion-reduce:active:scale-100\",\n          \"focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50\",\n          // Locked by the cooldown, not disabled: stay solid, dim the label, no hover lift.\n          isCooling && \"text-neutral-50/55 hover:brightness-100 disabled:opacity-100\",\n          className,\n        )}\n        {...props}\n      >\n        {/* Radial sweep that drains over the cooldown duration. */}\n        {isCooling && (\n          <motion.span\n            aria-hidden\n            className=\"pointer-events-none absolute inset-0 z-0 opacity-[0.12]\"\n            style={{ background: sweepBackground }}\n          />\n        )}\n\n        <span className=\"relative z-10 inline-block\">\n          <AnimatePresence initial={false} mode=\"wait\">\n            <motion.span\n              key={isCooling ? `cool-${taunt}` : \"idle\"}\n              initial={reduceMotion ? false : { y: 8, opacity: 0 }}\n              animate={{ y: 0, opacity: 1 }}\n              exit={reduceMotion ? undefined : { y: -8, opacity: 0 }}\n              transition={{ duration: 0.16 }}\n              className=\"inline-block whitespace-nowrap\"\n            >\n              {isCooling ? cooldownLabel : idleLabel}\n            </motion.span>\n          </AnimatePresence>\n        </span>\n      </button>\n    );\n  },\n);\n\nCooldownButton.displayName = \"CooldownButton\";\n\nexport default CooldownButton;\n"
    }
  ],
  "dependencies": [
    "clsx",
    "tailwind-merge",
    "motion"
  ]
}
