{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "projected-shadow",
  "title": "Projected Shadow",
  "description": "A CSS-powered primitive that layers an element with projected and contact shadows, supports an animation opt-out, and gathers on hover or active state.",
  "files": [
    {
      "path": "registry/base/ui/projected-shadow.tsx",
      "content": "import * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\nimport \"./projected-shadow.css\";\n\ntype ProjectedShadowTimingValue = number | string;\ntype ProjectedShadowLengthValue = number | string;\ntype CssVariableStyle = React.CSSProperties & Record<`--${string}`, string>;\n\nexport type ProjectedShadowOffset = {\n  /** Horizontal offset. Numeric values are converted to px. */\n  x?: ProjectedShadowLengthValue;\n  /** Vertical offset. Numeric values are converted to px. */\n  y?: ProjectedShadowLengthValue;\n};\n\nexport type ProjectedShadowProps = React.ComponentProps<\"span\"> & {\n  /** Keep the gathered state active without requiring hover. */\n  active?: boolean;\n  /** Enable the layered hover/focus animation. Set false for a neutral target. */\n  animated?: boolean;\n  /** Animation duration for release. Numeric values are converted to ms. */\n  duration?: ProjectedShadowTimingValue;\n  /** Animation duration for gather. Numeric values are converted to ms. */\n  activeDuration?: ProjectedShadowTimingValue;\n  /** CSS easing used by the transform and opacity transitions. */\n  ease?: string;\n  /** Blur radius for the far projected shadow. Numeric values are converted to px. */\n  projectedShadowBlur?: ProjectedShadowLengthValue;\n  /** Resting offset for the far projected shadow. */\n  projectedShadowOffset?: ProjectedShadowOffset;\n  /** Optional decorative content for the shadow layers. Defaults to children. */\n  shadow?: React.ReactNode;\n  /** Classes applied to the visible animated child wrapper. */\n  targetClassName?: string;\n  /** Classes applied to the far projected shadow layer. */\n  projectedShadowClassName?: string;\n  /** Classes applied to the close contact shadow layer. */\n  contactShadowClassName?: string;\n  /** Render the far projected shadow layer. Defaults to true. */\n  showProjectedShadow?: boolean;\n  /** Render the close contact shadow layer. Defaults to true. */\n  showContactShadow?: boolean;\n};\n\nexport function ProjectedShadow({\n  active,\n  activeDuration,\n  animated = true,\n  children,\n  className,\n  contactShadowClassName,\n  duration,\n  ease,\n  projectedShadowClassName,\n  projectedShadowBlur,\n  projectedShadowOffset,\n  shadow,\n  showContactShadow = true,\n  showProjectedShadow = true,\n  style,\n  targetClassName,\n  ...props\n}: ProjectedShadowProps) {\n  const shadowContent = shadow ?? children;\n\n  return (\n    <span\n      data-slot=\"projected-shadow\"\n      data-active={animated && active ? \"\" : undefined}\n      data-animated={animated ? \"\" : undefined}\n      className={cn(\n        \"projected-shadow relative inline-flex items-center justify-center\",\n        className,\n      )}\n      style={getProjectedShadowStyle(\n        {\n          activeDuration,\n          duration,\n          ease,\n          projectedShadowBlur,\n          projectedShadowOffset,\n        },\n        style,\n      )}\n      {...props}\n    >\n      {animated && showProjectedShadow ? (\n        <span\n          aria-hidden=\"true\"\n          data-slot=\"projected-shadow-projected\"\n          className={cn(\n            \"projected-shadow-layer projected-shadow-projected pointer-events-none absolute inset-0 z-0 inline-flex text-current\",\n            projectedShadowClassName,\n          )}\n        >\n          {shadowContent}\n        </span>\n      ) : null}\n\n      {animated && showContactShadow ? (\n        <span\n          aria-hidden=\"true\"\n          data-slot=\"projected-shadow-contact\"\n          className={cn(\n            \"projected-shadow-layer projected-shadow-contact pointer-events-none absolute inset-0 z-0 inline-flex text-current\",\n            contactShadowClassName,\n          )}\n        >\n          {shadowContent}\n        </span>\n      ) : null}\n\n      <span\n        data-slot=\"projected-shadow-target\"\n        className={cn(\n          \"projected-shadow-layer projected-shadow-target relative z-10 inline-flex text-current\",\n          targetClassName,\n        )}\n      >\n        {children}\n      </span>\n    </span>\n  );\n}\n\nfunction getProjectedShadowStyle(\n  {\n    activeDuration,\n    duration,\n    ease,\n    projectedShadowBlur,\n    projectedShadowOffset,\n  }: Pick<\n    ProjectedShadowProps,\n    | \"activeDuration\"\n    | \"duration\"\n    | \"ease\"\n    | \"projectedShadowBlur\"\n    | \"projectedShadowOffset\"\n  >,\n  style: React.CSSProperties | undefined,\n) {\n  if (\n    activeDuration === undefined &&\n    duration === undefined &&\n    ease === undefined &&\n    projectedShadowBlur === undefined &&\n    projectedShadowOffset?.x === undefined &&\n    projectedShadowOffset?.y === undefined\n  ) {\n    return style;\n  }\n\n  return {\n    ...style,\n    ...(activeDuration !== undefined\n      ? { \"--projected-shadow-active-duration\": toCssTime(activeDuration) }\n      : {}),\n    ...(duration !== undefined\n      ? { \"--projected-shadow-duration\": toCssTime(duration) }\n      : {}),\n    ...(ease !== undefined ? { \"--projected-shadow-ease\": ease } : {}),\n    ...(projectedShadowBlur !== undefined\n      ? { \"--projected-shadow-blur\": toCssLength(projectedShadowBlur) }\n      : {}),\n    ...(projectedShadowOffset?.x !== undefined\n      ? {\n          \"--projected-shadow-projected-x\": toCssLength(\n            projectedShadowOffset.x,\n          ),\n        }\n      : {}),\n    ...(projectedShadowOffset?.y !== undefined\n      ? {\n          \"--projected-shadow-projected-y\": toCssLength(\n            projectedShadowOffset.y,\n          ),\n        }\n      : {}),\n  } as CssVariableStyle;\n}\n\nfunction toCssTime(value: ProjectedShadowTimingValue) {\n  return typeof value === \"number\" ? `${value}ms` : value;\n}\n\nfunction toCssLength(value: ProjectedShadowLengthValue) {\n  return typeof value === \"number\" ? `${value}px` : value;\n}\n",
      "type": "registry:ui",
      "target": "components/ui/projected-shadow.tsx"
    },
    {
      "path": "registry/base/ui/projected-shadow.css",
      "content": ".projected-shadow {\n  --projected-shadow-duration: 280ms;\n  --projected-shadow-active-duration: 360ms;\n  --projected-shadow-ease: cubic-bezier(0.165, 0.84, 0.44, 1);\n  --projected-shadow-origin: center;\n  --projected-shadow-blur: 6px;\n  --projected-shadow-projected-opacity: 1;\n  --projected-shadow-projected-active-opacity: 0;\n  --projected-shadow-projected-x: -14%;\n  --projected-shadow-projected-y: -18%;\n  --projected-shadow-projected-scale: 1.08;\n  --projected-shadow-projected-rotate: -8deg;\n  --projected-shadow-projected-active-x: -4%;\n  --projected-shadow-projected-active-y: -5%;\n  --projected-shadow-projected-active-scale: 0.94;\n  --projected-shadow-projected-active-rotate: -3deg;\n  --projected-shadow-contact-opacity: 0;\n  --projected-shadow-contact-active-opacity: 0.72;\n  --projected-shadow-contact-x: -4%;\n  --projected-shadow-contact-y: -5%;\n  --projected-shadow-contact-scale: 0.92;\n  --projected-shadow-contact-rotate: -2deg;\n  --projected-shadow-contact-active-x: 0%;\n  --projected-shadow-contact-active-y: 0%;\n  --projected-shadow-contact-active-scale: 0.89;\n  --projected-shadow-contact-active-rotate: 0deg;\n  --projected-shadow-target-x: 8%;\n  --projected-shadow-target-y: -6%;\n  --projected-shadow-target-scale: 1.06;\n  --projected-shadow-target-rotate: 10deg;\n  --projected-shadow-target-active-x: 0%;\n  --projected-shadow-target-active-y: 0%;\n  --projected-shadow-target-active-scale: 0.86;\n  --projected-shadow-target-active-rotate: 0deg;\n}\n\n.projected-shadow-layer {\n  backface-visibility: hidden;\n  transform-origin: var(--projected-shadow-origin);\n}\n\n.projected-shadow[data-animated] .projected-shadow-layer {\n  transition-property: opacity, transform;\n  transition-duration: var(--projected-shadow-duration);\n  transition-timing-function: var(--projected-shadow-ease);\n}\n\n/* Promote only while the gather can actually run. Three layers per instance,\n   permanently promoted, is real GPU memory in a grid of these. */\n.projected-shadow[data-animated]:hover .projected-shadow-layer,\n.projected-shadow[data-animated]:focus-within .projected-shadow-layer,\n.projected-shadow[data-animated][data-active] .projected-shadow-layer {\n  will-change: transform, opacity;\n}\n\n.projected-shadow[data-animated] .projected-shadow-projected {\n  filter: blur(var(--projected-shadow-blur));\n  opacity: var(--projected-shadow-projected-opacity);\n  transform: translate3d(\n      var(--projected-shadow-projected-x),\n      var(--projected-shadow-projected-y),\n      0\n    )\n    scale(var(--projected-shadow-projected-scale))\n    rotate(var(--projected-shadow-projected-rotate));\n}\n\n.projected-shadow[data-animated] .projected-shadow-contact {\n  opacity: var(--projected-shadow-contact-opacity);\n  transform: translate3d(\n      var(--projected-shadow-contact-x),\n      var(--projected-shadow-contact-y),\n      0\n    )\n    scale(var(--projected-shadow-contact-scale))\n    rotate(var(--projected-shadow-contact-rotate));\n}\n\n.projected-shadow[data-animated] .projected-shadow-target {\n  transform: translate3d(\n      var(--projected-shadow-target-x),\n      var(--projected-shadow-target-y),\n      0\n    )\n    scale(var(--projected-shadow-target-scale))\n    rotate(var(--projected-shadow-target-rotate));\n}\n\n.projected-shadow[data-animated][data-active] .projected-shadow-layer {\n  transition-duration: var(--projected-shadow-active-duration);\n}\n\n.projected-shadow[data-animated][data-active] .projected-shadow-projected {\n  opacity: var(--projected-shadow-projected-active-opacity);\n  transform: translate3d(\n      var(--projected-shadow-projected-active-x),\n      var(--projected-shadow-projected-active-y),\n      0\n    )\n    scale(var(--projected-shadow-projected-active-scale))\n    rotate(var(--projected-shadow-projected-active-rotate));\n}\n\n.projected-shadow[data-animated][data-active] .projected-shadow-contact {\n  opacity: var(--projected-shadow-contact-active-opacity);\n  transform: translate3d(\n      var(--projected-shadow-contact-active-x),\n      var(--projected-shadow-contact-active-y),\n      0\n    )\n    scale(var(--projected-shadow-contact-active-scale))\n    rotate(var(--projected-shadow-contact-active-rotate));\n}\n\n.projected-shadow[data-animated][data-active] .projected-shadow-target {\n  transform: translate3d(\n      var(--projected-shadow-target-active-x),\n      var(--projected-shadow-target-active-y),\n      0\n    )\n    scale(var(--projected-shadow-target-active-scale))\n    rotate(var(--projected-shadow-target-active-rotate));\n}\n\n@media (hover: hover) and (pointer: fine) {\n  .projected-shadow[data-animated]:hover .projected-shadow-layer {\n    transition-duration: var(--projected-shadow-active-duration);\n  }\n\n  .projected-shadow[data-animated]:hover .projected-shadow-projected {\n    opacity: var(--projected-shadow-projected-active-opacity);\n    transform: translate3d(\n        var(--projected-shadow-projected-active-x),\n        var(--projected-shadow-projected-active-y),\n        0\n      )\n      scale(var(--projected-shadow-projected-active-scale))\n      rotate(var(--projected-shadow-projected-active-rotate));\n  }\n\n  .projected-shadow[data-animated]:hover .projected-shadow-contact {\n    opacity: var(--projected-shadow-contact-active-opacity);\n    transform: translate3d(\n        var(--projected-shadow-contact-active-x),\n        var(--projected-shadow-contact-active-y),\n        0\n      )\n      scale(var(--projected-shadow-contact-active-scale))\n      rotate(var(--projected-shadow-contact-active-rotate));\n  }\n\n  .projected-shadow[data-animated]:hover .projected-shadow-target {\n    transform: translate3d(\n        var(--projected-shadow-target-active-x),\n        var(--projected-shadow-target-active-y),\n        0\n      )\n      scale(var(--projected-shadow-target-active-scale))\n      rotate(var(--projected-shadow-target-active-rotate));\n  }\n}\n\n/* Keyboard parity with the hover gather when the wrapped child is focusable. */\n.projected-shadow[data-animated]:focus-within .projected-shadow-layer {\n  transition-duration: var(--projected-shadow-active-duration);\n}\n\n.projected-shadow[data-animated]:focus-within .projected-shadow-projected {\n  opacity: var(--projected-shadow-projected-active-opacity);\n  transform: translate3d(\n      var(--projected-shadow-projected-active-x),\n      var(--projected-shadow-projected-active-y),\n      0\n    )\n    scale(var(--projected-shadow-projected-active-scale))\n    rotate(var(--projected-shadow-projected-active-rotate));\n}\n\n.projected-shadow[data-animated]:focus-within .projected-shadow-contact {\n  opacity: var(--projected-shadow-contact-active-opacity);\n  transform: translate3d(\n      var(--projected-shadow-contact-active-x),\n      var(--projected-shadow-contact-active-y),\n      0\n    )\n    scale(var(--projected-shadow-contact-active-scale))\n    rotate(var(--projected-shadow-contact-active-rotate));\n}\n\n.projected-shadow[data-animated]:focus-within .projected-shadow-target {\n  transform: translate3d(\n      var(--projected-shadow-target-active-x),\n      var(--projected-shadow-target-active-y),\n      0\n    )\n    scale(var(--projected-shadow-target-active-scale))\n    rotate(var(--projected-shadow-target-active-rotate));\n}\n\n@media (prefers-reduced-motion: reduce) {\n  .projected-shadow[data-animated] {\n    --projected-shadow-blur: 0px;\n    --projected-shadow-projected-opacity: 0;\n    --projected-shadow-projected-active-opacity: 0;\n    --projected-shadow-projected-x: 0%;\n    --projected-shadow-projected-y: 0%;\n    --projected-shadow-projected-scale: 1;\n    --projected-shadow-projected-rotate: 0deg;\n    --projected-shadow-projected-active-x: 0%;\n    --projected-shadow-projected-active-y: 0%;\n    --projected-shadow-projected-active-scale: 1;\n    --projected-shadow-projected-active-rotate: 0deg;\n    --projected-shadow-contact-opacity: 0;\n    --projected-shadow-contact-active-opacity: 0;\n    --projected-shadow-contact-x: 0%;\n    --projected-shadow-contact-y: 0%;\n    --projected-shadow-contact-scale: 1;\n    --projected-shadow-contact-rotate: 0deg;\n    --projected-shadow-contact-active-x: 0%;\n    --projected-shadow-contact-active-y: 0%;\n    --projected-shadow-contact-active-scale: 1;\n    --projected-shadow-contact-active-rotate: 0deg;\n    --projected-shadow-target-x: 0%;\n    --projected-shadow-target-y: 0%;\n    --projected-shadow-target-scale: 1;\n    --projected-shadow-target-rotate: 0deg;\n    --projected-shadow-target-active-x: 0%;\n    --projected-shadow-target-active-y: 0%;\n    --projected-shadow-target-active-scale: 1;\n    --projected-shadow-target-active-rotate: 0deg;\n  }\n\n  .projected-shadow[data-animated] .projected-shadow-layer {\n    transition-duration: 0ms;\n    will-change: auto;\n  }\n}\n",
      "type": "registry:file",
      "target": "components/ui/projected-shadow.css"
    }
  ],
  "meta": {
    "tags": [
      "hover",
      "shadow",
      "projection",
      "contact-shadow",
      "layered-shadow",
      "primitive"
    ],
    "effects": [
      "projected-shadow",
      "hover-gather",
      "scale",
      "rotate",
      "reduced-motion"
    ]
  },
  "categories": [
    "effect"
  ],
  "type": "registry:ui"
}