pixi-effects 0.46.0 → 0.47.1

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 (70) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/README.md +5 -5
  3. package/ai/chat-template.html +3 -3
  4. package/ai/tools/check.mjs +2 -2
  5. package/ai/tools/lookup.mjs +6 -1
  6. package/ai/tools/pixi-effects.mjs +1 -0
  7. package/ai/tools/render.mjs +25 -2
  8. package/dist/Composition-AZEATHJB.cjs +16 -0
  9. package/dist/{Composition-PME3P5LP.cjs.map → Composition-AZEATHJB.cjs.map} +1 -1
  10. package/dist/Composition-ME3L3EFT.js +7 -0
  11. package/dist/{Composition-NSY7A32Y.js.map → Composition-ME3L3EFT.js.map} +1 -1
  12. package/dist/{Spans-2TZCNREQ.cjs → Spans-LTKEUHDN.cjs} +11 -11
  13. package/dist/{Spans-2TZCNREQ.cjs.map → Spans-LTKEUHDN.cjs.map} +1 -1
  14. package/dist/{Spans-TESJPQKX.js → Spans-YV7K4X5I.js} +6 -6
  15. package/dist/{Spans-TESJPQKX.js.map → Spans-YV7K4X5I.js.map} +1 -1
  16. package/dist/{blendModes-FMYO6SQN.js → blendModes-56NHK2MP.js} +4 -4
  17. package/dist/{blendModes-FMYO6SQN.js.map → blendModes-56NHK2MP.js.map} +1 -1
  18. package/dist/{blendModes-FD3THLNC.cjs → blendModes-ONOPMMAB.cjs} +5 -5
  19. package/dist/{blendModes-FD3THLNC.cjs.map → blendModes-ONOPMMAB.cjs.map} +1 -1
  20. package/dist/{chunk-LALEBV63.cjs → chunk-4NOTVQ3I.cjs} +29 -29
  21. package/dist/{chunk-LALEBV63.cjs.map → chunk-4NOTVQ3I.cjs.map} +1 -1
  22. package/dist/{chunk-JN7JHNZR.cjs → chunk-53DXJEO7.cjs} +5 -5
  23. package/dist/{chunk-JN7JHNZR.cjs.map → chunk-53DXJEO7.cjs.map} +1 -1
  24. package/dist/{chunk-DM2OLQOJ.js → chunk-6MHJKCXO.js} +5 -5
  25. package/dist/{chunk-DM2OLQOJ.js.map → chunk-6MHJKCXO.js.map} +1 -1
  26. package/dist/{chunk-K24FF4BT.js → chunk-7IDEWQPH.js} +19 -5
  27. package/dist/chunk-7IDEWQPH.js.map +1 -0
  28. package/dist/{chunk-DKCMJILW.cjs → chunk-BFERTUXK.cjs} +92 -92
  29. package/dist/{chunk-DKCMJILW.cjs.map → chunk-BFERTUXK.cjs.map} +1 -1
  30. package/dist/{chunk-3SAS3ZE5.cjs → chunk-NEMWRJQS.cjs} +11 -2
  31. package/dist/chunk-NEMWRJQS.cjs.map +1 -0
  32. package/dist/{chunk-32MMNDDN.js → chunk-NVKHUAHU.js} +11 -2
  33. package/dist/chunk-NVKHUAHU.js.map +1 -0
  34. package/dist/{chunk-2E74SP63.js → chunk-OVTTCX2E.js} +105 -25
  35. package/dist/chunk-OVTTCX2E.js.map +1 -0
  36. package/dist/{chunk-FPV3NPYC.js → chunk-QMVK55LC.js} +5 -5
  37. package/dist/{chunk-FPV3NPYC.js.map → chunk-QMVK55LC.js.map} +1 -1
  38. package/dist/{chunk-O66JGPVV.cjs → chunk-SMHTOWXG.cjs} +19 -5
  39. package/dist/chunk-SMHTOWXG.cjs.map +1 -0
  40. package/dist/{chunk-VNP7QCJE.js → chunk-UTDELFOU.js} +4 -4
  41. package/dist/{chunk-VNP7QCJE.js.map → chunk-UTDELFOU.js.map} +1 -1
  42. package/dist/{chunk-7M63Y6MO.cjs → chunk-YXIEPTFG.cjs} +150 -70
  43. package/dist/chunk-YXIEPTFG.cjs.map +1 -0
  44. package/dist/index.cjs +37 -37
  45. package/dist/index.d.cts +2 -0
  46. package/dist/index.d.ts +2 -0
  47. package/dist/index.js +6 -6
  48. package/dist/loader.css +1 -1
  49. package/dist/present.cjs +6 -6
  50. package/dist/present.js +6 -6
  51. package/dist/sound.cjs +2 -2
  52. package/dist/sound.js +1 -1
  53. package/dist/three.cjs +6 -6
  54. package/dist/three.js +2 -2
  55. package/docs/api.md +1 -1
  56. package/docs/dsl.md +2 -2
  57. package/llms-full.txt +8 -7
  58. package/package.json +1 -1
  59. package/skills/pixi-effects/SKILL.md +2 -1
  60. package/skills/pixi-effects/reference/cheatsheet.md +2 -2
  61. package/skills/pixi-effects/reference/recipes.md +1 -1
  62. package/skills/pixi-effects/template.html +6 -6
  63. package/dist/Composition-NSY7A32Y.js +0 -7
  64. package/dist/Composition-PME3P5LP.cjs +0 -16
  65. package/dist/chunk-2E74SP63.js.map +0 -1
  66. package/dist/chunk-32MMNDDN.js.map +0 -1
  67. package/dist/chunk-3SAS3ZE5.cjs.map +0 -1
  68. package/dist/chunk-7M63Y6MO.cjs.map +0 -1
  69. package/dist/chunk-K24FF4BT.js.map +0 -1
  70. package/dist/chunk-O66JGPVV.cjs.map +0 -1
package/dist/index.cjs CHANGED
@@ -1,138 +1,138 @@
1
1
  'use strict';
2
2
 
3
- var chunk7M63Y6MO_cjs = require('./chunk-7M63Y6MO.cjs');
4
- require('./chunk-DKCMJILW.cjs');
5
- var chunkLALEBV63_cjs = require('./chunk-LALEBV63.cjs');
6
- var chunkO66JGPVV_cjs = require('./chunk-O66JGPVV.cjs');
7
- require('./chunk-JN7JHNZR.cjs');
8
- require('./chunk-3SAS3ZE5.cjs');
3
+ var chunkYXIEPTFG_cjs = require('./chunk-YXIEPTFG.cjs');
4
+ require('./chunk-BFERTUXK.cjs');
5
+ var chunk4NOTVQ3I_cjs = require('./chunk-4NOTVQ3I.cjs');
6
+ var chunkSMHTOWXG_cjs = require('./chunk-SMHTOWXG.cjs');
7
+ require('./chunk-53DXJEO7.cjs');
8
+ require('./chunk-NEMWRJQS.cjs');
9
9
  var chunkTWZNOUM7_cjs = require('./chunk-TWZNOUM7.cjs');
10
10
 
11
11
 
12
12
 
13
13
  Object.defineProperty(exports, "Movie", {
14
14
  enumerable: true,
15
- get: function () { return chunk7M63Y6MO_cjs.Movie; }
15
+ get: function () { return chunkYXIEPTFG_cjs.Movie; }
16
16
  });
17
17
  Object.defineProperty(exports, "animateText", {
18
18
  enumerable: true,
19
- get: function () { return chunk7M63Y6MO_cjs.animateText; }
19
+ get: function () { return chunkYXIEPTFG_cjs.animateText; }
20
20
  });
21
21
  Object.defineProperty(exports, "audioEnvelope", {
22
22
  enumerable: true,
23
- get: function () { return chunk7M63Y6MO_cjs.audioEnvelope; }
23
+ get: function () { return chunkYXIEPTFG_cjs.audioEnvelope; }
24
24
  });
25
25
  Object.defineProperty(exports, "bpmEnvelope", {
26
26
  enumerable: true,
27
- get: function () { return chunk7M63Y6MO_cjs.bpmEnvelope; }
27
+ get: function () { return chunkYXIEPTFG_cjs.bpmEnvelope; }
28
28
  });
29
29
  Object.defineProperty(exports, "cameraPath", {
30
30
  enumerable: true,
31
- get: function () { return chunk7M63Y6MO_cjs.cameraPath; }
31
+ get: function () { return chunkYXIEPTFG_cjs.cameraPath; }
32
32
  });
33
33
  Object.defineProperty(exports, "computeEnvelope", {
34
34
  enumerable: true,
35
- get: function () { return chunk7M63Y6MO_cjs.computeEnvelope; }
35
+ get: function () { return chunkYXIEPTFG_cjs.computeEnvelope; }
36
36
  });
37
37
  Object.defineProperty(exports, "dismissLoader", {
38
38
  enumerable: true,
39
- get: function () { return chunk7M63Y6MO_cjs.dismissLoader; }
39
+ get: function () { return chunkYXIEPTFG_cjs.dismissLoader; }
40
40
  });
41
41
  Object.defineProperty(exports, "ease", {
42
42
  enumerable: true,
43
- get: function () { return chunk7M63Y6MO_cjs.parseEase; }
43
+ get: function () { return chunkYXIEPTFG_cjs.parseEase; }
44
44
  });
45
45
  Object.defineProperty(exports, "failLoader", {
46
46
  enumerable: true,
47
- get: function () { return chunk7M63Y6MO_cjs.failLoader; }
47
+ get: function () { return chunkYXIEPTFG_cjs.failLoader; }
48
48
  });
49
49
  Object.defineProperty(exports, "followPath", {
50
50
  enumerable: true,
51
- get: function () { return chunk7M63Y6MO_cjs.followPath; }
51
+ get: function () { return chunkYXIEPTFG_cjs.followPath; }
52
52
  });
53
53
  Object.defineProperty(exports, "kenBurns", {
54
54
  enumerable: true,
55
- get: function () { return chunk7M63Y6MO_cjs.kenBurns; }
55
+ get: function () { return chunkYXIEPTFG_cjs.kenBurns; }
56
56
  });
57
57
  Object.defineProperty(exports, "measureText", {
58
58
  enumerable: true,
59
- get: function () { return chunk7M63Y6MO_cjs.measureText; }
59
+ get: function () { return chunkYXIEPTFG_cjs.measureText; }
60
60
  });
61
61
  Object.defineProperty(exports, "musicBuffer", {
62
62
  enumerable: true,
63
- get: function () { return chunk7M63Y6MO_cjs.musicBuffer; }
63
+ get: function () { return chunkYXIEPTFG_cjs.musicBuffer; }
64
64
  });
65
65
  Object.defineProperty(exports, "musicEnvelope", {
66
66
  enumerable: true,
67
- get: function () { return chunk7M63Y6MO_cjs.musicEnvelope; }
67
+ get: function () { return chunkYXIEPTFG_cjs.musicEnvelope; }
68
68
  });
69
69
  Object.defineProperty(exports, "musicEvents", {
70
70
  enumerable: true,
71
- get: function () { return chunk7M63Y6MO_cjs.musicEvents; }
71
+ get: function () { return chunkYXIEPTFG_cjs.musicEvents; }
72
72
  });
73
73
  Object.defineProperty(exports, "orbit", {
74
74
  enumerable: true,
75
- get: function () { return chunk7M63Y6MO_cjs.orbit; }
75
+ get: function () { return chunkYXIEPTFG_cjs.orbit; }
76
76
  });
77
77
  Object.defineProperty(exports, "particles", {
78
78
  enumerable: true,
79
- get: function () { return chunk7M63Y6MO_cjs.particles; }
79
+ get: function () { return chunkYXIEPTFG_cjs.particles; }
80
80
  });
81
81
  Object.defineProperty(exports, "react", {
82
82
  enumerable: true,
83
- get: function () { return chunk7M63Y6MO_cjs.react; }
83
+ get: function () { return chunkYXIEPTFG_cjs.react; }
84
84
  });
85
85
  Object.defineProperty(exports, "resolveLoader", {
86
86
  enumerable: true,
87
- get: function () { return chunk7M63Y6MO_cjs.resolveLoader; }
87
+ get: function () { return chunkYXIEPTFG_cjs.resolveLoader; }
88
88
  });
89
89
  Object.defineProperty(exports, "samplePath", {
90
90
  enumerable: true,
91
- get: function () { return chunk7M63Y6MO_cjs.samplePath; }
91
+ get: function () { return chunkYXIEPTFG_cjs.samplePath; }
92
92
  });
93
93
  Object.defineProperty(exports, "splitText", {
94
94
  enumerable: true,
95
- get: function () { return chunk7M63Y6MO_cjs.splitText; }
95
+ get: function () { return chunkYXIEPTFG_cjs.splitText; }
96
96
  });
97
97
  Object.defineProperty(exports, "stagger", {
98
98
  enumerable: true,
99
- get: function () { return chunk7M63Y6MO_cjs.stagger; }
99
+ get: function () { return chunkYXIEPTFG_cjs.stagger; }
100
100
  });
101
101
  Object.defineProperty(exports, "wiggle", {
102
102
  enumerable: true,
103
- get: function () { return chunk7M63Y6MO_cjs.wiggle; }
103
+ get: function () { return chunkYXIEPTFG_cjs.wiggle; }
104
104
  });
105
105
  Object.defineProperty(exports, "withFade", {
106
106
  enumerable: true,
107
- get: function () { return chunk7M63Y6MO_cjs.withFade; }
107
+ get: function () { return chunkYXIEPTFG_cjs.withFade; }
108
108
  });
109
109
  Object.defineProperty(exports, "Sequence", {
110
110
  enumerable: true,
111
- get: function () { return chunkLALEBV63_cjs.Sequence; }
111
+ get: function () { return chunk4NOTVQ3I_cjs.Sequence; }
112
112
  });
113
113
  Object.defineProperty(exports, "noise", {
114
114
  enumerable: true,
115
- get: function () { return chunkLALEBV63_cjs.noise; }
115
+ get: function () { return chunk4NOTVQ3I_cjs.noise; }
116
116
  });
117
117
  Object.defineProperty(exports, "noise2", {
118
118
  enumerable: true,
119
- get: function () { return chunkLALEBV63_cjs.noise2; }
119
+ get: function () { return chunk4NOTVQ3I_cjs.noise2; }
120
120
  });
121
121
  Object.defineProperty(exports, "rand", {
122
122
  enumerable: true,
123
- get: function () { return chunkLALEBV63_cjs.rand; }
123
+ get: function () { return chunk4NOTVQ3I_cjs.rand; }
124
124
  });
125
125
  Object.defineProperty(exports, "random", {
126
126
  enumerable: true,
127
- get: function () { return chunkLALEBV63_cjs.random; }
127
+ get: function () { return chunk4NOTVQ3I_cjs.random; }
128
128
  });
129
129
  Object.defineProperty(exports, "registerSequenceType", {
130
130
  enumerable: true,
131
- get: function () { return chunkLALEBV63_cjs.registerSequenceType; }
131
+ get: function () { return chunk4NOTVQ3I_cjs.registerSequenceType; }
132
132
  });
133
133
  Object.defineProperty(exports, "inspectSound", {
134
134
  enumerable: true,
135
- get: function () { return chunkO66JGPVV_cjs.inspectSound; }
135
+ get: function () { return chunkSMHTOWXG_cjs.inspectSound; }
136
136
  });
137
137
  Object.defineProperty(exports, "warnUnknownOptions", {
138
138
  enumerable: true,
package/dist/index.d.cts CHANGED
@@ -449,6 +449,8 @@ interface AnimateTextTween {
449
449
  ease?: string;
450
450
  /** `out` only: the composition second the exit starts at (every piece leaves in the same wave from then on, and holds its exit state until the layer ends). Without it the exit is the end of the layer run backwards. Not for `in` (an entrance starts with its piece: the options' `at`). */
451
451
  at?: number;
452
+ /** `out` only: the wave of the exit, when it should not be the entrance's: `0` (all pieces leave at once), or `{ each: 0.1 }` and the other {@link stagger} options. Default: the same wave as the entrance (the options' `stagger`). */
453
+ stagger?: StaggerOptions | 0;
452
454
  }
453
455
  interface AnimateTextOptions {
454
456
  /** What a piece is: a character (default), a word or a line. */
package/dist/index.d.ts CHANGED
@@ -449,6 +449,8 @@ interface AnimateTextTween {
449
449
  ease?: string;
450
450
  /** `out` only: the composition second the exit starts at (every piece leaves in the same wave from then on, and holds its exit state until the layer ends). Without it the exit is the end of the layer run backwards. Not for `in` (an entrance starts with its piece: the options' `at`). */
451
451
  at?: number;
452
+ /** `out` only: the wave of the exit, when it should not be the entrance's: `0` (all pieces leave at once), or `{ each: 0.1 }` and the other {@link stagger} options. Default: the same wave as the entrance (the options' `stagger`). */
453
+ stagger?: StaggerOptions | 0;
452
454
  }
453
455
  interface AnimateTextOptions {
454
456
  /** What a piece is: a character (default), a word or a line. */
package/dist/index.js CHANGED
@@ -1,9 +1,9 @@
1
- export { Movie, animateText, audioEnvelope, bpmEnvelope, cameraPath, computeEnvelope, dismissLoader, parseEase as ease, failLoader, followPath, kenBurns, measureText, musicBuffer, musicEnvelope, musicEvents, orbit, particles, react, resolveLoader, samplePath, splitText, stagger, wiggle, withFade } from './chunk-2E74SP63.js';
2
- import './chunk-FPV3NPYC.js';
3
- export { Sequence, noise, noise2, rand, random, registerSequenceType } from './chunk-DM2OLQOJ.js';
4
- export { inspectSound } from './chunk-K24FF4BT.js';
5
- import './chunk-VNP7QCJE.js';
6
- import './chunk-32MMNDDN.js';
1
+ export { Movie, animateText, audioEnvelope, bpmEnvelope, cameraPath, computeEnvelope, dismissLoader, parseEase as ease, failLoader, followPath, kenBurns, measureText, musicBuffer, musicEnvelope, musicEvents, orbit, particles, react, resolveLoader, samplePath, splitText, stagger, wiggle, withFade } from './chunk-OVTTCX2E.js';
2
+ import './chunk-QMVK55LC.js';
3
+ export { Sequence, noise, noise2, rand, random, registerSequenceType } from './chunk-6MHJKCXO.js';
4
+ export { inspectSound } from './chunk-7IDEWQPH.js';
5
+ import './chunk-UTDELFOU.js';
6
+ import './chunk-NVKHUAHU.js';
7
7
  export { warnUnknownOptions } from './chunk-BXOCEZ4U.js';
8
8
  //# sourceMappingURL=index.js.map
9
9
  //# sourceMappingURL=index.js.map
package/dist/loader.css CHANGED
@@ -1,7 +1,7 @@
1
1
  /*
2
2
  * pixi-effects loader: one line of HTML on the page, a few lines of CSS, no JavaScript of your own.
3
3
  *
4
- * <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/pixi-effects@0.46.0/dist/loader.css">
4
+ * <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/pixi-effects@0.47.1/dist/loader.css">
5
5
  * <div class="stage" style="position: relative"> <!-- any box the canvas sits in, with position: relative -->
6
6
  * <canvas id="stage" width="1280" height="720"></canvas>
7
7
  * <div class="pe-loader" data-label="LOADING"></div> <!-- or class="pe-loader pe-loader--bar" -->
package/dist/present.cjs CHANGED
@@ -1,11 +1,11 @@
1
1
  'use strict';
2
2
 
3
- require('./chunk-7M63Y6MO.cjs');
4
- require('./chunk-DKCMJILW.cjs');
5
- require('./chunk-LALEBV63.cjs');
6
- require('./chunk-O66JGPVV.cjs');
7
- require('./chunk-JN7JHNZR.cjs');
8
- require('./chunk-3SAS3ZE5.cjs');
3
+ require('./chunk-YXIEPTFG.cjs');
4
+ require('./chunk-BFERTUXK.cjs');
5
+ require('./chunk-4NOTVQ3I.cjs');
6
+ require('./chunk-SMHTOWXG.cjs');
7
+ require('./chunk-53DXJEO7.cjs');
8
+ require('./chunk-NEMWRJQS.cjs');
9
9
  var chunkTWZNOUM7_cjs = require('./chunk-TWZNOUM7.cjs');
10
10
 
11
11
  // src/present/stops.ts
package/dist/present.js CHANGED
@@ -1,9 +1,9 @@
1
- import './chunk-2E74SP63.js';
2
- import './chunk-FPV3NPYC.js';
3
- import './chunk-DM2OLQOJ.js';
4
- import './chunk-K24FF4BT.js';
5
- import './chunk-VNP7QCJE.js';
6
- import './chunk-32MMNDDN.js';
1
+ import './chunk-OVTTCX2E.js';
2
+ import './chunk-QMVK55LC.js';
3
+ import './chunk-6MHJKCXO.js';
4
+ import './chunk-7IDEWQPH.js';
5
+ import './chunk-UTDELFOU.js';
6
+ import './chunk-NVKHUAHU.js';
7
7
  import { warnUnknownOptions } from './chunk-BXOCEZ4U.js';
8
8
 
9
9
  // src/present/stops.ts
package/dist/sound.cjs CHANGED
@@ -1,12 +1,12 @@
1
1
  'use strict';
2
2
 
3
- var chunkO66JGPVV_cjs = require('./chunk-O66JGPVV.cjs');
3
+ var chunkSMHTOWXG_cjs = require('./chunk-SMHTOWXG.cjs');
4
4
 
5
5
 
6
6
 
7
7
  Object.defineProperty(exports, "inspectSound", {
8
8
  enumerable: true,
9
- get: function () { return chunkO66JGPVV_cjs.inspectSound; }
9
+ get: function () { return chunkSMHTOWXG_cjs.inspectSound; }
10
10
  });
11
11
  //# sourceMappingURL=sound.cjs.map
12
12
  //# sourceMappingURL=sound.cjs.map
package/dist/sound.js CHANGED
@@ -1,3 +1,3 @@
1
- export { inspectSound } from './chunk-K24FF4BT.js';
1
+ export { inspectSound } from './chunk-7IDEWQPH.js';
2
2
  //# sourceMappingURL=sound.js.map
3
3
  //# sourceMappingURL=sound.js.map
package/dist/three.cjs CHANGED
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
- var chunkLALEBV63_cjs = require('./chunk-LALEBV63.cjs');
4
- require('./chunk-3SAS3ZE5.cjs');
3
+ var chunk4NOTVQ3I_cjs = require('./chunk-4NOTVQ3I.cjs');
4
+ require('./chunk-NEMWRJQS.cjs');
5
5
  require('./chunk-TWZNOUM7.cjs');
6
6
  var pixi_js = require('pixi.js');
7
7
  var three$1 = require('three');
@@ -170,7 +170,7 @@ function releaseThree(kind) {
170
170
 
171
171
  // src/three/ThreeSequence.ts
172
172
  var NOT_PURE = /\bMath\s*\.\s*random\s*\(|\bDate\s*\.\s*now\s*\(|\bperformance\s*\.\s*now\s*\(|\bnew\s+Date\s*\(/;
173
- var ThreeSequence = class extends chunkLALEBV63_cjs.Sequence {
173
+ var ThreeSequence = class extends chunk4NOTVQ3I_cjs.Sequence {
174
174
  _renderer = null;
175
175
  /** The renderer this layer draws with (`'webgl'` or `'webgpu'`), once it is built. */
176
176
  renderer = null;
@@ -221,7 +221,7 @@ var ThreeSequence = class extends chunkLALEBV63_cjs.Sequence {
221
221
  if (typeof spec.update === "function" && NOT_PURE.test(spec.update.toString())) {
222
222
  console.warn(`pixi-effects: three layer${spec.name ? ` "${spec.name}"` : ""}: update calls Math.random, Date.now, performance.now or new Date, so it is not deterministic: the same frame would draw differently in playback, in a seek and in an export. Derive the value from t`);
223
223
  }
224
- const scope = chunkLALEBV63_cjs.buildScope(this, this.parent, this.root);
224
+ const scope = chunk4NOTVQ3I_cjs.buildScope(this, this.parent, this.root);
225
225
  const width = resolveDim(spec.width, scope) ?? this.parent?.width ?? this.root.width;
226
226
  const height = resolveDim(spec.height, scope) ?? this.parent?.height ?? this.root.height;
227
227
  const resolution = spec.resolution ?? 1;
@@ -784,7 +784,7 @@ var ThreeSequence = class extends chunkLALEBV63_cjs.Sequence {
784
784
  function resolveDim(v, scope) {
785
785
  if (v === void 0) return void 0;
786
786
  if (typeof v === "number") return v;
787
- return chunkLALEBV63_cjs.evaluateExpr(v, scope);
787
+ return chunk4NOTVQ3I_cjs.evaluateExpr(v, scope);
788
788
  }
789
789
  var ids = /* @__PURE__ */ new WeakMap();
790
790
  var lastId = 0;
@@ -1026,7 +1026,7 @@ function registerThree(options = {}) {
1026
1026
  const kind = options.renderer ?? "webgl";
1027
1027
  checkRendererKind(kind, "registerThree's renderer");
1028
1028
  setPageRenderer(kind);
1029
- chunkLALEBV63_cjs.registerSequenceType("three", ThreeSequence);
1029
+ chunk4NOTVQ3I_cjs.registerSequenceType("three", ThreeSequence);
1030
1030
  }
1031
1031
  function three(spec) {
1032
1032
  return spec;
package/dist/three.js CHANGED
@@ -1,5 +1,5 @@
1
- import { Sequence, buildScope, evaluateExpr, registerSequenceType } from './chunk-DM2OLQOJ.js';
2
- import './chunk-32MMNDDN.js';
1
+ import { Sequence, buildScope, evaluateExpr, registerSequenceType } from './chunk-6MHJKCXO.js';
2
+ import './chunk-NVKHUAHU.js';
3
3
  import './chunk-BXOCEZ4U.js';
4
4
  import { Texture, Sprite, TextureSource, RenderTexture, Matrix } from 'pixi.js';
5
5
  import { WebGLRenderer, Scene, PerspectiveCamera, Raycaster, Vector2, Vector3, Quaternion, Plane, Box3, RenderTarget, Group, AnimationMixer, LoopOnce, LoopRepeat, AmbientLight, DirectionalLight, SpotLight, PointLight, Color, SRGBColorSpace, Fog } from 'three';
package/docs/api.md CHANGED
@@ -44,7 +44,7 @@ interface MovieOptions {
44
44
  composition?: CompositionSpec; // root composition (see DSL reference)
45
45
  vars?: Record<string, number | string>; // named values the composition reads: a number as a name in an expression, any as {name} in a string (see Variables)
46
46
  loop?: boolean; // start with movie.loop on: playing goes back to the start at the end (default false, like a <video>)
47
- loudness?: number | false; // the whole mix at this many LUFS (-16 is usual for web video): one gain, up or down, the loudest sample kept at -1 dBFS or under; false / left out: the mix as the layers make it (check says when it is quiet)
47
+ loudness?: number | false; // the whole mix at this many LUFS (-16 is usual for web video): one gain, up or down, and a look-ahead limiter keeping the true peak at -1 dBTP or under (a click over a quiet bed is pulled down; more than 10 dB of limiting is out of reach and `check` names it); false / left out: the mix as the layers make it (check says when it is quiet)
48
48
  poster?: number; // seconds: the frame shown before play (negative: from the end), see movie.poster
49
49
  motionBlur?: boolean | number | { samples?: number; shutter?: number }; // blur for render() / snapshot() / contactSheet(), not live playback; see Motion blur
50
50
  loader?: HTMLElement | string | false; // the page's loader (see pixi-effects/loader.css): shows how far init is (a bar, "BUILDING LAYERS 62%"), faded out when ready, stopped if init fails; default: a .pe-loader next to the canvas
package/docs/dsl.md CHANGED
@@ -224,7 +224,7 @@ Audio-only sequence. No visual. Volume is animatable via keyframes.
224
224
 
225
225
  Audio is mixed during `Movie.init()` and during `Movie.render()`. Volume keyframes interpolate linearly.
226
226
 
227
- **The loudness of the whole mix: `movie.init({ loudness: -16 })`.** Layers make whatever level their `volume` gives (a few `sfx` at volume 0.15 mix to about -40 LUFS, far quieter than web video's -14 to -16). `loudness` is a target for the finished mix in LUFS (ITU-R BS.1770): one gain, up or down, applied before the limiter and the same for playback and export. The loudest sample may not pass -1 dBFS, so a mix with one loud click in a quiet bed stops short of the target, and `check` / `inspectAudio()` say so (`reached -19.4 LUFS`) with the fix (lower that sound, or ask for less). `false` or no option leaves the mix as the layers make it; `check` then says when it is quiet (found in a trial of nine films with sound: eight were -22 to -26 LUFS and only one was raised).
227
+ **The loudness of the whole mix: `movie.init({ loudness: -16 })`.** Layers make whatever level their `volume` gives (a few `sfx` at volume 0.15 mix to about -40 LUFS, far quieter than web video's -14 to -16). `loudness` is a target for the finished mix in LUFS (ITU-R BS.1770): one gain, up or down, and a look-ahead limiter that takes a click or a hit on top of a quiet bed down so that no peak passes -1 dBTP (true peak), the same for playback and export. A mix whose peaks are within 10 dB of what the limiter can take reaches the target (`check` says where the limiter worked hardest and which sounds play there when it took 3 dB or more off); a click 30 dB over its bed is out of reach and stops short (`reached -19.4 LUFS`), and `check` / `inspectAudio()` name the moment and the sounds that make it, with the fix (lower that sound, or ask for less). **Raising a `sfx` over music is safe:** the limiter keeps the peak, so give an accent the volume the `buried` note suggests (an accent over music needs 2 to 3). `false` or no option leaves the mix as the layers make it; `check` then says when it is quiet (found in a trial of nine films with sound: eight were -22 to -26 LUFS and only one was raised).
228
228
 
229
229
  #### Sound effects without files: `sfx`
230
230
 
@@ -867,7 +867,7 @@ three({ type: 'three', width: 'GW', height: 'GH',
867
867
 
868
868
  - **The rules.** The layer must be a `composition` with a `width` and `height`, in the same composition as the three layer and **listed before it** (a layer built later cannot be found: the error lists the names that are there). `await` the call in `setup`. `resolution` (default 1) is the pixels of the texture per pixel of the layer: 2 for a screen that is seen large. `transparent: true` lets what is behind the mesh show through the empty and half-transparent parts of the composition (a sticker, a label with rounded corners; the material blends and does not write depth); default `false`, an opaque screen. The layer's own place, scale and rotation do not matter: its content is drawn from (0, 0) to (width, height); its alpha and filters do.
869
869
  - **The material.** `screen.material` is unlit (a screen shines by itself; scene lights do not shade it) and carries the colour decoding; put it on a mesh, or on one face of a box (`[dark, dark, dark, dark, screen.material, dark]`). Use `screen.texture` yourself and its colours may be wrong. Without `transparent`, give the composition a background rect: what it leaves empty is black on the mesh.
870
- - **Two routes, the same colours** (measured: the background, a moving dot and a growing bar equal the 2D layer, channel for channel, on both). When the three layer draws on PixiJS's own GPU device (`renderer: 'webgpu'` in a WebGPU movie, the default) the texture is PixiJS's own GPU texture used through three's `ExternalTexture`: no copy, 0.3 ms a frame, and the page's import map needs `"three/webgpu"` and `"three/tsl"` next to `"three"`. Otherwise (the layer has a canvas of its own with `shareGpu: false`, a `renderer: 'webgl'` layer, or PixiJS on WebGL) the pixels are read back into a canvas: 1.6 ms a frame at 480 x 270, 4 ms at 1280 x 720.
870
+ - **Two routes, the same colours** (measured: the background, a moving dot and a growing bar equal the 2D layer, channel for channel, on both). When the three layer has `renderer: 'webgpu'` and draws on PixiJS's own GPU device (a WebGPU movie) the texture is PixiJS's own GPU texture used through three's `ExternalTexture`: no copy, 0.3 ms a frame, and the page's import map needs `"three/webgpu"` and `"three/tsl"` next to `"three"` (the template's has only `"three"` and `"three/addons/"`: add the two lines for this route). Otherwise (the layer has the default `renderer: 'webgl'`, a canvas of its own with `shareGpu: false`, or PixiJS on WebGL) the pixels are read back into a canvas: 1.6 ms a frame at 480 x 270, 4 ms at 1280 x 720, and no import-map entry beyond the template's.
871
871
  - **A function of the time.** The 2D layer is drawn when the three layer draws, after the timeline has put every layer at the frame: a seek, an export and a repeat give the same picture (`check` draws the layer twice, in two orders, as for any three layer). A layer that has not started yet, or has ended, shows nothing on the mesh.
872
872
  - **A rebuild** (`setVars`, `resize`) keeps the three layer and shows the layer of the new tree (the text that changed shows on the mesh); a layer that changed size is drawn stretched to the texture.
873
873
  - **Seen small or at a slant** the texture is filtered, not aliased (1-pixel stripes on a plane a quarter of the picture come out as one even grey, not black and white stripes; four taps along a slant). The mip levels are PixiJS's own on the shared GPU, averaged in the stored sRGB values, so very fine bright detail seen very small is a little darker there than on a read-back canvas, whose mipmaps three makes in linear light (stripes average to 128 and 188).
package/llms-full.txt CHANGED
@@ -6,7 +6,7 @@ Concatenation of the skill, cheatsheet, pitfalls, recipes and the DSL / API / me
6
6
 
7
7
  <!-- ===== skills/pixi-effects/SKILL.md ===== -->
8
8
 
9
- > Written for pixi-effects 0.46.0. Update this skill with `npx skills update`; the newest docs are at https://yjmtmtk.github.io/pixi-effects/
9
+ > Written for pixi-effects 0.47.1. Update this skill with `npx skills update`; the newest docs are at https://yjmtmtk.github.io/pixi-effects/
10
10
 
11
11
  # pixi-effects — write a video as data
12
12
 
@@ -53,6 +53,7 @@ The same command checks a file you MADE before it goes somewhere with rules (a s
53
53
 
54
54
  - **Keyframe `at` starts at 0 when the layer appears** (negative = back from the layer's end). A layer's own `at` and a transition's `at` are composition time.
55
55
  - **Layers are hidden, not removed, after their lifespan; z-order is array order.** Switch scenes by stacking layers with `at`/`duration` over an opaque background rect.
56
+ - **Shell traps** (8 trial agents lost a command to these): zsh runs `echo ====` as a command (quote it, or leave the separator out); macOS `sed -i` needs an empty suffix, `sed -i '' …`.
56
57
  - **Angles are degrees. `+z` is toward the viewer. Numbers can be expressions** (`'GW/2 - w/2'`; no `pi`; no per-frame variable).
57
58
  - **Anchors**: rect/circle/ellipse are centred on `x,y`; text and image are top-left (use `anchorX/anchorY: 0.5`); compositions use `pivotX/pivotY`. A bar that grows from its base needs `anchorY: 1`.
58
59
  - **Sound effects need no files:** `{ type: 'audio', sfx: 'swoosh', at: 2 }` — presets `click pop swoosh swipe hit riser chime beep coin glitch typewriter`; knobs `pitch`, `brightness`, `seed`. The layer lasts as long as the sound (`duration` stretches it); a `riser` is loudest at its end (`at = hit − duration`), a `swoosh` 0.16 s in. A music file shorter than its layer goes silent — `loop: true`.
@@ -169,7 +170,7 @@ blendMode: 'normal' | 'add' | 'screen' | 'multiply' | 'overlay' | 'soft-light' |
169
170
 
170
171
  Animatable props (`initial` / keyframes): `x y alpha rotation scale scaleX scaleY pivotX pivotY anchorX anchorY skewX skewY tint width height visible autoAlpha`, plus for shapes their style/geometry, for text `fill`, for audio `volume`, for 3D `z rotationX rotationY`, filter params as `'filters.<name>.<param>'`, three objects as `'three.<obj>.<path>'`. **These go in `initial` / keyframes, never at the top level of a shape or text layer** (top-level `x`, `rotation`, `strokeColor`, `fillAlpha` each warn "is not a … key"): the top level of a shape holds its geometry (`width height radius d startAngle endAngle cornerRadius anchorX anchorY`), `trimStart` / `trimEnd`, `strokeCap` / `strokeJoin`, `fillGradient`.
171
172
 
172
- Keyframe kinds: `set` (jump at `at`; undone when you seek back), `to` (animate to), `from` (animate from), `from`+`to`; a value may be relative to where the layer stands (`from: { x: '-=36' }` slides in from 36 px to the left of its place). `from` / `from+to` show their start value from the layer's start, so a delayed fade-in needs no `initial.alpha`. `repeat` (finite extra plays), `yoyo`, `repeatDelay` loop any of them (endless repeats are not allowed). `ease` = an ease name (`'none'`, `'power2.out'`, `'expo.out'`, `'back.out(1.7)'`, `'elastic.out(1,0.5)'`, `'sine.inOut'`, `'bounce.out'`, **`'cubic-bezier(.4, 0, .2, 1)'`** (CSS syntax, x1 and x2 in 0…1; `.4, 0, .2, 1` is a gentle in-out; `.2, .8, .2, 1` is FRONT-LOADED, not slow motion: half of the change in the first 13 % of the time, 90 % by 39 %, 99 % by 74 %, then a long settle, so what sits at the END of the content gets the glide; `.2, .8, .5, 1` has a shorter tail). `power1…4` are the powers 2…5: `power2` is a cubic, `power2.in` has done only 12.5 % at half time (42 % at three quarters)… and springs: `'spring(mass, stiffness, damping)'` or `'spring.gentle' | 'snappy' | 'bouncy' | 'wobbly' | 'slow'`; **`duration: 'auto'` = the time the spring takes to settle** (presets, settle time / highest value reached as a multiple of the target: `snappy` 0.42 s / 1.01, `gentle` 0.72 s / 1.02, `bouncy` 1.08 s / 1.28, `slow` 1.29 s / 1.00, `wobbly` 1.61 s / 1.40: time the next beat from these) (real physics; with a number `duration` is the settle time and only the damping ratio shapes it; a damping ratio of 1 or more never overshoots, so `spring(1,170,26)` does not bounce: use damping below 20 at stiffness 170; `'auto'` works on keyframes only, not in `animateText` / `orbit` tweens; a `stagger` ease may be a spring, its delays are held inside the spread). Default ease is linear.
173
+ Keyframe kinds: `set` (jump at `at`; undone when you seek back), `to` (animate to), `from` (animate from), `from`+`to`; a value may be relative to where the layer stands (`from: { x: '-=36' }` slides in from 36 px to the left of its place). `from` / `from+to` show their start value from the layer's start, so a delayed fade-in needs no `initial.alpha`. `repeat` (finite extra plays), `yoyo`, `repeatDelay` loop any of them (endless repeats are not allowed: `repeat: -1` (the GSAP habit) is refused with a warning, write a count that covers the layer, `repeat: 40`; a `from` keyframe with a later `at` puts its start value on the layer from frame 0, the layer does not wait for the `at`). `ease` = an ease name (`'none'`, `'power2.out'`, `'expo.out'`, `'back.out(1.7)'`, `'elastic.out(1,0.5)'`, `'sine.inOut'`, `'bounce.out'`, **`'cubic-bezier(.4, 0, .2, 1)'`** (CSS syntax, x1 and x2 in 0…1; `.4, 0, .2, 1` is a gentle in-out; `.2, .8, .2, 1` is FRONT-LOADED, not slow motion: half of the change in the first 13 % of the time, 90 % by 39 %, 99 % by 74 %, then a long settle, so what sits at the END of the content gets the glide; `.2, .8, .5, 1` has a shorter tail). `power1…4` are the powers 2…5: `power2` is a cubic, `power2.in` has done only 12.5 % at half time (42 % at three quarters)… and springs: `'spring(mass, stiffness, damping)'` or `'spring.gentle' | 'snappy' | 'bouncy' | 'wobbly' | 'slow'`; **`duration: 'auto'` = the time the spring takes to settle** (presets, settle time / highest value reached as a multiple of the target: `snappy` 0.42 s / 1.01, `gentle` 0.72 s / 1.02, `bouncy` 1.08 s / 1.28, `slow` 1.29 s / 1.00, `wobbly` 1.61 s / 1.40: time the next beat from these) (real physics; with a number `duration` is the settle time and only the damping ratio shapes it; a damping ratio of 1 or more never overshoots, so `spring(1,170,26)` does not bounce: use damping below 20 at stiffness 170; `'auto'` works on keyframes only, not in `animateText` / `orbit` tweens; a `stagger` ease may be a spring, its delays are held inside the spread). Default ease is linear.
173
174
 
174
175
  ## Layer types
175
176
 
@@ -280,7 +281,7 @@ measureText('Hello', style) // → { width, height,
280
281
  splitText('Hello world', style, { by: 'chars' | 'words' | 'lines', x, y, align: 'left' | 'center' | 'right' })
281
282
  // → [{ text, x, y, width, index, line }] — one text layer per piece, same style, anchor top-left
282
283
  ```
283
- Per-letter / per-word / per-line animation in one call: `...animateText(text, style, { x, y, align, vertical: true /* tategaki: down a column, new line = new column to the left, x = RIGHT edge of the first column; 、。 ー 「」 placed and turned for you */, by: 'chars'|'words'|'lines', at, duration /* required: until the last piece is gone */, in: 'rise'|'drop'|'fade'|'pop'|'zoom'|'slide'|'spin'|{ from, to, duration, ease }|false, out: <same, in the same wave (leave `out` out and the pieces stay until the layer ends); by default BACKWARDS FROM THE LAYER'S END (at + duration, so to be gone at time T set duration = T − at); `out: { preset: 'fade', at: 6 }` starts the exit at composition second 6 instead>, stagger: { each, from, … }, idle: { y: 4, rotation: 1 }, styleFor: p => ({ fill }) })` → text layers `<name>-0…` (anchor centre; `x`/`y` in `from` are offsets) to spread into `sequences`. **`x` / `y` are pixel NUMBERS (an expression like `'GW/2'` throws: compute `W / 2`), and `y` is the TOP of the line box**: the visible text is centred `lineHeight / 2` below it, so an underline goes at `y + lineHeight`, not at `y` (recipe `animate-text`). By hand: `splitText(...)` + one layer per piece (recipe `split-text`). A web font must be loaded before measuring.
284
+ Per-letter / per-word / per-line animation in one call: `...animateText(text, style, { x, y, align, vertical: true /* tategaki: down a column, new line = new column to the left, x = RIGHT edge of the first column; 、。 ー 「」 placed and turned for you */, by: 'chars'|'words'|'lines', at, duration /* required: until the last piece is gone */, in: 'rise'|'drop'|'fade'|'pop'|'zoom'|'slide'|'spin'|{ from, to, duration, ease }|false, out: <same, in the same wave (leave `out` out and the pieces stay until the layer ends); by default BACKWARDS FROM THE LAYER'S END (at + duration, so to be gone at time T set duration = T − at); `out: { preset: 'fade', at: 6 }` starts the exit at composition second 6 instead; `out: { preset: 'fade', at: 6, stagger: 0 }` makes everything leave AT ONCE after it came in letter by letter (or `stagger: { each: 0.1 }`: its own wave); an exit is described by `from` = the state the piece leaves TO: `out: { from: { y: 18, alpha: 0 } }`; no exit: omit `out`, or `out: false` / `'none'`>, stagger: { each, from, … }, idle: { y: 4, rotation: 1 }, styleFor: p => ({ fill }) })` → text layers `<name>-0…` (anchor centre; `x`/`y` in `from` are offsets) to spread into `sequences`. **`x` / `y` are pixel NUMBERS (an expression like `'GW/2'` throws: compute `W / 2`), and `y` is the TOP of the line box**: the visible text is centred `lineHeight / 2` below it, so an underline goes at `y + lineHeight`, not at `y` (recipe `animate-text`). By hand: `splitText(...)` + one layer per piece (recipe `split-text`). A web font must be loaded before measuring.
284
285
 
285
286
  ## Assets
286
287
 
@@ -1858,7 +1859,7 @@ A moving, shadow-casting model is held to "a function of the time" by `check`: p
1858
1859
 
1859
1860
  ## An app or video on a phone, a television, a billboard (`threeLayerTexture`)
1860
1861
 
1861
- A 2D composition is the screen; a `three` layer holds the object it is on. The composition keeps animating (its own keyframes, text, charts, a counter) and the mesh shows it live, turning with the object. Put the composition BEFORE the three layer, give it a background rect, and `await threeLayerTexture(ctx, 'screen')` in `setup`: `screen.material` goes on the face of the mesh. The page's import map needs `"three/webgpu"` and `"three/tsl"` next to `"three"` (the template's has them when it has `three`).
1862
+ A 2D composition is the screen; a `three` layer holds the object it is on. The composition keeps animating (its own keyframes, text, charts, a counter) and the mesh shows it live, turning with the object. Put the composition BEFORE the three layer, give it a background rect, and `await threeLayerTexture(ctx, 'screen')` in `setup`: `screen.material` goes on the face of the mesh. The three layer as written here (the default `renderer`, WebGL) needs nothing beyond the template's import map: the screen's pixels are read back each frame. Only a layer with `renderer: 'webgpu'` (the no-copy route) also needs `"three/webgpu"` and `"three/tsl"` next to `"three"`.
1862
1863
 
1863
1864
  ```js
1864
1865
  // @docs-only three-screen
@@ -2533,7 +2534,7 @@ Audio-only sequence. No visual. Volume is animatable via keyframes.
2533
2534
 
2534
2535
  Audio is mixed during `Movie.init()` and during `Movie.render()`. Volume keyframes interpolate linearly.
2535
2536
 
2536
- **The loudness of the whole mix: `movie.init({ loudness: -16 })`.** Layers make whatever level their `volume` gives (a few `sfx` at volume 0.15 mix to about -40 LUFS, far quieter than web video's -14 to -16). `loudness` is a target for the finished mix in LUFS (ITU-R BS.1770): one gain, up or down, applied before the limiter and the same for playback and export. The loudest sample may not pass -1 dBFS, so a mix with one loud click in a quiet bed stops short of the target, and `check` / `inspectAudio()` say so (`reached -19.4 LUFS`) with the fix (lower that sound, or ask for less). `false` or no option leaves the mix as the layers make it; `check` then says when it is quiet (found in a trial of nine films with sound: eight were -22 to -26 LUFS and only one was raised).
2537
+ **The loudness of the whole mix: `movie.init({ loudness: -16 })`.** Layers make whatever level their `volume` gives (a few `sfx` at volume 0.15 mix to about -40 LUFS, far quieter than web video's -14 to -16). `loudness` is a target for the finished mix in LUFS (ITU-R BS.1770): one gain, up or down, and a look-ahead limiter that takes a click or a hit on top of a quiet bed down so that no peak passes -1 dBTP (true peak), the same for playback and export. A mix whose peaks are within 10 dB of what the limiter can take reaches the target (`check` says where the limiter worked hardest and which sounds play there when it took 3 dB or more off); a click 30 dB over its bed is out of reach and stops short (`reached -19.4 LUFS`), and `check` / `inspectAudio()` name the moment and the sounds that make it, with the fix (lower that sound, or ask for less). **Raising a `sfx` over music is safe:** the limiter keeps the peak, so give an accent the volume the `buried` note suggests (an accent over music needs 2 to 3). `false` or no option leaves the mix as the layers make it; `check` then says when it is quiet (found in a trial of nine films with sound: eight were -22 to -26 LUFS and only one was raised).
2537
2538
 
2538
2539
  #### Sound effects without files: `sfx`
2539
2540
 
@@ -3176,7 +3177,7 @@ three({ type: 'three', width: 'GW', height: 'GH',
3176
3177
 
3177
3178
  - **The rules.** The layer must be a `composition` with a `width` and `height`, in the same composition as the three layer and **listed before it** (a layer built later cannot be found: the error lists the names that are there). `await` the call in `setup`. `resolution` (default 1) is the pixels of the texture per pixel of the layer: 2 for a screen that is seen large. `transparent: true` lets what is behind the mesh show through the empty and half-transparent parts of the composition (a sticker, a label with rounded corners; the material blends and does not write depth); default `false`, an opaque screen. The layer's own place, scale and rotation do not matter: its content is drawn from (0, 0) to (width, height); its alpha and filters do.
3178
3179
  - **The material.** `screen.material` is unlit (a screen shines by itself; scene lights do not shade it) and carries the colour decoding; put it on a mesh, or on one face of a box (`[dark, dark, dark, dark, screen.material, dark]`). Use `screen.texture` yourself and its colours may be wrong. Without `transparent`, give the composition a background rect: what it leaves empty is black on the mesh.
3179
- - **Two routes, the same colours** (measured: the background, a moving dot and a growing bar equal the 2D layer, channel for channel, on both). When the three layer draws on PixiJS's own GPU device (`renderer: 'webgpu'` in a WebGPU movie, the default) the texture is PixiJS's own GPU texture used through three's `ExternalTexture`: no copy, 0.3 ms a frame, and the page's import map needs `"three/webgpu"` and `"three/tsl"` next to `"three"`. Otherwise (the layer has a canvas of its own with `shareGpu: false`, a `renderer: 'webgl'` layer, or PixiJS on WebGL) the pixels are read back into a canvas: 1.6 ms a frame at 480 x 270, 4 ms at 1280 x 720.
3180
+ - **Two routes, the same colours** (measured: the background, a moving dot and a growing bar equal the 2D layer, channel for channel, on both). When the three layer has `renderer: 'webgpu'` and draws on PixiJS's own GPU device (a WebGPU movie) the texture is PixiJS's own GPU texture used through three's `ExternalTexture`: no copy, 0.3 ms a frame, and the page's import map needs `"three/webgpu"` and `"three/tsl"` next to `"three"` (the template's has only `"three"` and `"three/addons/"`: add the two lines for this route). Otherwise (the layer has the default `renderer: 'webgl'`, a canvas of its own with `shareGpu: false`, or PixiJS on WebGL) the pixels are read back into a canvas: 1.6 ms a frame at 480 x 270, 4 ms at 1280 x 720, and no import-map entry beyond the template's.
3180
3181
  - **A function of the time.** The 2D layer is drawn when the three layer draws, after the timeline has put every layer at the frame: a seek, an export and a repeat give the same picture (`check` draws the layer twice, in two orders, as for any three layer). A layer that has not started yet, or has ended, shows nothing on the mesh.
3181
3182
  - **A rebuild** (`setVars`, `resize`) keeps the three layer and shows the layer of the new tree (the text that changed shows on the mesh); a layer that changed size is drawn stretched to the texture.
3182
3183
  - **Seen small or at a slant** the texture is filtered, not aliased (1-pixel stripes on a plane a quarter of the picture come out as one even grey, not black and white stripes; four taps along a slant). The mip levels are PixiJS's own on the shared GPU, averaged in the stored sRGB values, so very fine bright detail seen very small is a little darker there than on a read-back canvas, whose mipmaps three makes in linear light (stripes average to 128 and 188).
@@ -4211,7 +4212,7 @@ interface MovieOptions {
4211
4212
  composition?: CompositionSpec; // root composition (see DSL reference)
4212
4213
  vars?: Record<string, number | string>; // named values the composition reads: a number as a name in an expression, any as {name} in a string (see Variables)
4213
4214
  loop?: boolean; // start with movie.loop on: playing goes back to the start at the end (default false, like a <video>)
4214
- loudness?: number | false; // the whole mix at this many LUFS (-16 is usual for web video): one gain, up or down, the loudest sample kept at -1 dBFS or under; false / left out: the mix as the layers make it (check says when it is quiet)
4215
+ loudness?: number | false; // the whole mix at this many LUFS (-16 is usual for web video): one gain, up or down, and a look-ahead limiter keeping the true peak at -1 dBTP or under (a click over a quiet bed is pulled down; more than 10 dB of limiting is out of reach and `check` names it); false / left out: the mix as the layers make it (check says when it is quiet)
4215
4216
  poster?: number; // seconds: the frame shown before play (negative: from the end), see movie.poster
4216
4217
  motionBlur?: boolean | number | { samples?: number; shutter?: number }; // blur for render() / snapshot() / contactSheet(), not live playback; see Motion blur
4217
4218
  loader?: HTMLElement | string | false; // the page's loader (see pixi-effects/loader.css): shows how far init is (a bar, "BUILDING LAYERS 62%"), faded out when ready, stopped if init fails; default: a .pe-loader next to the canvas
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pixi-effects",
3
- "version": "0.46.0",
3
+ "version": "0.47.1",
4
4
  "description": "Declarative composition and video rendering for the web. Built on PixiJS v8.",
5
5
  "type": "module",
6
6
  "exports": {
@@ -3,7 +3,7 @@ name: pixi-effects
3
3
  description: Use when asked to make, animate, preview or export a video, motion graphics, an animated title, a lower third, kinetic type, a promo, a slideshow with transitions, an animated chart or data story, a social clip, a 2.5D / depth scene, or music and sound for a video, written as code in JavaScript or HTML. pixi-effects is a JS library where a video is a plain-object composition (text, shape, image, video, audio and file-free sfx / music, camera, light and shader layers with keyframes) that plays in the browser, is checked by a tool the agent can run, and exports MP4 / WebM / MOV with no server. Not for a project that already uses another video tool (Remotion, Motion Canvas, ffmpeg); follow that tool instead.
4
4
  ---
5
5
 
6
- > Written for pixi-effects 0.46.0. Update this skill with `npx skills update`; the newest docs are at https://yjmtmtk.github.io/pixi-effects/
6
+ > Written for pixi-effects 0.47.1. Update this skill with `npx skills update`; the newest docs are at https://yjmtmtk.github.io/pixi-effects/
7
7
 
8
8
  # pixi-effects — write a video as data
9
9
 
@@ -50,6 +50,7 @@ The same command checks a file you MADE before it goes somewhere with rules (a s
50
50
 
51
51
  - **Keyframe `at` starts at 0 when the layer appears** (negative = back from the layer's end). A layer's own `at` and a transition's `at` are composition time.
52
52
  - **Layers are hidden, not removed, after their lifespan; z-order is array order.** Switch scenes by stacking layers with `at`/`duration` over an opaque background rect.
53
+ - **Shell traps** (8 trial agents lost a command to these): zsh runs `echo ====` as a command (quote it, or leave the separator out); macOS `sed -i` needs an empty suffix, `sed -i '' …`.
53
54
  - **Angles are degrees. `+z` is toward the viewer. Numbers can be expressions** (`'GW/2 - w/2'`; no `pi`; no per-frame variable).
54
55
  - **Anchors**: rect/circle/ellipse are centred on `x,y`; text and image are top-left (use `anchorX/anchorY: 0.5`); compositions use `pivotX/pivotY`. A bar that grows from its base needs `anchorY: 1`.
55
56
  - **Sound effects need no files:** `{ type: 'audio', sfx: 'swoosh', at: 2 }` — presets `click pop swoosh swipe hit riser chime beep coin glitch typewriter`; knobs `pitch`, `brightness`, `seed`. The layer lasts as long as the sound (`duration` stretches it); a `riser` is loudest at its end (`at = hit − duration`), a `swoosh` 0.16 s in. A music file shorter than its layer goes silent — `loop: true`.
@@ -47,7 +47,7 @@ blendMode: 'normal' | 'add' | 'screen' | 'multiply' | 'overlay' | 'soft-light' |
47
47
 
48
48
  Animatable props (`initial` / keyframes): `x y alpha rotation scale scaleX scaleY pivotX pivotY anchorX anchorY skewX skewY tint width height visible autoAlpha`, plus for shapes their style/geometry, for text `fill`, for audio `volume`, for 3D `z rotationX rotationY`, filter params as `'filters.<name>.<param>'`, three objects as `'three.<obj>.<path>'`. **These go in `initial` / keyframes, never at the top level of a shape or text layer** (top-level `x`, `rotation`, `strokeColor`, `fillAlpha` each warn "is not a … key"): the top level of a shape holds its geometry (`width height radius d startAngle endAngle cornerRadius anchorX anchorY`), `trimStart` / `trimEnd`, `strokeCap` / `strokeJoin`, `fillGradient`.
49
49
 
50
- Keyframe kinds: `set` (jump at `at`; undone when you seek back), `to` (animate to), `from` (animate from), `from`+`to`; a value may be relative to where the layer stands (`from: { x: '-=36' }` slides in from 36 px to the left of its place). `from` / `from+to` show their start value from the layer's start, so a delayed fade-in needs no `initial.alpha`. `repeat` (finite extra plays), `yoyo`, `repeatDelay` loop any of them (endless repeats are not allowed). `ease` = an ease name (`'none'`, `'power2.out'`, `'expo.out'`, `'back.out(1.7)'`, `'elastic.out(1,0.5)'`, `'sine.inOut'`, `'bounce.out'`, **`'cubic-bezier(.4, 0, .2, 1)'`** (CSS syntax, x1 and x2 in 0…1; `.4, 0, .2, 1` is a gentle in-out; `.2, .8, .2, 1` is FRONT-LOADED, not slow motion: half of the change in the first 13 % of the time, 90 % by 39 %, 99 % by 74 %, then a long settle, so what sits at the END of the content gets the glide; `.2, .8, .5, 1` has a shorter tail). `power1…4` are the powers 2…5: `power2` is a cubic, `power2.in` has done only 12.5 % at half time (42 % at three quarters)… and springs: `'spring(mass, stiffness, damping)'` or `'spring.gentle' | 'snappy' | 'bouncy' | 'wobbly' | 'slow'`; **`duration: 'auto'` = the time the spring takes to settle** (presets, settle time / highest value reached as a multiple of the target: `snappy` 0.42 s / 1.01, `gentle` 0.72 s / 1.02, `bouncy` 1.08 s / 1.28, `slow` 1.29 s / 1.00, `wobbly` 1.61 s / 1.40: time the next beat from these) (real physics; with a number `duration` is the settle time and only the damping ratio shapes it; a damping ratio of 1 or more never overshoots, so `spring(1,170,26)` does not bounce: use damping below 20 at stiffness 170; `'auto'` works on keyframes only, not in `animateText` / `orbit` tweens; a `stagger` ease may be a spring, its delays are held inside the spread). Default ease is linear.
50
+ Keyframe kinds: `set` (jump at `at`; undone when you seek back), `to` (animate to), `from` (animate from), `from`+`to`; a value may be relative to where the layer stands (`from: { x: '-=36' }` slides in from 36 px to the left of its place). `from` / `from+to` show their start value from the layer's start, so a delayed fade-in needs no `initial.alpha`. `repeat` (finite extra plays), `yoyo`, `repeatDelay` loop any of them (endless repeats are not allowed: `repeat: -1` (the GSAP habit) is refused with a warning, write a count that covers the layer, `repeat: 40`; a `from` keyframe with a later `at` puts its start value on the layer from frame 0, the layer does not wait for the `at`). `ease` = an ease name (`'none'`, `'power2.out'`, `'expo.out'`, `'back.out(1.7)'`, `'elastic.out(1,0.5)'`, `'sine.inOut'`, `'bounce.out'`, **`'cubic-bezier(.4, 0, .2, 1)'`** (CSS syntax, x1 and x2 in 0…1; `.4, 0, .2, 1` is a gentle in-out; `.2, .8, .2, 1` is FRONT-LOADED, not slow motion: half of the change in the first 13 % of the time, 90 % by 39 %, 99 % by 74 %, then a long settle, so what sits at the END of the content gets the glide; `.2, .8, .5, 1` has a shorter tail). `power1…4` are the powers 2…5: `power2` is a cubic, `power2.in` has done only 12.5 % at half time (42 % at three quarters)… and springs: `'spring(mass, stiffness, damping)'` or `'spring.gentle' | 'snappy' | 'bouncy' | 'wobbly' | 'slow'`; **`duration: 'auto'` = the time the spring takes to settle** (presets, settle time / highest value reached as a multiple of the target: `snappy` 0.42 s / 1.01, `gentle` 0.72 s / 1.02, `bouncy` 1.08 s / 1.28, `slow` 1.29 s / 1.00, `wobbly` 1.61 s / 1.40: time the next beat from these) (real physics; with a number `duration` is the settle time and only the damping ratio shapes it; a damping ratio of 1 or more never overshoots, so `spring(1,170,26)` does not bounce: use damping below 20 at stiffness 170; `'auto'` works on keyframes only, not in `animateText` / `orbit` tweens; a `stagger` ease may be a spring, its delays are held inside the spread). Default ease is linear.
51
51
 
52
52
  ## Layer types
53
53
 
@@ -158,7 +158,7 @@ measureText('Hello', style) // → { width, height,
158
158
  splitText('Hello world', style, { by: 'chars' | 'words' | 'lines', x, y, align: 'left' | 'center' | 'right' })
159
159
  // → [{ text, x, y, width, index, line }] — one text layer per piece, same style, anchor top-left
160
160
  ```
161
- Per-letter / per-word / per-line animation in one call: `...animateText(text, style, { x, y, align, vertical: true /* tategaki: down a column, new line = new column to the left, x = RIGHT edge of the first column; 、。 ー 「」 placed and turned for you */, by: 'chars'|'words'|'lines', at, duration /* required: until the last piece is gone */, in: 'rise'|'drop'|'fade'|'pop'|'zoom'|'slide'|'spin'|{ from, to, duration, ease }|false, out: <same, in the same wave (leave `out` out and the pieces stay until the layer ends); by default BACKWARDS FROM THE LAYER'S END (at + duration, so to be gone at time T set duration = T − at); `out: { preset: 'fade', at: 6 }` starts the exit at composition second 6 instead>, stagger: { each, from, … }, idle: { y: 4, rotation: 1 }, styleFor: p => ({ fill }) })` → text layers `<name>-0…` (anchor centre; `x`/`y` in `from` are offsets) to spread into `sequences`. **`x` / `y` are pixel NUMBERS (an expression like `'GW/2'` throws: compute `W / 2`), and `y` is the TOP of the line box**: the visible text is centred `lineHeight / 2` below it, so an underline goes at `y + lineHeight`, not at `y` (recipe `animate-text`). By hand: `splitText(...)` + one layer per piece (recipe `split-text`). A web font must be loaded before measuring.
161
+ Per-letter / per-word / per-line animation in one call: `...animateText(text, style, { x, y, align, vertical: true /* tategaki: down a column, new line = new column to the left, x = RIGHT edge of the first column; 、。 ー 「」 placed and turned for you */, by: 'chars'|'words'|'lines', at, duration /* required: until the last piece is gone */, in: 'rise'|'drop'|'fade'|'pop'|'zoom'|'slide'|'spin'|{ from, to, duration, ease }|false, out: <same, in the same wave (leave `out` out and the pieces stay until the layer ends); by default BACKWARDS FROM THE LAYER'S END (at + duration, so to be gone at time T set duration = T − at); `out: { preset: 'fade', at: 6 }` starts the exit at composition second 6 instead; `out: { preset: 'fade', at: 6, stagger: 0 }` makes everything leave AT ONCE after it came in letter by letter (or `stagger: { each: 0.1 }`: its own wave); an exit is described by `from` = the state the piece leaves TO: `out: { from: { y: 18, alpha: 0 } }`; no exit: omit `out`, or `out: false` / `'none'`>, stagger: { each, from, … }, idle: { y: 4, rotation: 1 }, styleFor: p => ({ fill }) })` → text layers `<name>-0…` (anchor centre; `x`/`y` in `from` are offsets) to spread into `sequences`. **`x` / `y` are pixel NUMBERS (an expression like `'GW/2'` throws: compute `W / 2`), and `y` is the TOP of the line box**: the visible text is centred `lineHeight / 2` below it, so an underline goes at `y + lineHeight`, not at `y` (recipe `animate-text`). By hand: `splitText(...)` + one layer per piece (recipe `split-text`). A web font must be loaded before measuring.
162
162
 
163
163
  ## Assets
164
164
 
@@ -1388,7 +1388,7 @@ A moving, shadow-casting model is held to "a function of the time" by `check`: p
1388
1388
 
1389
1389
  ## An app or video on a phone, a television, a billboard (`threeLayerTexture`)
1390
1390
 
1391
- A 2D composition is the screen; a `three` layer holds the object it is on. The composition keeps animating (its own keyframes, text, charts, a counter) and the mesh shows it live, turning with the object. Put the composition BEFORE the three layer, give it a background rect, and `await threeLayerTexture(ctx, 'screen')` in `setup`: `screen.material` goes on the face of the mesh. The page's import map needs `"three/webgpu"` and `"three/tsl"` next to `"three"` (the template's has them when it has `three`).
1391
+ A 2D composition is the screen; a `three` layer holds the object it is on. The composition keeps animating (its own keyframes, text, charts, a counter) and the mesh shows it live, turning with the object. Put the composition BEFORE the three layer, give it a background rect, and `await threeLayerTexture(ctx, 'screen')` in `setup`: `screen.material` goes on the face of the mesh. The three layer as written here (the default `renderer`, WebGL) needs nothing beyond the template's import map: the screen's pixels are read back each frame. Only a layer with `renderer: 'webgpu'` (the no-copy route) also needs `"three/webgpu"` and `"three/tsl"` next to `"three"`.
1392
1392
 
1393
1393
  ```js
1394
1394
  // @docs-only three-screen
@@ -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.46.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.47.1/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,17 +37,17 @@
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.46.0/dist/index.js",
41
- "pixi-effects/controller": "https://cdn.jsdelivr.net/npm/pixi-effects@0.46.0/dist/Controller.js",
42
- "pixi-effects/present": "https://cdn.jsdelivr.net/npm/pixi-effects@0.46.0/dist/present.js",
43
- "pixi-effects/three": "https://cdn.jsdelivr.net/npm/pixi-effects@0.46.0/dist/three.js",
40
+ "pixi-effects": "https://cdn.jsdelivr.net/npm/pixi-effects@0.47.1/dist/index.js",
41
+ "pixi-effects/controller": "https://cdn.jsdelivr.net/npm/pixi-effects@0.47.1/dist/Controller.js",
42
+ "pixi-effects/present": "https://cdn.jsdelivr.net/npm/pixi-effects@0.47.1/dist/present.js",
43
+ "pixi-effects/three": "https://cdn.jsdelivr.net/npm/pixi-effects@0.47.1/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
  "three/addons/": "https://cdn.jsdelivr.net/npm/three@0.178.0/examples/jsm/"
47
47
  }
48
48
  }
49
49
  </script>
50
- <link rel="modulepreload" href="https://cdn.jsdelivr.net/npm/pixi-effects@0.46.0/dist/index.js" /> <!-- after the import map: the library downloads while the page is parsed -->
50
+ <link rel="modulepreload" href="https://cdn.jsdelivr.net/npm/pixi-effects@0.47.1/dist/index.js" /> <!-- after the import map: the library downloads while the page is parsed -->
51
51
 
52
52
  <script type="module">
53
53
  import { Movie, kenBurns, withFade } from 'pixi-effects';
@@ -1,7 +0,0 @@
1
- export { CompositionSequence } from './chunk-FPV3NPYC.js';
2
- import './chunk-DM2OLQOJ.js';
3
- import './chunk-VNP7QCJE.js';
4
- import './chunk-32MMNDDN.js';
5
- import './chunk-BXOCEZ4U.js';
6
- //# sourceMappingURL=Composition-NSY7A32Y.js.map
7
- //# sourceMappingURL=Composition-NSY7A32Y.js.map