{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "cutout-card",
  "type": "registry:block",
  "title": "Cutout card",
  "description": "Cutout card",
  "files": [
    {
      "path": "components/usages/cutoutcardusage.tsx",
      "content": "import { CutoutCardInsetLabel, CutoutCardImage, CutoutCardMedia, CutoutCard, cutoutCardSurfaceClassName, CutoutCorner, CutoutCardAction, CutoutCardContent, CutoutCardOverlay } from \"@/registry/open-source/cutout-card\";\n\nexport default function Usage() {\n    return (\n        <div className=\"h-screen w-full flex items-center justify-center relative overflow-hidden bg-background\">\n            <CutoutCard className={cutoutCardSurfaceClassName}>\n                <CutoutCardMedia className=\"h-72\">\n                    <CutoutCardImage alt=\"Preview\" src=\"/itjustworks.jpg\" />\n                    <CutoutCardOverlay />\n                    <CutoutCardInsetLabel className=\"bottom-0 left-0 rounded-tr-[20px] bg-stone-50 px-5 py-3\">\n                        <span className=\"text-[11px] font-semibold uppercase tracking-widest text-stone-500\">\n                            Featured\n                        </span>\n                        <CutoutCorner className=\"absolute -right-[31px] -bottom-px rotate-90 text-stone-50\" />\n                        <CutoutCorner className=\"absolute -top-[31px] -left-px rotate-90 text-stone-50\" />\n                    </CutoutCardInsetLabel>\n                </CutoutCardMedia>\n                <CutoutCardContent>{/* title, body */}</CutoutCardContent>\n                <CutoutCardAction className=\"right-6 bottom-6\">{/* e.g. button */}</CutoutCardAction>\n            </CutoutCard>\t\t</div>\n    );\n}",
      "type": "registry:block",
      "target": "~/example.tsx"
    },
    {
      "path": "components/usages/cutoutcardusage.tsx",
      "content": "import { CutoutCardInsetLabel, CutoutCardImage, CutoutCardMedia, CutoutCard, cutoutCardSurfaceClassName, CutoutCorner, CutoutCardAction, CutoutCardContent, CutoutCardOverlay } from \"@/registry/open-source/cutout-card\";\n\nexport default function Usage() {\n    return (\n        <div className=\"h-screen w-full flex items-center justify-center relative overflow-hidden bg-background\">\n            <CutoutCard className={cutoutCardSurfaceClassName}>\n                <CutoutCardMedia className=\"h-72\">\n                    <CutoutCardImage alt=\"Preview\" src=\"/itjustworks.jpg\" />\n                    <CutoutCardOverlay />\n                    <CutoutCardInsetLabel className=\"bottom-0 left-0 rounded-tr-[20px] bg-stone-50 px-5 py-3\">\n                        <span className=\"text-[11px] font-semibold uppercase tracking-widest text-stone-500\">\n                            Featured\n                        </span>\n                        <CutoutCorner className=\"absolute -right-[31px] -bottom-px rotate-90 text-stone-50\" />\n                        <CutoutCorner className=\"absolute -top-[31px] -left-px rotate-90 text-stone-50\" />\n                    </CutoutCardInsetLabel>\n                </CutoutCardMedia>\n                <CutoutCardContent>{/* title, body */}</CutoutCardContent>\n                <CutoutCardAction className=\"right-6 bottom-6\">{/* e.g. button */}</CutoutCardAction>\n            </CutoutCard>\t\t</div>\n    );\n}",
      "type": "registry:ui"
    },
    {
      "path": "registry/open-source/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 \"@/registry/utilities/cn\"\n\n// Credit:\n// https://www.cult-ui.com/docs/components/cutout-card\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-border/80 dark:border-border/60\",\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 relative cursor-pointer overflow-hidden rounded-[28px] bg-card text-card-foreground\",\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                \"pointer-events-none absolute inset-0 bg-linear-to-t from-background/35 via-transparent to-transparent dark:from-background/50\",\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"
    },
    {
      "path": "registry/utilities/cn.ts",
      "content": "import { ClassValue, clsx } from \"clsx\";\r\nimport { twMerge } from \"tailwind-merge\";\r\n\r\nexport function cn(...inputs: ClassValue[]) {\r\n\treturn twMerge(clsx(inputs));\r\n}\r\n",
      "type": "registry:ui"
    }
  ]
}