import React from 'react'; import { useCurrentFrame, useVideoConfig, interpolate, Easing, AbsoluteFill } from 'remotion'; // ============================================================================= // COMPOSITION CONFIG (Required for auto-discovery) // ============================================================================= export const compositionConfig = { id: 'TheRenderHook', durationInSeconds: 5, fps: 60, width: 1080, height: 1920, }; // ============================================================================= // PRE-GENERATED DATA // ============================================================================= const seededRandom = (seed: number): number => { const x = Math.sin(seed * 9999) * 10000; return x - Math.floor(x); }; // Window shatter fragments const shatterFragments = Array.from({ length: 40 }, (_, i) => ({ id: i, x: seededRandom(i * 1) * 900 - 450, y: seededRandom(i * 2) * 1100 - 550, rotation: seededRandom(i * 3) * 720 - 360, velocityX: (seededRandom(i * 4) - 0.5) * 1200, velocityY: (seededRandom(i * 5) - 0.5) * 1200, rotationSpeed: (seededRandom(i * 6) - 0.5) * 1080, width: 60 + seededRandom(i * 7) * 120, height: 60 + seededRandom(i * 8) * 120, clipPath: `polygon(${seededRandom(i * 9) * 30}% 0%, 100% ${seededRandom(i * 10) * 30}%, ${100 - seededRandom(i * 11) * 30}% 100%, 0% ${100 - seededRandom(i * 12) * 30}%)`, })); // Particle dust for explosion const dustParticles = Array.from({ length: 60 }, (_, i) => ({ id: i, startX: seededRandom(i * 13) * 800 - 400, startY: seededRandom(i * 14) * 900 - 450, velocityX: (seededRandom(i * 15) - 0.5) * 800, velocityY: (seededRandom(i * 16) - 0.5) * 800, size: 3 + seededRandom(i * 17) * 8, color: ['#00ffff', '#ff00ff', '#00bfff', '#ffffff'][Math.floor(seededRandom(i * 18) * 4)], })); // Background floating particles const floatingParticles = Array.from({ length: 20 }, (_, i) => ({ id: i, x: seededRandom(i * 19) * 1080, startY: 1920 + seededRandom(i * 20) * 200, size: 2 + seededRandom(i * 21) * 4, speed: 0.3 + seededRandom(i * 22) * 0.5, opacity: 0.1 + seededRandom(i * 23) * 0.2, })); // Glitch timing data const glitchFrames = [45, 78, 112, 145, 168]; const previewContents = [ { type: 'wireframe', color: '#00ffff' }, { type: 'partial', color: '#ff00ff' }, { type: 'particles', color: '#00bfff' }, { type: 'wave', color: '#00ffff' }, { type: 'cube', color: '#ff00ff' }, { type: 'bars', color: '#00bfff' }, { type: 'final', color: '#ffffff' }, ]; // Status text timeline const statusTexts = [ { start: 30, end: 60, text: 'Initializing render pipeline...' }, { start: 60, end: 90, text: 'Processing frames...' }, { start: 90, end: 120, text: 'Applying effects...' }, { start: 120, end: 150, text: 'Compositing layers...' }, { start: 150, end: 180, text: 'Finalizing output...' }, ]; // Easing presets const easeOut = Easing.bezier(0.33, 1, 0.68, 1); const easeInOut = Easing.bezier(0.37, 0, 0.63, 1); const overshoot = Easing.bezier(0.34, 1.56, 0.64, 1); // ============================================================================= // SUB-COMPONENTS // ============================================================================= const TrafficLights: React.FC = () => (
); const WireframePreview: React.FC<{ frame: number }> = ({ frame }) => { const rotation = (frame * 2) % 360; return (
); }; const ParticlePreview: React.FC<{ frame: number }> = ({ frame }) => { const particles = Array.from({ length: 30 }, (_, i) => ({ x: 50 + Math.cos((frame * 0.1 + i * 0.5)) * (20 + i * 2), y: 50 + Math.sin((frame * 0.1 + i * 0.7)) * (20 + i * 2), })); return (
{particles.map((p, i) => ( ))}
); }; const WavePreview: React.FC<{ frame: number }> = ({ frame }) => { const points = Array.from({ length: 50 }, (_, i) => { const x = i * 2; const y = 50 + Math.sin((i * 0.3) + frame * 0.15) * 25; return `${x},${y}`; }).join(' '); return (
); }; const CubePreview: React.FC<{ frame: number }> = ({ frame }) => { const rotation = frame * 3; return (
); }; const ColorBarsPreview: React.FC = () => (
{['#ff0000', '#00ff00', '#0000ff', '#ffff00', '#00ffff', '#ff00ff', '#ffffff'].map((color, i) => (
))}
); const FinalPreview: React.FC = () => (
{Array.from({ length: 10 }, (_, i) => (
))}
); // ============================================================================= // MAIN COMPONENT // ============================================================================= const TheRenderHook: React.FC = () => { const frame = useCurrentFrame(); const { fps } = useVideoConfig(); // Phase calculations const windowFadeIn = frame <= 20; const previewInit = frame > 20 && frame <= 30; const renderProgress = frame > 30 && frame <= 180; const completionBurst = frame > 180 && frame <= 220; const windowExplosion = frame > 220 && frame <= 240; const textReveal = frame > 240 && frame <= 280; const arrowPhase = frame > 280; const showWindow = frame <= 220; // Window appearance const windowOpacity = interpolate(frame, [0, 20], [0, 1], { extrapolateRight: 'clamp', extrapolateLeft: 'clamp' }); const windowScale = interpolate(frame, [0, 20], [0.95, 1], { extrapolateRight: 'clamp', extrapolateLeft: 'clamp', easing: overshoot }); // Progress bar calculation with realistic stutters const getProgress = (f: number): number => { if (f < 30) return 0; if (f >= 180) return 100; const progressFrame = f - 30; const totalProgressFrames = 150; // Non-linear progress with stutters if (progressFrame < 20) return interpolate(progressFrame, [0, 20], [0, 23], { extrapolateRight: 'clamp', extrapolateLeft: 'clamp' }); if (progressFrame < 50) return interpolate(progressFrame, [20, 50], [23, 45], { extrapolateRight: 'clamp', extrapolateLeft: 'clamp' }); if (progressFrame < 90) return interpolate(progressFrame, [50, 90], [45, 78], { extrapolateRight: 'clamp', extrapolateLeft: 'clamp' }); if (progressFrame < 130) return interpolate(progressFrame, [90, 130], [78, 94], { extrapolateRight: 'clamp', extrapolateLeft: 'clamp' }); return interpolate(progressFrame, [130, 150], [94, 100], { extrapolateRight: 'clamp', extrapolateLeft: 'clamp', easing: overshoot }); }; const progress = getProgress(frame); // Glitch effect calculations const isGlitching = glitchFrames.some(gf => Math.abs(frame - gf) < 3); const glitchOffsetX = isGlitching ? (seededRandom(frame) - 0.5) * 20 : 0; const rgbSplitAmount = isGlitching ? 5 : 0; // Completion burst glitch (frames 180-200) const burstGlitch = frame > 180 && frame <= 195; const burstIntensity = burstGlitch ? interpolate(frame, [180, 187, 195], [0, 1, 0], { extrapolateRight: 'clamp', extrapolateLeft: 'clamp' }) : 0; const burstFlash = frame > 183 && frame < 186 ? 0.8 : 0; const burstSplitX = burstIntensity * 20; const burstTearOffset = burstIntensity * 30; // Done checkmark animation const showDone = frame > 200 && frame <= 220; const doneScale = interpolate(frame, [200, 215], [0, 1], { extrapolateRight: 'clamp', extrapolateLeft: 'clamp', easing: overshoot }); const doneOpacity = interpolate(frame, [200, 205], [0, 1], { extrapolateRight: 'clamp', extrapolateLeft: 'clamp' }); // Get current preview type const getPreviewIndex = (f: number): number => { if (f < 30) return -1; const cycleFrame = (f - 30) % 25; const cycleIndex = Math.floor((f - 30) / 25); return cycleIndex % previewContents.length; }; // Get current status text const getCurrentStatus = (f: number): string => { if (f < 30) return 'Initializing...'; const status = statusTexts.find(s => f >= s.start && f < s.end); return status?.text || 'Finalizing output...'; }; // Window explosion animation const explosionProgress = interpolate(frame, [220, 240], [0, 1], { extrapolateRight: 'clamp', extrapolateLeft: 'clamp', easing: easeOut }); // Text reveal animations const line1Opacity = interpolate(frame, [240, 250], [0, 1], { extrapolateRight: 'clamp', extrapolateLeft: 'clamp' }); const line1X = interpolate(frame, [240, 255], [-100, 0], { extrapolateRight: 'clamp', extrapolateLeft: 'clamp', easing: overshoot }); const line2Opacity = interpolate(frame, [250, 260], [0, 1], { extrapolateRight: 'clamp', extrapolateLeft: 'clamp' }); const line2GlitchOffset = frame > 250 && frame < 258 ? (seededRandom(frame) - 0.5) * 10 : 0; const line3Opacity = interpolate(frame, [260, 270], [0, 1], { extrapolateRight: 'clamp', extrapolateLeft: 'clamp' }); const line3Scale = interpolate(frame, [260, 275], [0, 1], { extrapolateRight: 'clamp', extrapolateLeft: 'clamp', easing: overshoot }); // Text glow pulse after settle const textGlow = frame > 275 ? interpolate((frame - 275) % 40, [0, 20, 40], [0, 10, 0], { extrapolateRight: 'clamp', extrapolateLeft: 'clamp' }) : 0; // Arrow animation const arrowOpacity = interpolate(frame, [280, 290], [0, 1], { extrapolateRight: 'clamp', extrapolateLeft: 'clamp' }); const arrowBounce = frame > 280 ? Math.sin((frame - 280) * 0.3) * 15 : 0; const arrowGlow = frame > 280 ? interpolate((frame - 280) % 20, [0, 10, 20], [5, 15, 5], { extrapolateRight: 'clamp', extrapolateLeft: 'clamp' }) : 0; // Preview cycling speed increases over time const previewCycleSpeed = interpolate(frame, [30, 180], [6, 2], { extrapolateRight: 'clamp', extrapolateLeft: 'clamp' }); const currentPreviewIndex = Math.floor((frame - 30) / previewCycleSpeed) % previewContents.length; // Scanline effect const scanlineY = (frame * 8) % 600; return ( {/* Noise texture overlay */}
{/* Floating particles (after explosion) */} {frame > 240 && floatingParticles.map(particle => { const y = particle.startY - (frame - 240) * particle.speed * 3; if (y < -10) return null; return (
); })} {/* Render Window */} {showWindow && (
{/* Window frame */}
{/* Title bar */}
Render Output
{/* Preview area with tear effect during burst */}
{burstGlitch && ( <> {/* Top tear */}
{/* Bottom tear */}
)} {/* Main preview */}
{/* RGB split layers during glitch */} {(isGlitching || burstGlitch) && ( <>
)} {/* Scanline during render */} {renderProgress && (
)} {/* Preview content */} {previewInit && (
Initializing...
)} {renderProgress && ( <> {currentPreviewIndex === 0 && } {currentPreviewIndex === 1 && (
RENDER
)} {currentPreviewIndex === 2 && } {currentPreviewIndex === 3 && } {currentPreviewIndex === 4 && } {currentPreviewIndex === 5 && } {currentPreviewIndex === 6 && } )} {showDone && (
)}
{/* Status bar */}
{/* Status text and percentage */}
{showDone ? 'Complete!' : getCurrentStatus(frame)} {Math.round(progress)}%
{/* Progress bar */}
{/* Progress fill */}
{/* Glow overlay */}
{/* Time estimate */} {!showDone && (
⏱ Estimated: {((100 - progress) * 0.003).toFixed(1)}s remaining
)}
)} {/* Burst flash overlay */} {burstFlash > 0 && (
)} {/* Explosion fragments */} {windowExplosion && shatterFragments.map(fragment => { const fragX = fragment.x + fragment.velocityX * explosionProgress; const fragY = fragment.y + fragment.velocityY * explosionProgress; const fragRotation = fragment.rotation + fragment.rotationSpeed * explosionProgress; const fragOpacity = 1 - explosionProgress; return (
); })} {/* Dust particles */} {windowExplosion && dustParticles.map(particle => { const pX = particle.startX + particle.velocityX * explosionProgress; const pY = particle.startY + particle.velocityY * explosionProgress; const pOpacity = 1 - explosionProgress; return (
); })} {/* Hook text reveal */} {frame > 240 && (
{/* Line 1: Create Videos */}
Create Videos
{/* Line 2: With Code */}
With Code
{/* Line 3: In Seconds */}
In Seconds
)} {/* Arrow CTA */} {frame > 280 && (
{/* Arrow */}
{/* Text below arrow */}
See what's possible
)} ); }; export default TheRenderHook;