Todos os componentes

Fractal Grid Canvas

Fractal Grid Canvas é um componente de fundo para React e Tailwind CSS, da biblioteca Cult UI, com licença MIT. Copia e cola no teu projeto.

Cult UI MIT backgrounds

O que é

Fractal Grid Canvas é um componente de fundo para React e Tailwind CSS, da biblioteca Cult UI, com licença MIT. Copia e cola no teu projeto.

Como usar

Abre o componente no catálogo, carrega em copiar, e cola no teu projeto. O prompt copiado leva o código e as regras para o teu agente o reproduzir sem alterar nada.

Abrir no catálogo

Cinco cópias grátis por mês. Sem cartão.

Precisa de

npm i motion

Licença

Este componente vem de Cult UI e é redistribuído sob a licença MIT, que permite uso comercial. O aviso de copyright viaja com o código.

Fractal Grid Canvas

apps/www/registry/default/ui/canvas-fractal-grid.tsx
"use client"

import React, { useCallback, useEffect, useMemo, useRef, useState } from "react"
import { AnimatePresence, motion, useAnimation } from "motion/react"

interface GradientStop {
  color: string
  position: number
}

interface GradientType {
  stops: GradientStop[]
  centerX: number
  centerY: number
}

interface CanvasFractalGridProps {
  /** Size of each dot in pixels */
  dotSize?: number
  /** Spacing between dots in pixels */
  dotSpacing?: number
  /** Opacity of dots (0-1) */
  dotOpacity?: number
  /** Duration of the background gradient animation in seconds */
  gradientAnimationDuration?: number
  /** Stiffness of mouse tracking (higher values make it more responsive) */
  mouseTrackingStiffness?: number
  /** Damping of mouse tracking (higher values make it less bouncy) */
  mouseTrackingDamping?: number
  /** Intensity of the wave effect when hovering */
  waveIntensity?: number
  /** Radius of the wave effect in pixels */
  waveRadius?: number
  /** Array of gradient configurations for the background */
  gradients?: GradientType[]
  /** Color of the dots (supports any valid CSS color) */
  dotColor?: string
  /** Color of the dot glow effect (supports any valid CSS color) */
  glowColor?: string
  /** Enable or disable the noise overlay */
  enableNoise?: boolean
  /** Opacity of the noise overlay (0-1) */
  noiseOpacity?: number
  /** Enable or disable the mouse glow effect */
  enableMouseGlow?: boolean
  /** Set the initial performance level */
  initialPerformance?: "low" | "medium" | "high"
  /** Enable or disable the gradient animation */
  enableGradient?: boolean
}

const NoiseSVG = React.memo(() => (
  <svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%">
    <filter id="noise">
      <feTurbulence
        type="fractalNoise"
        baseFrequency="0.65"
        numOctaves="3"
        stitchTiles="stitch"
      />
    </filter>
    <rect width="100%" height="100%" filter="url(#noise)" />
  </svg>
))

NoiseSVG.displayName = "NoiseSVG"

const NoiseOverlay: React.FC<{ opacity: number }> = ({ opacity }) => (
  <div
    className="absolute inset-0 h-full w-full mix-blend-overlay"
    style={{ opacity }}
  >
    <NoiseSVG />
  </div>
)

const useResponsive = () => {
  const [windowSize, setWindowSize] = useState({
    width: 0,
    height: 0,
  })

  useEffect(() => {
    if (typeof window === "undefined") return

    const handleResize = () => {
      setWindowSize({
        width: window.innerWidth,
        height: window.innerHeight,
      })
    }

    // Set initial size
    handleResize()

    window.addEventListener("resize", handleResize)
    return () => window.removeEventListener("resize", handleResize)
  }, [])

  return {
    isMobile: windowSize.width < 768,
    isTablet: windowSize.width >= 768 && windowSize.width < 1024,
    isDesktop: windowSize.width >= 1024,
  }
}

const usePerformance = (
  initialPerformance: "low" | "medium" | "high" = "medium"
) => {
  const [performance, setPerformance] = useState(initialPerformance)
  const [fps, setFps] = useState(60)

  useEffect(() => {
    if (typeof window === "undefined") return

    let frameCount = 0
    let lastTime = globalThis.performance.now()
    let framerId: number

    const measureFps = (time: number) => {
      frameCount++
      if (time - lastTime > 1000) {
        setFps(Math.round((frameCount * 1000) / (time - lastTime)))
        frameCount = 0
        lastTime = time
      }
      framerId = requestAnimationFrame(measureFps)
    }

    framerId = requestAnimationFrame(measureFps)

    return () => cancelAnimationFrame(framerId)
  }, [])

  useEffect(() => {
    if (fps < 30 && performance !== "low") {
      setPerformance("low")
    } else if (fps >= 30 && fps < 50 && performance !== "medium") {
      setPerformance("medium")
    } else if (fps >= 50 && performance !== "high") {
      setPerformance("high")
    }
  }, [fps, performance])

  return { performance, fps }
}

const Gradient: React.FC<{
  gradients: GradientType[]
  animationDuration: number
}> = React.memo(({ gradients, animationDuration }) => {
  const controls = useAnimation()

  useEffect(() => {
    controls.start({
      background: gradients.map(
        (g) =>
          `radial-gradient(circle at ${g.centerX}% ${g.centerY}%, ${g.stops
            .map((s) => `${s.color} ${s.position}%`)
            .join(", ")})`
      ),
      transition: {
        duration: animationDuration,
        repeat: Infinity,
        repeatType: "reverse",
        ease: "linear",
      },
    })
  }, [controls, gradients, animationDuration])

  return (
    <motion.div className="absolute inset-0 h-full w-full" animate={controls} />
  )
})

Gradient.displayName = "Gradient"

const DotCanvas: React.FC<{
  dotSize: number
  dotSpacing: number
  dotOpacity: number
  waveIntensity: number
  waveRadius: number
  dotColor: string
  glowColor: string
  performance: "low" | "medium" | "high"
  mousePos: { x: number; y: number }
}> = React.memo(
  ({
    dotSize,
    dotSpacing,
    dotOpacity,
    waveIntensity,
    waveRadius,
    dotColor,
    glowColor,
    performance,
    mousePos,
  }) => {
    const canvasRef = useRef<HTMLCanvasElement>(null)
    const animationRef = useRef<number | null>(null)

    const drawDots = useCallback(
      (ctx: CanvasRenderingContext2D, time: number) => {
        const { width, height } = ctx.canvas
        ctx.clearRect(0, 0, width, height)

        const performanceSettings = {
          low: { skip: 3 },
          medium: { skip: 2 },
          high: { skip: 1 },
        }

        const skip = performanceSettings[performance].skip

        const cols = Math.ceil(width / dotSpacing)
        const rows = Math.ceil(height / dotSpacing)

        const centerX = mousePos.x * width
        const centerY = mousePos.y * height

        for (let i = 0; i < cols; i += skip) {
          for (let j = 0; j < rows; j += skip) {
            const x = i * dotSpacing
            const y = j * dotSpacing

            const distanceX = x - centerX
            const distanceY = y - centerY
            const distance = Math.sqrt(
              distanceX * distanceX + distanceY * distanceY
            )

            let dotX = x
            let dotY = y

            if (distance < waveRadius) {
              const waveStrength = Math.pow(1 - distance / waveRadius, 2)
              const angle = Math.atan2(distanceY, distanceX)
              const waveOffset =
                Math.sin(distance * 0.05 - time * 0.005) *
                waveIntensity *
                waveStrength
              dotX += Math.cos(angle) * waveOffset
              dotY += Math.sin(angle) * waveOffset

              const glowRadius = dotSize * (1 + waveStrength)
              const gradient = ctx.createRadialGradient(
                dotX,
                dotY,
                0,
                dotX,
                dotY,
                glowRadius
              )
              gradient.addColorStop(
                0,
                glowColor.replace("1)", `${dotOpacity * (1 + waveStrength)})`)
              )
              gradient.addColorStop(1, glowColor.replace("1)", "0)"))
              ctx.fillStyle = gradient
            } else {
              ctx.fillStyle = dotColor.replace("1)", `${dotOpacity})`)
            }

            ctx.beginPath()
            ctx.arc(dotX, dotY, dotSize / 2, 0, Math.PI * 2)
            ctx.fill()
          }
        }
      },
      [
        dotSize,
        dotSpacing,
        dotOpacity,
        waveIntensity,
        waveRadius,
        dotColor,
        glowColor,
        performance,
        mousePos,
      ]
    )

    useEffect(() => {
      if (typeof window === "undefined") return

      const canvas = canvasRef.current
      if (!canvas) return

      const ctx = canvas.getContext("2d")
      if (!ctx) return

      const resizeCanvas = () => {
        canvas.width = window.innerWidth
        canvas.height = window.innerHeight
      }

      resizeCanvas()
      window.addEventListener("resize", resizeCanvas)

      let lastTime = 0
      const animate = (time: number) => {
        if (time - lastTime > 16) {
          drawDots(ctx, time)
          lastTime = time
        }
        animationRef.current = requestAnimationFrame(animate)
      }

      animationRef.current = requestAnimationFrame(animate)

      return () => {
        window.removeEventListener("resize", resizeCanvas)
        if (animationRef.current) {
          cancelAnimationFrame(animationRef.current)
        }
      }
    }, [drawDots])

    return (
      <canvas
        ref={canvasRef}
        className="absolute inset-0 h-full w-full bg-gray-100"
        style={{ mixBlendMode: "multiply" }}
      />
    )
  }
)

DotCanvas.displayName = "DotCanvas"

const MouseGlow: React.FC<{
  glowColor: string
  mousePos: { x: number; y: number }
}> = React.memo(({ glowColor, mousePos }) => (
  <>
    <div
      className="absolute w-40 h-40 rounded-full pointer-events-none"
      style={{
        background: `radial-gradient(circle, ${glowColor.replace(
          "1)",
          "0.2)"
        )} 0%, ${glowColor.replace("1)", "0)")} 70%)`,
        left: `${mousePos.x * 100}%`,
        top: `${mousePos.y * 100}%`,
        transform: "translate(-50%, -50%)",
        filter: "blur(10px)",
      }}
    />
    <div
      className="absolute w-20 h-20 rounded-full pointer-events-none"
      style={{
        background: `radial-gradient(circle, ${glowColor.replace(
          "1)",
          "0.4)"
        )} 0%, ${glowColor.replace("1)", "0)")} 70%)`,
        left: `${mousePos.x * 100}%`,
        top: `${mousePos.y * 100}%`,
        transform: "translate(-50%, -50%)",
      }}
    />
  </>
))

MouseGlow.displayName = "MouseGlow"

const defaultGradients: GradientType[] = [
  {
    stops: [
      { color: "#FFD6A5", position: 0 },
      { color: "#FFADAD", position: 25 },
      { color: "#FFC6FF", position: 50 },
      { color: "transparent", position: 75 },
    ],
    centerX: 50,
    centerY: 50,
  },
  {
    stops: [
      { color: "#A0C4FF", position: 0 },
      { color: "#BDB2FF", position: 25 },
      { color: "#CAFFBF", position: 50 },
      { color: "transparent", position: 75 },
    ],
    centerX: 60,
    centerY: 40,
  },
  {
    stops: [
      { color: "#9BF6FF", position: 0 },
      { color: "#FDFFB6", position: 25 },
      { color: "#FFAFCC", position: 50 },
      { color: "transparent", position: 75 },
    ],
    centerX: 40,
    centerY: 60,
  },
]

export function CanvasFractalGrid({
  dotSize = 4,
  dotSpacing = 20,
  dotOpacity = 0.3,
  gradientAnimationDuration = 20,
  waveIntensity = 30,
  waveRadius = 200,
  gradients = defaultGradients,
  dotColor = "rgba(100, 100, 255, 1)",
  glowColor = "rgba(100, 100, 255, 1)",
  enableNoise = true,
  noiseOpacity = 0.03,
  enableMouseGlow = true,
  initialPerformance = "medium",
  enableGradient = false,
}: CanvasFractalGridProps) {
  const containerRef = useRef<HTMLDivElement>(null)
  const { isMobile, isTablet } = useResponsive()
  const { performance } = usePerformance(initialPerformance)
  const [mousePos, setMousePos] = useState({ x: 0, y: 0 })

  const handleMouseMove = useCallback((event: MouseEvent) => {
    const { clientX, clientY } = event
    const { left, top, width, height } =
      containerRef.current?.getBoundingClientRect() ?? {
        left: 0,
        top: 0,
        width: 0,
        height: 0,
      }
    const x = (clientX - left) / width
    const y = (clientY - top) / height
    setMousePos({ x, y })
  }, [])

  useEffect(() => {
    if (typeof window === "undefined") return

    window.addEventListener("mousemove", handleMouseMove)
    return () => window.removeEventListener("mousemove", handleMouseMove)
  }, [handleMouseMove])

  const responsiveDotSize = useMemo(() => {
    if (isMobile) return dotSize * 0.75
    if (isTablet) return dotSize * 0.9
    return dotSize
  }, [isMobile, isTablet, dotSize])

  const responsiveDotSpacing = useMemo(() => {
    if (isMobile) return dotSpacing * 1.5
    if (isTablet) return dotSpacing * 1.25
    return dotSpacing
  }, [isMobile, isTablet, dotSpacing])

  return (
    <AnimatePresence>
      <motion.div
        ref={containerRef}
        key="landing-animation"
        initial={{ opacity: 0 }}
        animate={{ opacity: 1 }}
        exit={{ opacity: 0 }}
        transition={{ duration: 1.5, ease: "easeOut" }}
        className="absolute inset-0 overflow-hidden w-full h-full"
      >
        {enableGradient && (
          <Gradient
            gradients={gradients}
            animationDuration={gradientAnimationDuration}
          />
        )}
        {enableGradient && (
          <motion.div
            className="absolute inset-0 h-full w-full"
            style={{
              background: "radial-gradient(circle, transparent, #FFFFFF)",
              backgroundSize: "100% 100%",
              backgroundPosition: "center",
              mixBlendMode: "overlay",
            }}
            animate={{
              backgroundPosition: `${mousePos.x * 100}% ${mousePos.y * 100}%`,
            }}
          />
        )}
        <DotCanvas
          dotSize={responsiveDotSize}
          dotSpacing={responsiveDotSpacing}
          dotOpacity={dotOpacity}
          waveIntensity={waveIntensity}
          waveRadius={waveRadius}
          dotColor={dotColor}
          glowColor={glowColor}
          performance={performance}
          mousePos={mousePos}
        />
        {enableNoise && <NoiseOverlay opacity={noiseOpacity} />}
        {enableMouseGlow && (
          <MouseGlow glowColor={glowColor} mousePos={mousePos} />
        )}
      </motion.div>
    </AnimatePresence>
  )
}

export default React.memo(CanvasFractalGrid)

// export default CanvasFractalGrid

// const Gradient2: React.FC<{
//   gradients: Gradient[]
//   animationDuration: number
// }> = ({ gradients, animationDuration }) => {
//   const controls = useAnimation()

//   useEffect(() => {
//     controls.start({
//       background: gradients.map(
//         (g) =>
//           `radial-gradient(circle at ${g.centerX}% ${g.centerY}%, ${g.stops
//             .map((s) => `${s.color} ${s.position}%`)
//             .join(", ")})`
//       ),
//       transition: {
//         duration: animationDuration,
//         repeat: Infinity,
//         repeatType: "reverse",
//         ease: "easeInOut",
//       },
//     })
//   }, [controls, gradients, animationDuration])

//   return (
//     <motion.div className="absolute inset-0 h-full w-full" animate={controls} />
//   )
// }

// const DotCanvas2: React.FC<{
//   dotSize: number
//   dotSpacing: number
//   dotOpacity: number
//   waveIntensity: number
//   waveRadius: number
//   dotColor: string
//   glowColor: string
//   performance: "low" | "medium" | "high"
//   mouseX: number
//   mouseY: number
// }> = ({
//   dotSize,
//   dotSpacing,
//   dotOpacity,
//   waveIntensity,
//   waveRadius,
//   dotColor,
//   glowColor,
//   performance,
//   mouseX,
//   mouseY,
// }) => {
//   const canvasRef = useRef<HTMLCanvasElement>(null)
//   const animationRef = useRef<number>()
//   const mouseRef = useRef({ x: mouseX, y: mouseY })

//   useEffect(() => {
//     mouseRef.current = { x: mouseX, y: mouseY }
//   }, [mouseX, mouseY])

//   const drawDots = useCallback(
//     (ctx: CanvasRenderingContext2D, time: number) => {
//       const { width, height } = ctx.canvas
//       ctx.clearRect(0, 0, width, height)

//       const performanceSettings = {
//         low: { skip: 3 },
//         medium: { skip: 2 },
//         high: { skip: 1 },
//       }

//       const skip = performanceSettings[performance].skip

//       const cols = Math.ceil(width / dotSpacing)
//       const rows = Math.ceil(height / dotSpacing)

//       const centerX = mouseRef.current.x * width
//       const centerY = mouseRef.current.y * height

//       for (let i = 0; i < cols; i += skip) {
//         for (let j = 0; j < rows; j += skip) {
//           const x = i * dotSpacing
//           const y = j * dotSpacing

//           const distanceX = x - centerX
//           const distanceY = y - centerY
//           const distance = Math.sqrt(
//             distanceX * distanceX + distanceY * distanceY
//           )

//           let dotX = x
//           let dotY = y

//           if (distance < waveRadius) {
//             const waveStrength = Math.pow(1 - distance / waveRadius, 2)
//             const angle = Math.atan2(distanceY, distanceX)
//             const waveOffset =
//               Math.sin(distance * 0.05 - time * 0.005) *
//               waveIntensity *
//               waveStrength
//             dotX += Math.cos(angle) * waveOffset
//             dotY += Math.sin(angle) * waveOffset

//             const glowRadius = dotSize * (1 + waveStrength)
//             const gradient = ctx.createRadialGradient(
//               dotX,
//               dotY,
//               0,
//               dotX,
//               dotY,
//               glowRadius
//             )
//             gradient.addColorStop(
//               0,
//               glowColor.replace("1)", `${dotOpacity * (1 + waveStrength)})`)
//             )
//             gradient.addColorStop(1, glowColor.replace("1)", "0)"))
//             ctx.fillStyle = gradient
//           } else {
//             ctx.fillStyle = dotColor.replace("1)", `${dotOpacity})`)
//           }

//           ctx.beginPath()
//           ctx.arc(dotX, dotY, dotSize / 2, 0, Math.PI * 2)
//           ctx.fill()
//         }
//       }
//     },
//     [
//       dotSize,
//       dotSpacing,
//       dotOpacity,
//       waveIntensity,
//       waveRadius,
//       dotColor,
//       glowColor,
//       performance,
//     ]
//   )

//   useEffect(() => {
//     const canvas = canvasRef.current
//     if (!canvas) return

//     const ctx = canvas.getContext("2d")
//     if (!ctx) return

//     const resizeCanvas = () => {
//       canvas.width = window.innerWidth
//       canvas.height = window.innerHeight
//     }

//     resizeCanvas()
//     window.addEventListener("resize", resizeCanvas)

//     let lastTime = 0
//     const animate = (time: number) => {
//       if (time - lastTime > 16) {
//         drawDots(ctx, time)
//         lastTime = time
//       }
//       animationRef.current = requestAnimationFrame(animate)
//     }

//     animationRef.current = requestAnimationFrame(animate)

//     return () => {
//       window.removeEventListener("resize", resizeCanvas)
//       if (animationRef.current) {
//         cancelAnimationFrame(animationRef.current)
//       }
//     }
//   }, [drawDots])

//   return (
//     <canvas
//       ref={canvasRef}
//       className="absolute inset-0 h-full w-full bg-gray-100"
//       style={{ mixBlendMode: "multiply" }}
//     />
//   )
// }

// const MouseGlow2: React.FC<{
//   glowColor: string
//   mouseX: any
//   mouseY: any
// }> = ({ glowColor, mouseX, mouseY }) => (
//   <>
//     <motion.div
//       className="absolute w-40 h-40 rounded-full pointer-events-none"
//       style={{
//         background: `radial-gradient(circle, ${glowColor.replace(
//           "1)",
//           "0.2)"
//         )} 0%, ${glowColor.replace("1)", "0)")} 70%)`,
//         x: mouseX,
//         y: mouseY,
//         translateX: "-50%",
//         translateY: "-50%",
//         filter: "blur(10px)",
//       }}
//     />
//     <motion.div
//       className="absolute w-20 h-20 rounded-full pointer-events-none"
//       style={{
//         background: `radial-gradient(circle, ${glowColor.replace(
//           "1)",
//           "0.4)"
//         )} 0%, ${glowColor.replace("1)", "0)")} 70%)`,
//         x: mouseX,
//         y: mouseY,
//         translateX: "-50%",
//         translateY: "-50%",
//       }}
//     />
//   </>
// )
// "use client"

// import React, { useCallback, useEffect, useMemo, useRef } from "react"
// import { AnimatePresence, motion, useAnimation, useSpring } from "motion/react"

// const NoiseSVG = React.memo(() => (
//   <svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%">
//     <filter id="noise">
//       <feTurbulence
//         type="fractalNoise"
//         baseFrequency="0.65"
//         numOctaves="3"
//         stitchTiles="stitch"
//       />
//     </filter>
//     <rect width="100%" height="100%" filter="url(#noise)" opacity="0.03" />
//   </svg>
// ))

// NoiseSVG.displayName = "NoiseSVG"

// interface GradientStop {
//   color: string
//   position: number
// }

// interface Gradient {
//   stops: GradientStop[]
//   centerX: number
//   centerY: number
// }

// interface CanvasFractalGridProps {
//   /** Size of each dot in pixels */
//   dotSize?: number
//   /** Spacing between dots in pixels */
//   dotSpacing?: number
//   /** Opacity of dots (0-1) */
//   dotOpacity?: number
//   /** Duration of the background gradient animation in seconds */
//   gradientAnimationDuration?: number
//   /** Stiffness of mouse tracking (higher values make it more responsive) */
//   mouseTrackingStiffness?: number
//   /** Damping of mouse tracking (higher values make it less bouncy) */
//   mouseTrackingDamping?: number
//   /** Intensity of the wave effect when hovering */
//   waveIntensity?: number
//   /** Radius of the wave effect in pixels */
//   waveRadius?: number
//   /** Array of gradient configurations for the background */
//   gradients?: Gradient[]
//   /** Color of the dots (supports any valid CSS color) */
//   dotColor?: string
//   /** Color of the dot glow effect (supports any valid CSS color) */
//   glowColor?: string
//   /** Enable or disable the noise overlay */
//   enableNoise?: boolean
//   /** Opacity of the noise overlay (0-1) */
//   noiseOpacity?: number
//   /** Enable or disable the mouse glow effect */
//   enableMouseGlow?: boolean
// }

// const defaultGradients: Gradient[] = [
//   {
//     stops: [
//       { color: "#FFD6A5", position: 0 },
//       { color: "#FFADAD", position: 25 },
//       { color: "#FFC6FF", position: 50 },
//       { color: "transparent", position: 75 },
//     ],
//     centerX: 50,
//     centerY: 50,
//   },
//   {
//     stops: [
//       { color: "#A0C4FF", position: 0 },
//       { color: "#BDB2FF", position: 25 },
//       { color: "#CAFFBF", position: 50 },
//       { color: "transparent", position: 75 },
//     ],
//     centerX: 60,
//     centerY: 40,
//   },
//   {
//     stops: [
//       { color: "#9BF6FF", position: 0 },
//       { color: "#FDFFB6", position: 25 },
//       { color: "#FFAFCC", position: 50 },
//       { color: "transparent", position: 75 },
//     ],
//     centerX: 40,
//     centerY: 60,
//   },
// ]

// export function CanvasFractalGrid({
//   dotSize = 4,
//   dotSpacing = 20,
//   dotOpacity = 0.3,
//   gradientAnimationDuration = 20,
//   mouseTrackingStiffness = 500,
//   mouseTrackingDamping = 150,
//   waveIntensity = 30,
//   waveRadius = 200,
//   gradients = defaultGradients,
//   dotColor = "rgba(100, 100, 255, 1)",
//   glowColor = "rgba(100, 100, 255, 1)",
//   enableNoise = true,
//   noiseOpacity = 0.03,
//   enableMouseGlow = true,
// }: CanvasFractalGridProps) {
//   const containerRef = useRef<HTMLDivElement>(null)
//   const canvasRef = useRef<HTMLCanvasElement>(null)
//   const animationRef = useRef<number>()
//   const mouseRef = useRef({ x: 0, y: 0 })

//   const mouseX = useSpring(0, {
//     stiffness: mouseTrackingStiffness,
//     damping: mouseTrackingDamping,
//   })
//   const mouseY = useSpring(0, {
//     stiffness: mouseTrackingStiffness,
//     damping: mouseTrackingDamping,
//   })

//   const handleMouseMove = useCallback(
//     (event: MouseEvent) => {
//       const { clientX, clientY } = event
//       const { left, top, width, height } =
//         containerRef.current?.getBoundingClientRect() ?? {
//           left: 0,
//           top: 0,
//           width: 0,
//           height: 0,
//         }
//       const x = (clientX - left) / width
//       const y = (clientY - top) / height
//       mouseX.set(x)
//       mouseY.set(y)
//       mouseRef.current = { x, y }
//     },
//     [mouseX, mouseY]
//   )

//   useEffect(() => {
//     window.addEventListener("mousemove", handleMouseMove)
//     return () => window.removeEventListener("mousemove", handleMouseMove)
//   }, [handleMouseMove])

//   const controls = useAnimation()

//   useEffect(() => {
//     controls.start({
//       background: gradients.map(
//         (g) =>
//           `radial-gradient(circle at ${g.centerX}% ${g.centerY}%, ${g.stops
//             .map((s) => `${s.color} ${s.position}%`)
//             .join(", ")})`
//       ),
//       transition: {
//         duration: gradientAnimationDuration,
//         repeat: Infinity,
//         repeatType: "reverse",
//         ease: "easeInOut",
//       },
//     })
//   }, [controls, gradients, gradientAnimationDuration])

//   const drawDots = useCallback(
//     (ctx: CanvasRenderingContext2D, time: number) => {
//       const { width, height } = ctx.canvas
//       ctx.clearRect(0, 0, width, height)

//       const cols = Math.ceil(width / dotSpacing)
//       const rows = Math.ceil(height / dotSpacing)

//       const centerX = mouseRef.current.x * width
//       const centerY = mouseRef.current.y * height

//       for (let i = 0; i < cols; i++) {
//         for (let j = 0; j < rows; j++) {
//           const x = i * dotSpacing
//           const y = j * dotSpacing

//           const distanceX = x - centerX
//           const distanceY = y - centerY
//           const distance = Math.sqrt(
//             distanceX * distanceX + distanceY * distanceY
//           )

//           let dotX = x
//           let dotY = y

//           if (distance < waveRadius) {
//             const waveStrength = Math.pow(1 - distance / waveRadius, 2)
//             const angle = Math.atan2(distanceY, distanceX)
//             const waveOffset =
//               Math.sin(distance * 0.05 - time * 0.005) *
//               waveIntensity *
//               waveStrength
//             dotX += Math.cos(angle) * waveOffset
//             dotY += Math.sin(angle) * waveOffset

//             const glowRadius = dotSize * (1 + waveStrength)
//             const gradient = ctx.createRadialGradient(
//               dotX,
//               dotY,
//               0,
//               dotX,
//               dotY,
//               glowRadius
//             )
//             gradient.addColorStop(
//               0,
//               glowColor.replace("1)", `${dotOpacity * (1 + waveStrength)})`)
//             )
//             gradient.addColorStop(1, glowColor.replace("1)", "0)"))
//             ctx.fillStyle = gradient
//           } else {
//             ctx.fillStyle = dotColor.replace("1)", `${dotOpacity})`)
//           }

//           ctx.beginPath()
//           ctx.arc(dotX, dotY, dotSize / 2, 0, Math.PI * 2)
//           ctx.fill()
//         }
//       }
//     },
//     [
//       dotSize,
//       dotSpacing,
//       dotOpacity,
//       waveIntensity,
//       waveRadius,
//       dotColor,
//       glowColor,
//     ]
//   )

//   useEffect(() => {
//     const canvas = canvasRef.current
//     if (!canvas) return

//     const ctx = canvas.getContext("2d")
//     if (!ctx) return

//     const resizeCanvas = () => {
//       canvas.width = window.innerWidth
//       canvas.height = window.innerHeight
//     }

//     resizeCanvas()
//     window.addEventListener("resize", resizeCanvas)

//     let lastTime = 0
//     const animate = (time: number) => {
//       if (time - lastTime > 16) {
//         drawDots(ctx, time)
//         lastTime = time
//       }
//       animationRef.current = requestAnimationFrame(animate)
//     }

//     animationRef.current = requestAnimationFrame(animate)

//     return () => {
//       window.removeEventListener("resize", resizeCanvas)
//       if (animationRef.current) {
//         cancelAnimationFrame(animationRef.current)
//       }
//     }
//   }, [drawDots])

//   const gradientStyle = useMemo(
//     () =>
//       ({
//         "--mouse-x": mouseX,
//         "--mouse-y": mouseY,
//       } as React.CSSProperties),
//     [mouseX, mouseY]
//   )

//   return (
//     <AnimatePresence>
//       <motion.div
//         ref={containerRef}
//         key="landing-animation"
//         initial={{ opacity: 0 }}
//         animate={{ opacity: 1 }}
//         exit={{ opacity: 0 }}
//         transition={{ duration: 1.5, ease: "easeOut" }}
//         className="absolute inset-0 overflow-hidden w-full h-full"
//         style={gradientStyle}
//       >
//         <motion.div
//           className="absolute inset-0 h-full w-full"
//           animate={controls}
//         />
//         <motion.div
//           className="absolute inset-0 h-full w-full"
//           style={{
//             background: "radial-gradient(circle, transparent, #FFFFFF)",
//             backgroundSize: "100% 100%",
//             backgroundPosition: "center",
//             mixBlendMode: "overlay",
//           }}
//           animate={{
//             backgroundPosition: `calc(var(--mouse-x) * 100%) calc(var(--mouse-y) * 100%)`,
//           }}
//         />
//         <canvas
//           ref={canvasRef}
//           className="absolute inset-0 h-full w-full bg-gray-100"
//           style={{ mixBlendMode: "multiply" }}
//         />
//         {enableNoise && (
//           <div
//             className="absolute inset-0 h-full w-full mix-blend-overlay"
//             style={{ opacity: noiseOpacity }}
//           >
//             <NoiseSVG />
//           </div>
//         )}
//         {enableMouseGlow && (
//           <>
//             <motion.div
//               className="absolute w-40 h-40 rounded-full pointer-events-none"
//               style={{
//                 background: `radial-gradient(circle, ${glowColor.replace(
//                   "1)",
//                   "0.2)"
//                 )} 0%, ${glowColor.replace("1)", "0)")} 70%)`,
//                 x: mouseX,
//                 y: mouseY,
//                 translateX: "-50%",
//                 translateY: "-50%",
//                 filter: "blur(10px)",
//               }}
//             />
//             <motion.div
//               className="absolute w-20 h-20 rounded-full pointer-events-none"
//               style={{
//                 background: `radial-gradient(circle, ${glowColor.replace(
//                   "1)",
//                   "0.4)"
//                 )} 0%, ${glowColor.replace("1)", "0)")} 70%)`,
//                 x: mouseX,
//                 y: mouseY,
//                 translateX: "-50%",
//                 translateY: "-50%",
//               }}
//             />
//           </>
//         )}
//       </motion.div>
//     </AnimatePresence>
//   )
// }

// export default CanvasFractalGrid

// "use client"

// import React, { useCallback, useEffect, useMemo, useRef } from "react"
// import { AnimatePresence, motion, useAnimation, useSpring } from "motion/react"

// const NoiseSVG = React.memo(() => (
//   <svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%">
//     <filter id="noise">
//       <feTurbulence
//         type="fractalNoise"
//         baseFrequency="0.65"
//         numOctaves="3"
//         stitchTiles="stitch"
//       />
//     </filter>
//     <rect width="100%" height="100%" filter="url(#noise)" opacity="0.03" />
//   </svg>
// ))

// NoiseSVG.displayName = "NoiseSVG"

// interface LandingAnimationProps {
//   dotSize?: number
//   dotSpacing?: number
//   dotOpacity?: number
//   animationDuration?: number
//   mouseTrackingStiffness?: number
//   mouseTrackingDamping?: number
//   waveIntensity?: number
//   waveRadius?: number
// }

// export function CanvasFractalGrid({
//   dotSize = 4,
//   dotSpacing = 20,
//   dotOpacity = 0.3,
//   animationDuration = 5,
//   mouseTrackingStiffness = 500,
//   mouseTrackingDamping = 150,
//   waveIntensity = 30,
//   waveRadius = 200,
// }: LandingAnimationProps) {
//   const containerRef = useRef<HTMLDivElement>(null)
//   const canvasRef = useRef<HTMLCanvasElement>(null)
//   const animationRef = useRef<number>()
//   const mouseRef = useRef({ x: 0, y: 0 })

//   const mouseX = useSpring(0, {
//     stiffness: mouseTrackingStiffness,
//     damping: mouseTrackingDamping,
//   })
//   const mouseY = useSpring(0, {
//     stiffness: mouseTrackingStiffness,
//     damping: mouseTrackingDamping,
//   })

//   const handleMouseMove = useCallback(
//     (event: MouseEvent) => {
//       const { clientX, clientY } = event
//       const { left, top, width, height } =
//         containerRef.current?.getBoundingClientRect() ?? {
//           left: 0,
//           top: 0,
//           width: 0,
//           height: 0,
//         }
//       const x = (clientX - left) / width
//       const y = (clientY - top) / height
//       mouseX.set(x)
//       mouseY.set(y)
//       mouseRef.current = { x, y }
//     },
//     [mouseX, mouseY]
//   )

//   useEffect(() => {
//     window.addEventListener("mousemove", handleMouseMove)
//     return () => window.removeEventListener("mousemove", handleMouseMove)
//   }, [handleMouseMove])

//   const controls = useAnimation()

//   useEffect(() => {
//     controls.start({
//       background: [
//         "radial-gradient(circle at 50% 50%, #FFD6A5 0%, #FFADAD 25%, #FFC6FF 50%, transparent 75%)",
//         "radial-gradient(circle at 60% 40%, #A0C4FF 0%, #BDB2FF 25%, #CAFFBF 50%, transparent 75%)",
//         "radial-gradient(circle at 40% 60%, #9BF6FF 0%, #FDFFB6 25%, #FFAFCC 50%, transparent 75%)",
//       ],
//       transition: {
//         duration: 20,
//         repeat: Infinity,
//         repeatType: "reverse",
//         ease: "easeInOut",
//       },
//     })
//   }, [controls])

//   const drawDots = useCallback(
//     (ctx: CanvasRenderingContext2D, time: number) => {
//       const { width, height } = ctx.canvas
//       ctx.clearRect(0, 0, width, height)

//       const cols = Math.ceil(width / dotSpacing)
//       const rows = Math.ceil(height / dotSpacing)

//       const centerX = mouseRef.current.x * width
//       const centerY = mouseRef.current.y * height

//       for (let i = 0; i < cols; i++) {
//         for (let j = 0; j < rows; j++) {
//           const x = i * dotSpacing
//           const y = j * dotSpacing

//           const distanceX = x - centerX
//           const distanceY = y - centerY
//           const distance = Math.sqrt(
//             distanceX * distanceX + distanceY * distanceY
//           )

//           let dotX = x
//           let dotY = y

//           if (distance < waveRadius) {
//             const waveStrength = Math.pow(1 - distance / waveRadius, 2)
//             const angle = Math.atan2(distanceY, distanceX)
//             const waveOffset =
//               Math.sin(distance * 0.05 - time * 0.005) *
//               waveIntensity *
//               waveStrength
//             dotX += Math.cos(angle) * waveOffset
//             dotY += Math.sin(angle) * waveOffset

//             const glowRadius = dotSize * (1 + waveStrength)
//             const gradient = ctx.createRadialGradient(
//               dotX,
//               dotY,
//               0,
//               dotX,
//               dotY,
//               glowRadius
//             )
//             gradient.addColorStop(
//               0,
//               `rgba(100, 100, 255, ${dotOpacity * (1 + waveStrength)})`
//             )
//             gradient.addColorStop(1, "rgba(100, 100, 255, 0)")
//             ctx.fillStyle = gradient
//           } else {
//             ctx.fillStyle = `rgba(100, 100, 255, ${dotOpacity})`
//           }

//           ctx.beginPath()
//           ctx.arc(dotX, dotY, dotSize / 2, 0, Math.PI * 2)
//           ctx.fill()
//         }
//       }
//     },
//     [dotSize, dotSpacing, dotOpacity, waveIntensity, waveRadius]
//   )

//   useEffect(() => {
//     const canvas = canvasRef.current
//     if (!canvas) return

//     const ctx = canvas.getContext("2d")
//     if (!ctx) return

//     const resizeCanvas = () => {
//       canvas.width = window.innerWidth
//       canvas.height = window.innerHeight
//     }

//     resizeCanvas()
//     window.addEventListener("resize", resizeCanvas)

//     let lastTime = 0
//     const animate = (time: number) => {
//       if (time - lastTime > 16) {
//         drawDots(ctx, time)
//         lastTime = time
//       }
//       animationRef.current = requestAnimationFrame(animate)
//     }

//     animationRef.current = requestAnimationFrame(animate)

//     let animationId: number

//     animationId = requestAnimationFrame(animate)

//     return () => {
//       window.removeEventListener("resize", resizeCanvas)
//       cancelAnimationFrame(animationId)
//     }
//   }, [drawDots])

//   const gradientStyle = useMemo(
//     () =>
//       ({
//         "--mouse-x": mouseX,
//         "--mouse-y": mouseY,
//       } as React.CSSProperties),
//     [mouseX, mouseY]
//   )

//   return (
//     <AnimatePresence>
//       <motion.div
//         ref={containerRef}
//         key="landing-animation"
//         initial={{ opacity: 0 }}
//         animate={{ opacity: 1 }}
//         exit={{ opacity: 0 }}
//         transition={{ duration: 1.5, ease: "easeOut" }}
//         // add -z-10 to fix z-index issue if you want to use this component as a background with no parent element setting bg color
//         className="absolute inset-0  overflow-hidden w-full h-full "
//         style={gradientStyle}
//       >
//         <motion.div
//           className="absolute inset-0 h-full w-full"
//           animate={controls}
//         />
//         <motion.div
//           className="absolute inset-0 h-full w-full"
//           animate={{
//             background: [
//               "radial-gradient(circle at 25% 75%, #FFC6FF 0%, #FFADAD 25%, #FFD6A5 50%, transparent 75%)",
//               "radial-gradient(circle at 25% 25%, #A0C4FF 0%, #BDB2FF 25%, #CAFFBF 50%, transparent 75%)",
//               "radial-gradient(circle at 25% 75%, #9BF6FF 0%, #FDFFB6 25%, #FFAFCC 50%, transparent 75%)",
//             ],
//           }}
//           transition={{
//             duration: 15,
//             repeat: Infinity,
//             repeatType: "reverse",
//             ease: "easeInOut",
//           }}
//         />
//         <motion.div
//           className="absolute inset-0 h-full w-full"
//           style={{
//             background: "radial-gradient(circle, transparent, #FFFFFF)",
//             backgroundSize: "100% 100%",
//             backgroundPosition: "center",
//             mixBlendMode: "overlay",
//           }}
//           animate={{
//             backgroundPosition: `calc(var(--mouse-x) * 100%) calc(var(--mouse-y) * 100%)`,
//           }}
//         />
//         <canvas
//           ref={canvasRef}
//           className="absolute inset-0 h-full w-full bg-gray-100"
//           style={{ mixBlendMode: "multiply" }}
//         />
//         <div className="absolute inset-0 h-full w-full opacity-30 mix-blend-overlay">
//           <NoiseSVG />
//         </div>
//         <motion.div
//           className="absolute w-40 h-40 rounded-full pointer-events-none"
//           style={{
//             background:
//               "radial-gradient(circle, rgba(100,100,255,0.2) 0%, rgba(100,100,255,0) 70%)",
//             x: mouseX,
//             y: mouseY,
//             translateX: "-50%",
//             translateY: "-50%",
//             filter: "blur(10px)",
//           }}
//         />
//         <motion.div
//           className="absolute w-20 h-20 rounded-full pointer-events-none"
//           style={{
//             background:
//               "radial-gradient(circle, rgba(100,100,255,0.4) 0%, rgba(100,100,255,0) 70%)",
//             x: mouseX,
//             y: mouseY,
//             translateX: "-50%",
//             translateY: "-50%",
//           }}
//         />
//       </motion.div>
//     </AnimatePresence>
//   )
// }

// export default CanvasFractalGrid

Componentes parecidos