Text Rain

Installation

1

Copy and paste the following code into your project.

"use client";

import { useState, useEffect } from "react";

const TextRain = ({ heading, subheading }) => {
  const [letters, setLetters] = useState([]);
  const [isAnimating, setIsAnimating] = useState(true);
  const [hoveredLetter, setHoveredLetter] = useState(null);

  useEffect(() => {
    const headingLetters = heading.split("").map((char, i) => ({
      char,
      id: `h-${i}`,
      isHeading: true,
      index: i,
      startDelay: Math.random() * 200,
      xOffset: Math.random() * 60 - 30,
    }));

    const subheadingLetters = subheading.split("").map((char, i) => ({
      char,
      id: `s-${i}`,
      isHeading: false,
      index: i,
      startDelay: Math.random() * 200 + 100,
      xOffset: Math.random() * 40 - 20,
    }));

    // Set letters immediately
    setLetters([...headingLetters, ...subheadingLetters]);

    const maxDelay = Math.max(
      ...[...headingLetters, ...subheadingLetters].map((l) => l.startDelay)
    );
    setTimeout(() => {
      setIsAnimating(false);
    }, maxDelay + 100);
  }, []);

  const getNeighborIndices = (id) => {
    const currentIndex = letters.findIndex((l) => l.id === id);
    return letters
      .map((l, i) => ({ id: l.id, distance: Math.abs(i - currentIndex) }))
      .filter(({ distance }) => distance <= 3);
  };

  return (
    <div className="relative min-h-screen w-full overflow-hidden bg-gradient-to-b from-black via-gray-900 to-blue-950">
      {/* Animated background elements */}
      <div className="absolute inset-0">
        <div className="absolute w-1/2 h-1/2 bg-blue-500/20 rounded-full filter blur-[120px] -top-1/4 -left-1/4 animate-pulse" />
        <div className="absolute w-1/2 h-1/2 bg-purple-500/20 rounded-full filter blur-[120px] -bottom-1/4 -right-1/4 animate-pulse delay-1000" />
        <div className="absolute w-1/3 h-1/3 bg-cyan-500/20 rounded-full filter blur-[100px] top-1/2 left-1/2 animate-pulse delay-500" />
      </div>

      {/* Particle effect background */}
      <div className="absolute inset-0 opacity-30">
        {[...Array(50)].map((_, i) => (
          <div
            key={i}
            className="absolute w-1 h-1 bg-white rounded-full animate-float"
            style={{
              left: `${Math.random() * 100}%`,
              top: `${Math.random() * 100}%`,
              animationDelay: `${Math.random() * 5}s`,
              animationDuration: `${5 + Math.random() * 5}s`,
            }}
          />
        ))}
      </div>

      {/* Main content container */}
      <div className="relative min-h-screen flex flex-col items-center justify-center px-4 perspective-1000">
        <div className="text-center">
          {/* Heading Container */}
          <div className="relative h-20 sm:h-24 md:h-32 mb-8 overflow-visible">
            <div className="flex justify-center flex-wrap">
              {letters
                .filter((l) => l.isHeading)
                .map((letter) => (
                  <div
                    key={letter.id}
                    className="relative inline-block transform-gpu"
                    onMouseEnter={() =>
                      !isAnimating && setHoveredLetter(letter.id)
                    }
                    onMouseLeave={() => setHoveredLetter(null)}
                    style={{
                      transform: isAnimating
                        ? `translateY(-100vh) translateX(${letter.xOffset}px) rotateX(360deg)`
                        : "translateY(0) translateX(0) rotateX(0)",
                      transition: `transform 1.5s cubic-bezier(0.34, 1.56, 0.64, 1) ${letter.startDelay}ms`,
                    }}
                  >
                    <span
                      className="text-4xl sm:text-5xl md:text-7xl font-bold relative inline-block transition-all duration-300 cursor-pointer px-0.5"
                      style={{
                        transform:
                          !isAnimating &&
                          getNeighborIndices(letter.id).some(
                            (n) => n.id === hoveredLetter
                          )
                            ? `translateY(${
                                Math.sin(
                                  getNeighborIndices(letter.id).find(
                                    (n) => n.id === hoveredLetter
                                  ).distance
                                ) * 8
                              }px) 
                           scale(${
                             1 +
                             1 /
                               (getNeighborIndices(letter.id).find(
                                 (n) => n.id === hoveredLetter
                               ).distance +
                                 1)
                           })`
                            : "translateY(0) scale(1)",
                        color: getNeighborIndices(letter.id).some(
                          (n) => n.id === hoveredLetter
                        )
                          ? `hsl(${
                              210 +
                              getNeighborIndices(letter.id).find(
                                (n) => n.id === hoveredLetter
                              ).distance *
                                15
                            }, 100%, 70%)`
                          : "white",
                        textShadow: getNeighborIndices(letter.id).some(
                          (n) => n.id === hoveredLetter
                        )
                          ? "0 0 15px rgba(255,255,255,0.5)"
                          : "0 0 10px rgba(255,255,255,0.2)",
                        transition:
                          "transform 0.3s ease-out, color 0.3s ease-out, text-shadow 0.3s ease-out",
                      }}
                    >
                      {letter.char}
                    </span>
                  </div>
                ))}
            </div>
          </div>

          {/* Subheading Container */}
          <div className="relative h-12 sm:h-16 md:h-20 overflow-visible">
            <div className="flex justify-center flex-wrap">
              {letters
                .filter((l) => !l.isHeading)
                .map((letter) => (
                  <div
                    key={letter.id}
                    className="relative inline-block transform-gpu"
                    onMouseEnter={() =>
                      !isAnimating && setHoveredLetter(letter.id)
                    }
                    onMouseLeave={() => setHoveredLetter(null)}
                    style={{
                      transform: isAnimating
                        ? `translateY(-100vh) translateX(${letter.xOffset}px) rotateX(360deg)`
                        : "translateY(0) translateX(0) rotateX(0)",
                      transition: `transform 1.5s cubic-bezier(0.34, 1.56, 0.64, 1) ${letter.startDelay}ms`,
                    }}
                  >
                    <span
                      className="text-xl sm:text-2xl md:text-3xl font-light relative inline-block transition-all duration-300 cursor-pointer px-0.5"
                      style={{
                        transform:
                          !isAnimating &&
                          getNeighborIndices(letter.id).some(
                            (n) => n.id === hoveredLetter
                          )
                            ? `translateY(${
                                Math.sin(
                                  getNeighborIndices(letter.id).find(
                                    (n) => n.id === hoveredLetter
                                  ).distance
                                ) * 4
                              }px)
                           scale(${
                             1 +
                             0.5 /
                               (getNeighborIndices(letter.id).find(
                                 (n) => n.id === hoveredLetter
                               ).distance +
                                 1)
                           })`
                            : "translateY(0) scale(1)",
                        color: getNeighborIndices(letter.id).some(
                          (n) => n.id === hoveredLetter
                        )
                          ? `hsl(${
                              170 +
                              getNeighborIndices(letter.id).find(
                                (n) => n.id === hoveredLetter
                              ).distance *
                                20
                            }, 100%, 80%)`
                          : "rgb(148, 163, 184)",
                        textShadow: getNeighborIndices(letter.id).some(
                          (n) => n.id === hoveredLetter
                        )
                          ? "0 0 10px rgba(255,255,255,0.3)"
                          : "0 0 5px rgba(255,255,255,0.1)",
                        transition:
                          "transform 0.3s ease-out, color 0.3s ease-out, text-shadow 0.3s ease-out",
                      }}
                    >
                      {letter.char}
                    </span>
                  </div>
                ))}
            </div>
          </div>
        </div>

        {/* Enhanced ground reflection */}
        <div className="absolute bottom-0 w-full">
          <div className="h-px bg-gradient-to-r from-transparent via-blue-500/30 to-transparent" />
          <div className="h-24 bg-gradient-to-t from-blue-950/50 to-transparent" />
        </div>
      </div>

      {/* Enhanced decorative elements */}
      <div className="absolute top-0 left-0 w-full h-24 bg-gradient-to-b from-black to-transparent opacity-80" />
      <div className="absolute bottom-0 left-0 w-full h-24 bg-gradient-to-t from-black to-transparent opacity-80" />

      <style jsx>{`
        @keyframes float {
          0%,
          100% {
            transform: translateY(0) translateX(0);
          }
          25% {
            transform: translateY(-20px) translateX(10px);
          }
          75% {
            transform: translateY(20px) translateX(-10px);
          }
        }
        .animate-float {
          animation: float linear infinite;
        }
      `}</style>
    </div>
  );
};

export default TextRain;
2

Implement the code as demonstrated in the preview