We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/chrishayuk/chuk-mcp-remotion'
If you have feedback or need assistance with the MCP directory API, please join our Discord server
{/* chuk-motion/src/chuk_motion/components/overlays/TextOverlay/template.tsx.j2 */}
import React from 'react';
import { AbsoluteFill, interpolate, spring, useCurrentFrame, useVideoConfig } from 'remotion';
interface TextOverlayProps {
text: string;
style: string;
animation: string;
position: string;
startFrame: number;
durationInFrames: number;
}
export const TextOverlay: React.FC<TextOverlayProps> = ({
text,
style,
animation,
position,
startFrame,
durationInFrames
}) => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const relativeFrame = frame - startFrame;
// Don't render if outside the time range
if (frame < startFrame || frame >= startFrame + durationInFrames) {
return null;
}
// Animation logic
let opacity = 1;
let transform = 'none';
let filter = 'none';
let displayText = text;
if (animation === 'blur_in') {
const progress = spring({
frame: relativeFrame,
fps,
config: {
damping: [[ motion.default_spring.config.damping ]],
mass: [[ motion.default_spring.config.mass ]],
stiffness: [[ motion.default_spring.config.stiffness ]]
}
});
opacity = interpolate(progress, [0, 1], [0, 1]);
const blur = interpolate(progress, [0, 1], [20, 0]);
filter = `blur(${blur}px)`;
} else if (animation === 'slide_up') {
const progress = spring({
frame: relativeFrame,
fps,
config: {
damping: [[ motion.default_spring.config.damping ]],
mass: [[ motion.default_spring.config.mass ]],
stiffness: [[ motion.default_spring.config.stiffness ]]
}
});
opacity = interpolate(progress, [0, 1], [0, 1]);
const translateY = interpolate(progress, [0, 1], [100, 0]);
transform = `translateY(${translateY}px)`;
} else if (animation === 'fade') {
opacity = interpolate(relativeFrame, [0, 15], [0, 1], {
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp'
});
} else if (animation === 'typewriter') {
const charsToShow = Math.floor(interpolate(relativeFrame, [0, 60], [0, text.length], {
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp'
}));
displayText = text.slice(0, charsToShow);
opacity = 1;
} else if (animation === 'scale_in') {
const progress = spring({
frame: relativeFrame,
fps,
config: {
damping: [[ motion.default_spring.config.damping ]],
mass: [[ motion.default_spring.config.mass ]],
stiffness: [[ motion.default_spring.config.stiffness ]]
}
});
opacity = interpolate(progress, [0, 1], [0, 1]);
const scale = interpolate(progress, [0, 1], [0.5, 1]);
transform = `scale(${scale})`;
}
// Fade out at the end
const fadeOut = interpolate(
relativeFrame,
[durationInFrames - 15, durationInFrames],
[1, 0],
{
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp'
}
);
const finalOpacity = opacity * fadeOut;
// Style variants
const styles: Record<string, any> = {
emphasis: {
fontSize: parseInt('[[ typography.font_sizes[typography.default_resolution]['3xl'] ]]'),
fontWeight: parseInt('[[ typography.font_weights.black ]]'),
color: '[[ colors.text.on_dark ]]',
textAlign: 'center',
textShadow: `0 ${parseInt('[[ spacing.spacing.xxs ]]')}px ${parseInt('[[ spacing.spacing.lg ]]')}px [[ colors.primary[0] ]]80`,
letterSpacing: '-0.02em',
lineHeight: 1.1,
maxWidth: 1200,
background: `linear-gradient(135deg, [[ colors.primary[0] ]], [[ colors.accent[0] ]])`,
WebkitBackgroundClip: 'text',
WebkitTextFillColor: 'transparent',
backgroundClip: 'text'
},
caption: {
fontSize: parseInt('[[ typography.font_sizes[typography.default_resolution].lg ]]'),
fontWeight: parseInt('[[ typography.font_weights.semibold ]]'),
color: '[[ colors.text.on_dark ]]',
textAlign: 'center',
background: '[[ colors.background.glass ]]',
backdropFilter: 'blur(10px)',
padding: `[[ spacing.spacing.lg ]] [[ spacing.spacing['2xl'] ]]`,
borderRadius: parseInt('[[ spacing.border_radius.lg ]]'),
maxWidth: 1400,
lineHeight: parseFloat('[[ typography.line_heights.normal ]]')
},
callout: {
fontSize: parseInt('[[ typography.font_sizes[typography.default_resolution]['2xl'] ]]'),
fontWeight: parseInt('[[ typography.font_weights.extrabold ]]'),
color: '[[ colors.text.on_dark ]]',
textAlign: 'center',
background: `linear-gradient(135deg, [[ colors.primary[0] ]], [[ colors.accent[0] ]])`,
padding: `[[ spacing.spacing.xl ]] [[ spacing.spacing['3xl'] ]]`,
borderRadius: parseInt('[[ spacing.spacing.md ]]'),
boxShadow: `0 ${parseInt('[[ spacing.spacing.xs ]]')}px ${parseInt('[[ spacing.spacing['2xl'] ]]')}px [[ colors.primary[0] ]]60`,
textTransform: 'uppercase',
letterSpacing: '0.05em',
maxWidth: 1200,
lineHeight: 1.2
},
subtitle: {
fontSize: parseInt('[[ typography.font_sizes[typography.default_resolution].xl ]]'),
fontWeight: parseInt('[[ typography.font_weights.semibold ]]'),
color: '[[ colors.text.on_dark ]]',
textAlign: 'center',
background: '[[ colors.background.dark ]]',
padding: `[[ spacing.spacing.lg ]] [[ spacing.spacing['2xl'] ]]`,
borderRadius: parseInt('[[ spacing.border_radius.md ]]'),
maxWidth: 1600,
lineHeight: parseFloat('[[ typography.line_heights.normal ]]'),
border: '2px solid [[ colors.border.light ]]'
},
quote: {
fontSize: parseInt('[[ typography.font_sizes[typography.default_resolution].xl ]]'),
fontWeight: parseInt('[[ typography.font_weights.semibold ]]'),
color: '[[ colors.text.on_dark ]]',
textAlign: 'center',
fontStyle: 'italic',
maxWidth: 1200,
lineHeight: parseFloat('[[ typography.line_heights.snug ]]'),
borderLeft: `${parseInt('[[ spacing.spacing.xs ]]')}px solid [[ colors.accent[0] ]]`,
paddingLeft: parseInt('[[ spacing.spacing['2xl'] ]]'),
position: 'relative'
}
};
const styleConfig = styles[style] || styles.emphasis;
// Position configuration
const positions: Record<string, any> = {
center: {
justifyContent: 'center',
alignItems: 'center'
},
top: {
justifyContent: 'flex-start',
alignItems: 'center',
paddingTop: parseInt('[[ spacing.spacing['4xl'] ]]')
},
bottom: {
justifyContent: 'flex-end',
alignItems: 'center',
paddingBottom: parseInt('[[ spacing.spacing['4xl'] ]]')
},
top_left: {
justifyContent: 'flex-start',
alignItems: 'flex-start',
padding: parseInt('[[ spacing.spacing['4xl'] ]]')
},
top_right: {
justifyContent: 'flex-start',
alignItems: 'flex-end',
padding: parseInt('[[ spacing.spacing['4xl'] ]]')
},
bottom_left: {
justifyContent: 'flex-end',
alignItems: 'flex-start',
padding: parseInt('[[ spacing.spacing['4xl'] ]]')
},
bottom_right: {
justifyContent: 'flex-end',
alignItems: 'flex-end',
padding: parseInt('[[ spacing.spacing['4xl'] ]]')
}
};
const positionConfig = positions[position] || positions.center;
return (
<AbsoluteFill
style={{
display: 'flex',
flexDirection: 'column',
...positionConfig,
fontFamily: "'[[ "', '".join(typography.body_font.fonts) ]]'",
pointerEvents: 'none',
padding: parseInt('[[ spacing.spacing['3xl'] ]]')
}}
>
<div
style={{
...styleConfig,
opacity: finalOpacity,
transform,
filter
}}
>
{displayText}
</div>
</AbsoluteFill>
);
};