{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"aspect-ratio","type":"registry:component","title":"Aspect Ratio","description":"Responsive media with smooth aspect ratio transitions.","author":"Saurabh <saurabh10102@gmail.com>","dependencies":["clsx","motion","tailwind-merge"],"registryDependencies":[],"files":[{"path":"components/motion/aspect-ratio.tsx","type":"registry:component","target":"@components/motion/aspect-ratio.tsx","content":"\"use client\";\n// beui.dev/components/motion/aspect-ratio\n\nimport { type HTMLMotionProps, motion, useReducedMotion } from \"motion/react\";\nimport { createContext, type ReactNode, useContext, useMemo } from \"react\";\nimport { SPRING_LAYOUT } from \"@/lib/ease\";\nimport { cn } from \"@/lib/utils\";\n\ntype AspectRatioContextValue = { ratio: number; animated: boolean };\nconst AspectRatioContext = createContext<AspectRatioContextValue | null>(null);\n\nexport interface AspectRatioProps\n  extends Omit<HTMLMotionProps<\"div\">, \"children\" | \"layout\" | \"layoutDependency\"> {\n  children?: ReactNode;\n  /** Width divided by height, for example 16 / 9. Must be finite and positive. */\n  ratio?: number;\n  /** Morph layout changes. Reduced motion always skips movement. */\n  animated?: boolean;\n  /** Classes for the content layer, such as alignment or padding. */\n  contentClassName?: string;\n}\n\nexport function AspectRatio({\n  ratio = 1,\n  animated = true,\n  children,\n  className,\n  contentClassName,\n  style,\n  ...props\n}: AspectRatioProps) {\n  const reduce = useReducedMotion();\n  const enabled = animated && !reduce;\n  const context = useMemo(() => ({ ratio, animated: enabled }), [ratio, enabled]);\n\n  if (!Number.isFinite(ratio) || ratio <= 0) {\n    throw new RangeError(\"AspectRatio: ratio must be a finite, positive number.\");\n  }\n\n  return (\n    <AspectRatioContext.Provider value={context}>\n      <motion.div\n        {...props}\n        layout={enabled}\n        transition={{ layout: SPRING_LAYOUT }}\n        className={cn(\"relative w-full min-w-0 overflow-hidden\", className)}\n        style={{ ...style, aspectRatio: ratio }}\n      >\n        <div className={cn(\"absolute inset-0\", contentClassName)}>{children}</div>\n      </motion.div>\n    </AspectRatioContext.Provider>\n  );\n}\n\nexport interface AspectRatioImageProps\n  extends Omit<HTMLMotionProps<\"img\">, \"layout\" | \"layoutDependency\" | \"width\" | \"height\"> {\n  alt: string;\n  /** Intrinsic width in pixels, used to reserve the image's cover crop. */\n  width: number;\n  /** Intrinsic height in pixels. Keep these dimensions accurate for the source. */\n  height: number;\n}\n\nexport function AspectRatioImage({\n  width,\n  height,\n  className,\n  style,\n  draggable = false,\n  ...props\n}: AspectRatioImageProps) {\n  const context = useContext(AspectRatioContext);\n  if (!context) throw new Error(\"AspectRatioImage must be inside AspectRatio.\");\n  if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0) {\n    throw new RangeError(\"AspectRatioImage: width and height must be finite, positive numbers.\");\n  }\n  const fillWidth = context.ratio >= width / height;\n\n  return (\n    <div className=\"absolute inset-0 grid grid-cols-1 grid-rows-1 place-items-center\">\n      {/* The image box keeps its intrinsic ratio at both endpoints. Its layout\n          projection therefore scales uniformly while the frame reshapes. */}\n      {/* biome-ignore lint/performance/noImgElement: Copy-paste registry components work outside Next.js. */}\n      <motion.img\n        {...props}\n        width={width}\n        height={height}\n        draggable={draggable}\n        layout={context.animated}\n        transition={{ layout: SPRING_LAYOUT }}\n        className={cn(\"col-start-1 row-start-1 block max-w-none\", className)}\n        style={{\n          ...style,\n          width: fillWidth ? \"100%\" : \"auto\",\n          height: fillWidth ? \"auto\" : \"100%\",\n        }}\n      />\n    </div>\n  );\n}\n"},{"path":"lib/ease.ts","type":"registry:lib","target":"@lib/ease.ts","content":"// Shared motion tokens. Easing curves mirror the CSS custom properties in\n// globals.css; springs are the canonical physics used across components.\n// Strong custom variants — defaults like `ease-in`/`ease-out` feel weak.\n\nexport const EASE_OUT = [0.16, 1, 0.3, 1] as const;\nexport const EASE_IN_OUT = [0.77, 0, 0.175, 1] as const;\nexport const EASE_DRAWER = [0.32, 0.72, 0, 1] as const;\n\n/** CSS string form of EASE_OUT for inline style transitions. */\nexport const EASE_OUT_CSS = \"cubic-bezier(0.16, 1, 0.3, 1)\";\n\n/** Press feedback on buttons and other tappable surfaces. */\nexport const SPRING_PRESS = {\n  type: \"spring\",\n  stiffness: 500,\n  damping: 30,\n  mass: 0.6,\n} as const;\n\n/** Content swaps — label/icon slots trading places inside a control. */\nexport const SPRING_SWAP = {\n  type: \"spring\",\n  stiffness: 460,\n  damping: 30,\n  mass: 0.55,\n} as const;\n\n/** Overlay panel entrances — modals and sheets summoned by pointer. */\nexport const SPRING_PANEL = {\n  type: \"spring\",\n  stiffness: 420,\n  damping: 40,\n  mass: 0.5,\n} as const;\n\n/** Shared-layout glides — pills, indicators and panels morphing between positions. */\nexport const SPRING_LAYOUT = {\n  type: \"spring\",\n  stiffness: 360,\n  damping: 32,\n  mass: 0.6,\n} as const;\n\n/** Cursor-follow physics for decorative mouse tracking (magnetic, tilt, dock). */\nexport const SPRING_MOUSE = {\n  stiffness: 200,\n  damping: 15,\n  mass: 0.3,\n} as const;\n\n/** Dragged handles and fills (sliders) — critically damped `useSpring` config,\n * so the value follows the pointer butterily and never rebounds off an end. */\nexport const SPRING_GLIDE = {\n  stiffness: 700,\n  damping: 50,\n  mass: 0.5,\n} as const;\n"},{"path":"lib/utils.ts","type":"registry:lib","target":"@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"}]}