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.
Files changed (63) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/README.md +6 -5
  3. package/ai/chat-template.html +3 -3
  4. package/ai/tools/check.mjs +42 -2
  5. package/ai/tools/lookup.mjs +12 -6
  6. package/ai/tools/render.mjs +15 -2
  7. package/ai/tools/varArgs.mjs +75 -0
  8. package/dist/{Base-CB1_FPZD.d.cts → Base-BKagtWc0.d.cts} +1 -1
  9. package/dist/{Base-Ne74ztNl.d.ts → Base-jRiMHcWT.d.ts} +1 -1
  10. package/dist/Composition-DQCPJPFE.js +7 -0
  11. package/dist/{Composition-RJGA7KDU.js.map → Composition-DQCPJPFE.js.map} +1 -1
  12. package/dist/Composition-V2XYSW6P.cjs +16 -0
  13. package/dist/{Composition-MUKR3FYD.cjs.map → Composition-V2XYSW6P.cjs.map} +1 -1
  14. package/dist/Controller.cjs +42 -1
  15. package/dist/Controller.cjs.map +1 -1
  16. package/dist/Controller.d.cts +10 -2
  17. package/dist/Controller.d.ts +10 -2
  18. package/dist/Controller.js +42 -1
  19. package/dist/Controller.js.map +1 -1
  20. package/dist/{Movie-DdnpLpw1.d.cts → Movie-BH81stkb.d.cts} +90 -3
  21. package/dist/{Movie-C2rZFkrO.d.ts → Movie-CfFPItoL.d.ts} +90 -3
  22. package/dist/{chunk-A3Q3SDSH.js → chunk-BGMCZA36.js} +3 -3
  23. package/dist/{chunk-A3Q3SDSH.js.map → chunk-BGMCZA36.js.map} +1 -1
  24. package/dist/{chunk-ZVUO325O.cjs → chunk-DJ5B7IKT.cjs} +9 -3
  25. package/dist/chunk-DJ5B7IKT.cjs.map +1 -0
  26. package/dist/{chunk-WHNADXXG.cjs → chunk-L73KRFXI.cjs} +61 -61
  27. package/dist/{chunk-WHNADXXG.cjs.map → chunk-L73KRFXI.cjs.map} +1 -1
  28. package/dist/{chunk-ZAXYPXKW.js → chunk-MMTESMJ5.js} +9 -4
  29. package/dist/chunk-MMTESMJ5.js.map +1 -0
  30. package/dist/{chunk-CRQFDY7E.js → chunk-UTIBYMIO.js} +461 -56
  31. package/dist/chunk-UTIBYMIO.js.map +1 -0
  32. package/dist/{chunk-HREBQAY4.cjs → chunk-X3WHETQ3.cjs} +485 -80
  33. package/dist/chunk-X3WHETQ3.cjs.map +1 -0
  34. package/dist/index.cjs +31 -31
  35. package/dist/index.d.cts +5 -5
  36. package/dist/index.d.ts +5 -5
  37. package/dist/index.js +3 -3
  38. package/dist/loader.css +1 -1
  39. package/dist/present.cjs +3 -3
  40. package/dist/present.d.cts +2 -2
  41. package/dist/present.d.ts +2 -2
  42. package/dist/present.js +3 -3
  43. package/dist/three.cjs +5 -5
  44. package/dist/three.d.cts +2 -2
  45. package/dist/three.d.ts +2 -2
  46. package/dist/three.js +1 -1
  47. package/dist/{types-BzIB65Vk.d.cts → types-CK_crNAI.d.cts} +2 -0
  48. package/dist/{types-BzIB65Vk.d.ts → types-CK_crNAI.d.ts} +2 -0
  49. package/docs/api.md +28 -1
  50. package/docs/dsl.md +32 -1
  51. package/llms-full.txt +145 -11
  52. package/package.json +83 -20
  53. package/skills/pixi-effects/SKILL.md +1 -1
  54. package/skills/pixi-effects/reference/cheatsheet.md +3 -0
  55. package/skills/pixi-effects/reference/pitfalls.md +4 -0
  56. package/skills/pixi-effects/reference/recipes.md +79 -8
  57. package/skills/pixi-effects/template.html +6 -6
  58. package/dist/Composition-MUKR3FYD.cjs +0 -16
  59. package/dist/Composition-RJGA7KDU.js +0 -7
  60. package/dist/chunk-CRQFDY7E.js.map +0 -1
  61. package/dist/chunk-HREBQAY4.cjs.map +0 -1
  62. package/dist/chunk-ZAXYPXKW.js.map +0 -1
  63. 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 * 0.12;
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: 4 - at, to: { fillColor: '#ffd166' }, duration: 0.5 }] : [{ at: 4 - at, to: { alpha: 0.45 }, duration: 0.5 }]), // highlight the maximum at t = 4 s
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: 4.2, initial: { x: cx, y: cy, fillColor: '#ffd166', alpha: 0 },
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: 4.2, initial: { x: cx, y: cy + 32, fillColor: '#ffd166', alpha: 0 },
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: 4.2, style: { fontSize: FS, fontWeight: 'bold', fill: '#1b1b2f' }, initial: { x: cx, y: cy, anchorX: 0.5, anchorY: 0.5, alpha: 0 },
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.30.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 -->
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.30.0/dist/index.js",
41
- "pixi-effects/controller": "https://cdn.jsdelivr.net/npm/pixi-effects@0.30.0/dist/Controller.js",
42
- "pixi-effects/present": "https://cdn.jsdelivr.net/npm/pixi-effects@0.30.0/dist/present.js",
43
- "pixi-effects/three": "https://cdn.jsdelivr.net/npm/pixi-effects@0.30.0/dist/three.js",
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.30.0/dist/index.js" /> <!-- after the import map: the library downloads while the page is parsed -->
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