{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"card","type":"registry:component","title":"Card","description":"Composable card with header, title, description, action, content and footer parts. Outlined or muted surfaces, standard or compact spacing, and an opt-in selectable state with press feedback that composes with Checkbox, Radio and Button.","author":"Saurabh <saurabh10102@gmail.com>","dependencies":["clsx","motion","tailwind-merge"],"registryDependencies":[],"files":[{"path":"components/motion/card.tsx","type":"registry:component","target":"@components/motion/card.tsx","content":"\"use client\";\n// beui.dev/components/motion/card\n\nimport { motion, useReducedMotion } from \"motion/react\";\nimport { type ComponentPropsWithRef, createContext, useContext } from \"react\";\nimport { SPRING_PRESS } from \"@/lib/ease\";\nimport { cn } from \"@/lib/utils\";\n\nexport type CardVariant = \"outlined\" | \"muted\";\nexport type CardSize = \"default\" | \"compact\";\n\nconst CardSizeContext = createContext<CardSize>(\"default\");\n\nconst VARIANT_CLASS: Record<CardVariant, string> = {\n  outlined: \"border-border bg-background\",\n  muted: \"border-transparent bg-muted\",\n};\n\n// Each part pads itself on the inline axis, so a part can be left out without\n// the others losing their edges. The root owns the block padding and the gap.\nconst ROOT_SIZE_CLASS: Record<CardSize, string> = {\n  default: \"gap-6 py-6\",\n  compact: \"gap-4 py-4\",\n};\nconst INSET_CLASS: Record<CardSize, string> = {\n  default: \"px-6\",\n  compact: \"px-4\",\n};\n\n/**\n * Spread onto the one control that owns a selectable card (a Checkbox or\n * RadioGroupItem root) to stretch its hit area over the whole card. Any other\n * interactive element inside that card needs `relative z-10` to stay on top.\n */\nexport const CARD_TARGET_CLASS = \"after:absolute after:inset-0\";\n\nexport interface CardProps extends ComponentPropsWithRef<\"div\"> {\n  /** Default \"outlined\". */\n  variant?: CardVariant;\n  /** Spacing for every part. Default \"default\". */\n  size?: CardSize;\n  /** Hover and press feedback, for a card that is one target as a whole. */\n  interactive?: boolean;\n  /** Marks a selectable card as chosen. Visual only; the control inside carries the state. */\n  selected?: boolean;\n}\n\nexport function Card({\n  variant = \"outlined\",\n  size = \"default\",\n  interactive = false,\n  selected = false,\n  className,\n  children,\n  ...props\n}: CardProps) {\n  const reduce = useReducedMotion();\n  return (\n    <CardSizeContext.Provider value={size}>\n      <motion.div\n        {...(props as React.ComponentProps<typeof motion.div>)}\n        data-slot=\"card\"\n        data-selected={selected || undefined}\n        whileTap={interactive && !reduce ? { scale: 0.985 } : undefined}\n        transition={SPRING_PRESS}\n        className={cn(\n          \"relative flex flex-col rounded-2xl border text-card-foreground\",\n          \"transition-[border-color,box-shadow,background-color] duration-200\",\n          VARIANT_CLASS[variant],\n          ROOT_SIZE_CLASS[size],\n          interactive && \"cursor-pointer hover:border-border-strong has-focus-visible:outline-2 has-focus-visible:outline-offset-2 has-focus-visible:outline-foreground\",\n          selected && \"border-primary shadow-[0_0_0_1px_var(--primary)] hover:border-primary\",\n          className,\n        )}\n      >\n        {children}\n      </motion.div>\n    </CardSizeContext.Provider>\n  );\n}\n\nexport type CardHeaderProps = ComponentPropsWithRef<\"div\">;\n\n/** Title and description, with an optional CardAction pinned to the top right. */\nexport function CardHeader({ className, ...props }: CardHeaderProps) {\n  const size = useContext(CardSizeContext);\n  return (\n    <div\n      {...props}\n      data-slot=\"card-header\"\n      className={cn(\n        \"grid auto-rows-min grid-rows-[auto_auto] items-start gap-1.5\",\n        \"has-data-[slot=card-action]:grid-cols-[1fr_auto]\",\n        INSET_CLASS[size],\n        className,\n      )}\n    />\n  );\n}\n\nexport interface CardTitleProps extends ComponentPropsWithRef<\"div\"> {\n  /** Heading level, when the card starts a section. Default \"div\". */\n  as?: \"div\" | \"h2\" | \"h3\" | \"h4\";\n}\n\nexport function CardTitle({ as: Tag = \"div\", className, ...props }: CardTitleProps) {\n  return (\n    <Tag\n      {...props}\n      data-slot=\"card-title\"\n      className={cn(\"text-[15px] leading-snug font-semibold text-foreground\", className)}\n    />\n  );\n}\n\nexport type CardDescriptionProps = ComponentPropsWithRef<\"p\">;\n\nexport function CardDescription({ className, ...props }: CardDescriptionProps) {\n  return (\n    <p\n      {...props}\n      data-slot=\"card-description\"\n      className={cn(\"text-sm leading-relaxed text-muted-foreground\", className)}\n    />\n  );\n}\n\nexport type CardActionProps = ComponentPropsWithRef<\"div\">;\n\n/** Sits beside the title and description inside CardHeader. */\nexport function CardAction({ className, ...props }: CardActionProps) {\n  return (\n    // Flex, not block: an inline-flex control (Checkbox, Radio) would\n    // otherwise sit on a text baseline that moves when its mark appears.\n    <div\n      {...props}\n      data-slot=\"card-action\"\n      className={cn(\n        \"col-start-2 row-span-2 row-start-1 flex items-center self-start justify-self-end\",\n        className,\n      )}\n    />\n  );\n}\n\nexport type CardContentProps = ComponentPropsWithRef<\"div\">;\n\nexport function CardContent({ className, ...props }: CardContentProps) {\n  const size = useContext(CardSizeContext);\n  return <div {...props} data-slot=\"card-content\" className={cn(\"text-sm\", INSET_CLASS[size], className)} />;\n}\n\nexport type CardFooterProps = ComponentPropsWithRef<\"div\">;\n\nexport function CardFooter({ className, ...props }: CardFooterProps) {\n  const size = useContext(CardSizeContext);\n  return (\n    <div\n      {...props}\n      data-slot=\"card-footer\"\n      className={cn(\"flex items-center gap-2\", INSET_CLASS[size], className)}\n    />\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"}]}