UI Elements
Hello

Word Preload

An engaging text loading animation component that reveals words with smooth transitions, perfect for splash screens and content reveals.

word-preload.tsx
"use client"
import { useEffect, useState } from "react"
import { motion, AnimatePresence, useReducedMotion } from "motion/react"

const words = ["Hello", "Hola", "Bonjour", "Hallo", "Ciao", "नमस्ते"]

// Accelerating rhythm — words appear faster as sequence builds momentum
const wordDurations = [650, 600, 550, 520, 500, 800]

// ease-out-quint: snappy entrance, smooth settle
const easeOutQuint = [0.23, 1, 0.32, 1] as const

export default function WordPreload() {
  const [index, setIndex] = useState(0)
  const [finished, setFinished] = useState(false)
  const [showLogo, setShowLogo] = useState(false)
  const shouldReduceMotion = useReducedMotion()

  // Cycle through words with accelerating rhythm
  useEffect(() => {
    if (finished) return
    if (index < words.length - 1) {
      const timer = setTimeout(() => setIndex((i) => i + 1), wordDurations[index])
      return () => clearTimeout(timer)
    } else {
      const timer = setTimeout(() => {
        setShowLogo(true)
        setTimeout(() => setFinished(true), 1500)
      }, wordDurations[words.length - 1])
      return () => clearTimeout(timer)
    }
  }, [index, finished])

  return (
    <div className="relative w-full h-[800px] bg-background text-foreground flex items-center justify-center overflow-hidden">
      <div className="relative w-full h-full flex items-center justify-center">
        {/* Word cycle */}
        <AnimatePresence mode="wait">
          {!showLogo && (
            <motion.div
              key={index}
              initial={shouldReduceMotion ? false : { opacity: 0, scale: 0.94, y: 14 }}
              animate={{ opacity: 1, scale: 1, y: 0 }}
              exit={{ opacity: 0, scale: 0.96, y: -10 }}
              transition={{
                duration: 0.38,
                exit: { duration: 0.3 },
                ease: easeOutQuint,
              }}
              className="absolute text-7xl font-light tracking-tight text-foreground will-change-transform"
              style={{ fontFamily: "system-ui, -apple-system, sans-serif" }}
            >
              {words[index]}
            </motion.div>
          )}
        </AnimatePresence>

        {/* Logo reveal */}
        <AnimatePresence>
          {showLogo && !finished && (
            <motion.div
              initial={shouldReduceMotion ? false : { opacity: 0, scale: 0.85 }}
              animate={{ opacity: 1, scale: 1 }}
              exit={{ opacity: 0, scale: 1.03 }}
              transition={{
                duration: 0.55,
                ease: easeOutQuint,
              }}
              className="absolute flex flex-col items-center space-y-6 will-change-transform"
            >
              <div className="w-16 h-16 text-foreground">
                <svg viewBox="0 0 24 24" fill="currentColor" className="w-full h-full">
                  <path d="M18.71 19.5c-.83 1.24-1.71 2.45-3.05 2.47-1.34.03-1.77-.79-3.29-.79-1.53 0-2 .77-3.27.82-1.31.05-2.3-1.32-3.14-2.53C4.25 17 2.94 12.45 4.7 9.39c.87-1.52 2.43-2.48 4.12-2.51 1.28-.02 2.5.87 3.29.87.78 0 2.26-1.07 3.81-.91.65.03 2.47.26 3.64 1.98-.09.06-2.17 1.28-2.15 3.81.03 3.02 2.65 4.03 2.68 4.04-.03.07-.42 1.44-1.38 2.83M13 3.5c.73-.83 1.94-1.46 2.94-1.5.13 1.17-.34 2.35-1.04 3.19-.69.85-1.83 1.51-2.95 1.42-.15-1.15.41-2.35 1.05-3.11z" />
                </svg>
              </div>

              {/* Loading dots */}
              <div className="flex space-x-1.5">
                {[0, 1, 2].map((i) => (
                  <motion.div
                    key={i}
                    className="w-2 h-2 bg-muted-foreground rounded-full"
                    animate={{
                      scale: [1, 1.3, 1],
                      opacity: [0.3, 1, 0.3],
                    }}
                    transition={{
                      duration: 1.2,
                      repeat: Infinity,
                      delay: i * 0.15,
                      ease: [0.45, 0, 0.55, 1],
                    }}
                  />
                ))}
              </div>
            </motion.div>
          )}
        </AnimatePresence>

        {/* Screen reveal */}
        <AnimatePresence>
          {finished && (
            <motion.div
              initial={{
                clipPath: "ellipse(0% 0% at 50% 100%)",
                opacity: 0,
              }}
              animate={{
                clipPath: "ellipse(150% 150% at 50% 100%)",
                opacity: 1,
              }}
              transition={{
                duration: 0.9,
                ease: easeOutQuint,
                delay: 0.12,
              }}
              className="absolute inset-0 w-full h-full bg-neutral-800 flex flex-col items-center justify-center overflow-hidden"
            >
              <motion.div
                initial={shouldReduceMotion ? false : { opacity: 0, y: 24 }}
                animate={{ opacity: 1, y: 0 }}
                transition={{
                  duration: 0.6,
                  delay: 0.5,
                  ease: easeOutQuint,
                }}
                className="text-center space-y-6 max-w-2xl px-8 will-change-transform"
              >
                <h1
                  className="text-6xl md:text-7xl font-semibold tracking-tight text-foreground text-balance"
                  style={{ fontFamily: "system-ui, -apple-system, sans-serif" }}
                >
                  Your Vibe Coded Landing Page
                </h1>
              </motion.div>
            </motion.div>
          )}
        </AnimatePresence>
      </div>
    </div>
  )
}