{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "orbit-gallery",
  "type": "registry:ui",
  "title": "Orbit Gallery",
  "description": "A smooth, interactive photo gallery arranged in concentric rings with synchronized scrolling, dragging, hover and image expansion.",
  "dependencies": [
    "gsap",
    "@gsap/react",
    "@radix-ui/react-dialog",
    "lucide-react",
    "clsx",
    "tailwind-merge"
  ],
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "src/registry/base/orbit-gallery/orbit-gallery.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport * as Dialog from \"@radix-ui/react-dialog\";\nimport { useGSAP } from \"@gsap/react\";\nimport gsap from \"gsap\";\nimport { Pause, Play, X } from \"lucide-react\";\nimport { cn } from \"@/lib/utils\";\n\ngsap.registerPlugin(useGSAP);\n\nexport interface OrbitGalleryItem {\n  src: string;\n  alt: string;\n}\n\nexport interface OrbitGalleryProps {\n  items: OrbitGalleryItem[];\n  ringCount?: number;\n  radius?: number;\n  ringGap?: number;\n  imageSize?: number;\n  imageAspectRatio?: number;\n  borderRadius?: number;\n  speed?: number;\n  reverse?: boolean;\n  autoRotate?: boolean;\n  pauseOnHover?: boolean;\n  tilt?: number;\n  inertia?: number;\n  hoverScale?: number;\n  scrollSensitivity?: number;\n  dragSensitivity?: number;\n  motionDuration?: number;\n  showControls?: boolean;\n  className?: string;\n  ariaLabel?: string;\n  onReady?: () => void;\n  onSelect?: (item: OrbitGalleryItem | null) => void;\n}\n\nconst bounded = (value: number, fallback: number, min: number, max: number) =>\n  Number.isFinite(value) ? Math.min(max, Math.max(min, value)) : fallback;\n\ntype Selection = {\n  item: OrbitGalleryItem;\n  source: HTMLButtonElement;\n  aspectRatio: number;\n};\ntype Slot = { ring: number; angle: number; itemIndex: number };\n\nfunction OrbitImage({\n  selection,\n  open,\n  duration,\n  borderRadius,\n  overlayRef,\n  onExit,\n}: {\n  selection: Selection;\n  open: boolean;\n  duration: number;\n  borderRadius: number;\n  overlayRef: React.RefObject<HTMLDivElement | null>;\n  onExit: () => void;\n}) {\n  const panelRef = React.useRef<HTMLDivElement>(null);\n  const closeRef = React.useRef<HTMLButtonElement>(null);\n  const timelineRef = React.useRef<gsap.core.Timeline | null>(null);\n  const initializedRef = React.useRef(false);\n  const transitionRef = React.useRef<(() => void) | null>(null);\n  const { item, source, aspectRatio } = selection;\n\n  useGSAP(\n    () => {\n      const resize = () => transitionRef.current?.();\n      const media = window.matchMedia(\"(prefers-reduced-motion: reduce)\");\n      window.addEventListener(\"resize\", resize);\n      media.addEventListener(\"change\", resize);\n      return () => {\n        window.removeEventListener(\"resize\", resize);\n        media.removeEventListener(\"change\", resize);\n        timelineRef.current?.kill();\n        initializedRef.current = false;\n        source.style.visibility = \"\";\n      };\n    },\n    { scope: panelRef },\n  );\n\n  useGSAP(\n    (_context, contextSafe) => {\n      const animate = contextSafe!(() => {\n        const panel = panelRef.current;\n        const overlay = overlayRef.current;\n        if (!panel || !overlay) return;\n        const reduced = window.matchMedia(\n          \"(prefers-reduced-motion: reduce)\",\n        ).matches;\n        const rect = source.getBoundingClientRect();\n        const rotation = Number(gsap.getProperty(source, \"rotation\")) || 0;\n        const thumbnail = source.querySelector<HTMLSpanElement>(\n          \"[data-orbit-thumbnail]\",\n        );\n        const scale = thumbnail\n          ? Number(gsap.getProperty(thumbnail, \"scaleX\")) || 1\n          : 1;\n        const origin = {\n          x: rect.left + rect.width / 2 - (source.offsetWidth * scale) / 2,\n          y: rect.top + rect.height / 2 - (source.offsetHeight * scale) / 2,\n          width: source.offsetWidth * scale,\n          height: source.offsetHeight * scale,\n          rotation,\n        };\n        const width = Math.min(\n          600,\n          window.innerWidth - 40,\n          (window.innerHeight - 100) * aspectRatio,\n        );\n        const target = {\n          x: (window.innerWidth - width) / 2,\n          y: (window.innerHeight - width / aspectRatio) / 2,\n          width,\n          height: width / aspectRatio,\n          rotation: 0,\n        };\n        timelineRef.current?.kill();\n        if (!initializedRef.current) {\n          gsap.set(panel, origin);\n          gsap.set([overlay, closeRef.current], { opacity: 0 });\n          source.style.visibility = \"hidden\";\n          initializedRef.current = true;\n        }\n        gsap.set(panel, { opacity: 1 });\n        const time = reduced ? 0 : duration;\n        const timeline = gsap.timeline({\n          onComplete: open\n            ? undefined\n            : contextSafe!(() => {\n                source.style.visibility = \"\";\n                onExit();\n              }),\n        });\n        timeline\n          .to(\n            panel,\n            {\n              ...(open ? target : origin),\n              duration: time,\n              ease: \"power3.inOut\",\n            },\n            0,\n          )\n          .to(\n            overlay,\n            {\n              opacity: open ? 1 : 0,\n              duration: time * 0.75,\n              ease: \"power2.out\",\n            },\n            0,\n          )\n          .to(\n            closeRef.current,\n            { opacity: open ? 1 : 0, duration: time * 0.3 },\n            open ? time * 0.65 : 0,\n          );\n        timelineRef.current = timeline;\n      });\n      transitionRef.current = animate;\n      animate();\n    },\n    {\n      scope: panelRef,\n      dependencies: [open, duration, aspectRatio, onExit, overlayRef, source],\n      revertOnUpdate: false,\n    },\n  );\n\n  return (\n    <Dialog.Content\n      ref={panelRef}\n      aria-describedby={undefined}\n      onOpenAutoFocus={(event) => {\n        event.preventDefault();\n        closeRef.current?.focus({ preventScroll: true });\n      }}\n      onCloseAutoFocus={(event) => {\n        event.preventDefault();\n        source.focus({ preventScroll: true });\n      }}\n      onInteractOutside={(event) => {\n        if (!open) event.preventDefault();\n      }}\n      className=\"fixed left-0 top-0 z-50 overflow-hidden bg-[#141419] shadow-2xl outline-none\"\n      style={{\n        borderRadius,\n        opacity: 0,\n        willChange: \"transform, width, height\",\n      }}\n    >\n      <Dialog.Title className=\"sr-only\">{item.alt}</Dialog.Title>\n      <img\n        src={item.src}\n        alt={item.alt}\n        draggable={false}\n        className=\"h-full w-full object-cover\"\n      />\n      <Dialog.Close asChild>\n        <button\n          ref={closeRef}\n          type=\"button\"\n          aria-label=\"Close image\"\n          className=\"absolute right-3 top-3 flex size-10 cursor-pointer items-center justify-center rounded-full border border-white/20 bg-black/60 text-white backdrop-blur-md focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white\"\n        >\n          <X size={18} aria-hidden=\"true\" />\n        </button>\n      </Dialog.Close>\n    </Dialog.Content>\n  );\n}\n\nexport function OrbitGallery({\n  items,\n  ringCount = 3,\n  radius = 350,\n  ringGap = 185,\n  imageSize = 96,\n  imageAspectRatio = 0.84,\n  borderRadius = 12,\n  speed = 3,\n  reverse = false,\n  autoRotate = true,\n  pauseOnHover = true,\n  tilt = 38,\n  inertia = 0.8,\n  hoverScale = 1.12,\n  scrollSensitivity = 0.06,\n  dragSensitivity = 0.22,\n  motionDuration = 0.65,\n  showControls = true,\n  className,\n  ariaLabel = \"Orbit image gallery\",\n  onReady,\n  onSelect,\n}: OrbitGalleryProps) {\n  const rootRef = React.useRef<HTMLDivElement>(null);\n  const overlayRef = React.useRef<HTMLDivElement>(null);\n  const phaseRef = React.useRef({ rotation: 0, offset: 0 });\n  const readyRef = React.useRef(onReady);\n  const notifiedRef = React.useRef(false);\n  const interactiveRef = React.useRef<{\n    hover: (button: HTMLButtonElement, active: boolean) => void;\n    focus: (index: number) => void;\n  } | null>(null);\n  const [paused, setPaused] = React.useState(false);\n  const [selection, setSelection] = React.useState<Selection | null>(null);\n  const [open, setOpen] = React.useState(false);\n  const selectedRef = React.useRef(false);\n  const pausedRef = React.useRef(false);\n  const count = Math.round(bounded(ringCount, 3, 1, 5));\n  const size = bounded(imageSize, 96, 40, 160);\n  const ratio = bounded(imageAspectRatio, 0.84, 0.5, 1.5);\n  const corners = bounded(borderRadius, 12, 0, 40);\n  const easing = bounded(inertia, 0.8, 0.15, 2);\n  const duration = bounded(motionDuration, 0.65, 0.2, 1.5);\n  const slots = React.useMemo(() => {\n    if (!items.length) return [];\n    return Array.from({ length: count }, (_, ring) => {\n      const length = Math.min(\n        40,\n        Math.max(12, Math.round(items.length * (1 + ring * 0.3))),\n      );\n      return Array.from(\n        { length },\n        (_, index): Slot => ({\n          ring,\n          angle: (index * 360) / length + ring * 9,\n          itemIndex: (index + ring * 7) % items.length,\n        }),\n      );\n    }).flat();\n  }, [count, items.length]);\n\n  React.useEffect(() => {\n    readyRef.current = onReady;\n  }, [onReady]);\n  React.useEffect(() => {\n    selectedRef.current = Boolean(selection);\n  }, [selection]);\n  React.useEffect(() => {\n    pausedRef.current = paused;\n  }, [paused]);\n\n  useGSAP(\n    (_context, contextSafe) => {\n      const root = rootRef.current;\n      if (!root) return;\n      const buttons = Array.from(\n        root.querySelectorAll<HTMLButtonElement>(\"[data-orbit-item]\"),\n      );\n      const setters = buttons.map((button) => gsap.quickSetter(button, \"css\"));\n      const phase = phaseRef.current;\n      const media = window.matchMedia(\"(prefers-reduced-motion: reduce)\");\n      let reduced = media.matches;\n      let width = 0;\n      let height = 0;\n      let targetOffset = phase.offset;\n      let visible = true;\n      let hovered = false;\n      let lastRotation = NaN;\n      let lastOffset = NaN;\n      let ignoreClickUntil = 0;\n      let dragging: {\n        id: number;\n        x: number;\n        y: number;\n        offset: number;\n        active: boolean;\n      } | null = null;\n      const move = gsap.quickTo(phase, \"offset\", {\n        duration: easing,\n        ease: \"power3.out\",\n      });\n      const motion = { velocity: 0 };\n      const changeVelocity = gsap.quickTo(motion, \"velocity\", {\n        duration: 0.6,\n        ease: \"power2.out\",\n      });\n      let targetVelocity = -1;\n      const render = (force = false) => {\n        if (\n          !width ||\n          !height ||\n          (!force &&\n            phase.rotation === lastRotation &&\n            phase.offset === lastOffset)\n        )\n          return;\n        const scale = Math.max(0.32, width / 1600);\n        const imageWidth = Math.max(30, size * scale);\n        buttons.forEach((_button, index) => {\n          const slot = slots[index];\n          const direction = slot.ring % 2 ? -1 : 1;\n          const angle =\n            slot.angle +\n            direction *\n              (phase.rotation + phase.offset) *\n              (1 + slot.ring * 0.12);\n          const radians = (angle * Math.PI) / 180;\n          const distance =\n            (bounded(radius, 350, 220, 550) +\n              slot.ring * bounded(ringGap, 185, 100, 260)) *\n            scale;\n          setters[index]({\n            x: width / 2 + Math.cos(radians) * distance - imageWidth / 2,\n            y:\n              height / 2 +\n              Math.sin(radians) * distance -\n              imageWidth / ratio / 2,\n            rotation: reduced\n              ? 0\n              : Math.sin(radians * 2) * bounded(tilt, 38, 0, 65),\n          });\n        });\n        lastRotation = phase.rotation;\n        lastOffset = phase.offset;\n      };\n      const measure = () => {\n        width = root.clientWidth;\n        height = root.clientHeight;\n        const imageWidth = Math.max(30, size * Math.max(0.32, width / 1600));\n        gsap.set(buttons, { width: imageWidth, height: imageWidth / ratio });\n        gsap.set(\n          buttons.map((button) =>\n            button.querySelector(\"[data-orbit-thumbnail]\"),\n          ),\n          { borderRadius: corners * Math.min(1, Math.max(0.32, width / 1600)) },\n        );\n        render(true);\n        if (width && height && !notifiedRef.current) {\n          notifiedRef.current = true;\n          readyRef.current?.();\n        }\n      };\n      const advance = (delta: number) => {\n        targetOffset += delta;\n        if (reduced) {\n          move.tween.pause();\n          phase.offset = targetOffset;\n          render();\n        } else move(targetOffset);\n      };\n      const wheel = (event: WheelEvent) => {\n        if (\n          event.ctrlKey ||\n          event.metaKey ||\n          selectedRef.current ||\n          !slots.length\n        )\n          return;\n        event.preventDefault();\n        const delta =\n          (event.deltaY || event.deltaX) *\n          (event.deltaMode === 1 ? 16 : event.deltaMode === 2 ? height : 1);\n        advance(\n          Math.max(-160, Math.min(160, delta)) *\n            bounded(scrollSensitivity, 0.06, 0.01, 0.3),\n        );\n      };\n      const pointerDown = (event: PointerEvent) => {\n        if (\n          event.button !== 0 ||\n          selectedRef.current ||\n          (event.target as HTMLElement).closest(\"[data-orbit-control]\")\n        )\n          return;\n        dragging = {\n          id: event.pointerId,\n          x: event.clientX,\n          y: event.clientY,\n          offset: targetOffset,\n          active: false,\n        };\n      };\n      const pointerMove = (event: PointerEvent) => {\n        if (!dragging || event.pointerId !== dragging.id) return;\n        const delta =\n          event.clientX - dragging.x + (event.clientY - dragging.y) * 0.5;\n        if (\n          !dragging.active &&\n          Math.hypot(event.clientX - dragging.x, event.clientY - dragging.y) < 6\n        )\n          return;\n        if (!dragging.active) {\n          dragging.active = true;\n          root.setPointerCapture(event.pointerId);\n        }\n        targetOffset =\n          dragging.offset - delta * bounded(dragSensitivity, 0.22, 0.05, 0.6);\n        if (reduced) {\n          phase.offset = targetOffset;\n          render();\n        } else move(targetOffset);\n      };\n      const pointerUp = (event: PointerEvent) => {\n        if (!dragging || event.pointerId !== dragging.id) return;\n        if (dragging.active) {\n          ignoreClickUntil = performance.now() + 350;\n          if (root.hasPointerCapture(event.pointerId))\n            root.releasePointerCapture(event.pointerId);\n        }\n        dragging = null;\n      };\n      const captureClick = (event: MouseEvent) => {\n        if (performance.now() < ignoreClickUntil) {\n          event.preventDefault();\n          event.stopPropagation();\n        }\n      };\n      const keyboard = (event: KeyboardEvent) => {\n        if (\n          selectedRef.current ||\n          (event.target as HTMLElement).closest(\"[data-orbit-control]\")\n        )\n          return;\n        if (event.key === \"ArrowLeft\" || event.key === \"ArrowRight\") {\n          event.preventDefault();\n          advance(event.key === \"ArrowRight\" ? 18 : -18);\n        }\n        if (event.key === \" \" && event.target === root) {\n          event.preventDefault();\n          setPaused((value) => !value);\n        }\n      };\n      const enter = () => {\n        hovered = true;\n      };\n      const leave = () => {\n        hovered = false;\n      };\n      const changeMotion = () => {\n        reduced = media.matches;\n        move.tween.pause();\n        targetOffset = phase.offset;\n        render(true);\n      };\n      const tick = (_time: number, delta: number) => {\n        if (!visible || document.hidden || selectedRef.current) {\n          move.tween.pause();\n          targetOffset = phase.offset;\n          return;\n        }\n        const velocity =\n          !reduced &&\n          autoRotate &&\n          !pausedRef.current &&\n          !(pauseOnHover && hovered) &&\n          !dragging\n            ? 1\n            : 0;\n        if (velocity !== targetVelocity) {\n          targetVelocity = velocity;\n          if (reduced) {\n            changeVelocity.tween.pause();\n            motion.velocity = 0;\n          } else changeVelocity(velocity);\n        }\n        if (motion.velocity) {\n          phase.rotation +=\n            (Math.min(delta, 40) / 1000) *\n            bounded(speed, 3, 0, 18) *\n            motion.velocity *\n            (reverse ? -1 : 1);\n        }\n        render();\n      };\n      interactiveRef.current = {\n        hover: contextSafe!((button: HTMLButtonElement, active: boolean) => {\n          const thumbnail = button.querySelector(\"[data-orbit-thumbnail]\");\n          gsap.to(thumbnail, {\n            scale: active ? bounded(hoverScale, 1.12, 1, 1.4) : 1,\n            duration: reduced ? 0 : 0.4,\n            ease: \"power3.out\",\n            overwrite: true,\n          });\n          button.style.zIndex = active ? \"2\" : \"1\";\n        }),\n        focus: (index: number) => {\n          const button = buttons[index];\n          if (!button) return;\n          const rect = button.getBoundingClientRect();\n          const frame = root.getBoundingClientRect();\n          if (rect.top < frame.top + 10 || rect.bottom > frame.bottom - 10) {\n            const slot = slots[index];\n            const rate = (slot.ring % 2 ? -1 : 1) * (1 + slot.ring * 0.12);\n            const angle = slot.angle + rate * (phase.rotation + phase.offset);\n            advance(-(((((angle + 180) % 360) + 360) % 360) - 180) / rate);\n          }\n        },\n      };\n      const resize = new ResizeObserver(measure);\n      const intersection = new IntersectionObserver(\n        ([entry]) => {\n          visible = entry.isIntersecting;\n        },\n        { threshold: 0 },\n      );\n      resize.observe(root);\n      intersection.observe(root);\n      root.addEventListener(\"wheel\", wheel, { passive: false });\n      root.addEventListener(\"pointerdown\", pointerDown);\n      root.addEventListener(\"pointermove\", pointerMove);\n      root.addEventListener(\"pointerup\", pointerUp);\n      root.addEventListener(\"pointercancel\", pointerUp);\n      root.addEventListener(\"lostpointercapture\", pointerUp);\n      root.addEventListener(\"click\", captureClick, true);\n      root.addEventListener(\"keydown\", keyboard);\n      root.addEventListener(\"pointerenter\", enter);\n      root.addEventListener(\"pointerleave\", leave);\n      media.addEventListener(\"change\", changeMotion);\n      measure();\n      gsap.ticker.add(tick);\n      return () => {\n        gsap.ticker.remove(tick);\n        move.tween.kill();\n        changeVelocity.tween.kill();\n        resize.disconnect();\n        intersection.disconnect();\n        root.removeEventListener(\"wheel\", wheel);\n        root.removeEventListener(\"pointerdown\", pointerDown);\n        root.removeEventListener(\"pointermove\", pointerMove);\n        root.removeEventListener(\"pointerup\", pointerUp);\n        root.removeEventListener(\"pointercancel\", pointerUp);\n        root.removeEventListener(\"lostpointercapture\", pointerUp);\n        root.removeEventListener(\"click\", captureClick, true);\n        root.removeEventListener(\"keydown\", keyboard);\n        root.removeEventListener(\"pointerenter\", enter);\n        root.removeEventListener(\"pointerleave\", leave);\n        media.removeEventListener(\"change\", changeMotion);\n        interactiveRef.current = null;\n      };\n    },\n    {\n      scope: rootRef,\n      dependencies: [\n        slots,\n        size,\n        ratio,\n        corners,\n        radius,\n        ringGap,\n        speed,\n        reverse,\n        autoRotate,\n        pauseOnHover,\n        tilt,\n        easing,\n        hoverScale,\n        scrollSensitivity,\n        dragSensitivity,\n      ],\n      revertOnUpdate: true,\n    },\n  );\n\n  const exit = React.useCallback(() => {\n    selectedRef.current = false;\n    setSelection(null);\n  }, []);\n  const close = (value: boolean) => {\n    if (!value) {\n      setOpen(false);\n      onSelect?.(null);\n    }\n  };\n\n  return (\n    <Dialog.Root open={Boolean(selection)} onOpenChange={close}>\n      <div\n        ref={rootRef}\n        role=\"region\"\n        aria-label={ariaLabel}\n        tabIndex={0}\n        className={cn(\n          \"relative isolate h-full min-h-64 w-full overflow-hidden bg-[#0c0c10] outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-white/40\",\n          className,\n        )}\n        style={{ touchAction: \"pan-y\" }}\n      >\n        <div\n          className=\"absolute inset-0\"\n          role=\"list\"\n          aria-label=\"Gallery images\"\n        >\n          {slots.map((slot, index) => {\n            const item = items[slot.itemIndex];\n            return (\n              <div key={`${slot.ring}-${index}`} role=\"listitem\">\n                <button\n                  type=\"button\"\n                  data-orbit-item\n                  aria-label={`View ${item.alt}, ring ${slot.ring + 1}`}\n                  aria-haspopup=\"dialog\"\n                  tabIndex={slot.ring === 0 && index < items.length ? 0 : -1}\n                  onPointerEnter={(event) =>\n                    interactiveRef.current?.hover(event.currentTarget, true)\n                  }\n                  onPointerLeave={(event) => {\n                    if (!event.currentTarget.matches(\":focus-visible\"))\n                      interactiveRef.current?.hover(event.currentTarget, false);\n                  }}\n                  onFocus={(event) => {\n                    if (event.currentTarget.matches(\":focus-visible\")) {\n                      interactiveRef.current?.hover(event.currentTarget, true);\n                      interactiveRef.current?.focus(index);\n                    }\n                  }}\n                  onBlur={(event) =>\n                    interactiveRef.current?.hover(event.currentTarget, false)\n                  }\n                  onClick={(event) => {\n                    if (selectedRef.current) return;\n                    selectedRef.current = true;\n                    const image = event.currentTarget.querySelector(\"img\");\n                    setSelection({\n                      item,\n                      source: event.currentTarget,\n                      aspectRatio:\n                        image?.naturalWidth && image.naturalHeight\n                          ? image.naturalWidth / image.naturalHeight\n                          : ratio,\n                    });\n                    setOpen(true);\n                    onSelect?.(item);\n                  }}\n                  className=\"absolute left-0 top-0 z-1 cursor-pointer border-0 bg-transparent p-0 outline-none will-change-transform focus-visible:z-10\"\n                  style={{ width: 0, height: 0 }}\n                >\n                  <span\n                    data-orbit-thumbnail\n                    className=\"block h-full w-full overflow-hidden bg-[#26262d] shadow-sm ring-white/80 in-focus-visible:ring-2\"\n                    style={{ borderRadius: corners }}\n                  >\n                    <img\n                      src={item.src}\n                      alt=\"\"\n                      draggable={false}\n                      loading=\"eager\"\n                      decoding=\"async\"\n                      className=\"h-full w-full select-none object-cover\"\n                    />\n                  </span>\n                </button>\n              </div>\n            );\n          })}\n        </div>\n        {showControls && autoRotate && (\n          <button\n            type=\"button\"\n            data-orbit-control\n            onClick={() => setPaused((value) => !value)}\n            aria-label={\n              paused ? \"Resume gallery motion\" : \"Pause gallery motion\"\n            }\n            aria-pressed={paused}\n            className=\"absolute bottom-4 left-4 z-10 flex size-9 cursor-pointer items-center justify-center rounded-full border border-white/15 bg-[#0c0c10]/80 text-white/60 backdrop-blur-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white\"\n          >\n            {paused ? (\n              <Play size={14} aria-hidden=\"true\" />\n            ) : (\n              <Pause size={14} aria-hidden=\"true\" />\n            )}\n          </button>\n        )}\n      </div>\n      {selection && (\n        <Dialog.Portal>\n          <Dialog.Overlay\n            ref={overlayRef}\n            className=\"fixed inset-0 z-40 bg-black/80 backdrop-blur-sm\"\n          />\n          <OrbitImage\n            selection={selection}\n            open={open}\n            duration={duration}\n            borderRadius={corners}\n            overlayRef={overlayRef}\n            onExit={exit}\n          />\n        </Dialog.Portal>\n      )}\n    </Dialog.Root>\n  );\n}\n\nexport default OrbitGallery;\n",
      "type": "registry:ui",
      "target": "@ui/orbit-gallery.tsx"
    }
  ]
}
