Personal Website for @jaspermayone.com
jaspermayone.com
1"use client";
2import localFont from "next/font/local";
3import { useEffect, useState } from "react";
4
5const CuteNotes = localFont({
6 src: "../../public/fonts/CuteNotes.ttf",
7 display: "swap",
8 variable: "--font-cute-notes",
9});
10
11interface AnimatedTitleProps {
12 firstWord: string;
13 secondWord?: string;
14 thirdWord?: string;
15 color?: string;
16}
17
18const AnimatedTitle = (props: AnimatedTitleProps) => {
19 const [activeIndex, setActiveIndex] = useState(-1);
20 const firstWord = props.firstWord;
21 const secondWord = props.secondWord;
22 const thirdWord = props.thirdWord;
23 const totalLength =
24 firstWord.length +
25 (secondWord ? secondWord.length : 0) +
26 (thirdWord ? thirdWord.length : 0);
27 const LETTER_DELAY = 300;
28 const CYCLE_PAUSE = 2000;
29
30 const color = props.color || "inherit";
31
32 useEffect(() => {
33 const animate = async () => {
34 while (true) {
35 for (let i = 0; i <= totalLength; i++) {
36 setActiveIndex(i === totalLength ? -1 : i);
37 await new Promise((resolve) => setTimeout(resolve, LETTER_DELAY));
38 }
39 await new Promise((resolve) => setTimeout(resolve, CYCLE_PAUSE));
40 }
41 };
42
43 animate();
44 return () => setActiveIndex(-1);
45 }, []);
46
47 return (
48 <h1
49 className={`m-0 text-5xl ${CuteNotes.className}`}
50 style={{
51 margin: 0,
52 marginBottom: ".25rem",
53 fontSize: "3.5em",
54 }}
55 title={`${firstWord} ${secondWord}`}
56 >
57 {firstWord.split("").map((letter, index) => (
58 <span
59 key={`first-${index}`}
60 className="transition-colors duration-300"
61 style={{
62 color: index === activeIndex ? "#4299e1" : color,
63 }}
64 >
65 {letter}
66 </span>
67 ))}{" "}
68 {secondWord?.split("").map((letter, index) => (
69 <span
70 key={`second-${index}`}
71 className="transition-colors duration-300"
72 style={{
73 color: index + firstWord.length === activeIndex ? "#4299e1" : color,
74 }}
75 >
76 {letter}
77 </span>
78 ))}{" "}
79 {thirdWord?.split("").map((letter, index) => (
80 <span
81 key={`third-${index}`}
82 className="transition-colors duration-300"
83 style={{
84 color:
85 index +
86 firstWord.length +
87 (secondWord ? secondWord.length : 0) ===
88 activeIndex
89 ? "#4299e1"
90 : color,
91 }}
92 >
93 {letter}
94 </span>
95 ))}
96 </h1>
97 );
98};
99
100export default AnimatedTitle;