{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"gradient-button","type":"registry:ui","title":"GradientButton","description":"Dark button with a beam of light that travels around its border and casts a soft coloured glow inside the face; the glow brightens on hover","author":"fancy-ui <https://fancy-ui.rama.app>","categories":["buttons"],"registryDependencies":["https://fancy-ui.rama.app/r/internals-dom.json","https://fancy-ui.rama.app/r/sound.json","https://fancy-ui.rama.app/r/utils.json"],"files":[{"path":"react/src/components/gradient-button/GradientButton.tsx","content":"\"use client\";\n\nimport { forwardRef } from \"react\";\nimport type { ButtonHTMLAttributes, CSSProperties, MouseEvent, ReactNode } from \"react\";\nimport { cn } from \"../../utils\";\nimport { useSoundCue } from \"../../sound/use-sound\";\nimport { useSoundFeedback } from \"../../sound/sound-feedback\";\nimport { useComposedRefs } from \"../../internals/dom/use-composed-refs\";\nimport \"./gradient-button.css\";\n\ntype BaseProps = {\n\t/** Colours of the beam, spread along its arc */\n\tcolors?: string[];\n\t/** Time for the beam to travel once around the button, in milliseconds */\n\tduration?: number;\n\t/** Width of the lit border in pixels */\n\tborderWidth?: number;\n\t/** Border radius in pixels */\n\tborderRadius?: number;\n\t/** Softness of the glow the beam casts inside the button, in pixels */\n\tblur?: number;\n\t/** Background color of the button face */\n\tbgColor?: string;\n\t/** Custom CSS class */\n\tclassName?: string;\n\t/** Button content */\n\tchildren?: ReactNode;\n\t/**\n\t * Plays the `hover` cue on mouse/pen pointer enter and the `press` cue on\n\t * click. Off by default; only audible once the user has enabled sound.\n\t */\n\tsound?: boolean;\n};\n\nexport interface GradientButtonProps\n\textends BaseProps, Omit<ButtonHTMLAttributes<HTMLButtonElement>, keyof BaseProps> {}\n\n/**\n * The beam as a conic gradient: a main arc carrying every colour over ~40%\n * of the turn, and a fainter echo opposite it. Turned by `--gb-angle`.\n */\nexport function beamGradient(colors: string[]): string {\n\tconst list = colors.length ? colors : [\"#ffffff\"];\n\tconst arc = (from: number, to: number, strength: number) => {\n\t\tconst span = to - from;\n\t\tconst stops = list.map((c, i) => {\n\t\t\tconst at = from + span * ((i + 1) / (list.length + 1));\n\t\t\tconst color = strength < 100 ? `color-mix(in srgb, ${c} ${strength}%, transparent)` : c;\n\t\t\treturn `${color} ${at.toFixed(1)}%`;\n\t\t});\n\t\treturn [`transparent ${from}%`, ...stops, `transparent ${to}%`].join(\", \");\n\t};\n\treturn `conic-gradient(from var(--gb-angle), ${arc(0, 42, 100)}, ${arc(50, 78, 45)}, transparent 100%)`;\n}\n\nconst DEFAULT_COLORS = [\"#34d399\", \"#22d3ee\", \"#6366f1\", \"#d946ef\", \"#f43f5e\", \"#f59e0b\"];\n\nexport const GradientButton = forwardRef<HTMLButtonElement, GradientButtonProps>(\n\t(\n\t\t{\n\t\t\tclassName,\n\t\t\tcolors = DEFAULT_COLORS,\n\t\t\tduration = 3000,\n\t\t\tborderWidth = 1.5,\n\t\t\tborderRadius = 12,\n\t\t\tblur = 4,\n\t\t\tbgColor = \"#161616\",\n\t\t\tchildren,\n\t\t\tonClick,\n\t\t\tsound = false,\n\t\t\t...restProps\n\t\t},\n\t\tref\n\t) => {\n\t\tconst playCue = useSoundCue(sound);\n\n\t\t// Hover cue for mouse/pen pointers only. `on: {}` binds nothing when sound is\n\t\t// off (and never the default click → press, which `handleClick` already plays).\n\t\tconst soundRef = useSoundFeedback({\n\t\t\ton: sound ? { pointerenter: \"hover\" } : {},\n\t\t\tdisabled: restProps.disabled,\n\t\t});\n\t\tconst composedRef = useComposedRefs(ref, soundRef);\n\n\t\tfunction handleClick(event: MouseEvent<HTMLButtonElement>) {\n\t\t\tif (!restProps.disabled) playCue(\"press\");\n\t\t\tonClick?.(event);\n\t\t}\n\n\t\tconst styleVars = {\n\t\t\t\"--gb-colors\": colors.join(\", \"),\n\t\t\t\"--gb-beam\": beamGradient(colors),\n\t\t\t\"--gb-duration\": `${duration}ms`,\n\t\t\t\"--gb-border-width\": `${borderWidth}px`,\n\t\t\t\"--gb-border-radius\": `${borderRadius}px`,\n\t\t\t\"--gb-blur\": `${blur}px`,\n\t\t\t\"--gb-bg-color\": bgColor,\n\t\t} as CSSProperties;\n\n\t\treturn (\n\t\t\t<button\n\t\t\t\tref={composedRef}\n\t\t\t\tclassName={cn(\n\t\t\t\t\t\"gradient-button relative flex min-h-10 min-w-28 cursor-pointer items-center justify-center overflow-hidden text-white\",\n\t\t\t\t\tclassName\n\t\t\t\t)}\n\t\t\t\tstyle={styleVars}\n\t\t\t\tonClick={handleClick}\n\t\t\t\t{...restProps}\n\t\t\t>\n\t\t\t\t{/* Face and label */}\n\t\t\t\t<span className=\"gradient-content inline-flex size-full items-center justify-center px-5 py-2.5\">\n\t\t\t\t\t{children}\n\t\t\t\t</span>\n\n\t\t\t\t{/* The glow the beam casts inside the face */}\n\t\t\t\t<span className=\"gradient-glow\" aria-hidden=\"true\">\n\t\t\t\t\t<span></span>\n\t\t\t\t</span>\n\n\t\t\t\t{/* The beam on the border */}\n\t\t\t\t<span className=\"gradient-border\" aria-hidden=\"true\" />\n\t\t\t</button>\n\t\t);\n\t}\n);\n\nGradientButton.displayName = \"GradientButton\";\n","type":"registry:ui","target":"@ui/fancy/components/gradient-button/GradientButton.tsx"},{"path":"react/src/components/gradient-button/gradient-button.css","content":"@property --gb-angle {\n\tsyntax: \"<angle>\";\n\tinherits: true;\n\tinitial-value: 0deg;\n}\n\n.gradient-button {\n\tborder-radius: var(--gb-border-radius);\n\tisolation: isolate;\n\tbackground: color-mix(in srgb, var(--gb-bg-color), #fff 8%);\n\tanimation: gb-spin var(--gb-duration) linear infinite;\n\ttransition: transform 0.2s ease;\n}\n\n.gradient-button:active {\n\ttransform: scale(0.98);\n}\n\n.gradient-button:disabled {\n\tcursor: not-allowed;\n\topacity: 0.5;\n\tanimation-play-state: paused;\n}\n\n/* Anchored on the root class: the Svelte compiler scoped these rules, plain CSS\n   would leak them into every consumer element sharing the class name. */\n.gradient-button .gradient-content {\n\tposition: relative;\n\tmargin: var(--gb-border-width);\n\tborder-radius: calc(var(--gb-border-radius) - var(--gb-border-width));\n\tbackground:\n\t\tlinear-gradient(to bottom, rgb(255 255 255 / 0.06), transparent 45%), var(--gb-bg-color);\n}\n\n.gradient-button .gradient-border,\n.gradient-button .gradient-glow,\n.gradient-button .gradient-glow > span {\n\tposition: absolute;\n\tpointer-events: none;\n\tborder-radius: inherit;\n}\n\n/* keep only the padding ring: the element minus its content box */\n.gradient-button .gradient-border,\n.gradient-button .gradient-glow > span {\n\tbackground: var(--gb-beam);\n\t-webkit-mask:\n\t\tlinear-gradient(#000 0 0) content-box,\n\t\tlinear-gradient(#000 0 0);\n\t-webkit-mask-composite: xor;\n\tmask:\n\t\tlinear-gradient(#000 0 0) content-box,\n\t\tlinear-gradient(#000 0 0);\n\tmask-composite: exclude;\n}\n\n.gradient-button .gradient-border {\n\tinset: 0;\n\tz-index: 2;\n\tpadding: var(--gb-border-width);\n}\n\n/* A wider band inside the border, blurred by its parent AFTER masking so\n   it falls off softly into the face. Screen: it lights, never darkens. */\n.gradient-button .gradient-glow {\n\tinset: 0;\n\tz-index: 1;\n\tfilter: blur(calc(var(--gb-blur) * 3));\n\tmix-blend-mode: screen;\n\topacity: 0.45;\n\ttransition: opacity 0.3s ease;\n}\n\n.gradient-button .gradient-glow > span {\n\tinset: 0;\n\tpadding: calc(var(--gb-blur) * 1.75);\n}\n\n.gradient-button:hover .gradient-glow,\n.gradient-button:focus-visible .gradient-glow {\n\topacity: 0.8;\n}\n\n.gradient-button:focus-visible {\n\toutline: 2px solid rgb(255 255 255 / 0.5);\n\toutline-offset: 2px;\n}\n\n@keyframes gb-spin {\n\tto {\n\t\t--gb-angle: 360deg;\n\t}\n}\n\n@media (prefers-reduced-motion: reduce) {\n\t.gradient-button {\n\t\tanimation: none;\n\t\t--gb-angle: 135deg;\n\t}\n}\n","type":"registry:ui","target":"@ui/fancy/components/gradient-button/gradient-button.css"},{"path":"react/src/components/gradient-button/index.ts","content":"\"use client\";\n\nexport { GradientButton } from \"./GradientButton\";\nexport type { GradientButtonProps } from \"./GradientButton\";\n","type":"registry:ui","target":"@ui/fancy/components/gradient-button/index.ts"}],"meta":{"license":"MIT","package":"fancy-ui-react","version":"0.7.0","source":"https://fancy-ui.rama.app/docs/components/gradient-button?framework=react"}}