diff --git a/bun.lock b/bun.lock old mode 100755 new mode 100644 index 6ffe25f..bff641a --- a/bun.lock +++ b/bun.lock @@ -1,6 +1,5 @@ { "lockfileVersion": 1, - "configVersion": 1, "workspaces": { "": { "name": "nextjs_tailwind_shadcn_ts", @@ -48,6 +47,7 @@ "embla-carousel-react": "^8.6.0", "framer-motion": "^12.23.2", "input-otp": "^1.4.2", + "lottie-web": "^5.13.0", "lucide-react": "^0.525.0", "next": "^16.1.1", "next-auth": "^4.24.11", @@ -1334,6 +1334,8 @@ "loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="], + "lottie-web": ["lottie-web@5.13.0", "", {}, "sha512-+gfBXl6sxXMPe8tKQm7qzLnUy5DUPJPKIyRHwtpCpyUEYjHYRJC/5gjUvdkuO2c3JllrPtHXH5UJJK8LRYl5yQ=="], + "lowlight": ["lowlight@1.20.0", "", { "dependencies": { "fault": "^1.0.0", "highlight.js": "~10.7.0" } }, "sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw=="], "lru-cache": ["lru-cache@6.0.0", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="], diff --git a/package.json b/package.json index 15eb5d9..f3cf5be 100755 --- a/package.json +++ b/package.json @@ -60,6 +60,7 @@ "embla-carousel-react": "^8.6.0", "framer-motion": "^12.23.2", "input-otp": "^1.4.2", + "lottie-web": "^5.13.0", "lucide-react": "^0.525.0", "next": "^16.1.1", "next-auth": "^4.24.11", diff --git a/packages/create-motionforge/templates/hello-world/page.tsx.template b/packages/create-motionforge/templates/hello-world/page.tsx.template index 0f5e5ec..2de287e 100644 --- a/packages/create-motionforge/templates/hello-world/page.tsx.template +++ b/packages/create-motionforge/templates/hello-world/page.tsx.template @@ -1,7 +1,16 @@ 'use client'; import React from 'react'; -import { Player, AbsoluteFill, useCurrentFrame, interpolate, spring, useVideoConfig } from 'motionforge'; +import { + Player, + AbsoluteFill, + useCurrentFrame, + interpolate, + spring, + useVideoConfig, + Lottie, + Sequence +} from 'motionforge'; const HelloWorldComposition = () => { const frame = useCurrentFrame(); @@ -25,12 +34,26 @@ const HelloWorldComposition = () => { style={{ opacity, transform: `scale(${scale}) translateY(${translateY}px)`, - textAlign: 'center' + textAlign: 'center', + zIndex: 10, }} >

Hello MotionForge!

Your programmatic video journey starts here.

+ + {/* Lottie Animation Example */} + + +
+ +
+
+
); }; diff --git a/packages/create-motionforge/templates/shared/package.json.template b/packages/create-motionforge/templates/shared/package.json.template index c7661c2..7dc9530 100644 --- a/packages/create-motionforge/templates/shared/package.json.template +++ b/packages/create-motionforge/templates/shared/package.json.template @@ -10,6 +10,7 @@ }, "dependencies": { "motionforge": "^1.2.0", + "lottie-web": "^5.13.0", "next": "15.1.0", "react": "^19.0.0", "react-dom": "^19.0.0", diff --git a/packages/motionforge/CHANGELOG.md b/packages/motionforge/CHANGELOG.md index 963af21..51c2e51 100644 --- a/packages/motionforge/CHANGELOG.md +++ b/packages/motionforge/CHANGELOG.md @@ -5,6 +5,18 @@ All notable changes to MotionForge will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.3.0] - 2024-03-20 + +### Added +- Full Lottie animation integration with `` component +- Deterministic frame synchronization with MotionForge timeline +- Support for both remote JSON URLs and local JSON objects +- Advanced Lottie controls: `frameStart`, `frameEnd`, `playbackRate`, `loop` +- Automatic relative frame mapping within `` +- Production-grade performance with memoization and instance cleanup +- CLI template updates with Lottie support and examples +- Comprehensive Lottie documentation in README + ## [1.2.0] - 2024-01-15 ### Added diff --git a/packages/motionforge/README.md b/packages/motionforge/README.md index b450538..5c08f29 100644 --- a/packages/motionforge/README.md +++ b/packages/motionforge/README.md @@ -259,6 +259,62 @@ MotionForge uses a dark theme by default with emerald green accents. Customize c } ``` +## 🎭 Using Lottie in MotionForge + +MotionForge provides first-class, production-grade Lottie support. Animations are synchronized with the frame system for deterministic, frame-perfect rendering. + +### Basic Usage + +```tsx +import { Lottie, AbsoluteFill } from 'motionforge'; + +const MyComposition = () => { + return ( + + + + ); +}; +``` + +### Advanced Configuration + +The `Lottie` component supports several props to control playback and appearance: + +| Prop | Type | Default | Description | +|------|------|---------|-------------| +| `src` | `string \| object` | - | URL to JSON or imported JSON object | +| `frameStart` | `number` | `0` | The frame at which the Lottie starts | +| `frameEnd` | `number` | - | The frame at which the Lottie ends | +| `playbackRate` | `number` | `1` | Speed of the animation | +| `loop` | `boolean` | `false` | Whether to loop the animation | +| `width` | `number \| string` | `100%` | Width of the container | +| `height` | `number \| string` | `100%` | Height of the container | + +### Syncing with Sequences + +`Lottie` automatically detects if it's inside a `Sequence` and adjusts its internal timing to match the relative frame. + +```tsx + + + +``` + +### Performance Tips + +1. **Pre-loading**: For imported JSON objects, Lottie initializes instantly. For URLs, it fetches the data once and memoizes the instance. +2. **SSR Support**: The component handles server-side rendering gracefully by only initializing the Lottie engine on the client. +3. **Memoization**: Internal animation instances are memoized and properly cleaned up to prevent memory leaks. + ## 📖 Documentation - [Getting Started](https://motionforge.dev/docs/getting-started) diff --git a/packages/motionforge/bun.lock b/packages/motionforge/bun.lock index 4886487..8e224f3 100644 --- a/packages/motionforge/bun.lock +++ b/packages/motionforge/bun.lock @@ -1,9 +1,11 @@ { "lockfileVersion": 1, - "configVersion": 1, "workspaces": { "": { "name": "motionforge", + "dependencies": { + "lottie-web": "^5.13.0", + }, "devDependencies": { "@types/react": "^19.0.0", "@types/react-dom": "^19.0.0", @@ -171,6 +173,8 @@ "load-tsconfig": ["load-tsconfig@0.2.5", "", {}, "sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg=="], + "lottie-web": ["lottie-web@5.13.0", "", {}, "sha512-+gfBXl6sxXMPe8tKQm7qzLnUy5DUPJPKIyRHwtpCpyUEYjHYRJC/5gjUvdkuO2c3JllrPtHXH5UJJK8LRYl5yQ=="], + "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], "mlly": ["mlly@1.8.0", "", { "dependencies": { "acorn": "^8.15.0", "pathe": "^2.0.3", "pkg-types": "^1.3.1", "ufo": "^1.6.1" } }, "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g=="], diff --git a/packages/motionforge/package.json b/packages/motionforge/package.json index 09013e4..ae6d810 100644 --- a/packages/motionforge/package.json +++ b/packages/motionforge/package.json @@ -1,6 +1,6 @@ { "name": "motionforge", - "version": "1.2.0", + "version": "1.3.0", "description": "A React-based framework for creating videos programmatically. Build stunning videos with React components, spring animations, and frame-perfect control.", "author": "MotionForge Team", "license": "MIT", @@ -73,6 +73,9 @@ "react": ">=18.0.0", "react-dom": ">=18.0.0" }, + "dependencies": { + "lottie-web": "^5.13.0" + }, "devDependencies": { "@types/react": "^19.0.0", "@types/react-dom": "^19.0.0", diff --git a/packages/motionforge/src/components/Lottie.tsx b/packages/motionforge/src/components/Lottie.tsx new file mode 100644 index 0000000..96e9a25 --- /dev/null +++ b/packages/motionforge/src/components/Lottie.tsx @@ -0,0 +1,182 @@ +'use client'; + +import React, { useEffect, useRef, useState, useMemo } from 'react'; +import lottie, { AnimationItem } from 'lottie-web'; +import { useCurrentFrame } from '../core/context'; +import { useRelativeCurrentFrame } from './Sequence'; + +export interface LottieProps { + /** + * Source of the Lottie animation. Can be a URL to a JSON file or an imported JSON object. + */ + src: string | object; + /** + * The frame at which the Lottie animation should start playing. + * Default is 0. + */ + frameStart?: number; + /** + * The frame at which the Lottie animation should end playing. + * Default is the last frame of the Lottie animation. + */ + frameEnd?: number; + /** + * Playback rate of the Lottie animation. + * Default is 1. + */ + playbackRate?: number; + /** + * Whether the Lottie animation should loop. + * Default is false. + */ + loop?: boolean; + /** + * Width of the Lottie container. + */ + width?: number | string; + /** + * Height of the Lottie container. + */ + height?: number | string; + /** + * Style overrides for the Lottie container. + */ + style?: React.CSSProperties; + /** + * CSS class name for the Lottie container. + */ + className?: string; +} + +/** + * Lottie Component for MotionForge. + * + * Provides production-grade Lottie support with deterministic frame synchronization. + * Synchronizes with the MotionForge frame system instead of using time-based playback. + */ +export const Lottie: React.FC = ({ + src, + frameStart = 0, + frameEnd, + playbackRate = 1, + loop = false, + width, + height, + style, + className, +}) => { + const containerRef = useRef(null); + const animationRef = useRef(null); + const [isLoaded, setIsLoaded] = useState(false); + const [error, setError] = useState(null); + + const absoluteFrame = useCurrentFrame(); + const relativeFrame = useRelativeCurrentFrame(); + + // Use relative frame if inside a Sequence, otherwise use absolute frame + const currentFrame = relativeFrame !== null ? relativeFrame : absoluteFrame; + + // Handle initialization + useEffect(() => { + if (typeof window === 'undefined' || !containerRef.current) return; + + let isCancelled = false; + + const params: any = { + container: containerRef.current, + renderer: 'svg', + loop: false, + autoplay: false, + rendererSettings: { + progressiveLoad: false, + hideOnTransparent: true, + } + }; + + if (typeof src === 'string') { + params.path = src; + } else { + params.animationData = src; + } + + try { + const anim = lottie.loadAnimation(params); + animationRef.current = anim; + + const onLoaded = () => { + if (!isCancelled) { + setIsLoaded(true); + } + }; + + // Both events can be useful depending on how Lottie is loaded + anim.addEventListener('DOMLoaded', onLoaded); + anim.addEventListener('data_ready', onLoaded); + + return () => { + isCancelled = true; + anim.destroy(); + animationRef.current = null; + }; + } catch (err) { + setError(err instanceof Error ? err.message : String(err)); + return () => {}; + } + }, [src]); + + // Handle frame synchronization + useEffect(() => { + if (!animationRef.current || !isLoaded) return; + + const anim = animationRef.current; + + // totalFrames is available once loaded + const totalLottieFrames = anim.totalFrames; + + // Determine the range of frames to play + const start = frameStart; + const end = frameEnd ?? totalLottieFrames; + const duration = end - start; + + if (duration <= 0) return; + + // Map MotionForge currentFrame to Lottie frame + // playbackRate affects how fast we move through the Lottie timeline + let targetFrame = currentFrame * playbackRate; + + if (loop) { + targetFrame = targetFrame % duration; + } else { + targetFrame = Math.min(targetFrame, duration - 0.01); + } + + // finalFrame is relative to the Lottie's original timeline + const finalFrame = start + targetFrame; + + // goToAndStop(value, isFrame) - second arg true means it's a frame number, not time + anim.goToAndStop(finalFrame, true); + }, [currentFrame, isLoaded, frameStart, frameEnd, playbackRate, loop]); + + const containerStyle: React.CSSProperties = useMemo(() => ({ + width: width ?? '100%', + height: height ?? '100%', + ...style, + }), [width, height, style]); + + if (error) { + return ( +
+ Lottie Error: {error} +
+ ); + } + + return ( +
+ ); +}; diff --git a/packages/motionforge/src/components/Media.tsx b/packages/motionforge/src/components/Media.tsx index c081791..d271953 100755 --- a/packages/motionforge/src/components/Media.tsx +++ b/packages/motionforge/src/components/Media.tsx @@ -6,7 +6,7 @@ import { interpolate } from '../utils/animation'; // Absolute Fill - Container component interface AbsoluteFillProps { - children: React.ReactNode; + children?: React.ReactNode; style?: React.CSSProperties; className?: string; } diff --git a/packages/motionforge/src/components/Sequence.tsx b/packages/motionforge/src/components/Sequence.tsx index 16eac52..6226ae9 100755 --- a/packages/motionforge/src/components/Sequence.tsx +++ b/packages/motionforge/src/components/Sequence.tsx @@ -107,7 +107,7 @@ const SequenceFrameProvider: React.FC = ({ }; // Relative Frame Context -const RelativeFrameContext = createContext(0); +const RelativeFrameContext = createContext(null); export const useRelativeCurrentFrame = () => useContext(RelativeFrameContext); diff --git a/packages/motionforge/src/demo/DemoLottie.tsx b/packages/motionforge/src/demo/DemoLottie.tsx new file mode 100644 index 0000000..0075b1f --- /dev/null +++ b/packages/motionforge/src/demo/DemoLottie.tsx @@ -0,0 +1,130 @@ +'use client'; + +import React from 'react'; +import { + AbsoluteFill, + Sequence, + Lottie, + useCurrentFrame, +} from '../index'; + +// Simple rotating square Lottie JSON for local testing +const simpleLottie = { + "v": "5.5.7", + "fr": 30, + "ip": 0, + "op": 30, + "w": 100, + "h": 100, + "nm": "Simple Square", + "ddd": 0, + "assets": [], + "layers": [ + { + "ddd": 0, + "ind": 1, + "ty": 1, + "nm": "Solid", + "sr": 1, + "ks": { + "o": { "a": 0, "k": 100, "ix": 11 }, + "r": { "a": 1, "k": [{ "t": 0, "s": [0] }, { "t": 30, "s": [360] }], "ix": 10 }, + "p": { "a": 0, "k": [50, 50, 0], "ix": 2 }, + "a": { "a": 0, "k": [50, 50, 0], "ix": 1 }, + "s": { "a": 0, "k": [100, 100, 100], "ix": 6 } + }, + "ao": 0, + "sw": 100, + "sh": 100, + "sc": "#10b981", + "ip": 0, + "op": 30, + "st": 0, + "bm": 0 + } + ] +}; + +/** + * Demo Composition showcasing Lottie integration in MotionForge. + */ +export const DemoLottie: React.FC = () => { + const frame = useCurrentFrame(); + + const backgroundHue = (frame * 0.5) % 360; + + return ( + + {/* Title */} +
+

Lottie Support

+

Production-grade, deterministic animations

+
+ + {/* Main Lottie - Direct JSON */} + + +
+ +
+
Direct JSON (Rotating Square)
+
+
+ + {/* Remote Lottie - 2x Speed */} + +
+
+ +
+
Remote URL (2x Speed)
+
+
+ + {/* Sliced Lottie */} + +
+
+ +
+
Sliced (Frames 10-20)
+
+
+ + {/* Footer info */} +
+ MotionForge Frame: {frame} | Deterministic Rendering: Enabled +
+
+ ); +}; + +export default DemoLottie; diff --git a/packages/motionforge/src/index.ts b/packages/motionforge/src/index.ts index c73095c..968199e 100755 --- a/packages/motionforge/src/index.ts +++ b/packages/motionforge/src/index.ts @@ -47,6 +47,8 @@ export { G, staticFile, } from './components/Media'; +export { Lottie } from './components/Lottie'; +export type { LottieProps } from './components/Lottie'; // Effect components export { diff --git a/src/app/page.tsx b/src/app/page.tsx index fc453c9..2888a3a 100755 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -7,6 +7,7 @@ import { DemoComposition2 } from '@/lib/remotion/demo/DemoComposition2'; import { DemoComposition3 } from '@/lib/remotion/demo/DemoComposition3'; import { DemoComposition4 } from '@/lib/remotion/demo/DemoComposition4'; import { DemoComposition5 } from '@/lib/remotion/demo/DemoComposition5'; +import { DemoLottie } from '@/lib/remotion/demo/DemoLottie'; import { PlayIcon, FilmIcon, @@ -75,6 +76,13 @@ const demoCompositions = [ component: DemoComposition5, durationInFrames: 1590, }, + { + id: 'lottie', + name: 'Lottie', + description: 'Deterministic Lottie animations', + component: DemoLottie, + durationInFrames: 300, + }, ]; // Code examples diff --git a/src/lib/remotion/components/Lottie.tsx b/src/lib/remotion/components/Lottie.tsx new file mode 100644 index 0000000..96e9a25 --- /dev/null +++ b/src/lib/remotion/components/Lottie.tsx @@ -0,0 +1,182 @@ +'use client'; + +import React, { useEffect, useRef, useState, useMemo } from 'react'; +import lottie, { AnimationItem } from 'lottie-web'; +import { useCurrentFrame } from '../core/context'; +import { useRelativeCurrentFrame } from './Sequence'; + +export interface LottieProps { + /** + * Source of the Lottie animation. Can be a URL to a JSON file or an imported JSON object. + */ + src: string | object; + /** + * The frame at which the Lottie animation should start playing. + * Default is 0. + */ + frameStart?: number; + /** + * The frame at which the Lottie animation should end playing. + * Default is the last frame of the Lottie animation. + */ + frameEnd?: number; + /** + * Playback rate of the Lottie animation. + * Default is 1. + */ + playbackRate?: number; + /** + * Whether the Lottie animation should loop. + * Default is false. + */ + loop?: boolean; + /** + * Width of the Lottie container. + */ + width?: number | string; + /** + * Height of the Lottie container. + */ + height?: number | string; + /** + * Style overrides for the Lottie container. + */ + style?: React.CSSProperties; + /** + * CSS class name for the Lottie container. + */ + className?: string; +} + +/** + * Lottie Component for MotionForge. + * + * Provides production-grade Lottie support with deterministic frame synchronization. + * Synchronizes with the MotionForge frame system instead of using time-based playback. + */ +export const Lottie: React.FC = ({ + src, + frameStart = 0, + frameEnd, + playbackRate = 1, + loop = false, + width, + height, + style, + className, +}) => { + const containerRef = useRef(null); + const animationRef = useRef(null); + const [isLoaded, setIsLoaded] = useState(false); + const [error, setError] = useState(null); + + const absoluteFrame = useCurrentFrame(); + const relativeFrame = useRelativeCurrentFrame(); + + // Use relative frame if inside a Sequence, otherwise use absolute frame + const currentFrame = relativeFrame !== null ? relativeFrame : absoluteFrame; + + // Handle initialization + useEffect(() => { + if (typeof window === 'undefined' || !containerRef.current) return; + + let isCancelled = false; + + const params: any = { + container: containerRef.current, + renderer: 'svg', + loop: false, + autoplay: false, + rendererSettings: { + progressiveLoad: false, + hideOnTransparent: true, + } + }; + + if (typeof src === 'string') { + params.path = src; + } else { + params.animationData = src; + } + + try { + const anim = lottie.loadAnimation(params); + animationRef.current = anim; + + const onLoaded = () => { + if (!isCancelled) { + setIsLoaded(true); + } + }; + + // Both events can be useful depending on how Lottie is loaded + anim.addEventListener('DOMLoaded', onLoaded); + anim.addEventListener('data_ready', onLoaded); + + return () => { + isCancelled = true; + anim.destroy(); + animationRef.current = null; + }; + } catch (err) { + setError(err instanceof Error ? err.message : String(err)); + return () => {}; + } + }, [src]); + + // Handle frame synchronization + useEffect(() => { + if (!animationRef.current || !isLoaded) return; + + const anim = animationRef.current; + + // totalFrames is available once loaded + const totalLottieFrames = anim.totalFrames; + + // Determine the range of frames to play + const start = frameStart; + const end = frameEnd ?? totalLottieFrames; + const duration = end - start; + + if (duration <= 0) return; + + // Map MotionForge currentFrame to Lottie frame + // playbackRate affects how fast we move through the Lottie timeline + let targetFrame = currentFrame * playbackRate; + + if (loop) { + targetFrame = targetFrame % duration; + } else { + targetFrame = Math.min(targetFrame, duration - 0.01); + } + + // finalFrame is relative to the Lottie's original timeline + const finalFrame = start + targetFrame; + + // goToAndStop(value, isFrame) - second arg true means it's a frame number, not time + anim.goToAndStop(finalFrame, true); + }, [currentFrame, isLoaded, frameStart, frameEnd, playbackRate, loop]); + + const containerStyle: React.CSSProperties = useMemo(() => ({ + width: width ?? '100%', + height: height ?? '100%', + ...style, + }), [width, height, style]); + + if (error) { + return ( +
+ Lottie Error: {error} +
+ ); + } + + return ( +
+ ); +}; diff --git a/src/lib/remotion/components/Media.tsx b/src/lib/remotion/components/Media.tsx index c081791..d271953 100755 --- a/src/lib/remotion/components/Media.tsx +++ b/src/lib/remotion/components/Media.tsx @@ -6,7 +6,7 @@ import { interpolate } from '../utils/animation'; // Absolute Fill - Container component interface AbsoluteFillProps { - children: React.ReactNode; + children?: React.ReactNode; style?: React.CSSProperties; className?: string; } diff --git a/src/lib/remotion/components/Sequence.tsx b/src/lib/remotion/components/Sequence.tsx index 16eac52..6226ae9 100755 --- a/src/lib/remotion/components/Sequence.tsx +++ b/src/lib/remotion/components/Sequence.tsx @@ -107,7 +107,7 @@ const SequenceFrameProvider: React.FC = ({ }; // Relative Frame Context -const RelativeFrameContext = createContext(0); +const RelativeFrameContext = createContext(null); export const useRelativeCurrentFrame = () => useContext(RelativeFrameContext); diff --git a/src/lib/remotion/demo/DemoLottie.tsx b/src/lib/remotion/demo/DemoLottie.tsx new file mode 100644 index 0000000..0075b1f --- /dev/null +++ b/src/lib/remotion/demo/DemoLottie.tsx @@ -0,0 +1,130 @@ +'use client'; + +import React from 'react'; +import { + AbsoluteFill, + Sequence, + Lottie, + useCurrentFrame, +} from '../index'; + +// Simple rotating square Lottie JSON for local testing +const simpleLottie = { + "v": "5.5.7", + "fr": 30, + "ip": 0, + "op": 30, + "w": 100, + "h": 100, + "nm": "Simple Square", + "ddd": 0, + "assets": [], + "layers": [ + { + "ddd": 0, + "ind": 1, + "ty": 1, + "nm": "Solid", + "sr": 1, + "ks": { + "o": { "a": 0, "k": 100, "ix": 11 }, + "r": { "a": 1, "k": [{ "t": 0, "s": [0] }, { "t": 30, "s": [360] }], "ix": 10 }, + "p": { "a": 0, "k": [50, 50, 0], "ix": 2 }, + "a": { "a": 0, "k": [50, 50, 0], "ix": 1 }, + "s": { "a": 0, "k": [100, 100, 100], "ix": 6 } + }, + "ao": 0, + "sw": 100, + "sh": 100, + "sc": "#10b981", + "ip": 0, + "op": 30, + "st": 0, + "bm": 0 + } + ] +}; + +/** + * Demo Composition showcasing Lottie integration in MotionForge. + */ +export const DemoLottie: React.FC = () => { + const frame = useCurrentFrame(); + + const backgroundHue = (frame * 0.5) % 360; + + return ( + + {/* Title */} +
+

Lottie Support

+

Production-grade, deterministic animations

+
+ + {/* Main Lottie - Direct JSON */} + + +
+ +
+
Direct JSON (Rotating Square)
+
+
+ + {/* Remote Lottie - 2x Speed */} + +
+
+ +
+
Remote URL (2x Speed)
+
+
+ + {/* Sliced Lottie */} + +
+
+ +
+
Sliced (Frames 10-20)
+
+
+ + {/* Footer info */} +
+ MotionForge Frame: {frame} | Deterministic Rendering: Enabled +
+
+ ); +}; + +export default DemoLottie; diff --git a/src/lib/remotion/hooks/performance.ts b/src/lib/remotion/hooks/performance.ts index 65dd32a..6235c5d 100644 --- a/src/lib/remotion/hooks/performance.ts +++ b/src/lib/remotion/hooks/performance.ts @@ -130,7 +130,7 @@ export function useOptimizedSpring( export function useOptimizedInterpolate( inputRange: number[], outputRange: number[], - options?: { easing?: (t: number) => number; extrapolateLeft?: string; extrapolateRight?: string } + options?: { easing?: (t: number) => number; extrapolateLeft?: 'clamp' | 'extend' | 'identity'; extrapolateRight?: 'clamp' | 'extend' | 'identity' } ): (frame: number) => number { return useCallback((frame: number) => { return interpolate(frame, inputRange, outputRange, options); diff --git a/src/lib/remotion/index.ts b/src/lib/remotion/index.ts index c73095c..968199e 100755 --- a/src/lib/remotion/index.ts +++ b/src/lib/remotion/index.ts @@ -47,6 +47,8 @@ export { G, staticFile, } from './components/Media'; +export { Lottie } from './components/Lottie'; +export type { LottieProps } from './components/Lottie'; // Effect components export {