{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "squeeze",
  "title": "Squeeze",
  "description": "A squeeze animation primitive plus a ready-made SVG card icon.",
  "files": [
    {
      "path": "registry/base/ui/squeeze.tsx",
      "content": "import * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\nimport \"./squeeze.css\";\n\ntype SqueezeCardValue = number | string;\n\nexport type SqueezeOptions = {\n  /** Horizontal rebound distance. Numeric values are converted to px. Set to 0 to disable. */\n  shakeX?: SqueezeCardValue;\n  /** Vertical rebound distance. Numeric values are converted to px. Set to 0 to disable. */\n  shakeY?: SqueezeCardValue;\n  /** Animation duration. Numeric values are converted to ms. */\n  duration?: SqueezeCardValue;\n};\n\nexport type SqueezeProps = React.ComponentProps<\"span\"> &\n  SqueezeOptions & {\n    /** Classes applied to the animated child wrapper. */\n    targetClassName?: string;\n  };\n\nexport function Squeeze({\n  className,\n  children,\n  duration,\n  shakeX,\n  shakeY,\n  style,\n  targetClassName,\n  ...props\n}: SqueezeProps) {\n  return (\n    <span\n      data-slot=\"squeeze\"\n      className={cn(\"squeeze inline-flex\", className)}\n      style={getSqueezeStyle({ duration, shakeX, shakeY }, style)}\n      {...props}\n    >\n      <span\n        data-slot=\"squeeze-target\"\n        className={cn(\"squeeze-target inline-flex\", targetClassName)}\n      >\n        {children}\n      </span>\n    </span>\n  );\n}\n\nexport type SqueezeCardProps = React.ComponentProps<\"div\"> &\n  SqueezeOptions & {\n    /** Classes applied to the animated SVG group. */\n    targetClassName?: string;\n  };\n\nexport function SqueezeCard({\n  className,\n  duration,\n  shakeX,\n  shakeY,\n  style,\n  targetClassName,\n  ...props\n}: SqueezeCardProps) {\n  return (\n    <div\n      data-slot=\"squeeze\"\n      className={cn(\n        \"squeeze flex flex-col items-center text-foreground\",\n        className,\n      )}\n      style={getSqueezeStyle({ duration, shakeX, shakeY }, style)}\n      {...props}\n    >\n      <svg\n        viewBox=\"0 0 100 100\"\n        aria-hidden=\"true\"\n        className=\"squeeze-svg h-auto w-full max-w-80\"\n      >\n        <g\n          data-slot=\"squeeze-target\"\n          className={cn(\n            \"squeeze-target squeeze-icon\",\n            targetClassName,\n          )}\n        >\n          <rect\n            x=\"26\"\n            y=\"30\"\n            width=\"48\"\n            height=\"40\"\n            rx=\"12\"\n            fill=\"currentColor\"\n            fillOpacity=\"0.1\"\n            stroke=\"currentColor\"\n            strokeWidth=\"6\"\n          />\n          <path\n            d=\"M 38 46 H 62\"\n            fill=\"none\"\n            stroke=\"currentColor\"\n            strokeLinecap=\"round\"\n            strokeWidth=\"6\"\n          />\n          <path\n            d=\"M 38 58 H 54\"\n            fill=\"none\"\n            stroke=\"currentColor\"\n            strokeLinecap=\"round\"\n            strokeWidth=\"6\"\n          />\n        </g>\n      </svg>\n    </div>\n  );\n}\n\nfunction getSqueezeStyle(\n  {\n    duration,\n    shakeX,\n    shakeY,\n  }: Pick<SqueezeOptions, \"duration\" | \"shakeX\" | \"shakeY\">,\n  style: React.CSSProperties | undefined,\n) {\n  if (duration === undefined && shakeX === undefined && shakeY === undefined) {\n    return style;\n  }\n\n  return {\n    ...style,\n    ...(duration !== undefined\n      ? { \"--squeeze-duration\": formatDurationValue(duration) }\n      : {}),\n    ...(shakeX !== undefined\n      ? { \"--squeeze-shake-x\": formatShakeValue(shakeX) }\n      : {}),\n    ...(shakeY !== undefined\n      ? { \"--squeeze-shake-y\": formatShakeValue(shakeY) }\n      : {}),\n  } as React.CSSProperties;\n}\n\nfunction formatShakeValue(value: SqueezeCardValue | undefined) {\n  return typeof value === \"number\" ? `${value}px` : value;\n}\n\nfunction formatDurationValue(value: SqueezeCardValue | undefined) {\n  return typeof value === \"number\" ? `${value}ms` : value;\n}\n",
      "type": "registry:ui",
      "target": "components/ui/squeeze.tsx"
    },
    {
      "path": "registry/base/ui/squeeze.css",
      "content": ".squeeze {\n  --squeeze-duration: 1500ms;\n  --squeeze-ease-out: cubic-bezier(0.22, 1, 0.36, 1);\n  --squeeze-rebound-ease: cubic-bezier(0.34, 1.56, 0.64, 1);\n  --squeeze-settle-ease: cubic-bezier(0.37, 0, 0.63, 1);\n  --squeeze-shake-x: 5px;\n  --squeeze-shake-y: 3px;\n}\n\n.squeeze-target,\n.squeeze-icon {\n  backface-visibility: hidden;\n  transform-origin: center;\n  animation: squeeze var(--squeeze-duration) both;\n}\n\n.squeeze-svg .squeeze-target,\n.squeeze-icon {\n  transform-box: fill-box;\n}\n\n@keyframes squeeze {\n  0% {\n    transform: translate(0, 0) scale(1);\n    animation-timing-function: var(--squeeze-ease-out);\n  }\n  6% {\n    transform: translate(\n        calc(var(--squeeze-shake-x) * -0.06),\n        calc(var(--squeeze-shake-y) * 0.1)\n      )\n      scale(1.08, 0.92);\n    animation-timing-function: var(--squeeze-ease-out);\n  }\n  13% {\n    transform: translate(\n        calc(var(--squeeze-shake-x) * -0.16),\n        calc(var(--squeeze-shake-y) * 0.24)\n      )\n      scale(1.2, 0.8);\n    animation-timing-function: var(--squeeze-ease-out);\n  }\n  23% {\n    transform: translate(\n        calc(var(--squeeze-shake-x) * -0.32),\n        calc(var(--squeeze-shake-y) * 0.4)\n      )\n      scale(1.34, 0.68);\n    animation-timing-function: var(--squeeze-ease-out);\n  }\n  28% {\n    transform: translate(\n        calc(var(--squeeze-shake-x) * -0.36),\n        calc(var(--squeeze-shake-y) * 0.44)\n      )\n      scale(1.38, 0.64);\n    animation-timing-function: var(--squeeze-rebound-ease);\n  }\n  35% {\n    transform: translate(\n        calc(var(--squeeze-shake-x) * 1.25),\n        calc(var(--squeeze-shake-y) * -1.25)\n      )\n      scale(0.72, 1.32);\n    animation-timing-function: var(--squeeze-settle-ease);\n  }\n  41% {\n    transform: translate(\n        calc(var(--squeeze-shake-x) * -0.72),\n        calc(var(--squeeze-shake-y) * 0.72)\n      )\n      scale(1.16, 0.81);\n    animation-timing-function: var(--squeeze-settle-ease);\n  }\n  47% {\n    transform: translate(\n        calc(var(--squeeze-shake-x) * 0.42),\n        calc(var(--squeeze-shake-y) * -0.42)\n      )\n      scale(0.91, 1.11);\n    animation-timing-function: var(--squeeze-settle-ease);\n  }\n  54% {\n    transform: translate(\n        calc(var(--squeeze-shake-x) * -0.24),\n        calc(var(--squeeze-shake-y) * 0.24)\n      )\n      scale(1.055, 0.94);\n    animation-timing-function: var(--squeeze-settle-ease);\n  }\n  62% {\n    transform: translate(\n        calc(var(--squeeze-shake-x) * 0.14),\n        calc(var(--squeeze-shake-y) * -0.14)\n      )\n      scale(0.969, 1.036);\n    animation-timing-function: var(--squeeze-settle-ease);\n  }\n  72% {\n    transform: translate(\n        calc(var(--squeeze-shake-x) * -0.08),\n        calc(var(--squeeze-shake-y) * 0.08)\n      )\n      scale(1.018, 0.979);\n    animation-timing-function: var(--squeeze-settle-ease);\n  }\n  82% {\n    transform: translate(\n        calc(var(--squeeze-shake-x) * 0.047),\n        calc(var(--squeeze-shake-y) * -0.047)\n      )\n      scale(0.99, 1.012);\n    animation-timing-function: var(--squeeze-settle-ease);\n  }\n  90% {\n    transform: translate(\n        calc(var(--squeeze-shake-x) * -0.027),\n        calc(var(--squeeze-shake-y) * 0.027)\n      )\n      scale(1.006, 0.993);\n    animation-timing-function: var(--squeeze-settle-ease);\n  }\n  95% {\n    transform: translate(\n        calc(var(--squeeze-shake-x) * 0.016),\n        calc(var(--squeeze-shake-y) * -0.016)\n      )\n      scale(0.997, 1.004);\n    animation-timing-function: var(--squeeze-settle-ease);\n  }\n  98% {\n    transform: translate(\n        calc(var(--squeeze-shake-x) * -0.004),\n        calc(var(--squeeze-shake-y) * 0.004)\n      )\n      scale(1.001, 0.999);\n    animation-timing-function: var(--squeeze-settle-ease);\n  }\n  100% {\n    transform: translate(0, 0) scale(1);\n  }\n}\n\n@media (prefers-reduced-motion: reduce) {\n  .squeeze-target,\n  .squeeze-icon {\n    animation: none;\n    will-change: auto;\n  }\n}\n",
      "type": "registry:file",
      "target": "components/ui/squeeze.css"
    }
  ],
  "meta": {
    "tags": [
      "svg",
      "svg-animation",
      "squeeze",
      "squash-stretch",
      "rebound",
      "shake",
      "primitive"
    ],
    "effects": [
      "squeeze",
      "squash-stretch",
      "rebound",
      "shake-x",
      "shake-y",
      "settle"
    ]
  },
  "categories": [
    "effect"
  ],
  "type": "registry:ui"
}