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 {