pixi-effects 0.6.0 → 0.8.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.
Files changed (52) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/README.md +18 -11
  3. package/ai/SKILL.md +1 -1
  4. package/ai/reference/cheatsheet.md +9 -1
  5. package/ai/template.html +4 -3
  6. package/ai/tools/check.mjs +14 -5
  7. package/ai/tools/render.mjs +202 -0
  8. package/ai/tools/view.mjs +269 -0
  9. package/dist/{Base-4vNLl4tH.d.cts → Base-CSIulyIq.d.cts} +1 -1
  10. package/dist/{Base-CD4AoT5-.d.ts → Base-DVgwyIUj.d.ts} +1 -1
  11. package/dist/Composition-CM62XJKT.cjs +14 -0
  12. package/dist/{Composition-Y5VZSDDP.cjs.map → Composition-CM62XJKT.cjs.map} +1 -1
  13. package/dist/Composition-RJDSQYTV.js +5 -0
  14. package/dist/{Composition-7SO5WYHU.js.map → Composition-RJDSQYTV.js.map} +1 -1
  15. package/dist/Controller.cjs +122 -20
  16. package/dist/Controller.cjs.map +1 -1
  17. package/dist/Controller.d.cts +45 -3
  18. package/dist/Controller.d.ts +45 -3
  19. package/dist/Controller.js +122 -21
  20. package/dist/Controller.js.map +1 -1
  21. package/dist/{Movie-Ch2SUI1h.d.ts → Movie-CQOhUmko.d.cts} +75 -2
  22. package/dist/{Movie-CADDBfpl.d.cts → Movie-HF3pSGjQ.d.ts} +75 -2
  23. package/dist/{chunk-KQM2ZZMK.cjs → chunk-AGMNSJJH.cjs} +175 -29
  24. package/dist/chunk-AGMNSJJH.cjs.map +1 -0
  25. package/dist/{chunk-7IJHJXDI.js → chunk-FNKSXBKB.js} +136 -10
  26. package/dist/chunk-FNKSXBKB.js.map +1 -0
  27. package/dist/{chunk-B2FTJLNB.js → chunk-O4AJYUPK.js} +3 -3
  28. package/dist/{chunk-B2FTJLNB.js.map → chunk-O4AJYUPK.js.map} +1 -1
  29. package/dist/{chunk-4AGJF5RK.cjs → chunk-PDBDDGG5.cjs} +21 -21
  30. package/dist/{chunk-4AGJF5RK.cjs.map → chunk-PDBDDGG5.cjs.map} +1 -1
  31. package/dist/index.cjs +255 -25
  32. package/dist/index.cjs.map +1 -1
  33. package/dist/index.d.cts +4 -4
  34. package/dist/index.d.ts +4 -4
  35. package/dist/index.js +248 -18
  36. package/dist/index.js.map +1 -1
  37. package/dist/three.cjs +6 -5
  38. package/dist/three.cjs.map +1 -1
  39. package/dist/three.d.cts +2 -2
  40. package/dist/three.d.ts +2 -2
  41. package/dist/three.js +2 -1
  42. package/dist/three.js.map +1 -1
  43. package/dist/{types-DrW9QeKU.d.ts → types-oJijbn-K.d.cts} +14 -1
  44. package/dist/{types-DrW9QeKU.d.cts → types-oJijbn-K.d.ts} +14 -1
  45. package/docs/api.md +57 -0
  46. package/docs/dsl.md +60 -1
  47. package/llms-full.txt +127 -3
  48. package/package.json +7 -4
  49. package/dist/Composition-7SO5WYHU.js +0 -4
  50. package/dist/Composition-Y5VZSDDP.cjs +0 -13
  51. package/dist/chunk-7IJHJXDI.js.map +0 -1
  52. package/dist/chunk-KQM2ZZMK.cjs.map +0 -1
package/llms-full.txt CHANGED
@@ -14,7 +14,7 @@ You describe a video as a tree of plain objects; the library plays it in a canva
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
16
  2. **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.
17
- 3. **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 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.
17
+ 3. **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
18
  **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
19
  4. **Look at it** — you cannot judge motion from code. The library has the tools built in:
20
20
  - `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.
@@ -91,7 +91,7 @@ Operators `+ - * /`, parentheses, unary `-`. Functions `min max abs floor ceil r
91
91
  name, at, duration,
92
92
  initial: { ...props applied before any keyframe },
93
93
  keyframes: [ { at, duration, ease, set | to | from | (from + to), repeat?, yoyo?, repeatDelay? } ],
94
- filters: [ { type: 'chromaKey', keyColor, threshold, smoothing, spill } | { type: 'custom', name, filter: <Pixi Filter> } ],
94
+ filters: [ { type: 'chromaKey', keyColor, threshold, smoothing, spill } | { type: 'glow', name?, outerStrength: 3, color: '#ffd166' } /* any named filter, see below */ | { type: 'custom', name, filter: <Pixi Filter> } ],
95
95
  mask: <a layer spec>, maskInverted, // a mask with no `at` of its own starts and ends with the layer it masks (its keyframes count from that layer's start); an explicit mask `at` is composition time
96
96
  filterArea: { x, y, width, height } // in the layer's OWN coordinates; lets blur/glow draw past the layer's bounds
97
97
  threeD: true // opt into 2.5D (see below)
@@ -139,6 +139,14 @@ Style (`initial` / keyframes): `fillColor fillAlpha strokeColor strokeAlpha stro
139
139
 
140
140
  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
141
 
142
+ ## Player
143
+
144
+ `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.
145
+
146
+ ## Filters by name (no import)
147
+
148
+ `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.
149
+
142
150
  ## Transitions (on the parent composition)
143
151
 
144
152
  ```js
@@ -1743,6 +1751,65 @@ Removes a key color from the source. Works on video, image, or composition layer
1743
1751
 
1744
1752
  Animatable: `threshold`, `smoothing`, `spill`. `keyColor` is set at build time.
1745
1753
 
1754
+ ### Named filters (`{ type: 'glow', … }`)
1755
+
1756
+ A filter by name, as plain data. `type` is the filter's class name without `Filter`, in camelCase (case does not matter), and every other key is that filter's own option. No import, and the layer stays JSON:
1757
+
1758
+ ```ts
1759
+ { type: 'text', text: 'NEON', style: { fontSize: 140, fill: '#fff' }, initial: { x: 'GW/2', y: 'GH/2', anchorX: 0.5, anchorY: 0.5 },
1760
+ filters: [{ type: 'glow', name: 'halo', outerStrength: 0, color: '#ff4d9d', distance: 20 }],
1761
+ keyframes: [{ at: 0, to: { 'filters.halo.outerStrength': 6 }, duration: 1 }] } // animate any scalar option by name
1762
+ ```
1763
+
1764
+ 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:
1765
+
1766
+ | `type` | Options (the useful ones) | What it does |
1767
+ |---|---|---|
1768
+ | `blur` | `strength`, `quality` | Gaussian blur (pixi.js) |
1769
+ | `noise` | `noise` (0–1), `seed` | Film grain (pixi.js) |
1770
+ | `alpha` | `alpha` | Whole-layer opacity (pixi.js) |
1771
+ | `colorMatrix` | `preset`: `'sepia' 'grayscale' 'negative' 'polaroid' 'technicolor' 'vintage' 'kodachrome' 'browni'`, or `matrix` (20 numbers) | Colour grade (pixi.js) |
1772
+ | `adjustment` | `gamma contrast saturation brightness red green blue alpha` (1 = unchanged) | Colour grading |
1773
+ | `hslAdjustment` | `hue` (degrees) `saturation lightness colorize alpha` | Hue shift |
1774
+ | `grayscale` | – | Black and white |
1775
+ | `glow` | `distance outerStrength innerStrength color alpha quality knockout` | Glow around the shape |
1776
+ | `dropShadow` | `offset: { x, y }` `blur alpha color shadowOnly` | Shadow |
1777
+ | `outline` | `thickness color alpha quality knockout` | Stroke around the shape |
1778
+ | `pixelate` | `size` (a number, or `[w, h]`); animate `sizeX` / `sizeY` | Mosaic |
1779
+ | `crt` | `curvature lineWidth lineContrast noise vignetting vignettingAlpha time` | Old monitor |
1780
+ | `rgbSplit` | `red green blue` (each `{ x, y }` offset) | Chromatic aberration |
1781
+ | `oldFilm` | `sepia noise scratch scratchDensity vignetting seed` | Aged film |
1782
+ | `glitch` | `slices offset direction seed average` | Digital glitch |
1783
+ | `bulgePinch` | `center radius strength` | Bulge (+) / pinch (−) |
1784
+ | `twist` | `radius angle offset: { x, y }` | Swirl |
1785
+ | `zoomBlur` | `strength center innerRadius radius` | Radial zoom streaks |
1786
+ | `kawaseBlur` | `strength quality` | Cheap, wide blur |
1787
+ | `radialBlur` / `motionBlur` | `angle center radius` / `velocity kernelSize` | Spin / directional blur |
1788
+ | `emboss` | `strength` | Relief |
1789
+ | `ascii` | `size color replaceColor` | Text-art |
1790
+ | `colorOverlay` | `color alpha` | Tint over everything |
1791
+ | `advancedBloom` / `bloom` | `threshold bloomScale brightness blur` | Bloom |
1792
+ | `bevel` | `thickness rotation lightColor lightAlpha shadowColor shadowAlpha` | Raised edge |
1793
+ | `dot` / `crossHatch` | `scale angle grayscale` / – | Halftone / hatching |
1794
+ | `godray` | `angle gain lacunarity alpha parallel time` | Light shafts (animate `time`) |
1795
+ | `reflection` | `mirror boundary amplitude waveLength alpha time` (ranges are `[start, end]`) | Water reflection |
1796
+ | `simplexNoise` | `strength scale offset` | Cloudy noise over the layer |
1797
+ | `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 |
1798
+ | `colorReplace` / `multiColorReplace` | `originalColor targetColor tolerance` / `replacements: [[from, to], …] tolerance` | Swap colours |
1799
+ | `convolution` | `matrix` (9 numbers), `width height` | Sharpen, edge detect |
1800
+ | `tiltShift` / `tiltShiftAxis` | `blur gradientBlur start end` (canvas pixels) | Miniature blur |
1801
+ | `shockwave` | `center amplitude wavelength brightness speed radius time` (animate `time` 0 → 1) | A ripple from a point |
1802
+ | `backdropBlur` | `strength` | Blurs what is BEHIND the layer, so alone it shows nothing |
1803
+ | the rest of pixi-filters (`colorMap`, `simpleLightmap`) | need a texture: `{ type: 'custom' }` | see the [pixi-filters docs](https://pixijs.io/filters/docs/) |
1804
+
1805
+ Gotchas, all measured:
1806
+
1807
+ - **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.
1808
+ - **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.
1809
+ - **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`.
1810
+ - `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.
1811
+ - Some filters take their value as one argument; the DSL hides that (`{ type: 'pixelate', size: 8 }`, `{ type: 'emboss', strength: 6 }`).
1812
+
1746
1813
  ### `custom`
1747
1814
 
1748
1815
  Escape hatch for any PIXI `Filter` instance — including PIXI's own built-ins (`BlurFilter`, `ColorMatrixFilter`, `NoiseFilter`, etc.), [pixi-filters](https://github.com/pixijs/filters), community packages, or your own `Filter` subclass. The instance is used as-is; animation works the same way as for `chromaKey` as long as the filter has writable scalar properties at the addressed paths.
@@ -1776,7 +1843,7 @@ Notes:
1776
1843
  Notes:
1777
1844
 
1778
1845
  - `filter` must be a PIXI `Filter` instance (constructor must have run on the consumer side). Plain object literals throw.
1779
- - `pixi-filters` is **not** a dependency of pixi-effects — install it on your side if you want to use it.
1846
+ - Use `custom` for a filter that has no name above (a community filter, or your own `Filter`). For everything in pixi.js and `pixi-filters`, prefer the named form.
1780
1847
  - Without a `name`, the filter still applies but cannot be addressed via `filters.<name>.<prop>` keyframe paths.
1781
1848
 
1782
1849
  ---
@@ -2064,10 +2131,18 @@ interface RenderOptions {
2064
2131
  }
2065
2132
  ```
2066
2133
 
2134
+ **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.
2135
+
2067
2136
  Returns a `Blob` whose `type` is the container's MIME (e.g. `video/mp4`). Emits `'progress'` repeatedly during the render.
2068
2137
 
2069
2138
  The renderer also forces a keyframe every ~2 seconds so the resulting file scrubs efficiently in standard players.
2070
2139
 
2140
+ ### `movie.timelineData(): TimelineData` and `movie.timelineChart(options?): string`
2141
+
2142
+ 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
+
2144
+ `movie.timelineSvg()` is the chart alone (one `<svg>`, geometry in `data-*` attributes, each row `<g class="row" data-start>`): `pixi-effects-view` puts a playhead on it and turns a click into a seek.
2145
+
2071
2146
  ### `movie.destroy(): Promise<void>`
2072
2147
 
2073
2148
  Pauses playback, destroys the underlying PIXI Application, releases audio buffers and AudioContext, and marks the instance unusable.
@@ -2086,6 +2161,12 @@ movie.off(event, fn): this
2086
2161
  | `'play'` | none | when `play()` actually transitions from paused |
2087
2162
  | `'pause'` | none | when `pause()` actually transitions from playing |
2088
2163
  | `'progress'`| `{ progress: number; frame: number; totalFrames: number }` | during `render()`, once per encoded frame |
2164
+ | `'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()` |
2165
+ | `'ended'` | none | playback ran off the end (after `'pause'`); pausing by hand does not emit it |
2166
+ | `'volumechange'` | `{ volume: number; muted: boolean }` | `volume` or `muted` really changed (clamping counts) |
2167
+ | `'error'` | `{ where: 'init' \| 'render' \| 'playback'; message: string; error: unknown }` | `init()` / `render()` failed (the call still rejects), or a frame failed during playback |
2168
+
2169
+ The names are the ones an HTML5 `<video>` uses, so a player written for `<video>` ports over.
2089
2170
 
2090
2171
  `progress` is `0..100` (rounded integer percent).
2091
2172
 
@@ -2111,6 +2192,24 @@ Flips `muted` and returns the new value.
2111
2192
 
2112
2193
  ---
2113
2194
 
2195
+ ## Building your own player
2196
+
2197
+ `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):
2198
+
2199
+ ```js
2200
+ const el = id => document.getElementById(id);
2201
+ movie.on('ready', () => { el('seek').max = movie.totalFrames; });
2202
+ movie.on('frame', ({ frame }) => { el('seek').value = frame; el('time').textContent = (frame / movie.frameRate).toFixed(1) + ' s'; });
2203
+ movie.on('play', () => { el('play').textContent = '❚❚'; });
2204
+ movie.on('pause', () => { el('play').textContent = '▶'; });
2205
+ movie.on('ended', () => { el('play').textContent = '↻'; }); // play() at the end starts again from 0
2206
+ movie.on('volumechange', ({ volume, muted }) => { el('vol').value = muted ? 0 : volume; });
2207
+ el('play').onclick = () => (movie.isPlaying ? movie.pause() : movie.play());
2208
+ el('seek').oninput = e => movie.gotoFrame(+e.target.value);
2209
+ ```
2210
+
2211
+ 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.
2212
+
2114
2213
  ## `Controller`
2115
2214
 
2116
2215
  Imported from `pixi-effects/controller`.
@@ -2142,6 +2241,7 @@ interface ControllerOptions {
2142
2241
  showExportButton?: boolean; // default true; hides ⬇ + popover
2143
2242
  enableKeyboardShortcuts?: boolean; // default true
2144
2243
  className?: string; // default 'movie-controller'
2244
+ theme?: ControllerTheme; // colours / thickness / font of the bar, see Theme
2145
2245
  }
2146
2246
  ```
2147
2247
 
@@ -2150,6 +2250,30 @@ Mounting strategy:
2150
2250
  - If `canvas.parentElement` already has a non-static `position`, the controller is appended directly into it.
2151
2251
  - Otherwise the canvas is wrapped in a `<div class="movie-controller-wrap">` (with `position: relative`). The wrapper is removed on `destroy()`.
2152
2252
 
2253
+ ### Theme: change the colours of the bar
2254
+
2255
+ 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:
2256
+
2257
+ ```js
2258
+ new Controller(movie, { canvas, theme: { accent: '#ff4d6d', foreground: '#ffe9a8', trackHeight: 5 } }); // in code
2259
+ controller.setTheme({ accent: '#7bd88f' }); // later, live; only the keys you give change
2260
+ controller.setTheme({ accent: null }); // null = back to the default
2261
+ ```
2262
+ ```css
2263
+ :root { --mc-accent: #ff4d6d; } /* or in your page CSS: the canvas's parent, or :root */
2264
+ ```
2265
+
2266
+ | `theme` key | CSS property | What | Default |
2267
+ |---|---|---|---|
2268
+ | `accent` | `--mc-accent` | progress bar, its thumb, focus rings, the export button | `#007AFF` |
2269
+ | `foreground` | `--mc-fg` | icons, the time, the volume slider | `#fff` |
2270
+ | `track` | `--mc-track` | the unfilled part of the progress / volume bars | `rgba(255,255,255,0.25)` |
2271
+ | `barBackground` | `--mc-bar-bg` | the background behind the buttons (any CSS background, e.g. a gradient) | black fading to transparent upward |
2272
+ | `trackHeight` | `--mc-track-height` | thickness of the progress bar (a number is px) | `3px` |
2273
+ | `font` | `--mc-font` | `font-family` of the bar | system UI font |
2274
+
2275
+ 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.
2276
+
2153
2277
  ### `controller.destroy(): void`
2154
2278
 
2155
2279
  Idempotent. Removes:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pixi-effects",
3
- "version": "0.6.0",
3
+ "version": "0.8.0",
4
4
  "description": "Declarative composition and video rendering for the web. Built on PixiJS v8.",
5
5
  "type": "module",
6
6
  "exports": {
@@ -20,7 +20,7 @@
20
20
  "main": "./dist/index.cjs",
21
21
  "module": "./dist/index.js",
22
22
  "types": "./dist/index.d.ts",
23
- "bin": { "pixi-effects-check": "ai/tools/check.mjs" },
23
+ "bin": { "pixi-effects-check": "ai/tools/check.mjs", "pixi-effects-render": "ai/tools/render.mjs", "pixi-effects-view": "ai/tools/view.mjs" },
24
24
  "files": ["dist", "ai", "docs/dsl.md", "docs/api.md", "llms.txt", "llms-full.txt", "CHANGELOG.md", "README.md", "LICENSE"],
25
25
  "keywords": [
26
26
  "pixi", "pixijs", "video", "composition", "compositing", "rendering",
@@ -49,10 +49,12 @@
49
49
  "peerDependencies": {
50
50
  "gsap": "^3.12.0",
51
51
  "pixi.js": "^8.0.0",
52
- "three": ">=0.150.0"
52
+ "three": ">=0.150.0",
53
+ "pixi-filters": "^6.1.0"
53
54
  },
54
55
  "peerDependenciesMeta": {
55
- "three": { "optional": true }
56
+ "three": { "optional": true },
57
+ "pixi-filters": { "optional": true }
56
58
  },
57
59
  "devDependencies": {
58
60
  "tsup": "^8.0.0",
@@ -61,6 +63,7 @@
61
63
  "happy-dom": "^15.0.0",
62
64
  "gsap": "^3.12.0",
63
65
  "pixi.js": "^8.22.0",
66
+ "pixi-filters": "^6.1.5",
64
67
  "three": "^0.185.1",
65
68
  "@types/three": "^0.185.0"
66
69
  },
@@ -1,4 +0,0 @@
1
- export { CompositionSequence } from './chunk-B2FTJLNB.js';
2
- import './chunk-7IJHJXDI.js';
3
- //# sourceMappingURL=Composition-7SO5WYHU.js.map
4
- //# sourceMappingURL=Composition-7SO5WYHU.js.map
@@ -1,13 +0,0 @@
1
- 'use strict';
2
-
3
- var chunk4AGJF5RK_cjs = require('./chunk-4AGJF5RK.cjs');
4
- require('./chunk-KQM2ZZMK.cjs');
5
-
6
-
7
-
8
- Object.defineProperty(exports, "CompositionSequence", {
9
- enumerable: true,
10
- get: function () { return chunk4AGJF5RK_cjs.CompositionSequence; }
11
- });
12
- //# sourceMappingURL=Composition-Y5VZSDDP.cjs.map
13
- //# sourceMappingURL=Composition-Y5VZSDDP.cjs.map