pixi-effects 0.47.3 → 0.47.4

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 (69) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +5 -5
  3. package/ai/chat-template.html +3 -3
  4. package/dist/Composition-HTHQ2RLF.js +7 -0
  5. package/dist/{Composition-6TXRN65C.js.map → Composition-HTHQ2RLF.js.map} +1 -1
  6. package/dist/Composition-ZYRDRLYB.cjs +16 -0
  7. package/dist/{Composition-IC274CKO.cjs.map → Composition-ZYRDRLYB.cjs.map} +1 -1
  8. package/dist/Controller.cjs +3 -3
  9. package/dist/Controller.js +1 -1
  10. package/dist/{Spans-M5KMHT6K.cjs → Spans-I7P7ZVZO.cjs} +12 -12
  11. package/dist/{Spans-M5KMHT6K.cjs.map → Spans-I7P7ZVZO.cjs.map} +1 -1
  12. package/dist/{Spans-3T6ZEEN4.js → Spans-JYCTJV4V.js} +7 -7
  13. package/dist/{Spans-3T6ZEEN4.js.map → Spans-JYCTJV4V.js.map} +1 -1
  14. package/dist/{blendModes-S72MBYKY.js → blendModes-535EMYGK.js} +5 -5
  15. package/dist/{blendModes-S72MBYKY.js.map → blendModes-535EMYGK.js.map} +1 -1
  16. package/dist/{blendModes-Y56QAMC4.cjs → blendModes-V5XHC7U4.cjs} +6 -6
  17. package/dist/{blendModes-Y56QAMC4.cjs.map → blendModes-V5XHC7U4.cjs.map} +1 -1
  18. package/dist/{chunk-HGKXEPDJ.js → chunk-34YNRCMT.js} +15 -8
  19. package/dist/chunk-34YNRCMT.js.map +1 -0
  20. package/dist/{chunk-VTGJRKW4.cjs → chunk-AES2L75M.cjs} +92 -92
  21. package/dist/{chunk-VTGJRKW4.cjs.map → chunk-AES2L75M.cjs.map} +1 -1
  22. package/dist/{chunk-JVO2W77G.js → chunk-BFRWSV3R.js} +3 -3
  23. package/dist/{chunk-JVO2W77G.js.map → chunk-BFRWSV3R.js.map} +1 -1
  24. package/dist/{chunk-33OVRSXF.cjs → chunk-CJS2S5XU.cjs} +50 -43
  25. package/dist/chunk-CJS2S5XU.cjs.map +1 -0
  26. package/dist/{chunk-TWZNOUM7.cjs → chunk-DOLTCXCL.cjs} +7 -2
  27. package/dist/chunk-DOLTCXCL.cjs.map +1 -0
  28. package/dist/{chunk-GDY56V74.cjs → chunk-DS2EWRV4.cjs} +96 -96
  29. package/dist/{chunk-GDY56V74.cjs.map → chunk-DS2EWRV4.cjs.map} +1 -1
  30. package/dist/{chunk-PC7DFDCV.js → chunk-JBDBICS2.js} +5 -5
  31. package/dist/{chunk-PC7DFDCV.js.map → chunk-JBDBICS2.js.map} +1 -1
  32. package/dist/{chunk-6LA7W362.js → chunk-NCM2WU7R.js} +10 -10
  33. package/dist/{chunk-6LA7W362.js.map → chunk-NCM2WU7R.js.map} +1 -1
  34. package/dist/{chunk-YSTOH5PC.cjs → chunk-NGJXVJB7.cjs} +6 -6
  35. package/dist/{chunk-YSTOH5PC.cjs.map → chunk-NGJXVJB7.cjs.map} +1 -1
  36. package/dist/{chunk-HX73PCVU.js → chunk-RTZ5MW3E.js} +6 -6
  37. package/dist/{chunk-HX73PCVU.js.map → chunk-RTZ5MW3E.js.map} +1 -1
  38. package/dist/{chunk-BXOCEZ4U.js → chunk-WGRWFQOB.js} +7 -2
  39. package/dist/chunk-WGRWFQOB.js.map +1 -0
  40. package/dist/{chunk-RU4N3B5B.cjs → chunk-WYR2AU4W.cjs} +7 -7
  41. package/dist/{chunk-RU4N3B5B.cjs.map → chunk-WYR2AU4W.cjs.map} +1 -1
  42. package/dist/index.cjs +37 -37
  43. package/dist/index.js +6 -6
  44. package/dist/loader.css +1 -1
  45. package/dist/{music-JKOM5YK6.js → music-P4JRT73P.js} +3 -3
  46. package/dist/{music-JKOM5YK6.js.map → music-P4JRT73P.js.map} +1 -1
  47. package/dist/{music-6WFTPVHU.cjs → music-XQEPBIDT.cjs} +7 -7
  48. package/dist/{music-6WFTPVHU.cjs.map → music-XQEPBIDT.cjs.map} +1 -1
  49. package/dist/present.cjs +13 -13
  50. package/dist/present.js +6 -6
  51. package/dist/{sfx-2O6UQEUP.cjs → sfx-G2XAMD7K.cjs} +8 -8
  52. package/dist/{sfx-2O6UQEUP.cjs.map → sfx-G2XAMD7K.cjs.map} +1 -1
  53. package/dist/{sfx-VUKARHDX.js → sfx-Z7OQIKHG.js} +3 -3
  54. package/dist/{sfx-VUKARHDX.js.map → sfx-Z7OQIKHG.js.map} +1 -1
  55. package/dist/three.cjs +7 -7
  56. package/dist/three.js +3 -3
  57. package/docs/api.md +2 -2
  58. package/docs/dsl.md +5 -4
  59. package/llms-full.txt +10 -9
  60. package/package.json +1 -1
  61. package/skills/pixi-effects/SKILL.md +1 -1
  62. package/skills/pixi-effects/reference/cheatsheet.md +2 -2
  63. package/skills/pixi-effects/template.html +6 -6
  64. package/dist/Composition-6TXRN65C.js +0 -7
  65. package/dist/Composition-IC274CKO.cjs +0 -16
  66. package/dist/chunk-33OVRSXF.cjs.map +0 -1
  67. package/dist/chunk-BXOCEZ4U.js.map +0 -1
  68. package/dist/chunk-HGKXEPDJ.js.map +0 -1
  69. package/dist/chunk-TWZNOUM7.cjs.map +0 -1
package/dist/three.cjs CHANGED
@@ -1,8 +1,8 @@
1
1
  'use strict';
2
2
 
3
- var chunk33OVRSXF_cjs = require('./chunk-33OVRSXF.cjs');
4
- require('./chunk-YSTOH5PC.cjs');
5
- require('./chunk-TWZNOUM7.cjs');
3
+ var chunkCJS2S5XU_cjs = require('./chunk-CJS2S5XU.cjs');
4
+ require('./chunk-NGJXVJB7.cjs');
5
+ require('./chunk-DOLTCXCL.cjs');
6
6
  var pixi_js = require('pixi.js');
7
7
  var three$1 = require('three');
8
8
 
@@ -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 chunk33OVRSXF_cjs.Sequence {
173
+ var ThreeSequence = class extends chunkCJS2S5XU_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;
@@ -231,7 +231,7 @@ var ThreeSequence = class extends chunk33OVRSXF_cjs.Sequence {
231
231
  if (typeof spec.update === "function" && NOT_PURE.test(spec.update.toString())) {
232
232
  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`);
233
233
  }
234
- const scope = chunk33OVRSXF_cjs.buildScope(this, this.parent, this.root);
234
+ const scope = chunkCJS2S5XU_cjs.buildScope(this, this.parent, this.root);
235
235
  const width = resolveDim(spec.width, scope) ?? this.parent?.width ?? this.root.width;
236
236
  const height = resolveDim(spec.height, scope) ?? this.parent?.height ?? this.root.height;
237
237
  const resolution = spec.resolution ?? 1;
@@ -800,7 +800,7 @@ var ThreeSequence = class extends chunk33OVRSXF_cjs.Sequence {
800
800
  function resolveDim(v, scope) {
801
801
  if (v === void 0) return void 0;
802
802
  if (typeof v === "number") return v;
803
- return chunk33OVRSXF_cjs.evaluateExpr(v, scope);
803
+ return chunkCJS2S5XU_cjs.evaluateExpr(v, scope);
804
804
  }
805
805
  var ids = /* @__PURE__ */ new WeakMap();
806
806
  var lastId = 0;
@@ -1093,7 +1093,7 @@ function registerThree(options = {}) {
1093
1093
  const kind = options.renderer ?? "webgl";
1094
1094
  checkRendererKind(kind, "registerThree's renderer");
1095
1095
  setPageRenderer(kind);
1096
- chunk33OVRSXF_cjs.registerSequenceType("three", ThreeSequence);
1096
+ chunkCJS2S5XU_cjs.registerSequenceType("three", ThreeSequence);
1097
1097
  }
1098
1098
  function three(spec) {
1099
1099
  return spec;
package/dist/three.js CHANGED
@@ -1,6 +1,6 @@
1
- import { Sequence, buildScope, evaluateExpr, registerSequenceType } from './chunk-HGKXEPDJ.js';
2
- import './chunk-JVO2W77G.js';
3
- import './chunk-BXOCEZ4U.js';
1
+ import { Sequence, buildScope, evaluateExpr, registerSequenceType } from './chunk-34YNRCMT.js';
2
+ import './chunk-BFRWSV3R.js';
3
+ import './chunk-WGRWFQOB.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';
6
6
 
package/docs/api.md CHANGED
@@ -237,9 +237,9 @@ interface SoundReport {
237
237
  duration: number; sampleRate: number; channels: number;
238
238
  peakDb: number; // the loudest sample, dBFS
239
239
  loudness: { integratedLufs: number | null; // ITU-R BS.1770; null for a sound under 0.4 s or with nothing above −70 LUFS
240
- truePeakDb: number; clippedSamples: number; silences: Array<{ from: number; to: number }> };
240
+ truePeakDb: number; clippedSamples: number; silences: Array<{ from: number; to: number }> }; // runs of 1 s or more where every channel stays below −60 dBFS, in seconds
241
241
  windows: Array<{ t: number; rmsDb: number }>; // the level over time
242
- notes: string[]; // quiet or loud for web video, little headroom, silent stretches
242
+ notes: string[]; // quiet or loud for web video, little headroom, silent stretches (`the mix is silent from X s to Y s (N s)`, at most 3, then `and N more silent stretch(es)`)
243
243
  }
244
244
  ```
245
245
 
package/docs/dsl.md CHANGED
@@ -63,7 +63,7 @@ interface SequenceCommon {
63
63
  | `text`, `image`, `video` | the **top-left** corner | `anchorX` / `anchorY: 0.5` to centre |
64
64
  | `composition` | its top-left corner | `pivotX` / `pivotY` = the point that sits at `x, y` and that rotation / scale turn about |
65
65
 
66
- Shape geometry (`width height radius anchorX …`) may be written at the top level or in `initial`; the top level wins. `rotation`, `skew*` and `rotationX/Y` are in **degrees**. **`skewX` / `skewY` are PixiJS's, not CSS's shear:** they turn an axis about the layer's anchor (the anchored point stays), so `skewX: 40` tilts the layer's vertical edges by 40° and shortens them to `cos 40°` of their length (a CSS `skewX(40deg)` or After Effects' skew keeps the height and moves the bottom by `h · tan 40°`). A point (x, y) of the layer, measured from its anchor, lands at `(x cos β + y sin α, x sin β + y cos α)` for `skewX: α`, `skewY: β`. Pinned by `tests/films/geometry-skew`.
66
+ Shape geometry (`width height radius anchorX …`) may be written at the top level or in `initial`; the top level wins. `rotation`, `skew*` and `rotationX/Y` are in **degrees**; a positive `rotation` turns **clockwise** on the screen (+x toward +y, like CSS `rotate()`), about the anchor (a composition: about `pivotX` / `pivotY`, scale first and then rotation). **`skewX` / `skewY` are PixiJS's, not CSS's shear:** they turn an axis about the layer's anchor (the anchored point stays), so `skewX: 40` tilts the layer's vertical edges by 40° and shortens them to `cos 40°` of their length (a CSS `skewX(40deg)` or After Effects' skew keeps the height and moves the bottom by `h · tan 40°`). A point (x, y) of the layer, measured from its anchor, lands at `(x cos β + y sin α, x sin β + y cos α)` for `skewX: α`, `skewY: β`. Pinned by `tests/films/geometry-skew`.
67
67
 
68
68
  A layer is **not removed** when its lifespan `[at, at + duration)` ends: it is only hidden, and it keeps its last animated values. Outside the lifespan it is invisible. **z-order is array order** (later = on top). Build scenes by giving layers `at` / `duration` and stacking them; a full-screen background rect in each scene hides what is below it.
69
69
 
@@ -370,7 +370,7 @@ Every primitive draws centred on its local origin (so `anchorX`/`anchorY` and `p
370
370
  keyframes: [{ at: 0, to: { trimEnd: 1 }, duration: 1, ease: 'power2.inOut' }] } // a check mark drawing itself
371
371
  ```
372
372
 
373
- **Morph (`morphTo` / `morph`).** (Two outlines with the **same number of corners**, such as two quads, are paired corner to corner, so every in-between picture is a straight-edged quad and no corner is rounded; `morphPoints` only matters when the corner counts differ or the outlines are curves.) A `path` can turn into another outline: give `morphTo` (SVG path data in the same canvas coordinates as `d`) and animate `morph` from 0 (the `d` outline) to 1 (the `morphTo` outline). In between it is drawn as a polygon of points half way from one to the other (`morphPoints`: 8–2048, default 128). Closed outlines are lined up (start point and direction) so they do not twist; sub-paths are paired in order, and an extra one grows from (or shrinks into) its own centre. Fill and stroke both follow, and `trimStart` / `trimEnd` walk the morphing outline. Write both outlines as `M … L … C … Z` in absolute coordinates (arcs and holes work too, but keep the same number of sub-paths on both sides).
373
+ **Morph (`morphTo` / `morph`).** (Two outlines with the **same number of corners** (the points of the outline as written: a line back to the start before `Z` is not a corner, but an extra point on an edge IS one), such as two quads, are paired corner to corner, the start of each outline lined up with the nearest of the other's, so every in-between picture is a straight-edged quad and no corner is rounded; `morphPoints` only matters when the corner counts differ or the outlines are curves.) A `path` can turn into another outline: give `morphTo` (SVG path data in the same canvas coordinates as `d`) and animate `morph` from 0 (the `d` outline) to 1 (the `morphTo` outline). In between it is drawn as a polygon of points half way from one to the other (`morphPoints`: 8–2048, default 128). Closed outlines are lined up (start point and direction) so they do not twist; sub-paths are paired in order, and an extra one grows from (or shrinks into) its own centre. Fill and stroke both follow, and `trimStart` / `trimEnd` walk the morphing outline. Write both outlines as `M … L … C … Z` in absolute coordinates (arcs and holes work too, but keep the same number of sub-paths on both sides).
374
374
 
375
375
  ```ts
376
376
  { type: 'shape', shape: 'path', d: 'M 640 160 C 780 160 860 260 860 360 C 860 470 770 560 640 560 C 510 560 420 470 420 360 C 420 250 500 160 640 160 Z',
@@ -561,7 +561,7 @@ Place any 2D layer in depth and view it through a camera — After Effects style
561
561
 
562
562
  | Thing | Convention |
563
563
  |---|---|
564
- | Axes | `+x` right, `+y` down, **`+z` toward the viewer** (like CSS `translateZ`). Bigger `z` = nearer = larger on screen. |
564
+ | Axes | `+x` right, `+y` down, **`+z` toward the viewer** (like CSS `translateZ`). Bigger `z` = nearer = larger on screen: a flat layer at `z` is drawn `camZ / (camZ − z)` times its size, `camZ` being the camera's distance from the picture plane (about 989 for 720p at fov 40: `z = −989` is half size, `z = +495` is twice). A layer at or behind the camera plane (`z ≥ camZ`) is hidden for that frame and says so once: `layer "card" is at or behind the camera plane (z = 1000) and is hidden`. |
565
565
  | Units | Positions in composition pixels. **Rotations in degrees.** |
566
566
  | Rotation signs | Same as CSS: `rotationY: 30` swings the right edge away; `rotationX: 30` brings the bottom edge toward you; `rotation` is the Z axis. |
567
567
  | Rotation centre | The same point 2D `rotation` uses: `anchorX/anchorY` (sprites, text, shapes) or `pivotX/pivotY` (compositions). Set `anchorX: 0.5, anchorY: 0.5` to spin about the centre. |
@@ -798,6 +798,7 @@ The layer's own three.js renderer is `WebGLRenderer` unless you ask for `rendere
798
798
  - Materials that rewrite three's shaders (`onBeforeCompile`, `ShaderMaterial`, `RawShaderMaterial`) do not run on the WebGPU renderer: a layer that chose `renderer: 'webgpu'` stops building, naming the material and the fix (`renderer: 'webgl'`), instead of drawing a picture that is wrong without saying so. Only what `ctx.scene` holds is looked at: a `ShaderMaterial` kept in a scene of your own and drawn through `ctx.renderer` (a post-processing quad) is not found; three then logs `Material "ShaderMaterial" is not compatible` on every render. The built-in `Mesh*Material`s, `InstancedMesh` and `Points` do run.
799
799
  - **Lights follow the time.** Three's WebGPU renderer updates its light uniforms (and its `time`) once per animation-loop frame, so renders that follow each other in one task (a seek, an export) would all show the same lights and a lamp keyframed with `three.lamp.intensity` would not move. The layer therefore advances three's node frame before each render and gives its `time` the layer's own time (this reaches into a private field of three's renderer: a three that no longer has it makes the layer warn once).
800
800
  - Measured on one Apple (Metal) machine, headless Chrome, 2026-10-15: the same lit box drew with a mean difference of 0.04–0.06 (of 255) per channel from the WebGL picture (only edge pixels differ). On the repository's own pages the WebGPU picture equals the WebGL one except where a material used `onBeforeCompile` (`three-product-spin`: the floor reflection no longer fades) and where the scene has fog or a hot, thin edge: see "Where the two renderers differ on purpose". The render alone, waiting for the GPU, took about 0.8–0.9 ms either way for a box; for 2,880 instanced rods under two point lights it took about 0.9–1.2 ms on WebGPU against 1.7–2.1 ms on WebGL (1280×720 to 1920×1080). A 90-frame MP4 export took the same time with either (about 22 frames per second: the encode, not three, was the limit).
801
+ - **2D layers on the two backends** (measured by `tests/films/backends-agree` on the same machine, 2026-10-20: shapes, text, a linear gradient, multiply, alpha, rotation, a blur filter): the mean difference per channel is 0.016–0.021 of 255. Away from edges the pictures are equal (text, gradient, blur and multiply: 0); the only differences are the antialiasing of curved and rotated edges, up to 34/255 on one pixel at alpha 0.6. Another GPU may differ by a little more.
801
802
 
802
803
  #### Where the two renderers differ on purpose
803
804
 
@@ -1095,7 +1096,7 @@ interface Keyframe {
1095
1096
  The four kinds are mutually exclusive per keyframe:
1096
1097
 
1097
1098
  - **`set`** — instantaneous property assignment at `at`.
1098
- - **`to`** — tween from whatever the property is at `at` to the given values, over `duration`.
1099
+ - **`to`** — tween from whatever the property is at `at` to the given values, over `duration`. **Keyframes on one property run in the order of their `at`, not of the list** (written backwards they play the same). A `to` starts from where the keyframe before it ENDED, so two keyframes of one property that overlap in time make the picture jump: the later one takes over at its `at`, starting from the earlier one's end value (A: x 40 → 160 over 0.5–1.5 s, B: → 280 from 1 s: the layer is at 96 just before 1 s and at 160 just after). Start the second after the first ends. Pinned by `tests/films/time-keyframe-order`.
1099
1100
  - **`from`** — tween from the given values back to the current property, over `duration`.
1100
1101
  - **`from` + `to`** — full fromTo tween, with explicit start and end values.
1101
1102
 
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.47.3. 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.4. 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
 
@@ -168,7 +168,7 @@ threeD: true // opt into 2.5D (see below)
168
168
  blendMode: 'normal' | 'add' | 'screen' | 'multiply' | 'overlay' | 'soft-light' | 'hard-light' | 'color-dodge' | 'color-burn' | 'darken' | 'lighten' | 'difference' | 'exclusion' | 'hue' | 'saturation' | 'color' | 'luminosity' | 'linear-burn' // CSS names (hyphenated); blends with EVERYTHING BELOW the layer; add / screen brighten (glows), multiply darkens, soft-light / overlay for colour casts and contrast, color-dodge for light sources; on a composition the children each blend (to blend it as one picture: threeD: true or a filter); the 14 new ones cost ~2 ms a layer on WebGL (10+ on screen warns)
169
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 `layer "dot": "x" is a property, not a rect shape key, so it is ignored: write it in initial (initial: { x: 40 }) or in a keyframe`; every warning of this kind names the layer, or says "unnamed <type> layer"): the top level of a shape holds its geometry (`width height radius d startAngle endAngle cornerRadius anchorX anchorY`), `trimStart` / `trimEnd`, `strokeCap` / `strokeJoin`, `fillGradient`.
171
+ Animatable props (`initial` / keyframes): `x y alpha rotation scale scaleX scaleY pivotX pivotY anchorX anchorY skewX skewY tint width height visible autoAlpha` (and PixiJS's own shorthands `position positionX positionY anchor pivot skew tilePosition* tileScale*`), 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>'`. A mistyped property in `initial` or a keyframe says `layer "x": "alhpa" is not an animatable property — did you mean "alpha"? (in initial, set, to or from)` (once per layer however often it is written); a mistyped text style key says `layer "t": style.fontsize is not a text style key — did you mean "fontSize"? (it is ignored). Keys: …` and lists the valid keys (`style.colour` / `style.color` mean `fill`). **These go in `initial` / keyframes, never at the top level of a shape or text layer** (top-level `x`, `rotation`, `strokeColor`, `fillAlpha` each warn `layer "dot": "x" is a property, not a rect shape key, so it is ignored: write it in initial (initial: { x: 40 }) or in a keyframe`; every warning of this kind names the layer, or says "unnamed <type> layer"): the top level of a shape holds its geometry (`width height radius d startAngle endAngle cornerRadius anchorX anchorY`), `trimStart` / `trimEnd`, `strokeCap` / `strokeJoin`, `fillGradient`.
172
172
 
173
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.
174
174
 
@@ -176,7 +176,7 @@ Keyframe kinds: `set` (jump at `at`; undone when you seek back), `to` (animate t
176
176
 
177
177
  **text** — `text`, `resolution` (1–8: draw at N× the pixels so text inside a parent scaled up N× stays sharp), `style` (any PixiJS TextStyle field: `fontSize fontFamily fontWeight fill letterSpacing lineHeight align wordWrap wordWrapWidth stroke dropShadow padding`; expressions OK for numbers), `colorSpace`. Default anchor is **top-left**; use `anchorX/anchorY: 0.5` to centre on `x,y`. A text layer has one animatable number, `value`, printed where the text contains `{value}` (`text: '{value} users'`, `initial: { value: 0 }`, keyframe `to: { value: 2480 }`; `format: { decimals, grouping, pad /* zero-pad the whole part: pad 2 → 05 */, time: 'hh:mm:ss'|'h:mm:ss'|'hh:mm'|'mm:ss'|'m:ss' /* the value in SECONDS as a clock */ }`) — that is how counters work. **Changing text:** a keyframe `set: { text: 'Two' }` swaps the string at that time (undone on seek back; `to` / `from` cannot tween a string); `visibleChars` (number, first N characters; starts at 0 once animated) is a typewriter: `to: { visibleChars: 30 }, duration: 2.4, ease: 'none'` — use `anchorX: 0` + `style.align: 'left'` so it grows rightward. Multi-line text is centre-aligned by default (`style.align: 'left'` to change). **Colour inside one line:** `spans: [{ text: 'type', fill: '#7fa07f' }, ': ', { text: "'a'", fill: '#ffd166', fontWeight: '700' }, ',\n next line']` instead of `text` (strings use the layer's `style`; a span changes only fill / fontWeight / fontStyle / fontVariant / letterSpacing / dropShadow, never a size, font or stroke (those are the layer's `style`); `\n` starts a line; `style.align: 'left'` for code; `visibleChars` types over all the spans; it becomes a group of text layers `<name>.0…`).
178
178
 
179
- **image** — `asset`, `tint`, `colorSpace: 'rgb'|'oklab'|'oklch'`, `scaleMode: 'nearest'` (hard square pixels when scaled up: pixel art; default `'linear'`, smooth; it belongs to the asset). Default anchor top-left; natural size = `w`,`h`.
179
+ **image** — `asset`, `tint`, `colorSpace: 'rgb'|'oklab'|'oklch'`, `scaleMode: 'nearest'` (hard square pixels when scaled up: pixel art; default `'linear'`, smooth; it belongs to the asset). Default anchor top-left; natural size = `w`,`h`. `scale: 2` keeps the ratio; **`width` or `height` alone sets that side only and stretches the picture** (PixiJS: it changes `scale.x` or `scale.y`); `initial: { scale: 'cover' | 'contain' }` fits the image to the canvas (as for video) and keeps its ratio.
180
180
 
181
181
  **video** — `asset`, `loop`, `audio`, `volume`, **`speed`**; `initial: { scale: 'cover' }`. `speed: 0.5` slow motion, `speed: -1` backward (it starts at |speed| × `duration`, so a 3 s file with no `duration` plays whole, backward); with no `duration` the layer lasts the file's length ÷ |speed|. For anything that is not one straight speed — a freeze, a ramp, a loop — animate **`time`** (seconds in the file) like any property: `keyframes: [{ at: 0, from: { time: 0 }, to: { time: 2 }, duration: 1 }, { at: 1, to: { time: 2 }, duration: 0.5 }, { at: 1.5, to: { time: 6 }, duration: 3, ease: 'power2.inOut' }]` (play 2 s, hold, then accelerate). `repeat` loops it, `yoyo` ping-pongs. `speed` itself is not animatable (it warns): animate `time`. The sound follows (pitch too, like a tape).
182
182
 
@@ -2373,7 +2373,7 @@ interface SequenceCommon {
2373
2373
  | `text`, `image`, `video` | the **top-left** corner | `anchorX` / `anchorY: 0.5` to centre |
2374
2374
  | `composition` | its top-left corner | `pivotX` / `pivotY` = the point that sits at `x, y` and that rotation / scale turn about |
2375
2375
 
2376
- Shape geometry (`width height radius anchorX …`) may be written at the top level or in `initial`; the top level wins. `rotation`, `skew*` and `rotationX/Y` are in **degrees**. **`skewX` / `skewY` are PixiJS's, not CSS's shear:** they turn an axis about the layer's anchor (the anchored point stays), so `skewX: 40` tilts the layer's vertical edges by 40° and shortens them to `cos 40°` of their length (a CSS `skewX(40deg)` or After Effects' skew keeps the height and moves the bottom by `h · tan 40°`). A point (x, y) of the layer, measured from its anchor, lands at `(x cos β + y sin α, x sin β + y cos α)` for `skewX: α`, `skewY: β`. Pinned by `tests/films/geometry-skew`.
2376
+ Shape geometry (`width height radius anchorX …`) may be written at the top level or in `initial`; the top level wins. `rotation`, `skew*` and `rotationX/Y` are in **degrees**; a positive `rotation` turns **clockwise** on the screen (+x toward +y, like CSS `rotate()`), about the anchor (a composition: about `pivotX` / `pivotY`, scale first and then rotation). **`skewX` / `skewY` are PixiJS's, not CSS's shear:** they turn an axis about the layer's anchor (the anchored point stays), so `skewX: 40` tilts the layer's vertical edges by 40° and shortens them to `cos 40°` of their length (a CSS `skewX(40deg)` or After Effects' skew keeps the height and moves the bottom by `h · tan 40°`). A point (x, y) of the layer, measured from its anchor, lands at `(x cos β + y sin α, x sin β + y cos α)` for `skewX: α`, `skewY: β`. Pinned by `tests/films/geometry-skew`.
2377
2377
 
2378
2378
  A layer is **not removed** when its lifespan `[at, at + duration)` ends: it is only hidden, and it keeps its last animated values. Outside the lifespan it is invisible. **z-order is array order** (later = on top). Build scenes by giving layers `at` / `duration` and stacking them; a full-screen background rect in each scene hides what is below it.
2379
2379
 
@@ -2680,7 +2680,7 @@ Every primitive draws centred on its local origin (so `anchorX`/`anchorY` and `p
2680
2680
  keyframes: [{ at: 0, to: { trimEnd: 1 }, duration: 1, ease: 'power2.inOut' }] } // a check mark drawing itself
2681
2681
  ```
2682
2682
 
2683
- **Morph (`morphTo` / `morph`).** (Two outlines with the **same number of corners**, such as two quads, are paired corner to corner, so every in-between picture is a straight-edged quad and no corner is rounded; `morphPoints` only matters when the corner counts differ or the outlines are curves.) A `path` can turn into another outline: give `morphTo` (SVG path data in the same canvas coordinates as `d`) and animate `morph` from 0 (the `d` outline) to 1 (the `morphTo` outline). In between it is drawn as a polygon of points half way from one to the other (`morphPoints`: 8–2048, default 128). Closed outlines are lined up (start point and direction) so they do not twist; sub-paths are paired in order, and an extra one grows from (or shrinks into) its own centre. Fill and stroke both follow, and `trimStart` / `trimEnd` walk the morphing outline. Write both outlines as `M … L … C … Z` in absolute coordinates (arcs and holes work too, but keep the same number of sub-paths on both sides).
2683
+ **Morph (`morphTo` / `morph`).** (Two outlines with the **same number of corners** (the points of the outline as written: a line back to the start before `Z` is not a corner, but an extra point on an edge IS one), such as two quads, are paired corner to corner, the start of each outline lined up with the nearest of the other's, so every in-between picture is a straight-edged quad and no corner is rounded; `morphPoints` only matters when the corner counts differ or the outlines are curves.) A `path` can turn into another outline: give `morphTo` (SVG path data in the same canvas coordinates as `d`) and animate `morph` from 0 (the `d` outline) to 1 (the `morphTo` outline). In between it is drawn as a polygon of points half way from one to the other (`morphPoints`: 8–2048, default 128). Closed outlines are lined up (start point and direction) so they do not twist; sub-paths are paired in order, and an extra one grows from (or shrinks into) its own centre. Fill and stroke both follow, and `trimStart` / `trimEnd` walk the morphing outline. Write both outlines as `M … L … C … Z` in absolute coordinates (arcs and holes work too, but keep the same number of sub-paths on both sides).
2684
2684
 
2685
2685
  ```ts
2686
2686
  { type: 'shape', shape: 'path', d: 'M 640 160 C 780 160 860 260 860 360 C 860 470 770 560 640 560 C 510 560 420 470 420 360 C 420 250 500 160 640 160 Z',
@@ -2871,7 +2871,7 @@ Place any 2D layer in depth and view it through a camera — After Effects style
2871
2871
 
2872
2872
  | Thing | Convention |
2873
2873
  |---|---|
2874
- | Axes | `+x` right, `+y` down, **`+z` toward the viewer** (like CSS `translateZ`). Bigger `z` = nearer = larger on screen. |
2874
+ | Axes | `+x` right, `+y` down, **`+z` toward the viewer** (like CSS `translateZ`). Bigger `z` = nearer = larger on screen: a flat layer at `z` is drawn `camZ / (camZ − z)` times its size, `camZ` being the camera's distance from the picture plane (about 989 for 720p at fov 40: `z = −989` is half size, `z = +495` is twice). A layer at or behind the camera plane (`z ≥ camZ`) is hidden for that frame and says so once: `layer "card" is at or behind the camera plane (z = 1000) and is hidden`. |
2875
2875
  | Units | Positions in composition pixels. **Rotations in degrees.** |
2876
2876
  | Rotation signs | Same as CSS: `rotationY: 30` swings the right edge away; `rotationX: 30` brings the bottom edge toward you; `rotation` is the Z axis. |
2877
2877
  | Rotation centre | The same point 2D `rotation` uses: `anchorX/anchorY` (sprites, text, shapes) or `pivotX/pivotY` (compositions). Set `anchorX: 0.5, anchorY: 0.5` to spin about the centre. |
@@ -3108,6 +3108,7 @@ The layer's own three.js renderer is `WebGLRenderer` unless you ask for `rendere
3108
3108
  - Materials that rewrite three's shaders (`onBeforeCompile`, `ShaderMaterial`, `RawShaderMaterial`) do not run on the WebGPU renderer: a layer that chose `renderer: 'webgpu'` stops building, naming the material and the fix (`renderer: 'webgl'`), instead of drawing a picture that is wrong without saying so. Only what `ctx.scene` holds is looked at: a `ShaderMaterial` kept in a scene of your own and drawn through `ctx.renderer` (a post-processing quad) is not found; three then logs `Material "ShaderMaterial" is not compatible` on every render. The built-in `Mesh*Material`s, `InstancedMesh` and `Points` do run.
3109
3109
  - **Lights follow the time.** Three's WebGPU renderer updates its light uniforms (and its `time`) once per animation-loop frame, so renders that follow each other in one task (a seek, an export) would all show the same lights and a lamp keyframed with `three.lamp.intensity` would not move. The layer therefore advances three's node frame before each render and gives its `time` the layer's own time (this reaches into a private field of three's renderer: a three that no longer has it makes the layer warn once).
3110
3110
  - Measured on one Apple (Metal) machine, headless Chrome, 2026-10-15: the same lit box drew with a mean difference of 0.04–0.06 (of 255) per channel from the WebGL picture (only edge pixels differ). On the repository's own pages the WebGPU picture equals the WebGL one except where a material used `onBeforeCompile` (`three-product-spin`: the floor reflection no longer fades) and where the scene has fog or a hot, thin edge: see "Where the two renderers differ on purpose". The render alone, waiting for the GPU, took about 0.8–0.9 ms either way for a box; for 2,880 instanced rods under two point lights it took about 0.9–1.2 ms on WebGPU against 1.7–2.1 ms on WebGL (1280×720 to 1920×1080). A 90-frame MP4 export took the same time with either (about 22 frames per second: the encode, not three, was the limit).
3111
+ - **2D layers on the two backends** (measured by `tests/films/backends-agree` on the same machine, 2026-10-20: shapes, text, a linear gradient, multiply, alpha, rotation, a blur filter): the mean difference per channel is 0.016–0.021 of 255. Away from edges the pictures are equal (text, gradient, blur and multiply: 0); the only differences are the antialiasing of curved and rotated edges, up to 34/255 on one pixel at alpha 0.6. Another GPU may differ by a little more.
3111
3112
 
3112
3113
  #### Where the two renderers differ on purpose
3113
3114
 
@@ -3405,7 +3406,7 @@ interface Keyframe {
3405
3406
  The four kinds are mutually exclusive per keyframe:
3406
3407
 
3407
3408
  - **`set`** — instantaneous property assignment at `at`.
3408
- - **`to`** — tween from whatever the property is at `at` to the given values, over `duration`.
3409
+ - **`to`** — tween from whatever the property is at `at` to the given values, over `duration`. **Keyframes on one property run in the order of their `at`, not of the list** (written backwards they play the same). A `to` starts from where the keyframe before it ENDED, so two keyframes of one property that overlap in time make the picture jump: the later one takes over at its `at`, starting from the earlier one's end value (A: x 40 → 160 over 0.5–1.5 s, B: → 280 from 1 s: the layer is at 96 just before 1 s and at 160 just after). Start the second after the first ends. Pinned by `tests/films/time-keyframe-order`.
3409
3410
  - **`from`** — tween from the given values back to the current property, over `duration`.
3410
3411
  - **`from` + `to`** — full fromTo tween, with explicit start and end values.
3411
3412
 
@@ -4410,9 +4411,9 @@ interface SoundReport {
4410
4411
  duration: number; sampleRate: number; channels: number;
4411
4412
  peakDb: number; // the loudest sample, dBFS
4412
4413
  loudness: { integratedLufs: number | null; // ITU-R BS.1770; null for a sound under 0.4 s or with nothing above −70 LUFS
4413
- truePeakDb: number; clippedSamples: number; silences: Array<{ from: number; to: number }> };
4414
+ truePeakDb: number; clippedSamples: number; silences: Array<{ from: number; to: number }> }; // runs of 1 s or more where every channel stays below −60 dBFS, in seconds
4414
4415
  windows: Array<{ t: number; rmsDb: number }>; // the level over time
4415
- notes: string[]; // quiet or loud for web video, little headroom, silent stretches
4416
+ notes: string[]; // quiet or loud for web video, little headroom, silent stretches (`the mix is silent from X s to Y s (N s)`, at most 3, then `and N more silent stretch(es)`)
4416
4417
  }
4417
4418
  ```
4418
4419
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pixi-effects",
3
- "version": "0.47.3",
3
+ "version": "0.47.4",
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.47.3. 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.4. 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
 
@@ -45,7 +45,7 @@ threeD: true // opt into 2.5D (see below)
45
45
  blendMode: 'normal' | 'add' | 'screen' | 'multiply' | 'overlay' | 'soft-light' | 'hard-light' | 'color-dodge' | 'color-burn' | 'darken' | 'lighten' | 'difference' | 'exclusion' | 'hue' | 'saturation' | 'color' | 'luminosity' | 'linear-burn' // CSS names (hyphenated); blends with EVERYTHING BELOW the layer; add / screen brighten (glows), multiply darkens, soft-light / overlay for colour casts and contrast, color-dodge for light sources; on a composition the children each blend (to blend it as one picture: threeD: true or a filter); the 14 new ones cost ~2 ms a layer on WebGL (10+ on screen warns)
46
46
  ```
47
47
 
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 `layer "dot": "x" is a property, not a rect shape key, so it is ignored: write it in initial (initial: { x: 40 }) or in a keyframe`; every warning of this kind names the layer, or says "unnamed <type> layer"): the top level of a shape holds its geometry (`width height radius d startAngle endAngle cornerRadius anchorX anchorY`), `trimStart` / `trimEnd`, `strokeCap` / `strokeJoin`, `fillGradient`.
48
+ Animatable props (`initial` / keyframes): `x y alpha rotation scale scaleX scaleY pivotX pivotY anchorX anchorY skewX skewY tint width height visible autoAlpha` (and PixiJS's own shorthands `position positionX positionY anchor pivot skew tilePosition* tileScale*`), 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>'`. A mistyped property in `initial` or a keyframe says `layer "x": "alhpa" is not an animatable property — did you mean "alpha"? (in initial, set, to or from)` (once per layer however often it is written); a mistyped text style key says `layer "t": style.fontsize is not a text style key — did you mean "fontSize"? (it is ignored). Keys: …` and lists the valid keys (`style.colour` / `style.color` mean `fill`). **These go in `initial` / keyframes, never at the top level of a shape or text layer** (top-level `x`, `rotation`, `strokeColor`, `fillAlpha` each warn `layer "dot": "x" is a property, not a rect shape key, so it is ignored: write it in initial (initial: { x: 40 }) or in a keyframe`; every warning of this kind names the layer, or says "unnamed <type> layer"): 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
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
 
@@ -53,7 +53,7 @@ Keyframe kinds: `set` (jump at `at`; undone when you seek back), `to` (animate t
53
53
 
54
54
  **text** — `text`, `resolution` (1–8: draw at N× the pixels so text inside a parent scaled up N× stays sharp), `style` (any PixiJS TextStyle field: `fontSize fontFamily fontWeight fill letterSpacing lineHeight align wordWrap wordWrapWidth stroke dropShadow padding`; expressions OK for numbers), `colorSpace`. Default anchor is **top-left**; use `anchorX/anchorY: 0.5` to centre on `x,y`. A text layer has one animatable number, `value`, printed where the text contains `{value}` (`text: '{value} users'`, `initial: { value: 0 }`, keyframe `to: { value: 2480 }`; `format: { decimals, grouping, pad /* zero-pad the whole part: pad 2 → 05 */, time: 'hh:mm:ss'|'h:mm:ss'|'hh:mm'|'mm:ss'|'m:ss' /* the value in SECONDS as a clock */ }`) — that is how counters work. **Changing text:** a keyframe `set: { text: 'Two' }` swaps the string at that time (undone on seek back; `to` / `from` cannot tween a string); `visibleChars` (number, first N characters; starts at 0 once animated) is a typewriter: `to: { visibleChars: 30 }, duration: 2.4, ease: 'none'` — use `anchorX: 0` + `style.align: 'left'` so it grows rightward. Multi-line text is centre-aligned by default (`style.align: 'left'` to change). **Colour inside one line:** `spans: [{ text: 'type', fill: '#7fa07f' }, ': ', { text: "'a'", fill: '#ffd166', fontWeight: '700' }, ',\n next line']` instead of `text` (strings use the layer's `style`; a span changes only fill / fontWeight / fontStyle / fontVariant / letterSpacing / dropShadow, never a size, font or stroke (those are the layer's `style`); `\n` starts a line; `style.align: 'left'` for code; `visibleChars` types over all the spans; it becomes a group of text layers `<name>.0…`).
55
55
 
56
- **image** — `asset`, `tint`, `colorSpace: 'rgb'|'oklab'|'oklch'`, `scaleMode: 'nearest'` (hard square pixels when scaled up: pixel art; default `'linear'`, smooth; it belongs to the asset). Default anchor top-left; natural size = `w`,`h`.
56
+ **image** — `asset`, `tint`, `colorSpace: 'rgb'|'oklab'|'oklch'`, `scaleMode: 'nearest'` (hard square pixels when scaled up: pixel art; default `'linear'`, smooth; it belongs to the asset). Default anchor top-left; natural size = `w`,`h`. `scale: 2` keeps the ratio; **`width` or `height` alone sets that side only and stretches the picture** (PixiJS: it changes `scale.x` or `scale.y`); `initial: { scale: 'cover' | 'contain' }` fits the image to the canvas (as for video) and keeps its ratio.
57
57
 
58
58
  **video** — `asset`, `loop`, `audio`, `volume`, **`speed`**; `initial: { scale: 'cover' }`. `speed: 0.5` slow motion, `speed: -1` backward (it starts at |speed| × `duration`, so a 3 s file with no `duration` plays whole, backward); with no `duration` the layer lasts the file's length ÷ |speed|. For anything that is not one straight speed — a freeze, a ramp, a loop — animate **`time`** (seconds in the file) like any property: `keyframes: [{ at: 0, from: { time: 0 }, to: { time: 2 }, duration: 1 }, { at: 1, to: { time: 2 }, duration: 0.5 }, { at: 1.5, to: { time: 6 }, duration: 3, ease: 'power2.inOut' }]` (play 2 s, hold, then accelerate). `repeat` loops it, `yoyo` ping-pongs. `speed` itself is not animatable (it warns): animate `time`. The sound follows (pitch too, like a tape).
59
59
 
@@ -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.47.3/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.4/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.47.3/dist/index.js",
41
- "pixi-effects/controller": "https://cdn.jsdelivr.net/npm/pixi-effects@0.47.3/dist/Controller.js",
42
- "pixi-effects/present": "https://cdn.jsdelivr.net/npm/pixi-effects@0.47.3/dist/present.js",
43
- "pixi-effects/three": "https://cdn.jsdelivr.net/npm/pixi-effects@0.47.3/dist/three.js",
40
+ "pixi-effects": "https://cdn.jsdelivr.net/npm/pixi-effects@0.47.4/dist/index.js",
41
+ "pixi-effects/controller": "https://cdn.jsdelivr.net/npm/pixi-effects@0.47.4/dist/Controller.js",
42
+ "pixi-effects/present": "https://cdn.jsdelivr.net/npm/pixi-effects@0.47.4/dist/present.js",
43
+ "pixi-effects/three": "https://cdn.jsdelivr.net/npm/pixi-effects@0.47.4/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.47.3/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.4/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-HX73PCVU.js';
2
- import './chunk-HGKXEPDJ.js';
3
- import './chunk-PC7DFDCV.js';
4
- import './chunk-JVO2W77G.js';
5
- import './chunk-BXOCEZ4U.js';
6
- //# sourceMappingURL=Composition-6TXRN65C.js.map
7
- //# sourceMappingURL=Composition-6TXRN65C.js.map
@@ -1,16 +0,0 @@
1
- 'use strict';
2
-
3
- var chunkGDY56V74_cjs = require('./chunk-GDY56V74.cjs');
4
- require('./chunk-33OVRSXF.cjs');
5
- require('./chunk-RU4N3B5B.cjs');
6
- require('./chunk-YSTOH5PC.cjs');
7
- require('./chunk-TWZNOUM7.cjs');
8
-
9
-
10
-
11
- Object.defineProperty(exports, "CompositionSequence", {
12
- enumerable: true,
13
- get: function () { return chunkGDY56V74_cjs.CompositionSequence; }
14
- });
15
- //# sourceMappingURL=Composition-IC274CKO.cjs.map
16
- //# sourceMappingURL=Composition-IC274CKO.cjs.map