{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "iridescence",
  "title": "Iridescence",
  "author": "WebberUI",
  "description": "虹彩偏移波動的著色器背景",
  "dependencies": [
    "motion"
  ],
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "registry/webber/iridescence.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { useReducedMotion } from \"motion/react\";\nimport { cn } from \"@/lib/utils\";\n\n/** RGB 色調（各通道 0~1）——整體乘上這個顏色。白色 = 完整虹彩光譜 */\ntype RGB = [number, number, number];\n\n/** 預設色調：偏冷的青紫，保留大部分虹彩層次又不至於過曝 */\nconst DEFAULT_COLOR: RGB = [0.42, 0.55, 0.95];\n\ninterface IridescenceProps {\n  /** 整體色調（各通道 0~1）；`[1, 1, 1]` 為完整虹彩，越暗越收斂到單一色相 */\n  color?: RGB;\n  /** 波動速度倍率 */\n  speed?: number;\n  /** 滑鼠偏移對圖樣的影響幅度（`mouseReact` 開啟時生效） */\n  amplitude?: number;\n  /** 圖樣是否跟隨滑鼠偏移（僅滑鼠指標生效，觸控不作用） */\n  mouseReact?: boolean;\n  /** 內部取樣邊長（px）。越大越細緻但越吃效能，畫面會拉伸插值到滿版 */\n  detail?: number;\n  /** 前景內容，疊在虹彩之上；佈局（如置中）直接寫在 `className` */\n  children?: React.ReactNode;\n  className?: string;\n}\n\n/**\n * 以 canvas 2D 近似 iridescence 著色器：對降取樣網格逐格計算顏色，\n * 再由 canvas 內建雙線性插值拉伸到滿版，得到平滑的虹彩波動。\n */\nexport function Iridescence({\n  color = DEFAULT_COLOR,\n  speed = 1,\n  amplitude = 0.1,\n  mouseReact = false,\n  detail = 96,\n  children,\n  className,\n}: IridescenceProps) {\n  const reducedMotion = useReducedMotion();\n  const canvasRef = React.useRef<HTMLCanvasElement | null>(null);\n  const containerRef = React.useRef<HTMLDivElement | null>(null);\n  // 游標位置正規化為 0~1，預設置中；用 ref 讓繪製迴圈直接讀取，不觸發 re-render\n  const mouseRef = React.useRef({ x: 0.5, y: 0.5 });\n\n  // 以 ref 保存最新 props，繪製迴圈只建立一次卻能讀到即時值\n  const propsRef = React.useRef({ color, speed, amplitude, mouseReact, detail });\n  propsRef.current = { color, speed, amplitude, mouseReact, detail };\n\n  React.useEffect(() => {\n    const canvas = canvasRef.current;\n    const container = containerRef.current;\n    if (!canvas || !container) return;\n    const ctx = canvas.getContext(\"2d\");\n    if (!ctx) return;\n\n    let width = 1;\n    let height = 1;\n    let image: ImageData | null = null;\n\n    // 依容器長寬比配置降取樣網格，較長邊固定為 detail\n    function resize() {\n      const { detail: d } = propsRef.current;\n      const rect = container!.getBoundingClientRect();\n      const ratio = rect.height > 0 ? rect.width / rect.height : 1;\n      if (ratio >= 1) {\n        width = Math.max(2, Math.round(d));\n        height = Math.max(2, Math.round(d / ratio));\n      } else {\n        height = Math.max(2, Math.round(d));\n        width = Math.max(2, Math.round(d * ratio));\n      }\n      canvas!.width = width;\n      canvas!.height = height;\n      image = ctx!.createImageData(width, height);\n    }\n\n    resize();\n\n    // 逐格計算一幀虹彩，寫入 ImageData\n    function render(time: number) {\n      if (!image) return;\n      const { color: col, speed: spd, amplitude: amp, mouseReact: react } =\n        propsRef.current;\n      const data = image.data;\n      const t = time * 0.001 * spd;\n      const mr = Math.min(width, height);\n      const mouse = react ? mouseRef.current : { x: 0.5, y: 0.5 };\n      const mx = (mouse.x - 0.5) * amp;\n      const my = (mouse.y - 0.5) * amp;\n\n      for (let py = 0; py < height; py++) {\n        // 螢幕座標 y 向下、著色器 y 向上，翻轉一次讓走向與原版一致\n        const ny = (1 - py / (height - 1)) * 2 - 1;\n        const uvy = (ny * height) / mr + my;\n        for (let px = 0; px < width; px++) {\n          const nx = (px / (width - 1)) * 2 - 1;\n          const uvx = (nx * width) / mr + mx;\n\n          let d = -t * 0.5;\n          let a = 0;\n          for (let i = 0; i < 8; i++) {\n            a += Math.cos(i - d - a * uvx);\n            d += Math.sin(uvy * i + a);\n          }\n          d += t * 0.5;\n\n          let r = Math.cos(uvx * d) * 0.6 + 0.4;\n          let g = Math.cos(uvy * a) * 0.6 + 0.4;\n          let b = Math.cos(a + d) * 0.5 + 0.5;\n\n          const cd = Math.cos(d);\n          const ca = Math.cos(a);\n          r = Math.cos(r * cd * 0.5 + 0.5) * col[0];\n          g = Math.cos(g * ca * 0.5 + 0.5) * col[1];\n          b = Math.cos(b * cd * 0.5 + 0.5) * col[2];\n\n          const idx = (py * width + px) * 4;\n          data[idx] = Math.max(0, Math.min(1, r)) * 255;\n          data[idx + 1] = Math.max(0, Math.min(1, g)) * 255;\n          data[idx + 2] = Math.max(0, Math.min(1, b)) * 255;\n          data[idx + 3] = 255;\n        }\n      }\n      ctx!.putImageData(image, 0, 0);\n    }\n\n    // 減少動態效果：只畫一幀靜態虹彩，不啟動迴圈\n    if (reducedMotion) {\n      render(0);\n      return;\n    }\n\n    let raf = 0;\n    function loop(time: number) {\n      render(time);\n      raf = requestAnimationFrame(loop);\n    }\n    raf = requestAnimationFrame(loop);\n\n    const observer = new ResizeObserver(() => resize());\n    observer.observe(container);\n\n    return () => {\n      cancelAnimationFrame(raf);\n      observer.disconnect();\n    };\n  }, [reducedMotion]);\n\n  function handlePointerMove(e: React.PointerEvent<HTMLDivElement>) {\n    if (!mouseReact || e.pointerType !== \"mouse\") return;\n    const rect = e.currentTarget.getBoundingClientRect();\n    mouseRef.current = {\n      x: (e.clientX - rect.left) / rect.width,\n      y: (e.clientY - rect.top) / rect.height,\n    };\n  }\n\n  return (\n    <div\n      ref={containerRef}\n      onPointerMove={mouseReact ? handlePointerMove : undefined}\n      className={cn(\n        \"relative isolate overflow-hidden bg-neutral-950\",\n        className,\n      )}\n    >\n      <canvas\n        ref={canvasRef}\n        aria-hidden\n        className=\"pointer-events-none absolute inset-0 -z-10 h-full w-full\"\n        style={{ imageRendering: \"auto\" }}\n      />\n      {children}\n    </div>\n  );\n}\n",
      "type": "registry:ui"
    }
  ],
  "categories": [
    "background"
  ],
  "type": "registry:ui"
}