pixi-effects 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/README.md +6 -6
  3. package/ai/SKILL.md +3 -1
  4. package/ai/reference/cheatsheet.md +6 -5
  5. package/ai/reference/pitfalls.md +24 -3
  6. package/ai/template.html +3 -3
  7. package/dist/{Base-Ba4Ta4ap.d.ts → Base-5R7C5pHR.d.cts} +7 -1
  8. package/dist/{Base-BDw6dKRB.d.cts → Base-DhY9oMxr.d.ts} +7 -1
  9. package/dist/Composition-D7RCAGEA.cjs +13 -0
  10. package/dist/{Composition-6FDH5OPM.cjs.map → Composition-D7RCAGEA.cjs.map} +1 -1
  11. package/dist/Composition-RGTRZ2JX.js +4 -0
  12. package/dist/{Composition-FEYAUMFI.js.map → Composition-RGTRZ2JX.js.map} +1 -1
  13. package/dist/Controller.d.cts +2 -2
  14. package/dist/Controller.d.ts +2 -2
  15. package/dist/{Movie-W8ISiEBB.d.cts → Movie-CghFXprP.d.cts} +1 -1
  16. package/dist/{Movie-DMpaT67V.d.ts → Movie-DRZtIGU7.d.ts} +1 -1
  17. package/dist/{chunk-SIRVULFF.cjs → chunk-6BQ6IEW2.cjs} +119 -36
  18. package/dist/chunk-6BQ6IEW2.cjs.map +1 -0
  19. package/dist/{chunk-DIJG2RSF.js → chunk-A574IA4F.js} +44 -6
  20. package/dist/chunk-A574IA4F.js.map +1 -0
  21. package/dist/{chunk-ZL262ZRM.cjs → chunk-QGBOC5YL.cjs} +56 -18
  22. package/dist/chunk-QGBOC5YL.cjs.map +1 -0
  23. package/dist/{chunk-PN5A6QA7.js → chunk-ZQX7WIYL.js} +119 -37
  24. package/dist/chunk-ZQX7WIYL.js.map +1 -0
  25. package/dist/index.cjs +58 -16
  26. package/dist/index.cjs.map +1 -1
  27. package/dist/index.d.cts +4 -4
  28. package/dist/index.d.ts +4 -4
  29. package/dist/index.js +50 -8
  30. package/dist/index.js.map +1 -1
  31. package/dist/three.cjs +5 -5
  32. package/dist/three.d.cts +2 -2
  33. package/dist/three.d.ts +2 -2
  34. package/dist/three.js +1 -1
  35. package/dist/{types-3c8Vymgw.d.cts → types-CRE9WKd4.d.cts} +6 -0
  36. package/dist/{types-3c8Vymgw.d.ts → types-CRE9WKd4.d.ts} +6 -0
  37. package/docs/dsl.md +12 -1
  38. package/llms-full.txt +45 -10
  39. package/package.json +2 -1
  40. package/dist/Composition-6FDH5OPM.cjs +0 -13
  41. package/dist/Composition-FEYAUMFI.js +0 -4
  42. package/dist/chunk-DIJG2RSF.js.map +0 -1
  43. package/dist/chunk-PN5A6QA7.js.map +0 -1
  44. package/dist/chunk-SIRVULFF.cjs.map +0 -1
  45. package/dist/chunk-ZL262ZRM.cjs.map +0 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,29 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.4.0
4
+
5
+ **Changed**
6
+
7
+ - A mask with no `at` of its own now starts and ends with the layer it masks, so its keyframes are measured from that layer's start (as `docs/dsl.md` always said: "shares its lifetime"). An explicit mask `at` is still composition time. **Migration:** a mask reveal that used `at: 0` on a layer that appears later now plays when the layer appears (before, it had finished long before). Give the mask an explicit `at` to keep the old timing.
8
+ - An `audio` layer with `loop: true` and no `duration` lasts until the composition ends (it used to stop after one clip length). Without `loop` and without `duration` it lasts as long as its clip, so a one-shot sound effect needs no `duration`.
9
+
10
+ **Fixed**
11
+
12
+ - **Volume keyframes** behaved unlike every other keyframe: a `to` ramped from the end of the previous keyframe, so the documented fade-out `{ at: -2, to: { volume: 0 }, duration: 2 }` faded the music across the whole piece, and `set` ramped towards its time instead of jumping. Each change now holds the current value until its own start, and keyframes apply in time order.
13
+ - A `set` keyframe (text `{value}` counter and fill, image tint, shape style) stayed applied after seeking back before it.
14
+ - **A shape could be missing after a jump seek** (contact sheet, snapshot, scrubbing): geometry was drawn only while rendering, after the culler had measured the previous frame's bounds, so a rect growing from `width: 0` at the left edge was culled. Shapes now redraw before the cull.
15
+ - `threeD` layers whose size changes (a growing shape, text) no longer make Pixi warn "destroyed while still bound": the render texture is resized in place.
16
+ - Text `style` words (`stroke: { join: 'round' }`, named colours, `textBaseline`, `whiteSpace`) were parsed as expressions and warned.
17
+ - `movie.inspect` judges overlaps on what is visible (cut to the canvas and the layer's mask), so a ticker parked off the canvas is not reported, and does not report the two scenes of a running transition.
18
+ - The same "keyframe starts after the layer ends" warning, printed once per layer by a loop, is summarised after three.
19
+ - `docs/dsl.md` contradicted itself on whether text content can change (it can, through `{value}`).
20
+
21
+ **Added**
22
+
23
+ - `blendMode: 'normal' | 'add' | 'screen' | 'multiply'` on every layer (a composition's children inherit it): glows and light leaks that add up.
24
+ - A new `sfx-swoosh.mp3` in `examples/_assets` (an airy band-pass noise sweep, 0.55 s, stereo) replacing the old one.
25
+ - `examples/gallery`: 30 portfolio pieces written as plain data by three AI models, with a showcase page, and the stumble notes that fed these fixes. `ai/reference/pitfalls.md` items 32–48.
26
+
3
27
  ## 0.3.0
4
28
 
5
29
  **Breaking**
package/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # pixi-effects
2
2
 
3
- > **Status**: experimental — current release `0.3.0`. The API may still change between minor versions (see [CHANGELOG](./CHANGELOG.md): `0.3.0` changed keyframe `at` to be relative to the layer).
3
+ > **Status**: experimental — current release `0.4.0`. The API may still change between minor versions (see [CHANGELOG](./CHANGELOG.md): `0.4.0` makes a mask share its layer's lifetime, `0.3.0` made keyframe `at` relative to the layer).
4
4
 
5
- **[Live demos →](https://yjmtmtk.github.io/pixi-effects/)** · 12 numbered examples + an in-browser playground.
5
+ **[Gallery →](https://yjmtmtk.github.io/pixi-effects/examples/gallery/)** · 30 portfolio pieces written as plain data by AI models · **[Live demos →](https://yjmtmtk.github.io/pixi-effects/)** · 12 numbered examples + an in-browser playground.
6
6
 
7
7
  Declarative composition and video rendering for the web. After Effects-style timelines on top of [PixiJS v8](https://pixijs.com/) and [GSAP](https://gsap.com/), with strict TypeScript types. Render to MP4 / WebM / MOV via [mediabunny](https://mediabunny.dev/).
8
8
 
@@ -35,8 +35,8 @@ Drop the imports into an [importmap](https://developer.mozilla.org/docs/Web/HTML
35
35
  "gsap": "https://esm.sh/gsap@3.12.5",
36
36
  "gsap/PixiPlugin": "https://esm.sh/gsap@3.12.5/PixiPlugin",
37
37
  "mediabunny": "https://esm.sh/mediabunny",
38
- "pixi-effects": "https://cdn.jsdelivr.net/npm/pixi-effects@0.3.0/dist/index.js",
39
- "pixi-effects/controller": "https://cdn.jsdelivr.net/npm/pixi-effects@0.3.0/dist/Controller.js"
38
+ "pixi-effects": "https://cdn.jsdelivr.net/npm/pixi-effects@0.4.0/dist/index.js",
39
+ "pixi-effects/controller": "https://cdn.jsdelivr.net/npm/pixi-effects@0.4.0/dist/Controller.js"
40
40
  }
41
41
  }
42
42
  </script>
@@ -47,7 +47,7 @@ Drop the imports into an [importmap](https://developer.mozilla.org/docs/Web/HTML
47
47
  </script>
48
48
  ```
49
49
 
50
- Load the `dist/` files **as they are** (jsDelivr's `/npm/…/dist/…`, or unpkg's `https://unpkg.com/pixi-effects@0.3.0/dist/index.js`) rather than a CDN-rebundled build such as `esm.sh/pixi-effects` or jsDelivr's `+esm`: the entries (`pixi-effects`, `…/controller`, `…/three`) share internal chunks, which only works when each file is served untouched.
50
+ Load the `dist/` files **as they are** (jsDelivr's `/npm/…/dist/…`, or unpkg's `https://unpkg.com/pixi-effects@0.4.0/dist/index.js`) rather than a CDN-rebundled build such as `esm.sh/pixi-effects` or jsDelivr's `+esm`: the entries (`pixi-effects`, `…/controller`, `…/three`) share internal chunks, which only works when each file is served untouched.
51
51
 
52
52
  > **Using three.js?** Add two more entries (`three` and `pixi-effects/three`) to this importmap — see [Adding three.js](#adding-threejs-optional) below.
53
53
  >
@@ -63,7 +63,7 @@ Add two more entries to the importmap above: three.js itself, and the `pixi-effe
63
63
  "imports": {
64
64
  "...": "(everything from the importmap above)",
65
65
  "three": "https://esm.sh/three@0.178.0",
66
- "pixi-effects/three": "https://cdn.jsdelivr.net/npm/pixi-effects@0.3.0/dist/three.js"
66
+ "pixi-effects/three": "https://cdn.jsdelivr.net/npm/pixi-effects@0.4.0/dist/three.js"
67
67
  }
68
68
  }
69
69
  </script>
package/ai/SKILL.md CHANGED
@@ -28,7 +28,9 @@ You describe a video as a tree of plain objects; the library plays it in a canva
28
28
  - **`audio` shorter than its layer goes silent** — add `loop: true`.
29
29
  - **Masks live in the parent's coordinates and don't follow the masked layer.** **`filterArea` is in the layer's own coordinates.**
30
30
  - **2.5D needs `threeD: true`** on each layer plus a `{ type: 'camera' }` layer; camera props go in `initial`/keyframes; keep every `z` below the camera distance (≈ 989 at 720p, fov 40); under a dolly zoom only `z = 0` stays fixed.
31
- - **Built in:** `fillGradient` (linear/radial, alpha stops → vignettes), `{value}` counters, `repeat`/`yoyo`, `orbit()`. **Not in the DSL (hand-roll, recipes exist or loops are easy):** other per-frame curves, per-letter text animators (one layer per letter), blend modes, particle emitters, group/parent layers.
31
+ - **Built in:** `fillGradient` (linear/radial, alpha stops → vignettes), `{value}` counters, `repeat`/`yoyo`, `orbit()`. **Not in the DSL (hand-roll, recipes exist or loops are easy):** arc / progress-ring and stroke draw-on, text that changes over time (other than `{value}`), other per-frame curves and jitter, per-letter text animators (one layer per letter), particle emitters, group/parent layers.
32
+ - **Blend modes:** `blendMode: 'add' | 'screen' | 'multiply'` on any layer (glows, light leaks).
33
+ - **One `Movie` per page.** A sound effect needs no `duration` (it lasts as long as its clip). Seeking is safe: counters, colours and growing shapes are right after any jump.
32
34
 
33
35
  ## Layout sanity (the cheap bugs a screenshot catches)
34
36
 
@@ -36,24 +36,25 @@ name, at, duration,
36
36
  initial: { ...props applied before any keyframe },
37
37
  keyframes: [ { at, duration, ease, set | to | from | (from + to), repeat?, yoyo?, repeatDelay? } ],
38
38
  filters: [ { type: 'chromaKey', keyColor, threshold, smoothing, spill } | { type: 'custom', name, filter: <Pixi Filter> } ],
39
- mask: <a layer spec>, maskInverted,
39
+ mask: <a layer spec>, maskInverted, // a mask with no `at` of its own starts and ends with the layer it masks (its keyframes count from that layer's start); an explicit mask `at` is composition time
40
40
  filterArea: { x, y, width, height } // in the layer's OWN coordinates; lets blur/glow draw past the layer's bounds
41
41
  threeD: true // opt into 2.5D (see below)
42
+ blendMode: 'normal' | 'add' | 'screen' | 'multiply' // add / screen brighten (glows, light leaks: overlapping halos add up); on a composition its children inherit the mode
42
43
  ```
43
44
 
44
45
  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>'`.
45
46
 
46
- Keyframe kinds: `set` (jump), `to` (animate to), `from` (animate from), `from`+`to`. `repeat` (finite extra plays), `yoyo`, `repeatDelay` loop any of them (endless repeats are not allowed). `ease` = any GSAP ease (`'none'`, `'power2.out'`, `'expo.out'`, `'back.out(1.7)'`, `'elastic.out(1,0.5)'`, `'sine.inOut'`, `'bounce.out'`…). Default ease is linear.
47
+ Keyframe kinds: `set` (jump at `at`; undone when you seek back), `to` (animate to), `from` (animate from), `from`+`to`. `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` = any GSAP ease (`'none'`, `'power2.out'`, `'expo.out'`, `'back.out(1.7)'`, `'elastic.out(1,0.5)'`, `'sine.inOut'`, `'bounce.out'`…). Default ease is linear.
47
48
 
48
49
  ## Layer types
49
50
 
50
- **text** — `text`, `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 }`) — that is how counters work. Other content cannot change over time.
51
+ **text** — `text`, `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 }`) — that is how counters work. Other content cannot change over time. Multi-line text is centre-aligned by default (`style.align: 'left'` to change).
51
52
 
52
53
  **image** — `asset`, `tint`, `colorSpace: 'rgb'|'oklab'|'oklch'`. Default anchor top-left; natural size = `w`,`h`.
53
54
 
54
55
  **video** — `asset`, `loop`, `audio`, `volume`; `initial: { scale: 'cover' }`.
55
56
 
56
- **audio** — `asset`, `loop`, `volume`; fade with volume keyframes. **A file shorter than the layer goes silent unless `loop: true`.**
57
+ **audio** — `asset`, `loop`, `volume`; fade with volume keyframes (`{ at: -2, to: { volume: 0 }, duration: 2 }` fades the last 2 s). **Omit `duration` for a one-shot sound effect: it lasts exactly as long as its clip.** With `loop: true` and no `duration` it lasts until the composition ends. An explicit `duration` longer than the clip goes silent after the clip unless `loop: true`.
57
58
 
58
59
  **shape** — `shape: 'rect' | 'circle' | 'ellipse' | 'line' | 'polygon' | 'path'`:
59
60
 
@@ -68,7 +69,7 @@ Keyframe kinds: `set` (jump), `to` (animate to), `from` (animate from), `from`+`
68
69
 
69
70
  Style (`initial` / keyframes): `fillColor fillAlpha strokeColor strokeAlpha strokeWidth`; `colorSpace: 'oklab'|'oklch'` for clean colour tweens. rect/circle/ellipse are **centred on `x,y` by default** (`anchorX/anchorY` default 0.5): for a bar growing from its base use `anchorY: 1` (or `anchorX: 0` for left-to-right). **`fillGradient`** (top level or `initial`; instead of `fillColor`): `{ type?: 'linear'|'radial', stops: [[0, '#000'], [1, 'rgba(0,0,0,.6)']], angle? /* linear, deg, 90 = top→bottom */, center?, innerRadius?, radius? /* radial, 0–1 of bounds */ }` — stops may have alpha, so a radial transparent→dark is a vignette.
70
71
 
71
- **composition** — `width height duration sequences transitions`. With `threeD: true` it is a **card**: its children are drawn into one texture and move / rotate in depth together. Children use the composition's local coordinates and times. Position/rotate/scale it as a unit; to rotate/scale about its centre set `pivotX/pivotY` to the centre and `x/y` to where that point should sit.
72
+ **composition** — `width height duration sequences transitions`. With `threeD: true` it is a **card**: its children are drawn into one texture (content outside its `width × height` is clipped: size it for a soft shadow too) and move / rotate in depth together. Children use the composition's local coordinates and times. Position/rotate/scale it as a unit; to rotate/scale about its centre set `pivotX/pivotY` to the centre and `x/y` to where that point should sit.
72
73
 
73
74
  **camera** (2.5D) — props in `initial`/keyframes: `x y z lookAtX lookAtY lookAtZ fov` (defaults: centred, looking at the z = 0 plane, `fov` 40, `z` auto = `(H/2)/tan(fov/2)` ≈ 989 at 720p). Never on the camera object itself. An orbit is `orbit()`; by hand: `x = cx + R·sin θ`, `z = R·cos θ`, `R = (H/2)/tan(fov/2)`, `lookAt` = the centre at `z = 0`.
74
75
 
@@ -1,6 +1,6 @@
1
1
  # pixi-effects pitfalls — what actually went wrong
2
2
 
3
- Collected by having fresh AI sessions build six different pieces (kinetic type, news lower-third, slideshow, 2.5D showcase, three.js title, data-driven bar chart) from the docs alone. Each entry is a real stumble. **Status**: `fixed` = the library now handles or warns; `docs` = nothing can catch it, so remember it; `hand-roll` = the DSL has no feature, see the workaround.
3
+ Collected by having fresh AI sessions build pieces from the docs alone: first six (kinetic type, news lower-third, slideshow, 2.5D showcase, three.js title, bar chart), then a portfolio of 16 more (items 32 and up), made by three different models. Each entry is a real stumble. **Status**: `fixed` = the library now handles or warns; `docs` = nothing can catch it, so remember it; `hand-roll` = the DSL has no feature, see the workaround.
4
4
 
5
5
  A good habit that catches most of these: after `init`, read `window.__logs` (see `ai/template.html`) — every pixi-effects warning says what to change.
6
6
 
@@ -10,7 +10,7 @@ A good habit that catches most of these: after `init`, read `window.__logs` (see
10
10
  2. **A layer's own `at` and a transition's `at` are composition time** (seconds from the parent composition's start). Only keyframes are layer-local.
11
11
  3. **Layers are hidden, not removed, after `[at, at+duration)`** and keep their last values. Z-order is array order (later = on top). To switch scenes, stack scenes with `at`/`duration`, each starting with an opaque full-screen rect. *(docs)*
12
12
  4. **Transitions need overlapping lifespans.** Both layers must be alive for the whole window: scene `i` starts at `i·(L−T)`, the transition's `at` is the next scene's start, total = `n·(L−T)+T`. A scene that ends while the transition is still running leaves a gap. *(docs)*
13
- 5. **Audio shorter than its layer goes silent** — `bgm.mp3` is 6 s. Add `loop: true` or shorten the layer. *(fixed: warns)* Volume fades: `volume: 0` + `{ at: 0, to: { volume: v }, duration: 2 }` + `{ at: -2, to: { volume: 0 }, duration: 2 }`.
13
+ 5. **Audio length.** Omit `duration` on a one-shot sound effect: the layer lasts exactly as long as its clip (pop 0.13 s, click 0.1 s, chime 0.6 s, swoosh 0.55 s; nothing to measure). `loop: true` with no `duration` lasts until the composition ends. An explicit `duration` longer than the clip goes silent after the clip unless `loop: true` (`bgm.mp3` is 6 s) and warns. Volume fades work like any keyframe: `volume: 0` + `{ at: 0, to: { volume: v }, duration: 2 }` + `{ at: -2, to: { volume: 0 }, duration: 2 }` (the last 2 s only; `set` is a jump). *(fixed: volume keyframes used to ramp from the previous keyframe's end, so the music faded across the whole piece)*
14
14
  6. **Snap times to the frame grid (`Math.round(t*30)/30`) when two things must stay locked** (a bar and its label). Drive both from one JS easing function with per-frame `set` keyframes rather than mixing a tween with computed positions. *(docs)*
15
15
  7. **Pick eases on purpose.** `expo.in` stays tiny until the very end (a "flood" circle never reaches the corners — use a bigger radius and `power2.in`). A zoom transition with `power2.out` looks like a cut because the incoming scene is nearly opaque after 40 %. Default is linear. *(docs)*
16
16
  8. **`render()` runs in roughly real time** (6 s of 720p ≈ 6 s). Listen to `movie.on('progress', e => e.progress)` (0–100). *(docs)*
@@ -20,7 +20,7 @@ A good habit that catches most of these: after `init`, read `window.__logs` (see
20
20
  9. **Defaults differ by type.** rect/circle/ellipse are centred on `x,y`; text and image are top-left; a composition has no anchor — use `pivotX/pivotY` and set `x,y` to where the pivot should land. For a bar growing from its base use `anchorY: 1` (or `anchorX: 0`). *(docs)*
21
21
  10. **Shape geometry may be at the top level or in `initial`** (`width height radius anchorX …`). *(fixed: `initial` used to be silently ignored)*
22
22
  11. **Angles are degrees** — `rotation`, `skew*`, `rotationX/Y` — and `rotation: -0.2` is almost no rotation. *(docs)*
23
- 12. **Text**: anchor 0.5 centres the line *box*, not the cap height (nudge underlines by ~`0.46·size`). Arial Black is ~0.8·size wide per glyph, monospace ~0.6·size; leave ~7 % for hold-scale. `style` accepts any PixiJS TextStyle field: `letterSpacing`, `wordWrap` + `wordWrapWidth`, `lineHeight`, `stroke`, `dropShadow`, `padding`. A glow (`dropShadow` with `blur`) is clipped unless `padding ≥ 2·blur`. *(docs)*
23
+ 12. **Text**: anchor 0.5 centres the line *box*, not the cap height (an underline under serif or italic text needs ~`0.9·size` below the centre: `0.46·size` touches the descenders of a g or y). Arial Black is ~0.8·size wide per glyph, monospace ~0.6·size; leave ~7 % for hold-scale. `style` accepts any PixiJS TextStyle field: `letterSpacing`, `wordWrap` + `wordWrapWidth`, `lineHeight`, `stroke`, `dropShadow`, `padding`. A glow (`dropShadow` with `blur`) is clipped unless `padding ≥ 2·blur`. *(docs)*
24
24
  13. **`w` / `h` in expressions** are the layer's own size. For text they now describe the styled text (`x: '-w'` scrolls a marquee fully out). *(fixed)*
25
25
  14. **Masks live in the parent's coordinate space and do not follow the masked layer.** Reveal by growing the mask's `width` from a fixed edge (`anchorX: 0`); exit by tweening `x` and `width` together; slide the content separately. *(docs)*
26
26
  15. **`line` / `polygon` / `path` use plain canvas coordinates** when you give no `x,y` (they used to be drawn around (0,0)); with `x,y` those place the shape's centre. Stroke colour and width go in `initial`. A background pill must be sized from its text (~0.58 × fontSize per glyph + padding): `inspect` does not check text against shapes. *(fixed)*
@@ -52,3 +52,24 @@ A good habit that catches most of these: after `init`, read `window.__logs` (see
52
52
  30b. **Unknown option names now warn** (`contactSheet({ cols: 2 })` → did you mean `columns`; `init({ fps })` → `frameRate`). Layer names show up in `inspect` paths, so give layers a `name`. `inspect` ignores faint (alpha < 0.3) layers and text whose x / y is animated (tickers), so a scrolling marquee is not reported as cut off. Pixi may print `[BindGroup] … destroyed while still bound` warnings at `movie.destroy()`: harmless (playback and seeking never warn). *(docs)*
53
53
  30c. **Look with the built-in tools, not OS screenshots**: `await movie.contactSheet({ count: 6, as: 'dataURL' })` returns one labelled image of the whole animation; `await movie.inspect(frame)` returns every layer's canvas bounds and `issues`. If you do use a browser tool, `screenshot` needs an **absolute path**, macOS `sed -i` needs `''`, and a static server may redirect `/x.html` → `/x` and drop `?query`. Keep the template's `try/catch` around `init` so failures land in `window.__logs`. Check ≥ 5 timestamps including mid-transition. *(fixed: tools built in)*
54
54
  31. **Check audio without rendering by decoding the render**: `const b = await movie.render({format:'mp4'})` then `new OfflineAudioContext(...).decodeAudioData(await b.arrayBuffer())` and measure RMS over time. `movie.audioBuffer` holds the mix. *(docs)*
55
+
56
+ ## Round 2 — found by building 16 portfolio pieces (examples/gallery)
57
+
58
+ 32. **Seeking is safe now** *(fixed)*: a `set` keyframe (counter value, text fill, image tint, shape style) is undone when you seek back before it, and shapes are redrawn before they are culled, so a rect growing from `width: 0` at the left edge draws after any jump seek (contact sheet, snapshot, scrubbing, export). The old workarounds (`width: 0.01`, a one-frame `to` instead of `set`) are not needed.
59
+ 33. **A mask starts and ends with the layer it masks** *(fixed)*, so its keyframes count from that layer's start. A mask with an explicit `at` keeps composition time. Before, a mask `at: 0` reveal had finished before a layer that appeared at 8 s, and the layer stayed invisible without any warning.
60
+ 34. **`from` and `from+to` show their start value from the LAYER's start**, not from the keyframe's `at` (GSAP behaviour): a delayed fade-in `{ at: 2, from: { alpha: 0 }, to: { alpha: 1 } }` needs no `initial.alpha`. To keep the current value until `at` and then jump, use `set`. *(docs)*
61
+ 35. **One `Movie` per page.** Creating a second Movie (for an isolated repro) and destroying it breaks the first (`null.clear`). Reload instead. *(docs)*
62
+ 36. **Multi-line text is centre-aligned by default** (`style.align: 'left'` for left-aligned paragraphs). *(docs)*
63
+ 37. **A `threeD` composition (a card) clips its children at its own width × height.** Size the card for its soft shadow too. A growing `threeD` shape or text is fine: its texture is resized in place (it used to warn "destroyed while still bound"). *(docs / fixed)*
64
+ 38. **Under an orbit the side the camera swings toward becomes the NEAR side**: a "far" card at `z: −200` can be drawn bigger than 1× on that side and run off the frame (`inspect` only checks text). Pull those cards inward / lower `z`, and check a frame at the extreme of the sweep. After `orbit()` ends the default camera returns, so to hold the final pose add a camera layer that starts there. Near particles balloon under a dolly zoom: cap particle `z` at about +150, or one becomes a fake full stop. *(docs)*
65
+ 39. **`fillGradient` stops span the whole unmasked shape**, not the visible part: under a mask, place the stops for the visible fraction (e.g. 0.64→1). A translucent shape over near-black reads as grey: put the alpha in the gradient stops. *(docs)*
66
+ 40. **Rotate a polygon / path about a chosen point** by wrapping it in a `composition` with `pivotX/pivotY` (its centre is otherwise the bounds centre). *(docs)*
67
+ 41. **Glow a whole set of layers** by putting them in one `composition` with a blur filter and `filterArea` (only a composition carries a filter for its children): wide enough for the blur, and the set is built once, so the extra copy is just a second group. *(docs)*
68
+ 42. **Transition looks:** the default `dissolve` reads as a hard black-and-white camouflage and `scale: 220` as static; `scale: 90, smoothing: 0.5` is a soft film dissolve. The default edge softness of `iris` (0.02) and `wipe` blurs flat graphics at 720p: `smoothing: 0.004`–`0.01` gives a clean edge. *(docs)*
69
+ 43. **Layers built in a loop need their z-order planned**: anything pushed to the array after the loop is drawn over what the loop made (rings crossed over a header). Keyframes on different properties of one layer may overlap in time. Keep a subject in the upper ~60 % of a source image if a caption will sit under it (`inspect` does not look at picture content). *(docs)*
70
+ 44. **`{value}` has no number padding**: a clock or timecode cannot show `:05`. Keep the value in a range that always has the same digit count, or split digits into two counters. *(hand-roll)*
71
+ 45. **The same mistake in a loop warns once**: the warning "keyframe starts after the layer ends" is summarised after three layers. The usual cause is an off-by-one (`f <= frames` instead of `f < frames`) that puts the last sample exactly at the layer's end. *(fixed)*
72
+ 46. **Still not in the DSL (hand-roll; round-two notes have workarounds):** arc / progress-ring primitive and stroke draw-on / trim path (use a growing rect mask for a one-direction reveal, tick marks, or pre-drawn arcs shown one per frame), text content that changes over time (only `{value}`), per-frame jitter (film grain = seeded `set` keyframes on an oversized image), particle emitters, per-letter / per-line text split (measure with `canvas.measureText` using Pixi's font string), shared masks, `zIndex`.
73
+ 46b. **Blend modes exist now** (`blendMode: 'add' | 'screen' | 'multiply'` on any layer, threeD layers and compositions included; a composition's children inherit it). Additive white cores no longer read grey on a coloured background. *(fixed)*
74
+ 47. **Browser tooling:** the `agent-browser` daemon is shared, so a `contactSheet` can come back from another session: after saving an image, look at it and confirm it is YOUR piece. In zsh a variable holding `agent-browser --session x` is not split into words (use a small wrapper script). `inspect` now clips overlaps to the visible area and ignores the two scenes of a running transition; it still does not compare text with picture content or shapes.
75
+ 48. **A composition's `alpha` is applied to each child separately, not to the flattened group** (measured: two overlapping opaque circles in a composition at `alpha: 0.5` show the lower one through the upper one in the overlap). Fading a group that has overlapping children therefore shows the overlap; fade the children themselves, or keep overlapping parts out of the group. Expressions have no way to read a sibling's size: measure text with `canvas.measureText` (the font string Pixi uses) and compute pill widths in JS. *(docs)*
package/ai/template.html CHANGED
@@ -33,9 +33,9 @@
33
33
  "gsap": "https://esm.sh/gsap@3.12.5",
34
34
  "gsap/PixiPlugin": "https://esm.sh/gsap@3.12.5/PixiPlugin",
35
35
  "mediabunny": "https://esm.sh/mediabunny",
36
- "pixi-effects": "https://cdn.jsdelivr.net/npm/pixi-effects@0.3.0/dist/index.js",
37
- "pixi-effects/controller": "https://cdn.jsdelivr.net/npm/pixi-effects@0.3.0/dist/Controller.js",
38
- "pixi-effects/three": "https://cdn.jsdelivr.net/npm/pixi-effects@0.3.0/dist/three.js",
36
+ "pixi-effects": "https://cdn.jsdelivr.net/npm/pixi-effects@0.4.0/dist/index.js",
37
+ "pixi-effects/controller": "https://cdn.jsdelivr.net/npm/pixi-effects@0.4.0/dist/Controller.js",
38
+ "pixi-effects/three": "https://cdn.jsdelivr.net/npm/pixi-effects@0.4.0/dist/three.js",
39
39
  "three": "https://esm.sh/three@0.178.0"
40
40
  }
41
41
  }
@@ -1,5 +1,5 @@
1
1
  import { Filter, Container, RenderTexture, Matrix } from 'pixi.js';
2
- import { S as SequenceSpec, C as CompositionShape, t as AudioDescriptor } from './types-3c8Vymgw.js';
2
+ import { S as SequenceSpec, C as CompositionShape, t as AudioDescriptor } from './types-CRE9WKd4.cjs';
3
3
  import { gsap } from 'gsap';
4
4
 
5
5
  /**
@@ -86,6 +86,12 @@ declare abstract class Sequence {
86
86
  * project their `threeD` layers; everything else has nothing to do.
87
87
  */
88
88
  updateSpace(_t: number, _host: SpaceHost): void;
89
+ /**
90
+ * Runs once per rendered frame, after the timeline seek and before the cull. Layers whose drawn
91
+ * geometry follows tweened state (shapes) redraw here, so the culler measures where the layer is NOW
92
+ * and not where it was drawn at the previous render.
93
+ */
94
+ syncFrame(): void;
89
95
  destroy(): void;
90
96
  }
91
97
 
@@ -1,5 +1,5 @@
1
1
  import { Filter, Container, RenderTexture, Matrix } from 'pixi.js';
2
- import { S as SequenceSpec, C as CompositionShape, t as AudioDescriptor } from './types-3c8Vymgw.cjs';
2
+ import { S as SequenceSpec, C as CompositionShape, t as AudioDescriptor } from './types-CRE9WKd4.js';
3
3
  import { gsap } from 'gsap';
4
4
 
5
5
  /**
@@ -86,6 +86,12 @@ declare abstract class Sequence {
86
86
  * project their `threeD` layers; everything else has nothing to do.
87
87
  */
88
88
  updateSpace(_t: number, _host: SpaceHost): void;
89
+ /**
90
+ * Runs once per rendered frame, after the timeline seek and before the cull. Layers whose drawn
91
+ * geometry follows tweened state (shapes) redraw here, so the culler measures where the layer is NOW
92
+ * and not where it was drawn at the previous render.
93
+ */
94
+ syncFrame(): void;
89
95
  destroy(): void;
90
96
  }
91
97
 
@@ -0,0 +1,13 @@
1
+ 'use strict';
2
+
3
+ var chunkQGBOC5YL_cjs = require('./chunk-QGBOC5YL.cjs');
4
+ require('./chunk-6BQ6IEW2.cjs');
5
+
6
+
7
+
8
+ Object.defineProperty(exports, "CompositionSequence", {
9
+ enumerable: true,
10
+ get: function () { return chunkQGBOC5YL_cjs.CompositionSequence; }
11
+ });
12
+ //# sourceMappingURL=Composition-D7RCAGEA.cjs.map
13
+ //# sourceMappingURL=Composition-D7RCAGEA.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":[],"names":[],"mappings":"","file":"Composition-6FDH5OPM.cjs"}
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"Composition-D7RCAGEA.cjs"}
@@ -0,0 +1,4 @@
1
+ export { CompositionSequence } from './chunk-A574IA4F.js';
2
+ import './chunk-ZQX7WIYL.js';
3
+ //# sourceMappingURL=Composition-RGTRZ2JX.js.map
4
+ //# sourceMappingURL=Composition-RGTRZ2JX.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":[],"names":[],"mappings":"","file":"Composition-FEYAUMFI.js"}
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"Composition-RGTRZ2JX.js"}
@@ -1,7 +1,7 @@
1
- import { M as Movie } from './Movie-W8ISiEBB.cjs';
1
+ import { M as Movie } from './Movie-CghFXprP.cjs';
2
2
  import 'pixi.js';
3
3
  import 'gsap';
4
- import './types-3c8Vymgw.cjs';
4
+ import './types-CRE9WKd4.cjs';
5
5
 
6
6
  declare function formatTime(seconds: number): string;
7
7
  declare function frameToPercent(frame: number, totalFrames: number): number;
@@ -1,7 +1,7 @@
1
- import { M as Movie } from './Movie-DMpaT67V.js';
1
+ import { M as Movie } from './Movie-DRZtIGU7.js';
2
2
  import 'pixi.js';
3
3
  import 'gsap';
4
- import './types-3c8Vymgw.js';
4
+ import './types-CRE9WKd4.js';
5
5
 
6
6
  declare function formatTime(seconds: number): string;
7
7
  declare function frameToPercent(frame: number, totalFrames: number): number;
@@ -1,6 +1,6 @@
1
1
  import { Application } from 'pixi.js';
2
2
  import { gsap } from 'gsap';
3
- import { A as AssetSpec, g as CompositionSpec } from './types-3c8Vymgw.cjs';
3
+ import { A as AssetSpec, g as CompositionSpec } from './types-CRE9WKd4.cjs';
4
4
 
5
5
  interface Rect {
6
6
  x: number;
@@ -1,6 +1,6 @@
1
1
  import { Application } from 'pixi.js';
2
2
  import { gsap } from 'gsap';
3
- import { A as AssetSpec, g as CompositionSpec } from './types-3c8Vymgw.js';
3
+ import { A as AssetSpec, g as CompositionSpec } from './types-CRE9WKd4.js';
4
4
 
5
5
  interface Rect {
6
6
  x: number;
@@ -798,6 +798,14 @@ var Sequence = class {
798
798
  */
799
799
  updateSpace(_t, _host) {
800
800
  }
801
+ /**
802
+ * Runs once per rendered frame, after the timeline seek and before the cull. Layers whose drawn
803
+ * geometry follows tweened state (shapes) redraw here, so the culler measures where the layer is NOW
804
+ * and not where it was drawn at the previous render.
805
+ */
806
+ syncFrame() {
807
+ this.maskSequence?.syncFrame();
808
+ }
801
809
  destroy() {
802
810
  this.maskSequence?.destroy();
803
811
  this.maskSequence = null;
@@ -834,11 +842,16 @@ function lintTiming(spec, parentDuration, warn = defaultWarn) {
834
842
  const kat = kf.at ?? 0;
835
843
  if (kat >= duration) {
836
844
  warn(
837
- `pixi-effects: ${who}: keyframes[${i}] starts at ${kat}s, after the layer ends (duration ${duration}s), so it never plays. Keyframe \`at\` is measured from the start of this layer, not from the start of the composition.`
845
+ `pixi-effects: ${who}: keyframes[${i}] starts at ${kat}s, after the layer ends (duration ${duration}s), so it never plays. Keyframe \`at\` is measured from the start of this layer, not from the start of the composition.`,
846
+ "late-keyframe"
838
847
  );
839
848
  }
840
849
  });
841
850
  }
851
+ function summarizeWarnings(messages, shown = 3) {
852
+ if (messages.length <= shown) return messages;
853
+ return [...messages.slice(0, shown), `pixi-effects: ${messages.length - shown} more layers with the same problem (the same loop or helper is probably building them): fix the ones above and these go away.`];
854
+ }
842
855
  function lintText(spec, warn = defaultWarn) {
843
856
  if (spec.type !== "text") return;
844
857
  if (collectPropKeys(spec).has("value") && !(spec.text ?? "").includes("{value}")) {
@@ -1013,6 +1026,27 @@ var CameraSequence = class extends Sequence {
1013
1026
  }
1014
1027
  };
1015
1028
 
1029
+ // src/core/revertibleSet.ts
1030
+ function revertibleSet(timeline, at, read, write, value) {
1031
+ let p = 0;
1032
+ let before;
1033
+ const proxy = {
1034
+ get p() {
1035
+ return p;
1036
+ },
1037
+ set p(next) {
1038
+ if (next >= 1 && p < 1) {
1039
+ before = read();
1040
+ write(value);
1041
+ } else if (next < 1 && p >= 1) {
1042
+ write(before);
1043
+ }
1044
+ p = next;
1045
+ }
1046
+ };
1047
+ timeline.set(proxy, { p: 1 }, at);
1048
+ }
1049
+
1016
1050
  // src/expr/colorInterp.ts
1017
1051
  function buildColorInterp(from, to, space) {
1018
1052
  const fromRGBA = parseToRGBA(from);
@@ -1203,9 +1237,10 @@ var ImageSequence = class extends Sequence {
1203
1237
  const toTint = kf.to?.tint;
1204
1238
  const setTint = kf.set?.tint;
1205
1239
  if (setTint !== void 0) {
1206
- timeline.call(() => {
1207
- this.target.tint = setTint;
1208
- }, [], at);
1240
+ const sprite = this.target;
1241
+ revertibleSet(timeline, at, () => sprite.tint, (c) => {
1242
+ sprite.tint = c;
1243
+ }, setTint);
1209
1244
  }
1210
1245
  if (toTint !== void 0) {
1211
1246
  tweenColor(
@@ -1252,7 +1287,21 @@ function stripKeysInKeyframe(kf, keys) {
1252
1287
  from: stripKeys(kf.from, keys)
1253
1288
  };
1254
1289
  }
1255
- var STYLE_OPAQUE_KEYS = ["fontFamily", "fill", "align", "fontStyle", "fontWeight"];
1290
+ var STYLE_OPAQUE_KEYS = [
1291
+ "fontFamily",
1292
+ "fill",
1293
+ "align",
1294
+ "fontStyle",
1295
+ "fontWeight",
1296
+ "fontVariant",
1297
+ "textBaseline",
1298
+ "whiteSpace",
1299
+ "lineJoin",
1300
+ "join",
1301
+ "cap",
1302
+ "color",
1303
+ "stroke"
1304
+ ];
1256
1305
  function formatValue(v, f) {
1257
1306
  const fixed = v.toFixed(Math.max(0, Math.floor(f?.decimals ?? 0)));
1258
1307
  if (!f?.grouping) return fixed;
@@ -1367,10 +1416,10 @@ function bindValueKeyframes(timeline, state, refresh, keyframes, parentDuration,
1367
1416
  const loop = loopVars(kf);
1368
1417
  if (setV !== void 0) {
1369
1418
  const v = resolveNumber(setV, scope);
1370
- timeline.call(() => {
1371
- state.value = v;
1419
+ revertibleSet(timeline, at, () => state.value, (n) => {
1420
+ state.value = n;
1372
1421
  refresh();
1373
- }, [], at);
1422
+ }, v);
1374
1423
  }
1375
1424
  if (toV !== void 0 && fromV !== void 0) {
1376
1425
  timeline.fromTo(
@@ -1399,10 +1448,10 @@ function bindFillKeyframes(timeline, text, keyframes, parentDuration, offset, co
1399
1448
  const fromFill = pickFill(kf.from);
1400
1449
  const toFill = pickFill(kf.to);
1401
1450
  if (setFill !== void 0) {
1402
- timeline.call(() => {
1403
- state.fill = setFill;
1451
+ revertibleSet(timeline, at, () => state.fill, (c) => {
1452
+ state.fill = c;
1404
1453
  writeFill();
1405
- }, [], at);
1454
+ }, setFill);
1406
1455
  }
1407
1456
  if (toFill !== void 0) {
1408
1457
  tweenColor(
@@ -1433,7 +1482,8 @@ var AudioSequence = class extends Sequence {
1433
1482
  const data = await pixi_js.Assets.get(this.spec.asset);
1434
1483
  this._audioBuffer = data.audioBuffer;
1435
1484
  if (this.duration === void 0) {
1436
- this.duration = this.spec.duration ?? data.duration ?? this.root.duration;
1485
+ const rest = Math.max(0, (this.parent?.duration ?? this.root.duration) - this.at);
1486
+ this.duration = this.spec.duration ?? (this.spec.loop ? rest : data.duration) ?? this.root.duration;
1437
1487
  }
1438
1488
  if (!this.spec.loop && data.duration !== void 0 && this.duration > data.duration + 0.05) {
1439
1489
  console.warn(
@@ -1449,21 +1499,32 @@ var AudioSequence = class extends Sequence {
1449
1499
  const initialVolume = this.spec.volume ?? 1;
1450
1500
  const volumeKeyframes = [];
1451
1501
  const dur = this.duration;
1452
- for (const raw of this.spec.keyframes ?? []) {
1453
- const kf = normalizeKeyframe(raw, dur);
1454
- const set = kf.set;
1455
- const to = kf.to;
1456
- const from = kf.from;
1457
- if (kf.kind === "set" && set && "volume" in set) {
1458
- volumeKeyframes.push({ time: baseTime + this.at + kf.at, value: set.volume });
1459
- } else if (kf.kind === "to" && to && "volume" in to) {
1460
- volumeKeyframes.push({ time: baseTime + this.at + kf.at + kf.duration, value: to.volume });
1461
- } else if (kf.kind === "fromTo" && to && "volume" in to) {
1462
- volumeKeyframes.push({ time: baseTime + this.at + kf.at, value: from?.volume ?? initialVolume });
1463
- volumeKeyframes.push({ time: baseTime + this.at + kf.at + kf.duration, value: to.volume });
1464
- } else if (kf.kind === "from" && from && "volume" in from) {
1465
- volumeKeyframes.push({ time: baseTime + this.at + kf.at, value: from.volume });
1466
- volumeKeyframes.push({ time: baseTime + this.at + kf.at + kf.duration, value: initialVolume });
1502
+ const t0 = baseTime + this.at;
1503
+ const keyframes = (this.spec.keyframes ?? []).map((raw) => normalizeKeyframe(raw, dur)).filter((kf) => [kf.set, kf.to, kf.from].some((p) => p && "volume" in p)).sort((x, y) => x.at - y.at);
1504
+ let current = initialVolume;
1505
+ const point = (time, value) => {
1506
+ volumeKeyframes.push({ time, value });
1507
+ };
1508
+ for (const kf of keyframes) {
1509
+ const start = t0 + kf.at;
1510
+ const end = start + kf.duration;
1511
+ const set = kf.set?.volume;
1512
+ const to = kf.to?.volume;
1513
+ const from = kf.from?.volume;
1514
+ point(start, current);
1515
+ if (kf.kind === "set" && set !== void 0) {
1516
+ point(start, set);
1517
+ current = set;
1518
+ } else if (kf.kind === "to" && to !== void 0) {
1519
+ point(end, to);
1520
+ current = to;
1521
+ } else if (kf.kind === "fromTo" && to !== void 0) {
1522
+ point(start, from ?? current);
1523
+ point(end, to);
1524
+ current = to;
1525
+ } else if (kf.kind === "from" && from !== void 0) {
1526
+ point(start, from);
1527
+ point(end, current);
1467
1528
  }
1468
1529
  }
1469
1530
  out.push({
@@ -1761,12 +1822,32 @@ var ShapeSequence = class extends Sequence {
1761
1822
  if (init.y === void 0) graphics.y = cy;
1762
1823
  }
1763
1824
  graphics.onRender = () => {
1764
- graphics.clear();
1765
- this._drawGeometry(graphics);
1766
- applyState(graphics, this._state);
1825
+ if (this._fresh) {
1826
+ this._fresh = false;
1827
+ return;
1828
+ }
1829
+ this._redraw();
1767
1830
  };
1768
1831
  this.buildFilters();
1769
1832
  }
1833
+ _fresh = false;
1834
+ _redraw() {
1835
+ const graphics = this.target;
1836
+ graphics.clear();
1837
+ this._drawGeometry(graphics);
1838
+ applyState(graphics, this._state);
1839
+ }
1840
+ /**
1841
+ * Draw from the tweened `_state` before the culler runs. The culler measures the geometry as last
1842
+ * drawn; a layer drawn only inside `onRender` is culled on stale bounds (a rect growing from
1843
+ * `width: 0` at the left edge was skipped after a jump seek, and one frame late in playback).
1844
+ */
1845
+ syncFrame() {
1846
+ super.syncFrame();
1847
+ if (!this.target?.renderable) return;
1848
+ this._redraw();
1849
+ this._fresh = true;
1850
+ }
1770
1851
  bindTimeline(timeline, offset = 0) {
1771
1852
  if (!this.target) return;
1772
1853
  const scope = this.scope();
@@ -1930,9 +2011,10 @@ function bindLiveKeyframes(timeline, state, liveKeys, keyframes, parentDuration,
1930
2011
  if (live) {
1931
2012
  for (const [k, v] of Object.entries(live)) {
1932
2013
  const resolved = resolveLiveValue(k, v, scope);
1933
- timeline.call(() => {
1934
- state[k] = resolved;
1935
- }, [], at);
2014
+ const live2 = state;
2015
+ revertibleSet(timeline, at, () => live2[k], (v2) => {
2016
+ live2[k] = v2;
2017
+ }, resolved);
1936
2018
  }
1937
2019
  }
1938
2020
  }
@@ -1991,7 +2073,7 @@ function numOrZero(v, scope) {
1991
2073
  var _CompositionSequence = null;
1992
2074
  async function getCompositionSequence() {
1993
2075
  if (!_CompositionSequence) {
1994
- _CompositionSequence = (await import('./Composition-6FDH5OPM.cjs')).CompositionSequence;
2076
+ _CompositionSequence = (await import('./Composition-D7RCAGEA.cjs')).CompositionSequence;
1995
2077
  }
1996
2078
  return _CompositionSequence;
1997
2079
  }
@@ -2045,5 +2127,6 @@ exports.lintTiming = lintTiming;
2045
2127
  exports.projectLayer = projectLayer;
2046
2128
  exports.registerSequenceType = registerSequenceType;
2047
2129
  exports.resolveAt = resolveAt;
2048
- //# sourceMappingURL=chunk-SIRVULFF.cjs.map
2049
- //# sourceMappingURL=chunk-SIRVULFF.cjs.map
2130
+ exports.summarizeWarnings = summarizeWarnings;
2131
+ //# sourceMappingURL=chunk-6BQ6IEW2.cjs.map
2132
+ //# sourceMappingURL=chunk-6BQ6IEW2.cjs.map