{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "gradient-selector",
  "title": "Gradient selector",
  "description": "Gradient selector is a production-ready controls component from BJORK UI, packaged for shadcn registries.",
  "dependencies": [
    "clsx",
    "framer-motion",
    "tailwind-merge"
  ],
  "files": [
    {
      "path": "components/bjork-ui/dropdowns/gradient-selector.tsx",
      "content": "\"use client\";\n\nimport { useState, useRef, useEffect } from \"react\";\nimport { motion, useReducedMotion } from \"framer-motion\";\nimport { cn } from \"@/lib/utils\";\n\nexport interface GradientOption {\n  id: string;\n  label: string;\n  value: string;\n  color: string;\n  gradientFrom: string;\n  gradientTo: string;\n}\n\ninterface GradientSelectorProps {\n  options?: GradientOption[];\n  defaultSelected?: string;\n  onSelectionChange?: (option: GradientOption, index: number) => void;\n  className?: string;\n}\n\nconst defaultOptions: GradientOption[] = [\n  {\n    id: \"100k\",\n    label: \"$100K\",\n    value: \"100000\",\n    color: \"#8a7a60\",\n    gradientFrom: \"#8a7a60\",\n    gradientTo: \"#bd4514\",\n  },\n  {\n    id: \"1m\",\n    label: \"$1M\",\n    value: \"1000000\",\n    color: \"#bd4514\",\n    gradientFrom: \"#bd4514\",\n    gradientTo: \"#ec5c13\",\n  },\n  {\n    id: \"5m\",\n    label: \"$5M\",\n    value: \"5000000\",\n    color: \"#ec5c13\",\n    gradientFrom: \"#ec5c13\",\n    gradientTo: \"#f07a35\",\n  },\n  {\n    id: \"10m\",\n    label: \"$10M\",\n    value: \"10000000\",\n    color: \"#f07a35\",\n    gradientFrom: \"#f07a35\",\n    gradientTo: \"#d86a2c\",\n  },\n  {\n    id: \"10m-plus\",\n    label: \"$10M+\",\n    value: \"10000000+\",\n    color: \"#d86a2c\",\n    gradientFrom: \"#d86a2c\",\n    gradientTo: \"#8a7a60\",\n  },\n];\n\nexport function GradientSelector({\n  options = defaultOptions,\n  defaultSelected,\n  onSelectionChange,\n  className\n}: GradientSelectorProps) {\n  const [selectedIndex, setSelectedIndex] = useState<number>(\n    defaultSelected ? options.findIndex(opt => opt.id === defaultSelected) : -1\n  );\n  \n  const [gradientPosition, setGradientPosition] = useState<{ x: number; y: number } | null>(null);\n  const containerRef = useRef<HTMLDivElement>(null);\n  const circleRefs = useRef<(HTMLDivElement | null)[]>([]);\n  const shouldReduceMotion = useReducedMotion();\n\n  // Ensure minimum of 3 options\n  const validOptions = options.length >= 3 ? options : defaultOptions.slice(0, Math.max(3, options.length));\n\n  // Update gradient position when selection changes\n  useEffect(() => {\n    if (selectedIndex >= 0 && circleRefs.current[selectedIndex] && containerRef.current) {\n      const circleElement = circleRefs.current[selectedIndex];\n      const containerElement = containerRef.current;\n      \n      const circleRect = circleElement.getBoundingClientRect();\n      const containerRect = containerElement.getBoundingClientRect();\n      \n      // Calculate position relative to container\n      const relativeX = circleRect.left + (circleRect.width / 2) - containerRect.left;\n      const relativeY = circleRect.top + (circleRect.height / 2) - containerRect.top;\n      \n      setGradientPosition({ x: relativeX, y: relativeY });\n    } else {\n      setGradientPosition(null);\n    }\n  }, [selectedIndex]);\n\n  const handleCircleClick = (option: GradientOption, index: number) => {\n    setSelectedIndex(index);\n    onSelectionChange?.(option, index);\n  };\n\n  // Create orbital dots around a circle - dots match the circle's color\n  const createOrbitalDots = (count: number, radius: number, color: string) => {\n    const dots = [];\n    for (let i = 0; i < count; i++) {\n      const angle = (i / count) * 2 * Math.PI;\n      const x = Math.cos(angle) * radius;\n      const y = Math.sin(angle) * radius;\n      \n      dots.push(\n        <motion.div\n          key={i}\n          className=\"absolute w-1 h-1 rounded-full\"\n          initial={{ \n            opacity: 0, \n            scale: 0.3,\n            rotate: shouldReduceMotion ? 0 : -90,\n            x: x - 2, // Account for half the dot width\n            y: y - 2  // Account for half the dot height\n          }}\n          animate={{ \n            opacity: 1, \n            scale: 1,\n            rotate: 0,\n            x: x - 2,\n            y: y - 2\n          }}\n          transition={{\n            duration: shouldReduceMotion ? 0.2 : 0.6,\n            delay: shouldReduceMotion ? 0 : i * 0.03,\n            type: \"spring\",\n            stiffness: 400,\n            damping: 25,\n            ease: [0.04, 0.62, 0.23, 0.98]\n          }}\n          style={{\n            backgroundColor: color,\n            left: '50%',\n            top: '50%',\n          }}\n        />\n      );\n    }\n    return dots;\n  };\n\n  const getCircleSize = (index: number) => {\n    if (index === 0) return \"w-3 h-3\";\n    if (index === 1) return \"w-3.5 h-3.5\";\n    return \"w-4 h-4\";\n  };\n\n  const getLineStyle = (lineIndex: number) => {\n    const isLitUp = selectedIndex > lineIndex; // Line lights up when you progress past it\n    const currentOption = validOptions[lineIndex];\n    const nextOption = validOptions[lineIndex + 1];\n    \n    if (isLitUp) {\n      // Fully lit with gradient\n      return {\n        background: `linear-gradient(to right, ${currentOption.gradientFrom}, ${nextOption?.gradientTo || currentOption.gradientTo})`\n      };\n    } else {\n      return {\n        background: `#8f877c`\n      };\n    }\n  };\n\n  return (\n    <div \n      ref={containerRef}\n      className={cn(\n        \"relative flex flex-col items-center gap-8 overflow-hidden rounded-[20px] border border-[#d8d3c7] bg-[#f4f1e9] p-8 text-[#171717] shadow-[inset_0_7px_14px_rgba(255,255,255,0.62),inset_0_0.5px_0.5px_rgba(255,255,255,0.9),0_18px_34px_-16px_rgba(55,47,36,0.28)] dark:border-[#161616] dark:bg-[#121212] dark:text-[#ededed] dark:shadow-[inset_0_7px_14px_rgba(255,255,255,0.03),inset_0_0.5px_0.5px_rgba(255,255,255,0.06),0_18px_34px_-16px_rgba(0,0,0,0.9)]\",\n        className\n      )}\n    >\n      {/* Radial gradient overlay */}\n      {selectedIndex >= 0 && gradientPosition && (\n        <div \n          className=\"absolute inset-0 pointer-events-none z-0\"\n          style={{\n            background: `radial-gradient(circle at ${gradientPosition.x}px ${gradientPosition.y + 440}px, ${validOptions[selectedIndex].color}18 0%, ${validOptions[selectedIndex].color}10 30%, transparent 70%)`,\n          }}\n        />\n      )}\n      \n      <div className=\"relative z-10 flex items-center gap-6 rounded-full border border-[#d8d3c7] bg-[#eee9df] p-6 shadow-[inset_0_12px_24px_rgba(68,54,34,0.08),inset_0_1px_0_rgba(255,255,255,0.7)] dark:border-[#232323] dark:bg-[#090909] dark:shadow-[inset_0_12px_24px_rgba(0,0,0,0.45),inset_0_1px_0_rgba(255,255,255,0.035)]\">\n        {validOptions.map((option, index) => (\n          <div key={option.id} className=\"flex items-center gap-6\">\n            {/* Circle */}\n            <div \n              ref={(el) => { circleRefs.current[index] = el; }}\n              className={cn(\n                \"relative cursor-pointer transition-all duration-200 hover:scale-110\",\n                getCircleSize(index),\n                \"rounded-full border-2 border-transparent\"\n              )}\n              onClick={() => handleCircleClick(option, index)}\n              style={{\n                backgroundColor: selectedIndex >= index ? option.color : '#8f877c',\n                boxShadow: selectedIndex >= index \n                  ? `0 0 20px ${option.color}40, 0 0 40px ${option.color}20`\n                  : 'none'\n              }}\n            >\n              {selectedIndex === index && createOrbitalDots(12, 16, option.color)}\n            </div>\n            \n            {/* Line (don't render after last circle) */}\n            {index < validOptions.length - 1 && (\n              <div \n                className={cn(\"w-24 rounded-full transition-all duration-300\", \n                  index === 0 ? \"h-1.5\" : \n                  index === 1 ? \"h-1.75\" : \n                  index === 2 ? \"h-2\" : \"h-2.25\"\n                )}\n                style={getLineStyle(index)}\n              />\n            )}\n          </div>\n        ))}\n      </div>\n      \n      {/* Labels */}\n      <div className=\"relative z-10 flex items-center gap-4\">\n        {validOptions.map((option, index) => (\n          <div key={`label-${option.id}`} className=\"flex items-center gap-2\">\n            <span \n              className={cn(\n                \"text-sm font-medium transition-colors duration-200 cursor-pointer\",\n                selectedIndex >= index ? \"text-[#171717] dark:text-[#ededed]\" : \"text-[#171717]/42 dark:text-[#ededed]/34\"\n              )}\n              onClick={() => handleCircleClick(option, index)}\n              style={{\n                color: selectedIndex >= index ? option.color : undefined\n              }}\n            >\n              {option.label}\n            </span>\n            {index < validOptions.length - 1 && <div className=\"w-24\" />}\n          </div>\n        ))}\n      </div>\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "@components/bjork-ui/dropdowns/gradient-selector.tsx"
    },
    {
      "path": "lib/utils.ts",
      "content": "import { clsx, type ClassValue } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs))\n}\n",
      "type": "registry:lib",
      "target": "@lib/utils.ts"
    }
  ],
  "cssVars": {
    "light": {
      "bjork-bg": "#f7f3ea",
      "bjork-surface": "#fffcf6",
      "bjork-surface-muted": "rgba(250, 246, 237, 0.94)",
      "bjork-surface-hover": "#f5efe3",
      "bjork-surface-active": "#efe7d8",
      "bjork-panel": "#f8f2e7",
      "bjork-menu": "rgba(255, 252, 246, 0.98)",
      "bjork-field": "rgba(255, 252, 246, 0.96)",
      "bjork-field-muted": "rgba(248, 242, 231, 0.82)",
      "bjork-field-inset": "rgba(239, 231, 216, 0.72)",
      "bjork-error-bg": "#fff0e8",
      "bjork-text": "#171717",
      "bjork-text-strong": "rgba(23, 23, 23, 0.9)",
      "bjork-text-medium": "rgba(23, 23, 23, 0.72)",
      "bjork-text-muted": "rgba(23, 23, 23, 0.52)",
      "bjork-text-soft": "rgba(23, 23, 23, 0.36)",
      "bjork-text-faint": "rgba(23, 23, 23, 0.22)",
      "bjork-inverted-text": "#fff8f0",
      "bjork-border": "#eee6db",
      "bjork-border-muted": "#f5ede2",
      "bjork-border-strong": "#e1d7c8",
      "bjork-thumb": "#a49b8e",
      "bjork-track": "#ded6ca",
      "bjork-accent": "#ec7d43",
      "bjork-accent-hover": "#f0935f",
      "bjork-accent-soft": "rgba(236, 125, 67, 0.1)",
      "bjork-accent-muted": "rgba(236, 125, 67, 0.2)",
      "bjork-accent-badge": "rgba(236, 125, 67, 0.16)",
      "bjork-accent-badge-foreground": "#8d421d",
      "bjork-accent-foreground": "#3f2112",
      "bjork-shadow-surface": "inset 0 7px 14px rgba(88, 72, 49, 0.045), inset 0 0.5px 0.5px rgba(255, 255, 255, 0.92), inset 1px 0 0 rgba(88, 72, 49, 0.026), inset -1px 0 0 rgba(255, 255, 255, 0.68), 0 14px 22px -9px rgba(66, 52, 33, 0.11)",
      "bjork-shadow-soft": "inset 0 1px 0 rgba(88, 72, 49, 0.045), inset 0 0.5px 0.5px rgba(255, 255, 255, 0.86)",
      "bjork-shadow-panel": "inset 0 1px 0 rgba(88, 72, 49, 0.04), inset 1px 0 0 rgba(88, 72, 49, 0.024), inset -1px 0 0 rgba(255, 255, 255, 0.64), 0 18px 38px -30px rgba(66, 52, 33, 0.2)",
      "bjork-shadow-menu": "inset 0 1px 0 rgba(88, 72, 49, 0.04), inset 0 12px 24px rgba(88, 72, 49, 0.022), inset 1px 0 0 rgba(88, 72, 49, 0.024), inset -1px 0 0 rgba(255, 255, 255, 0.62), 0 22px 44px -26px rgba(66, 52, 33, 0.22)",
      "bjork-shadow-inset": "inset 0 1px 10px rgba(88, 72, 49, 0.12), inset 0 0.5px 0.5px rgba(255, 255, 255, 0.72)",
      "bjork-ring-offset": "#f7f3ea"
    },
    "dark": {
      "bjork-bg": "#111111",
      "bjork-surface": "#121212",
      "bjork-surface-muted": "rgba(22, 22, 22, 0.92)",
      "bjork-surface-hover": "#161616",
      "bjork-surface-active": "#202020",
      "bjork-panel": "#0d0d0d",
      "bjork-menu": "rgba(18, 18, 18, 0.98)",
      "bjork-field": "rgba(18, 18, 18, 0.95)",
      "bjork-field-muted": "rgba(18, 18, 18, 0.55)",
      "bjork-field-inset": "rgba(9, 9, 9, 0.75)",
      "bjork-error-bg": "#130d0a",
      "bjork-text": "#ededed",
      "bjork-text-strong": "rgba(237, 237, 237, 0.9)",
      "bjork-text-medium": "rgba(237, 237, 237, 0.72)",
      "bjork-text-muted": "rgba(237, 237, 237, 0.52)",
      "bjork-text-soft": "rgba(237, 237, 237, 0.36)",
      "bjork-text-faint": "rgba(237, 237, 237, 0.22)",
      "bjork-inverted-text": "#080808",
      "bjork-border": "#232323",
      "bjork-border-muted": "#1c1c1c",
      "bjork-border-strong": "#343434",
      "bjork-thumb": "#626262",
      "bjork-track": "#090909",
      "bjork-accent": "#ec5c13",
      "bjork-accent-hover": "#f07832",
      "bjork-accent-soft": "rgba(236, 92, 19, 0.12)",
      "bjork-accent-muted": "rgba(236, 92, 19, 0.24)",
      "bjork-accent-badge": "#ec5c13",
      "bjork-accent-badge-foreground": "#fff2ea",
      "bjork-accent-foreground": "#fff2ea",
      "bjork-shadow-surface": "inset 0 7px 14px rgba(255, 255, 255, 0.03), inset 0 0.5px 0.5px rgba(255, 255, 255, 0.06), 0 14px 20px -6px rgba(0, 0, 0, 0.45)",
      "bjork-shadow-soft": "inset 0 1px 0 rgba(255, 255, 255, 0.045)",
      "bjork-shadow-panel": "inset 0 1px 0 rgba(255, 255, 255, 0.035), 0 18px 36px -28px rgba(0, 0, 0, 0.9)",
      "bjork-shadow-menu": "inset 0 1px 0 rgba(255, 255, 255, 0.055), inset 0 12px 24px rgba(255, 255, 255, 0.018), inset 0 -18px 26px rgba(0, 0, 0, 0.24), 0 22px 42px -22px rgba(0, 0, 0, 0.9)",
      "bjork-shadow-inset": "inset 0 1px 10px rgba(0, 0, 0, 0.45)",
      "bjork-ring-offset": "#080808"
    }
  },
  "css": {
    ".bjork-range": {
      "box-shadow": "inset 0 1px 1px rgba(255, 255, 255, 0.04), 0 0 14px rgba(189, 69, 20, 0.06)"
    },
    ".bjork-range::-webkit-slider-thumb": {
      "appearance": "none",
      "width": "16px",
      "height": "8px",
      "border": "0",
      "border-radius": "999px",
      "background": "var(--bjork-thumb)",
      "box-shadow": "inset 0 1px 0 rgba(255, 255, 255, 0.18), 0 0 0 1px rgba(0, 0, 0, 0.42)"
    },
    ".bjork-range::-moz-range-thumb": {
      "width": "16px",
      "height": "8px",
      "border": "0",
      "border-radius": "999px",
      "background": "var(--bjork-thumb)",
      "box-shadow": "inset 0 1px 0 rgba(255, 255, 255, 0.18), 0 0 0 1px rgba(0, 0, 0, 0.42)"
    },
    ".bjork-layered-button-accent, .bjork-layered-button-raised": {
      "position": "relative",
      "isolation": "isolate",
      "appearance": "none",
      "overflow": "hidden",
      "border": "0",
      "border-radius": "13px",
      "box-shadow": "inset 0 1px 0 #ffffff38, 0 4px 6px -1px #0000001a, 0 2px 4px -2px #0000001a",
      "transition": "filter 150ms ease-out, box-shadow 150ms ease-out"
    },
    ".bjork-layered-button-accent::after, .bjork-layered-button-raised::after": {
      "position": "absolute",
      "inset": "0",
      "pointer-events": "none",
      "content": "\"\"",
      "border": "1.5px solid transparent",
      "border-radius": "inherit",
      "background": "linear-gradient(#ffffffb8, #0000003d 41% 75%, #ffffff47) border-box",
      "mix-blend-mode": "overlay",
      "-webkit-mask-image": "linear-gradient(#000, #000), linear-gradient(#000, #000)",
      "mask-image": "linear-gradient(#000, #000), linear-gradient(#000, #000)",
      "-webkit-mask-clip": "padding-box, border-box",
      "mask-clip": "padding-box, border-box",
      "-webkit-mask-origin": "padding-box, border-box",
      "mask-origin": "padding-box, border-box",
      "-webkit-mask-composite": "xor",
      "mask-composite": "exclude"
    },
    ".bjork-layered-button-accent": {
      "color": "oklch(100% 0 0)",
      "text-shadow": "0 -1px #00000040",
      "background": "radial-gradient(ellipse at -20px top, rgba(255, 255, 255, 0.14), transparent), linear-gradient(180deg, #ec5c13 0%, #ec5c13 62%, color-mix(in oklab, #ec5c13 84%, black 16%) 100%)"
    },
    ".bjork-layered-button-raised": {
      "color": "oklch(100% 0 0)",
      "text-shadow": "0 -1px #00000026",
      "background": "radial-gradient(ellipse at -20px top, #ffffff38, #fff0), linear-gradient(180deg, oklch(0.1881 0.006 265), oklch(0.5493 0.0081 265))"
    },
    ".dark .bjork-layered-button-raised": {
      "color": "oklch(0.2574 0.0056 265)",
      "text-shadow": "0 -1px #00000026",
      "background": "radial-gradient(ellipse at -20px top, #ffffff38, #fff0), linear-gradient(180deg, color-mix(in oklab, oklch(0.9674 0.0013 265) 80%, oklch(0.2574 0.0056 265) 20%), color-mix(in oklab, oklch(0.669 0.0107 265) 80%, oklch(0.2574 0.0056 265) 20%))"
    },
    "@media (hover: hover)": {
      ".bjork-layered-button-accent:hover": {
        "filter": "brightness(1.08) saturate(1.04)"
      },
      ".bjork-layered-button-raised:hover": {
        "filter": "brightness(1.08) contrast(0.9)"
      }
    }
  },
  "meta": {
    "collection": "Controls",
    "registryUrl": "https://ui.isaiahbjork.com/gradient-selector.json",
    "installCommand": "npx shadcn@latest add https://ui.isaiahbjork.com/gradient-selector.json"
  },
  "type": "registry:component"
}