> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/BintzGavin/helios/llms.txt
> Use this file to discover all available pages before exploring further.

# Compositions

> How to structure and configure Helios video compositions

A **composition** in Helios is a combination of HTML/CSS/JavaScript and configuration that defines what should be rendered into a video.

## Composition basics

At its core, a composition is:

1. **Configuration**: Duration, FPS, dimensions, and other metadata
2. **Content**: HTML, CSS, and JavaScript that renders the visual output
3. **Timeline**: Optional tracks and clips for multi-layer compositions

### Creating a composition

```typescript theme={null}
import { Helios } from '@helios-project/core';

const helios = new Helios({
  duration: 10,      // 10 seconds
  fps: 30,           // 30 frames per second
  width: 1920,       // Optional, defaults to 1920
  height: 1080,      // Optional, defaults to 1080
  autoSyncAnimations: true  // Enable WAAPI sync
});
```

See `packages/core/src/types.ts:11` for the full `HeliosConfig` interface.

## Configuration options

### Required properties

<Tabs>
  <Tab title="duration">
    Duration of the composition in **seconds** (not frames).

    ```typescript theme={null}
    const helios = new Helios({
      duration: 10,  // 10 second video
      fps: 30
    });
    ```

    * Must be non-negative
    * Can be changed dynamically with `setDuration()`
    * Frames are calculated as `duration * fps`
  </Tab>

  <Tab title="fps">
    Frame rate in frames per second.

    ```typescript theme={null}
    const helios = new Helios({
      duration: 10,
      fps: 60  // 60fps for smooth motion
    });
    ```

    Common values:

    * `24` - Cinematic
    * `30` - Standard web video
    * `60` - High frame rate

    Can be changed with `setFps()` (preserves playback time).
  </Tab>
</Tabs>

### Optional properties

```typescript theme={null}
interface HeliosConfig {
  width?: number;              // Default: 1920
  height?: number;             // Default: 1080
  initialFrame?: number;       // Default: 0
  loop?: boolean;              // Default: false
  playbackRange?: [number, number];  // Frame range for partial playback
  autoSyncAnimations?: boolean;      // Default: false
  inputProps?: TInputProps;    // User-defined data
  schema?: HeliosSchema;       // Validation schema for inputProps
  playbackRate?: number;       // Default: 1.0
  volume?: number;             // Default: 1.0 (0-1)
  muted?: boolean;             // Default: false
  audioTracks?: Record<string, AudioTrackState>;
  captions?: string | CaptionCue[];  // SRT string or cue array
  markers?: Marker[];          // Timeline markers
}
```

## Input props and schemas

Compositions can accept user-defined data through `inputProps`. This is useful for creating reusable templates.

### Basic input props

```typescript theme={null}
interface MyProps {
  title: string;
  author: string;
  bgColor: string;
}

const helios = new Helios<MyProps>({
  duration: 5,
  fps: 30,
  inputProps: {
    title: 'Hello World',
    author: 'Jane Doe',
    bgColor: '#ff6b6b'
  }
});

// Subscribe to changes
helios.subscribe(({ inputProps }) => {
  document.querySelector('h1').textContent = inputProps.title;
});

// Update props dynamically
helios.setInputProps({
  title: 'New Title',
  author: 'John Smith',
  bgColor: '#4ecdc4'
});
```

### Schema validation

Define a schema to validate and provide defaults for input props:

```typescript theme={null}
import { HeliosSchema } from '@helios-project/core';

const schema: HeliosSchema = {
  title: {
    type: 'string',
    default: 'Untitled',
    description: 'Video title'
  },
  author: {
    type: 'string',
    default: 'Anonymous'
  },
  bgColor: {
    type: 'string',
    default: '#ffffff',
    pattern: '^#[0-9a-fA-F]{6}$'  // Hex color validation
  },
  opacity: {
    type: 'number',
    default: 1.0,
    min: 0,
    max: 1
  }
};

const helios = new Helios({
  duration: 5,
  fps: 30,
  schema,
  inputProps: {
    title: 'My Video'
    // Other props get defaults from schema
  }
});
```

The schema is automatically validated on construction and when calling `setInputProps()`.

See `packages/core/src/schema.ts` for the full validation API.

## Timeline and clips

Helios supports multi-track timelines for complex compositions.

### Timeline structure

```typescript theme={null}
import { HeliosTimeline } from '@helios-project/core';

const timeline: HeliosTimeline = {
  tracks: [
    {
      id: 'main',
      name: 'Main Track',
      clips: [
        {
          id: 'intro',
          source: 'intro-scene',
          start: 0,      // Start at 0 seconds
          duration: 3    // 3 seconds long
        },
        {
          id: 'main',
          source: 'main-scene',
          start: 3,
          duration: 5
        }
      ]
    },
    {
      id: 'overlay',
      name: 'Graphics Overlay',
      clips: [
        {
          id: 'logo',
          source: 'logo-graphic',
          start: 1,
          duration: 6,
          props: { scale: 1.2 }  // Clip-specific data
        }
      ]
    }
  ]
};

const helios = new Helios({
  duration: 8,
  fps: 30,
  timeline
});

// Access active clips
helios.subscribe(({ activeClips, currentTime }) => {
  console.log('Active clips:', activeClips);
  // At time=2s: [intro-scene, logo-graphic]
});
```

See `packages/core/src/types.ts:31` for timeline type definitions.

### Active clip tracking

Helios automatically computes which clips are active at the current time:

```typescript theme={null}
helios.activeClips.value; // ReadonlySignal<HeliosClip[]>

// Subscribe to changes
helios.activeClips.subscribe(clips => {
  clips.forEach(clip => {
    console.log(`Clip ${clip.id} is active`);
  });
});
```

The computation happens at `Helios.ts:508`:

```typescript theme={null}
this._activeClips = computed(() => {
  const time = this._currentTime.value;
  const timeline = this._timeline.value;
  if (!timeline || !timeline.tracks) return [];

  const active: HeliosClip[] = [];
  for (const track of timeline.tracks) {
    for (const clip of track.clips) {
       const end = clip.start + clip.duration;
       if (time >= clip.start && time < end) {
         active.push(clip);
       }
    }
  }
  return active;
});
```

## Composition file structure

For server-side rendering, compositions are typically standalone HTML files:

```html theme={null}
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>My Composition</title>
  <style>
    body {
      margin: 0;
      width: 1920px;
      height: 1080px;
      background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
    }

    .title {
      position: absolute;
      top: 50%;
      left: 50%;
      transform: translate(-50%, -50%);
      color: white;
      font-size: 72px;
      animation: fadeIn 2s ease-out forwards;
    }

    @keyframes fadeIn {
      from { opacity: 0; transform: translate(-50%, -50%) scale(0.8); }
      to { opacity: 1; transform: translate(-50%, -50%) scale(1); }
    }
  </style>
</head>
<body>
  <h1 class="title">Hello Helios</h1>

  <script type="module">
    import { Helios } from '@helios-project/core';

    // Create global instance for renderer
    window.helios = new Helios({
      duration: 5,
      fps: 30,
      autoSyncAnimations: true
    });

    // Bind to document timeline (important for rendering)
    window.helios.bindToDocumentTimeline();
  </script>
</body>
</html>
```

<Note>
  The `bindToDocumentTimeline()` call is critical for server-side rendering. It tells Helios to read from `document.timeline.currentTime` (or `__HELIOS_VIRTUAL_TIME__` in headless mode) instead of driving its own playback loop.
</Note>

See `Helios.ts:1085` for the timeline binding implementation.

## Captions and markers

### Captions

Helios supports SRT and WebVTT caption formats:

```typescript theme={null}
const srtString = `
1
00:00:00,000 --> 00:00:02,000
Welcome to Helios

2
00:00:02,500 --> 00:00:05,000
Programmatic video creation
`;

const helios = new Helios({
  duration: 10,
  fps: 30,
  captions: srtString
});

// Access active captions
helios.activeCaptions.subscribe(cues => {
  cues.forEach(cue => {
    console.log(cue.text);
  });
});
```

Or pass parsed cue objects:

```typescript theme={null}
import { CaptionCue } from '@helios-project/core';

const helios = new Helios({
  duration: 10,
  fps: 30,
  captions: [
    {
      start: 0,
      end: 2000,
      text: 'Welcome to Helios'
    },
    {
      start: 2500,
      end: 5000,
      text: 'Programmatic video creation'
    }
  ]
});
```

See `packages/core/src/captions.ts` for parsing implementation.

### Markers

Markers are named points on the timeline:

```typescript theme={null}
import { Marker } from '@helios-project/core';

const helios = new Helios({
  duration: 10,
  fps: 30,
  markers: [
    { id: 'intro', time: 0, label: 'Introduction' },
    { id: 'main', time: 3, label: 'Main Content' },
    { id: 'outro', time: 8, label: 'Conclusion' }
  ]
});

// Seek to marker
helios.seekToMarker('main');

// Add/remove markers dynamically
helios.addMarker({ id: 'cta', time: 9.5, label: 'Call to Action' });
helios.removeMarker('intro');
```

## Playback range

Render or preview only a portion of the composition:

```typescript theme={null}
const helios = new Helios({
  duration: 10,
  fps: 30,
  playbackRange: [60, 180]  // Frames 60-180 (2-6 seconds at 30fps)
});

// Or set dynamically
helios.setPlaybackRange(90, 150);
helios.clearPlaybackRange();
```

When a playback range is active:

* `play()` starts at the range start
* Playback stops at the range end
* Looping wraps within the range

## Composition patterns

### React composition

```typescript theme={null}
import React from 'react';
import { createRoot } from 'react-dom/client';
import { Helios } from '@helios-project/core';
import { HeliosProvider, useVideoFrame } from '@helios-project/react';

function MyComposition() {
  const { currentFrame, currentTime } = useVideoFrame();
  const opacity = Math.min(1, currentTime / 2);

  return (
    <div style={{ opacity }}>
      <h1>Frame {currentFrame}</h1>
    </div>
  );
}

// Global setup
const helios = new Helios({ duration: 5, fps: 30 });
window.helios = helios;

const root = createRoot(document.getElementById('root'));
root.render(
  <HeliosProvider helios={helios}>
    <MyComposition />
  </HeliosProvider>
);

helios.bindToDocumentTimeline();
```

### Canvas composition

```typescript theme={null}
import { Helios } from '@helios-project/core';

const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');

const helios = new Helios({
  duration: 5,
  fps: 30,
  width: 1920,
  height: 1080
});

helios.subscribe(({ currentTime, width, height }) => {
  // Clear canvas
  ctx.clearRect(0, 0, width, height);

  // Draw based on time
  const x = (currentTime / 5) * width;
  ctx.fillStyle = '#ff6b6b';
  ctx.fillRect(x - 50, height / 2 - 50, 100, 100);
});

window.helios = helios;
helios.bindToDocumentTimeline();
```

### Multi-scene composition

```typescript theme={null}
const scenes = {
  intro: document.querySelector('#intro'),
  main: document.querySelector('#main'),
  outro: document.querySelector('#outro')
};

const helios = new Helios({
  duration: 15,
  fps: 30,
  timeline: {
    tracks: [
      {
        id: 'scenes',
        clips: [
          { id: 'intro-clip', source: 'intro', start: 0, duration: 3 },
          { id: 'main-clip', source: 'main', start: 3, duration: 9 },
          { id: 'outro-clip', source: 'outro', start: 12, duration: 3 }
        ]
      }
    ]
  }
});

helios.subscribe(({ activeClips }) => {
  // Hide all scenes
  Object.values(scenes).forEach(el => el.style.display = 'none');

  // Show active scenes
  activeClips.forEach(clip => {
    scenes[clip.source].style.display = 'block';
  });
});
```

## Best practices

### Use absolute positioning

```css theme={null}
/* Good: Absolute positioning for predictable layout */
body {
  margin: 0;
  width: 1920px;
  height: 1080px;
  position: relative;
}

.element {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}
```

### Preload assets

```html theme={null}
<!-- Preload critical assets -->
<link rel="preload" href="./logo.png" as="image">
<link rel="preload" href="./audio.mp3" as="audio">
```

### Use CSS animations for simple motion

```css theme={null}
/* Prefer CSS over JavaScript when possible */
@keyframes slideIn {
  from { transform: translateX(-100%); }
  to { transform: translateX(0); }
}

.element {
  animation: slideIn 1s ease-out forwards;
}
```

### Bind to document timeline for rendering

```typescript theme={null}
// Always call this in compositions meant for rendering
window.helios = new Helios({ /* config */ });
window.helios.bindToDocumentTimeline();
```

<Info>
  Without `bindToDocumentTimeline()`, the renderer won't be able to control the composition's timeline via CDP virtual time.
</Info>

## Next steps

* Explore [timeline control](/concepts/timeline-control) for playback APIs
* Learn about [animations](/concepts/animations) for motion control
* Understand [drivers](/concepts/drivers) for custom time sources
