{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "aurora-background",
  "title": "Aurora Background",
  "author": "WebberUI",
  "description": "極光流動背景：大型模糊色塊以 transform 緩慢漂移，可選噪點疊層與滑鼠跟隨。",
  "dependencies": [
    "motion"
  ],
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "registry/webber/aurora-background.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport {\n  motion,\n  useMotionValue,\n  useReducedMotion,\n  useSpring,\n  useTransform,\n  type MotionValue,\n  type SpringOptions,\n} from \"motion/react\";\nimport { cn } from \"@/lib/utils\";\n\n/** 預設冷色系極光配色：青 → 靛 → 翠綠 */\nconst DEFAULT_COLORS = [\"#22d3ee\", \"#818cf8\", \"#34d399\"];\n\n/** 滑鼠跟隨的 spring：低剛性、緩慢收斂，讓色塊像浮在水面上 */\nconst FOLLOW_SPRING: SpringOptions = {\n  stiffness: 50,\n  damping: 20,\n  mass: 1.2,\n};\n\n/** 噪點疊層：feTurbulence SVG 內嵌為 data URI，不需額外網路請求 */\nconst GRAIN_URL = `url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.8' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E\")`;\n\ninterface BlobConfig {\n  /** 初始定位與尺寸——只負責擺位，位移一律交給 transform */\n  className: string;\n  /** 漂移目標（px 與 scale），`repeatType: \"mirror\"` 會在原點與目標間往返 */\n  drift: { x: number; y: number; scale: number };\n  /** 各自不同的漂移週期（秒），避免三塊同步呼吸 */\n  duration: number;\n  /** 互動模式下跟隨滑鼠的幅度（px），正負相間形成視差 */\n  depth: number;\n}\n\nconst BLOBS: BlobConfig[] = [\n  {\n    className: \"left-[-12%] top-[-25%] h-[75%] w-[55%]\",\n    drift: { x: 60, y: 45, scale: 1.15 },\n    duration: 17,\n    depth: 14,\n  },\n  {\n    className: \"right-[-15%] top-[5%] h-[85%] w-[60%]\",\n    drift: { x: -70, y: 35, scale: 1.1 },\n    duration: 23,\n    depth: -10,\n  },\n  {\n    className: \"bottom-[-30%] left-[18%] h-[75%] w-[65%]\",\n    drift: { x: 55, y: -50, scale: 1.2 },\n    duration: 19,\n    depth: 8,\n  },\n];\n\ninterface AuroraBlobProps {\n  config: BlobConfig;\n  color: string;\n  isStatic: boolean;\n  followX: MotionValue<number>;\n  followY: MotionValue<number>;\n}\n\nfunction AuroraBlob({\n  config,\n  color,\n  isStatic,\n  followX,\n  followY,\n}: AuroraBlobProps) {\n  // 外層負責滑鼠跟隨、內層負責漂移迴圈，兩組 transform 互不干擾\n  const x = useTransform(followX, (v) => v * config.depth);\n  const y = useTransform(followY, (v) => v * config.depth);\n\n  return (\n    <motion.div\n      style={{ x, y }}\n      className={cn(\n        \"absolute opacity-60 dark:opacity-45\",\n        config.className,\n      )}\n    >\n      <motion.div\n        className=\"h-full w-full rounded-full blur-3xl\"\n        style={{\n          background: `radial-gradient(ellipse at center, ${color} 0%, transparent 70%)`,\n        }}\n        animate={isStatic ? { x: 0, y: 0, scale: 1 } : config.drift}\n        transition={\n          isStatic\n            ? { duration: 0 }\n            : {\n                duration: config.duration,\n                repeat: Infinity,\n                repeatType: \"mirror\",\n                ease: \"easeInOut\",\n              }\n        }\n      />\n    </motion.div>\n  );\n}\n\ninterface AuroraBackgroundProps {\n  /** 三個極光漸層色，依序對應三個色塊；預設冷色系極光配色 */\n  colors?: string[];\n  /** 色塊輕微跟隨滑鼠（僅滑鼠指標生效，觸控不作用） */\n  interactive?: boolean;\n  /** 疊加低透明度噪點，減少大面積漸層的色帶感 */\n  grain?: boolean;\n  /** 前景內容，疊在極光之上；佈局（如置中）直接寫在 `className` */\n  children?: React.ReactNode;\n  className?: string;\n}\n\nexport function AuroraBackground({\n  colors = DEFAULT_COLORS,\n  interactive = false,\n  grain = false,\n  children,\n  className,\n}: AuroraBackgroundProps) {\n  const reducedMotion = useReducedMotion();\n  // 首次渲染必須與 SSR 輸出一致，掛載後才依 reduced-motion 切換靜態版\n  const [mounted, setMounted] = React.useState(false);\n  React.useEffect(() => setMounted(true), []);\n  const isStatic = Boolean(mounted && reducedMotion);\n\n  // 游標位置正規化為 -1 ~ 1，各色塊再乘上自己的 depth 幅度\n  const pointerX = useMotionValue(0);\n  const pointerY = useMotionValue(0);\n  const followX = useSpring(pointerX, FOLLOW_SPRING);\n  const followY = useSpring(pointerY, FOLLOW_SPRING);\n\n  function handlePointerMove(e: React.PointerEvent<HTMLDivElement>) {\n    if (isStatic || e.pointerType !== \"mouse\") return;\n    const rect = e.currentTarget.getBoundingClientRect();\n    pointerX.set(((e.clientX - rect.left) / rect.width) * 2 - 1);\n    pointerY.set(((e.clientY - rect.top) / rect.height) * 2 - 1);\n  }\n\n  function handlePointerLeave() {\n    pointerX.set(0);\n    pointerY.set(0);\n  }\n\n  return (\n    <div\n      onPointerMove={interactive ? handlePointerMove : undefined}\n      onPointerLeave={interactive ? handlePointerLeave : undefined}\n      className={cn(\n        \"relative isolate overflow-hidden bg-neutral-50 dark:bg-neutral-950\",\n        className,\n      )}\n    >\n      <div aria-hidden className=\"pointer-events-none absolute inset-0 -z-10\">\n        {BLOBS.map((blob, i) => (\n          <AuroraBlob\n            key={i}\n            config={blob}\n            color={colors[i % colors.length] ?? DEFAULT_COLORS[i]}\n            isStatic={isStatic}\n            followX={followX}\n            followY={followY}\n          />\n        ))}\n        {grain && (\n          <div\n            className=\"absolute inset-0 opacity-[0.05] dark:opacity-[0.07]\"\n            style={{ backgroundImage: GRAIN_URL, backgroundSize: \"180px 180px\" }}\n          />\n        )}\n      </div>\n      {children}\n    </div>\n  );\n}\n",
      "type": "registry:ui"
    }
  ],
  "categories": [
    "background"
  ],
  "type": "registry:ui"
}