"use client"; import { useEffect, useState } from "react"; import { motion, useReducedMotion } from "motion/react"; import { cn } from "@/lib/utils"; const CHARSET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789/_-"; type ScrambleTextProps = { text: string; className?: string; durationMs?: number; }; export function ScrambleText({ text, className, durationMs = 1200 }: ScrambleTextProps) { const reduceMotion = useReducedMotion(); const [display, setDisplay] = useState(text); useEffect(() => { if (reduceMotion) { setDisplay(text); return; } let frame = 0; const totalFrames = Math.ceil(durationMs / 32); const locked = new Set(); const id = window.setInterval(() => { frame += 1; const progress = frame / totalFrames; for (let i = 0; i < text.length; i++) { if (text[i] === " ") continue; if (Math.random() < progress * 0.15 + 0.05) locked.add(i); } const next = text .split("") .map((char, i) => { if (char === " ") return " "; if (locked.has(i) || progress > 0.92) return char; return CHARSET[Math.floor(Math.random() * CHARSET.length)] ?? char; }) .join(""); setDisplay(next); if (frame >= totalFrames) { window.clearInterval(id); setDisplay(text); } }, 32); return () => window.clearInterval(id); }, [text, durationMs, reduceMotion]); return ( {display} ); }