/** * Template: Cinematic Preloader * * A reusable preloader component with customizable themes. * Features: Boot sequence, progress indicator, text scramble, canvas animation */ import React, { useState, useEffect, useRef } from 'react'; import { motion } from 'framer-motion'; // ============================================================================= // TYPES // ============================================================================= interface PreloaderProps { onComplete: () => void; theme?: 'cyber' | 'minimal' | 'matrix'; accentColor?: string; bootMessages?: string[]; duration?: number; } interface PreloaderTheme { background: string; accent: string; text: string; muted: string; } // ============================================================================= // THEMES // ============================================================================= const THEMES: Record = { cyber: { background: '#050505', accent: '#ff4d00', text: '#ffffff', muted: '#71717a', }, minimal: { background: '#0a0a0a', accent: '#ffffff', text: '#ffffff', muted: '#52525b', }, matrix: { background: '#000000', accent: '#00ff00', text: '#00ff00', muted: '#003300', }, }; // ============================================================================= // DEFAULT BOOT MESSAGES // ============================================================================= const DEFAULT_BOOT_MESSAGES = [ 'INITIALIZING CORE SYSTEMS...', 'LOADING NEURAL NETWORKS...', 'ESTABLISHING SECURE CONNECTION...', 'SYNCING DATA PROTOCOLS...', 'CALIBRATING INTERFACE...', 'SYSTEM READY', ]; // ============================================================================= // HOOKS // ============================================================================= /** * Text scramble effect hook */ const useTextScramble = (finalText: string, trigger: boolean, speed = 30) => { const [displayText, setDisplayText] = useState(''); const chars = '!<>-_\\/[]{}—=+*^?#________'; useEffect(() => { if (!trigger) { setDisplayText(''); return; } let iteration = 0; const interval = setInterval(() => { setDisplayText( finalText .split('') .map((char, i) => { if (char === ' ') return ' '; if (i < iteration) return char; return chars[Math.floor(Math.random() * chars.length)]; }) .join('') ); iteration += 1 / 3; if (iteration >= finalText.length) { clearInterval(interval); setDisplayText(finalText); } }, speed); return () => clearInterval(interval); }, [finalText, trigger, speed]); return displayText; }; // ============================================================================= // CANVAS ANIMATIONS // ============================================================================= /** * Data Grid Canvas Animation */ const DataGridCanvas: React.FC<{ accent: string }> = ({ accent }) => { const canvasRef = useRef(null); useEffect(() => { const canvas = canvasRef.current; if (!canvas) return; const ctx = canvas.getContext('2d'); if (!ctx) return; const resize = () => { canvas.width = window.innerWidth; canvas.height = window.innerHeight; }; resize(); window.addEventListener('resize', resize); const cellSize = 25; let scanY = 0; let animationId: number; const animate = () => { ctx.fillStyle = '#050505'; ctx.fillRect(0, 0, canvas.width, canvas.height); const cols = Math.ceil(canvas.width / cellSize); const rows = Math.ceil(canvas.height / cellSize); // Draw active cells near scan line for (let y = 0; y < rows; y++) { for (let x = 0; x < cols; x++) { const distFromScan = Math.abs(y * cellSize - scanY); if (distFromScan < 80 && Math.random() > 0.75) { const alpha = 1 - distFromScan / 80; ctx.fillStyle = accent + Math.floor(alpha * 255).toString(16).padStart(2, '0'); ctx.fillRect( x * cellSize + 2, y * cellSize + 2, cellSize - 4, cellSize - 4 ); } } } // Draw scan line ctx.fillStyle = accent; ctx.fillRect(0, scanY - 1, canvas.width, 2); ctx.fillStyle = accent + '40'; ctx.fillRect(0, scanY - 20, canvas.width, 40); scanY += 4; if (scanY > canvas.height) scanY = 0; animationId = requestAnimationFrame(animate); }; animate(); return () => { cancelAnimationFrame(animationId); window.removeEventListener('resize', resize); }; }, [accent]); return ; }; /** * Matrix Rain Canvas Animation */ const MatrixCanvas: React.FC<{ accent: string }> = ({ accent }) => { const canvasRef = useRef(null); useEffect(() => { const canvas = canvasRef.current; if (!canvas) return; const ctx = canvas.getContext('2d'); if (!ctx) return; canvas.width = window.innerWidth; canvas.height = window.innerHeight; const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789@#$%^&*'; const fontSize = 14; const columns = Math.floor(canvas.width / fontSize); const drops: number[] = Array(columns).fill(1); let animationId: number; const animate = () => { ctx.fillStyle = 'rgba(0, 0, 0, 0.05)'; ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.fillStyle = accent; ctx.font = `${fontSize}px monospace`; for (let i = 0; i < drops.length; i++) { const char = chars[Math.floor(Math.random() * chars.length)]; ctx.fillText(char, i * fontSize, drops[i] * fontSize); if (drops[i] * fontSize > canvas.height && Math.random() > 0.975) { drops[i] = 0; } drops[i]++; } animationId = requestAnimationFrame(animate); }; animate(); return () => cancelAnimationFrame(animationId); }, [accent]); return ; }; // ============================================================================= // PROGRESS INDICATORS // ============================================================================= /** * Circular HUD Progress */ const CircularProgress: React.FC<{ progress: number; accent: string; text: string; }> = ({ progress, accent, text }) => { const circumference = 2 * Math.PI * 58; const strokeDashoffset = circumference - (circumference * progress) / 100; return (
{/* Background circle */} {/* Progress circle */} {/* Glow effect */}
{Math.floor(progress)}%
); }; /** * Linear Bar Progress */ const LinearProgress: React.FC<{ progress: number; accent: string; }> = ({ progress, accent }) => (
); // ============================================================================= // CONSOLE LOG DISPLAY // ============================================================================= const BootConsole: React.FC<{ messages: string[]; currentIndex: number; accent: string; muted: string; }> = ({ messages, currentIndex, accent, muted }) => (
{messages.slice(0, currentIndex + 1).map((msg, i) => (
[{String(i).padStart(2, '0')}] {msg}
))}
); // ============================================================================= // MAIN PRELOADER COMPONENT // ============================================================================= export const CinematicPreloader: React.FC = ({ onComplete, theme = 'cyber', accentColor, bootMessages = DEFAULT_BOOT_MESSAGES, duration = 3000, }) => { const [progress, setProgress] = useState(0); const [currentLog, setCurrentLog] = useState(0); const [complete, setComplete] = useState(false); const colors = THEMES[theme]; const accent = accentColor || colors.accent; const scrambledText = useTextScramble('SYSTEM READY', complete, 30); // Progress simulation useEffect(() => { const interval = setInterval(() => { setProgress(prev => { if (prev >= 100) { clearInterval(interval); setComplete(true); setTimeout(onComplete, 800); return 100; } return Math.min(prev + Math.random() * (100 / (duration / 50)), 100); }); }, 50); return () => clearInterval(interval); }, [onComplete, duration]); // Log progression useEffect(() => { const logInterval = duration / bootMessages.length; const interval = setInterval(() => { setCurrentLog(prev => Math.min(prev + 1, bootMessages.length - 1)); }, logInterval); return () => clearInterval(interval); }, [bootMessages.length, duration]); // Render canvas based on theme const renderCanvas = () => { switch (theme) { case 'matrix': return ; default: return ; } }; return ( {renderCanvas()}
{/* Progress Indicator */} {/* Status Text */} {complete && ( {scrambledText} )} {/* Boot Console */}
{/* Scanlines overlay */}
); }; // ============================================================================= // USAGE EXAMPLE // ============================================================================= /* import { CinematicPreloader } from './templates/preloader'; function App() { const [loading, setLoading] = useState(true); return ( <> {loading && ( setLoading(false)} theme="cyber" accentColor="#ff4d00" bootMessages={[ 'INITIALIZING ENGINE...', 'LOADING ASSETS...', 'CONNECTING TO SERVER...', 'READY', ]} duration={3000} /> )} {!loading && } ); } */ export default CinematicPreloader;