Skip to main content
Glama
motion.py30 kB
# chuk-motion/src/chuk_motion/tokens/motion.py """ Motion token system for Remotion video design system. Comprehensive motion system including: - Duration tokens (instant to ultra_slow with frame conversions) - Easing curves (15+ cubic bezier presets) - Spring configurations (gentle to explosive) - Enter/exit transitions (fade, slide, scale, etc.) - Tempo/rhythm tokens (sprint to cinematic) - Platform-specific timing (hook durations, scene change intervals) Critical for consistent, platform-optimized motion design across components. """ from typing import Any from pydantic import BaseModel, Field # ============================================================================ # PYDANTIC MODELS # ============================================================================ class DurationConfig(BaseModel): """Duration configuration with multiple time representations.""" ms: int = Field(..., description="Duration in milliseconds") frames_30fps: int = Field(..., description="Duration in frames at 30 FPS") frames_60fps: int = Field(..., description="Duration in frames at 60 FPS") seconds: float = Field(..., description="Duration in seconds") css: str = Field(..., description="CSS-compatible duration string") description: str = Field(..., description="Human-readable description") class EasingConfig(BaseModel): """Easing curve configuration.""" curve: list[float] = Field(..., description="Cubic bezier curve [x1, y1, x2, y2]") css: str = Field(..., description="CSS-compatible easing function") description: str = Field(..., description="Human-readable description") usage: str = Field(..., description="When to use this easing") class RemotionSpringConfig(BaseModel): """Remotion spring physics configuration.""" damping: float = Field(..., description="Damping coefficient") mass: float = Field(..., description="Mass of the spring") stiffness: float = Field(..., description="Stiffness of the spring") overshootClamping: bool = Field(..., description="Whether to clamp overshoot") class SpringConfig(BaseModel): """Spring animation configuration.""" config: RemotionSpringConfig = Field(..., description="Remotion spring config") description: str = Field(..., description="Human-readable description") feel: str = Field(..., description="How the animation feels") usage: str = Field(..., description="When to use this spring") class TransitionProperties(BaseModel): """Properties for a transition animation.""" from_value: Any = Field(..., alias="from", description="Starting value") to: Any = Field(..., description="Ending value") class Config: populate_by_name = True class EnterTransition(BaseModel): """Enter transition configuration.""" properties: dict[str, TransitionProperties] = Field(..., description="Animated properties") description: str = Field(..., description="Human-readable description") usage: str = Field(..., description="When to use this transition") default_duration: str = Field(..., description="Recommended duration token") default_easing: str = Field(..., description="Recommended easing token") class ExitTransition(BaseModel): """Exit transition configuration.""" properties: dict[str, TransitionProperties] = Field(..., description="Animated properties") description: str = Field(..., description="Human-readable description") usage: str = Field(..., description="When to use this transition") default_duration: str = Field(..., description="Recommended duration token") default_easing: str = Field(..., description="Recommended easing token") class TempoConfig(BaseModel): """Tempo/rhythm configuration for narrative pacing.""" beat_duration: float = Field(..., description="Duration of one 'beat' in seconds") frames_30fps: int = Field(..., description="Frames per beat at 30 FPS") frames_60fps: int = Field(..., description="Frames per beat at 60 FPS") caption_duration: float = Field(..., description="Recommended caption display duration") scene_change_interval: float = Field(..., description="Recommended scene change interval") cuts_per_minute: int = Field(..., description="Average cuts per minute") description: str = Field(..., description="Human-readable description") feel: str = Field(..., description="How the pacing feels") usage: str = Field(..., description="When to use this tempo") class CTATiming(BaseModel): """Call-to-action timing configuration.""" first_cta: float = Field(..., description="Time for first CTA in seconds") final_cta: float = Field(..., description="Time for final CTA (negative = from end)") mid_roll_cta: float | None = Field(default=None, description="Optional mid-roll CTA timing") class PlatformTimingConfig(BaseModel): """Platform-specific timing configuration.""" hook_duration: float = Field(..., description="Duration of opening hook in seconds") scene_change_interval: float = Field(..., description="Average scene change interval") caption_display_duration: float = Field(..., description="Recommended caption duration") cta_timing: CTATiming = Field(..., description="Call-to-action timing") attention_span: str = Field(..., description="Platform attention span characteristic") recommended_tempo: str = Field(..., description="Recommended tempo token") description: str = Field(..., description="Human-readable description") class MotionTokens(BaseModel): """Complete motion token system.""" duration: dict[str, DurationConfig] easing: dict[str, EasingConfig] spring_configs: dict[str, SpringConfig] enter: dict[str, EnterTransition] exit: dict[str, ExitTransition] tempo: dict[str, TempoConfig] platform_timing: dict[str, PlatformTimingConfig] # ============================================================================ # MOTION TOKENS DATA # ============================================================================ MOTION_TOKENS = MotionTokens( # ============================================================================ # DURATION TOKENS # ============================================================================ duration={ "instant": DurationConfig( ms=0, frames_30fps=0, frames_60fps=0, seconds=0.0, css="0s", description="Instant (no animation)", ), "ultra_fast": DurationConfig( ms=100, frames_30fps=3, frames_60fps=6, seconds=0.1, css="0.1s", description="Ultra fast motion", ), "fast": DurationConfig( ms=200, frames_30fps=6, frames_60fps=12, seconds=0.2, css="0.2s", description="Fast motion", ), "normal": DurationConfig( ms=350, frames_30fps=11, frames_60fps=21, seconds=0.35, css="0.35s", description="Normal motion (default)", ), "medium": DurationConfig( ms=500, frames_30fps=15, frames_60fps=30, seconds=0.5, css="0.5s", description="Medium motion", ), "slow": DurationConfig( ms=700, frames_30fps=21, frames_60fps=42, seconds=0.7, css="0.7s", description="Slow motion", ), "slower": DurationConfig( ms=1000, frames_30fps=30, frames_60fps=60, seconds=1.0, css="1.0s", description="Slower motion", ), "ultra_slow": DurationConfig( ms=1500, frames_30fps=45, frames_60fps=90, seconds=1.5, css="1.5s", description="Ultra slow motion", ), }, # ============================================================================ # EASING CURVES # ============================================================================ easing={ "linear": EasingConfig( curve=[0.0, 0.0, 1.0, 1.0], css="linear", description="No easing, constant speed", usage="Use for continuous loops, loading spinners", ), "ease_in_out": EasingConfig( curve=[0.42, 0.0, 0.58, 1.0], css="ease-in-out", description="Starts slow, accelerates, decelerates", usage="Default for most transitions", ), "ease_out": EasingConfig( curve=[0.0, 0.0, 0.58, 1.0], css="ease-out", description="Starts fast, decelerates", usage="Enter transitions, appearing elements", ), "ease_in": EasingConfig( curve=[0.42, 0.0, 1.0, 1.0], css="ease-in", description="Starts slow, accelerates", usage="Exit transitions, disappearing elements", ), "ease_out_back": EasingConfig( curve=[0.34, 1.56, 0.64, 1.0], css="cubic-bezier(0.34, 1.56, 0.64, 1)", description="Overshoots slightly then settles", usage="Playful entrances, bounce effects", ), "ease_out_expo": EasingConfig( curve=[0.16, 1.0, 0.3, 1.0], css="cubic-bezier(0.16, 1, 0.3, 1)", description="Exponential deceleration", usage="Snappy UI responses, quick slides", ), "ease_out_quint": EasingConfig( curve=[0.22, 1.0, 0.36, 1.0], css="cubic-bezier(0.22, 1, 0.36, 1)", description="Smooth, elegant deceleration", usage="Refined transitions, elegant slides", ), "ease_out_cubic": EasingConfig( curve=[0.33, 1.0, 0.68, 1.0], css="cubic-bezier(0.33, 1, 0.68, 1)", description="Moderate deceleration", usage="General purpose, balanced feel", ), "ease_in_cubic": EasingConfig( curve=[0.32, 0.0, 0.67, 0.0], css="cubic-bezier(0.32, 0, 0.67, 0)", description="Moderate acceleration", usage="Exit animations, dismissals", ), "ease_in_out_quart": EasingConfig( curve=[0.76, 0.0, 0.24, 1.0], css="cubic-bezier(0.76, 0, 0.24, 1)", description="Strong acceleration and deceleration", usage="Dramatic transitions, scene changes", ), "ease_in_out_back": EasingConfig( curve=[0.68, -0.55, 0.27, 1.55], css="cubic-bezier(0.68, -0.55, 0.27, 1.55)", description="Anticipates before moving, overshoots", usage="Playful, energetic transitions", ), "ease_out_circ": EasingConfig( curve=[0.08, 0.82, 0.17, 1.0], css="cubic-bezier(0.08, 0.82, 0.17, 1)", description="Circular deceleration", usage="Smooth, rounded motion", ), "bounce": EasingConfig( curve=[0.68, -0.55, 0.27, 1.55], css="cubic-bezier(0.68, -0.55, 0.27, 1.55)", description="Bounces at the end", usage="Fun, playful emphasis", ), "elastic": EasingConfig( curve=[0.68, -0.55, 0.27, 1.55], css="cubic-bezier(0.68, -0.55, 0.27, 1.55)", description="Elastic spring effect", usage="Attention-grabbing, cartoonish", ), "anticipate": EasingConfig( curve=[0.68, -0.55, 0.27, 1.55], css="cubic-bezier(0.68, -0.55, 0.27, 1.55)", description="Pulls back before moving forward", usage="Dramatic reveals, scene transitions", ), }, # ============================================================================ # SPRING CONFIGURATIONS # ============================================================================ spring_configs={ "gentle": SpringConfig( config=RemotionSpringConfig( damping=100, mass=1.0, stiffness=100, overshootClamping=False ), description="Smooth, gradual spring with minimal overshoot", feel="Calm, refined, elegant", usage="Subtle UI transitions, professional content", ), "smooth": SpringConfig( config=RemotionSpringConfig( damping=50, mass=1.0, stiffness=120, overshootClamping=False ), description="Balanced spring animation (default)", feel="Natural, pleasant, general-purpose", usage="Most transitions, default choice", ), "bouncy": SpringConfig( config=RemotionSpringConfig( damping=30, mass=1.0, stiffness=150, overshootClamping=False ), description="Playful spring with noticeable bounce", feel="Fun, energetic, playful", usage="Playful UI, creator content, emphasizing actions", ), "snappy": SpringConfig( config=RemotionSpringConfig( damping=80, mass=0.5, stiffness=200, overshootClamping=False ), description="Quick, responsive spring", feel="Fast, responsive, modern", usage="UI responses, button presses, snappy interactions", ), "wobbly": SpringConfig( config=RemotionSpringConfig( damping=20, mass=1.2, stiffness=100, overshootClamping=False ), description="Exaggerated wobble spring", feel="Cartoonish, attention-grabbing, silly", usage="Comedic emphasis, playful reveals", ), "stiff": SpringConfig( config=RemotionSpringConfig( damping=150, mass=1.0, stiffness=300, overshootClamping=True ), description="Very stiff, minimal bounce", feel="Precise, controlled, mechanical", usage="Precise UI movements, technical content", ), "explosive": SpringConfig( config=RemotionSpringConfig( damping=10, mass=0.8, stiffness=250, overshootClamping=False ), description="Dramatic, high-energy spring", feel="Explosive, dramatic, intense", usage="Hero moments, dramatic reveals, climactic scenes", ), }, # ============================================================================ # ENTER TRANSITIONS # ============================================================================ enter={ "fade_in": EnterTransition( properties={ "opacity": TransitionProperties(**{"from": 0, "to": 1}), }, description="Simple fade in", usage="Subtle entrances, text, overlays", default_duration="normal", default_easing="ease_out", ), "fade_up": EnterTransition( properties={ "opacity": TransitionProperties(**{"from": 0, "to": 1}), "translateY": TransitionProperties(**{"from": 30, "to": 0}), }, description="Fade in while sliding up", usage="Content blocks, cards, sections", default_duration="medium", default_easing="ease_out_expo", ), "fade_down": EnterTransition( properties={ "opacity": TransitionProperties(**{"from": 0, "to": 1}), "translateY": TransitionProperties(**{"from": -30, "to": 0}), }, description="Fade in while sliding down", usage="Dropdown content, notifications", default_duration="medium", default_easing="ease_out_expo", ), "scale_in": EnterTransition( properties={ "opacity": TransitionProperties(**{"from": 0, "to": 1}), "scale": TransitionProperties(**{"from": 0.8, "to": 1}), }, description="Fade in while scaling up", usage="Modals, popups, emphasis", default_duration="medium", default_easing="ease_out_back", ), "zoom_in": EnterTransition( properties={ "opacity": TransitionProperties(**{"from": 0, "to": 1}), "scale": TransitionProperties(**{"from": 0, "to": 1}), }, description="Zoom from 0% to 100%", usage="Dramatic entrances, hero elements", default_duration="medium", default_easing="ease_out_expo", ), "slide_in_left": EnterTransition( properties={ "opacity": TransitionProperties(**{"from": 0, "to": 1}), "translateX": TransitionProperties(**{"from": -100, "to": 0}), }, description="Slide in from left", usage="Side panels, nav menus", default_duration="medium", default_easing="ease_out_expo", ), "slide_in_right": EnterTransition( properties={ "opacity": TransitionProperties(**{"from": 0, "to": 1}), "translateX": TransitionProperties(**{"from": 100, "to": 0}), }, description="Slide in from right", usage="Side panels, nav menus", default_duration="medium", default_easing="ease_out_expo", ), "bounce_in": EnterTransition( properties={ "opacity": TransitionProperties(**{"from": 0, "to": 1}), "scale": TransitionProperties(**{"from": 0.3, "to": 1}), }, description="Bounce in with overshoot", usage="Playful emphasis, notifications", default_duration="medium", default_easing="ease_out_back", ), "rotate_in": EnterTransition( properties={ "opacity": TransitionProperties(**{"from": 0, "to": 1}), "rotate": TransitionProperties(**{"from": -180, "to": 0}), }, description="Rotate in from -180deg", usage="Playful transitions, icons", default_duration="medium", default_easing="ease_out_back", ), "blur_in": EnterTransition( properties={ "opacity": TransitionProperties(**{"from": 0, "to": 1}), "blur": TransitionProperties(**{"from": 20, "to": 0}), }, description="Fade in from blurred", usage="Backgrounds, hero images", default_duration="slow", default_easing="ease_out", ), }, # ============================================================================ # EXIT TRANSITIONS # ============================================================================ exit={ "fade_out": ExitTransition( properties={ "opacity": TransitionProperties(**{"from": 1, "to": 0}), }, description="Simple fade out", usage="Subtle exits, dismissals", default_duration="normal", default_easing="ease_in", ), "fade_out_down": ExitTransition( properties={ "opacity": TransitionProperties(**{"from": 1, "to": 0}), "translateY": TransitionProperties(**{"from": 0, "to": 30}), }, description="Fade out while sliding down", usage="Content blocks, cards", default_duration="medium", default_easing="ease_in_cubic", ), "fade_out_up": ExitTransition( properties={ "opacity": TransitionProperties(**{"from": 1, "to": 0}), "translateY": TransitionProperties(**{"from": 0, "to": -30}), }, description="Fade out while sliding up", usage="Notifications, toasts", default_duration="medium", default_easing="ease_in_cubic", ), "scale_out": ExitTransition( properties={ "opacity": TransitionProperties(**{"from": 1, "to": 0}), "scale": TransitionProperties(**{"from": 1, "to": 0.8}), }, description="Fade out while scaling down", usage="Modals, popups", default_duration="medium", default_easing="ease_in_cubic", ), "zoom_out": ExitTransition( properties={ "opacity": TransitionProperties(**{"from": 1, "to": 0}), "scale": TransitionProperties(**{"from": 1, "to": 0}), }, description="Zoom from 100% to 0%", usage="Dramatic exits", default_duration="medium", default_easing="ease_in_cubic", ), "slide_out_left": ExitTransition( properties={ "opacity": TransitionProperties(**{"from": 1, "to": 0}), "translateX": TransitionProperties(**{"from": 0, "to": -100}), }, description="Slide out to left", usage="Side panels, dismissals", default_duration="medium", default_easing="ease_in_cubic", ), "slide_out_right": ExitTransition( properties={ "opacity": TransitionProperties(**{"from": 1, "to": 0}), "translateX": TransitionProperties(**{"from": 0, "to": 100}), }, description="Slide out to right", usage="Side panels, dismissals", default_duration="medium", default_easing="ease_in_cubic", ), "blur_out": ExitTransition( properties={ "opacity": TransitionProperties(**{"from": 1, "to": 0}), "blur": TransitionProperties(**{"from": 0, "to": 20}), }, description="Fade out to blurred", usage="Backgrounds, scene transitions", default_duration="slow", default_easing="ease_in", ), }, # ============================================================================ # TEMPO / RHYTHM TOKENS # ============================================================================ tempo={ "sprint": TempoConfig( beat_duration=1.0, frames_30fps=30, frames_60fps=60, caption_duration=0.8, scene_change_interval=2.0, cuts_per_minute=45, description="Ultra-fast pacing for maximum retention", feel="Frenetic, intense, rapid-fire", usage="TikTok, Shorts, attention-grabbing content", ), "fast": TempoConfig( beat_duration=1.5, frames_30fps=45, frames_60fps=90, caption_duration=1.2, scene_change_interval=3.0, cuts_per_minute=30, description="Fast pacing for energetic content", feel="Energetic, dynamic, engaging", usage="Short-form content, energetic tutorials", ), "medium": TempoConfig( beat_duration=2.0, frames_30fps=60, frames_60fps=120, caption_duration=1.8, scene_change_interval=4.5, cuts_per_minute=20, description="Balanced pacing (default)", feel="Comfortable, natural, conversational", usage="Most content types, general tutorials", ), "slow": TempoConfig( beat_duration=3.0, frames_30fps=90, frames_60fps=180, caption_duration=2.5, scene_change_interval=6.0, cuts_per_minute=12, description="Deliberate pacing for clarity", feel="Thoughtful, clear, educational", usage="Technical content, presentations, explanations", ), "cinematic": TempoConfig( beat_duration=4.0, frames_30fps=120, frames_60fps=240, caption_duration=3.5, scene_change_interval=8.0, cuts_per_minute=8, description="Slow, cinematic pacing", feel="Atmospheric, dramatic, contemplative", usage="Storytelling, documentaries, cinematic content", ), }, # ============================================================================ # PLATFORM-SPECIFIC TIMING # ============================================================================ platform_timing={ "tiktok": PlatformTimingConfig( hook_duration=1.5, scene_change_interval=2.0, caption_display_duration=0.8, cta_timing=CTATiming( first_cta=3.0, final_cta=-2.0, ), attention_span="ultra_short", recommended_tempo="sprint", description="TikTok algorithm-optimized timing", ), "youtube_shorts": PlatformTimingConfig( hook_duration=2.0, scene_change_interval=3.0, caption_display_duration=1.0, cta_timing=CTATiming( first_cta=5.0, final_cta=-3.0, ), attention_span="short", recommended_tempo="fast", description="YouTube Shorts optimized timing", ), "instagram_reel": PlatformTimingConfig( hook_duration=2.0, scene_change_interval=3.5, caption_display_duration=1.2, cta_timing=CTATiming( first_cta=5.0, final_cta=-3.0, ), attention_span="short", recommended_tempo="fast", description="Instagram Reels optimized timing", ), "youtube_long_form": PlatformTimingConfig( hook_duration=3.0, scene_change_interval=5.0, caption_display_duration=2.0, cta_timing=CTATiming( first_cta=15.0, mid_roll_cta=180.0, final_cta=-10.0, ), attention_span="medium", recommended_tempo="medium", description="YouTube long-form optimized timing", ), "linkedin": PlatformTimingConfig( hook_duration=2.5, scene_change_interval=4.0, caption_display_duration=2.0, cta_timing=CTATiming( first_cta=10.0, final_cta=-5.0, ), attention_span="medium", recommended_tempo="medium", description="LinkedIn feed optimized timing", ), "presentation": PlatformTimingConfig( hook_duration=4.0, scene_change_interval=6.0, caption_display_duration=3.0, cta_timing=CTATiming( first_cta=20.0, final_cta=-10.0, ), attention_span="long", recommended_tempo="slow", description="Presentation/educational optimized timing", ), }, ) # ============================================================================ # UTILITY FUNCTIONS # ============================================================================ def get_duration(name: str) -> DurationConfig: """Get a duration config by name, fallback to 'normal' if not found.""" return MOTION_TOKENS.duration.get(name, MOTION_TOKENS.duration["normal"]) def get_easing(name: str) -> EasingConfig: """Get an easing config by name, fallback to 'ease_out' if not found.""" return MOTION_TOKENS.easing.get(name, MOTION_TOKENS.easing["ease_out"]) def get_spring(name: str) -> SpringConfig: """Get a spring config by name, fallback to 'smooth' if not found.""" return MOTION_TOKENS.spring_configs.get(name, MOTION_TOKENS.spring_configs["smooth"]) def get_enter_transition(name: str) -> EnterTransition: """Get an enter transition by name, fallback to 'fade_in' if not found.""" return MOTION_TOKENS.enter.get(name, MOTION_TOKENS.enter["fade_in"]) def get_exit_transition(name: str) -> ExitTransition: """Get an exit transition by name, fallback to 'fade_out' if not found.""" return MOTION_TOKENS.exit.get(name, MOTION_TOKENS.exit["fade_out"]) def get_tempo(name: str) -> TempoConfig: """Get a tempo config by name, fallback to 'medium' if not found.""" return MOTION_TOKENS.tempo.get(name, MOTION_TOKENS.tempo["medium"]) def get_platform_timing(platform: str) -> PlatformTimingConfig: """Get platform timing config by name, fallback to 'youtube_long_form' if not found.""" return MOTION_TOKENS.platform_timing.get( platform, MOTION_TOKENS.platform_timing["youtube_long_form"] ) def list_durations() -> list[str]: """List all available duration token names.""" return list(MOTION_TOKENS.duration.keys()) def list_easings() -> list[str]: """List all available easing token names.""" return list(MOTION_TOKENS.easing.keys()) def list_springs() -> list[str]: """List all available spring config names.""" return list(MOTION_TOKENS.spring_configs.keys()) def list_enter_transitions() -> list[str]: """List all available enter transition names.""" return list(MOTION_TOKENS.enter.keys()) def list_exit_transitions() -> list[str]: """List all available exit transition names.""" return list(MOTION_TOKENS.exit.keys()) def list_tempos() -> list[str]: """List all available tempo names.""" return list(MOTION_TOKENS.tempo.keys()) def list_platforms() -> list[str]: """List all available platform timing configs.""" return list(MOTION_TOKENS.platform_timing.keys())

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