Skip to main content
Glama
template.tsx.j26.8 kB
{/* chuk-motion/src/chuk_motion/components/overlays/TitleScene/template.tsx.j2 */} import React from 'react'; import { AbsoluteFill, interpolate, spring, useCurrentFrame, useVideoConfig } from 'remotion'; interface TitleSceneProps { title: string; subtitle?: string; startFrame: number; durationInFrames: number; variant?: string; animation?: string; } export const TitleScene: React.FC<TitleSceneProps> = ({ title, subtitle, startFrame, durationInFrames, variant = 'bold', animation = 'fade' }) => { 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 = title; if (animation === 'fade_zoom') { // Fade + Zoom animation 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.8, 1]); transform = `scale(${scale})`; } else if (animation === 'slide_up') { // Slide up animation 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 === 'typewriter') { // Typewriter animation const charsToShow = Math.floor(interpolate(relativeFrame, [0, 60], [0, title.length], { extrapolateLeft: 'clamp', extrapolateRight: 'clamp' })); displayText = title.slice(0, charsToShow); opacity = 1; } else if (animation === 'blur_in') { // Blur to focus animation const progress = spring({ frame: relativeFrame, fps, config: { damping: 150, mass: 0.5, stiffness: 150 } }); opacity = interpolate(progress, [0, 1], [0, 1]); const blur = interpolate(progress, [0, 1], [20, 0]); filter = `blur(${blur}px)`; } else if (animation === 'fade_slide') { // Fade + Slide animation 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 translateX = interpolate(progress, [0, 1], [-50, 0]); transform = `translateX(${translateX}px)`; } else if (animation === 'zoom') { // Zoom animation 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], [1.2, 1]); transform = `scale(${scale})`; } else { // Default: fade in opacity = interpolate(relativeFrame, [0, 20], [0, 1], { extrapolateLeft: 'clamp', extrapolateRight: 'clamp' }); } // Fade out at the end const fadeOutStart = durationInFrames - 20; const fadeOut = interpolate( relativeFrame, [fadeOutStart, durationInFrames], [1, 0], { extrapolateLeft: 'clamp', extrapolateRight: 'clamp' } ); const finalOpacity = opacity * fadeOut; // Variant styles using theme tokens const variants: Record<string, any> = { minimal: { background: '[[ colors.background.light ]]', textColor: '[[ colors.text.on_light ]]', accentColor: '[[ colors.primary[0] ]]' }, standard: { background: '[[ colors.gradient.primary_to_secondary ]]', textColor: '[[ colors.text.on_dark ]]', accentColor: '[[ colors.accent[0] ]]' }, bold: { background: '[[ colors.gradient.bold ]]', textColor: '[[ colors.text.on_dark ]]', accentColor: '[[ colors.text.on_dark ]]' }, kinetic: { background: '[[ colors.background.dark ]]', textColor: '[[ colors.text.on_dark ]]', accentColor: '[[ colors.accent[0] ]]' }, glass: { background: '[[ colors.background.glass ]]', textColor: '[[ colors.text.on_dark ]]', accentColor: '[[ colors.accent[0] ]]' } }; const variantStyle = variants[variant] || variants.bold; return ( <AbsoluteFill style={{ background: variantStyle.background, display: 'flex', flexDirection: 'column', justifyContent: 'center', alignItems: 'center', fontFamily: "'[[ "', '".join(typography.primary_font.fonts) ]]'", padding: parseInt('[[ spacing.spacing['4xl'] ]]') }} > <div style={{ opacity: finalOpacity, transform, filter, textAlign: 'center' }} > <h1 style={{ fontSize: variant === 'bold' ? parseInt('[[ typography.font_sizes[typography.default_resolution]['4xl'] ]]') : parseInt('[[ typography.font_sizes[typography.default_resolution]['2xl'] ]]'), fontWeight: variant === 'kinetic' ? parseInt('[[ typography.font_weights.black ]]') : parseInt('[[ typography.font_weights.bold ]]'), color: variantStyle.textColor, margin: 0, lineHeight: parseFloat('[[ typography.line_heights.tight ]]'), letterSpacing: '[[ typography.letter_spacing.tight ]]', textTransform: variant === 'kinetic' ? 'uppercase' : 'none', maxWidth: 1200 }} > {displayText} </h1> {subtitle && ( <p style={{ fontSize: parseInt('[[ typography.font_sizes[typography.default_resolution].lg ]]'), fontWeight: parseInt('[[ typography.font_weights.medium ]]'), color: variantStyle.accentColor, margin: '[[ spacing.spacing.xl ]] 0 0 0', lineHeight: parseFloat('[[ typography.line_heights.snug ]]'), maxWidth: 900 }} > {subtitle} </p> )} </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