Skip to main content
Glama
template.tsx.j25.68 kB
{/* 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> ); };

Latest Blog Posts

MCP directory API

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