{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "glass-button",
  "title": "Glass Button",
  "description": "An iOS-style liquid-glass button: a circular icon disc or text capsule on a frosted glass surface, with a velocity-eased press gel that squeezes the button and clears the tint while held.",
  "registryDependencies": [
    "https://loreglasses.com/r/glass.json"
  ],
  "files": [
    {
      "path": "registry/default/glass-button/glass-button.tsx",
      "content": "\"use client\";\n\nimport {\n  GlassSurface,\n  type GlassSurfaceHandle,\n} from \"@/components/ui/glass-surface\";\nimport {\n  glassEase,\n  MotionValue,\n  PRESS_DURATION,\n  prefersReducedMotion,\n  RELEASE_DURATION,\n  tween,\n} from \"@/components/ui/glass-motion\";\nimport { cn } from \"@/lib/utils\";\nimport { type ComponentProps, useCallback, useEffect, useRef } from \"react\";\n\nconst PRESS_SCALE = 0.92;\nconst PRESS_LIFT = -0.2;\n\ninterface GlassButtonProps extends ComponentProps<\"button\"> {\n  size?: number;\n  tint?: number;\n  tintColor?: string;\n  variant?: \"icon\" | \"capsule\";\n}\n\nfunction GlassButton({\n  size = 44,\n  tint = 0.2,\n  tintColor,\n  variant = \"icon\",\n  className,\n  style,\n  children,\n  type = \"button\",\n  onPointerDown,\n  onPointerUp,\n  onPointerCancel,\n  onPointerLeave,\n  onKeyDown,\n  onKeyUp,\n  ...props\n}: GlassButtonProps) {\n  const innerRef = useRef<HTMLSpanElement | null>(null);\n  const surfaceHandle = useRef<GlassSurfaceHandle | null>(null);\n  const scale = useRef(new MotionValue(1));\n  const lift = useRef(new MotionValue(0));\n  const pressed = useRef(false);\n  const frame = useRef(0);\n\n  const apply = useCallback(() => {\n    frame.current = 0;\n    const inner = innerRef.current;\n    if (inner) {\n      const s = scale.current.get();\n      inner.style.transform = s === 1 ? \"\" : `scale(${s})`;\n    }\n    surfaceHandle.current?.setTintLift(lift.current.get());\n  }, []);\n\n  const schedule = useCallback(() => {\n    if (frame.current === 0) {\n      frame.current = requestAnimationFrame(apply);\n    }\n  }, [apply]);\n\n  useEffect(() => {\n    const subs = [scale.current.on(schedule), lift.current.on(schedule)];\n    return () => {\n      for (const off of subs) {\n        off();\n      }\n      cancelAnimationFrame(frame.current);\n    };\n  }, [schedule]);\n\n  const press = useCallback(() => {\n    if (pressed.current) {\n      return;\n    }\n    pressed.current = true;\n    const d = prefersReducedMotion() ? 0 : PRESS_DURATION;\n    tween(scale.current, PRESS_SCALE, d, glassEase);\n    tween(lift.current, PRESS_LIFT, d, glassEase);\n  }, []);\n\n  const release = useCallback(() => {\n    if (!pressed.current) {\n      return;\n    }\n    pressed.current = false;\n    const d = prefersReducedMotion() ? 0 : RELEASE_DURATION;\n    tween(scale.current, 1, d, glassEase);\n    tween(lift.current, 0, d, glassEase);\n  }, []);\n\n  return (\n    <button\n      className={cn(\n        \"relative inline-flex shrink-0 cursor-pointer select-none items-center justify-center text-[#1d1d1f] outline-none focus-visible:outline-2 focus-visible:outline-[#9896ff] focus-visible:outline-offset-2 disabled:cursor-not-allowed disabled:opacity-40 dark:text-[#f5f5f7]\",\n        className\n      )}\n      onKeyDown={(e) => {\n        if ((e.key === \" \" || e.key === \"Enter\") && !e.repeat) {\n          press();\n        }\n        onKeyDown?.(e);\n      }}\n      onKeyUp={(e) => {\n        release();\n        onKeyUp?.(e);\n      }}\n      onPointerCancel={(e) => {\n        release();\n        onPointerCancel?.(e);\n      }}\n      onPointerDown={(e) => {\n        press();\n        onPointerDown?.(e);\n      }}\n      onPointerLeave={(e) => {\n        release();\n        onPointerLeave?.(e);\n      }}\n      onPointerUp={(e) => {\n        release();\n        onPointerUp?.(e);\n      }}\n      style={{\n        height: size,\n        width: variant === \"icon\" ? size : undefined,\n        borderRadius: size / 2,\n        ...style,\n      }}\n      type={type}\n      {...props}\n    >\n      <span\n        className={cn(\n          \"relative inline-flex h-full items-center justify-center rounded-[inherit]\",\n          variant === \"icon\" && \"w-full\"\n        )}\n        ref={innerRef}\n        style={{\n          paddingInline: variant === \"capsule\" ? Math.round(size * 0.45) : 0,\n          willChange: \"transform\",\n        }}\n      >\n        <GlassSurface\n          aria-hidden=\"true\"\n          blur={8}\n          className=\"absolute inset-0\"\n          handleRef={surfaceHandle}\n          radius={size / 2}\n          tint={tint}\n          tintColor={tintColor}\n        />\n        <span className=\"relative inline-flex items-center justify-center gap-2 font-medium text-[15px] leading-none\">\n          {children}\n        </span>\n      </span>\n    </button>\n  );\n}\n\nexport { GlassButton };\nexport type { GlassButtonProps };\n",
      "type": "registry:ui"
    }
  ],
  "docs": "Import { GlassButton } from \"@/components/ui/glass-button\". variant=\"icon\" (default) renders a fixed-size disc (pass an icon as children with an aria-label); variant=\"capsule\" renders a text pill that grows with its label. `size` sets disc diameter / capsule height in px; `tint` is 0 (clear) to 1 (opaque). All native <button> props are forwarded.",
  "type": "registry:ui"
}