{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "wave-spinner",
  "type": "registry:ui",
  "title": "Wave Spinner",
  "description": "A beautiful animated loading spinner with wave effects, multiple patterns, colors, and highly customizable animations.",
  "dependencies": [
    "class-variance-authority"
  ],
  "files": [
    {
      "path": "registry/new-york/ui/wave-spinner.tsx",
      "content": "\"use client\";\n\nimport { cn } from \"@/lib/utils\";\nimport { type VariantProps, cva } from \"class-variance-authority\";\nimport type { FC } from \"react\";\nimport \"./wave-spinner.css\";\n\n/**\n * WaveSpinner - A beautiful animated loading spinner with wave effects\n *\n * Features multiple patterns, colors, sizes, and animation styles.\n * Designed to be highly customizable while maintaining visual consistency.\n */\n\n// Animation delay patterns for different visual effects\nconst DELAY_PATTERNS = {\n  /** Diagonal wave from top-left */\n  diagonalTL: [0, 0.12, 0.24, 0.12, 0.24, 0.36, 0.24, 0.36, 0.48],\n  /** Diagonal wave from top-right */\n  diagonalTR: [0.24, 0.12, 0, 0.36, 0.24, 0.12, 0.48, 0.36, 0.24],\n  /** Diagonal wave from bottom-left */\n  diagonalBL: [0.24, 0.36, 0.48, 0.12, 0.24, 0.36, 0, 0.12, 0.24],\n  /** Diagonal wave from bottom-right */\n  diagonalBR: [0.48, 0.36, 0.24, 0.36, 0.24, 0.12, 0.24, 0.12, 0],\n  /** Ripple effect from center */\n  ripple: [0.36, 0.24, 0.36, 0.24, 0, 0.24, 0.36, 0.24, 0.36],\n  /** Horizontal wave */\n  horizontal: [0, 0.12, 0.24, 0, 0.12, 0.24, 0, 0.12, 0.24],\n  /** Vertical wave */\n  vertical: [0, 0, 0, 0.12, 0.12, 0.12, 0.24, 0.24, 0.24],\n  /** Random-like pattern */\n  random: [0.1, 0.3, 0.2, 0.4, 0, 0.35, 0.15, 0.25, 0.45],\n  /** Spiral from center */\n  spiral: [0.4, 0.32, 0.24, 0.48, 0, 0.16, 0.56, 0.64, 0.08],\n} as const;\n\n// Grid configurations for different patterns\nconst GRID_CONFIGS = {\n  /** 3x3 grid (standard) */\n  square3x3: { cols: 3, count: 9 },\n  /** 2x2 grid (minimal) */\n  square2x2: { cols: 2, count: 4 },\n  /** 4x4 grid (detailed) */\n  square4x4: { cols: 4, count: 16 },\n  /** Single row (linear) */\n  line: { cols: 3, count: 3 },\n  /** Diamond pattern */\n  diamond: { cols: 3, count: 5, specialLayout: \"diamond\" as const },\n  /** Cross pattern */\n  cross: { cols: 3, count: 5, specialLayout: \"cross\" as const },\n  /** Circle arrangement */\n  circle: { cols: 1, count: 8, specialLayout: \"circle\" as const },\n} as const;\n\ntype DelayPattern = keyof typeof DELAY_PATTERNS;\ntype GridPattern = keyof typeof GRID_CONFIGS;\n\n// Color presets matching GAIA's design system\nconst COLOR_PRESETS = {\n  /** GAIA primary blue (#00bbff) */\n  primary: \"#00bbff\",\n  /** Success green */\n  success: \"#22c55e\",\n  /** Warning amber */\n  warning: \"#f59e0b\",\n  /** Danger red */\n  danger: \"#ef4444\",\n  /** Muted zinc */\n  muted: \"#71717a\",\n  /** Purple accent */\n  purple: \"#a855f7\",\n  /** Cyan accent */\n  cyan: \"#06b6d4\",\n  /** Rose accent */\n  rose: \"#f43f5e\",\n  /** Indigo accent */\n  indigo: \"#6366f1\",\n  /** Emerald accent */\n  emerald: \"#10b981\",\n} as const;\n\ntype ColorPreset = keyof typeof COLOR_PRESETS;\n\n// CVA variants for the container\nconst waveSpinnerVariants = cva(\"flex items-center justify-center w-fit\", {\n  variants: {\n    size: {\n      xs: \"[--dot-size:3px] [--gap-size:1px]\",\n      sm: \"[--dot-size:4px] [--gap-size:1.5px]\",\n      md: \"[--dot-size:6px] [--gap-size:2px]\",\n      lg: \"[--dot-size:8px] [--gap-size:3px]\",\n      xl: \"[--dot-size:10px] [--gap-size:4px]\",\n    },\n  },\n  defaultVariants: {\n    size: \"md\",\n  },\n});\n\n// Props interface with full documentation\nexport interface WaveSpinnerProps extends VariantProps<\n  typeof waveSpinnerVariants\n> {\n  /** The color of the spinner dots. Can be a preset name or any CSS color value */\n  color?: ColorPreset | (string & {});\n  /** The pattern of the grid layout */\n  pattern?: GridPattern;\n  /** The animation delay pattern for the wave effect */\n  animation?: DelayPattern;\n  /** Animation duration in seconds */\n  duration?: number;\n  /** The shape of individual dots */\n  dotShape?: \"square\" | \"rounded\" | \"circle\";\n  /** Additional className for customization */\n  className?: string;\n  /** Label for accessibility */\n  \"aria-label\"?: string;\n}\n\n// Helper to get delay pattern for various grid sizes\nconst getDelayForIndex = (\n  index: number,\n  animation: DelayPattern,\n  gridConfig: (typeof GRID_CONFIGS)[GridPattern],\n): number => {\n  const pattern = DELAY_PATTERNS[animation];\n\n  // Special layouts use their own delay patterns\n  if (\"specialLayout\" in gridConfig) {\n    const count = gridConfig.count;\n    // Create a proportional delay based on position\n    const normalizedIndex = index / (count - 1);\n    const patternIndex = Math.floor(normalizedIndex * (pattern.length - 1));\n    return pattern[patternIndex] ?? 0;\n  }\n\n  // For larger grids, interpolate\n  if (gridConfig.count > 9) {\n    const gridSize = Math.sqrt(gridConfig.count);\n    const row = Math.floor(index / gridSize);\n    const col = index % gridSize;\n    // Map to 3x3 equivalent position\n    const mappedRow = Math.floor((row / gridSize) * 3);\n    const mappedCol = Math.floor((col / gridSize) * 3);\n    const mappedIndex = mappedRow * 3 + mappedCol;\n    return pattern[Math.min(mappedIndex, pattern.length - 1)] ?? 0;\n  }\n\n  // For smaller grids, use direct mapping\n  if (gridConfig.count === 4) {\n    // 2x2 mapping: 0,1,3,4 of 3x3\n    const mapping = [0, 2, 6, 8];\n    return pattern[mapping[index] ?? 0] ?? 0;\n  }\n\n  // For 3 dots (line), use first row\n  if (gridConfig.count === 3) {\n    return pattern[index] ?? 0;\n  }\n\n  return pattern[index % pattern.length] ?? 0;\n};\n\n// Component to render special layouts\nconst SpecialLayoutDots: FC<{\n  config: (typeof GRID_CONFIGS)[GridPattern];\n  color: string;\n  dotShape: string;\n  animation: DelayPattern;\n  duration: number;\n}> = ({ config, color, dotShape, animation, duration }) => {\n  if (!(\"specialLayout\" in config)) return null;\n\n  const borderRadius =\n    dotShape === \"circle\" ? \"50%\" : dotShape === \"rounded\" ? \"30%\" : \"0\";\n\n  if (config.specialLayout === \"diamond\") {\n    // Diamond pattern: center + 4 corners arranged as diamond\n    const positions = [\n      { top: \"0%\", left: \"50%\", transform: \"translateX(-50%)\" },\n      { top: \"50%\", left: \"0%\", transform: \"translateY(-50%)\" },\n      { top: \"50%\", left: \"50%\", transform: \"translate(-50%, -50%)\" },\n      { top: \"50%\", left: \"100%\", transform: \"translate(-100%, -50%)\" },\n      { top: \"100%\", left: \"50%\", transform: \"translate(-50%, -100%)\" },\n    ];\n\n    return (\n      <div\n        className=\"relative\"\n        style={{\n          width: \"calc(var(--dot-size) * 3 + var(--gap-size) * 2)\",\n          height: \"calc(var(--dot-size) * 3 + var(--gap-size) * 2)\",\n        }}\n      >\n        {positions.map((pos, idx) => (\n          <div\n            key={idx}\n            className=\"absolute transition-all\"\n            style={{\n              width: \"var(--dot-size)\",\n              height: \"var(--dot-size)\",\n              backgroundColor: color,\n              borderRadius,\n              animation: `waveSpinnerPulse ${duration}s ease-out infinite`,\n              animationDelay: `${getDelayForIndex(idx, animation, config)}s`,\n              ...pos,\n            }}\n          />\n        ))}\n      </div>\n    );\n  }\n\n  if (config.specialLayout === \"cross\") {\n    // Cross pattern: center + 4 edges\n    const positions = [\n      { top: \"0%\", left: \"50%\", transform: \"translateX(-50%)\" },\n      { top: \"50%\", left: \"0%\", transform: \"translateY(-50%)\" },\n      { top: \"50%\", left: \"50%\", transform: \"translate(-50%, -50%)\" },\n      { top: \"50%\", left: \"100%\", transform: \"translate(-100%, -50%)\" },\n      { top: \"100%\", left: \"50%\", transform: \"translate(-50%, -100%)\" },\n    ];\n\n    return (\n      <div\n        className=\"relative\"\n        style={{\n          width: \"calc(var(--dot-size) * 3 + var(--gap-size) * 2)\",\n          height: \"calc(var(--dot-size) * 3 + var(--gap-size) * 2)\",\n        }}\n      >\n        {positions.map((pos, idx) => (\n          <div\n            key={idx}\n            className=\"absolute transition-all\"\n            style={{\n              width: \"var(--dot-size)\",\n              height: \"var(--dot-size)\",\n              backgroundColor: color,\n              borderRadius,\n              animation: `waveSpinnerPulse ${duration}s ease-out infinite`,\n              animationDelay: `${getDelayForIndex(idx, animation, config)}s`,\n              ...pos,\n            }}\n          />\n        ))}\n      </div>\n    );\n  }\n\n  if (config.specialLayout === \"circle\") {\n    // Circle arrangement: 8 dots in a circle\n    const angleStep = (2 * Math.PI) / config.count;\n    const radius = \"calc(var(--dot-size) * 1.5)\";\n\n    return (\n      <div\n        className=\"relative\"\n        style={{\n          width: \"calc(var(--dot-size) * 4)\",\n          height: \"calc(var(--dot-size) * 4)\",\n        }}\n      >\n        {Array.from({ length: config.count }).map((_, idx) => {\n          const angle = idx * angleStep - Math.PI / 2;\n          const x = Math.cos(angle);\n          const y = Math.sin(angle);\n          return (\n            <div\n              key={idx}\n              className=\"absolute transition-all\"\n              style={{\n                width: \"var(--dot-size)\",\n                height: \"var(--dot-size)\",\n                backgroundColor: color,\n                borderRadius: \"50%\", // Always circular for circle pattern\n                animation: `waveSpinnerPulse ${duration}s ease-out infinite`,\n                animationDelay: `${(idx / config.count) * duration}s`,\n                top: \"50%\",\n                left: \"50%\",\n                transform: `translate(calc(-50% + ${x} * ${radius}), calc(-50% + ${y} * ${radius}))`,\n              }}\n            />\n          );\n        })}\n      </div>\n    );\n  }\n\n  return null;\n};\n\nexport const WaveSpinner: FC<WaveSpinnerProps> = ({\n  color = \"primary\",\n  pattern = \"square3x3\",\n  animation = \"diagonalTL\",\n  duration = 0.7,\n  dotShape = \"square\",\n  size,\n  className,\n  \"aria-label\": ariaLabel = \"Loading\",\n}) => {\n  // Resolve color from preset or use as-is\n  const resolvedColor =\n    color in COLOR_PRESETS ? COLOR_PRESETS[color as ColorPreset] : color;\n\n  const gridConfig = GRID_CONFIGS[pattern];\n  const borderRadius =\n    dotShape === \"circle\" ? \"50%\" : dotShape === \"rounded\" ? \"30%\" : \"0\";\n\n  // Check if this is a special layout\n  const isSpecialLayout = \"specialLayout\" in gridConfig;\n\n  return (\n    <div\n      className={cn(waveSpinnerVariants({ size }), className)}\n      role=\"status\"\n      aria-label={ariaLabel}\n    >\n      <div className=\"relative flex items-center justify-center\">\n        {isSpecialLayout ? (\n          <SpecialLayoutDots\n            config={gridConfig}\n            color={resolvedColor}\n            dotShape={dotShape}\n            animation={animation}\n            duration={duration}\n          />\n        ) : (\n          <div\n            className=\"relative grid\"\n            style={{\n              gridTemplateColumns: `repeat(${gridConfig.cols}, var(--dot-size))`,\n              gap: \"var(--gap-size)\",\n            }}\n          >\n            {Array.from({ length: gridConfig.count }).map((_, idx) => (\n              <div\n                key={idx}\n                className=\"transition-all\"\n                style={{\n                  width: \"var(--dot-size)\",\n                  height: \"var(--dot-size)\",\n                  backgroundColor: resolvedColor,\n                  borderRadius,\n                  animation: `waveSpinnerPulse ${duration}s ease-out infinite`,\n                  animationDelay: `${getDelayForIndex(idx, animation, gridConfig)}s`,\n                }}\n              />\n            ))}\n          </div>\n        )}\n      </div>\n    </div>\n  );\n};\n\n// Re-export types and constants for external use\nexport { COLOR_PRESETS, DELAY_PATTERNS, GRID_CONFIGS };\nexport type { ColorPreset, DelayPattern, GridPattern };\n",
      "type": "registry:ui"
    },
    {
      "path": "registry/new-york/ui/wave-spinner.css",
      "content": "/* Wave Spinner Animations */\n\n@keyframes waveSpinnerPulse {\n\t0%,\n\t100% {\n\t\topacity: 0.3;\n\t\ttransform: scale(0.8);\n\t}\n\t50% {\n\t\topacity: 1;\n\t\ttransform: scale(1);\n\t}\n}\n\n@keyframes waveSpinnerFade {\n\t0%,\n\t100% {\n\t\topacity: 0.2;\n\t}\n\t50% {\n\t\topacity: 1;\n\t}\n}\n\n@keyframes waveSpinnerBounce {\n\t0%,\n\t100% {\n\t\ttransform: translateY(0) scale(1);\n\t\topacity: 0.6;\n\t}\n\t50% {\n\t\ttransform: translateY(-25%) scale(1.1);\n\t\topacity: 1;\n\t}\n}\n\n@keyframes waveSpinnerGrow {\n\t0%,\n\t100% {\n\t\ttransform: scale(0.5);\n\t\topacity: 0.4;\n\t}\n\t50% {\n\t\ttransform: scale(1);\n\t\topacity: 1;\n\t}\n}\n\n/* Reduced motion support */\n@media (prefers-reduced-motion: reduce) {\n\t[role=\"status\"] div {\n\t\tanimation-duration: 0s !important;\n\t\tanimation-delay: 0s !important;\n\t\topacity: 0.7 !important;\n\t\ttransform: scale(1) !important;\n\t}\n}\n",
      "type": "registry:ui"
    }
  ]
}