pixi-effects 0.16.1 → 0.16.2

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 (50) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/README.md +11 -9
  3. package/ai/SKILL.md +1 -1
  4. package/ai/chat-template.html +3 -3
  5. package/ai/reference/cheatsheet.md +7 -3
  6. package/ai/reference/pitfalls.md +1 -0
  7. package/ai/reference/recipes.md +23 -0
  8. package/ai/template.html +5 -5
  9. package/dist/{Base-qvqNxvyt.d.ts → Base-BQUodYZK.d.ts} +1 -1
  10. package/dist/{Base-DPEqyeB0.d.cts → Base-OHqp8qvX.d.cts} +1 -1
  11. package/dist/Composition-3WWNVRR5.cjs +14 -0
  12. package/dist/{Composition-ZZGSMQ4E.cjs.map → Composition-3WWNVRR5.cjs.map} +1 -1
  13. package/dist/Composition-VJWY2NDL.js +5 -0
  14. package/dist/{Composition-X2VZSVPD.js.map → Composition-VJWY2NDL.js.map} +1 -1
  15. package/dist/Controller.d.cts +2 -2
  16. package/dist/Controller.d.ts +2 -2
  17. package/dist/{Movie-BIGDIla0.d.ts → Movie-DilBC348.d.ts} +8 -2
  18. package/dist/{Movie-CJMfAizW.d.cts → Movie-oA0SUAyb.d.cts} +8 -2
  19. package/dist/Presenter.d.cts +2 -2
  20. package/dist/Presenter.d.ts +2 -2
  21. package/dist/{chunk-V54NBCWG.cjs → chunk-2ROVCPSL.cjs} +28 -27
  22. package/dist/chunk-2ROVCPSL.cjs.map +1 -0
  23. package/dist/{chunk-SPPNTV7E.cjs → chunk-FJA5VUOF.cjs} +30 -13
  24. package/dist/chunk-FJA5VUOF.cjs.map +1 -0
  25. package/dist/{chunk-I2YXLHIU.js → chunk-XTIHKODH.js} +5 -4
  26. package/dist/chunk-XTIHKODH.js.map +1 -0
  27. package/dist/{chunk-7D7RAQWM.js → chunk-ZNKQH356.js} +30 -13
  28. package/dist/chunk-ZNKQH356.js.map +1 -0
  29. package/dist/index.cjs +128 -38
  30. package/dist/index.cjs.map +1 -1
  31. package/dist/index.d.cts +37 -5
  32. package/dist/index.d.ts +37 -5
  33. package/dist/index.js +106 -17
  34. package/dist/index.js.map +1 -1
  35. package/dist/loader.css +1 -1
  36. package/dist/three.cjs +5 -5
  37. package/dist/three.d.cts +2 -2
  38. package/dist/three.d.ts +2 -2
  39. package/dist/three.js +1 -1
  40. package/dist/{types-C8XEUoqe.d.cts → types-SS51lP6O.d.cts} +7 -1
  41. package/dist/{types-C8XEUoqe.d.ts → types-SS51lP6O.d.ts} +7 -1
  42. package/docs/dsl.md +31 -1
  43. package/llms-full.txt +63 -5
  44. package/package.json +2 -1
  45. package/dist/Composition-X2VZSVPD.js +0 -5
  46. package/dist/Composition-ZZGSMQ4E.cjs +0 -14
  47. package/dist/chunk-7D7RAQWM.js.map +0 -1
  48. package/dist/chunk-I2YXLHIU.js.map +0 -1
  49. package/dist/chunk-SPPNTV7E.cjs.map +0 -1
  50. package/dist/chunk-V54NBCWG.cjs.map +0 -1
package/dist/index.d.cts CHANGED
@@ -1,7 +1,7 @@
1
- export { A as AudioInspectOptions, a as AudioReport, b as AudioSourceReport, C as ContactSheetOptions, F as FrameEvent, I as InspectOptions, c as InspectReport, L as LayerInfo, d as LoaderOption, M as MotionBlurOptions, e as MotionBlurSpec, f as Movie, g as MovieErrorEvent, h as MovieOptions, P as ProgressEvent, R as RenderOptions, S as SnapshotOptions, i as SoundMeasure, j as StopEvent, k as StopImage, l as StopImagesOptions, T as TimelineData, m as TimelineHtmlOptions, n as TimelineRow, o as TimelineSvgOptions, p as TimelineTransition, V as VolumeEvent, q as dismissLoader, r as failLoader, s as resolveLoader } from './Movie-CJMfAizW.cjs';
2
- import { S as SequenceSpec, C as CompositionShape, I as ImageSequenceSpec, a as CameraSequenceSpec, K as Keyframe, T as TextSequenceSpec, M as MusicOptions, P as Props, b as TransitionSpec, c as CompositionSpec, d as SequenceCommon } from './types-C8XEUoqe.cjs';
3
- export { A as ArcShapeSpec, e as AssetSpec, f as AudioAssetSpec, g as AudioMusicSpec, h as AudioSequenceSpec, i as AudioSfxSpec, j as ChromaKeyFilterSpec, k as CircleShapeSpec, l as CompositionSequenceSpec, m as CrossfadeTransition, n as CustomFilterSpec, D as DipTransition, o as DissolveTransition, E as EllipseShapeSpec, p as Expr, F as FilterSpec, G as GradientSpec, q as IrisTransition, L as LineShapeSpec, r as MusicDrum, s as MusicDrums, t as MusicInstrument, u as MusicLevel, v as MusicTrack, N as NullSequenceSpec, w as PathShapeSpec, x as PolygonShapeSpec, y as PropValue, R as RectShapeSpec, z as SfxKnobs, B as SfxOptions, H as SfxPreset, J as SfxVoice, O as ShapeSequenceSpec, Q as SlideTransition, U as Stop, V as StopSpec, W as TransitionCommon, X as VideoSequenceSpec, Y as WipeTransition, Z as ZoomTransition, _ as normalizeStops, $ as pictureStops } from './types-C8XEUoqe.cjs';
4
- import { S as Sequence } from './Base-DPEqyeB0.cjs';
1
+ export { A as AudioInspectOptions, a as AudioReport, b as AudioSourceReport, C as ContactSheetOptions, F as FrameEvent, I as InspectOptions, c as InspectReport, L as LayerInfo, d as LoaderOption, M as MotionBlurOptions, e as MotionBlurSpec, f as Movie, g as MovieErrorEvent, h as MovieOptions, P as ProgressEvent, R as RenderOptions, S as SnapshotOptions, i as SoundMeasure, j as StopEvent, k as StopImage, l as StopImagesOptions, T as TimelineData, m as TimelineHtmlOptions, n as TimelineRow, o as TimelineSvgOptions, p as TimelineTransition, V as VolumeEvent, q as dismissLoader, r as failLoader, s as resolveLoader } from './Movie-oA0SUAyb.cjs';
2
+ import { S as SequenceSpec, C as CompositionShape, I as ImageSequenceSpec, a as CameraSequenceSpec, K as Keyframe, T as TextSequenceSpec, M as MusicOptions, P as Props, b as TransitionSpec, c as CompositionSpec, d as SequenceCommon } from './types-SS51lP6O.cjs';
3
+ export { A as ArcShapeSpec, e as AssetSpec, f as AudioAssetSpec, g as AudioMusicSpec, h as AudioSequenceSpec, i as AudioSfxSpec, j as ChromaKeyFilterSpec, k as CircleShapeSpec, l as CompositionSequenceSpec, m as CrossfadeTransition, n as CustomFilterSpec, D as DipTransition, o as DissolveTransition, E as EllipseShapeSpec, p as Expr, F as FilterSpec, G as GradientSpec, q as IrisTransition, L as LineShapeSpec, r as MusicDrum, s as MusicDrums, t as MusicInstrument, u as MusicLevel, v as MusicTrack, N as NullSequenceSpec, w as PathShapeSpec, x as PolygonShapeSpec, y as PropValue, R as RectShapeSpec, z as SfxKnobs, B as SfxOptions, H as SfxPreset, J as SfxVoice, O as ShapeSequenceSpec, Q as SlideTransition, U as Stop, V as StopSpec, W as TransitionCommon, X as VideoSequenceSpec, Y as WipeTransition, Z as ZoomTransition, _ as normalizeStops, $ as pictureStops } from './types-SS51lP6O.cjs';
4
+ import { S as Sequence } from './Base-OHqp8qvX.cjs';
5
5
  import 'pixi.js';
6
6
  import 'gsap';
7
7
 
@@ -268,6 +268,38 @@ interface FollowPathOptions {
268
268
  */
269
269
  declare function followPath(opts: FollowPathOptions): Keyframe[];
270
270
 
271
+ interface CameraPathOptions {
272
+ /** The route: `[x, y, z]` points the camera passes, in order (at least two). */
273
+ points: Array<[number, number, number]>;
274
+ /** Start, in seconds from the start of the camera layer. Default 0. */
275
+ at?: number;
276
+ /** How long the flight takes, in seconds. Required. */
277
+ duration: number;
278
+ /** GSAP ease for the progress along the route (default `'none'`: constant speed; `'power2.in'` is an accelerating rush). */
279
+ ease?: string;
280
+ /** Where the camera looks: a fixed `[x, y, z]`, or `'ahead'` (default): a point further along the route, so it faces where it flies. */
281
+ look?: [number, number, number] | 'ahead';
282
+ /** With `look: 'ahead'`: how far ahead, as a fraction 0–1 of the route's length (default 0.08). Past the end it continues along the last direction. */
283
+ lookAhead?: number;
284
+ /** `true` (default): a smooth curve through the points (Catmull-Rom). `false`: straight lines between them. */
285
+ smooth?: boolean;
286
+ /** Where on the route to start and stop, as fractions 0–1 of its length (default 0 → 1). `from: 1, to: 0` flies it backwards. */
287
+ from?: number;
288
+ to?: number;
289
+ /** Samples per second: set it to your movie's `frameRate`. Default 30. */
290
+ frameRate?: number;
291
+ }
292
+ /**
293
+ * A camera flight as keyframes for `x`, `y`, `z`, `lookAtX`, `lookAtY` and `lookAtZ`: put the result in a camera layer's `keyframes`.
294
+ * The route is walked by arc length (an even speed whatever the curve; an `ease` shapes it) and sampled once per frame as short
295
+ * straight steps, so the camera is exactly on the route at every rendered frame when `frameRate` matches the movie's.
296
+ *
297
+ * ```js
298
+ * { type: 'camera', keyframes: cameraPath({ points: [[640, 360, 2400], [700, 340, 900], [640, 360, -1500]], duration: 8, ease: 'power2.in', frameRate: 30 }) }
299
+ * ```
300
+ */
301
+ declare function cameraPath(opts: CameraPathOptions): Keyframe[];
302
+
271
303
  interface StaggerOptions {
272
304
  /** Seconds between neighbours (the farthest one waits `each` × its distance). Default 0.1. Not together with `amount`. */
273
305
  each?: number;
@@ -684,4 +716,4 @@ interface WithFadeOptions {
684
716
  */
685
717
  declare function withFade<T extends SequenceCommon>(spec: T, opts: WithFadeOptions): T;
686
718
 
687
- export { type AnimateTextOptions, type AnimateTextTween, type AudioEnvelope, type AudioEnvelopeSource, CameraSequenceSpec, CompositionSpec, type DeckOptions, type DeckPage, type DeckResult, type DeckTransition, type EnvelopeOptions, type FollowPathOptions, ImageSequenceSpec, type KenBurnsOptions, Keyframe, type MeasureStyle, MusicOptions, type OrbitOptions, type ParticlesOptions, Props, type ReactOptions, type ReactProp, SequenceCommon, type SequenceCtor, SequenceSpec, type SplitOptions, type StaggerOptions, type TextPiece, type TextPreset, TextSequenceSpec, type TextSize, TransitionSpec, type WiggleOptions, type WiggleProp, type WithFadeOptions, animateText, audioEnvelope, bpmEnvelope, computeEnvelope, deck, followPath, kenBurns, measureText, musicBuffer, musicEnvelope, noise, orbit, particles, rand, random, react, registerSequenceType, splitText, stagger, wiggle, withFade };
719
+ export { type AnimateTextOptions, type AnimateTextTween, type AudioEnvelope, type AudioEnvelopeSource, type CameraPathOptions, CameraSequenceSpec, CompositionSpec, type DeckOptions, type DeckPage, type DeckResult, type DeckTransition, type EnvelopeOptions, type FollowPathOptions, ImageSequenceSpec, type KenBurnsOptions, Keyframe, type MeasureStyle, MusicOptions, type OrbitOptions, type ParticlesOptions, Props, type ReactOptions, type ReactProp, SequenceCommon, type SequenceCtor, SequenceSpec, type SplitOptions, type StaggerOptions, type TextPiece, type TextPreset, TextSequenceSpec, type TextSize, TransitionSpec, type WiggleOptions, type WiggleProp, type WithFadeOptions, animateText, audioEnvelope, bpmEnvelope, cameraPath, computeEnvelope, deck, followPath, kenBurns, measureText, musicBuffer, musicEnvelope, noise, orbit, particles, rand, random, react, registerSequenceType, splitText, stagger, wiggle, withFade };
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
- export { A as AudioInspectOptions, a as AudioReport, b as AudioSourceReport, C as ContactSheetOptions, F as FrameEvent, I as InspectOptions, c as InspectReport, L as LayerInfo, d as LoaderOption, M as MotionBlurOptions, e as MotionBlurSpec, f as Movie, g as MovieErrorEvent, h as MovieOptions, P as ProgressEvent, R as RenderOptions, S as SnapshotOptions, i as SoundMeasure, j as StopEvent, k as StopImage, l as StopImagesOptions, T as TimelineData, m as TimelineHtmlOptions, n as TimelineRow, o as TimelineSvgOptions, p as TimelineTransition, V as VolumeEvent, q as dismissLoader, r as failLoader, s as resolveLoader } from './Movie-BIGDIla0.js';
2
- import { S as SequenceSpec, C as CompositionShape, I as ImageSequenceSpec, a as CameraSequenceSpec, K as Keyframe, T as TextSequenceSpec, M as MusicOptions, P as Props, b as TransitionSpec, c as CompositionSpec, d as SequenceCommon } from './types-C8XEUoqe.js';
3
- export { A as ArcShapeSpec, e as AssetSpec, f as AudioAssetSpec, g as AudioMusicSpec, h as AudioSequenceSpec, i as AudioSfxSpec, j as ChromaKeyFilterSpec, k as CircleShapeSpec, l as CompositionSequenceSpec, m as CrossfadeTransition, n as CustomFilterSpec, D as DipTransition, o as DissolveTransition, E as EllipseShapeSpec, p as Expr, F as FilterSpec, G as GradientSpec, q as IrisTransition, L as LineShapeSpec, r as MusicDrum, s as MusicDrums, t as MusicInstrument, u as MusicLevel, v as MusicTrack, N as NullSequenceSpec, w as PathShapeSpec, x as PolygonShapeSpec, y as PropValue, R as RectShapeSpec, z as SfxKnobs, B as SfxOptions, H as SfxPreset, J as SfxVoice, O as ShapeSequenceSpec, Q as SlideTransition, U as Stop, V as StopSpec, W as TransitionCommon, X as VideoSequenceSpec, Y as WipeTransition, Z as ZoomTransition, _ as normalizeStops, $ as pictureStops } from './types-C8XEUoqe.js';
4
- import { S as Sequence } from './Base-qvqNxvyt.js';
1
+ export { A as AudioInspectOptions, a as AudioReport, b as AudioSourceReport, C as ContactSheetOptions, F as FrameEvent, I as InspectOptions, c as InspectReport, L as LayerInfo, d as LoaderOption, M as MotionBlurOptions, e as MotionBlurSpec, f as Movie, g as MovieErrorEvent, h as MovieOptions, P as ProgressEvent, R as RenderOptions, S as SnapshotOptions, i as SoundMeasure, j as StopEvent, k as StopImage, l as StopImagesOptions, T as TimelineData, m as TimelineHtmlOptions, n as TimelineRow, o as TimelineSvgOptions, p as TimelineTransition, V as VolumeEvent, q as dismissLoader, r as failLoader, s as resolveLoader } from './Movie-DilBC348.js';
2
+ import { S as SequenceSpec, C as CompositionShape, I as ImageSequenceSpec, a as CameraSequenceSpec, K as Keyframe, T as TextSequenceSpec, M as MusicOptions, P as Props, b as TransitionSpec, c as CompositionSpec, d as SequenceCommon } from './types-SS51lP6O.js';
3
+ export { A as ArcShapeSpec, e as AssetSpec, f as AudioAssetSpec, g as AudioMusicSpec, h as AudioSequenceSpec, i as AudioSfxSpec, j as ChromaKeyFilterSpec, k as CircleShapeSpec, l as CompositionSequenceSpec, m as CrossfadeTransition, n as CustomFilterSpec, D as DipTransition, o as DissolveTransition, E as EllipseShapeSpec, p as Expr, F as FilterSpec, G as GradientSpec, q as IrisTransition, L as LineShapeSpec, r as MusicDrum, s as MusicDrums, t as MusicInstrument, u as MusicLevel, v as MusicTrack, N as NullSequenceSpec, w as PathShapeSpec, x as PolygonShapeSpec, y as PropValue, R as RectShapeSpec, z as SfxKnobs, B as SfxOptions, H as SfxPreset, J as SfxVoice, O as ShapeSequenceSpec, Q as SlideTransition, U as Stop, V as StopSpec, W as TransitionCommon, X as VideoSequenceSpec, Y as WipeTransition, Z as ZoomTransition, _ as normalizeStops, $ as pictureStops } from './types-SS51lP6O.js';
4
+ import { S as Sequence } from './Base-BQUodYZK.js';
5
5
  import 'pixi.js';
6
6
  import 'gsap';
7
7
 
@@ -268,6 +268,38 @@ interface FollowPathOptions {
268
268
  */
269
269
  declare function followPath(opts: FollowPathOptions): Keyframe[];
270
270
 
271
+ interface CameraPathOptions {
272
+ /** The route: `[x, y, z]` points the camera passes, in order (at least two). */
273
+ points: Array<[number, number, number]>;
274
+ /** Start, in seconds from the start of the camera layer. Default 0. */
275
+ at?: number;
276
+ /** How long the flight takes, in seconds. Required. */
277
+ duration: number;
278
+ /** GSAP ease for the progress along the route (default `'none'`: constant speed; `'power2.in'` is an accelerating rush). */
279
+ ease?: string;
280
+ /** Where the camera looks: a fixed `[x, y, z]`, or `'ahead'` (default): a point further along the route, so it faces where it flies. */
281
+ look?: [number, number, number] | 'ahead';
282
+ /** With `look: 'ahead'`: how far ahead, as a fraction 0–1 of the route's length (default 0.08). Past the end it continues along the last direction. */
283
+ lookAhead?: number;
284
+ /** `true` (default): a smooth curve through the points (Catmull-Rom). `false`: straight lines between them. */
285
+ smooth?: boolean;
286
+ /** Where on the route to start and stop, as fractions 0–1 of its length (default 0 → 1). `from: 1, to: 0` flies it backwards. */
287
+ from?: number;
288
+ to?: number;
289
+ /** Samples per second: set it to your movie's `frameRate`. Default 30. */
290
+ frameRate?: number;
291
+ }
292
+ /**
293
+ * A camera flight as keyframes for `x`, `y`, `z`, `lookAtX`, `lookAtY` and `lookAtZ`: put the result in a camera layer's `keyframes`.
294
+ * The route is walked by arc length (an even speed whatever the curve; an `ease` shapes it) and sampled once per frame as short
295
+ * straight steps, so the camera is exactly on the route at every rendered frame when `frameRate` matches the movie's.
296
+ *
297
+ * ```js
298
+ * { type: 'camera', keyframes: cameraPath({ points: [[640, 360, 2400], [700, 340, 900], [640, 360, -1500]], duration: 8, ease: 'power2.in', frameRate: 30 }) }
299
+ * ```
300
+ */
301
+ declare function cameraPath(opts: CameraPathOptions): Keyframe[];
302
+
271
303
  interface StaggerOptions {
272
304
  /** Seconds between neighbours (the farthest one waits `each` × its distance). Default 0.1. Not together with `amount`. */
273
305
  each?: number;
@@ -684,4 +716,4 @@ interface WithFadeOptions {
684
716
  */
685
717
  declare function withFade<T extends SequenceCommon>(spec: T, opts: WithFadeOptions): T;
686
718
 
687
- export { type AnimateTextOptions, type AnimateTextTween, type AudioEnvelope, type AudioEnvelopeSource, CameraSequenceSpec, CompositionSpec, type DeckOptions, type DeckPage, type DeckResult, type DeckTransition, type EnvelopeOptions, type FollowPathOptions, ImageSequenceSpec, type KenBurnsOptions, Keyframe, type MeasureStyle, MusicOptions, type OrbitOptions, type ParticlesOptions, Props, type ReactOptions, type ReactProp, SequenceCommon, type SequenceCtor, SequenceSpec, type SplitOptions, type StaggerOptions, type TextPiece, type TextPreset, TextSequenceSpec, type TextSize, TransitionSpec, type WiggleOptions, type WiggleProp, type WithFadeOptions, animateText, audioEnvelope, bpmEnvelope, computeEnvelope, deck, followPath, kenBurns, measureText, musicBuffer, musicEnvelope, noise, orbit, particles, rand, random, react, registerSequenceType, splitText, stagger, wiggle, withFade };
719
+ export { type AnimateTextOptions, type AnimateTextTween, type AudioEnvelope, type AudioEnvelopeSource, type CameraPathOptions, CameraSequenceSpec, CompositionSpec, type DeckOptions, type DeckPage, type DeckResult, type DeckTransition, type EnvelopeOptions, type FollowPathOptions, ImageSequenceSpec, type KenBurnsOptions, Keyframe, type MeasureStyle, MusicOptions, type OrbitOptions, type ParticlesOptions, Props, type ReactOptions, type ReactProp, SequenceCommon, type SequenceCtor, SequenceSpec, type SplitOptions, type StaggerOptions, type TextPiece, type TextPreset, TextSequenceSpec, type TextSize, TransitionSpec, type WiggleOptions, type WiggleProp, type WithFadeOptions, animateText, audioEnvelope, bpmEnvelope, cameraPath, computeEnvelope, deck, followPath, kenBurns, measureText, musicBuffer, musicEnvelope, noise, orbit, particles, rand, random, react, registerSequenceType, splitText, stagger, wiggle, withFade };
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
- import { CompositionSequence } from './chunk-I2YXLHIU.js';
2
- import { ensureFilterLibrary, homeDistance, DEFAULT_FOV, random, noise, flattenSvgPath, rand, DEG, resolveAt, collectPropKeys } from './chunk-7D7RAQWM.js';
3
- export { noise, rand, random, registerSequenceType } from './chunk-7D7RAQWM.js';
1
+ import { CompositionSequence } from './chunk-XTIHKODH.js';
2
+ import { ensureFilterLibrary, homeDistance, DEFAULT_FOV, random, noise, flattenSvgPath, rand, DEG, resolveAt, collectPropKeys } from './chunk-ZNKQH356.js';
3
+ export { noise, rand, random, registerSequenceType } from './chunk-ZNKQH356.js';
4
4
  import { warnUnknownOptions, suggestName } from './chunk-DKGG7WD4.js';
5
5
  import * as PIXI from 'pixi.js';
6
6
  import { extensions, CullerPlugin, Application, Container, Rectangle, Culler, CanvasTextMetrics, TextStyle, Assets, ExtensionType, Filter, UniformGroup, GpuProgram, GlProgram, defaultFilterVert } from 'pixi.js';
@@ -2120,7 +2120,8 @@ var Movie = class {
2120
2120
  _renderNow() {
2121
2121
  const app = this.app;
2122
2122
  if (!app?.renderer) return;
2123
- this._rootSequence?.syncFrame();
2123
+ if (!this._synced) this._rootSequence?.syncFrame();
2124
+ this._synced = false;
2124
2125
  Culler.shared.cull(app.stage, app.renderer.screen, false);
2125
2126
  app.renderer.render({ container: app.stage });
2126
2127
  }
@@ -2129,9 +2130,17 @@ var Movie = class {
2129
2130
  throw new Error(`pixi-effects: movie.${what}() needs a ready movie \u2014 await movie.init(...) first`);
2130
2131
  }
2131
2132
  }
2132
- /** Projects every `threeD` layer for the current frame (see src/space). */
2133
+ /** True when the layers were already synced to the playhead for the frame about to be drawn (so `_renderNow` need not do it again). */
2134
+ _synced = false;
2135
+ /**
2136
+ * Projects every `threeD` layer for the current frame (see src/space). A threeD card is drawn into its own texture HERE, before the stage is
2137
+ * drawn, so the shapes inside it must be brought up to the playhead first: syncing only in `_renderNow` left every card showing the state of
2138
+ * the frame before (after a jump seek, a card whose children were animated showed a stale picture).
2139
+ */
2133
2140
  _updateSpace(t = this._timeOf(this.currentFrame)) {
2134
2141
  if (!this._rootSequence || !this.app) return;
2142
+ this._rootSequence.syncFrame();
2143
+ this._synced = true;
2135
2144
  this._rootSequence.updateSpace(t, this.app.renderer);
2136
2145
  }
2137
2146
  /** The motion blur an operation uses: its own option when given (`false` is off), otherwise the movie's. */
@@ -2436,6 +2445,9 @@ function splitText(text, style = {}, opts = {}) {
2436
2445
  align = "left";
2437
2446
  }
2438
2447
  const x0 = opts.x ?? 0, y0 = opts.y ?? 0;
2448
+ for (const [n, v] of [["x", x0], ["y", y0]]) {
2449
+ if (typeof v !== "number" || !Number.isFinite(v)) throw new Error(`splitText(): ${n} must be a number of pixels (e.g. ${n}: 640), got ${JSON.stringify(v)}: the pieces are placed by measuring, so an expression such as 'GW/2' cannot be used here. \`y\` is the top of the line.`);
2450
+ }
2439
2451
  if (text === "") return [];
2440
2452
  const ts = new TextStyle(style);
2441
2453
  const lineHeight = CanvasTextMetrics.measureText("M", ts).lineHeight ?? CanvasTextMetrics.measureText("M", ts).height;
@@ -2796,6 +2808,78 @@ function followPath(opts) {
2796
2808
  return out;
2797
2809
  }
2798
2810
 
2811
+ // src/presets/cameraPath.ts
2812
+ var KEYS4 = ["points", "at", "duration", "ease", "look", "lookAhead", "smooth", "from", "to", "frameRate"];
2813
+ var MAX_STEPS2 = 4e3;
2814
+ var PER_SPAN = 48;
2815
+ function cameraPath(opts) {
2816
+ warnUnknownOptions("cameraPath()", opts, KEYS4);
2817
+ const { points, at = 0, duration, ease = "none", look = "ahead", lookAhead = 0.08, smooth = true, from = 0, to = 1, frameRate = 30 } = opts;
2818
+ if (!Array.isArray(points) || points.length < 2) throw new Error("cameraPath(): points needs at least two points: [[x, y, z], [x, y, z], \u2026]");
2819
+ points.forEach((p, i) => {
2820
+ if (!Array.isArray(p) || p.length !== 3 || !p.every(Number.isFinite)) throw new Error(`cameraPath(): points[${i}] must be [x, y, z] numbers, got ${JSON.stringify(p)}`);
2821
+ });
2822
+ if (!(Number.isFinite(duration) && duration > 0)) throw new Error(`cameraPath(): duration must be a positive number of seconds, got ${duration}`);
2823
+ if (!(Number.isFinite(frameRate) && frameRate > 0)) throw new Error(`cameraPath(): frameRate must be a positive number (samples per second), got ${frameRate}`);
2824
+ if (look !== "ahead" && !(Array.isArray(look) && look.length === 3 && look.every(Number.isFinite))) throw new Error(`cameraPath(): look must be 'ahead' or [x, y, z] numbers, got ${JSON.stringify(look)}`);
2825
+ if (!(Number.isFinite(lookAhead) && lookAhead >= 0 && lookAhead <= 1)) throw new Error(`cameraPath(): lookAhead must be a fraction of the route between 0 and 1, got ${lookAhead}`);
2826
+ for (const [n, v] of [["from", from], ["to", to]]) {
2827
+ if (!(Number.isFinite(v) && v >= 0 && v <= 1)) throw new Error(`cameraPath(): ${n} must be a fraction of the route between 0 and 1, got ${v}`);
2828
+ }
2829
+ const steps = Math.max(1, Math.round(duration * frameRate));
2830
+ if (steps > MAX_STEPS2) throw new Error(`cameraPath(): ${steps} samples is too many (max ${MAX_STEPS2}); lower frameRate or duration`);
2831
+ const curve = [];
2832
+ if (smooth && points.length > 2) {
2833
+ const pts = points;
2834
+ for (let i = 0; i < pts.length - 1; i++) {
2835
+ const p0 = pts[Math.max(i - 1, 0)], p1 = pts[i], p2 = pts[i + 1], p3 = pts[Math.min(i + 2, pts.length - 1)];
2836
+ for (let s = 0; s < PER_SPAN; s++) curve.push(catmullRom(p0, p1, p2, p3, s / PER_SPAN));
2837
+ }
2838
+ curve.push(pts[pts.length - 1]);
2839
+ } else curve.push(...points);
2840
+ const cum = [0];
2841
+ for (let i = 1; i < curve.length; i++) cum.push(cum[i - 1] + dist(curve[i - 1], curve[i]));
2842
+ const total = cum[cum.length - 1];
2843
+ if (!(total > 0)) throw new Error("cameraPath(): the route has no length (the points are all the same)");
2844
+ const find = (s) => {
2845
+ let lo = 0, hi = cum.length - 1;
2846
+ while (lo < hi - 1) {
2847
+ const mid = lo + hi >> 1;
2848
+ if (cum[mid] <= s) lo = mid;
2849
+ else hi = mid;
2850
+ }
2851
+ return lo;
2852
+ };
2853
+ const along = (fraction) => {
2854
+ const s = fraction * total;
2855
+ if (s >= total) {
2856
+ const a2 = curve[curve.length - 2], b2 = curve[curve.length - 1], len = dist(a2, b2) || 1, extra = s - total;
2857
+ return [b2[0] + (b2[0] - a2[0]) / len * extra, b2[1] + (b2[1] - a2[1]) / len * extra, b2[2] + (b2[2] - a2[2]) / len * extra];
2858
+ }
2859
+ const i = find(Math.max(s, 0)), span = cum[i + 1] - cum[i], t = span > 0 ? (s - cum[i]) / span : 0;
2860
+ const a = curve[i], b = curve[i + 1];
2861
+ return [a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t, a[2] + (b[2] - a[2]) * t];
2862
+ };
2863
+ const shape = parseEase(ease);
2864
+ const round3 = (v) => Math.round(v * 1e6) / 1e6;
2865
+ const sample = (k) => {
2866
+ const p = from + (to - from) * shape(k / steps);
2867
+ const pos = along(Math.min(Math.max(p, 0), 1));
2868
+ const target = look === "ahead" ? along(Math.max(p + (to >= from ? lookAhead : -lookAhead), 0)) : look;
2869
+ return { x: round3(pos[0]), y: round3(pos[1]), z: round3(pos[2]), lookAtX: round3(target[0]), lookAtY: round3(target[1]), lookAtZ: round3(target[2]) };
2870
+ };
2871
+ const out = [{ at, set: sample(0) }];
2872
+ const step = duration / steps;
2873
+ for (let k = 1; k <= steps; k++) out.push({ at: round3(at + (k - 1) * step), to: sample(k), duration: round3(step), ease: "none" });
2874
+ return out;
2875
+ }
2876
+ var dist = (a, b) => Math.hypot(b[0] - a[0], b[1] - a[1], b[2] - a[2]);
2877
+ function catmullRom(p0, p1, p2, p3, t) {
2878
+ const t2 = t * t, t3 = t2 * t;
2879
+ const c = (a, b, d, e) => 0.5 * (2 * b + (d - a) * t + (2 * a - 5 * b + 4 * d - e) * t2 + (3 * b - a - 3 * d + e) * t3);
2880
+ return [c(p0[0], p1[0], p2[0], p3[0]), c(p0[1], p1[1], p2[1], p3[1]), c(p0[2], p1[2], p2[2], p3[2])];
2881
+ }
2882
+
2799
2883
  // src/presets/stagger.ts
2800
2884
  var STAGGER_KEYS = ["each", "amount", "from", "grid", "ease", "seed"];
2801
2885
  var FROM_NAMES = ["start", "end", "center", "edges", "random"];
@@ -2815,11 +2899,11 @@ function stagger(items, options = {}) {
2815
2899
  if (grid && !(Array.isArray(grid) && grid.length === 2 && grid.every((g) => Number.isInteger(g) && g > 0) && grid[0] * grid[1] >= n)) {
2816
2900
  throw new Error(`stagger(): grid must be [columns, rows] and hold all ${n} items, got ${JSON.stringify(grid)}`);
2817
2901
  }
2818
- const dist = distances(n, from, grid, seed);
2819
- const max = dist.reduce((m, d) => Math.max(m, d), 0);
2902
+ const dist2 = distances(n, from, grid, seed);
2903
+ const max = dist2.reduce((m, d) => Math.max(m, d), 0);
2820
2904
  const total = amount !== void 0 ? amount : (each ?? 0.1) * max;
2821
2905
  const shape = parseEase(ease);
2822
- const delays = dist.map((d) => max === 0 ? 0 : Math.round(total * shape(d / max) * 1e6) / 1e6);
2906
+ const delays = dist2.map((d) => max === 0 ? 0 : Math.round(total * shape(d / max) * 1e6) / 1e6);
2823
2907
  if (isCount) return delays;
2824
2908
  return items.map((layer, i) => ({ ...layer, at: Math.round(((layer.at ?? 0) + delays[i]) * 1e6) / 1e6 }));
2825
2909
  }
@@ -2862,7 +2946,7 @@ function distances(n, from, grid, seed) {
2862
2946
 
2863
2947
  // src/presets/wiggle.ts
2864
2948
  var WIGGLE_KEYS = ["at", "duration", "freq", "seed", "ease", "props"];
2865
- var MAX_STEPS2 = 2e3;
2949
+ var MAX_STEPS3 = 2e3;
2866
2950
  function wiggle(opts) {
2867
2951
  warnUnknownOptions("wiggle()", opts, WIGGLE_KEYS);
2868
2952
  const { at = 0, duration, freq = 3, seed = 0, ease = "sine.inOut", props } = opts;
@@ -2876,7 +2960,7 @@ function wiggle(opts) {
2876
2960
  if (p.around === void 0 || p.around === null || p.around === "") throw new Error(`wiggle(): props.${n}.around is missing`);
2877
2961
  }
2878
2962
  const steps = Math.ceil(duration * freq - 1e-9);
2879
- if (steps > MAX_STEPS2) throw new Error(`wiggle(): ${steps} steps is too many (max ${MAX_STEPS2}); lower freq or duration`);
2963
+ if (steps > MAX_STEPS3) throw new Error(`wiggle(): ${steps} steps is too many (max ${MAX_STEPS3}); lower freq or duration`);
2880
2964
  const set = {};
2881
2965
  for (const n of names) set[n] = props[n].around;
2882
2966
  const out = [{ at, set }];
@@ -2898,7 +2982,7 @@ function wiggle(opts) {
2898
2982
  }
2899
2983
 
2900
2984
  // src/presets/animateText.ts
2901
- var KEYS4 = ["by", "x", "y", "align", "name", "at", "duration", "in", "out", "stagger", "idle", "styleFor"];
2985
+ var KEYS5 = ["by", "x", "y", "align", "name", "at", "duration", "in", "out", "stagger", "idle", "styleFor"];
2902
2986
  var PRESETS = {
2903
2987
  rise: { from: { y: 40, alpha: 0 }, duration: 0.5, ease: "back.out(2)" },
2904
2988
  drop: { from: { y: -50, alpha: 0 }, duration: 0.7, ease: "bounce.out" },
@@ -2910,8 +2994,13 @@ var PRESETS = {
2910
2994
  };
2911
2995
  var PRESET_NAMES = Object.keys(PRESETS);
2912
2996
  function animateText(text, style, options) {
2913
- warnUnknownOptions("animateText()", options, KEYS4);
2997
+ warnUnknownOptions("animateText()", options, KEYS5);
2914
2998
  const { by = "chars", x = 0, y = 0, align = "left", name = "text", at = 0, duration, idle, styleFor } = options;
2999
+ for (const [n, v] of [["x", x], ["y", y]]) {
3000
+ if (typeof v !== "number" || !Number.isFinite(v)) {
3001
+ throw new Error(`animateText(): ${n} must be a number of pixels (e.g. ${n}: 640), got ${JSON.stringify(v)}. The letters are measured once, when the layers are made, so an expression such as 'GW/2' cannot be used here: compute the number (W / 2) yourself. \`y\` is the TOP of the line.`);
3002
+ }
3003
+ }
2915
3004
  if (!(Number.isFinite(duration) && duration > 0)) throw new Error(`animateText(): duration must be a positive number of seconds (how long until the last piece is gone), got ${duration}`);
2916
3005
  const pieces = splitText(text, style, { by, x, y, align });
2917
3006
  if (pieces.length === 0) return [];
@@ -2994,11 +3083,11 @@ function resolveTween(spec, which) {
2994
3083
  }
2995
3084
 
2996
3085
  // src/presets/react.ts
2997
- var KEYS5 = ["at", "duration", "props", "audioOffset", "loop", "frameRate"];
3086
+ var KEYS6 = ["at", "duration", "props", "audioOffset", "loop", "frameRate"];
2998
3087
  var PROP_KEYS = ["base", "amount", "band", "beats", "decay", "attack", "release", "invert", "curve"];
2999
- var MAX_STEPS3 = 4e3;
3088
+ var MAX_STEPS4 = 4e3;
3000
3089
  function react(env, options) {
3001
- warnUnknownOptions("react()", options, KEYS5);
3090
+ warnUnknownOptions("react()", options, KEYS6);
3002
3091
  const { at = 0, duration, props, audioOffset = 0, loop = false } = options;
3003
3092
  const frameRate = options.frameRate ?? env.frameRate;
3004
3093
  if (!(Number.isFinite(duration) && duration > 0)) throw new Error(`react(): duration must be a positive number of seconds, got ${duration}`);
@@ -3013,7 +3102,7 @@ function react(env, options) {
3013
3102
  if (p.beats && !(p.decay === void 0 || Number.isFinite(p.decay) && p.decay > 0)) throw new Error(`react(): props.${n}.decay must be a positive number of seconds, got ${p.decay}`);
3014
3103
  }
3015
3104
  const steps = Math.max(1, Math.round(duration * frameRate));
3016
- if (steps > MAX_STEPS3) throw new Error(`react(): too many keyframes (${steps} steps, max ${MAX_STEPS3}); lower frameRate (now ${frameRate}) or duration`);
3105
+ if (steps > MAX_STEPS4) throw new Error(`react(): too many keyframes (${steps} steps, max ${MAX_STEPS4}); lower frameRate (now ${frameRate}) or duration`);
3017
3106
  const wrap = (a) => loop && env.duration > 0 ? (a % env.duration + env.duration) % env.duration : a;
3018
3107
  const beatPulse = (a, decay) => {
3019
3108
  const beats = env.beats;
@@ -3347,6 +3436,6 @@ function withFade(spec, opts) {
3347
3436
  return spec;
3348
3437
  }
3349
3438
 
3350
- export { Movie, animateText, audioEnvelope, bpmEnvelope, computeEnvelope, deck, dismissLoader, failLoader, followPath, kenBurns, measureText, musicBuffer, musicEnvelope, normalizeStops, orbit, particles, pictureStops, react, resolveLoader, splitText, stagger, wiggle, withFade };
3439
+ export { Movie, animateText, audioEnvelope, bpmEnvelope, cameraPath, computeEnvelope, deck, dismissLoader, failLoader, followPath, kenBurns, measureText, musicBuffer, musicEnvelope, normalizeStops, orbit, particles, pictureStops, react, resolveLoader, splitText, stagger, wiggle, withFade };
3351
3440
  //# sourceMappingURL=index.js.map
3352
3441
  //# sourceMappingURL=index.js.map