feature/branding
tsx 101 lines 2.42 KB
Raw
1 "use client";
2
3 import { useEffect, useMemo, useState } from "react";
4
5 const INTERVAL = 2200;
6 const DURATION = 340;
7
8 interface WordCyclerProps {
9 words: string[];
10 className?: string;
11 offset?: number; // delay before starting, so two cyclers can sync
12 }
13
14 export function WordCycler({ words, className, offset = 0 }: WordCyclerProps) {
15 const shuffled = useMemo(() => {
16 const arr = [...words];
17 for (let i = arr.length - 1; i > 0; i--) {
18 const j = Math.floor(Math.random() * (i + 1));
19 [arr[i], arr[j]] = [arr[j], arr[i]];
20 }
21 return arr;
22 }, [words]);
23
24 const [index, setIndex] = useState(0);
25 const [phase, setPhase] = useState<"visible" | "exit" | "enter">("visible");
26
27 useEffect(() => {
28 const start = setTimeout(() => {
29 const timer = setInterval(() => {
30 setPhase("exit");
31
32 setTimeout(() => {
33 setIndex((i) => (i + 1) % shuffled.length);
34 setPhase("enter");
35
36 requestAnimationFrame(() => {
37 requestAnimationFrame(() => {
38 setPhase("visible");
39 });
40 });
41 }, DURATION);
42 }, INTERVAL);
43
44 return () => clearInterval(timer);
45 }, offset);
46
47 return () => clearTimeout(start);
48 }, [words, offset]);
49
50 return (
51 <span
52 style={{
53 display: "inline-block",
54 overflow: "hidden",
55 verticalAlign: "bottom",
56 lineHeight: "inherit",
57 }}
58 >
59 <span
60 className={className}
61 style={{
62 display: "inline-block",
63 transition:
64 phase === "enter"
65 ? "none"
66 : `transform ${DURATION}ms cubic-bezier(0.4,0,0.2,1), opacity ${DURATION}ms ease`,
67 transform:
68 phase === "visible"
69 ? "translateY(0)"
70 : phase === "exit"
71 ? "translateY(-110%)"
72 : "translateY(110%)",
73 opacity: phase === "visible" ? 1 : 0,
74 }}
75 >
76 {shuffled[index]}
77 </span>
78 </span>
79 );
80 }
81
82 const SERVICE_WORDS = ["Deploy", "Auth", "Mail", "GresIQ"];
83 const FRAMEWORK_WORDS = ["Rust", "Tauri", "Next.js", "Ruby on Rails", "Vapor"];
84
85 export function ServiceWordCycler() {
86 return (
87 <WordCycler
88 words={SERVICE_WORDS}
89 className="text-indigo-600 dark:text-indigo-400"
90 />
91 );
92 }
93
94 export function FrameworkWordCycler() {
95 return (
96 <WordCycler
97 words={FRAMEWORK_WORDS}
98 className="text-indigo-600 dark:text-indigo-400"
99 />
100 );
101 }