{
	"$schema": "https://ui.shadcn.com/schema/registry-item.json",
	"name": "MorphSlider-TS-TW",
	"title": "MorphSlider",
	"description": "WebGL slider that melts between images with a displacement transition.",
	"type": "registry:component",
	"files": [
		{
			"type": "registry:component",
			"path": "MorphSlider/MorphSlider.tsx",
			"content": "import { useEffect, useRef, useState, useCallback } from 'react';\nimport type { CSSProperties } from 'react';\nimport { Renderer, Triangle, Program, Mesh, Texture } from 'ogl';\nimport { gsap } from 'gsap';\n\nexport type MorphTransition = 'melt' | 'ripple' | 'shear' | 'swirl';\n\nexport interface MorphItem {\n  image: string;\n  caption?: string;\n}\n\nexport interface MorphSliderProps {\n  items?: MorphItem[];\n  startIndex?: number;\n  transition?: MorphTransition;\n  duration?: number;\n  ease?: string;\n  intensity?: number;\n  scale?: number;\n  aberration?: number;\n  drift?: number;\n  autoplay?: boolean;\n  autoplayDelay?: number;\n  loop?: boolean;\n  radius?: number;\n  overlayColor?: string;\n  showCaptions?: boolean;\n  showControls?: boolean;\n  showIndicators?: boolean;\n  className?: string;\n  [key: string]: unknown;\n}\n\ninterface EngineOptions {\n  transition: MorphTransition;\n  duration: number;\n  ease: string;\n  intensity: number;\n  scale: number;\n  aberration: number;\n  drift: number;\n  overlayColor: string;\n  loop: boolean;\n}\n\ntype GL = Renderer['gl'];\n\nconst TRANSITIONS: Record<MorphTransition, number> = { melt: 0, ripple: 1, shear: 2, swirl: 3 };\n\nconst DEFAULT_ITEMS: MorphItem[] = [\n  {\n    image: 'https://images.unsplash.com/photo-1782977389500-dd7adad33ebe?q=80&w=1600&auto=format&fit=crop',\n    caption: 'One'\n  },\n  {\n    image: 'https://images.unsplash.com/photo-1781499455083-6ccc3beb20cd?q=80&w=1600&auto=format&fit=crop',\n    caption: 'Two'\n  },\n  {\n    image: 'https://images.unsplash.com/photo-1776394254711-4a0d7345269a?q=80&w=1600&auto=format&fit=crop',\n    caption: 'Three'\n  },\n  {\n    image: 'https://images.unsplash.com/photo-1781242629922-6f39cc3671cd?q=80&w=1600&auto=format&fit=crop',\n    caption: 'Four'\n  }\n];\n\nconst vertexShader = `\nattribute vec2 position;\nattribute vec2 uv;\nvarying vec2 vUv;\nvoid main() {\n  vUv = uv;\n  gl_Position = vec4(position, 0.0, 1.0);\n}\n`;\n\nconst fragmentShader = `\nprecision highp float;\n\nuniform sampler2D tCurrent;\nuniform sampler2D tNext;\nuniform vec2 uResolution;\nuniform vec2 uCurrentSize;\nuniform vec2 uNextSize;\nuniform float uProgress;\nuniform float uDir;\nuniform int uMode;\nuniform float uIntensity;\nuniform float uScale;\nuniform float uAberration;\nuniform float uDrift;\nuniform float uTime;\nuniform float uReduce;\nuniform vec2 uPointer;\nuniform vec3 uOverlay;\n\nvarying vec2 vUv;\n\nconst float PI = 3.14159265359;\n\nfloat hash11(float p) {\n  p = fract(p * 0.1031);\n  p *= p + 33.33;\n  p *= p + p;\n  return fract(p);\n}\n\nfloat hash21(vec2 p) {\n  vec3 p3 = fract(vec3(p.xyx) * 0.1031);\n  p3 += dot(p3, p3.yzx + 33.33);\n  return fract((p3.x + p3.y) * p3.z);\n}\n\nfloat noise(vec2 p) {\n  vec2 i = floor(p);\n  vec2 f = fract(p);\n  vec2 u = f * f * (3.0 - 2.0 * f);\n  float a = hash21(i);\n  float b = hash21(i + vec2(1.0, 0.0));\n  float c = hash21(i + vec2(0.0, 1.0));\n  float d = hash21(i + vec2(1.0, 1.0));\n  return mix(mix(a, b, u.x), mix(c, d, u.x), u.y);\n}\n\nfloat fbm(vec2 p) {\n  float v = 0.0;\n  float a = 0.5;\n  for (int i = 0; i < 5; i++) {\n    v += a * noise(p);\n    p *= 2.0;\n    a *= 0.5;\n  }\n  return v;\n}\n\nmat2 rot(float a) {\n  float s = sin(a);\n  float c = cos(a);\n  return mat2(c, -s, s, c);\n}\n\nvec2 coverUV(vec2 uv, vec2 res, vec2 img) {\n  float rA = res.x / max(res.y, 1.0);\n  float iA = img.x / max(img.y, 1.0);\n  vec2 s = vec2(1.0);\n  float ratio = rA / max(iA, 0.0001);\n  if (ratio > 1.0) {\n    s.y = 1.0 / ratio;\n  } else {\n    s.x = ratio;\n  }\n  return (uv - 0.5) * s + 0.5;\n}\n\nvoid main() {\n  float p = clamp(uProgress, 0.0, 1.0);\n  float env = sin(p * PI);\n\n  vec2 uv = vUv;\n\n  uv += vec2(sin(uTime * 0.25 + uv.y * 4.0), cos(uTime * 0.22 + uv.x * 4.0)) * uDrift * 0.008;\n  uv = (uv - 0.5) * (1.0 - uDrift * 0.02 * sin(uTime * 0.4)) + 0.5;\n\n  vec2 uvC = uv;\n  vec2 uvN = uv;\n  float m = smoothstep(0.0, 1.0, p);\n\n  if (uReduce < 0.5) {\n    if (uMode == 3) {\n      vec2 c = uv - 0.5;\n      float r = length(c);\n      float ang = env * uIntensity * 3.5 * (1.0 - r);\n      uvC = rot(ang) * c + 0.5;\n      uvN = rot(-ang) * c + 0.5;\n      m = smoothstep(0.0, 1.0, p);\n    } else if (uMode == 1) {\n      float d = distance(uv, uPointer);\n      float ring = p * 1.6;\n      float wave = sin((d - ring) * 30.0) * env;\n      vec2 dir = normalize(uv - uPointer + 1e-4);\n      vec2 disp = dir * wave * uIntensity * 0.25;\n      uvC = uv + disp;\n      uvN = uv + disp * 0.6;\n      m = 1.0 - smoothstep(ring - 0.03, ring + 0.03, d);\n    } else if (uMode == 2) {\n      float slices = 14.0;\n      float row = floor(uv.y * slices);\n      float rnd = hash11(row);\n      vec2 disp = vec2((rnd - 0.5) * env * uIntensity * 0.6, 0.0);\n      uvC = uv + disp;\n      uvN = uv + disp;\n      float localX = uDir > 0.0 ? uv.x : 1.0 - uv.x;\n      float th = p * 1.5 - 0.25 + (rnd - 0.5) * 0.25;\n      m = 1.0 - smoothstep(th - 0.06, th + 0.06, localX);\n    } else {\n      float nn = fbm(uv * uScale + uTime * 0.03);\n      float warp = fbm(uv * uScale * 1.7 - uTime * 0.02);\n      vec2 g = vec2(nn, warp) - 0.5;\n      uvC = uv + g * uIntensity * 0.5 * p;\n      uvN = uv - g * uIntensity * 0.5 * (1.0 - p);\n      m = smoothstep(nn - 0.15, nn + 0.15, p);\n    }\n  }\n\n  vec2 sC = coverUV(uvC, uResolution, uCurrentSize);\n  vec2 sN = coverUV(uvN, uResolution, uNextSize);\n\n  float ca = uReduce < 0.5 ? uAberration * env * 0.03 : 0.0;\n\n  vec3 colC = vec3(\n    texture2D(tCurrent, sC + vec2(ca, 0.0)).r,\n    texture2D(tCurrent, sC).g,\n    texture2D(tCurrent, sC - vec2(ca, 0.0)).b\n  );\n  vec3 colN = vec3(\n    texture2D(tNext, sN + vec2(ca, 0.0)).r,\n    texture2D(tNext, sN).g,\n    texture2D(tNext, sN - vec2(ca, 0.0)).b\n  );\n\n  vec3 col = mix(colC, colN, m);\n\n  float vig = smoothstep(1.25, 0.25, length(uv - 0.5));\n  col = mix(col, uOverlay, (1.0 - vig) * 0.28);\n\n  gl_FragColor = vec4(col, 1.0);\n}\n`;\n\nfunction makeFallbackTexture(gl: GL): Texture {\n  const size = 4;\n  const data = new Uint8Array(size * size * 4);\n  for (let i = 0; i < size * size; i++) {\n    data[i * 4] = 24;\n    data[i * 4 + 1] = 24;\n    data[i * 4 + 2] = 28;\n    data[i * 4 + 3] = 255;\n  }\n  return new Texture(gl, { image: data, width: size, height: size, generateMipmaps: false });\n}\n\nfunction hexToRgb(hex: string): [number, number, number] {\n  let h = (hex || '#000000').replace('#', '');\n  if (h.length === 3) {\n    h = h\n      .split('')\n      .map(c => c + c)\n      .join('');\n  }\n  const n = parseInt(h, 16);\n  return [((n >> 16) & 255) / 255, ((n >> 8) & 255) / 255, (n & 255) / 255];\n}\n\ninterface EngineConfig {\n  items: MorphItem[];\n  startIndex: number;\n  reducedMotion: boolean;\n  getOptions: () => EngineOptions;\n  onIndexChange: (index: number) => void;\n  dprCap: number;\n}\n\nclass MorphEngine {\n  private container: HTMLElement;\n  private items: MorphItem[];\n  private getOptions: () => EngineOptions;\n  private onIndexChange: (index: number) => void;\n  private reducedMotion: boolean;\n\n  private current: number;\n  private animating = false;\n  private dragging = false;\n  private dragDir = 0;\n  private shownIndex: number;\n  private tween: gsap.core.Tween | null = null;\n\n  private renderer: Renderer;\n  private gl: GL;\n  private canvas: HTMLCanvasElement;\n  private geometry: Triangle;\n  private program: Program;\n  private mesh: Mesh;\n  private textures: Texture[];\n  private sizes: [number, number][];\n  private resizeObserver: ResizeObserver;\n  private raf = 0;\n  private boundLoop: (t: number) => void;\n  private boundContextLost: (e: Event) => void;\n\n  constructor(container: HTMLElement, config: EngineConfig) {\n    this.container = container;\n    this.items = config.items;\n    this.getOptions = config.getOptions;\n    this.onIndexChange = config.onIndexChange;\n    this.reducedMotion = config.reducedMotion;\n    this.current = config.startIndex;\n    this.shownIndex = config.startIndex;\n\n    this.renderer = new Renderer({\n      alpha: false,\n      antialias: true,\n      dpr: Math.min(window.devicePixelRatio || 1, config.dprCap)\n    });\n    this.gl = this.renderer.gl;\n    this.gl.clearColor(0.05, 0.05, 0.06, 1);\n\n    this.canvas = this.gl.canvas as HTMLCanvasElement;\n    this.canvas.className = 'block w-full h-full';\n    container.appendChild(this.canvas);\n\n    this.geometry = new Triangle(this.gl);\n\n    this.textures = this.items.map(() => makeFallbackTexture(this.gl));\n    this.sizes = this.items.map(() => [1, 1] as [number, number]);\n\n    const opts = this.getOptions();\n    this.program = new Program(this.gl, {\n      vertex: vertexShader,\n      fragment: fragmentShader,\n      uniforms: {\n        tCurrent: { value: this.textures[this.current] },\n        tNext: { value: this.textures[this.current] },\n        uResolution: { value: [1, 1] },\n        uCurrentSize: { value: this.sizes[this.current] },\n        uNextSize: { value: this.sizes[this.current] },\n        uProgress: { value: 0 },\n        uDir: { value: 1 },\n        uMode: { value: TRANSITIONS[opts.transition] ?? 0 },\n        uIntensity: { value: opts.intensity },\n        uScale: { value: opts.scale },\n        uAberration: { value: opts.aberration },\n        uDrift: { value: opts.drift },\n        uTime: { value: 0 },\n        uReduce: { value: this.reducedMotion ? 1 : 0 },\n        uPointer: { value: [0.5, 0.5] },\n        uOverlay: { value: hexToRgb(opts.overlayColor) }\n      }\n    });\n\n    this.mesh = new Mesh(this.gl, { geometry: this.geometry, program: this.program });\n\n    this.boundContextLost = this.onContextLost.bind(this);\n    this.canvas.addEventListener('webglcontextlost', this.boundContextLost, false);\n\n    this.resizeObserver = new ResizeObserver(() => this.resize());\n    this.resizeObserver.observe(container);\n    this.resize();\n\n    this.loadTextures();\n\n    this.boundLoop = this.loop.bind(this);\n    this.raf = requestAnimationFrame(this.boundLoop);\n  }\n\n  private loadTextures(): void {\n    this.items.forEach((item, index) => {\n      const img = new Image();\n      img.crossOrigin = 'anonymous';\n      img.src = item.image;\n      img.onload = () => {\n        const texture = new Texture(this.gl, { generateMipmaps: false });\n        texture.image = img;\n        this.textures[index] = texture;\n        this.sizes[index] = [img.naturalWidth || 1, img.naturalHeight || 1];\n        if (index === this.current) {\n          this.program.uniforms.tCurrent.value = texture;\n          this.program.uniforms.uCurrentSize.value = this.sizes[index];\n        }\n      };\n      img.onerror = () => {};\n    });\n  }\n\n  private resize(): void {\n    const rect = this.container.getBoundingClientRect();\n    const w = Math.max(rect.width, 1);\n    const h = Math.max(rect.height, 1);\n    this.renderer.setSize(w, h);\n    this.program.uniforms.uResolution.value = [this.gl.canvas.width, this.gl.canvas.height];\n  }\n\n  private syncOptions(): void {\n    const opts = this.getOptions();\n    this.program.uniforms.uMode.value = TRANSITIONS[opts.transition] ?? 0;\n    this.program.uniforms.uIntensity.value = opts.intensity;\n    this.program.uniforms.uScale.value = opts.scale;\n    this.program.uniforms.uAberration.value = opts.aberration;\n    this.program.uniforms.uDrift.value = opts.drift;\n    this.program.uniforms.uOverlay.value = hexToRgb(opts.overlayColor);\n  }\n\n  private loop(t: number): void {\n    this.program.uniforms.uTime.value = t * 0.001;\n    if (!this.dragging && !this.animating) this.syncOptions();\n    this.renderer.render({ scene: this.mesh });\n    this.raf = requestAnimationFrame(this.boundLoop);\n  }\n\n  private wrap(i: number): number {\n    const n = this.items.length;\n    return ((i % n) + n) % n;\n  }\n\n  private prepareNext(dir: number): number {\n    const target = this.wrap(this.current + dir);\n    this.program.uniforms.tCurrent.value = this.textures[this.current];\n    this.program.uniforms.uCurrentSize.value = this.sizes[this.current];\n    this.program.uniforms.tNext.value = this.textures[target];\n    this.program.uniforms.uNextSize.value = this.sizes[target];\n    this.program.uniforms.uDir.value = dir;\n    return target;\n  }\n\n  goTo(dir: number): void {\n    if (this.animating || this.dragging || this.items.length < 2) return;\n    const opts = this.getOptions();\n    if (!opts.loop) {\n      const raw = this.current + dir;\n      if (raw < 0 || raw > this.items.length - 1) return;\n    }\n    this.syncOptions();\n    const target = this.prepareNext(dir);\n    this.animating = true;\n    this.announce(target);\n    const duration = this.reducedMotion ? Math.min(opts.duration, 0.4) : opts.duration;\n    this.tween = gsap.fromTo(\n      this.program.uniforms.uProgress,\n      { value: 0 },\n      {\n        value: 1,\n        duration,\n        ease: opts.ease,\n        onComplete: () => this.commit(target)\n      }\n    );\n  }\n\n  private announce(index: number): void {\n    if (index === this.shownIndex) return;\n    this.shownIndex = index;\n    this.onIndexChange(index);\n  }\n\n  private commit(target: number): void {\n    this.current = target;\n    this.program.uniforms.tCurrent.value = this.textures[target];\n    this.program.uniforms.uCurrentSize.value = this.sizes[target];\n    this.program.uniforms.uProgress.value = 0;\n    this.animating = false;\n    this.tween = null;\n    this.announce(target);\n  }\n\n  next(): void {\n    this.goTo(1);\n  }\n\n  prev(): void {\n    this.goTo(-1);\n  }\n\n  setPointer(x: number, y: number): void {\n    this.program.uniforms.uPointer.value = [x, y];\n  }\n\n  beginDrag(): boolean {\n    if (this.animating || this.items.length < 2) return false;\n    this.dragging = true;\n    this.dragDir = 0;\n    this.syncOptions();\n    return true;\n  }\n\n  drag(ndx: number): void {\n    if (!this.dragging) return;\n    const opts = this.getOptions();\n    const dir = ndx < 0 ? 1 : -1;\n    if (!opts.loop) {\n      const raw = this.current + dir;\n      if (raw < 0 || raw > this.items.length - 1) {\n        this.program.uniforms.uProgress.value = 0;\n        return;\n      }\n    }\n    if (dir !== this.dragDir) {\n      this.dragDir = dir;\n      this.prepareNext(dir);\n    }\n    const progress = Math.min(Math.abs(ndx), 1);\n    this.program.uniforms.uProgress.value = progress;\n    this.announce(progress > 0.5 ? this.wrap(this.current + dir) : this.current);\n  }\n\n  endDrag(): void {\n    if (!this.dragging) return;\n    this.dragging = false;\n    const p = this.program.uniforms.uProgress.value as number;\n    if (this.dragDir === 0) return;\n    const target = this.wrap(this.current + this.dragDir);\n    const duration = this.reducedMotion ? 0.3 : 0.5;\n    this.animating = true;\n    if (p > 0.4) {\n      this.announce(target);\n      this.tween = gsap.to(this.program.uniforms.uProgress, {\n        value: 1,\n        duration,\n        ease: 'power2.out',\n        onComplete: () => this.commit(target)\n      });\n    } else {\n      this.announce(this.current);\n      this.tween = gsap.to(this.program.uniforms.uProgress, {\n        value: 0,\n        duration,\n        ease: 'power2.out',\n        onComplete: () => {\n          this.animating = false;\n          this.tween = null;\n        }\n      });\n    }\n  }\n\n  private onContextLost(e: Event): void {\n    e.preventDefault();\n    cancelAnimationFrame(this.raf);\n  }\n\n  destroy(): void {\n    cancelAnimationFrame(this.raf);\n    if (this.tween) this.tween.kill();\n    this.resizeObserver.disconnect();\n    this.canvas.removeEventListener('webglcontextlost', this.boundContextLost);\n    this.textures.forEach(tex => {\n      if (tex && tex.texture) this.gl.deleteTexture(tex.texture);\n    });\n    if (this.program && this.program.program) this.gl.deleteProgram(this.program.program);\n    const ext = this.gl.getExtension('WEBGL_lose_context');\n    if (ext) ext.loseContext();\n    if (this.canvas.parentNode) this.canvas.parentNode.removeChild(this.canvas);\n  }\n}\n\nexport default function MorphSlider({\n  items = DEFAULT_ITEMS,\n  startIndex = 0,\n  transition = 'melt',\n  duration = 1.1,\n  ease = 'power2.inOut',\n  intensity = 0.55,\n  scale = 2.4,\n  aberration = 0.35,\n  drift = 0.4,\n  autoplay = false,\n  autoplayDelay = 4,\n  loop = true,\n  radius = 16,\n  overlayColor = '#000000',\n  showCaptions = true,\n  showControls = true,\n  showIndicators = true,\n  className = '',\n  ...props\n}: MorphSliderProps) {\n  const containerRef = useRef<HTMLDivElement>(null);\n  const engineRef = useRef<MorphEngine | null>(null);\n  const [index, setIndex] = useState(startIndex);\n  const [hovering, setHovering] = useState(false);\n\n  const optsRef = useRef<EngineOptions>({\n    transition,\n    duration,\n    ease,\n    intensity,\n    scale,\n    aberration,\n    drift,\n    overlayColor,\n    loop\n  });\n  optsRef.current = { transition, duration, ease, intensity, scale, aberration, drift, overlayColor, loop };\n\n  useEffect(() => {\n    if (!containerRef.current) return undefined;\n    const reducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;\n\n    const engine = new MorphEngine(containerRef.current, {\n      items,\n      startIndex,\n      reducedMotion,\n      dprCap: 2,\n      getOptions: () => optsRef.current,\n      onIndexChange: setIndex\n    });\n    engineRef.current = engine;\n    setIndex(startIndex);\n\n    return () => {\n      engine.destroy();\n      engineRef.current = null;\n    };\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, [items, startIndex]);\n\n  const handleNext = useCallback(() => engineRef.current?.next(), []);\n  const handlePrev = useCallback(() => engineRef.current?.prev(), []);\n\n  useEffect(() => {\n    if (!autoplay || hovering) return undefined;\n    const id = window.setTimeout(() => engineRef.current?.next(), Math.max(autoplayDelay, 1) * 1000);\n    return () => window.clearTimeout(id);\n  }, [autoplay, autoplayDelay, hovering, index]);\n\n  useEffect(() => {\n    const el = containerRef.current;\n    if (!el) return undefined;\n    let startX = 0;\n    let width = 1;\n    let active = false;\n\n    const onDown = (e: PointerEvent) => {\n      const rect = el.getBoundingClientRect();\n      width = rect.width || 1;\n      startX = e.clientX;\n      const px = (e.clientX - rect.left) / rect.width;\n      const py = (e.clientY - rect.top) / rect.height;\n      engineRef.current?.setPointer(px, 1 - py);\n      active = engineRef.current?.beginDrag() ?? false;\n      if (active && el.setPointerCapture) {\n        try {\n          el.setPointerCapture(e.pointerId);\n        } catch {}\n      }\n    };\n    const onMove = (e: PointerEvent) => {\n      if (!active) return;\n      const ndx = (e.clientX - startX) / width;\n      engineRef.current?.drag(ndx);\n    };\n    const onUp = () => {\n      if (!active) return;\n      active = false;\n      engineRef.current?.endDrag();\n    };\n\n    el.addEventListener('pointerdown', onDown);\n    el.addEventListener('pointermove', onMove);\n    el.addEventListener('pointerup', onUp);\n    el.addEventListener('pointercancel', onUp);\n\n    return () => {\n      el.removeEventListener('pointerdown', onDown);\n      el.removeEventListener('pointermove', onMove);\n      el.removeEventListener('pointerup', onUp);\n      el.removeEventListener('pointercancel', onUp);\n    };\n  }, []);\n\n  const onKeyDown = useCallback(\n    (e: React.KeyboardEvent<HTMLDivElement>) => {\n      if (e.key === 'ArrowRight') {\n        e.preventDefault();\n        handleNext();\n      } else if (e.key === 'ArrowLeft') {\n        e.preventDefault();\n        handlePrev();\n      }\n    },\n    [handleNext, handlePrev]\n  );\n\n  const hasCaptions = items.some(item => item.caption);\n\n  return (\n    <div\n      className={`relative w-full h-full overflow-hidden select-none bg-[#0c0c0e] ${className}`.trim()}\n      style={\n        {\n          borderRadius: `${radius}px`,\n          '--ms-swap': `${(duration * 0.66).toFixed(3)}s`,\n          '--ms-dot': `${(duration * 0.45).toFixed(3)}s`,\n          touchAction: 'pan-y'\n        } as CSSProperties\n      }\n      onMouseEnter={() => setHovering(true)}\n      onMouseLeave={() => setHovering(false)}\n      {...props}\n    >\n      <div\n        ref={containerRef}\n        className=\"absolute inset-0 cursor-grab active:cursor-grabbing outline-none focus-visible:shadow-[inset_0_0_0_2px_rgba(255,255,255,0.7)]\"\n        role=\"group\"\n        aria-roledescription=\"carousel\"\n        aria-label=\"Image morph slider\"\n        tabIndex={0}\n        onKeyDown={onKeyDown}\n      />\n\n      {showCaptions && hasCaptions && (\n        <div\n          className=\"morph-slider-caption pointer-events-none absolute bottom-[22px] left-[22px] z-[2] grid max-w-[70%]\"\n          aria-live=\"polite\"\n        >\n          {items.map((item, i) =>\n            item.caption ? (\n              <span\n                key={i}\n                aria-hidden={i === index ? undefined : true}\n                className={`morph-slider-caption-text pointer-events-none inline-block rounded-[10px] bg-[rgba(10,10,12,0.42)] px-[14px] py-[8px] text-[15px] font-semibold tracking-[0.01em] text-white backdrop-blur-[8px] [grid-area:1/1] [justify-self:start] [transition:opacity_var(--ms-swap)_cubic-bezier(0.16,1,0.3,1),transform_var(--ms-swap)_cubic-bezier(0.16,1,0.3,1),filter_var(--ms-swap)_cubic-bezier(0.16,1,0.3,1)] ${\n                  i === index\n                    ? 'opacity-100 [transform:translateY(0)] [filter:blur(0)]'\n                    : 'opacity-0 [transform:translateY(12px)] [filter:blur(6px)]'\n                }`}\n              >\n                {item.caption}\n              </span>\n            ) : null\n          )}\n        </div>\n      )}\n\n      {showControls && (\n        <div className=\"absolute top-1/2 left-0 right-0 z-[3] flex justify-between px-4 -translate-y-1/2 pointer-events-none\">\n          <button\n            type=\"button\"\n            className=\"pointer-events-auto inline-flex items-center justify-center w-10 h-10 rounded-full text-white border border-white/20 bg-[rgba(12,12,14,0.4)] backdrop-blur-md cursor-pointer transition-transform duration-200 hover:scale-105 hover:bg-[rgba(24,24,28,0.6)] active:scale-95 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-white/80\"\n            aria-label=\"Previous slide\"\n            onClick={handlePrev}\n          >\n            <svg viewBox=\"0 0 24 24\" width=\"18\" height=\"18\" aria-hidden=\"true\">\n              <path\n                d=\"M15 5l-7 7 7 7\"\n                fill=\"none\"\n                stroke=\"currentColor\"\n                strokeWidth=\"2\"\n                strokeLinecap=\"round\"\n                strokeLinejoin=\"round\"\n              />\n            </svg>\n          </button>\n          <button\n            type=\"button\"\n            className=\"pointer-events-auto inline-flex items-center justify-center w-10 h-10 rounded-full text-white border border-white/20 bg-[rgba(12,12,14,0.4)] backdrop-blur-md cursor-pointer transition-transform duration-200 hover:scale-105 hover:bg-[rgba(24,24,28,0.6)] active:scale-95 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-white/80\"\n            aria-label=\"Next slide\"\n            onClick={handleNext}\n          >\n            <svg viewBox=\"0 0 24 24\" width=\"18\" height=\"18\" aria-hidden=\"true\">\n              <path\n                d=\"M9 5l7 7-7 7\"\n                fill=\"none\"\n                stroke=\"currentColor\"\n                strokeWidth=\"2\"\n                strokeLinecap=\"round\"\n                strokeLinejoin=\"round\"\n              />\n            </svg>\n          </button>\n        </div>\n      )}\n\n      {showIndicators && (\n        <div\n          className=\"absolute left-0 right-0 bottom-[18px] z-[3] flex gap-2 justify-center items-center\"\n          role=\"tablist\"\n          aria-label=\"Slides\"\n        >\n          {items.map((item, i) => (\n            <button\n              key={i}\n              type=\"button\"\n              role=\"tab\"\n              aria-selected={i === index}\n              aria-label={`Go to slide ${i + 1}`}\n              className={`h-2 rounded-full cursor-pointer [transition:width_var(--ms-dot)_cubic-bezier(0.16,1,0.3,1),background-color_var(--ms-dot)_ease] focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-white/80 ${\n                i === index ? 'w-[22px] bg-white/95' : 'w-2 bg-white/35'\n              }`}\n              onClick={() => {\n                const engine = engineRef.current;\n                if (!engine || i === index) return;\n                engine.goTo(i > index ? 1 : -1);\n              }}\n            />\n          ))}\n        </div>\n      )}\n    </div>\n  );\n}\n"
		}
	],
	"registryDependencies": [],
	"dependencies": [
		"ogl@^1.0.11",
		"gsap@^3.13.0"
	]
}