'use client'; import { forwardRef, HTMLAttributes, useEffect, useState, useCallback, useRef } from 'react'; export interface CharGlitchProps extends HTMLAttributes { children: string; intensity?: 'subtle' | 'medium' | 'intense'; variant?: 'blood' | 'cyber' | 'matrix' | 'corrupt'; mode?: 'random' | 'hover' | 'continuous' | 'wave'; interval?: number; glitchChars?: string; scramble?: boolean; } const GLITCH_CHARS = '!@#$%^&*()_+-=[]{}|;:,.<>?/~`0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'; const variantColors = { blood: { primary: 'text-rose-500', shadow: 'drop-shadow-[0_0_2px_#ff0040]' }, cyber: { primary: 'text-cyan-400', shadow: 'drop-shadow-[0_0_2px_#00ffff]' }, matrix: { primary: 'text-green-400', shadow: 'drop-shadow-[0_0_2px_#00ff00]' }, corrupt: { primary: 'text-white', shadow: 'drop-shadow-[0_0_2px_#ffffff]' }, }; export const CharGlitch = forwardRef( ( { children, intensity = 'medium', variant = 'blood', mode = 'random', interval = 100, glitchChars = GLITCH_CHARS, scramble = false, className = '', ...props }, ref ) => { const [glitchingIndices, setGlitchingIndices] = useState>(new Set()); const [scrambledChars, setScrambledChars] = useState([]); const [revealed, setRevealed] = useState([]); const intervalRef = useRef(); const durations = { subtle: 300, medium: 150, intense: 80 }; const triggerGlitch = useCallback((index: number) => { setGlitchingIndices(prev => new Set(prev).add(index)); setTimeout(() => { setGlitchingIndices(prev => { const next = new Set(prev); next.delete(index); return next; }); }, durations[intensity]); }, [intensity]); useEffect(() => { if (scramble) { setScrambledChars(children.split('').map(() => glitchChars[Math.floor(Math.random() * glitchChars.length)] )); setRevealed(new Array(children.length).fill(false)); let index = 0; const revealInterval = setInterval(() => { if (index < children.length) { setRevealed(prev => { const next = [...prev]; next[index] = true; return next; }); index++; } else { clearInterval(revealInterval); } }, 50); return () => clearInterval(revealInterval); } }, [children, scramble, glitchChars]); useEffect(() => { if (mode === 'random' && !scramble) { intervalRef.current = setInterval(() => { const randomIndex = Math.floor(Math.random() * children.length); triggerGlitch(randomIndex); }, interval); return () => { if (intervalRef.current) clearInterval(intervalRef.current); }; } if (mode === 'wave' && !scramble) { let waveIndex = 0; intervalRef.current = setInterval(() => { triggerGlitch(waveIndex % children.length); waveIndex++; }, interval); return () => { if (intervalRef.current) clearInterval(intervalRef.current); }; } }, [mode, interval, children, triggerGlitch, scramble]); return ( {children.split('').map((char, i) => { const isGlitching = glitchingIndices.has(i); const displayChar = scramble && !revealed[i] ? scrambledChars[i] : char; const { primary, shadow } = variantColors[variant]; return ( {displayChar} ); })} ); } ); CharGlitch.displayName = 'CharGlitch'; export default CharGlitch;