{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "card",
  "title": "Card",
  "description": "Bordered surface with header, content and footer slots that reads as one region before anyone reads the text.",
  "dependencies": [
    "motion"
  ],
  "registryDependencies": [
    "utils",
    "https://miseui.junsu4621.workers.dev/r/beam-border.json",
    "https://miseui.junsu4621.workers.dev/r/materialize.json"
  ],
  "files": [
    {
      "path": "registry/miseui/ui/card.tsx",
      "content": "import type * as React from \"react\"\nimport { CARD_EFFECT_ROOTS, type CardEffect } from \"@/registry/miseui/ui/card-effects\"\nimport { cn } from \"@/registry/miseui/lib/utils\"\n\n/** One horizontal inset for every slot, so edges line up and the region reads as a unit. */\nconst INSET_CLASS = \"px-6\"\n\nconst CARD_CLASS = \"flex flex-col gap-6 rounded-xl border bg-card py-6 text-card-foreground shadow-xs\"\n\ntype CardProps = React.ComponentProps<\"div\"> & {\n  /** A living treatment on the surface. Undefined is the plain card. */\n  effect?: CardEffect\n}\n\nfunction Card({ className, effect, ...props }: CardProps) {\n  if (!effect) return <div data-slot=\"card\" className={cn(CARD_CLASS, className)} {...props} />\n  const Root = CARD_EFFECT_ROOTS[effect]\n  return <Root data-slot=\"card\" data-effect={effect} className={cn(CARD_CLASS, className)} {...props} />\n}\n\nfunction CardHeader({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"card-header\"\n      className={cn(\n        \"grid auto-rows-min items-start gap-1.5\",\n        \"has-data-[slot=card-action]:grid-cols-[1fr_auto]\",\n        INSET_CLASS,\n        className,\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction CardTitle({ className, ...props }: React.ComponentProps<\"div\">) {\n  return <div data-slot=\"card-title\" className={cn(\"leading-none font-semibold\", className)} {...props} />\n}\n\nfunction CardDescription({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"card-description\"\n      className={cn(\"text-sm text-muted-foreground\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction CardAction({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"card-action\"\n      className={cn(\"col-start-2 row-span-2 row-start-1 self-start justify-self-end\", className)}\n      {...props}\n    />\n  )\n}\n\nfunction CardContent({ className, ...props }: React.ComponentProps<\"div\">) {\n  return <div data-slot=\"card-content\" className={cn(INSET_CLASS, className)} {...props} />\n}\n\nfunction CardFooter({ className, ...props }: React.ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"card-footer\"\n      className={cn(\"flex items-center gap-2\", INSET_CLASS, className)}\n      {...props}\n    />\n  )\n}\n\nexport { Card, CardHeader, CardTitle, CardDescription, CardAction, CardContent, CardFooter, type CardProps, type CardEffect }\n",
      "type": "registry:ui"
    },
    {
      "path": "registry/miseui/ui/card-effects.tsx",
      "content": "import type * as React from \"react\"\nimport { useEffect, useState } from \"react\"\nimport { motion, useReducedMotion, useSpring, type HTMLMotionProps } from \"motion/react\"\nimport { BeamBorder, type BeamBorderProps } from \"@/registry/miseui/ui/beam-border\"\nimport { Materialize, type MaterializeProps } from \"@/registry/miseui/ui/materialize\"\nimport { cn } from \"@/registry/miseui/lib/utils\"\n\ntype CardEffect = \"beam\" | \"spotlight\" | \"tilt\" | \"materialize\"\ntype CardRootProps = React.ComponentProps<\"div\">\n\nconst BEAM_LENGTH_PX = 220\nconst BEAM_WIDTH_PX = 2\nconst TILT_MAX_DEGREES = 3\nconst TILT_PERSPECTIVE_PX = 900\nconst TILT_SPRING = { bounce: 0, visualDuration: 0.35 }\nconst FINE_POINTER_QUERY = \"(hover: hover) and (pointer: fine)\"\n/** Keyboard users and reduced motion get the highlight resting on the top edge, where a lit edge reads as \"active\". */\nconst SPOT_AT = \"at var(--spot-x, 50%) var(--spot-y, 0%)\"\nconst SPOT_FILL = `radial-gradient(420px circle ${SPOT_AT}, color-mix(in oklab, var(--mise) 26%, transparent), transparent 72%)`\nconst SPOT_RIM = `radial-gradient(280px circle ${SPOT_AT}, var(--mise), transparent 72%)`\nconst GLARE_FILL = `radial-gradient(360px circle ${SPOT_AT}, color-mix(in oklab, var(--foreground) 9%, transparent), transparent 72%)`\n/** Content box cut out of the border box leaves only the padding ring painted. */\nconst RING_MASK = \"linear-gradient(#000 0 0) content-box exclude, linear-gradient(#000 0 0)\"\nconst HOVER_LAYER_CLASS = cn(\n  \"pointer-events-none absolute -z-10 rounded-[inherit] opacity-0\",\n  \"transition-opacity duration-(--duration-base) ease-(--ease-mise) motion-reduce:transition-none\",\n  \"group-hover/card:opacity-100 group-focus-within/card:opacity-100\",\n)\n\nfunction canFollowPointer(reduceMotion: boolean | null) {\n  return !reduceMotion && window.matchMedia(FINE_POINTER_QUERY).matches\n}\n\n/** Written straight to the element: a pointer stream must not re-render the card and everything inside it. */\nfunction writeSpot(event: React.PointerEvent<HTMLDivElement>, rect: DOMRect) {\n  const { style } = event.currentTarget\n  style.setProperty(\"--spot-x\", `${event.clientX - rect.left}px`)\n  style.setProperty(\"--spot-y\", `${event.clientY - rect.top}px`)\n}\n\n/** The card itself is the beam's root, so its width, grid slot and ref behave as on a plain card. */\nfunction BeamCard({ className, ...props }: CardRootProps) {\n  return (\n    <BeamBorder\n      size={BEAM_LENGTH_PX}\n      borderWidth={BEAM_WIDTH_PX}\n      {...(props as BeamBorderProps)}\n      className={cn(className, \"border-transparent\")}\n    />\n  )\n}\n\nfunction SpotlightCard({ className, onPointerMove, children, ...props }: CardRootProps) {\n  const reduceMotion = useReducedMotion()\n  const follow = (event: React.PointerEvent<HTMLDivElement>) => {\n    onPointerMove?.(event)\n    if (canFollowPointer(reduceMotion)) writeSpot(event, event.currentTarget.getBoundingClientRect())\n  }\n  return (\n    <div className={cn(\"group/card relative isolate\", className)} onPointerMove={follow} {...props}>\n      <span\n        aria-hidden=\"true\"\n        data-testid=\"card-spot\"\n        className={cn(HOVER_LAYER_CLASS, \"inset-0\")}\n        style={{ background: SPOT_FILL }}\n      />\n      <span\n        aria-hidden=\"true\"\n        data-testid=\"card-spot-rim\"\n        className={cn(HOVER_LAYER_CLASS, \"-inset-px\")}\n        style={{ background: SPOT_RIM, padding: 1, mask: RING_MASK, maskComposite: \"exclude\" }}\n      />\n      {children}\n    </div>\n  )\n}\n\nfunction TiltCard({ className, style, onPointerMove, onPointerLeave, children, ...props }: CardRootProps) {\n  const reduceMotion = useReducedMotion()\n  const rotateX = useSpring(0, TILT_SPRING)\n  const rotateY = useSpring(0, TILT_SPRING)\n\n  const follow = (event: React.PointerEvent<HTMLDivElement>) => {\n    onPointerMove?.(event)\n    if (!canFollowPointer(reduceMotion)) return\n    const rect = event.currentTarget.getBoundingClientRect()\n    if (rect.width <= 0 || rect.height <= 0) return\n    const shareX = (event.clientX - rect.left) / rect.width - 0.5\n    const shareY = (event.clientY - rect.top) / rect.height - 0.5\n    // The edge under the pointer comes forward. Tilting it away would slide the card out from under\n    // a pointer near that edge, firing leave, then enter, in a loop.\n    rotateY.set(-shareX * 2 * TILT_MAX_DEGREES)\n    rotateX.set(shareY * 2 * TILT_MAX_DEGREES)\n    writeSpot(event, rect)\n  }\n  const settle = (event: React.PointerEvent<HTMLDivElement>) => {\n    onPointerLeave?.(event)\n    rotateX.set(0)\n    rotateY.set(0)\n  }\n\n  return (\n    <motion.div\n      {...(props as HTMLMotionProps<\"div\">)}\n      className={cn(\n        \"group/card relative isolate transition-shadow duration-(--duration-base) ease-(--ease-mise) hover:shadow-lg\",\n        className,\n      )}\n      style={{ ...(style as HTMLMotionProps<\"div\">[\"style\"]), rotateX, rotateY, transformPerspective: TILT_PERSPECTIVE_PX }}\n      onPointerMove={follow}\n      onPointerLeave={settle}\n    >\n      {/* Glass catches a little light as it turns; without it, 3 degrees reads as a layout wobble. */}\n      <span\n        aria-hidden=\"true\"\n        data-testid=\"card-glare\"\n        className={cn(HOVER_LAYER_CLASS, \"inset-0\")}\n        style={{ background: GLARE_FILL }}\n      />\n      {children}\n    </motion.div>\n  )\n}\n\n/**\n * Materialize only animates a false-to-true flip of `show` (its presence is initial={false}), so the card\n * mounts hidden and flips on the next frame. The trap: server-rendered HTML holds no card content.\n */\nfunction MaterializeCard({ children, ...props }: CardRootProps) {\n  const [show, setShow] = useState(false)\n  useEffect(() => {\n    const frame = requestAnimationFrame(() => setShow(true))\n    return () => cancelAnimationFrame(frame)\n  }, [])\n  return (\n    <Materialize show={show} {...(props as Omit<MaterializeProps, \"show\" | \"children\">)}>\n      {children}\n    </Materialize>\n  )\n}\n\nconst CARD_EFFECT_ROOTS: Record<CardEffect, (props: CardRootProps) => React.JSX.Element> = {\n  beam: BeamCard,\n  spotlight: SpotlightCard,\n  tilt: TiltCard,\n  materialize: MaterializeCard,\n}\n\nexport { CARD_EFFECT_ROOTS, type CardEffect }\n",
      "type": "registry:ui"
    }
  ],
  "categories": [
    "display"
  ],
  "type": "registry:ui"
}