{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "cutout-card",
  "type": "registry:ui",
  "description": "Image card with cutout corners, hover motion, inset labels, pins, and reveal actions",
  "dependencies": [
    "motion",
    "@radix-ui/react-use-controllable-state"
  ],
  "files": [
    {
      "path": "registry/default/ui/cutout-card.tsx",
      "content": "\"use client\"\n\nimport {\n  createContext,\n  useCallback,\n  useContext,\n  useMemo,\n  type ComponentProps,\n  type HTMLAttributes,\n  type MouseEventHandler,\n} from \"react\"\nimport Image from \"next/image\"\nimport { useControllableState } from \"@radix-ui/react-use-controllable-state\"\nimport { motion, useReducedMotion } from \"motion/react\"\n\nimport { cn } from \"@/lib/utils\"\n\n// ============================================================================\n// Tokens — optional chrome for demos / quick styling\n// ============================================================================\n\n/** Border + shadow stack using theme tokens so elevation reads in light and dark. */\nexport const cutoutCardSurfaceShadowClassName = cn(\n  \"border-border/80 dark:border-border/60 border\",\n  \"shadow-[0px_1px_2px_-1px_color-mix(in_oklab,var(--foreground)_8%,transparent),0px_4px_8px_-2px_color-mix(in_oklab,var(--foreground)_6%,transparent),0px_8px_16px_-4px_color-mix(in_oklab,var(--foreground)_5%,transparent)]\",\n  \"transition-[box-shadow,border-color] duration-500 ease-[cubic-bezier(0.23,1,0.32,1)]\",\n  \"hover:border-border hover:shadow-[0px_2px_4px_-1px_color-mix(in_oklab,var(--foreground)_10%,transparent),0px_8px_16px_-4px_color-mix(in_oklab,var(--foreground)_8%,transparent),0px_16px_32px_-8px_color-mix(in_oklab,var(--foreground)_6%,transparent)]\"\n)\n\nexport const cutoutCardSurfaceClassName = cn(\n  \"group/cutout bg-card text-card-foreground relative cursor-pointer overflow-hidden rounded-[28px]\",\n  cutoutCardSurfaceShadowClassName\n)\n\n/** Staggered text/footer entrance inside `CutoutCardContent` — use with `motion.div` children. */\nexport function useCutoutContentStaggerVariants() {\n  const reduceMotion = useReducedMotion()\n\n  return useMemo(() => {\n    if (reduceMotion) {\n      return {\n        container: {\n          hidden: {},\n          show: {\n            transition: { staggerChildren: 0.03, delayChildren: 0 },\n          },\n        },\n        item: {\n          hidden: { opacity: 0 },\n          show: {\n            opacity: 1,\n            transition: { duration: 0.2, ease: [0.23, 1, 0.32, 1] },\n          },\n        },\n      } as const\n    }\n\n    return {\n      container: {\n        hidden: {},\n        show: {\n          transition: { staggerChildren: 0.055, delayChildren: 0.06 },\n        },\n      },\n      item: {\n        hidden: { opacity: 0, y: 12, filter: \"blur(5px)\" },\n        show: {\n          opacity: 1,\n          y: 0,\n          filter: \"blur(0px)\",\n          transition: { type: \"spring\", duration: 0.48, bounce: 0.14 },\n        },\n      },\n    } as const\n  }, [reduceMotion])\n}\n\nconst CORNER_PATH = \"M0 200C155.996 199.961 200.029 156.308 200 0V200H0Z\"\n\n// ============================================================================\n// Context\n// ============================================================================\n\nexport interface CutoutCardContextValue {\n  hovered: boolean\n  setHovered: (next: boolean) => void\n}\n\nconst CutoutCardContext = createContext<CutoutCardContextValue | null>(null)\n\nexport function useCutoutCard() {\n  const ctx = useContext(CutoutCardContext)\n  if (!ctx) {\n    throw new Error(\"useCutoutCard must be used within <CutoutCard>\")\n  }\n  return ctx\n}\n\nexport function useOptionalCutoutCard() {\n  return useContext(CutoutCardContext)\n}\n\n// ============================================================================\n// Root\n// ============================================================================\n\nexport type CutoutCardProps = Omit<\n  ComponentProps<typeof motion.div>,\n  \"defaultValue\"\n> & {\n  /** When set, hover state is controlled by the parent. */\n  hovered?: boolean\n  /** Initial hover state when uncontrolled. */\n  defaultHovered?: boolean\n  /** Called when pointer hover changes (after internal state updates). */\n  onHoveredChange?: (hovered: boolean) => void\n  /**\n   * When true (default), pointer enter/leave on the root update hover state.\n   * Set false if you only drive hover programmatically or via CSS.\n   */\n  trackPointerHover?: boolean\n}\n\nexport function CutoutCard({\n  className,\n  hovered: hoveredProp,\n  defaultHovered = false,\n  onHoveredChange,\n  trackPointerHover = true,\n  onMouseEnter,\n  onMouseLeave,\n  children,\n  ...props\n}: CutoutCardProps) {\n  const reduceMotion = useReducedMotion()\n  const [hovered, setHovered] = useControllableState({\n    prop: hoveredProp,\n    defaultProp: defaultHovered,\n    onChange: onHoveredChange,\n  })\n\n  const setHoveredStable = useCallback(\n    (next: boolean) => {\n      setHovered(next)\n    },\n    [setHovered]\n  )\n\n  const ctx = useMemo<CutoutCardContextValue>(\n    () => ({\n      hovered: hovered ?? false,\n      setHovered: setHoveredStable,\n    }),\n    [hovered, setHoveredStable]\n  )\n\n  const handleMouseEnter: MouseEventHandler<HTMLDivElement> = (e) => {\n    onMouseEnter?.(e)\n    if (e.defaultPrevented || !trackPointerHover) {\n      return\n    }\n    setHoveredStable(true)\n  }\n\n  const handleMouseLeave: MouseEventHandler<HTMLDivElement> = (e) => {\n    onMouseLeave?.(e)\n    if (e.defaultPrevented || !trackPointerHover) {\n      return\n    }\n    setHoveredStable(false)\n  }\n\n  return (\n    <CutoutCardContext.Provider value={ctx}>\n      <motion.div\n        animate={{ opacity: 1 }}\n        className={cn(className)}\n        data-slot=\"cutout-card\"\n        data-state={ctx.hovered ? \"hovered\" : \"idle\"}\n        initial={{ opacity: 0 }}\n        onMouseEnter={handleMouseEnter}\n        onMouseLeave={handleMouseLeave}\n        transition={\n          reduceMotion\n            ? { duration: 0.22, ease: [0.23, 1, 0.32, 1] }\n            : { duration: 0.36, ease: [0.23, 1, 0.32, 1] }\n        }\n        {...props}\n      >\n        {children}\n      </motion.div>\n    </CutoutCardContext.Provider>\n  )\n}\n\n// ============================================================================\n// Layout primitives\n// ============================================================================\n\nexport type CutoutCardMediaProps = HTMLAttributes<HTMLDivElement>\n\nexport function CutoutCardMedia({ className, ...props }: CutoutCardMediaProps) {\n  return (\n    <div\n      className={cn(\"relative overflow-hidden\", className)}\n      data-slot=\"cutout-card-media\"\n      {...props}\n    />\n  )\n}\n\nexport type CutoutCardImageProps = ComponentProps<typeof Image>\n\n/** Uses `fill` by default; parent `CutoutCardMedia` should be `relative` with a defined block size. */\nexport function CutoutCardImage({\n  className,\n  alt = \"\",\n  fill = true,\n  sizes = \"(max-width: 768px) 100vw, 28rem\",\n  ...props\n}: CutoutCardImageProps) {\n  return (\n    <Image\n      alt={alt}\n      className={cn(\n        \"object-cover transition-transform duration-700 ease-[cubic-bezier(0.23,1,0.32,1)] group-hover/cutout:scale-105\",\n        fill && \"h-full w-full\",\n        className\n      )}\n      data-slot=\"cutout-card-image\"\n      {...props}\n      fill={fill}\n      sizes={fill ? sizes : undefined}\n    />\n  )\n}\n\nexport type CutoutCardOverlayProps = HTMLAttributes<HTMLDivElement>\n\nexport function CutoutCardOverlay({\n  className,\n  ...props\n}: CutoutCardOverlayProps) {\n  return (\n    <div\n      className={cn(\n        \"from-background/35 dark:from-background/50 pointer-events-none absolute inset-0 bg-linear-to-t via-transparent to-transparent\",\n        className\n      )}\n      data-slot=\"cutout-card-overlay\"\n      {...props}\n    />\n  )\n}\n\nexport type CutoutCardContentProps = HTMLAttributes<HTMLDivElement>\n\nexport function CutoutCardContent({\n  className,\n  ...props\n}: CutoutCardContentProps) {\n  return (\n    <div\n      className={cn(\"p-6\", className)}\n      data-slot=\"cutout-card-content\"\n      {...props}\n    />\n  )\n}\n\nexport type CutoutCardFooterProps = HTMLAttributes<HTMLDivElement>\n\nexport function CutoutCardFooter({\n  className,\n  ...props\n}: CutoutCardFooterProps) {\n  return (\n    <div\n      className={cn(\"flex items-center justify-between\", className)}\n      data-slot=\"cutout-card-footer\"\n      {...props}\n    />\n  )\n}\n\n// ============================================================================\n// Cutout geometry\n// ============================================================================\n\nexport type CutoutCornerProps = ComponentProps<\"svg\"> & {\n  /** Pixel width/height of the SVG viewBox (square). */\n  size?: number\n}\n\nexport function CutoutCorner({\n  className,\n  size = 32,\n  viewBox = \"0 0 200 200\",\n  ...props\n}: CutoutCornerProps) {\n  return (\n    <>\n      {/* biome-ignore lint/a11y/noSvgWithoutTitle: decorative corner mask; hidden from AT via aria-hidden */}\n      <svg\n        aria-hidden\n        className={cn(className)}\n        data-slot=\"cutout-corner\"\n        height={size}\n        viewBox={viewBox}\n        width={size}\n        xmlns=\"http://www.w3.org/2000/svg\"\n        {...props}\n      >\n        <path d={CORNER_PATH} fill=\"currentColor\" />\n      </svg>\n    </>\n  )\n}\n\nexport type CutoutCardInsetLabelProps = HTMLAttributes<HTMLDivElement>\n\n/** Absolutely positioned strip (e.g. bottom-left “Featured”); add corners as siblings inside. Static (no entrance motion) to avoid compositing seams next to the media edge. */\nexport function CutoutCardInsetLabel({\n  className,\n  ...props\n}: CutoutCardInsetLabelProps) {\n  return (\n    <div\n      className={cn(\"absolute\", className)}\n      data-slot=\"cutout-card-inset-label\"\n      {...props}\n    />\n  )\n}\n\nexport type CutoutCardPinProps = HTMLAttributes<HTMLDivElement>\n\n/** Corner badge shell (e.g. top-right “New”); add corners as siblings inside. Static (no entrance motion). */\nexport function CutoutCardPin({ className, ...props }: CutoutCardPinProps) {\n  return (\n    <div\n      className={cn(\"absolute\", className)}\n      data-slot=\"cutout-card-pin\"\n      {...props}\n    />\n  )\n}\n\n// ============================================================================\n// Context-sensitive action region\n// ============================================================================\n\nexport type CutoutCardActionProps = ComponentProps<typeof motion.div> & {\n  /**\n   * When true (default), visibility follows card hover from context.\n   * Set false to always show the region.\n   */\n  revealOnHover?: boolean\n}\n\nexport function CutoutCardAction({\n  className,\n  revealOnHover = true,\n  ...props\n}: CutoutCardActionProps) {\n  const { hovered } = useCutoutCard()\n  const reduceMotion = useReducedMotion()\n  const visible = !revealOnHover || hovered\n\n  return (\n    <motion.div\n      animate={\n        visible\n          ? { opacity: 1, transform: \"translateY(0px)\" }\n          : { opacity: 0, transform: \"translateY(8px)\" }\n      }\n      className={cn(\n        \"absolute\",\n        revealOnHover && !visible && \"pointer-events-none\",\n        className\n      )}\n      data-reveal={revealOnHover ? \"hover\" : \"always\"}\n      data-slot=\"cutout-card-action\"\n      transition={\n        reduceMotion\n          ? { duration: 0.15, ease: [0.23, 1, 0.32, 1] }\n          : { duration: 0.24, ease: [0.23, 1, 0.32, 1] }\n      }\n      {...props}\n    />\n  )\n}\n",
      "type": "registry:ui"
    }
  ]
}