{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "pintch-switch",
  "type": "registry:component",
  "title": "Pintch Switch",
  "author": "Harsh Kanjiya <https://harshkanjiya.com>",
  "description": "A flat-track toggle with a raised pintch-style thumb.",
  "dependencies": [],
  "devDependencies": [],
  "registryDependencies": [],
  "files": [
    {
      "path": "components/pintch-switch/pintch-switch.tsx",
      "content": "'use client'\r\n\r\nimport { useSound } from '@/hooks/use-sound'\r\nimport React, { useState } from 'react'\r\n\r\nexport function PintchSwitch() {\r\n  const [isOn, setIsOn] = useState(false)\r\n\r\n  const toggle = () => setIsOn(p => !p)\r\n\r\n  const handleKeyDown = (e: React.KeyboardEvent) => {\r\n    if (e.key === ' ' || e.key === 'Enter') {\r\n      e.preventDefault()\r\n      toggle()\r\n    }\r\n  }\r\n\r\n  const playClick = useSound(\"/audio/ui-sounds/toggle-2.mp3\");\r\n\r\n  return (\r\n    <button\r\n      role=\"switch\"\r\n      aria-checked={isOn}\r\n      onClick={() => {\r\n        toggle();\r\n        playClick();\r\n      }}\r\n      onKeyDown={handleKeyDown}\r\n      className={[\r\n        'relative flex items-center',\r\n        'w-[120px] h-[46px] rounded-full',\r\n        'cursor-pointer outline-none select-none',\r\n        'focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-neutral-400',\r\n        'bg-[#e8e8e8] dark:bg-[#151518]',\r\n        'shadow-[inset_3px_3px_7px_rgba(0,0,0,0.09),inset_-2px_-2px_5px_rgba(255,255,255,0.8)]',\r\n        'dark:shadow-[inset_3px_3px_7px_rgba(0,0,0,0.5),inset_-2px_-2px_5px_rgba(50,50,65,0.35)]',\r\n      ].join(' ')}\r\n    >\r\n      {/* Left dot — visible when ON */}\r\n      <span\r\n        aria-hidden=\"true\"\r\n        className={[\r\n          'pointer-events-none absolute left-[13px]',\r\n          'w-[5px] h-[5px] rounded-full',\r\n          'bg-[#a8b8c4] dark:bg-[#607a8c]',\r\n          'transition-opacity duration-300',\r\n          isOn ? 'opacity-60' : 'opacity-0',\r\n        ].join(' ')}\r\n      />\r\n\r\n      {/* Right dot — orange, visible when OFF */}\r\n      <span\r\n        aria-hidden=\"true\"\r\n        className={[\r\n          'pointer-events-none absolute right-[13px]',\r\n          'w-[5px] h-[5px] rounded-full',\r\n          'bg-[#f05a00] dark:bg-[#ff6820]',\r\n          'transition-opacity duration-300',\r\n          isOn ? 'opacity-0' : 'opacity-100',\r\n        ].join(' ')}\r\n      />\r\n\r\n      {/* Thumb */}\r\n      <span\r\n        aria-hidden=\"true\"\r\n        className={[\r\n          'pointer-events-none absolute top-[3px] left-[3px]',\r\n          'flex items-center justify-center gap-[4px]',\r\n          'px-[3px] h-[40px] rounded-full',\r\n          'bg-[#e5e5e5] dark:bg-[#2a2a32]',\r\n          'transition-transform duration-300 ease-in-out',\r\n          // Light: strong white top-highlight + soft bottom shadow\r\n          // Dark: replace white highlights with low-opacity light tint; keep the bottom shadow heavy\r\n          'shadow-[0_3px_10px_rgba(0,0,0,0.35),inset_0px_8px_8px_rgba(255,255,255,0.9),inset_2px_-1px_8px_rgba(0,0,0,0.12)]',\r\n          'dark:shadow-[0_4px_12px_rgba(0,0,0,0.6),inset_0px_6px_8px_rgba(255,255,255,0.07),inset_0px_-4px_8px_rgba(0,0,0,0.55),inset_1px_1px_0px_rgba(255,255,255,0.06)]',\r\n          isOn ? 'translate-x-[42px]' : 'translate-x-0',\r\n        ].join(' ')}\r\n      >\r\n        {/* knob1 */}\r\n        <span\r\n          className={[\r\n            'w-[31px] h-[31px] rounded-full',\r\n            // Light: bright fill + crisp top-left highlight + soft inset shadow\r\n            'bg-[#f0f0f0]',\r\n            'shadow-[inset_2px_3px_6px_rgba(0,0,0,0.08),inset_-3px_-3px_8px_rgba(255,255,255,0.95),inset_-1px_-1px_0px_rgba(255,255,255,1)]',\r\n            // Dark: muted slate fill; very subtle top-left light catch; strong bottom-right depression\r\n            'dark:bg-[#3c3c48]',\r\n            'dark:shadow-[inset_2px_3px_8px_rgba(0,0,0,0.55),inset_-2px_-2px_6px_rgba(255,255,255,0.055),inset_1px_1px_0px_rgba(255,255,255,0.04)]',\r\n          ].join(' ')}\r\n        />\r\n        {/* knob2 */}\r\n        <span\r\n          className={[\r\n            'w-[31px] h-[31px] rounded-full',\r\n            'bg-[#f0f0f0]',\r\n            'shadow-[inset_2px_3px_8px_rgba(0,0,0,0.12),inset_-4px_-4px_10px_rgba(255,255,255,1),inset_-2px_-2px_0px_rgba(255,255,255,1)]',\r\n            'dark:bg-[#3c3c48]',\r\n            'dark:shadow-[inset_2px_3px_8px_rgba(0,0,0,0.55),inset_-3px_-3px_8px_rgba(255,255,255,0.05),inset_-1px_-1px_0px_rgba(0,0,0,0.3)]',\r\n          ].join(' ')}\r\n        />\r\n      </span>\r\n    </button>\r\n  )\r\n}\r\n\r\nexport default PintchSwitch",
      "type": "registry:component"
    },
    {
      "path": "components/pintch-switch/index.ts",
      "content": "export { PintchSwitch, default } from \"./pintch-switch\"\r\n",
      "type": "registry:component"
    },
    {
      "path": "components/pintch-switch/pintch-switch.module.css",
      "content": "/* Track: flat on-surface channel (inset only, no outer neumorphic raise) */\r\n.track {\r\n  box-shadow:\r\n    inset 3px 3px 7px rgba(0, 0, 0, 0.09),\r\n    inset -2px -2px 5px rgba(255, 255, 255, 0.8);\r\n}\r\n\r\n/* Thumb: prominently raised above the surface */\r\n.thumb {}\r\n\r\n/* Inner knob: raised pintch bump */\r\n.knob {\r\n  box-shadow:\r\n    /* 0 3px 8px rgba(0, 0, 0, 0.18),\r\n    0 1px 3px rgba(0, 0, 0, 0.12), */\r\n    inset 0 1px 2px rgba(255, 255, 255, 0.95),\r\n    inset 0 -1px 2px rgba(0, 0, 0, 0.08);\r\n}\r\n\r\n.knob1 {\r\n  box-shadow:\r\n    inset 3px 4px 14px 0px rgba(0, 0, 0, 0.1),\r\n    inset -2px -2px 10px 0px rgba(255, 255, 255, 1),\r\n    inset -1px -1px 0px 0px rgba(255, 255, 255, 1);\r\n}\r\n\r\n.knob2 {\r\n  box-shadow:\r\n    inset 3px 4px 14px 0px rgba(0, 0, 0, 0.15),\r\n    inset -4px -4px 12px 1px rgba(255, 255, 255, 1),\r\n    inset -2px -2px 0px 0px rgba(255, 255, 255, 1);\r\n}\r\n\r\n:global(.dark) .track {\r\n  box-shadow:\r\n    inset 3px 3px 7px rgba(0, 0, 0, 0.5),\r\n    inset -2px -2px 5px rgba(50, 50, 65, 0.35);\r\n}\r\n\r\n:global(.dark) .thumb {\r\n  box-shadow:\r\n    0 6px 18px rgba(0, 0, 0, 0.6),\r\n    0 2px 6px rgba(0, 0, 0, 0.4),\r\n    0 0 0 1px rgba(255, 255, 255, 0.07),\r\n    inset 0 1px 2px rgba(255, 255, 255, 0.08);\r\n}\r\n\r\n:global(.dark) .knob {\r\n  box-shadow:\r\n    0 3px 8px rgba(0, 0, 0, 0.5),\r\n    0 1px 3px rgba(0, 0, 0, 0.35),\r\n    inset 0 1px 2px rgba(255, 255, 255, 0.1),\r\n    inset 0 -1px 2px rgba(0, 0, 0, 0.3);\r\n}",
      "type": "registry:file",
      "target": "components/pintch-switch/pintch-switch.module.css"
    },
    {
      "path": "hooks/use-sound.ts",
      "content": "import { useCallback, useEffect, useRef } from \"react\";\n\n/**\n * Custom React hook to load and play a sound from a given URL using the Web Audio API.\n *\n * This hook fetches the audio file at the specified URL, decodes it, and prepares it for playback.\n * It returns a `play` function that can be called to play the loaded sound.\n *\n * @param url - The URL of the audio file to load and play.\n * @returns A function that, when called, plays the loaded sound.\n *\n * @remarks\n * - If the Web Audio API is not supported in the browser, a warning is logged and playback is disabled.\n * - The audio context and buffer are managed internally using React refs.\n * - Errors during fetching or decoding the audio are logged to the console.\n *\n * @example\n * ```tsx\n * const playClick = useSound('/sounds/click.mp3');\n * // Later in an event handler:\n * playClick();\n * ```\n */\nexport function useSound(url: string) {\n  const audioCtxRef = useRef<AudioContext | null>(null);\n  const bufferRef = useRef<AudioBuffer | null>(null);\n\n  useEffect(() => {\n    const AudioContextClass =\n      window.AudioContext ||\n      (window as unknown as { webkitAudioContext: typeof AudioContext })\n        .webkitAudioContext;\n\n    if (!AudioContextClass) {\n      console.warn(\"Web Audio API is not supported in this browser.\");\n      return;\n    }\n\n    const audioCtx = new AudioContextClass();\n    audioCtxRef.current = audioCtx;\n\n    fetch(url)\n      .then((res) => res.arrayBuffer())\n      .then((data) => audioCtx.decodeAudioData(data))\n      .then((decoded) => {\n        bufferRef.current = decoded;\n      })\n      .catch((err) => {\n        console.log(`Failed to load click sound from ${url}:`, err);\n      });\n  }, [url]);\n\n  const play = useCallback((volume: number = 1) => {\n    if (audioCtxRef.current && bufferRef.current) {\n      const source = audioCtxRef.current.createBufferSource();\n      const gainNode = audioCtxRef.current.createGain();\n\n      source.buffer = bufferRef.current;\n      gainNode.gain.value = volume;\n\n      source.connect(gainNode);\n      gainNode.connect(audioCtxRef.current.destination);\n      source.start(0);\n    }\n  }, []);\n\n  return play;\n}\n",
      "type": "registry:hook"
    }
  ]
}