pixi-effects 0.7.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +21 -0
- package/README.md +8 -7
- package/ai/SKILL.md +6 -5
- package/ai/reference/cheatsheet.md +9 -1
- package/ai/template.html +4 -3
- package/ai/tools/check.mjs +8 -0
- package/dist/Composition-CM62XJKT.cjs +14 -0
- package/dist/{Composition-3KHQPRBA.cjs.map → Composition-CM62XJKT.cjs.map} +1 -1
- package/dist/Composition-RJDSQYTV.js +5 -0
- package/dist/{Composition-DT657FO5.js.map → Composition-RJDSQYTV.js.map} +1 -1
- package/dist/Controller.cjs +55 -20
- package/dist/Controller.cjs.map +1 -1
- package/dist/Controller.d.cts +31 -2
- package/dist/Controller.d.ts +31 -2
- package/dist/Controller.js +55 -21
- package/dist/Controller.js.map +1 -1
- package/dist/{Movie-B0OUKlZI.d.ts → Movie-QMlFyHkf.d.ts} +49 -1
- package/dist/{Movie-BK9daiTt.d.cts → Movie-qGHp15c_.d.cts} +49 -1
- package/dist/{chunk-6I55T4OH.cjs → chunk-AGMNSJJH.cjs} +13 -4
- package/dist/chunk-AGMNSJJH.cjs.map +1 -0
- package/dist/{chunk-4O2NYDJG.js → chunk-FNKSXBKB.js} +13 -4
- package/dist/chunk-FNKSXBKB.js.map +1 -0
- package/dist/{chunk-36Z4RIRK.js → chunk-O4AJYUPK.js} +3 -3
- package/dist/{chunk-36Z4RIRK.js.map → chunk-O4AJYUPK.js.map} +1 -1
- package/dist/{chunk-4VSIJLU3.cjs → chunk-PDBDDGG5.cjs} +21 -21
- package/dist/{chunk-4VSIJLU3.cjs.map → chunk-PDBDDGG5.cjs.map} +1 -1
- package/dist/index.cjs +141 -26
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +130 -15
- package/dist/index.js.map +1 -1
- package/dist/three.cjs +5 -5
- package/dist/three.js +1 -1
- package/docs/api.md +67 -0
- package/docs/dsl.md +14 -2
- package/llms-full.txt +96 -8
- package/package.json +4 -1
- package/dist/Composition-3KHQPRBA.cjs +0 -14
- package/dist/Composition-DT657FO5.js +0 -5
- package/dist/chunk-4O2NYDJG.js.map +0 -1
- package/dist/chunk-6I55T4OH.cjs.map +0 -1
package/dist/three.cjs
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var chunkAGMNSJJH_cjs = require('./chunk-AGMNSJJH.cjs');
|
|
4
4
|
require('./chunk-UQBGW5DO.cjs');
|
|
5
5
|
var pixi_js = require('pixi.js');
|
|
6
6
|
var three$1 = require('three');
|
|
7
7
|
|
|
8
|
-
var ThreeSequence = class extends
|
|
8
|
+
var ThreeSequence = class extends chunkAGMNSJJH_cjs.Sequence {
|
|
9
9
|
_renderer = null;
|
|
10
10
|
_scene = null;
|
|
11
11
|
_camera = null;
|
|
@@ -24,7 +24,7 @@ var ThreeSequence = class extends chunk6I55T4OH_cjs.Sequence {
|
|
|
24
24
|
if (this.duration === void 0) {
|
|
25
25
|
this.duration = this.parent?.duration ?? this.root.duration;
|
|
26
26
|
}
|
|
27
|
-
const scope =
|
|
27
|
+
const scope = chunkAGMNSJJH_cjs.buildScope(this, this.parent, this.root);
|
|
28
28
|
const width = resolveDim(spec.width, scope) ?? this.parent?.width ?? this.root.width;
|
|
29
29
|
const height = resolveDim(spec.height, scope) ?? this.parent?.height ?? this.root.height;
|
|
30
30
|
const resolution = spec.resolution ?? 1;
|
|
@@ -144,12 +144,12 @@ var ThreeSequence = class extends chunk6I55T4OH_cjs.Sequence {
|
|
|
144
144
|
function resolveDim(v, scope) {
|
|
145
145
|
if (v === void 0) return void 0;
|
|
146
146
|
if (typeof v === "number") return v;
|
|
147
|
-
return
|
|
147
|
+
return chunkAGMNSJJH_cjs.evaluateExpr(v, scope);
|
|
148
148
|
}
|
|
149
149
|
|
|
150
150
|
// src/three/index.ts
|
|
151
151
|
function registerThree() {
|
|
152
|
-
|
|
152
|
+
chunkAGMNSJJH_cjs.registerSequenceType("three", ThreeSequence);
|
|
153
153
|
}
|
|
154
154
|
function three(spec) {
|
|
155
155
|
return spec;
|
package/dist/three.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Sequence, buildScope, evaluateExpr, registerSequenceType } from './chunk-
|
|
1
|
+
import { Sequence, buildScope, evaluateExpr, registerSequenceType } from './chunk-FNKSXBKB.js';
|
|
2
2
|
import './chunk-DKGG7WD4.js';
|
|
3
3
|
import { Texture, Sprite } from 'pixi.js';
|
|
4
4
|
import { WebGLRenderer, Scene, PerspectiveCamera } from 'three';
|
package/docs/api.md
CHANGED
|
@@ -163,10 +163,28 @@ interface RenderOptions {
|
|
|
163
163
|
}
|
|
164
164
|
```
|
|
165
165
|
|
|
166
|
+
**Audio codec fallback:** unless you name `audio.codec`, the first codec this browser can actually encode is used (mp4: `aac`, then `opus`, `mp3`, `flac`; webm: `opus`, `vorbis`). Chrome on Linux has no AAC encoder, so there an mp4 with sound carries Opus and a warning says so (before, the export failed with a message about encoder configurations). A codec you name is never swapped: if it cannot be encoded the error says which one would work.
|
|
167
|
+
|
|
166
168
|
Returns a `Blob` whose `type` is the container's MIME (e.g. `video/mp4`). Emits `'progress'` repeatedly during the render.
|
|
167
169
|
|
|
168
170
|
The renderer also forces a keyframe every ~2 seconds so the resulting file scrubs efficiently in standard players.
|
|
169
171
|
|
|
172
|
+
### Poster time: `movie.init({ poster })`, `movie.poster`, `movie.posterImage()`
|
|
173
|
+
|
|
174
|
+
A movie can say which moment stands for it before it plays, the way `<video poster>` names a picture, except that here it is a **time**, so the picture comes from the same data and needs no separate image file:
|
|
175
|
+
|
|
176
|
+
```js
|
|
177
|
+
await movie.init({ /* … */ poster: 9.5 }); // seconds; a negative value counts back from the end (-2 = two seconds before the end)
|
|
178
|
+
movie.poster; // 9.5, or null
|
|
179
|
+
movie.posterFrame; // 285 at 30 fps, or null
|
|
180
|
+
const blob = await movie.posterImage(); // the picture at that time (frame 0 if there is no poster); { as: 'dataURL', type: 'image/jpeg', scale: 0.5 } like snapshot()
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
- Once the movie is ready the **canvas shows the poster frame**, while the playhead stays at frame 0 (`currentFrame` is 0, the player shows 0:00). The first `play()` or `gotoFrame()` replaces it, and **play starts from 0**.
|
|
184
|
+
- `posterImage()` leaves the movie as it was: still showing the poster, or back at the frame it was at. `contactSheet()` does the same.
|
|
185
|
+
- A time past the end is clamped to the last frame, and something that is not a number is ignored; both warn.
|
|
186
|
+
- `pixi-effects-check` writes the poster as `poster.jpg`.
|
|
187
|
+
|
|
170
188
|
### `movie.timelineData(): TimelineData` and `movie.timelineChart(options?): string`
|
|
171
189
|
|
|
172
190
|
The whole movie as rows on a time axis, for a person to read at a glance. `timelineData()` is the data: `{ duration, rows, transitions }`, a row per layer with `name`, `type`, `depth` (1 inside a composition), absolute `start` / `end` in seconds, `keys` (when each keyframe starts) and, for a family of generated layers, `parts` (one span per layer). Layers of one type whose names differ only by numbers (`ring10-0`, `ring9-1`, `pop-3` …), four or more, become ONE row (`ring#-# ×19`), so a movie with 700 generated layers is a screenful. `timelineChart({ title })` returns one self-contained HTML page (an inline SVG, no scripts): bars coloured by type, ◆ at each keyframe start, a shaded band per transition, a tooltip per bar. `pixi-effects-check` writes it next to the contact sheet as `timeline.html`.
|
|
@@ -191,6 +209,12 @@ movie.off(event, fn): this
|
|
|
191
209
|
| `'play'` | none | when `play()` actually transitions from paused |
|
|
192
210
|
| `'pause'` | none | when `pause()` actually transitions from playing |
|
|
193
211
|
| `'progress'`| `{ progress: number; frame: number; totalFrames: number }` | during `render()`, once per encoded frame |
|
|
212
|
+
| `'seeking'` / `'seeked'` | `{ frame, totalFrames }` (the frame it lands on) | a `gotoFrame()` to another frame starts / has finished. NOT emitted for playback ticks, `render()`, `snapshot()` or `contactSheet()` |
|
|
213
|
+
| `'ended'` | none | playback ran off the end (after `'pause'`); pausing by hand does not emit it |
|
|
214
|
+
| `'volumechange'` | `{ volume: number; muted: boolean }` | `volume` or `muted` really changed (clamping counts) |
|
|
215
|
+
| `'error'` | `{ where: 'init' \| 'render' \| 'playback'; message: string; error: unknown }` | `init()` / `render()` failed (the call still rejects), or a frame failed during playback |
|
|
216
|
+
|
|
217
|
+
The names are the ones an HTML5 `<video>` uses, so a player written for `<video>` ports over.
|
|
194
218
|
|
|
195
219
|
`progress` is `0..100` (rounded integer percent).
|
|
196
220
|
|
|
@@ -216,6 +240,24 @@ Flips `muted` and returns the new value.
|
|
|
216
240
|
|
|
217
241
|
---
|
|
218
242
|
|
|
243
|
+
## Building your own player
|
|
244
|
+
|
|
245
|
+
`Controller` is one optional player; everything it does is public on the movie, so a player of your own is plain HTML + CSS + a few handlers (`examples/15-custom-player.html` is a complete one):
|
|
246
|
+
|
|
247
|
+
```js
|
|
248
|
+
const el = id => document.getElementById(id);
|
|
249
|
+
movie.on('ready', () => { el('seek').max = movie.totalFrames; });
|
|
250
|
+
movie.on('frame', ({ frame }) => { el('seek').value = frame; el('time').textContent = (frame / movie.frameRate).toFixed(1) + ' s'; });
|
|
251
|
+
movie.on('play', () => { el('play').textContent = '❚❚'; });
|
|
252
|
+
movie.on('pause', () => { el('play').textContent = '▶'; });
|
|
253
|
+
movie.on('ended', () => { el('play').textContent = '↻'; }); // play() at the end starts again from 0
|
|
254
|
+
movie.on('volumechange', ({ volume, muted }) => { el('vol').value = muted ? 0 : volume; });
|
|
255
|
+
el('play').onclick = () => (movie.isPlaying ? movie.pause() : movie.play());
|
|
256
|
+
el('seek').oninput = e => movie.gotoFrame(+e.target.value);
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
What you have: `play()`, `pause()`, `gotoFrame(frame)`, `volume`, `muted` / `toggleMute()`; the state `currentFrame`, `totalFrames`, `frameRate`, `duration`, `isPlaying`, `isReady`; the events above; `render()` for a download button; `snapshot()` for a thumbnail; `timelineData()` / `timelineSvg()` for a timeline like `pixi-effects-view`'s. Keep the movie's `canvas` wherever your layout wants it (the page's CSS sizes it). To only change how `Controller` looks, see **Theme** below.
|
|
260
|
+
|
|
219
261
|
## `Controller`
|
|
220
262
|
|
|
221
263
|
Imported from `pixi-effects/controller`.
|
|
@@ -247,6 +289,7 @@ interface ControllerOptions {
|
|
|
247
289
|
showExportButton?: boolean; // default true; hides ⬇ + popover
|
|
248
290
|
enableKeyboardShortcuts?: boolean; // default true
|
|
249
291
|
className?: string; // default 'movie-controller'
|
|
292
|
+
theme?: ControllerTheme; // colours / thickness / font of the bar, see Theme
|
|
250
293
|
}
|
|
251
294
|
```
|
|
252
295
|
|
|
@@ -255,6 +298,30 @@ Mounting strategy:
|
|
|
255
298
|
- If `canvas.parentElement` already has a non-static `position`, the controller is appended directly into it.
|
|
256
299
|
- Otherwise the canvas is wrapped in a `<div class="movie-controller-wrap">` (with `position: relative`). The wrapper is removed on `destroy()`.
|
|
257
300
|
|
|
301
|
+
### Theme: change the colours of the bar
|
|
302
|
+
|
|
303
|
+
The bar is blue by default. Every colour and the track thickness are CSS custom properties with the old values as defaults, so there are two ways to change them:
|
|
304
|
+
|
|
305
|
+
```js
|
|
306
|
+
new Controller(movie, { canvas, theme: { accent: '#ff4d6d', foreground: '#ffe9a8', trackHeight: 5 } }); // in code
|
|
307
|
+
controller.setTheme({ accent: '#7bd88f' }); // later, live; only the keys you give change
|
|
308
|
+
controller.setTheme({ accent: null }); // null = back to the default
|
|
309
|
+
```
|
|
310
|
+
```css
|
|
311
|
+
:root { --mc-accent: #ff4d6d; } /* or in your page CSS: the canvas's parent, or :root */
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
| `theme` key | CSS property | What | Default |
|
|
315
|
+
|---|---|---|---|
|
|
316
|
+
| `accent` | `--mc-accent` | progress bar, its thumb, focus rings, the export button | `#007AFF` |
|
|
317
|
+
| `foreground` | `--mc-fg` | icons, the time, the volume slider | `#fff` |
|
|
318
|
+
| `track` | `--mc-track` | the unfilled part of the progress / volume bars | `rgba(255,255,255,0.25)` |
|
|
319
|
+
| `barBackground` | `--mc-bar-bg` | the background behind the buttons (any CSS background, e.g. a gradient) | black fading to transparent upward |
|
|
320
|
+
| `trackHeight` | `--mc-track-height` | thickness of the progress bar (a number is px) | `3px` |
|
|
321
|
+
| `font` | `--mc-font` | `font-family` of the bar | system UI font |
|
|
322
|
+
|
|
323
|
+
A misspelt key warns with the valid ones. The default stylesheet is exported as `CONTROLLER_CSS` (from `pixi-effects/controller`) if you want to copy and restyle more.
|
|
324
|
+
|
|
258
325
|
### `controller.destroy(): void`
|
|
259
326
|
|
|
260
327
|
Idempotent. Removes:
|
package/docs/dsl.md
CHANGED
|
@@ -944,7 +944,7 @@ A filter by name, as plain data. `type` is the filter's class name without `Filt
|
|
|
944
944
|
keyframes: [{ at: 0, to: { 'filters.halo.outerStrength': 6 }, duration: 1 }] } // animate any scalar option by name
|
|
945
945
|
```
|
|
946
946
|
|
|
947
|
-
Name a filter (`name: 'halo'`) to animate it with `'filters.halo.<option>'`. `blur`, `noise`, `alpha` and `colorMatrix` come from pixi.js. All the others come from the **`pixi-filters`** package, which is loaded the first time a layer uses one (add `"pixi-filters": "https://esm.sh/pixi-filters@6.1.5?external=pixi.js"` to the import map, or `npm i pixi-filters`; `ai/template.html` already has it). A misspelt type says what you probably meant.
|
|
947
|
+
Name a filter (`name: 'halo'`) to animate it with `'filters.halo.<option>'`. `blur`, `noise`, `alpha` and `colorMatrix` come from pixi.js. All the others come from the **`pixi-filters`** package, which is loaded the first time a layer uses one (add `"pixi-filters": "https://esm.sh/pixi-filters@6.1.5?external=pixi.js"` to the import map, or `npm i pixi-filters`; `ai/template.html` already has it). A misspelt type says what you probably meant. Rendering and exporting were verified in a real browser for 40 of them (`examples/_checks/named-filters.html` and `named-filters-2.html`); `colorMap` and `simpleLightmap` need a texture, which JSON cannot hold, so use `custom` for those two:
|
|
948
948
|
|
|
949
949
|
| `type` | Options (the useful ones) | What it does |
|
|
950
950
|
|---|---|---|
|
|
@@ -972,13 +972,25 @@ Name a filter (`name: 'halo'`) to animate it with `'filters.halo.<option>'`. `bl
|
|
|
972
972
|
| `ascii` | `size color replaceColor` | Text-art |
|
|
973
973
|
| `colorOverlay` | `color alpha` | Tint over everything |
|
|
974
974
|
| `advancedBloom` / `bloom` | `threshold bloomScale brightness blur` | Bloom |
|
|
975
|
-
| `bevel
|
|
975
|
+
| `bevel` | `thickness rotation lightColor lightAlpha shadowColor shadowAlpha` | Raised edge |
|
|
976
|
+
| `dot` / `crossHatch` | `scale angle grayscale` / – | Halftone / hatching |
|
|
977
|
+
| `godray` | `angle gain lacunarity alpha parallel time` | Light shafts (animate `time`) |
|
|
978
|
+
| `reflection` | `mirror boundary amplitude waveLength alpha time` (ranges are `[start, end]`) | Water reflection |
|
|
979
|
+
| `simplexNoise` | `strength scale offset` | Cloudy noise over the layer |
|
|
980
|
+
| `colorGradient` | `gradientType: 'linear' 'radial' 'conic'` (the filter's own option is `type`, which the DSL uses), `stops: [{ offset, color, alpha }]`, `angle` | A gradient laid over the layer |
|
|
981
|
+
| `colorReplace` / `multiColorReplace` | `originalColor targetColor tolerance` / `replacements: [[from, to], …] tolerance` | Swap colours |
|
|
982
|
+
| `convolution` | `matrix` (9 numbers), `width height` | Sharpen, edge detect |
|
|
983
|
+
| `tiltShift` / `tiltShiftAxis` | `blur gradientBlur start end` (canvas pixels) | Miniature blur |
|
|
984
|
+
| `shockwave` | `center amplitude wavelength brightness speed radius time` (animate `time` 0 → 1) | A ripple from a point |
|
|
985
|
+
| `backdropBlur` | `strength` | Blurs what is BEHIND the layer, so alone it shows nothing |
|
|
986
|
+
| the rest of pixi-filters (`colorMap`, `simpleLightmap`) | need a texture: `{ type: 'custom' }` | see the [pixi-filters docs](https://pixijs.io/filters/docs/) |
|
|
976
987
|
|
|
977
988
|
Gotchas, all measured:
|
|
978
989
|
|
|
979
990
|
- **Centres and offsets are in canvas pixels, not the layer's:** `twist.offset`, `bulgePinch.center`, `zoomBlur.center`, `radialBlur.center`, `shockwave.center` default to the canvas's top-left corner, so a layer in the middle looks unaffected. Put the centre on the layer: `{ type: 'twist', offset: { x: 640, y: 360 } }` for a layer centred on a 1280×720 canvas.
|
|
980
991
|
- **A filter works inside the layer's bounds.** Glow, dropShadow, outline and blur draw outside it and are clipped unless you widen the area (`filterArea: { x: -24, y: -24, width: w + 48, height: h + 48 }`, in the layer's own coordinates). Do not widen it for `grayscale`, `oldFilm` or other filters that paint the whole area: they fill the extra margin with black.
|
|
981
992
|
- **Animate scalars by name** (`'filters.halo.outerStrength'`). Options that are points (`rgbSplit.red`, `dropShadow.offset`) do not tween as a whole; animate `'filters.<name>.red.x'` if the filter exposes it, or swap the filter. `pixelate` animates through `sizeX` / `sizeY`, not `size`.
|
|
993
|
+
- `shockwave` and `godray` are driven by `time` (a still frame at `time: 0` looks like nothing): animate it. `radialBlur`, `shockwave` and `tiltShift` positions are canvas pixels too.
|
|
982
994
|
- Some filters take their value as one argument; the DSL hides that (`{ type: 'pixelate', size: 8 }`, `{ type: 'emboss', strength: 6 }`).
|
|
983
995
|
|
|
984
996
|
### `custom`
|
package/llms-full.txt
CHANGED
|
@@ -13,16 +13,17 @@ You describe a video as a tree of plain objects; the library plays it in a canva
|
|
|
13
13
|
## Workflow
|
|
14
14
|
|
|
15
15
|
1. **Start from `template.html`** (copy it; it loads the library from a CDN, collects every console warning into `window.__logs`, and exposes `window.movie`). One HTML file, plain JS. Default canvas 1280×720 @ 30 fps.
|
|
16
|
-
2. **
|
|
17
|
-
3. **
|
|
16
|
+
2. **Give the movie a poster time.** In `movie.init` add `poster: <seconds>`: the most striking moment (a title fully on screen, not the first frame, which is often black). The canvas shows that picture before play (the playhead stays at 0 and play starts from 0), and the check writes it as `poster.jpg`.
|
|
17
|
+
3. **Write the composition as data.** Anything repeated (letters, bars, particles, scenes) is a JS function or loop that returns specs — never copy-paste objects. Derive coordinates and times from constants/data, not magic numbers.
|
|
18
|
+
4. **Show the human:** `node ai/tools/view.mjs my-video.html` opens their browser on the page with a scrubbable, zoomable timeline under it (click / drag to seek, + / − or Ctrl/⌘ + wheel to zoom) (`npx pixi-effects-view …` in an installed package); it keeps running until Ctrl-C. **Deliver the file:** `node ai/tools/render.mjs my-video.html -o my-video.mp4` (`npx pixi-effects-render …` in an installed package) renders the page headless to mp4 / webm / mov / mkv and exits 0 only when the file is written; run it AFTER the check below passes. **Run the check — one command does steps 3, 4 and the export:** `node ai/tools/check.mjs my-video.html` (Node ≥ 22 and Chrome installed; no npm install; in an installed package it is `npx pixi-effects-check my-video.html`). It starts its own private Chrome (nothing is shared with other sessions), waits for `window.__ready`, and writes `timeline.html` (every layer as a bar on a time axis: ◆ keyframes, shaded transitions; hand it to the human to open) and prints: console warnings, `movie.inspect` over the WHOLE timeline (grouped), `movie.inspectAudio()`, then exports an mp4 and decodes it again (video size and length, audio length, loudness per second, silence). It writes `check-out/<name>/sheet.png` — **open that picture and look at it** — plus the mp4 and `report.json`. Exit 0 = nothing to fix; exit 1 = a list of problems; fix them and run it again. Text that overlaps text is listed under `review` (not a failure: ghost layers, glow copies and per-letter boxes are often intentional; look at the sheet; `--strict` makes them fail). The manual steps below are for looking at one frame closely.
|
|
18
19
|
**Or by hand: run it and read the logs first.** `window.__ready` must be `true` and `window.__logs` should be `[]`. A pixi-effects warning is an instruction: fix what it names.
|
|
19
|
-
|
|
20
|
+
5. **Look at it** — you cannot judge motion from code. The library has the tools built in:
|
|
20
21
|
- `await movie.contactSheet({ count: 6, as: 'dataURL' })` → ONE image of several labelled frames (pass `frames: [...]` / `times: [...]` to choose; include the middle of every transition and the last second). Save it and view it.
|
|
21
22
|
- `await movie.inspect(frame)` → every layer's canvas bounds and visibility, plus `issues` (text off the canvas, cut off by an edge, empty, or overlapping other text). Run it at several frames; fix every issue it names.
|
|
22
23
|
- `await movie.snapshot(frame, { as: 'dataURL' })` → one frame, canvas only (no player bar).
|
|
23
24
|
- `movie.inspectAudio()` → **hear with numbers**: for every sound its `start`, `sound.length`, `sound.peakDb`, `sound.loudestAt` (line this up with the picture) and `sound.brightnessHz` (< 500 dark, > 8000 airy), plus `issues` (limited mix, inaudible or cut-off sounds). Check it whenever the movie has audio; `render()` encodes this same mix.
|
|
24
25
|
From a browser tool the result is a `data:` URL string: `agent-browser eval "movie.contactSheet({ count: 6, as: 'dataURL' })" | python3 ai/tools/save-image.py /absolute/sheet.png`, then open the PNG. Sheet tiles are small (~480 px): use `snapshot` for detail. `inspect` output is long on busy scenes: pass `{ layers: 'none' }` and read `issues` first. Give layers a `name` so inspect paths are readable. A moving ticker is not flagged as cut off.
|
|
25
|
-
|
|
26
|
+
6. **Iterate** on what you saw (clipped text, collisions, wrong timing). Then export: `const blob = await movie.render({ format: 'mp4' })` (about real time; `movie.on('progress', …)` reports 0–100).
|
|
26
27
|
|
|
27
28
|
## The rules that cause most failures
|
|
28
29
|
|
|
@@ -46,7 +47,7 @@ Centred text must be positioned by `anchorX/anchorY: 0.5`; estimate text width a
|
|
|
46
47
|
- `reference/cheatsheet.md` — every layer type, prop, default and rule on one page.
|
|
47
48
|
- `reference/recipes.md` — tested building blocks: slam type, lower-third, marquee, count-up, bar chart, 2.5D title, camera orbit, slideshow with transitions + music, looping, particles, gradients/vignette, three.js metal.
|
|
48
49
|
- `reference/pitfalls.md` — the full list of real mistakes, with status.
|
|
49
|
-
- `tools/check.mjs` — the one-command review (see Workflow step
|
|
50
|
+
- `tools/check.mjs` — the one-command review (see Workflow step 5).
|
|
50
51
|
- `template.html` — the starting file. (It loads the library from a CDN; when testing against a local build, point the three `pixi-effects*` importmap entries at `../../dist/…`.)
|
|
51
52
|
- `tools/save-image.py` — save a `dataURL` result (contact sheet / snapshot) to a PNG.
|
|
52
53
|
- Full reference: `docs/dsl.md` and `docs/api.md` in the repository.
|
|
@@ -139,9 +140,17 @@ Style (`initial` / keyframes): `fillColor fillAlpha strokeColor strokeAlpha stro
|
|
|
139
140
|
|
|
140
141
|
Add `threeD: true` to any visual layer, then use `z`, `rotationX`, `rotationY` (centre rotation with `anchorX/Y: 0.5` or `pivotX/Y`). Add a `{ type: 'camera' }` layer for a view. A `threeD` layer at `z: 0` with the default camera looks identical to a 2D one. Consecutive `threeD` layers are drawn farthest-first (equal `z` keeps array order); non-`threeD` layers ignore the camera and keep array order. Layers at/behind the camera plane are hidden. Not supported on `threeD` layers: masks, transitions, `filterArea`.
|
|
141
142
|
|
|
143
|
+
## Poster
|
|
144
|
+
|
|
145
|
+
`movie.init({ …, poster: 9.5 })` names the moment (seconds; negative = from the end) that stands for the movie: the canvas shows it before play (the playhead stays at 0, play starts from 0), `movie.poster` / `movie.posterFrame` read it, `await movie.posterImage({ as: 'dataURL', type: 'image/jpeg', scale: 0.5 })` is the picture. Pick the most striking moment, not frame 0 (which is often black).
|
|
146
|
+
|
|
147
|
+
## Player
|
|
148
|
+
|
|
149
|
+
`new Controller(movie, { canvas, theme: { accent: '#ff4d6d' } })` (import from `pixi-effects/controller`; theme keys `accent foreground track barBackground trackHeight font`, or CSS `--mc-accent` …; `controller.setTheme(…)`). Your own player: `movie.play() pause() gotoFrame(f) volume muted toggleMute()`, state `currentFrame totalFrames frameRate duration isPlaying`, events `ready frame play pause ended seeking seeked volumechange error progress` (`<video>` names). `examples/15-custom-player.html` is a complete one.
|
|
150
|
+
|
|
142
151
|
## Filters by name (no import)
|
|
143
152
|
|
|
144
|
-
`filters: [{ type: 'glow', name: 'halo', outerStrength: 3, color: '#ffd166' }]` — `type` = the pixi / pixi-filters class name without `Filter`, camelCase (`glow dropShadow outline blur noise alpha adjustment hslAdjustment grayscale pixelate crt rgbSplit oldFilm glitch bulgePinch twist zoomBlur kawaseBlur radialBlur motionBlur emboss ascii colorOverlay advancedBloom bevel dot crossHatch tiltShift …`), the other keys are its options. `colorMatrix` takes `preset: 'sepia'|'grayscale'|'negative'|'polaroid'|'technicolor'|'vintage'|'kodachrome'|'browni'`. Name it to animate: `'filters.halo.outerStrength'` (scalars only; `pixelate` via `sizeX`/`sizeY`). Options: `glow` distance outerStrength innerStrength color alpha · `dropShadow` offset{x,y} blur alpha color · `outline` thickness color · `blur` strength · `adjustment` gamma contrast saturation brightness · `hslAdjustment` hue lightness saturation · `pixelate` size · `crt` curvature lineWidth noise vignetting · `rgbSplit` red/green/blue {x,y} · `oldFilm` sepia noise scratch vignetting · `glitch` slices offset · `twist` radius angle offset · `bulgePinch` center radius strength · `zoomBlur` strength center innerRadius · `colorOverlay` color alpha. **Centres/offsets are CANVAS pixels** (default top-left corner): put them on the layer. Glow / shadow / blur draw outside the layer: widen `filterArea` (not for `grayscale` / `oldFilm`, which fill the margin black). Full table: `docs/dsl.md` → Named filters.
|
|
153
|
+
`filters: [{ type: 'glow', name: 'halo', outerStrength: 3, color: '#ffd166' }]` — `type` = the pixi / pixi-filters class name without `Filter`, camelCase (`glow dropShadow outline blur noise alpha adjustment hslAdjustment grayscale pixelate crt rgbSplit oldFilm glitch bulgePinch twist zoomBlur kawaseBlur radialBlur motionBlur emboss ascii colorOverlay advancedBloom bevel dot crossHatch tiltShift …`), the other keys are its options. `colorMatrix` takes `preset: 'sepia'|'grayscale'|'negative'|'polaroid'|'technicolor'|'vintage'|'kodachrome'|'browni'`. Name it to animate: `'filters.halo.outerStrength'` (scalars only; `pixelate` via `sizeX`/`sizeY`). Options: `glow` distance outerStrength innerStrength color alpha · `dropShadow` offset{x,y} blur alpha color · `outline` thickness color · `blur` strength · `adjustment` gamma contrast saturation brightness · `hslAdjustment` hue lightness saturation · `pixelate` size · `crt` curvature lineWidth noise vignetting · `rgbSplit` red/green/blue {x,y} · `oldFilm` sepia noise scratch vignetting · `glitch` slices offset · `twist` radius angle offset · `bulgePinch` center radius strength · `zoomBlur` strength center innerRadius · `colorOverlay` color alpha · `colorGradient` gradientType('linear'|'radial'|'conic') stops[{offset,color,alpha}] angle · `colorReplace` originalColor targetColor tolerance · `convolution` matrix(9) · `shockwave` / `godray` are driven by `time` (animate it) · `colorMap` / `simpleLightmap` need a texture (use `custom`). **Centres/offsets are CANVAS pixels** (default top-left corner): put them on the layer. Glow / shadow / blur draw outside the layer: widen `filterArea` (not for `grayscale` / `oldFilm`, which fill the margin black). Full table: `docs/dsl.md` → Named filters.
|
|
145
154
|
|
|
146
155
|
## Transitions (on the parent composition)
|
|
147
156
|
|
|
@@ -1757,7 +1766,7 @@ A filter by name, as plain data. `type` is the filter's class name without `Filt
|
|
|
1757
1766
|
keyframes: [{ at: 0, to: { 'filters.halo.outerStrength': 6 }, duration: 1 }] } // animate any scalar option by name
|
|
1758
1767
|
```
|
|
1759
1768
|
|
|
1760
|
-
Name a filter (`name: 'halo'`) to animate it with `'filters.halo.<option>'`. `blur`, `noise`, `alpha` and `colorMatrix` come from pixi.js. All the others come from the **`pixi-filters`** package, which is loaded the first time a layer uses one (add `"pixi-filters": "https://esm.sh/pixi-filters@6.1.5?external=pixi.js"` to the import map, or `npm i pixi-filters`; `ai/template.html` already has it). A misspelt type says what you probably meant.
|
|
1769
|
+
Name a filter (`name: 'halo'`) to animate it with `'filters.halo.<option>'`. `blur`, `noise`, `alpha` and `colorMatrix` come from pixi.js. All the others come from the **`pixi-filters`** package, which is loaded the first time a layer uses one (add `"pixi-filters": "https://esm.sh/pixi-filters@6.1.5?external=pixi.js"` to the import map, or `npm i pixi-filters`; `ai/template.html` already has it). A misspelt type says what you probably meant. Rendering and exporting were verified in a real browser for 40 of them (`examples/_checks/named-filters.html` and `named-filters-2.html`); `colorMap` and `simpleLightmap` need a texture, which JSON cannot hold, so use `custom` for those two:
|
|
1761
1770
|
|
|
1762
1771
|
| `type` | Options (the useful ones) | What it does |
|
|
1763
1772
|
|---|---|---|
|
|
@@ -1785,13 +1794,25 @@ Name a filter (`name: 'halo'`) to animate it with `'filters.halo.<option>'`. `bl
|
|
|
1785
1794
|
| `ascii` | `size color replaceColor` | Text-art |
|
|
1786
1795
|
| `colorOverlay` | `color alpha` | Tint over everything |
|
|
1787
1796
|
| `advancedBloom` / `bloom` | `threshold bloomScale brightness blur` | Bloom |
|
|
1788
|
-
| `bevel
|
|
1797
|
+
| `bevel` | `thickness rotation lightColor lightAlpha shadowColor shadowAlpha` | Raised edge |
|
|
1798
|
+
| `dot` / `crossHatch` | `scale angle grayscale` / – | Halftone / hatching |
|
|
1799
|
+
| `godray` | `angle gain lacunarity alpha parallel time` | Light shafts (animate `time`) |
|
|
1800
|
+
| `reflection` | `mirror boundary amplitude waveLength alpha time` (ranges are `[start, end]`) | Water reflection |
|
|
1801
|
+
| `simplexNoise` | `strength scale offset` | Cloudy noise over the layer |
|
|
1802
|
+
| `colorGradient` | `gradientType: 'linear' 'radial' 'conic'` (the filter's own option is `type`, which the DSL uses), `stops: [{ offset, color, alpha }]`, `angle` | A gradient laid over the layer |
|
|
1803
|
+
| `colorReplace` / `multiColorReplace` | `originalColor targetColor tolerance` / `replacements: [[from, to], …] tolerance` | Swap colours |
|
|
1804
|
+
| `convolution` | `matrix` (9 numbers), `width height` | Sharpen, edge detect |
|
|
1805
|
+
| `tiltShift` / `tiltShiftAxis` | `blur gradientBlur start end` (canvas pixels) | Miniature blur |
|
|
1806
|
+
| `shockwave` | `center amplitude wavelength brightness speed radius time` (animate `time` 0 → 1) | A ripple from a point |
|
|
1807
|
+
| `backdropBlur` | `strength` | Blurs what is BEHIND the layer, so alone it shows nothing |
|
|
1808
|
+
| the rest of pixi-filters (`colorMap`, `simpleLightmap`) | need a texture: `{ type: 'custom' }` | see the [pixi-filters docs](https://pixijs.io/filters/docs/) |
|
|
1789
1809
|
|
|
1790
1810
|
Gotchas, all measured:
|
|
1791
1811
|
|
|
1792
1812
|
- **Centres and offsets are in canvas pixels, not the layer's:** `twist.offset`, `bulgePinch.center`, `zoomBlur.center`, `radialBlur.center`, `shockwave.center` default to the canvas's top-left corner, so a layer in the middle looks unaffected. Put the centre on the layer: `{ type: 'twist', offset: { x: 640, y: 360 } }` for a layer centred on a 1280×720 canvas.
|
|
1793
1813
|
- **A filter works inside the layer's bounds.** Glow, dropShadow, outline and blur draw outside it and are clipped unless you widen the area (`filterArea: { x: -24, y: -24, width: w + 48, height: h + 48 }`, in the layer's own coordinates). Do not widen it for `grayscale`, `oldFilm` or other filters that paint the whole area: they fill the extra margin with black.
|
|
1794
1814
|
- **Animate scalars by name** (`'filters.halo.outerStrength'`). Options that are points (`rgbSplit.red`, `dropShadow.offset`) do not tween as a whole; animate `'filters.<name>.red.x'` if the filter exposes it, or swap the filter. `pixelate` animates through `sizeX` / `sizeY`, not `size`.
|
|
1815
|
+
- `shockwave` and `godray` are driven by `time` (a still frame at `time: 0` looks like nothing): animate it. `radialBlur`, `shockwave` and `tiltShift` positions are canvas pixels too.
|
|
1795
1816
|
- Some filters take their value as one argument; the DSL hides that (`{ type: 'pixelate', size: 8 }`, `{ type: 'emboss', strength: 6 }`).
|
|
1796
1817
|
|
|
1797
1818
|
### `custom`
|
|
@@ -2115,10 +2136,28 @@ interface RenderOptions {
|
|
|
2115
2136
|
}
|
|
2116
2137
|
```
|
|
2117
2138
|
|
|
2139
|
+
**Audio codec fallback:** unless you name `audio.codec`, the first codec this browser can actually encode is used (mp4: `aac`, then `opus`, `mp3`, `flac`; webm: `opus`, `vorbis`). Chrome on Linux has no AAC encoder, so there an mp4 with sound carries Opus and a warning says so (before, the export failed with a message about encoder configurations). A codec you name is never swapped: if it cannot be encoded the error says which one would work.
|
|
2140
|
+
|
|
2118
2141
|
Returns a `Blob` whose `type` is the container's MIME (e.g. `video/mp4`). Emits `'progress'` repeatedly during the render.
|
|
2119
2142
|
|
|
2120
2143
|
The renderer also forces a keyframe every ~2 seconds so the resulting file scrubs efficiently in standard players.
|
|
2121
2144
|
|
|
2145
|
+
### Poster time: `movie.init({ poster })`, `movie.poster`, `movie.posterImage()`
|
|
2146
|
+
|
|
2147
|
+
A movie can say which moment stands for it before it plays, the way `<video poster>` names a picture, except that here it is a **time**, so the picture comes from the same data and needs no separate image file:
|
|
2148
|
+
|
|
2149
|
+
```js
|
|
2150
|
+
await movie.init({ /* … */ poster: 9.5 }); // seconds; a negative value counts back from the end (-2 = two seconds before the end)
|
|
2151
|
+
movie.poster; // 9.5, or null
|
|
2152
|
+
movie.posterFrame; // 285 at 30 fps, or null
|
|
2153
|
+
const blob = await movie.posterImage(); // the picture at that time (frame 0 if there is no poster); { as: 'dataURL', type: 'image/jpeg', scale: 0.5 } like snapshot()
|
|
2154
|
+
```
|
|
2155
|
+
|
|
2156
|
+
- Once the movie is ready the **canvas shows the poster frame**, while the playhead stays at frame 0 (`currentFrame` is 0, the player shows 0:00). The first `play()` or `gotoFrame()` replaces it, and **play starts from 0**.
|
|
2157
|
+
- `posterImage()` leaves the movie as it was: still showing the poster, or back at the frame it was at. `contactSheet()` does the same.
|
|
2158
|
+
- A time past the end is clamped to the last frame, and something that is not a number is ignored; both warn.
|
|
2159
|
+
- `pixi-effects-check` writes the poster as `poster.jpg`.
|
|
2160
|
+
|
|
2122
2161
|
### `movie.timelineData(): TimelineData` and `movie.timelineChart(options?): string`
|
|
2123
2162
|
|
|
2124
2163
|
The whole movie as rows on a time axis, for a person to read at a glance. `timelineData()` is the data: `{ duration, rows, transitions }`, a row per layer with `name`, `type`, `depth` (1 inside a composition), absolute `start` / `end` in seconds, `keys` (when each keyframe starts) and, for a family of generated layers, `parts` (one span per layer). Layers of one type whose names differ only by numbers (`ring10-0`, `ring9-1`, `pop-3` …), four or more, become ONE row (`ring#-# ×19`), so a movie with 700 generated layers is a screenful. `timelineChart({ title })` returns one self-contained HTML page (an inline SVG, no scripts): bars coloured by type, ◆ at each keyframe start, a shaded band per transition, a tooltip per bar. `pixi-effects-check` writes it next to the contact sheet as `timeline.html`.
|
|
@@ -2143,6 +2182,12 @@ movie.off(event, fn): this
|
|
|
2143
2182
|
| `'play'` | none | when `play()` actually transitions from paused |
|
|
2144
2183
|
| `'pause'` | none | when `pause()` actually transitions from playing |
|
|
2145
2184
|
| `'progress'`| `{ progress: number; frame: number; totalFrames: number }` | during `render()`, once per encoded frame |
|
|
2185
|
+
| `'seeking'` / `'seeked'` | `{ frame, totalFrames }` (the frame it lands on) | a `gotoFrame()` to another frame starts / has finished. NOT emitted for playback ticks, `render()`, `snapshot()` or `contactSheet()` |
|
|
2186
|
+
| `'ended'` | none | playback ran off the end (after `'pause'`); pausing by hand does not emit it |
|
|
2187
|
+
| `'volumechange'` | `{ volume: number; muted: boolean }` | `volume` or `muted` really changed (clamping counts) |
|
|
2188
|
+
| `'error'` | `{ where: 'init' \| 'render' \| 'playback'; message: string; error: unknown }` | `init()` / `render()` failed (the call still rejects), or a frame failed during playback |
|
|
2189
|
+
|
|
2190
|
+
The names are the ones an HTML5 `<video>` uses, so a player written for `<video>` ports over.
|
|
2146
2191
|
|
|
2147
2192
|
`progress` is `0..100` (rounded integer percent).
|
|
2148
2193
|
|
|
@@ -2168,6 +2213,24 @@ Flips `muted` and returns the new value.
|
|
|
2168
2213
|
|
|
2169
2214
|
---
|
|
2170
2215
|
|
|
2216
|
+
## Building your own player
|
|
2217
|
+
|
|
2218
|
+
`Controller` is one optional player; everything it does is public on the movie, so a player of your own is plain HTML + CSS + a few handlers (`examples/15-custom-player.html` is a complete one):
|
|
2219
|
+
|
|
2220
|
+
```js
|
|
2221
|
+
const el = id => document.getElementById(id);
|
|
2222
|
+
movie.on('ready', () => { el('seek').max = movie.totalFrames; });
|
|
2223
|
+
movie.on('frame', ({ frame }) => { el('seek').value = frame; el('time').textContent = (frame / movie.frameRate).toFixed(1) + ' s'; });
|
|
2224
|
+
movie.on('play', () => { el('play').textContent = '❚❚'; });
|
|
2225
|
+
movie.on('pause', () => { el('play').textContent = '▶'; });
|
|
2226
|
+
movie.on('ended', () => { el('play').textContent = '↻'; }); // play() at the end starts again from 0
|
|
2227
|
+
movie.on('volumechange', ({ volume, muted }) => { el('vol').value = muted ? 0 : volume; });
|
|
2228
|
+
el('play').onclick = () => (movie.isPlaying ? movie.pause() : movie.play());
|
|
2229
|
+
el('seek').oninput = e => movie.gotoFrame(+e.target.value);
|
|
2230
|
+
```
|
|
2231
|
+
|
|
2232
|
+
What you have: `play()`, `pause()`, `gotoFrame(frame)`, `volume`, `muted` / `toggleMute()`; the state `currentFrame`, `totalFrames`, `frameRate`, `duration`, `isPlaying`, `isReady`; the events above; `render()` for a download button; `snapshot()` for a thumbnail; `timelineData()` / `timelineSvg()` for a timeline like `pixi-effects-view`'s. Keep the movie's `canvas` wherever your layout wants it (the page's CSS sizes it). To only change how `Controller` looks, see **Theme** below.
|
|
2233
|
+
|
|
2171
2234
|
## `Controller`
|
|
2172
2235
|
|
|
2173
2236
|
Imported from `pixi-effects/controller`.
|
|
@@ -2199,6 +2262,7 @@ interface ControllerOptions {
|
|
|
2199
2262
|
showExportButton?: boolean; // default true; hides ⬇ + popover
|
|
2200
2263
|
enableKeyboardShortcuts?: boolean; // default true
|
|
2201
2264
|
className?: string; // default 'movie-controller'
|
|
2265
|
+
theme?: ControllerTheme; // colours / thickness / font of the bar, see Theme
|
|
2202
2266
|
}
|
|
2203
2267
|
```
|
|
2204
2268
|
|
|
@@ -2207,6 +2271,30 @@ Mounting strategy:
|
|
|
2207
2271
|
- If `canvas.parentElement` already has a non-static `position`, the controller is appended directly into it.
|
|
2208
2272
|
- Otherwise the canvas is wrapped in a `<div class="movie-controller-wrap">` (with `position: relative`). The wrapper is removed on `destroy()`.
|
|
2209
2273
|
|
|
2274
|
+
### Theme: change the colours of the bar
|
|
2275
|
+
|
|
2276
|
+
The bar is blue by default. Every colour and the track thickness are CSS custom properties with the old values as defaults, so there are two ways to change them:
|
|
2277
|
+
|
|
2278
|
+
```js
|
|
2279
|
+
new Controller(movie, { canvas, theme: { accent: '#ff4d6d', foreground: '#ffe9a8', trackHeight: 5 } }); // in code
|
|
2280
|
+
controller.setTheme({ accent: '#7bd88f' }); // later, live; only the keys you give change
|
|
2281
|
+
controller.setTheme({ accent: null }); // null = back to the default
|
|
2282
|
+
```
|
|
2283
|
+
```css
|
|
2284
|
+
:root { --mc-accent: #ff4d6d; } /* or in your page CSS: the canvas's parent, or :root */
|
|
2285
|
+
```
|
|
2286
|
+
|
|
2287
|
+
| `theme` key | CSS property | What | Default |
|
|
2288
|
+
|---|---|---|---|
|
|
2289
|
+
| `accent` | `--mc-accent` | progress bar, its thumb, focus rings, the export button | `#007AFF` |
|
|
2290
|
+
| `foreground` | `--mc-fg` | icons, the time, the volume slider | `#fff` |
|
|
2291
|
+
| `track` | `--mc-track` | the unfilled part of the progress / volume bars | `rgba(255,255,255,0.25)` |
|
|
2292
|
+
| `barBackground` | `--mc-bar-bg` | the background behind the buttons (any CSS background, e.g. a gradient) | black fading to transparent upward |
|
|
2293
|
+
| `trackHeight` | `--mc-track-height` | thickness of the progress bar (a number is px) | `3px` |
|
|
2294
|
+
| `font` | `--mc-font` | `font-family` of the bar | system UI font |
|
|
2295
|
+
|
|
2296
|
+
A misspelt key warns with the valid ones. The default stylesheet is exported as `CONTROLLER_CSS` (from `pixi-effects/controller`) if you want to copy and restyle more.
|
|
2297
|
+
|
|
2210
2298
|
### `controller.destroy(): void`
|
|
2211
2299
|
|
|
2212
2300
|
Idempotent. Removes:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pixi-effects",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "Declarative composition and video rendering for the web. Built on PixiJS v8.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -41,6 +41,8 @@
|
|
|
41
41
|
"typecheck": "tsc --noEmit",
|
|
42
42
|
"build:ai": "node scripts/build-llms.mjs",
|
|
43
43
|
"build:gallery": "node scripts/build-gallery.mjs",
|
|
44
|
+
"posters": "node scripts/make-posters.mjs",
|
|
45
|
+
"guide": "node scripts/build-guide.mjs guide-preview",
|
|
44
46
|
"prepublishOnly": "npm run build:ai && npm run typecheck && npm test && npm run build"
|
|
45
47
|
},
|
|
46
48
|
"dependencies": {
|
|
@@ -62,6 +64,7 @@
|
|
|
62
64
|
"vitest": "^2.1.0",
|
|
63
65
|
"happy-dom": "^15.0.0",
|
|
64
66
|
"gsap": "^3.12.0",
|
|
67
|
+
"marked": "^18.0.14",
|
|
65
68
|
"pixi.js": "^8.22.0",
|
|
66
69
|
"pixi-filters": "^6.1.5",
|
|
67
70
|
"three": "^0.185.1",
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
var chunk4VSIJLU3_cjs = require('./chunk-4VSIJLU3.cjs');
|
|
4
|
-
require('./chunk-6I55T4OH.cjs');
|
|
5
|
-
require('./chunk-UQBGW5DO.cjs');
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
Object.defineProperty(exports, "CompositionSequence", {
|
|
10
|
-
enumerable: true,
|
|
11
|
-
get: function () { return chunk4VSIJLU3_cjs.CompositionSequence; }
|
|
12
|
-
});
|
|
13
|
-
//# sourceMappingURL=Composition-3KHQPRBA.cjs.map
|
|
14
|
-
//# sourceMappingURL=Composition-3KHQPRBA.cjs.map
|