ComponentsAboutContactPrivacyTerms

Built by Lacy Morrow

    Close file
    Close file
    Close file
    Close file
    'use client';
    import { type JSX, useEffect, useState } from 'react';
    import { motion, MotionProps } from 'motion/react';
    
    export type TextScrambleProps = {
      children: string;
      duration?: number;
      speed?: number;
      characterSet?: string;
      as?: React.ElementType;
      className?: string;
      trigger?: boolean;
      onScrambleComplete?: () => void;
    } & MotionProps;
    
    const defaultChars =
      'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
    
    export function TextScramble({
      children,
      duration = 0.8,
      speed = 0.04,
      characterSet = defaultChars,
      className,
      as: Component = 'p',
      trigger = true,
      onScrambleComplete,
      ...props
    }: TextScrambleProps) {
      const MotionComponent = motion.create(
        Component as keyof JSX.IntrinsicElements
      );
      const [displayText, setDisplayText] = useState(children);
      const [isAnimating, setIsAnimating] = useState(false);
      const text = children;
    
      const scramble = async () => {
        if (isAnimating) return;
        setIsAnimating(true);
    
        const steps = duration / speed;
        let step = 0;
    
        const interval = setInterval(() => {
          let scrambled = '';
          const progress = step / steps;
    
          for (let i = 0; i < text.length; i++) {
            if (text[i] === ' ') {
              scrambled += ' ';
              continue;
            }
    
            if (progress * text.length > i) {
              scrambled += text[i];
            } else {
              scrambled +=
                characterSet[Math.floor(Math.random() * characterSet.length)];
            }
          }
    
          setDisplayText(scrambled);
          step++;
    
          if (step > steps) {
            clearInterval(interval);
            setDisplayText(text);
            setIsAnimating(false);
            onScrambleComplete?.();
          }
        }, speed * 1000);
      };
    
      useEffect(() => {
        if (!trigger) return;
    
        scramble();
      }, [trigger]);
    
      return (
        <MotionComponent className={className} {...props}>
          {displayText}
        </MotionComponent>
      );
    }