pixi-effects 0.30.0 → 0.31.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 +25 -0
- package/README.md +6 -5
- package/ai/chat-template.html +3 -3
- package/ai/tools/check.mjs +42 -2
- package/ai/tools/lookup.mjs +12 -6
- package/ai/tools/render.mjs +15 -2
- package/ai/tools/varArgs.mjs +75 -0
- package/dist/{Base-CB1_FPZD.d.cts → Base-BKagtWc0.d.cts} +1 -1
- package/dist/{Base-Ne74ztNl.d.ts → Base-jRiMHcWT.d.ts} +1 -1
- package/dist/Composition-DQCPJPFE.js +7 -0
- package/dist/{Composition-RJGA7KDU.js.map → Composition-DQCPJPFE.js.map} +1 -1
- package/dist/Composition-V2XYSW6P.cjs +16 -0
- package/dist/{Composition-MUKR3FYD.cjs.map → Composition-V2XYSW6P.cjs.map} +1 -1
- package/dist/Controller.cjs +42 -1
- package/dist/Controller.cjs.map +1 -1
- package/dist/Controller.d.cts +10 -2
- package/dist/Controller.d.ts +10 -2
- package/dist/Controller.js +42 -1
- package/dist/Controller.js.map +1 -1
- package/dist/{Movie-DdnpLpw1.d.cts → Movie-BH81stkb.d.cts} +90 -3
- package/dist/{Movie-C2rZFkrO.d.ts → Movie-CfFPItoL.d.ts} +90 -3
- package/dist/{chunk-A3Q3SDSH.js → chunk-BGMCZA36.js} +3 -3
- package/dist/{chunk-A3Q3SDSH.js.map → chunk-BGMCZA36.js.map} +1 -1
- package/dist/{chunk-ZVUO325O.cjs → chunk-DJ5B7IKT.cjs} +9 -3
- package/dist/chunk-DJ5B7IKT.cjs.map +1 -0
- package/dist/{chunk-WHNADXXG.cjs → chunk-L73KRFXI.cjs} +61 -61
- package/dist/{chunk-WHNADXXG.cjs.map → chunk-L73KRFXI.cjs.map} +1 -1
- package/dist/{chunk-ZAXYPXKW.js → chunk-MMTESMJ5.js} +9 -4
- package/dist/chunk-MMTESMJ5.js.map +1 -0
- package/dist/{chunk-CRQFDY7E.js → chunk-UTIBYMIO.js} +461 -56
- package/dist/chunk-UTIBYMIO.js.map +1 -0
- package/dist/{chunk-HREBQAY4.cjs → chunk-X3WHETQ3.cjs} +485 -80
- package/dist/chunk-X3WHETQ3.cjs.map +1 -0
- package/dist/index.cjs +31 -31
- package/dist/index.d.cts +5 -5
- package/dist/index.d.ts +5 -5
- package/dist/index.js +3 -3
- package/dist/loader.css +1 -1
- package/dist/present.cjs +3 -3
- package/dist/present.d.cts +2 -2
- package/dist/present.d.ts +2 -2
- package/dist/present.js +3 -3
- package/dist/three.cjs +5 -5
- package/dist/three.d.cts +2 -2
- package/dist/three.d.ts +2 -2
- package/dist/three.js +1 -1
- package/dist/{types-BzIB65Vk.d.cts → types-CK_crNAI.d.cts} +2 -0
- package/dist/{types-BzIB65Vk.d.ts → types-CK_crNAI.d.ts} +2 -0
- package/docs/api.md +28 -1
- package/docs/dsl.md +32 -1
- package/llms-full.txt +145 -11
- package/package.json +83 -20
- package/skills/pixi-effects/SKILL.md +1 -1
- package/skills/pixi-effects/reference/cheatsheet.md +3 -0
- package/skills/pixi-effects/reference/pitfalls.md +4 -0
- package/skills/pixi-effects/reference/recipes.md +79 -8
- package/skills/pixi-effects/template.html +6 -6
- package/dist/Composition-MUKR3FYD.cjs +0 -16
- package/dist/Composition-RJGA7KDU.js +0 -7
- package/dist/chunk-CRQFDY7E.js.map +0 -1
- package/dist/chunk-HREBQAY4.cjs.map +0 -1
- package/dist/chunk-ZAXYPXKW.js.map +0 -1
- package/dist/chunk-ZVUO325O.cjs.map +0 -1
|
@@ -190,6 +190,8 @@ const max = Math.max(...data.map(d => d[1]));
|
|
|
190
190
|
const step = [1, 2, 2.5, 5, 10].map(m => m * 10 ** Math.floor(Math.log10(max / 4))).find(v => max / v <= 5) ?? 10;
|
|
191
191
|
const NICE = Math.ceil(max / step) * step; // axis top, so the chart survives changed data
|
|
192
192
|
const BASE = 600, CHART_H = 380, BAR_W = 90, GAP = 40, DUR = 0.9, EASE = 'power3.out';
|
|
193
|
+
const EACH = 0.12; // seconds between bars: 0.12 is a quick wave; EACH = DUR + 0.3 grows them one at a time
|
|
194
|
+
const PEAK_AT = Math.max(4, 0.4 + (data.length - 1) * EACH + DUR + 0.2); // the highlight and the callout come after the last bar has grown
|
|
193
195
|
const X0 = (1280 - (data.length * BAR_W + (data.length - 1) * GAP)) / 2, X1 = X0 + data.length * BAR_W + (data.length - 1) * GAP;
|
|
194
196
|
const sequences = [];
|
|
195
197
|
for (let v = 0; v <= NICE; v += step) { // grid lines + tick labels
|
|
@@ -200,14 +202,14 @@ for (let v = 0; v <= NICE; v += step) { // grid lines + ti
|
|
|
200
202
|
const best = data.findIndex(d => d[1] === max);
|
|
201
203
|
data.forEach(([label, value], i) => {
|
|
202
204
|
const x = X0 + i * (BAR_W + GAP) + BAR_W / 2;
|
|
203
|
-
const at = 0.4 + i *
|
|
205
|
+
const at = 0.4 + i * EACH;
|
|
204
206
|
const h = CHART_H * value / NICE;
|
|
205
207
|
sequences.push({
|
|
206
208
|
type: 'shape', shape: 'rect', width: BAR_W, height: 0, cornerRadius: 8, anchorY: 1, at, colorSpace: 'oklab',
|
|
207
209
|
initial: { x, y: BASE, fillColor: '#4f6df5' },
|
|
208
210
|
keyframes: [
|
|
209
211
|
{ at: 0, to: { height: h }, duration: DUR, ease: EASE },
|
|
210
|
-
...(i === best ? [{ at:
|
|
212
|
+
...(i === best ? [{ at: PEAK_AT - at, to: { fillColor: '#ffd166' }, duration: 0.5 }] : [{ at: PEAK_AT - at, to: { alpha: 0.45 }, duration: 0.5 }]), // highlight the maximum at t = 4 s
|
|
211
213
|
],
|
|
212
214
|
});
|
|
213
215
|
sequences.push({ // value label: counts up while riding the bar's top
|
|
@@ -221,11 +223,11 @@ data.forEach(([label, value], i) => {
|
|
|
221
223
|
// callout: a pill sized from its text (~0.58 x fontSize per glyph + padding), a pointer triangle, clamped inside the plot
|
|
222
224
|
const text = 'Peak: ' + data[best][0] + ' ' + max, FS = 26;
|
|
223
225
|
const PW = text.length * FS * 0.58 + 44, cx = Math.min(Math.max(X0 + best * (BAR_W + GAP) + BAR_W / 2, X0 + PW / 2), X1 - PW / 2), cy = BASE - CHART_H - 70;
|
|
224
|
-
sequences.push({ type: 'shape', shape: 'rect', width: PW, height: 52, cornerRadius: 26, at:
|
|
226
|
+
sequences.push({ type: 'shape', shape: 'rect', width: PW, height: 52, cornerRadius: 26, at: PEAK_AT + 0.2, initial: { x: cx, y: cy, fillColor: '#ffd166', alpha: 0 },
|
|
225
227
|
keyframes: [{ at: 0, to: { alpha: 1 }, duration: 0.3 }] });
|
|
226
|
-
sequences.push({ type: 'shape', shape: 'polygon', points: [[-10, 0], [10, 0], [0, 12]], at:
|
|
228
|
+
sequences.push({ type: 'shape', shape: 'polygon', points: [[-10, 0], [10, 0], [0, 12]], at: PEAK_AT + 0.2, initial: { x: cx, y: cy + 32, fillColor: '#ffd166', alpha: 0 },
|
|
227
229
|
keyframes: [{ at: 0, to: { alpha: 1 }, duration: 0.3 }] });
|
|
228
|
-
sequences.push({ type: 'text', text, at:
|
|
230
|
+
sequences.push({ type: 'text', text, at: PEAK_AT + 0.2, style: { fontSize: FS, fontWeight: 'bold', fill: '#1b1b2f' }, initial: { x: cx, y: cy, anchorX: 0.5, anchorY: 0.5, alpha: 0 },
|
|
229
231
|
keyframes: [{ at: 0, to: { alpha: 1 }, duration: 0.3 }] });
|
|
230
232
|
return sequences;
|
|
231
233
|
```
|
|
@@ -356,6 +358,23 @@ return { sequences, transitions, duration: total };
|
|
|
356
358
|
|
|
357
359
|
Source images should be at least canvas-sized (`kenBurns` zooms in): draw generated images at 1920×1080 for a 1280×720 movie.
|
|
358
360
|
|
|
361
|
+
**A plain cut instead of a transition, generated images, music with no file:** scenes are layers one after the other (`at` = `i × L`) with no `transitions`; the images are the assets `p1…p3` (draw them with `placeholderPhoto`, recipe `placeholder-images`). The tune is written as text: at 96 bpm a beat is 0.625 s, so 24 beats are 15 s and every track has to add up to 24 (the check's `music` read-back shows each track's length).
|
|
362
|
+
|
|
363
|
+
```js
|
|
364
|
+
// @recipe slideshow-cuts
|
|
365
|
+
const N = 3, L = 5, total = N * L;
|
|
366
|
+
const sequences = Array.from({ length: N }, (_, i) =>
|
|
367
|
+
kenBurns({ asset: 'p' + (i + 1), name: 'scene' + i, at: i * L, duration: L, motion: i % 2 ? 'position' : 'scale' }));
|
|
368
|
+
sequences.push({ type: 'audio', at: 0, volume: 0.7, duration: total, music: {
|
|
369
|
+
bpm: 96,
|
|
370
|
+
tracks: [
|
|
371
|
+
{ inst: 'pad', notes: 'Am:4 F:4 C:4 G:4 Am:4 F:4' }, // 6 chords x 4 beats = 24 beats = 15 s
|
|
372
|
+
{ inst: 'bass', notes: 'a1:4 f1:4 c2:4 g1:4 a1:4 f1:4' },
|
|
373
|
+
],
|
|
374
|
+
} });
|
|
375
|
+
return { sequences, duration: total };
|
|
376
|
+
```
|
|
377
|
+
|
|
359
378
|
---
|
|
360
379
|
|
|
361
380
|
## Music with no file (`music`)
|
|
@@ -656,7 +675,7 @@ return { duration: d.duration, sequences: d.composition.sequences, transitions:
|
|
|
656
675
|
```
|
|
657
676
|
|
|
658
677
|
```js
|
|
659
|
-
// @docs-only
|
|
678
|
+
// @docs-only deck-in-page
|
|
660
679
|
// in the page: the deck is spread into init; the Presenter (or the Controller, which then shows the stops and a Present button) plays it
|
|
661
680
|
import { Movie } from 'pixi-effects';
|
|
662
681
|
import { deck, Presenter } from 'pixi-effects/present'; // (add "pixi-effects/present" to the import map)
|
|
@@ -1025,7 +1044,7 @@ return [{ type: 'composition', name: 'film', width: 'GW', height: 'GH', sequence
|
|
|
1025
1044
|
Draw on a canvas and register `canvas.toDataURL()` as an asset (`data:` URLs work). Make the image at least canvas-sized (1920×1080 for a 1280×720 movie) so `kenBurns` zooms stay sharp.
|
|
1026
1045
|
|
|
1027
1046
|
```js
|
|
1028
|
-
// @docs-only
|
|
1047
|
+
// @docs-only placeholder-images
|
|
1029
1048
|
function placeholderPhoto(name, hueA, hueB, w = 1920, h = 1080) {
|
|
1030
1049
|
const c = Object.assign(document.createElement('canvas'), { width: w, height: h });
|
|
1031
1050
|
const g = c.getContext('2d');
|
|
@@ -1044,7 +1063,7 @@ function placeholderPhoto(name, hueA, hueB, w = 1920, h = 1080) {
|
|
|
1044
1063
|
`ctx.renderer` is a normal `WebGLRenderer`, so an environment map works; lights alone make `metalness: 1` look black. `three/addons` is not in the importmap: build the environment yourself. Size the layer to the area you want (it clips at its own rectangle) and pull the camera back (`z ≈ 5.4` for a radius-1 torus knot at fov 50).
|
|
1045
1064
|
|
|
1046
1065
|
```js
|
|
1047
|
-
// @docs-only
|
|
1066
|
+
// @docs-only metallic-three
|
|
1048
1067
|
// registerThree(); const THREE = await import('three');
|
|
1049
1068
|
three({
|
|
1050
1069
|
type: 'three', width: 'GW * 0.56', height: 'GH * 0.9', initial: { x: 'GW * 0.77', y: 'GH/2', anchorX: 0.5, anchorY: 0.5 },
|
|
@@ -1155,3 +1174,55 @@ while IFS=, read -r name score; do
|
|
|
1155
1174
|
npx pixi-effects render batch.html --query "name=$(printf %s "$name" | jq -sRr @uri)&score=$score" -o "out/$name.mp4" --quiet
|
|
1156
1175
|
done < table.csv
|
|
1157
1176
|
```
|
|
1177
|
+
|
|
1178
|
+
---
|
|
1179
|
+
|
|
1180
|
+
## One film, several languages and colours (variables)
|
|
1181
|
+
|
|
1182
|
+
Declare what changes as `vars` and write `{name}` in the strings and the names as identifiers in the expressions. One page then makes every variant: `pixi-effects render film.html --var lang=ja --var accent=#3949ab -o film-ja.mp4`, and a page can switch live with `movie.setVars(...)`. Load the fonts of every language before the first build (pitfall 103); `check` says a variable nothing reads, and a `{name}` nobody declared.
|
|
1183
|
+
|
|
1184
|
+
```js
|
|
1185
|
+
// @docs-only variants
|
|
1186
|
+
const COPY = { en: { head: 'Spring sale', sub: 'Up to 40% off' }, ja: { head: '春のセール', sub: '最大40%オフ' } };
|
|
1187
|
+
const lang = new URLSearchParams(location.search).get('lang') ?? 'en';
|
|
1188
|
+
await movie.init({
|
|
1189
|
+
canvas, width: 1920, height: 1080, duration: 5,
|
|
1190
|
+
vars: { head: COPY[lang].head, sub: COPY[lang].sub, accent: '#e5399a', lift: 0 }, // numbers can be read in expressions, any value in {name}
|
|
1191
|
+
composition: { sequences: [
|
|
1192
|
+
{ type: 'shape', shape: 'rect', width: 'GW', height: 'GH', initial: { x: 'GW/2', y: 'GH/2', fillColor: '#12151f' } },
|
|
1193
|
+
{ type: 'text', text: '{head}', at: 0.3, duration: 4.7, style: { fontSize: 120, fontWeight: 'bold', fill: '{accent}' },
|
|
1194
|
+
initial: { x: 'GW/2', y: 'GH/2 - 40 - lift', anchorX: 0.5, anchorY: 0.5 }, keyframes: [{ at: 0, from: { alpha: 0 }, to: { alpha: 1 }, duration: 0.6 }] },
|
|
1195
|
+
{ type: 'text', text: '{sub}', at: 0.9, duration: 4.1, style: { fontSize: 56, fill: '#ffffff' },
|
|
1196
|
+
initial: { x: 'GW/2', y: 'GH/2 + 80', anchorX: 0.5, anchorY: 0.5 }, keyframes: [{ at: 0, from: { alpha: 0 }, to: { alpha: 1 }, duration: 0.6 }] },
|
|
1197
|
+
] },
|
|
1198
|
+
});
|
|
1199
|
+
// later, from a button: await movie.setVars({ head: COPY.ja.head, sub: COPY.ja.sub, accent: '#3949ab' }); // same frame, sound untouched
|
|
1200
|
+
```
|
|
1201
|
+
|
|
1202
|
+
---
|
|
1203
|
+
|
|
1204
|
+
## A layout that follows the window (liquid)
|
|
1205
|
+
|
|
1206
|
+
`resize(w, h)` builds the composition again at that size, and every expression sees it as `W` / `H` / `GW` / `GH`: write the layout in those, never in pixels. For a different arrangement per shape (wide / tall) write the composition in a function of the size and hand it to `rebuild`. Watch a **box** that you size with CSS (`#box { width: 100vw; height: 100vh }`, the canvas inside it `width: 100%; height: 100%`), not the canvas: the movie sets the canvas's pixel size, and an observer on the canvas would see its own change and grow it again. `check --size 1920x1080,1080x1920` makes a contact sheet and a layout review for each size without a browser window.
|
|
1207
|
+
|
|
1208
|
+
```js
|
|
1209
|
+
// @docs-only liquid-layout
|
|
1210
|
+
const layout = (w, h) => {
|
|
1211
|
+
const tall = h > w;
|
|
1212
|
+
return { sequences: [
|
|
1213
|
+
{ type: 'shape', shape: 'rect', width: 'GW', height: 'GH', initial: { x: 'GW/2', y: 'GH/2', fillColor: '#12151f' } },
|
|
1214
|
+
{ type: 'text', text: 'Headline', style: { fontSize: tall ? 'GW * 0.12' : 'GH * 0.14', fontWeight: 'bold', fill: '#ffffff' },
|
|
1215
|
+
initial: { x: tall ? 'GW * 0.08' : 'GW/2', y: tall ? 'GH * 0.7' : 'GH/2', anchorX: tall ? 0 : 0.5, anchorY: 0.5 } },
|
|
1216
|
+
] };
|
|
1217
|
+
};
|
|
1218
|
+
// <div id="box" style="width:100vw;height:100vh"><canvas id="stage" style="width:100%;height:100%;display:block"></canvas></div>
|
|
1219
|
+
const box = document.getElementById('box');
|
|
1220
|
+
const pixels = () => [Math.round(box.clientWidth * devicePixelRatio), Math.round(box.clientHeight * devicePixelRatio)];
|
|
1221
|
+
const [w0, h0] = pixels();
|
|
1222
|
+
await movie.init({ canvas, width: w0, height: h0, duration: 6, composition: layout(w0, h0) });
|
|
1223
|
+
new ResizeObserver(() => {
|
|
1224
|
+
const [w, h] = pixels();
|
|
1225
|
+
movie.rebuild({ composition: layout(w, h), width: w, height: h }).catch(console.error); // overlapping calls are joined: only the latest size is built
|
|
1226
|
+
}).observe(box);
|
|
1227
|
+
```
|
|
1228
|
+
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
6
|
<title>pixi-effects video</title>
|
|
7
|
-
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/pixi-effects@0.
|
|
7
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/pixi-effects@0.31.0/dist/loader.css" /> <!-- the loader: HTML + CSS only, so it is on screen at once and keeps moving while the page builds the video -->
|
|
8
8
|
<style>
|
|
9
9
|
body { background: #0d1220; margin: 0; padding: 16px; }
|
|
10
10
|
.stage { position: relative; width: min(960px, 100%); } /* the player and the loader both sit inside this box */
|
|
@@ -37,16 +37,16 @@
|
|
|
37
37
|
"imports": {
|
|
38
38
|
"pixi.js": "https://esm.sh/pixi.js@8.22.0?bundle-deps",
|
|
39
39
|
"mediabunny": "https://esm.sh/mediabunny",
|
|
40
|
-
"pixi-effects": "https://cdn.jsdelivr.net/npm/pixi-effects@0.
|
|
41
|
-
"pixi-effects/controller": "https://cdn.jsdelivr.net/npm/pixi-effects@0.
|
|
42
|
-
"pixi-effects/present": "https://cdn.jsdelivr.net/npm/pixi-effects@0.
|
|
43
|
-
"pixi-effects/three": "https://cdn.jsdelivr.net/npm/pixi-effects@0.
|
|
40
|
+
"pixi-effects": "https://cdn.jsdelivr.net/npm/pixi-effects@0.31.0/dist/index.js",
|
|
41
|
+
"pixi-effects/controller": "https://cdn.jsdelivr.net/npm/pixi-effects@0.31.0/dist/Controller.js",
|
|
42
|
+
"pixi-effects/present": "https://cdn.jsdelivr.net/npm/pixi-effects@0.31.0/dist/present.js",
|
|
43
|
+
"pixi-effects/three": "https://cdn.jsdelivr.net/npm/pixi-effects@0.31.0/dist/three.js",
|
|
44
44
|
"pixi-filters": "https://esm.sh/pixi-filters@6.1.5?external=pixi.js",
|
|
45
45
|
"three": "https://esm.sh/three@0.178.0"
|
|
46
46
|
}
|
|
47
47
|
}
|
|
48
48
|
</script>
|
|
49
|
-
<link rel="modulepreload" href="https://cdn.jsdelivr.net/npm/pixi-effects@0.
|
|
49
|
+
<link rel="modulepreload" href="https://cdn.jsdelivr.net/npm/pixi-effects@0.31.0/dist/index.js" /> <!-- after the import map: the library downloads while the page is parsed -->
|
|
50
50
|
|
|
51
51
|
<script type="module">
|
|
52
52
|
import { Movie, kenBurns, withFade } from 'pixi-effects';
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
var chunkWHNADXXG_cjs = require('./chunk-WHNADXXG.cjs');
|
|
4
|
-
require('./chunk-ZVUO325O.cjs');
|
|
5
|
-
require('./chunk-2DXPTKQZ.cjs');
|
|
6
|
-
require('./chunk-LYOKH7KE.cjs');
|
|
7
|
-
require('./chunk-TWZNOUM7.cjs');
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
Object.defineProperty(exports, "CompositionSequence", {
|
|
12
|
-
enumerable: true,
|
|
13
|
-
get: function () { return chunkWHNADXXG_cjs.CompositionSequence; }
|
|
14
|
-
});
|
|
15
|
-
//# sourceMappingURL=Composition-MUKR3FYD.cjs.map
|
|
16
|
-
//# sourceMappingURL=Composition-MUKR3FYD.cjs.map
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
export { CompositionSequence } from './chunk-A3Q3SDSH.js';
|
|
2
|
-
import './chunk-ZAXYPXKW.js';
|
|
3
|
-
import './chunk-4KB3GUEK.js';
|
|
4
|
-
import './chunk-RCVZX2JP.js';
|
|
5
|
-
import './chunk-BXOCEZ4U.js';
|
|
6
|
-
//# sourceMappingURL=Composition-RJGA7KDU.js.map
|
|
7
|
-
//# sourceMappingURL=Composition-RJGA7KDU.js.map
|