import React from 'react';
import {
AbsoluteFill,
Sequence,
Video,
Img,
interpolate,
useCurrentFrame,
useVideoConfig,
} from 'remotion';
// CapCut-style template presets.
// Each preset defines pacing, visual behavior, typography, and motion — not just colors.
export const TEMPLATE_PRESETS = {
viral: {
clipDuration: 5,
transitionDuration: 0.3,
zoom: { start: 1.05, end: 1.15 },
overlay: { color: '#000000', opacity: 0.12 },
subtitle: {
fontFamily: '"Impact", "Arial Black", sans-serif',
fontSize: 52,
fontWeight: '900',
color: '#FFE600',
stroke: '#000000',
strokeWidth: 5,
position: 'bottom',
animation: 'pop',
letterSpacing: '1px',
textTransform: 'uppercase',
},
cinematicBars: false,
vignette: false,
grain: false,
},
luxury: {
clipDuration: 7,
transitionDuration: 1.0,
zoom: { start: 1.0, end: 1.08 },
overlay: { color: '#0d0800', opacity: 0.38 },
subtitle: {
fontFamily: '"Georgia", "Palatino Linotype", serif',
fontSize: 38,
fontWeight: 'normal',
color: '#D4AF37',
stroke: '#1a1000',
strokeWidth: 2,
position: 'bottom',
animation: 'fade',
letterSpacing: '3px',
textTransform: 'uppercase',
},
cinematicBars: true,
vignette: true,
grain: false,
},
business: {
clipDuration: 6,
transitionDuration: 0.5,
zoom: { start: 1.0, end: 1.05 },
overlay: { color: '#001020', opacity: 0.22 },
subtitle: {
fontFamily: '"Helvetica Neue", "Arial", sans-serif',
fontSize: 42,
fontWeight: '700',
color: '#FFFFFF',
stroke: '#003399',
strokeWidth: 3,
position: 'bottom',
animation: 'slide',
letterSpacing: '0.5px',
textTransform: 'none',
},
cinematicBars: false,
vignette: false,
grain: false,
},
gym: {
clipDuration: 3,
transitionDuration: 0.12,
zoom: { start: 1.0, end: 1.22 },
overlay: { color: '#200000', opacity: 0.28 },
subtitle: {
fontFamily: '"Impact", "Arial Black", sans-serif',
fontSize: 56,
fontWeight: '900',
color: '#FF2020',
stroke: '#000000',
strokeWidth: 6,
position: 'middle',
animation: 'slam',
letterSpacing: '2px',
textTransform: 'uppercase',
},
cinematicBars: false,
vignette: false,
grain: false,
},
cinematic: {
clipDuration: 8,
transitionDuration: 1.2,
zoom: { start: 1.0, end: 1.06 },
overlay: { color: '#050510', opacity: 0.45 },
subtitle: {
fontFamily: '"Georgia", "Times New Roman", serif',
fontSize: 34,
fontWeight: 'normal',
color: '#EEE0C0',
stroke: '#000000',
strokeWidth: 1,
position: 'bottom',
animation: 'fade',
letterSpacing: '2px',
textTransform: 'none',
},
cinematicBars: true,
vignette: true,
grain: true,
},
motivation: {
clipDuration: 5,
transitionDuration: 0.4,
zoom: { start: 1.02, end: 1.12 },
overlay: { color: '#0a0020', opacity: 0.2 },
subtitle: {
fontFamily: '"Impact", "Arial Black", sans-serif',
fontSize: 50,
fontWeight: '900',
color: '#FFFFFF',
stroke: '#6600CC',
strokeWidth: 4,
position: 'bottom',
animation: 'pop',
letterSpacing: '1px',
textTransform: 'uppercase',
},
cinematicBars: false,
vignette: false,
grain: false,
},
dark: {
clipDuration: 4,
transitionDuration: 0.25,
zoom: { start: 1.0, end: 1.10 },
overlay: { color: '#000000', opacity: 0.55 },
subtitle: {
fontFamily: '"Impact", "Arial Black", sans-serif',
fontSize: 48,
fontWeight: '900',
color: '#FFFFFF',
stroke: '#000000',
strokeWidth: 4,
position: 'bottom',
animation: 'fade',
letterSpacing: '2px',
textTransform: 'uppercase',
},
cinematicBars: true,
vignette: true,
grain: false,
},
};
// Calculate composition duration based on template + clip count.
export const calculateMetadata = ({ props }) => {
const fps = 30;
const preset = TEMPLATE_PRESETS[props.template] || TEMPLATE_PRESETS.viral;
const clips = props.clips || [];
const clipCount = Math.max(clips.length, 1);
const transSec = preset.transitionDuration;
const totalSec = Math.min(
clipCount * preset.clipDuration - Math.max(0, clipCount - 1) * transSec,
30
);
return {
fps,
durationInFrames: Math.max(Math.round(totalSec * fps), fps),
width: 540,
height: 960,
};
};
// ─── Clip with zoom + fade transitions ───────────────────────────────────────
const ClipLayer = ({ src, preset, clipDurFrames }) => {
const frame = useCurrentFrame();
const fps = 30;
const transFrames = Math.round(preset.transitionDuration * fps);
const fadeIn = interpolate(frame, [0, Math.max(transFrames, 1)], [0, 1], {
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
});
const fadeOut = interpolate(
frame,
[clipDurFrames - Math.max(transFrames, 1), clipDurFrames],
[1, 0],
{ extrapolateLeft: 'clamp', extrapolateRight: 'clamp' }
);
const opacity = Math.min(fadeIn, fadeOut);
const scale = interpolate(frame, [0, clipDurFrames], [preset.zoom.start, preset.zoom.end], {
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
});
return (