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/LowerThird/template.tsx.j2 */}
import React from 'react';
import { AbsoluteFill, interpolate, spring, useCurrentFrame, useVideoConfig } from 'remotion';
interface LowerThirdProps {
name: string;
title?: string;
startFrame: number;
durationInFrames: number;
variant?: string;
position?: string;
}
export const LowerThird: React.FC<LowerThirdProps> = ({
name,
title,
startFrame,
durationInFrames,
variant = 'glass',
position = 'bottom_left'
}) => {
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: slide in from left
const slideIn = spring({
frame: relativeFrame,
fps,
config: {
damping: [[ motion.default_spring.config.damping ]],
mass: [[ motion.default_spring.config.mass ]],
stiffness: [[ motion.default_spring.config.stiffness ]]
}
});
// Fade in
const opacity = interpolate(relativeFrame, [0, 10], [0, 1], {
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp'
});
// Fade out near end
const fadeOut = interpolate(
relativeFrame,
[durationInFrames - 10, durationInFrames],
[1, 0],
{
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp'
}
);
const finalOpacity = Math.min(opacity, fadeOut);
// Position based on runtime prop
const positions: Record<string, any> = {
bottom_left: { bottom: parseInt('[[ spacing.spacing['2xl'] ]]'), left: interpolate(slideIn, [0, 1], [-400, parseInt('[[ spacing.spacing['2xl'] ]]')]) },
bottom_center: { bottom: parseInt('[[ spacing.spacing['2xl'] ]]'), left: '50%', transform: `translateX(-50%) translateX(${interpolate(slideIn, [0, 1], [-400, 0])}px)` },
bottom_right: { bottom: parseInt('[[ spacing.spacing['2xl'] ]]'), right: interpolate(slideIn, [0, 1], [-400, parseInt('[[ spacing.spacing['2xl'] ]]')]) },
top_left: { top: parseInt('[[ spacing.spacing['2xl'] ]]'), left: interpolate(slideIn, [0, 1], [-400, parseInt('[[ spacing.spacing['2xl'] ]]')]) },
top_center: { top: parseInt('[[ spacing.spacing['2xl'] ]]'), left: '50%', transform: `translateX(-50%) translateX(${interpolate(slideIn, [0, 1], [-400, 0])}px)` },
top_right: { top: parseInt('[[ spacing.spacing['2xl'] ]]'), right: interpolate(slideIn, [0, 1], [-400, parseInt('[[ spacing.spacing['2xl'] ]]')]) }
};
const positionStyle = positions[position] || positions.bottom_left;
// Variant styles using theme tokens
const variants: Record<string, any> = {
minimal: {
background: '[[ colors.background.dark ]]',
backdropFilter: 'none',
padding: `${parseInt('[[ spacing.spacing.md ]]')}px ${parseInt('[[ spacing.spacing.lg ]]')}px`,
borderRadius: parseInt('[[ spacing.border_radius.md ]]')
},
standard: {
background: '[[ colors.background.dark ]]',
backdropFilter: 'none',
padding: `${parseInt('[[ spacing.spacing.lg ]]')}px ${parseInt('[[ spacing.spacing.xl ]]')}px`,
borderRadius: parseInt('[[ spacing.border_radius.lg ]]'),
borderLeft: `${parseInt('[[ spacing.border_width.thick ]]')}px solid [[ colors.primary[0] ]]`
},
glass: {
background: '[[ colors.background.glass ]]',
backdropFilter: 'blur(10px)',
padding: `${parseInt('[[ spacing.spacing.lg ]]')}px ${parseInt('[[ spacing.spacing.xl ]]')}px`,
borderRadius: parseInt('[[ spacing.border_radius.lg ]]'),
border: '1px solid [[ colors.border.light ]]'
},
bold: {
background: `linear-gradient(135deg, [[ colors.primary[0] ]], [[ colors.accent[0] ]])`,
backdropFilter: 'none',
padding: `${parseInt('[[ spacing.spacing.lg ]]')}px ${parseInt('[[ spacing.spacing['2xl'] ]]')}px`,
borderRadius: parseInt('[[ spacing.border_radius.lg ]]'),
boxShadow: `0 10px 40px [[ colors.shadow.dark ]]`
},
animated: {
background: '[[ colors.background.glass ]]',
backdropFilter: 'blur(10px)',
padding: `${parseInt('[[ spacing.spacing.lg ]]')}px ${parseInt('[[ spacing.spacing.xl ]]')}px`,
borderRadius: parseInt('[[ spacing.border_radius.lg ]]'),
border: `${parseInt('[[ spacing.border_width.medium ]]')}px solid [[ colors.accent[0] ]]`,
boxShadow: `0 0 ${parseInt('[[ spacing.spacing.lg ]]')}px [[ colors.accent[0] ]]80`
}
};
const variantStyle = variants[variant] || variants.glass;
return (
<AbsoluteFill style={{ pointerEvents: 'none' }}>
<div
style={{
position: 'absolute',
...positionStyle,
...variantStyle,
opacity: finalOpacity,
fontFamily: "'[[ "', '".join(typography.body_font.fonts) ]]'"
}}
>
<div
style={{
fontSize: parseInt('[[ typography.font_sizes[typography.default_resolution].base ]]'),
fontWeight: parseInt('[[ typography.font_weights.bold ]]'),
color: '[[ colors.text.on_dark ]]',
marginBottom: title ? parseInt('[[ spacing.spacing.xxs ]]') : 0,
lineHeight: 1.2
}}
>
{name}
</div>
{title && (
<div
style={{
fontSize: parseInt('[[ typography.font_sizes[typography.default_resolution].sm ]]'),
fontWeight: parseInt('[[ typography.font_weights.medium ]]'),
color: '[[ colors.accent[0] ]]',
lineHeight: 1.3
}}
>
{title}
</div>
)}
</div>
</AbsoluteFill>
);
};