Skip to main content
Glama
template.tsx.j27.85 kB
{/* chuk-motion/src/chuk_motion/components/overlays/TrueFocus/template.tsx.j2 */} import React, { useMemo } from 'react'; import { AbsoluteFill, useCurrentFrame, interpolate, spring, useVideoConfig } from 'remotion'; interface TrueFocusProps { text: string; fontSize?: 'xl' | '2xl' | '3xl' | '4xl'; fontWeight?: 'bold' | 'extrabold' | 'black'; textColor?: string; frameColor?: string; glowColor?: string; blurAmount?: number; wordDuration?: number; position?: 'center' | 'top' | 'bottom'; startFrame?: number; durationInFrames?: number; } export const TrueFocus: React.FC<TrueFocusProps> = ({ text = 'True Focus', fontSize = '3xl', fontWeight = 'black', textColor = '[[ colors.text.on_dark ]]', frameColor = '[[ colors.primary[0] ]]', glowColor = '[[ colors.primary[0] ]]', blurAmount = 5, wordDuration = 30, // frames per word position = 'center', startFrame = 0, durationInFrames = 150, }) => { const frame = useCurrentFrame(); const { fps } = useVideoConfig(); // Split text into words - MUST be called before any conditional returns const words = useMemo(() => text.split(' '), [text]); const wordCount = words.length; const relativeFrame = frame - startFrame; // Don't render if outside time range - AFTER all hooks if (frame < startFrame || frame >= startFrame + durationInFrames) { return null; } // Calculate which word is currently focused const cycleProgress = (relativeFrame / wordDuration) % wordCount; const currentWordIndex = Math.floor(cycleProgress); // Smooth transition progress for frame animation const transitionProgress = cycleProgress - currentWordIndex; const smoothTransition = spring({ frame: transitionProgress * fps / 2, // Adjust for smooth spring fps, config: { damping: [[ motion.default_spring.config.damping ]], stiffness: [[ motion.default_spring.config.stiffness ]], mass: [[ motion.default_spring.config.mass ]], }, }); // Overall fade in const fadeIn = interpolate(relativeFrame, [0, 20], [0, 1], { extrapolateRight: 'clamp', }); // Position styles const positionStyles = { center: { justifyContent: 'center', alignItems: 'center' }, top: { justifyContent: 'center', alignItems: 'flex-start', paddingTop: '[[ spacing.spacing['3xl'] ]]' }, bottom: { justifyContent: 'center', alignItems: 'flex-end', paddingBottom: '[[ spacing.spacing['3xl'] ]]' }, }; // Font size mapping const fontSizeMap = { 'xl': parseInt('[[ typography.font_sizes[typography.default_resolution].xl ]]'), '2xl': parseInt('[[ typography.font_sizes[typography.default_resolution]['2xl'] ]]'), '3xl': parseInt('[[ typography.font_sizes[typography.default_resolution]['3xl'] ]]'), '4xl': parseInt('[[ typography.font_sizes[typography.default_resolution]['4xl'] ]]'), }; const fontWeightMap = { 'bold': '[[ typography.font_weights.bold ]]', 'extrabold': '[[ typography.font_weights.extrabold ]]', 'black': '[[ typography.font_weights.black ]]', }; return ( <AbsoluteFill style={{ opacity: fadeIn, display: 'flex', ...positionStyles[position], pointerEvents: 'none', }} > <div style={{ position: 'relative', display: 'flex', flexDirection: 'row', gap: '[[ spacing.spacing.lg ]]', flexWrap: 'wrap', justifyContent: 'center', alignItems: 'center', padding: '[[ spacing.spacing.xl ]]', }} > {words.map((word, index) => { // Calculate blur for this word const isFocused = index === currentWordIndex; const blur = isFocused ? 0 : blurAmount; // Calculate frame position and size for focused word // Frame appears around the focused word with animated corners const isFrameTarget = index === currentWordIndex; return ( <div key={index} style={{ position: 'relative', fontSize: fontSizeMap[fontSize], fontWeight: fontWeightMap[fontWeight], fontFamily: "'[[ "', '".join(typography.primary_font.fonts) ]]'", color: textColor, filter: `blur(${blur}px)`, transition: `filter 0.3s ease`, letterSpacing: '[[ typography.letter_spacing.tight ]]', lineHeight: '[[ typography.line_heights.tight ]]', }} > {word} {/* Animated frame with corner brackets */} {isFrameTarget && ( <div style={{ position: 'absolute', inset: `-[[ spacing.spacing.sm ]]`, pointerEvents: 'none', opacity: smoothTransition, }} > {/* Top-left corner */} <div style={{ position: 'absolute', top: 0, left: 0, width: '[[ spacing.spacing.lg ]]', height: '[[ spacing.spacing.lg ]]', borderTop: `[[ spacing.border_width.thick ]] solid ${frameColor}`, borderLeft: `[[ spacing.border_width.thick ]] solid ${frameColor}`, borderRadius: '[[ spacing.border_radius.xs ]]', filter: `drop-shadow(0 0 [[ spacing.spacing.xs ]] ${glowColor})`, }} /> {/* Top-right corner */} <div style={{ position: 'absolute', top: 0, right: 0, width: '[[ spacing.spacing.lg ]]', height: '[[ spacing.spacing.lg ]]', borderTop: `[[ spacing.border_width.thick ]] solid ${frameColor}`, borderRight: `[[ spacing.border_width.thick ]] solid ${frameColor}`, borderRadius: '[[ spacing.border_radius.xs ]]', filter: `drop-shadow(0 0 [[ spacing.spacing.xs ]] ${glowColor})`, }} /> {/* Bottom-left corner */} <div style={{ position: 'absolute', bottom: 0, left: 0, width: '[[ spacing.spacing.lg ]]', height: '[[ spacing.spacing.lg ]]', borderBottom: `[[ spacing.border_width.thick ]] solid ${frameColor}`, borderLeft: `[[ spacing.border_width.thick ]] solid ${frameColor}`, borderRadius: '[[ spacing.border_radius.xs ]]', filter: `drop-shadow(0 0 [[ spacing.spacing.xs ]] ${glowColor})`, }} /> {/* Bottom-right corner */} <div style={{ position: 'absolute', bottom: 0, right: 0, width: '[[ spacing.spacing.lg ]]', height: '[[ spacing.spacing.lg ]]', borderBottom: `[[ spacing.border_width.thick ]] solid ${frameColor}`, borderRight: `[[ spacing.border_width.thick ]] solid ${frameColor}`, borderRadius: '[[ spacing.border_radius.xs ]]', filter: `drop-shadow(0 0 [[ spacing.spacing.xs ]] ${glowColor})`, }} /> </div> )} </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