{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "reveal-button",
  "type": "registry:ui",
  "title": "RevealButton",
  "description": "A button that reveals a hidden secret value on hold or toggle, with a blur-in animation and an eye icon indicator.",
  "files": [
    {
      "path": "components/evil-buttons/reveal-button.tsx",
      "type": "registry:ui",
      "target": "components/evil-buttons/reveal-button.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { AnimatePresence, motion } from \"motion/react\";\nimport { cn } from \"@/lib/utils\";\n\nexport type RevealButtonVariant = \"dark\" | \"light\";\n\n// Graded surfaces matching MinimalButton: outer hairline, faint inner ring, top highlight, soft drop.\n// Revealed swaps the neutral inner ring for an emerald one.\nconst surfaces: Record<\n  RevealButtonVariant,\n  {\n    base: string;\n    revealed: string;\n    label: string;\n    value: string;\n    chip: string;\n    chipRevealed: string;\n  }\n> = {\n  dark: {\n    base: \"text-neutral-50 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    revealed:\n      \"shadow-[0_0_0_1px_rgb(0_0_0/0.9),inset_0_0_0_1px_rgb(52_211_153/0.35),inset_0_1px_0_rgb(255_255_255/0.14),0_1px_2px_rgb(0_0_0/0.25),0_4px_12px_-4px_rgb(16_185_129/0.35)]\",\n    label: \"text-neutral-400\",\n    value: \"text-neutral-50\",\n    chip: \"bg-black/25 text-neutral-400 shadow-[inset_0_1px_2px_rgb(0_0_0/0.45),0_0_0_1px_rgb(255_255_255/0.07)]\",\n    chipRevealed:\n      \"text-emerald-400 shadow-[inset_0_1px_2px_rgb(0_0_0/0.45),0_0_0_1px_rgb(52_211_153/0.3)]\",\n  },\n  light: {\n    base: \"text-neutral-900 bg-linear-to-b from-white to-neutral-100 shadow-[0_0_0_1px_rgb(0_0_0/0.1),inset_0_1px_0_rgb(255_255_255/0.9),0_1px_2px_rgb(0_0_0/0.06),0_4px_12px_-4px_rgb(0_0_0/0.14)]\",\n    revealed:\n      \"shadow-[0_0_0_1px_rgb(16_185_129/0.45),inset_0_1px_0_rgb(255_255_255/0.9),0_1px_2px_rgb(0_0_0/0.06),0_4px_12px_-4px_rgb(16_185_129/0.3)]\",\n    label: \"text-neutral-500\",\n    value: \"text-neutral-900\",\n    chip: \"bg-neutral-200/60 text-neutral-500 shadow-[inset_0_1px_2px_rgb(0_0_0/0.12),0_0_0_1px_rgb(0_0_0/0.06)]\",\n    chipRevealed:\n      \"text-emerald-600 shadow-[inset_0_1px_2px_rgb(0_0_0/0.12),0_0_0_1px_rgb(16_185_129/0.35)]\",\n  },\n};\n\nexport interface RevealButtonProps\n  extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, \"children\"> {\n  secret?: React.ReactNode;\n  label?: React.ReactNode;\n  hiddenLabel?: React.ReactNode;\n  revealMode?: \"hold\" | \"toggle\";\n  /** Dark (default) or light graded surface. */\n  variant?: RevealButtonVariant;\n  maskedValue?: string;\n  onRevealChange?: (revealed: boolean) => void;\n}\n\nexport const RevealButton = React.forwardRef<\n  HTMLButtonElement,\n  RevealButtonProps\n>(\n  (\n    {\n      secret = \"sk_live_••••_9xQ4\",\n      label = \"Reveal\",\n      hiddenLabel = \"Hidden\",\n      revealMode = \"hold\",\n      variant = \"dark\",\n      maskedValue = \"•••• •••• ••••\",\n      onRevealChange,\n      className,\n      type = \"button\",\n      ...props\n    },\n    ref,\n  ) => {\n    const [revealed, setRevealed] = React.useState(false);\n    const surface = surfaces[variant];\n\n    const updateRevealed = (next: boolean) => {\n      setRevealed(next);\n      onRevealChange?.(next);\n    };\n\n    const pressHandlers =\n      revealMode === \"hold\"\n        ? {\n            onPointerDown: () => updateRevealed(true),\n            onPointerUp: () => updateRevealed(false),\n            onPointerLeave: () => updateRevealed(false),\n            onBlur: () => updateRevealed(false),\n            onKeyDown: (e: React.KeyboardEvent<HTMLButtonElement>) => {\n              if ((e.key === \" \" || e.key === \"Enter\") && !e.repeat) {\n                e.preventDefault();\n                updateRevealed(true);\n              }\n            },\n            onKeyUp: (e: React.KeyboardEvent<HTMLButtonElement>) => {\n              if (e.key === \" \" || e.key === \"Enter\") {\n                e.preventDefault();\n                updateRevealed(false);\n              }\n            },\n          }\n        : {\n            onClick: () => updateRevealed(!revealed),\n          };\n\n    return (\n      <button\n        ref={ref}\n        type={type}\n        aria-pressed={revealed}\n        aria-live=\"polite\"\n        data-state={revealed ? \"revealed\" : \"hidden\"}\n        className={cn(\n          \"group relative inline-flex min-w-40 cursor-pointer items-center justify-between gap-3 overflow-hidden rounded-xl px-3 py-2 text-xs outline-none select-none sm:min-w-64 sm:gap-4 sm:px-4 sm:py-2.5 sm:text-sm\",\n          surface.base,\n          \"transition-[scale,filter,box-shadow] duration-300 ease-[cubic-bezier(0.34,1.35,0.64,1)] hover:brightness-110\",\n          \"active:scale-[0.98] active:brightness-95 active:duration-100 active:ease-out 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          revealed && surface.revealed,\n          className,\n        )}\n        {...pressHandlers}\n        {...props}\n      >\n        <span className=\"flex min-w-0 flex-col items-start\">\n          <span\n            className={cn(\n              \"text-xs font-medium uppercase tracking-wider\",\n              surface.label,\n            )}\n          >\n            {revealed ? label : hiddenLabel}\n          </span>\n          <AnimatePresence initial={false} mode=\"wait\">\n            <motion.span\n              key={revealed ? \"secret\" : \"masked\"}\n              initial={{ y: 6, opacity: 0, filter: \"blur(4px)\" }}\n              animate={{ y: 0, opacity: 1, filter: \"blur(0px)\" }}\n              exit={{ y: -6, opacity: 0, filter: \"blur(4px)\" }}\n              transition={{ duration: 0.16 }}\n              className={cn(\n                \"block max-w-28 truncate font-mono text-xs font-semibold sm:max-w-44 sm:text-sm\",\n                surface.value,\n              )}\n            >\n              {revealed ? secret : maskedValue}\n            </motion.span>\n          </AnimatePresence>\n        </span>\n        <span\n          aria-hidden\n          className={cn(\n            // Recessed chip: a sunken well with an inner shadow and a faint rim.\n            \"inline-flex size-7 shrink-0 items-center justify-center rounded-full transition-colors sm:size-9\",\n            surface.chip,\n            revealed && surface.chipRevealed,\n          )}\n        >\n          {revealed ? (\n            <svg\n              xmlns=\"http://www.w3.org/2000/svg\"\n              viewBox=\"0 0 24 24\"\n              fill=\"none\"\n              stroke=\"currentColor\"\n              strokeWidth=\"2.25\"\n              strokeLinecap=\"round\"\n              strokeLinejoin=\"round\"\n              className=\"size-4\"\n            >\n              <path d=\"m3 3 18 18\" />\n              <path d=\"M10.58 10.58a2 2 0 0 0 2.83 2.83\" />\n              <path d=\"M9.88 4.24A10.94 10.94 0 0 1 12 4c7 0 10 8 10 8a18.5 18.5 0 0 1-2.16 3.19\" />\n              <path d=\"M6.61 6.61A18.7 18.7 0 0 0 2 12s3 8 10 8a10.8 10.8 0 0 0 5.39-1.61\" />\n            </svg>\n          ) : (\n            <svg\n              xmlns=\"http://www.w3.org/2000/svg\"\n              viewBox=\"0 0 24 24\"\n              fill=\"none\"\n              stroke=\"currentColor\"\n              strokeWidth=\"2.25\"\n              strokeLinecap=\"round\"\n              strokeLinejoin=\"round\"\n              className=\"size-4\"\n            >\n              <path d=\"M2 12s3-8 10-8 10 8 10 8-3 8-10 8-10-8-10-8Z\" />\n              <circle cx=\"12\" cy=\"12\" r=\"3\" />\n            </svg>\n          )}\n        </span>\n      </button>\n    );\n  },\n);\n\nRevealButton.displayName = \"RevealButton\";\n"
    }
  ],
  "dependencies": [
    "clsx",
    "tailwind-merge",
    "motion"
  ]
}
