{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "glide-carousel",
  "title": "Glide Carousel",
  "description": "A canvas-rendered, drag-to-scroll carousel with a curved wave layout and ambient wind/drift/magnetic card sway, click-to-open lightbox modal, autoplay, and image/video support.",
  "dependencies": [
    "clsx",
    "tailwind-merge"
  ],
  "files": [
    {
      "path": "registry/new-york/glide-carousel/glide-carousel.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { clsx, type ClassValue } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nfunction cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs));\n}\n\nexport interface GlideCarouselItem {\n  src: string;\n  type?: \"image\" | \"video\";\n  title?: string;\n  description?: string;\n}\n\nexport type GlideCarouselWindMode = \"wind\" | \"drift\" | \"magnetic\" | \"none\";\n\nexport interface GlideCarouselProps extends Omit<React.ComponentPropsWithoutRef<\"div\">, \"children\"> {\n  items?: GlideCarouselItem[];\n  cardWidth?: number;\n  cardHeight?: number;\n  cardRadius?: number;\n  waveStrength?: number;\n  bgColor?: string;\n  autoPlay?: boolean;\n  autoPlaySpeed?: number;\n  autoPlayDirection?: \"right\" | \"left\";\n  showArrows?: boolean;\n  gap?: number;\n  windStrength?: number;\n  windMode?: GlideCarouselWindMode;\n  parallaxInner?: boolean;\n}\n\nconst DEFAULT_ITEMS: GlideCarouselItem[] = [\n  { src: \"https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=800&q=80\", title: \"Alpine Ridge\", description: \"A quiet trail above the clouds.\" },\n  { src: \"https://images.unsplash.com/photo-1519681393784-d120267933ba?w=800&q=80\", title: \"Northern Lights\", description: \"Aurora over the fjord.\" },\n  { src: \"https://images.unsplash.com/photo-1441974231531-c6227db76b6e?w=800&q=80\", title: \"Forest Path\", description: \"Morning light through pines.\" },\n  { src: \"https://images.unsplash.com/photo-1470071459604-3b5ec3a7fe05?w=800&q=80\", title: \"Mountain Lake\", description: \"Still water, sharp reflection.\" },\n  { src: \"https://images.unsplash.com/photo-1472214103451-9374bd1c798e?w=800&q=80\", title: \"Desert Dunes\", description: \"Wind-carved lines at dusk.\" },\n];\n\nexport function GlideCarousel({\n  items: itemsProp = DEFAULT_ITEMS,\n  cardWidth = 320,\n  cardHeight = 420,\n  cardRadius = 20,\n  waveStrength = 18,\n  bgColor = \"#000000\",\n  autoPlay = true,\n  autoPlaySpeed = 1,\n  autoPlayDirection = \"right\",\n  showArrows = true,\n  gap = 32,\n  windStrength = 1.0,\n  windMode = \"wind\",\n  parallaxInner = true,\n  className,\n  style,\n  ...props\n}: GlideCarouselProps) {\n  const uid = React.useId().replace(/[^a-zA-Z0-9]/g, \"\");\n  const canvasRef = React.useRef<HTMLCanvasElement>(null);\n  const containerRef = React.useRef<HTMLDivElement>(null);\n  const mediaRefs = React.useRef<Record<number, HTMLImageElement | HTMLVideoElement>>({});\n  const scrollXRef = React.useRef(0);\n  const targetXRef = React.useRef(0);\n  const rafRef = React.useRef<number | null>(null);\n  const isDraggingRef = React.useRef(false);\n  const dragStartRef = React.useRef(0);\n  const dragScrollRef = React.useRef(0);\n  const lastTimeRef = React.useRef(0);\n  const velocityRef = React.useRef(0);\n  const lastDragXRef = React.useRef(0);\n  const smoothMouseRef = React.useRef({ x: 0, y: 0 });\n  const rawMouseRef = React.useRef({ x: 0, y: 0 });\n  const hoveredIndexRef = React.useRef(-1);\n  const timeRef = React.useRef(0);\n  const dragDistRef = React.useRef(0);\n\n  const propsRef = React.useRef({\n    cardWidth,\n    cardHeight,\n    cardRadius,\n    waveStrength,\n    gap,\n    windStrength,\n    autoPlaySpeed,\n    autoPlayDirection,\n    autoPlay,\n    parallaxInner,\n    windMode,\n  });\n  React.useEffect(() => {\n    propsRef.current = {\n      cardWidth,\n      cardHeight,\n      cardRadius,\n      waveStrength,\n      gap,\n      windStrength,\n      autoPlaySpeed,\n      autoPlayDirection,\n      autoPlay,\n      parallaxInner,\n      windMode,\n    };\n  }, [cardWidth, cardHeight, cardRadius, waveStrength, gap, windStrength, autoPlaySpeed, autoPlayDirection, autoPlay, parallaxInner, windMode]);\n\n  const [modalItem, setModalItem] = React.useState<GlideCarouselItem | null>(null);\n\n  const activeRef = React.useRef({ windMode, autoPlay, autoPlayDir: autoPlayDirection, parallax: parallaxInner });\n  React.useEffect(() => {\n    activeRef.current = { windMode, autoPlay, autoPlayDir: autoPlayDirection, parallax: parallaxInner };\n  }, [windMode, autoPlay, autoPlayDirection, parallaxInner]);\n\n  const items: GlideCarouselItem[] = React.useMemo(\n    () => (Array.isArray(itemsProp) ? itemsProp.filter((i) => i?.src) : []),\n    [itemsProp]\n  );\n  const count = items.length;\n  const itemsRef = React.useRef(items);\n  const countRef = React.useRef(count);\n  React.useEffect(() => {\n    itemsRef.current = items;\n    countRef.current = count;\n  }, [items, count]);\n\n  const drawRef = React.useRef<() => void>(() => {});\n  React.useEffect(() => {\n    drawRef.current = () => {\n      const canvas = canvasRef.current;\n      if (!canvas) return;\n      const ctx = canvas.getContext(\"2d\");\n      if (!ctx) return;\n      // Draw in CSS px; the backing store is at the device pixel ratio (see resize below).\n      const W = canvas.clientWidth,\n        H = canvas.clientHeight;\n      const dpr = W ? canvas.width / W : 1;\n      ctx.setTransform(dpr, 0, 0, dpr, 0, 0);\n      ctx.clearRect(0, 0, W, H);\n      const { cardWidth, cardHeight, cardRadius, waveStrength, gap, windStrength } = propsRef.current;\n      const { windMode: activeWindMode, parallax: activeParallax } = activeRef.current;\n      const items = itemsRef.current;\n      const count = countRef.current;\n\n      if (count === 0) {\n        const ph = 4,\n          phW = cardWidth * 0.85,\n          phH = cardHeight * 0.85,\n          phGap = gap;\n        const totalPh = ph * (phW + phGap) - phGap;\n        for (let i = 0; i < ph; i++) {\n          const bx = W / 2 - totalPh / 2 + i * (phW + phGap);\n          const normX = (bx + phW / 2 - W / 2) / (W * 0.6);\n          const phScale = Math.max(0.72, 1 - Math.abs(normX) * 0.22);\n          const phAlpha = Math.max(0.25, 1 - Math.abs(normX) * 0.5);\n          const sw = phW * phScale,\n            sh = phH * phScale;\n          const rx = bx + (phW - sw) / 2,\n            ry = H / 2 - sh / 2,\n            r = cardRadius * phScale;\n          ctx.save();\n          ctx.globalAlpha = phAlpha;\n          ctx.beginPath();\n          ctx.moveTo(rx + r, ry);\n          ctx.lineTo(rx + sw - r, ry);\n          ctx.quadraticCurveTo(rx + sw, ry, rx + sw, ry + r);\n          ctx.lineTo(rx + sw, ry + sh - r);\n          ctx.quadraticCurveTo(rx + sw, ry + sh, rx + sw - r, ry + sh);\n          ctx.lineTo(rx + r, ry + sh);\n          ctx.quadraticCurveTo(rx, ry + sh, rx, ry + sh - r);\n          ctx.lineTo(rx, ry + r);\n          ctx.quadraticCurveTo(rx, ry, rx + r, ry);\n          ctx.closePath();\n          ctx.fillStyle = `hsl(${i * 35 + 200},18%,28%)`;\n          ctx.fill();\n          ctx.restore();\n        }\n        ctx.save();\n        ctx.globalAlpha = 0.4;\n        ctx.fillStyle = \"#ffffff\";\n        ctx.font = \"13px 'Helvetica Neue', Arial, sans-serif\";\n        ctx.textAlign = \"center\";\n        ctx.textBaseline = \"middle\";\n        ctx.fillText(\"No items\", W / 2, H / 2);\n        ctx.restore();\n        return;\n      }\n\n      const cx = W / 2,\n        cy = H / 2,\n        scroll = scrollXRef.current;\n      const slotW = cardWidth + gap,\n        loopW = count * slotW,\n        t = timeRef.current;\n\n      for (let copy = -1; copy <= 1; copy++) {\n        items.forEach((item, i) => {\n          const baseX = cx - loopW / 2 + i * slotW + copy * loopW - scroll + gap / 2;\n          const normX = (baseX + cardWidth / 2 - cx) / (W * 0.6);\n          if (Math.abs(normX) > 2.2) return;\n\n          const waveY = Math.sin(normX * Math.PI * 0.85) * waveStrength * -1;\n          const rotY = normX * 22;\n          const scale = Math.max(0.68, 1 - Math.abs(normX) * 0.22);\n          const alpha = Math.max(0.2, 1 - Math.abs(normX) * 0.55);\n\n          const p1 = i * 2.39 + copy * 1.13;\n          const p2 = i * 1.17 + copy * 0.73;\n          const p3 = i * 3.07 + copy * 1.51;\n          let swayZ = 0,\n            swayX = 0,\n            swayY = 0,\n            swayScale = 1;\n\n          if (activeWindMode === \"wind\") {\n            const ws = windStrength;\n            swayZ = (Math.sin(t * 0.00045 + p1) * 3.2 + Math.sin(t * 0.00091 + p2 * 1.4) * 1.6 + Math.sin(t * 0.00173 + p3 * 0.8) * 0.7) * ws;\n            swayX = (Math.cos(t * 0.00038 + p2) * 2.4 + Math.cos(t * 0.00082 + p1 * 1.2) * 1.1 + Math.sin(t * 0.00151 + p3 * 1.7) * 0.5) * ws;\n            swayY = (Math.sin(t * 0.00052 + p3) * 3.8 + Math.sin(t * 0.00097 + p1 * 0.9) * 1.4 + Math.cos(t * 0.00139 + p2 * 1.6) * 0.6) * ws;\n            swayScale = 1 + (Math.sin(t * 0.00061 + p2) * 0.008 + Math.sin(t * 0.00112 + p1 * 1.3) * 0.004) * ws;\n          } else if (activeWindMode === \"drift\") {\n            const ws = windStrength;\n            swayZ = (Math.sin(t * 0.00018 + p1) * 6 + Math.sin(t * 0.00031 + p2) * 2.5) * ws;\n            swayX = (Math.sin(t * 0.00014 + p2) * 5 + Math.cos(t * 0.00025 + p3) * 2) * ws;\n            swayY = (Math.sin(t * 0.00021 + p3) * 22 + Math.sin(t * 0.00037 + p1) * 10) * ws;\n            swayScale = 1 + (Math.sin(t * 0.00028 + p2) * 0.025 + Math.sin(t * 0.00019 + p1) * 0.012) * ws;\n          } else if (activeWindMode === \"magnetic\") {\n            const ws = windStrength;\n            const mx = smoothMouseRef.current.x,\n              my = smoothMouseRef.current.y;\n            const cardNormX = (baseX + cardWidth / 2 - cx) / (W * 0.5);\n            const distX = mx - cardNormX * 0.5,\n              distY = my;\n            const dist = Math.sqrt(distX * distX + distY * distY);\n            const pull = Math.max(0, 1 - dist * 1.8);\n            swayX = distX * 12 * pull * ws;\n            swayZ = distX * -6 * pull * ws;\n            swayY = distY * 18 * pull * ws;\n            swayScale = 1 + pull * 0.04 * ws;\n          }\n\n          const cardX = baseX + cardWidth / 2,\n            cardY = cy + waveY + swayY;\n          ctx.save();\n          ctx.globalAlpha = alpha;\n          ctx.translate(cardX, cardY);\n          ctx.rotate((swayZ * Math.PI) / 180);\n\n          const totalRotY = rotY + swayX;\n          const skewRad = (totalRotY * Math.PI) / 180;\n          const cosV = Math.cos(skewRad),\n            sinV = Math.sin(skewRad) * 0.4;\n          const finalScale = scale * swayScale;\n          const scaledW = cardWidth * finalScale,\n            scaledH = cardHeight * finalScale;\n          const rx = -scaledW / 2,\n            ry = -scaledH / 2,\n            r = cardRadius * finalScale;\n\n          ctx.shadowColor = \"rgba(0,0,0,0.45)\";\n          ctx.shadowBlur = 32 * finalScale;\n          ctx.shadowOffsetY = 14 * finalScale;\n          ctx.transform(cosV, sinV * 0.22, sinV * 0.08, finalScale, 0, 0);\n\n          ctx.beginPath();\n          ctx.moveTo(rx + r, ry);\n          ctx.lineTo(rx + scaledW - r, ry);\n          ctx.quadraticCurveTo(rx + scaledW, ry, rx + scaledW, ry + r);\n          ctx.lineTo(rx + scaledW, ry + scaledH - r);\n          ctx.quadraticCurveTo(rx + scaledW, ry + scaledH, rx + scaledW - r, ry + scaledH);\n          ctx.lineTo(rx + r, ry + scaledH);\n          ctx.quadraticCurveTo(rx, ry + scaledH, rx, ry + scaledH - r);\n          ctx.lineTo(rx, ry + r);\n          ctx.quadraticCurveTo(rx, ry, rx + r, ry);\n          ctx.closePath();\n          ctx.clip();\n          ctx.shadowColor = \"transparent\";\n\n          const media = mediaRefs.current[i];\n          if (media) {\n            const mw = media instanceof HTMLVideoElement ? media.videoWidth : (media as HTMLImageElement).naturalWidth;\n            const mh = media instanceof HTMLVideoElement ? media.videoHeight : (media as HTMLImageElement).naturalHeight;\n            if (mw && mh) {\n              const sc = Math.max(scaledW / mw, scaledH / mh);\n              const ipx = activeParallax ? smoothMouseRef.current.x * 18 : 0;\n              const ipy = activeParallax ? smoothMouseRef.current.y * 12 : 0;\n              const dw = mw * sc * 1.08,\n                dh = mh * sc * 1.08;\n              ctx.drawImage(media, rx + (scaledW - dw) / 2 + ipx, ry + (scaledH - dh) / 2 + ipy, dw, dh);\n            } else {\n              ctx.fillStyle = `hsl(${i * 40},30%,40%)`;\n              ctx.fillRect(rx, ry, scaledW, scaledH);\n            }\n          } else {\n            ctx.fillStyle = `hsl(${i * 40},30%,40%)`;\n            ctx.fillRect(rx, ry, scaledW, scaledH);\n          }\n\n          const lightX = rx + scaledW * (0.5 + Math.sin(swayZ * 0.08) * 0.3);\n          const grad = ctx.createLinearGradient(lightX - scaledW * 0.3, ry, lightX + scaledW * 0.3, ry + scaledH * 0.55);\n          grad.addColorStop(0, \"rgba(255,255,255,0.18)\");\n          grad.addColorStop(0.5, \"rgba(255,255,255,0.06)\");\n          grad.addColorStop(1, \"rgba(255,255,255,0)\");\n          ctx.fillStyle = grad;\n          ctx.fillRect(rx, ry, scaledW, scaledH);\n\n          const isHovered = hoveredIndexRef.current === i;\n          if (isHovered && item.title) {\n            const titleAlpha = Math.min(1, alpha * 1.4);\n            const titleGrad = ctx.createLinearGradient(rx, ry + scaledH * 0.55, rx, ry + scaledH);\n            titleGrad.addColorStop(0, \"rgba(0,0,0,0)\");\n            titleGrad.addColorStop(1, \"rgba(0,0,0,0.72)\");\n            ctx.fillStyle = titleGrad;\n            ctx.fillRect(rx, ry, scaledW, scaledH);\n            ctx.globalAlpha = titleAlpha;\n            ctx.fillStyle = \"#ffffff\";\n            ctx.font = `400 ${Math.round(15 * finalScale)}px 'Georgia', serif`;\n            ctx.textAlign = \"left\";\n            ctx.textBaseline = \"bottom\";\n            ctx.fillText(item.title, rx + 14 * finalScale, ry + scaledH - 14 * finalScale, scaledW - 28 * finalScale);\n          }\n          ctx.restore();\n        });\n      }\n    };\n  });\n\n  const hitTestRef = React.useRef((clientX: number, clientY: number): number => {\n    const canvas = canvasRef.current;\n    if (!canvas) return -1;\n    const rect = canvas.getBoundingClientRect();\n    const W = canvas.clientWidth,\n      H = canvas.clientHeight,\n      sx = rect.width ? W / rect.width : 1,\n      sy = rect.height ? H / rect.height : 1;\n    const mx = (clientX - rect.left) * sx,\n      my = (clientY - rect.top) * sy;\n    const cx = W / 2,\n      cy = H / 2;\n    const { cardWidth, cardHeight, gap, waveStrength, windStrength } = propsRef.current;\n    const count = countRef.current;\n    const slotW = cardWidth + gap,\n      loopW = count * slotW;\n    const scroll = scrollXRef.current,\n      t = timeRef.current;\n    for (let copy = -1; copy <= 1; copy++) {\n      for (let i = count - 1; i >= 0; i--) {\n        const baseX = cx - loopW / 2 + i * slotW + copy * loopW - scroll + gap / 2;\n        const normX = (baseX + cardWidth / 2 - cx) / (W * 0.6);\n        if (Math.abs(normX) > 2.2) continue;\n        const waveY = Math.sin(normX * Math.PI * 0.85) * waveStrength * -1;\n        const p3 = i * 3.07 + copy * 1.51;\n        const swayY = Math.sin(t * 0.00052 + p3) * 3.8 * windStrength;\n        const scale = Math.max(0.68, 1 - Math.abs(normX) * 0.22);\n        const scaledW = cardWidth * scale,\n          scaledH = cardHeight * scale;\n        const cardCX = baseX + cardWidth / 2,\n          cardCY = cy + waveY + swayY;\n        if (mx >= cardCX - scaledW / 2 && mx <= cardCX + scaledW / 2 && my >= cardCY - scaledH / 2 && my <= cardCY + scaledH / 2) return i;\n      }\n    }\n    return -1;\n  });\n\n  React.useEffect(() => {\n    const tick = (time: number) => {\n      const dt = Math.min(time - lastTimeRef.current, 32);\n      lastTimeRef.current = time;\n      timeRef.current = time;\n      // Per-frame easing, scaled by elapsed time (k = 60 Hz frames) so it feels the same on 60 Hz\n      // and 120 Hz screens.\n      const k = dt / (1000 / 60);\n      const ease = (a: number) => 1 - Math.pow(1 - a, k);\n      smoothMouseRef.current.x += (rawMouseRef.current.x - smoothMouseRef.current.x) * ease(0.06);\n      smoothMouseRef.current.y += (rawMouseRef.current.y - smoothMouseRef.current.y) * ease(0.06);\n      const { cardWidth, gap, autoPlaySpeed } = propsRef.current;\n      const { autoPlay, autoPlayDir } = activeRef.current;\n      const count = countRef.current;\n      const loopW = count * (cardWidth + gap);\n      if (!isDraggingRef.current) {\n        if (autoPlay) {\n          const dir = autoPlayDir === \"left\" ? -1 : 1;\n          targetXRef.current += dir * autoPlaySpeed * dt * 0.05;\n        }\n        if (Math.abs(velocityRef.current) > 0.1) {\n          targetXRef.current += velocityRef.current * k;\n          velocityRef.current *= Math.pow(0.92, k);\n        }\n      }\n      if (loopW > 0) {\n        while (targetXRef.current > loopW) targetXRef.current -= loopW;\n        while (targetXRef.current < 0) targetXRef.current += loopW;\n        let diff = targetXRef.current - scrollXRef.current;\n        if (diff > loopW / 2) diff -= loopW;\n        if (diff < -loopW / 2) diff += loopW;\n        scrollXRef.current += diff * ease(0.09);\n        if (scrollXRef.current > loopW) scrollXRef.current -= loopW;\n        if (scrollXRef.current < 0) scrollXRef.current += loopW;\n      }\n      drawRef.current();\n      rafRef.current = requestAnimationFrame(tick);\n    };\n    rafRef.current = requestAnimationFrame(tick);\n    return () => {\n      if (rafRef.current) cancelAnimationFrame(rafRef.current);\n    };\n  }, []);\n\n  React.useEffect(() => {\n    const resize = () => {\n      const el = containerRef.current,\n        canvas = canvasRef.current;\n      if (!el || !canvas) return;\n      // Backing store at the device pixel ratio, so the cards are sharp on high-density screens.\n      const dpr = Math.min(window.devicePixelRatio || 1, 2);\n      canvas.width = Math.round(el.clientWidth * dpr);\n      canvas.height = Math.round(el.clientHeight * dpr);\n    };\n    resize();\n    const ro = new ResizeObserver(resize);\n    if (containerRef.current) ro.observe(containerRef.current);\n    return () => ro.disconnect();\n  }, []);\n\n  React.useEffect(() => {\n    Object.entries(mediaRefs.current).forEach(([key, media]) => {\n      const idx = Number(key);\n      const currentSrc = media instanceof HTMLVideoElement ? media.src : (media as HTMLImageElement).src;\n      if (!items[idx] || items[idx].src !== currentSrc) {\n        if (media instanceof HTMLVideoElement) {\n          media.pause();\n          media.src = \"\";\n        }\n        delete mediaRefs.current[idx];\n      }\n    });\n    const loadedRef: Record<number, boolean> = {};\n    const tryLoad = () => {\n      if (modalItem) return;\n      const canvas = canvasRef.current;\n      if (!canvas) return;\n      const { cardWidth, gap } = propsRef.current;\n      const count = countRef.current;\n      const W = canvas.clientWidth,\n        cx = W / 2;\n      const slotW = cardWidth + gap,\n        loopW = count * slotW;\n      const scroll = scrollXRef.current;\n      items.forEach((item, i) => {\n        if (!item?.src || loadedRef[i] || mediaRefs.current[i]) return;\n        const baseX = cx - loopW / 2 + i * slotW - scroll + gap / 2;\n        if (Math.abs(baseX + cardWidth / 2 - cx) > W * 1.5) return;\n        loadedRef[i] = true;\n        if (item.type === \"video\") {\n          const v = document.createElement(\"video\");\n          v.src = item.src;\n          v.muted = true;\n          v.loop = true;\n          v.playsInline = true;\n          v.autoplay = true;\n          v.play().catch(() => {});\n          mediaRefs.current[i] = v;\n        } else {\n          const img = new Image();\n          img.crossOrigin = \"anonymous\";\n          img.src = item.src;\n          mediaRefs.current[i] = img;\n        }\n      });\n    };\n    tryLoad();\n    const id = setInterval(tryLoad, 500);\n    return () => clearInterval(id);\n  }, [items, modalItem]);\n\n  React.useEffect(() => {\n    const el = containerRef.current;\n    if (!el) return;\n    const onMove = (e: MouseEvent) => {\n      const r = el.getBoundingClientRect();\n      rawMouseRef.current = { x: (e.clientX - r.left) / r.width - 0.5, y: (e.clientY - r.top) / r.height - 0.5 };\n      const idx = hitTestRef.current(e.clientX, e.clientY);\n      if (idx !== hoveredIndexRef.current) {\n        hoveredIndexRef.current = idx;\n        const canvas = canvasRef.current;\n        if (canvas) canvas.style.cursor = idx >= 0 ? \"pointer\" : \"grab\";\n      }\n    };\n    el.addEventListener(\"mousemove\", onMove);\n    return () => el.removeEventListener(\"mousemove\", onMove);\n  }, []);\n\n  React.useEffect(() => {\n    const canvas = canvasRef.current;\n    const el = containerRef.current;\n    if (!canvas || !el) return;\n\n    const onDown = (e: MouseEvent) => {\n      isDraggingRef.current = true;\n      dragStartRef.current = e.clientX;\n      dragScrollRef.current = targetXRef.current;\n      lastDragXRef.current = e.clientX;\n      dragDistRef.current = 0;\n      velocityRef.current = 0;\n      canvas.style.cursor = \"grabbing\";\n    };\n    const onMove = (e: MouseEvent) => {\n      if (!isDraggingRef.current) return;\n      const dx = dragStartRef.current - e.clientX;\n      targetXRef.current = dragScrollRef.current + dx * 1.2;\n      velocityRef.current = (lastDragXRef.current - e.clientX) * 0.6;\n      dragDistRef.current += Math.abs(e.movementX);\n      lastDragXRef.current = e.clientX;\n    };\n    const onUp = (e: MouseEvent) => {\n      isDraggingRef.current = false;\n      canvas.style.cursor = \"grab\";\n      if (dragDistRef.current < 6) {\n        const idx = hitTestRef.current(e.clientX, e.clientY);\n        if (idx >= 0 && itemsRef.current[idx]) setModalItem(itemsRef.current[idx]);\n      }\n    };\n    const onTouchStart = (e: TouchEvent) => {\n      isDraggingRef.current = true;\n      dragStartRef.current = e.touches[0].clientX;\n      dragScrollRef.current = targetXRef.current;\n      lastDragXRef.current = e.touches[0].clientX;\n      dragDistRef.current = 0;\n      velocityRef.current = 0;\n    };\n    const onTouchMove = (e: TouchEvent) => {\n      if (!isDraggingRef.current) return;\n      const dx = dragStartRef.current - e.touches[0].clientX;\n      dragDistRef.current += Math.abs(dx);\n      targetXRef.current = dragScrollRef.current + dx * 1.2;\n      velocityRef.current = (lastDragXRef.current - e.touches[0].clientX) * 0.6;\n      lastDragXRef.current = e.touches[0].clientX;\n    };\n    const onTouchEnd = (e: TouchEvent) => {\n      isDraggingRef.current = false;\n      if (dragDistRef.current < 10) {\n        const t = e.changedTouches[0];\n        const idx = hitTestRef.current(t.clientX, t.clientY);\n        if (idx >= 0 && itemsRef.current[idx]) setModalItem(itemsRef.current[idx]);\n      }\n    };\n    const onLeave = () => {\n      if (isDraggingRef.current) {\n        isDraggingRef.current = false;\n        canvas.style.cursor = \"grab\";\n      }\n    };\n    canvas.addEventListener(\"mousedown\", onDown);\n    el.addEventListener(\"mousemove\", onMove);\n    el.addEventListener(\"mouseup\", onUp);\n    el.addEventListener(\"mouseleave\", onLeave);\n    canvas.addEventListener(\"touchstart\", onTouchStart, { passive: true });\n    canvas.addEventListener(\"touchmove\", onTouchMove, { passive: true });\n    canvas.addEventListener(\"touchend\", onTouchEnd);\n    return () => {\n      canvas.removeEventListener(\"mousedown\", onDown);\n      el.removeEventListener(\"mousemove\", onMove);\n      el.removeEventListener(\"mouseup\", onUp);\n      el.removeEventListener(\"mouseleave\", onLeave);\n      canvas.removeEventListener(\"touchstart\", onTouchStart);\n      canvas.removeEventListener(\"touchmove\", onTouchMove);\n      canvas.removeEventListener(\"touchend\", onTouchEnd);\n    };\n  }, []);\n\n  React.useEffect(() => {\n    const el = containerRef.current;\n    if (!el) return;\n    const onKey = (e: KeyboardEvent) => {\n      if (e.key === \"Escape\") setModalItem(null);\n    };\n    el.addEventListener(\"keydown\", onKey);\n    return () => el.removeEventListener(\"keydown\", onKey);\n  }, []);\n\n  return (\n    <div className={cn(\"flex h-full w-full flex-col\", className)} style={style} {...props}>\n      <style>{`\n        @keyframes ${uid}_modalIn { from { opacity: 0 } to { opacity: 1 } }\n        @keyframes ${uid}_modalCardIn { from { opacity: 0; transform: scale(0.94) translateY(12px) } to { opacity: 1; transform: scale(1) translateY(0) } }\n      `}</style>\n      <div ref={containerRef} tabIndex={0} className=\"relative min-h-0 flex-1 overflow-hidden outline-none\" style={{ background: bgColor }}>\n        <canvas ref={canvasRef} className=\"block h-full w-full\" style={{ cursor: \"grab\" }} />\n\n        {showArrows && (\n          <>\n            <button\n              type=\"button\"\n              aria-label=\"Previous\"\n              onMouseDown={(e) => e.stopPropagation()}\n              onClick={() => {\n                if (!isDraggingRef.current) {\n                  targetXRef.current -= propsRef.current.cardWidth + propsRef.current.gap;\n                  velocityRef.current = 0;\n                }\n              }}\n              className=\"absolute top-1/2 left-4 z-30 flex h-11 w-11 -translate-y-1/2 items-center justify-center rounded-full border border-white/20 bg-black/35 outline-none backdrop-blur-md transition-colors hover:bg-black/60\"\n            >\n              <svg aria-hidden=\"true\" width=\"14\" height=\"14\" viewBox=\"0 0 14 14\" fill=\"none\">\n                <path d=\"M9 2L4 7L9 12\" stroke=\"white\" strokeWidth=\"1.5\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n              </svg>\n            </button>\n            <button\n              type=\"button\"\n              aria-label=\"Next\"\n              onMouseDown={(e) => e.stopPropagation()}\n              onClick={() => {\n                if (!isDraggingRef.current) {\n                  targetXRef.current += propsRef.current.cardWidth + propsRef.current.gap;\n                  velocityRef.current = 0;\n                }\n              }}\n              className=\"absolute top-1/2 right-4 z-30 flex h-11 w-11 -translate-y-1/2 items-center justify-center rounded-full border border-white/20 bg-black/35 outline-none backdrop-blur-md transition-colors hover:bg-black/60\"\n            >\n              <svg width=\"14\" height=\"14\" viewBox=\"0 0 14 14\" fill=\"none\">\n                <path d=\"M5 2L10 7L5 12\" stroke=\"white\" strokeWidth=\"1.5\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n              </svg>\n            </button>\n          </>\n        )}\n\n        {modalItem && (\n          <div\n            role=\"dialog\"\n            aria-modal=\"true\"\n            aria-label={modalItem.title || \"Media preview\"}\n            onClick={() => setModalItem(null)}\n            className=\"absolute inset-0 z-[9999] flex items-center justify-center bg-black/75 p-6 backdrop-blur-2xl\"\n            style={{ animation: `${uid}_modalIn 0.3s cubic-bezier(0.2,0,0,1) both` }}\n          >\n            <div\n              onClick={(e) => e.stopPropagation()}\n              className=\"w-full max-w-[520px] overflow-hidden rounded-2xl border border-white/10\"\n              style={{ background: \"rgba(18,18,18,0.95)\", animation: `${uid}_modalCardIn 0.35s cubic-bezier(0.2,0,0,1) both` }}\n            >\n              <div className=\"relative w-full overflow-hidden bg-[#111]\" style={{ paddingTop: \"62%\" }}>\n                {modalItem.type === \"video\" ? (\n                  <video src={modalItem.src} autoPlay muted loop playsInline className=\"absolute inset-0 h-full w-full object-cover\" />\n                ) : (\n                  // eslint-disable-next-line @next/next/no-img-element\n                  <img src={modalItem.src} alt={modalItem.title || \"\"} className=\"absolute inset-0 h-full w-full object-cover\" />\n                )}\n              </div>\n              <div style={{ padding: \"24px 28px 28px\" }}>\n                {modalItem.title && (\n                  <div className=\"mb-2.5 text-[22px] font-normal tracking-[-0.02em] text-white\" style={{ fontFamily: \"'Georgia', serif\" }}>\n                    {modalItem.title}\n                  </div>\n                )}\n                {modalItem.description && (\n                  <div className=\"mb-5 text-[13px] leading-[1.65] text-white/55\">{modalItem.description}</div>\n                )}\n                <div className=\"flex items-center justify-end\">\n                  <button\n                    type=\"button\"\n                    aria-label=\"Close\"\n                    onClick={() => setModalItem(null)}\n                    className=\"flex h-9 w-9 items-center justify-center rounded-full border border-white/12 bg-white/8 outline-none\"\n                  >\n                    <svg aria-hidden=\"true\" width=\"12\" height=\"12\" viewBox=\"0 0 12 12\" fill=\"none\">\n                      <path d=\"M1 1L11 11M11 1L1 11\" stroke=\"white\" strokeWidth=\"1.5\" strokeLinecap=\"round\" />\n                    </svg>\n                  </button>\n                </div>\n              </div>\n            </div>\n          </div>\n        )}\n      </div>\n    </div>\n  );\n}\n",
      "type": "registry:component"
    }
  ],
  "type": "registry:component"
}