import React from 'react';
import {
useCurrentFrame,
useVideoConfig,
interpolate,
Easing,
AbsoluteFill,
Img,
} from 'remotion';
// =============================================================================
// COMPOSITION CONFIG
// =============================================================================
export const compositionConfig = {
id: 'TheProcess',
durationInSeconds: 10,
fps: 30,
width: 2160,
height: 2160,
};
// =============================================================================
// STYLE CONSTANTS
// =============================================================================
const COLORS = {
primary: '#7C3AED',
secondary: '#A78BFA',
accent: '#C084FC',
background: '#0A0A12',
text: '#FFFFFF',
} as const;
const EASINGS = {
easeOut: Easing.bezier(0.33, 1, 0.68, 1),
easeInOut: Easing.bezier(0.37, 0, 0.63, 1),
};
const CAT_IMAGE_URL =
'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQWRoqiCsMF9IV50udIf1AVIV2cvHf7DK2l4Q&s';
// =============================================================================
// NOISE CONFIG — matching Slide 3
// =============================================================================
const GRID_SIZE = 120;
const PIXEL_SIZE = 2160 / GRID_SIZE;
const NUM_NOISE_FRAMES = 6;
// =============================================================================
// TIMING
// =============================================================================
const DENOISE_START = 15; // frame 0.5s — start denoising
const DENOISE_END = 195; // frame 6.5s — fully clean
const HOLD_START = 195;
const HOLD_END = 240; // hold clean for ~1.5s
const RESET_START = 240;
const RESET_END = 270; // fade back to noise for loop
const TOTAL_STEPS = 50;
// =============================================================================
// PRE-GENERATED NOISE DATA
// =============================================================================
const seededRandom = (seed: number): number => {
const x = Math.sin(seed * 9999) * 10000;
return x - Math.floor(x);
};
const getNoiseColor = (seed: number): string => {
const r = seededRandom(seed);
const base = Math.floor(r * 140 + 30);
const colorChance = seededRandom(seed + 997);
if (colorChance < 0.06) {
const b = Math.min(base + 40, 200);
return `rgb(${base - 15},${base - 5},${b})`;
} else if (colorChance < 0.10) {
const rb = Math.min(base + 25, 190);
return `rgb(${rb},${base - 10},${Math.min(base + 35, 200)})`;
} else if (colorChance < 0.13) {
return `rgb(${base - 15},${Math.min(base + 25, 180)},${Math.min(base + 20, 185)})`;
}
const g = base + Math.floor(seededRandom(seed + 3) * 20 - 10);
const b = base + Math.floor(seededRandom(seed + 7) * 20 - 10);
return `rgb(${base},${Math.max(20, g)},${Math.max(20, b)})`;
};
// Pre-generate noise frames
const NOISE_FRAMES: string[][][] = Array.from({ length: NUM_NOISE_FRAMES }, (_, nf) =>
Array.from({ length: GRID_SIZE }, (_, row) =>
Array.from({ length: GRID_SIZE }, (_, col) => {
const seed = nf * 1000000 + row * GRID_SIZE + col;
return getNoiseColor(seed);
})
)
);
const FLICKER_MAP: boolean[][] = Array.from({ length: GRID_SIZE }, (_, row) =>
Array.from({ length: GRID_SIZE }, (_, col) => {
return seededRandom(row * GRID_SIZE + col + 777) < 0.08;
})
);
// =============================================================================
// NOISE ROW COMPONENT
// =============================================================================
const NoiseRow: React.FC<{
rowIndex: number;
baseNoiseFrame: number;
flickerNoiseFrame: number;
}> = React.memo(({ rowIndex, baseNoiseFrame, flickerNoiseFrame }) => {
const baseColors = NOISE_FRAMES[baseNoiseFrame][rowIndex];
const flickerColors = NOISE_FRAMES[flickerNoiseFrame][rowIndex];
const flickers = FLICKER_MAP[rowIndex];
return (
{baseColors.map((color, col) => (
))}
);
});
// =============================================================================
// MAIN COMPONENT
// =============================================================================
const TheProcess: React.FC = () => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
// === DENOISE PROGRESS ===
// Forward pass: 0 → 1
const denoiseForward = interpolate(frame, [DENOISE_START, DENOISE_END], [0, 1], {
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
});
// Reverse for loop reset
const denoiseReverse = interpolate(frame, [RESET_START, RESET_END], [0, 1], {
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
easing: EASINGS.easeInOut,
});
// Combined progress: goes 0→1 during denoise, then 1→0 during reset
const progress = frame < RESET_START
? denoiseForward
: 1 - denoiseReverse;
// === STEP COUNTER ===
const currentStep = Math.min(
Math.floor(progress * TOTAL_STEPS) + 1,
TOTAL_STEPS
);
const displayStep = progress <= 0 ? 0 : currentStep;
// === IMAGE REVEAL ===
// Image blur: starts very blurry, ends sharp
// Use an exponential curve so early steps barely change, later steps sharpen fast
const blurAmount = interpolate(progress, [0, 0.3, 0.6, 0.85, 1], [80, 55, 30, 10, 0], {
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
});
// Image opacity: fades in gradually
const imageOpacity = interpolate(progress, [0, 0.15, 0.5, 1], [0, 0.3, 0.7, 1], {
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
});
// Image saturation: starts desaturated, fully colored at end
const imageSaturation = interpolate(progress, [0, 0.5, 1], [0, 0.5, 1], {
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
});
// Pixelation: scale down then up for blocky look (decreases over time)
// At progress 0: scale to 1/16 then back up → very pixelated
// At progress 1: no pixelation
const pixelScale = interpolate(progress, [0, 0.4, 0.75, 1], [0.04, 0.1, 0.35, 1], {
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
});
// === NOISE OVERLAY ===
// Noise fades out as image comes in
const noiseOpacity = interpolate(progress, [0, 0.4, 0.8, 1], [1, 0.7, 0.25, 0], {
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
});
// Noise flicker
const baseNoiseFrame = Math.floor(frame / 20) % NUM_NOISE_FRAMES;
const flickerNoiseFrame = Math.floor(frame / 3) % NUM_NOISE_FRAMES;
const flickerBrightness = interpolate(
seededRandom(frame * 7),
[0, 1],
[0.95, 1.05]
);
// === STEP COUNTER UI ===
const counterOpacity = interpolate(frame, [8, 18], [0, 1], {
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
});
// Counter pulse on step change
const stepFraction = progress * TOTAL_STEPS;
const stepProgress = stepFraction - Math.floor(stepFraction);
const counterScale = progress > 0 && progress < 1
? interpolate(stepProgress, [0, 0.1, 1], [1.08, 1, 1], {
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
})
: 1;
// === GLOW on completion ===
const completionGlow = interpolate(frame, [DENOISE_END, DENOISE_END + 15], [0, 0.25], {
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
});
const completionGlowFade = interpolate(frame, [RESET_START, RESET_END], [1, 0], {
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
});
return (
{/* === LAYER 1: Cat image (pixelated + blurred) === */}
{/* Pixelation container: render image small, scale up with pixelated rendering */}
{/* === LAYER 2: Noise overlay (fades out) === */}
{noiseOpacity > 0.01 && (
{Array.from({ length: GRID_SIZE }, (_, rowIndex) => (
))}
)}
{/* Scanlines */}
{noiseOpacity > 0.1 && (
)}
{/* === LAYER 3: Completion glow === */}
{/* Vignette */}
{/* === STEP COUNTER === */}
{/* Step label */}
Step
{/* Step number */}
{displayStep}
{/* Divider */}
/
{/* Total */}
{TOTAL_STEPS}
{/* Progress bar */}
);
};
export default TheProcess;