{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "jelly-button",
  "title": "Jelly Button",
  "description": "A squishy button that deforms on press and springs back with a jelly wobble, running entirely on the compositor.",
  "registryDependencies": ["@godui/godui-theme"],
  "files": [
    {
      "path": "packages/components/src/jelly-button/jelly-button.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\n\nexport type JellyButtonVariant = \"primary\" | \"secondary\" | \"outline\";\nexport type JellyButtonSize = \"sm\" | \"md\" | \"lg\";\n\nexport type JellyButtonProps = React.ButtonHTMLAttributes<HTMLButtonElement> & {\n  variant?: JellyButtonVariant;\n  size?: JellyButtonSize;\n  /**\n   * How hard the button squashes on press, `0`–`1`. Higher = more deform.\n   * Drives the `--jelly-press` scale the button snaps to while held.\n   * @default 0.6\n   */\n  squash?: number;\n};\n\n// Squishy/tactile button. The whole surface squashes on press (scaleX ↑,\n// scaleY ↓ from `origin-bottom`) and springs back with a single overshoot via\n// the `back` easing — a jelly wobble that never leaves the compositor. Only\n// `scale` animates (MotionScore S); color/shadow differences between states are\n// static, never transitioned. Reduced motion drops the deform entirely.\nconst BUTTON_CLASS =\n  \"group relative inline-flex origin-bottom cursor-pointer select-none items-center justify-center border-none font-medium [will-change:transform] [-webkit-tap-highlight-color:transparent] [transition:scale_300ms_cubic-bezier(0.3,0.7,0.4,1.5)] hover:[scale:1.04] focus:outline-none focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background focus-visible:[scale:1.04] active:[scale:var(--jelly-press)] active:[transition:scale_120ms_cubic-bezier(0.3,0.7,0.4,1)] data-[pressed=true]:[scale:var(--jelly-press)] data-[pressed=true]:[transition:scale_120ms_cubic-bezier(0.3,0.7,0.4,1)] disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 motion-reduce:[transition:none] motion-reduce:hover:[scale:1] motion-reduce:focus-visible:[scale:1] motion-reduce:active:[scale:1] motion-reduce:data-[pressed=true]:[scale:1]\";\n\nconst variantClasses: Record<JellyButtonVariant, string> = {\n  primary: \"bg-primary text-primary-foreground shadow-sm\",\n  secondary: \"bg-secondary text-secondary-foreground shadow-sm\",\n  outline:\n    \"border border-border bg-background text-foreground shadow-xs hover:bg-accent hover:text-accent-foreground\",\n};\n\nconst sizeClasses: Record<JellyButtonSize, string> = {\n  sm: \"rounded-[var(--button-radius-sm)] px-[var(--button-px-sm)] py-[var(--button-py-sm)] text-[length:var(--button-text-sm)] leading-[var(--button-leading-sm)]\",\n  md: \"rounded-[var(--button-radius-md)] px-[var(--button-px-md)] py-[var(--button-py-md)] text-[length:var(--button-text-md)] leading-[var(--button-leading-md)]\",\n  lg: \"rounded-[var(--button-radius-lg)] px-[var(--button-px-lg)] py-[var(--button-py-lg)] text-[length:var(--button-text-lg)] leading-[var(--button-leading-lg)]\",\n};\n\n/** Map the `squash` knob to the `scale: <x> <y>` the button snaps to on press. */\nfunction pressScale(squash: number): string {\n  const amount = Math.min(Math.max(squash, 0), 1) * 0.22;\n  return `${(1 + amount).toFixed(3)} ${(1 - amount).toFixed(3)}`;\n}\n\nconst JellyButton = React.forwardRef<HTMLButtonElement, JellyButtonProps>(\n  (\n    {\n      className,\n      variant = \"primary\",\n      size = \"md\",\n      squash = 0.6,\n      style,\n      children,\n      onKeyDown,\n      onKeyUp,\n      ...props\n    },\n    ref,\n  ) => {\n    const [pressed, setPressed] = React.useState(false);\n\n    // Keyboard activation (Enter/Space) mirrors the pointer squash so the\n    // wobble reads the same for keyboard users.\n    const handleKeyDown = (event: React.KeyboardEvent<HTMLButtonElement>) => {\n      if (event.key === \"Enter\" || event.key === \" \") setPressed(true);\n      onKeyDown?.(event);\n    };\n    const handleKeyUp = (event: React.KeyboardEvent<HTMLButtonElement>) => {\n      if (event.key === \"Enter\" || event.key === \" \") setPressed(false);\n      onKeyUp?.(event);\n    };\n\n    return (\n      <button\n        ref={ref}\n        type=\"button\"\n        data-variant={variant}\n        data-pressed={pressed ? \"true\" : undefined}\n        onKeyDown={handleKeyDown}\n        onKeyUp={handleKeyUp}\n        style={\n          {\n            \"--jelly-press\": pressScale(squash),\n            ...style,\n          } as React.CSSProperties\n        }\n        className={`${BUTTON_CLASS} ${variantClasses[variant]} ${sizeClasses[size]} ${className ?? \"\"}`}\n        {...props}\n      >\n        {children}\n      </button>\n    );\n  },\n);\nJellyButton.displayName = \"JellyButton\";\n\nexport { JellyButton };\n",
      "type": "registry:ui",
      "target": "components/godui/jelly-button.tsx"
    }
  ],
  "type": "registry:ui"
}
