| "use client"; |
|
|
| import { useState, useEffect } from "react"; |
|
|
| interface AnimatedTextProps { |
| className?: string; |
| } |
|
|
| export function AnimatedText({ className = "" }: AnimatedTextProps) { |
| const [displayText, setDisplayText] = useState(""); |
| const [currentSuggestionIndex, setCurrentSuggestionIndex] = useState(0); |
| const [isTyping, setIsTyping] = useState(true); |
| const [showCursor, setShowCursor] = useState(true); |
| const [lastTypedIndex, setLastTypedIndex] = useState(-1); |
| const [animationComplete, setAnimationComplete] = useState(false); |
|
|
| |
| const [suggestions] = useState(() => { |
| const baseSuggestions = [ |
| "create a stunning portfolio!", |
| "build a tic tac toe game!", |
| "design a website for my restaurant!", |
| "make a sleek landing page!", |
| "build an e-commerce store!", |
| "create a personal blog!", |
| "develop a modern dashboard!", |
| "design a company website!", |
| "build a todo app!", |
| "create an online gallery!", |
| "make a contact form!", |
| "build a weather app!", |
| ]; |
|
|
| |
| const shuffled = [...baseSuggestions]; |
| for (let i = shuffled.length - 1; i > 0; i--) { |
| const j = Math.floor(Math.random() * (i + 1)); |
| [shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]]; |
| } |
|
|
| return shuffled; |
| }); |
|
|
| useEffect(() => { |
| if (animationComplete) return; |
|
|
| let timeout: NodeJS.Timeout; |
|
|
| const typeText = () => { |
| const currentSuggestion = suggestions[currentSuggestionIndex]; |
|
|
| if (isTyping) { |
| if (displayText.length < currentSuggestion.length) { |
| setDisplayText(currentSuggestion.slice(0, displayText.length + 1)); |
| setLastTypedIndex(displayText.length); |
| timeout = setTimeout(typeText, 80); |
| } else { |
| |
| setLastTypedIndex(-1); |
| timeout = setTimeout(() => { |
| setIsTyping(false); |
| }, 2000); |
| } |
| } |
| }; |
|
|
| timeout = setTimeout(typeText, 100); |
| return () => clearTimeout(timeout); |
| }, [ |
| displayText, |
| currentSuggestionIndex, |
| isTyping, |
| suggestions, |
| animationComplete, |
| ]); |
|
|
| |
| useEffect(() => { |
| if (animationComplete) { |
| setShowCursor(false); |
| return; |
| } |
|
|
| const cursorInterval = setInterval(() => { |
| setShowCursor((prev) => !prev); |
| }, 600); |
|
|
| return () => clearInterval(cursorInterval); |
| }, [animationComplete]); |
|
|
| useEffect(() => { |
| if (lastTypedIndex >= 0) { |
| const timeout = setTimeout(() => { |
| setLastTypedIndex(-1); |
| }, 400); |
|
|
| return () => clearTimeout(timeout); |
| } |
| }, [lastTypedIndex]); |
|
|
| return ( |
| <p className={`font-mono ${className}`}> |
| Hey DeepSite, |
| {displayText.split("").map((char, index) => ( |
| <span |
| key={`${currentSuggestionIndex}-${index}`} |
| className={`transition-colors duration-300 ${ |
| index === lastTypedIndex ? "text-neutral-100" : "" |
| }`} |
| > |
| {char} |
| </span> |
| ))} |
| <span |
| className={`${ |
| showCursor ? "opacity-100" : "opacity-0" |
| } transition-opacity`} |
| > |
| | |
| </span> |
| </p> |
| ); |
| } |
|
|