# Motion Tokens Skills

This document is to provide skills that help LLMs generate code to enable dotLottie Motion Tokens to be used in a project, specifically Codepen, and enable designers to showcase their prototypes.

## Getting Started

In the very first prompt, the designer will already have a Codepen project with the following HTML and CSS files:

```html
<canvas id="dotLottie-canvas" data-src="LINK_TO_DOTLOTTIE_ASSET"></canvas>
```

```css
html,
body {
  height: 100%;
  margin: 0;
}

body {
  display: grid;
  place-items: center;
  background: #111;
}

#dotLottie-canvas {
  width: 320px;
  height: 320px;
  display: block;
}
```

These files generally shouldn't be changed unless the designer specifically requires it. You can ignore them otherwise.

Your job is to write the JavaScript file.

## Interpreting the .json

The designer will also provide a .json of the animation and possibly a few other .json files for themes and state machines. The animation file is a standard Lottie animation. It contains a number of layers and slots. The designer will refer to these slots as Motion Tokens. This will be the focus of their project, and you will have to help them wire up the code in the JavaScript to control these motion tokens.

The .json containing themes may be referenced in their request. For instance, the user may ask you to switch to a specific theme when a specific trigger happens. The themes may also be used simply to contain specific values of data for you to use. Like they may direct you to use a fill color of a specific token that is within a theme. They don't necessarily want to use the theme as a theme, but rather just as a way to show you the exact value that they want.

**NOTE**: You cannot blend themes. If the user asks you to transition between themes in any way that isn't instant, then you need to write a new function that effectively does the same thing, but use the theme data as reference values to target.

The .json containing a state machine can be used in a number of ways. For instance, it can be simply turned on at runtime. Or it can interface with the motion tokens. Like maybe the user will direct you to change a rotation token to 45 degrees when a certain event is triggered in the state machine.

## Building the JavaScript

```yaml
name: dotlottie
description: Guidelines for implementing dotLottie animations in web applications
```

# dotLottie Implementation Guidelines

You are an expert at implementing Lottie animations using dotLottie runtimes. Follow these guidelines when working with dotLottie in web projects.

## Package Selection

### Use `@lottiefiles/dotlottie-web` when:

- You need direct canvas control
- Building framework-agnostic code
- Maximum performance is critical
- You want the smallest bundle

### Use `@lottiefiles/dotlottie-react` when:

- Building React applications
- You want declarative component API
- You need React lifecycle integration

## Installation

```bash
# Web (vanilla JS, Vue, Svelte, etc.)
npm install @lottiefiles/dotlottie-web

# React
npm install @lottiefiles/dotlottie-react
```

## Basic Implementation

### Vanilla JavaScript

```typescript
import { DotLottie } from "@lottiefiles/dotlottie-web";

const dotLottie = new DotLottie({
  canvas: document.getElementById("canvas") as HTMLCanvasElement,
  src: "https://example.com/animation.lottie",
  autoplay: true,
  loop: true,
});
```

### React

```tsx
import { DotLottieReact } from "@lottiefiles/dotlottie-react";

function Animation() {
  return <DotLottieReact src="https://example.com/animation.lottie" autoplay loop />;
}
```

### React with Instance Control

```tsx
import { useRef } from "react";
import { DotLottieReact } from "@lottiefiles/dotlottie-react";
import type { DotLottie } from "@lottiefiles/dotlottie-web";

function Animation() {
  const dotLottieRef = useRef<DotLottie | null>(null);

  return (
    <DotLottieReact
      src="https://example.com/animation.lottie"
      dotLottieRefCallback={(dotLottie) => (dotLottieRef.current = dotLottie)}
    />
  );
}
```

## .lottie vs .json

**Always prefer `.lottie` format over `.json`:**

- Smaller file size (compressed)
- Supports multiple animations in one file
- Embedded assets (images, fonts)
- State machines for interactivity
- Theming with slots

## Web Workers (Recommended for Performance)

Use `DotLottieWorker` to offload animation rendering to a Web Worker, keeping the main thread free for UI interactions:

### Basic Worker Usage

```typescript
import { DotLottieWorker } from "@lottiefiles/dotlottie-web";

const dotLottie = new DotLottieWorker({
  canvas: document.getElementById("canvas") as HTMLCanvasElement,
  src: "https://example.com/animation.lottie",
  autoplay: true,
  loop: true,
});
```

### Worker Grouping (Multiple Animations)

By default, all `DotLottieWorker` instances share the same worker. Group animations into separate workers using `workerId`:

```typescript
// Hero animation in its own worker
const heroAnimation = new DotLottieWorker({
  canvas: heroCanvas,
  src: "hero.lottie",
  workerId: "hero-worker",
});

// UI animations share a different worker
const buttonAnimation = new DotLottieWorker({
  canvas: buttonCanvas,
  src: "button.lottie",
  workerId: "ui-worker",
});
```

### When to Use Workers

- **Use `DotLottieWorker`** for:
  - Multiple simultaneous animations
  - Complex animations with many layers
  - Animations running alongside heavy JS operations
  - Mobile devices where main thread performance is critical

- **Use regular `DotLottie`** for:
  - Single simple animations
  - When you need synchronous frame access
  - SSR environments (workers not available)

### React with Workers

```tsx
import { DotLottieWorkerReact } from "@lottiefiles/dotlottie-react";

function Animation() {
  return (
    <DotLottieWorkerReact
      src="animation.lottie"
      autoplay
      loop
      workerId="my-worker" // Optional: dedicate to specific worker
    />
  );
}
```

## State Machines (Interactivity)

State machines enable interactive animations without code. See the [State Machine Guide](https://github.com/LottieFiles/dotlottie-web/wiki/dotLottie-State-Machine-Guide) for details.

```typescript
const dotLottie = new DotLottie({
  canvas,
  src: "interactive.lottie", // Contains state machine
  autoplay: true,
});

// Fire events to trigger state transitions
dotLottie.stateMachineFireEvent("click");
dotLottie.stateMachineFireEvent("hover");
dotLottie.stateMachineFireEvent("custom-event");

// Set numeric/boolean/string inputs for state conditions
// See: https://github.com/LottieFiles/dotlottie-web/wiki/dotLottie-State-Machine-Guide#working-with-inputs
dotLottie.stateMachineSetNumericInput("progress", 0.5);
dotLottie.stateMachineSetBooleanInput("isActive", true);
dotLottie.stateMachineSetStringInput("mode", "dark");
```

### State Machine Events

- `click` - User click/tap
- `hover` - Mouse enter
- `unhover` - Mouse leave
- `complete` - Animation finished
- Custom events defined in the state machine

## Theming with Slots

Slots allow runtime color/value customization. Themes follow the [dotLottie 2.0 spec](https://dotlottie.io/spec/2.0/#themes).

```typescript
const dotLottie = new DotLottie({
  canvas,
  src: "themed.lottie",
  themeId: "dark-mode", // Use embedded theme by ID
});

// Or apply theme data directly (JSON string per dotLottie 2.0 spec)
// See: https://dotlottie.io/spec/2.0/#themes
dotLottie.setThemeData(
  JSON.stringify({
    rules: [
      { id: "primary-color", value: [1, 0.34, 0.13] }, // RGB values 0-1
    ],
  })
);
```

## Dynamic Slot Overriding

Slots enable runtime customization of animated properties. Use the typed slot APIs for type-safe property overriding.

### Discovering Available Slots

```typescript
// Get all slot IDs in the animation
const slotIds = dotLottie.getSlotIds();
// Returns: ['primary-color', 'scale', 'title-text', ...]

// Get the type of a specific slot
const type = dotLottie.getSlotType("primary-color");
// Returns: 'color' | 'scalar' | 'vector' | 'gradient' | 'text' | 'image' | undefined

// Get current value of a slot
const value = dotLottie.getSlot("primary-color");
// Returns the parsed slot value

// Get all slots as an object
const allSlots = dotLottie.getSlots();
// Returns: { 'primary-color': {...}, 'scale': {...}, ... }
```

### Slot Types and Their Setters

#### Color Slots

Override fill colors, stroke colors, or any color property.

```typescript
// Static color [r, g, b, a] - values 0-1
dotLottie.setColorSlot("background-color", [1, 0, 0, 1]); // Red

// Animated color (keyframes)
dotLottie.setColorSlot("background-color", [
  { t: 0, s: [1, 0, 0, 1] }, // Red at frame 0
  { t: 60, s: [0, 0, 1, 1] }, // Blue at frame 60
]);
```

#### Scalar Slots

Override single numeric values like rotation, opacity, stroke width.

```typescript
// Static value
dotLottie.setScalarSlot("rotation", 45);
dotLottie.setScalarSlot("opacity", 0.5);

// Animated value
dotLottie.setScalarSlot("rotation", [
  { t: 0, s: 0 }, // 0° at frame 0
  { t: 60, s: 360 }, // 360° at frame 60
]);
```

#### Vector Slots

Override position, scale, or any 2D/3D point property.

```typescript
// Static 2D vector
dotLottie.setVectorSlot("scale", [150, 150]);
dotLottie.setVectorSlot("position", [100, 200]);

// Animated vector
dotLottie.setVectorSlot("position", [
  { t: 0, s: [0, 0] },
  { t: 60, s: [100, 200] },
]);
```

#### Gradient Slots

Override gradient fills and strokes. Format: `[offset, r, g, b, offset, r, g, b, ...]`

```typescript
// Static gradient (red to blue)
dotLottie.setGradientSlot("bg-gradient", [
  0,
  1,
  0,
  0, // Red at position 0
  1,
  0,
  0,
  1, // Blue at position 1
]);

// Animated gradient
dotLottie.setGradientSlot("bg-gradient", [
  { t: 0, s: [0, 1, 0, 0, 1, 0, 0, 1] },
  { t: 60, s: [0, 0, 1, 0, 1, 1, 0, 0] },
]);
```

#### Text Slots

Override text content and styling. Supports partial updates - only provided properties change.

```typescript
// Full text document
dotLottie.setTextSlot("title", {
  t: "Hello World", // Text content
  s: 48, // Font size
  f: "Arial", // Font family
  fc: [0, 0, 0, 1], // Fill color (RGBA)
  j: 2, // Justify: 0=left, 1=right, 2=center
  tr: 50, // Tracking (letter spacing)
});

// Partial update - only change text, keep existing styles
dotLottie.setTextSlot("title", { t: "New Text" });

// Change color only
dotLottie.setTextSlot("title", { fc: [1, 0, 0, 1] });
```

### Resetting Slots

```typescript
// Reset a single slot to its original value
dotLottie.resetSlot("primary-color");

// Clear a single slot's custom value
dotLottie.clearSlot("primary-color");

// Reset all slots to original values
dotLottie.resetSlots();

// Clear all custom slot values
dotLottie.clearSlots();
```

### Bulk Slot Updates

```typescript
// Set multiple slots at once (untyped, for advanced use)
dotLottie.setSlots({
  "primary-color": { a: 0, k: [1, 0, 0, 1] },
  scale: { a: 0, k: [150, 150] },
});
```

### Common Use Cases

#### Dynamic Branding

```typescript
function applyBrandColors(primary: string, secondary: string) {
  const primaryRgb = hexToRgb(primary); // Your conversion function
  const secondaryRgb = hexToRgb(secondary);

  dotLottie.setColorSlot("brand-primary", [...primaryRgb, 1]);
  dotLottie.setColorSlot("brand-secondary", [...secondaryRgb, 1]);
}
```

#### User-Customizable Text

```typescript
function updateGreeting(userName: string) {
  dotLottie.setTextSlot("greeting", { t: `Hello, ${userName}!` });
}
```

#### Responsive Scaling

```typescript
function updateScale(scaleFactor: number) {
  dotLottie.setVectorSlot("content-scale", [scaleFactor * 100, scaleFactor * 100]);
}
```

#### Dark Mode Toggle

```typescript
function setDarkMode(isDark: boolean) {
  if (isDark) {
    dotLottie.setColorSlot("background", [0.1, 0.1, 0.1, 1]);
    dotLottie.setColorSlot("text-color", [1, 1, 1, 1]);
  } else {
    dotLottie.resetSlot("background");
    dotLottie.resetSlot("text-color");
  }
}
```

#### Progress Indicator

```typescript
function updateProgress(percent: number) {
  // Assuming a scalar slot controls fill amount or similar
  dotLottie.setScalarSlot("progress-value", percent);
}
```

### React Integration

```tsx
import { useRef, useEffect } from "react";
import { DotLottieReact } from "@lottiefiles/dotlottie-react";
import type { DotLottie } from "@lottiefiles/dotlottie-web";

function BrandedAnimation({ primaryColor, userName }) {
  const dotLottieRef = useRef<DotLottie | null>(null);

  useEffect(() => {
    if (dotLottieRef.current) {
      dotLottieRef.current.setColorSlot("brand-color", primaryColor);
      dotLottieRef.current.setTextSlot("greeting", { t: `Welcome, ${userName}!` });
    }
  }, [primaryColor, userName]);

  return (
    <DotLottieReact
      src="branded-animation.lottie"
      autoplay
      loop
      dotLottieRefCallback={(dotLottie) => {
        dotLottieRef.current = dotLottie;
      }}
    />
  );
}
```

## Markers & Segments

### Playing Specific Segments

```typescript
// Play frames 0-60
dotLottie.setSegment(0, 60);
dotLottie.play();

// Play by marker name (defined in animation)
dotLottie.setMarker("intro");
dotLottie.play();
```

### Getting Markers

```typescript
const markers = dotLottie.markers();
// Returns: [{ name: 'intro', time: 0, duration: 60 }, ...]
```

## Event Handling

```typescript
dotLottie.addEventListener("load", () => {
  console.log("Animation loaded");
});

dotLottie.addEventListener("play", () => {
  console.log("Playing");
});

dotLottie.addEventListener("complete", () => {
  console.log("Animation completed");
});

dotLottie.addEventListener("frame", ({ currentFrame }) => {
  console.log("Frame:", currentFrame);
});

// Clean up
dotLottie.removeEventListener("load", handler);
```

## Performance Best Practices

### 1. Use Web Workers for Complex Animations

```typescript
import { DotLottieWorker } from "@lottiefiles/dotlottie-web";

// Offload rendering to worker thread
const dotLottie = new DotLottieWorker({
  canvas,
  src: "complex-animation.lottie",
});
```

### 2. Lazy Load Animations

```typescript
// Only load when visible
const observer = new IntersectionObserver((entries) => {
  entries.forEach((entry) => {
    if (entry.isIntersecting) {
      loadAnimation();
      observer.disconnect();
    }
  });
});
observer.observe(container);
```

### 3. Auto-Freeze is Enabled by Default

DotLottie automatically freezes animations when they're not visible (offscreen). To disable this behavior:

```typescript
const dotLottie = new DotLottie({
  canvas,
  src: "animation.lottie",
  renderConfig: {
    freezeOnOffscreen: false, // Disable auto-freeze (not recommended)
  },
});
```

### 4. Device Pixel Ratio

By default, devicePixelRatio is set to 75% of the actual value for better performance. For full retina quality (with higher performance cost):

```typescript
const dotLottie = new DotLottie({
  canvas,
  src: "animation.lottie",
  renderConfig: {
    devicePixelRatio: window.devicePixelRatio, // Full retina (higher CPU/GPU)
  },
});
```

### 5. Clean Up (Vanilla JS only)

```typescript
// Always destroy when done (vanilla JS)
dotLottie.destroy();
```

**Note:** `DotLottieReact` handles cleanup automatically on unmount - no manual cleanup needed.

### 6. Frame Interpolation Control

```typescript
const dotLottie = new DotLottie({
  canvas,
  src: "animation.lottie",
  useFrameInterpolation: true, // Smooth playback (default)
  // useFrameInterpolation: false, // Match original AE frame rate
});
```

## Multi-Animation Files

A single `.lottie` file can contain multiple animations:

```typescript
// Load specific animation by ID
dotLottie.loadAnimation("animation-2");

// Get all animation IDs
const animations = dotLottie.manifest?.animations;
// Returns: [{ id: 'animation-1' }, { id: 'animation-2' }]
```

## Canvas Sizing

**Set canvas size via CSS styles** (recommended). DotLottie will automatically determine the optimal drawing area:

```html
<canvas id="canvas" style="width: 400px; height: 400px;"></canvas>
```

### Auto-Resize to Container

Use the `autoResize` render config to automatically resize when the container changes:

```typescript
const dotLottie = new DotLottie({
  canvas,
  src: "animation.lottie",
  renderConfig: {
    autoResize: true, // Canvas resizes to fit container
  },
});
```

## Common Patterns

### Play on Hover

```typescript
canvas.addEventListener("mouseenter", () => dotLottie.play());
canvas.addEventListener("mouseleave", () => dotLottie.pause());
```

### Play on Click (Once)

```typescript
canvas.addEventListener("click", () => {
  dotLottie.setFrame(0);
  dotLottie.setLoop(false);
  dotLottie.play();
});
```

### Scrub with Scroll

```typescript
window.addEventListener("scroll", () => {
  const progress = window.scrollY / (document.body.scrollHeight - window.innerHeight);
  const frame = progress * dotLottie.totalFrames;
  dotLottie.setFrame(frame);
});
```

### Loading States (React)

```tsx
function Animation() {
  const [isLoaded, setIsLoaded] = useState(false);

  return (
    <>
      {!isLoaded && <Skeleton />}
      <DotLottieReact
        src="animation.lottie"
        style={{ opacity: isLoaded ? 1 : 0 }}
        dotLottieRefCallback={(dotLottie) => {
          dotLottie.addEventListener("load", () => setIsLoaded(true));
        }}
      />
    </>
  );
}
```

### Responsive Animation

```tsx
function ResponsiveAnimation() {
  return (
    <DotLottieReact
      src="animation.lottie"
      autoplay
      loop
      style={{ width: "100%", maxWidth: "400px" }}
      renderConfig={{ autoResize: true }}
    />
  );
}
```

## Debugging

```typescript
// Check if loaded
console.log("Loaded:", dotLottie.isLoaded);

// Get animation info
console.log("Duration:", dotLottie.duration);
console.log("Total Frames:", dotLottie.totalFrames);
console.log("Current Frame:", dotLottie.currentFrame);
console.log("Is Playing:", dotLottie.isPlaying);
console.log("Loop:", dotLottie.loop);
console.log("Speed:", dotLottie.speed);

// Get manifest (for .lottie files)
console.log("Manifest:", dotLottie.manifest);
```

## Error Handling

```typescript
dotLottie.addEventListener("loadError", (error) => {
  console.error("Failed to load animation:", error);
  // Show fallback UI
});
```

## SSR / Next.js Considerations

dotLottie requires browser APIs. For SSR frameworks:

```tsx
import dynamic from "next/dynamic";

const DotLottieReact = dynamic(
  () => import("@lottiefiles/dotlottie-react").then((mod) => mod.DotLottieReact),
  { ssr: false }
);

function Animation() {
  return <DotLottieReact src="animation.lottie" autoplay loop />;
}
```

## Resources

- Documentation: [dotLottie Player Documentation | LottieFiles Developer Portal](https://developers.lottiefiles.com/docs/dotlottie-player)
- State Machine Guide: [dotLottie State Machine Guide · LottieFiles/dotlottie-web Wiki · GitHub](https://github.com/LottieFiles/dotlottie-web/wiki/dotLottie-State-Machine-Guide)
- GitHub: [GitHub - LottieFiles/dotlottie-web: Official LottieFiles player for rendering Lottie and dotLottie animations in the web. Supports React, Vue, Svelte, SolidJS and Web Components.](https://github.com/LottieFiles/dotlottie-web)
- dotLottie 2.0 Spec: [dotLottie v2.0 Specification | dotLottie](https://dotlottie.io/spec/2.0/)
- Create .lottie files: [https://lottiefiles.com](https://lottiefiles.com) or [https://creators.lottiefiles.com](https://creators.lottiefiles.com)