{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "wavy-block",
  "title": "Wavy Block",
  "description": "block animates its children in waves pattern.",
  "dependencies": [
    "motion"
  ],
  "files": [
    {
      "path": "registry/scroll-animations/wavy-block/index.tsx",
      "content": "'use client';\n\nimport {\n  HTMLMotionProps,\n  motion,\n  MotionValue,\n  SpringOptions,\n  useMotionValue,\n  useReducedMotion,\n  useScroll,\n  UseScrollOptions,\n  useSpring,\n  useTransform,\n} from 'motion/react';\nimport React from 'react';\n\ninterface WavyTextsConfig {\n  baseOffsetFactor: number;\n  baseExtra: number;\n  baseAmplitude: number;\n  lengthEffect: number;\n  frequency: number;\n  progressScale: number;\n  phaseShiftDeg: number;\n  spring: SpringOptions;\n}\ninterface WavyBlockItemProps extends HTMLMotionProps<'div'> {\n  index: number;\n  config?: Partial<WavyTextsConfig>;\n  axis?: 'x' | 'y';\n}\ninterface WavyBlockContextValue {\n  scrollYProgress: MotionValue<number>;\n  maxLen: number;\n}\n\nconst WavyBlockContext = React.createContext<WavyBlockContextValue | undefined>(\n  undefined,\n);\n\nfunction useWavyBlockContext() {\n  const context = React.useContext(WavyBlockContext);\n  if (context === undefined) {\n    throw new Error('useWavyBlockContext must be used within a WavyBlock');\n  }\n  return context;\n}\nconst toRadian = (deg: number) => (deg * Math.PI) / 180;\nconst TAU = Math.PI * 2;\nconst DEFAULT_X_CONFIG: WavyTextsConfig = {\n  baseOffsetFactor: 0.1,\n  baseExtra: 0,\n  baseAmplitude: 160,\n  lengthEffect: 0.6,\n  frequency: 35,\n  progressScale: 1,\n  phaseShiftDeg: -180,\n  spring: { damping: 22, stiffness: 300 },\n};\nconst DEFAULT_Y_CONFIG: WavyTextsConfig = {\n  ...DEFAULT_X_CONFIG,\n  baseOffsetFactor: 0,\n  baseAmplitude: 120,\n  progressScale: 2,\n  phaseShiftDeg: 0,\n};\n\nexport function WavyBlockItem({\n  index,\n  axis = 'x',\n  config,\n  style,\n  ...props\n}: WavyBlockItemProps) {\n  const { scrollYProgress, maxLen } = useWavyBlockContext();\n  const reducedMotion = useReducedMotion();\n  const lengthFactor = Math.min(1, Math.max(0, maxLen / (maxLen || 1)));\n  const resolvedConfig = React.useMemo(() => {\n    const defaults = axis === 'y' ? DEFAULT_Y_CONFIG : DEFAULT_X_CONFIG;\n    return {\n      ...defaults,\n      ...config,\n      spring: {\n        ...defaults.spring,\n        ...config?.spring,\n      },\n    };\n  }, [axis, config]);\n\n  const [isMounted, setIsMounted] = React.useState<boolean>(false);\n\n  const calculateAxisOffset = React.useCallback(\n    (p: number, viewportSize?: number) => {\n      const phase = resolvedConfig.progressScale * p * TAU;\n\n      const size =\n        viewportSize ??\n        (typeof window !== 'undefined'\n          ? axis === 'y'\n            ? window.innerHeight\n            : window.innerWidth\n          : axis === 'y'\n            ? 900\n            : 1200);\n      const hasManualBaseOffset =\n        config?.baseOffsetFactor !== undefined ||\n        config?.baseExtra !== undefined;\n      const baseOffset =\n        axis === 'y' && !hasManualBaseOffset\n          ? 0\n          : resolvedConfig.baseOffsetFactor * size + resolvedConfig.baseExtra;\n\n      const amplitudeScale = 1 - resolvedConfig.lengthEffect * lengthFactor;\n      const amplitude = resolvedConfig.baseAmplitude * amplitudeScale;\n\n      const waveOffset =\n        axis === 'y'\n          ? amplitude *\n            Math.sin(\n              phase +\n                toRadian(resolvedConfig.frequency * index) +\n                toRadian(resolvedConfig.phaseShiftDeg),\n            )\n          : amplitude *\n            Math.cos(\n              phase +\n                toRadian(resolvedConfig.frequency * index) +\n                toRadian(resolvedConfig.phaseShiftDeg),\n            );\n\n      return baseOffset + waveOffset;\n    },\n    [\n      resolvedConfig,\n      lengthFactor,\n      index,\n      axis,\n      config?.baseOffsetFactor,\n      config?.baseExtra,\n    ],\n  );\n\n  const initialOffset = calculateAxisOffset(0);\n  const rawOffset = useMotionValue(initialOffset);\n  const springOffset = useSpring(rawOffset, resolvedConfig.spring);\n  const offset = reducedMotion ? rawOffset : springOffset;\n\n  React.useLayoutEffect(() => {\n    setIsMounted(true);\n  }, []);\n\n  React.useEffect(() => {\n    if (!scrollYProgress || !isMounted) return;\n\n    const unsub = scrollYProgress.onChange((p) => {\n      const viewportSize =\n        typeof window !== 'undefined'\n          ? axis === 'y'\n            ? window.innerHeight\n            : window.innerWidth\n          : axis === 'y'\n            ? 900\n            : 1200;\n      const newOffset = calculateAxisOffset(p, viewportSize);\n      rawOffset.set(newOffset);\n    });\n\n    return () => {\n      if (unsub) unsub();\n    };\n  }, [scrollYProgress, rawOffset, calculateAxisOffset, isMounted, axis]);\n\n  const motionAxisStyle = axis === 'y' ? { y: offset } : { x: offset };\n\n  return (\n    <motion.div\n      style={{ ...motionAxisStyle, ...style }}\n      suppressHydrationWarning\n      {...props}\n    />\n  );\n}\n\nexport function WavyBlock({\n  offset = ['start end', 'end start'],\n  ...props\n}: React.ComponentPropsWithRef<'div'> & {\n  offset?: UseScrollOptions['offset'];\n}) {\n  const containerRef = React.useRef<HTMLDivElement>(null);\n  const { current } = containerRef;\n\n  const maxLen = React.useMemo(() => {\n    if (!current?.children || current.children.length === 0) return 1;\n    const childrenArray = Array.from(current.children);\n    return Math.max(\n      ...childrenArray.map((child) => (child ? String(child).length : 0)),\n    );\n  }, [current?.children]);\n\n  const { scrollYProgress } = useScroll({\n    target: containerRef,\n    offset: offset,\n  });\n  return (\n    <WavyBlockContext.Provider value={{ scrollYProgress, maxLen }}>\n      <div ref={containerRef} {...props} />\n    </WavyBlockContext.Provider>\n  );\n}\n\nexport function WavyBlockSticky({\n  yRange = [0, 300],\n  yInput = [0, 1],\n  style,\n  ...props\n}: HTMLMotionProps<'div'> & {\n  yRange?: [number, number];\n  yInput?: [number, number];\n}) {\n  const { scrollYProgress } = useWavyBlockContext();\n\n  const y = useTransform(scrollYProgress, yInput, yRange);\n  return <motion.div style={{ y, ...style }} {...props} />;\n}\n",
      "type": "registry:block",
      "target": "components/systaliko-ui/wavy-block.tsx"
    }
  ],
  "type": "registry:block"
}