pixi-effects 0.34.0 → 0.36.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 (79) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/README.md +5 -5
  3. package/ai/chat-template.html +3 -3
  4. package/ai/tools/check.mjs +26 -7
  5. package/ai/tools/lookup.mjs +14 -2
  6. package/dist/{Base-CXsMdmJB.d.ts → Base-CdF7AJop.d.ts} +1 -1
  7. package/dist/{Base-pNq6sVGl.d.cts → Base-CmiFiwQM.d.cts} +1 -1
  8. package/dist/Composition-BMRCE5FH.js +7 -0
  9. package/dist/{Composition-NDXKQHB4.js.map → Composition-BMRCE5FH.js.map} +1 -1
  10. package/dist/Composition-XRLYR6GL.cjs +16 -0
  11. package/dist/{Composition-OLH7RC5Z.cjs.map → Composition-XRLYR6GL.cjs.map} +1 -1
  12. package/dist/Controller.cjs +130 -1
  13. package/dist/Controller.cjs.map +1 -1
  14. package/dist/Controller.d.cts +24 -2
  15. package/dist/Controller.d.ts +24 -2
  16. package/dist/Controller.js +130 -1
  17. package/dist/Controller.js.map +1 -1
  18. package/dist/{Movie-f0tQG9y5.d.ts → Movie-Bh1HPdKw.d.cts} +30 -2
  19. package/dist/{Movie-CL6SsAWe.d.cts → Movie-D5WFfZdZ.d.ts} +30 -2
  20. package/dist/{Spans-QRMN2EZO.cjs → Spans-4IRBSOLI.cjs} +11 -11
  21. package/dist/{Spans-QRMN2EZO.cjs.map → Spans-4IRBSOLI.cjs.map} +1 -1
  22. package/dist/{Spans-QHHWC2PB.js → Spans-MWP2FX3T.js} +6 -6
  23. package/dist/{Spans-QHHWC2PB.js.map → Spans-MWP2FX3T.js.map} +1 -1
  24. package/dist/{blendModes-WIUCYIMC.cjs → blendModes-KU7XLV7U.cjs} +5 -5
  25. package/dist/{blendModes-WIUCYIMC.cjs.map → blendModes-KU7XLV7U.cjs.map} +1 -1
  26. package/dist/{blendModes-PP5NPBXY.js → blendModes-OOI3T6BE.js} +4 -4
  27. package/dist/{blendModes-PP5NPBXY.js.map → blendModes-OOI3T6BE.js.map} +1 -1
  28. package/dist/{chunk-ITDYFBB6.js → chunk-2JFFKHXO.js} +5 -5
  29. package/dist/chunk-2JFFKHXO.js.map +1 -0
  30. package/dist/{chunk-GL2AQFLQ.cjs → chunk-42ML5AKN.cjs} +5 -5
  31. package/dist/chunk-42ML5AKN.cjs.map +1 -0
  32. package/dist/{chunk-XV4L72FW.js → chunk-AZ6AINSW.js} +5 -5
  33. package/dist/{chunk-XV4L72FW.js.map → chunk-AZ6AINSW.js.map} +1 -1
  34. package/dist/{chunk-FVZEBYLW.cjs → chunk-CC7SUR7Q.cjs} +237 -54
  35. package/dist/chunk-CC7SUR7Q.cjs.map +1 -0
  36. package/dist/{chunk-Y253JQOH.js → chunk-G3DX65HO.js} +110 -10
  37. package/dist/chunk-G3DX65HO.js.map +1 -0
  38. package/dist/{chunk-LEECUCTG.cjs → chunk-KJSEQVBV.cjs} +129 -29
  39. package/dist/chunk-KJSEQVBV.cjs.map +1 -0
  40. package/dist/{chunk-IMQJVRMT.cjs → chunk-NEUPEENI.cjs} +5 -5
  41. package/dist/{chunk-IMQJVRMT.cjs.map → chunk-NEUPEENI.cjs.map} +1 -1
  42. package/dist/{chunk-67LG7MN5.js → chunk-VF56PRAA.js} +205 -22
  43. package/dist/chunk-VF56PRAA.js.map +1 -0
  44. package/dist/{chunk-RTJD3ZSW.js → chunk-WIIU42FH.js} +4 -4
  45. package/dist/{chunk-RTJD3ZSW.js.map → chunk-WIIU42FH.js.map} +1 -1
  46. package/dist/{chunk-5IJENZOG.cjs → chunk-Y5UWFH6Y.cjs} +91 -91
  47. package/dist/{chunk-5IJENZOG.cjs.map → chunk-Y5UWFH6Y.cjs.map} +1 -1
  48. package/dist/index.cjs +33 -33
  49. package/dist/index.d.cts +20 -6
  50. package/dist/index.d.ts +20 -6
  51. package/dist/index.js +5 -5
  52. package/dist/loader.css +1 -1
  53. package/dist/present.cjs +5 -5
  54. package/dist/present.d.cts +2 -2
  55. package/dist/present.d.ts +2 -2
  56. package/dist/present.js +5 -5
  57. package/dist/three.cjs +6 -6
  58. package/dist/three.d.cts +2 -2
  59. package/dist/three.d.ts +2 -2
  60. package/dist/three.js +2 -2
  61. package/dist/{types-CavnUvd5.d.ts → types-B6zDTU2V.d.cts} +14 -1
  62. package/dist/{types-CavnUvd5.d.cts → types-B6zDTU2V.d.ts} +14 -1
  63. package/docs/api.md +15 -4
  64. package/docs/dsl.md +7 -5
  65. package/llms-full.txt +95 -18
  66. package/package.json +1 -1
  67. package/skills/pixi-effects/SKILL.md +3 -3
  68. package/skills/pixi-effects/reference/cheatsheet.md +7 -6
  69. package/skills/pixi-effects/reference/pitfalls.md +3 -0
  70. package/skills/pixi-effects/reference/recipes.md +60 -0
  71. package/skills/pixi-effects/template.html +6 -6
  72. package/dist/Composition-NDXKQHB4.js +0 -7
  73. package/dist/Composition-OLH7RC5Z.cjs +0 -16
  74. package/dist/chunk-67LG7MN5.js.map +0 -1
  75. package/dist/chunk-FVZEBYLW.cjs.map +0 -1
  76. package/dist/chunk-GL2AQFLQ.cjs.map +0 -1
  77. package/dist/chunk-ITDYFBB6.js.map +0 -1
  78. package/dist/chunk-LEECUCTG.cjs.map +0 -1
  79. package/dist/chunk-Y253JQOH.js.map +0 -1
package/dist/index.cjs CHANGED
@@ -1,125 +1,125 @@
1
1
  'use strict';
2
2
 
3
- var chunkFVZEBYLW_cjs = require('./chunk-FVZEBYLW.cjs');
4
- require('./chunk-5IJENZOG.cjs');
5
- var chunkLEECUCTG_cjs = require('./chunk-LEECUCTG.cjs');
6
- require('./chunk-IMQJVRMT.cjs');
7
- require('./chunk-GL2AQFLQ.cjs');
3
+ var chunkCC7SUR7Q_cjs = require('./chunk-CC7SUR7Q.cjs');
4
+ require('./chunk-Y5UWFH6Y.cjs');
5
+ var chunkKJSEQVBV_cjs = require('./chunk-KJSEQVBV.cjs');
6
+ require('./chunk-NEUPEENI.cjs');
7
+ require('./chunk-42ML5AKN.cjs');
8
8
  var chunkTWZNOUM7_cjs = require('./chunk-TWZNOUM7.cjs');
9
9
 
10
10
 
11
11
 
12
12
  Object.defineProperty(exports, "Movie", {
13
13
  enumerable: true,
14
- get: function () { return chunkFVZEBYLW_cjs.Movie; }
14
+ get: function () { return chunkCC7SUR7Q_cjs.Movie; }
15
15
  });
16
16
  Object.defineProperty(exports, "animateText", {
17
17
  enumerable: true,
18
- get: function () { return chunkFVZEBYLW_cjs.animateText; }
18
+ get: function () { return chunkCC7SUR7Q_cjs.animateText; }
19
19
  });
20
20
  Object.defineProperty(exports, "audioEnvelope", {
21
21
  enumerable: true,
22
- get: function () { return chunkFVZEBYLW_cjs.audioEnvelope; }
22
+ get: function () { return chunkCC7SUR7Q_cjs.audioEnvelope; }
23
23
  });
24
24
  Object.defineProperty(exports, "bpmEnvelope", {
25
25
  enumerable: true,
26
- get: function () { return chunkFVZEBYLW_cjs.bpmEnvelope; }
26
+ get: function () { return chunkCC7SUR7Q_cjs.bpmEnvelope; }
27
27
  });
28
28
  Object.defineProperty(exports, "cameraPath", {
29
29
  enumerable: true,
30
- get: function () { return chunkFVZEBYLW_cjs.cameraPath; }
30
+ get: function () { return chunkCC7SUR7Q_cjs.cameraPath; }
31
31
  });
32
32
  Object.defineProperty(exports, "computeEnvelope", {
33
33
  enumerable: true,
34
- get: function () { return chunkFVZEBYLW_cjs.computeEnvelope; }
34
+ get: function () { return chunkCC7SUR7Q_cjs.computeEnvelope; }
35
35
  });
36
36
  Object.defineProperty(exports, "dismissLoader", {
37
37
  enumerable: true,
38
- get: function () { return chunkFVZEBYLW_cjs.dismissLoader; }
38
+ get: function () { return chunkCC7SUR7Q_cjs.dismissLoader; }
39
39
  });
40
40
  Object.defineProperty(exports, "ease", {
41
41
  enumerable: true,
42
- get: function () { return chunkFVZEBYLW_cjs.parseEase; }
42
+ get: function () { return chunkCC7SUR7Q_cjs.parseEase; }
43
43
  });
44
44
  Object.defineProperty(exports, "failLoader", {
45
45
  enumerable: true,
46
- get: function () { return chunkFVZEBYLW_cjs.failLoader; }
46
+ get: function () { return chunkCC7SUR7Q_cjs.failLoader; }
47
47
  });
48
48
  Object.defineProperty(exports, "followPath", {
49
49
  enumerable: true,
50
- get: function () { return chunkFVZEBYLW_cjs.followPath; }
50
+ get: function () { return chunkCC7SUR7Q_cjs.followPath; }
51
51
  });
52
52
  Object.defineProperty(exports, "kenBurns", {
53
53
  enumerable: true,
54
- get: function () { return chunkFVZEBYLW_cjs.kenBurns; }
54
+ get: function () { return chunkCC7SUR7Q_cjs.kenBurns; }
55
55
  });
56
56
  Object.defineProperty(exports, "measureText", {
57
57
  enumerable: true,
58
- get: function () { return chunkFVZEBYLW_cjs.measureText; }
58
+ get: function () { return chunkCC7SUR7Q_cjs.measureText; }
59
59
  });
60
60
  Object.defineProperty(exports, "musicBuffer", {
61
61
  enumerable: true,
62
- get: function () { return chunkFVZEBYLW_cjs.musicBuffer; }
62
+ get: function () { return chunkCC7SUR7Q_cjs.musicBuffer; }
63
63
  });
64
64
  Object.defineProperty(exports, "musicEnvelope", {
65
65
  enumerable: true,
66
- get: function () { return chunkFVZEBYLW_cjs.musicEnvelope; }
66
+ get: function () { return chunkCC7SUR7Q_cjs.musicEnvelope; }
67
67
  });
68
68
  Object.defineProperty(exports, "musicEvents", {
69
69
  enumerable: true,
70
- get: function () { return chunkFVZEBYLW_cjs.musicEvents; }
70
+ get: function () { return chunkCC7SUR7Q_cjs.musicEvents; }
71
71
  });
72
72
  Object.defineProperty(exports, "orbit", {
73
73
  enumerable: true,
74
- get: function () { return chunkFVZEBYLW_cjs.orbit; }
74
+ get: function () { return chunkCC7SUR7Q_cjs.orbit; }
75
75
  });
76
76
  Object.defineProperty(exports, "particles", {
77
77
  enumerable: true,
78
- get: function () { return chunkFVZEBYLW_cjs.particles; }
78
+ get: function () { return chunkCC7SUR7Q_cjs.particles; }
79
79
  });
80
80
  Object.defineProperty(exports, "react", {
81
81
  enumerable: true,
82
- get: function () { return chunkFVZEBYLW_cjs.react; }
82
+ get: function () { return chunkCC7SUR7Q_cjs.react; }
83
83
  });
84
84
  Object.defineProperty(exports, "resolveLoader", {
85
85
  enumerable: true,
86
- get: function () { return chunkFVZEBYLW_cjs.resolveLoader; }
86
+ get: function () { return chunkCC7SUR7Q_cjs.resolveLoader; }
87
87
  });
88
88
  Object.defineProperty(exports, "splitText", {
89
89
  enumerable: true,
90
- get: function () { return chunkFVZEBYLW_cjs.splitText; }
90
+ get: function () { return chunkCC7SUR7Q_cjs.splitText; }
91
91
  });
92
92
  Object.defineProperty(exports, "stagger", {
93
93
  enumerable: true,
94
- get: function () { return chunkFVZEBYLW_cjs.stagger; }
94
+ get: function () { return chunkCC7SUR7Q_cjs.stagger; }
95
95
  });
96
96
  Object.defineProperty(exports, "wiggle", {
97
97
  enumerable: true,
98
- get: function () { return chunkFVZEBYLW_cjs.wiggle; }
98
+ get: function () { return chunkCC7SUR7Q_cjs.wiggle; }
99
99
  });
100
100
  Object.defineProperty(exports, "withFade", {
101
101
  enumerable: true,
102
- get: function () { return chunkFVZEBYLW_cjs.withFade; }
102
+ get: function () { return chunkCC7SUR7Q_cjs.withFade; }
103
103
  });
104
104
  Object.defineProperty(exports, "Sequence", {
105
105
  enumerable: true,
106
- get: function () { return chunkLEECUCTG_cjs.Sequence; }
106
+ get: function () { return chunkKJSEQVBV_cjs.Sequence; }
107
107
  });
108
108
  Object.defineProperty(exports, "noise", {
109
109
  enumerable: true,
110
- get: function () { return chunkLEECUCTG_cjs.noise; }
110
+ get: function () { return chunkKJSEQVBV_cjs.noise; }
111
111
  });
112
112
  Object.defineProperty(exports, "rand", {
113
113
  enumerable: true,
114
- get: function () { return chunkLEECUCTG_cjs.rand; }
114
+ get: function () { return chunkKJSEQVBV_cjs.rand; }
115
115
  });
116
116
  Object.defineProperty(exports, "random", {
117
117
  enumerable: true,
118
- get: function () { return chunkLEECUCTG_cjs.random; }
118
+ get: function () { return chunkKJSEQVBV_cjs.random; }
119
119
  });
120
120
  Object.defineProperty(exports, "registerSequenceType", {
121
121
  enumerable: true,
122
- get: function () { return chunkLEECUCTG_cjs.registerSequenceType; }
122
+ get: function () { return chunkKJSEQVBV_cjs.registerSequenceType; }
123
123
  });
124
124
  Object.defineProperty(exports, "warnUnknownOptions", {
125
125
  enumerable: true,
package/dist/index.d.cts CHANGED
@@ -1,8 +1,8 @@
1
- import { M as MusicEvents } from './Movie-CL6SsAWe.cjs';
2
- export { A as AudioInspectOptions, a as AudioReport, b as AudioSourceReport, C as ContactSheetOptions, F as FontReport, c as FrameEvent, I as InspectOptions, d as InspectReport, e as IssueGroup, L as LayerInfo, f as LoadProgressState, g as LoadStage, h as LoaderOption, i as LoopEvent, j as MotionBlurOptions, k as MotionBlurSpec, l as Movie, m as MovieErrorEvent, n as MovieOptions, o as MusicDrumEvents, p as MusicDrumHit, q as MusicNote, r as MusicSummary, s as MusicTrackEvents, O as OnionSkinOptions, P as ProgressEvent, R as RebuildOptions, t as RebuildReason, u as RebuildReport, v as RenderOptions, w as ReviewOptions, x as ReviewReport, S as SnapshotOptions, y as SoundMeasure, T as TimelineData, z as TimelineHtmlOptions, B as TimelineRow, D as TimelineSvgOptions, E as TimelineTransition, V as VolumeEvent, G as dismissLoader, H as failLoader, J as resolveLoader } from './Movie-CL6SsAWe.cjs';
3
- import { S as SequenceSpec, C as CompositionShape, I as ImageSequenceSpec, a as CameraSequenceSpec, K as Keyframe, T as TextSequenceSpec, M as MusicOptions, b as SequenceCommon } from './types-CavnUvd5.cjs';
4
- export { A as ArcShapeSpec, c as AssetSpec, d as AudioAssetSpec, e as AudioMusicSpec, f as AudioSequenceSpec, g as AudioSfxSpec, B as BlendModeName, h as ChromaKeyFilterSpec, i as CircleShapeSpec, j as CompositionSequenceSpec, k as CompositionSpec, l as CrossfadeTransition, m as CustomFilterSpec, D as DipTransition, n as DissolveTransition, E as EllipseShapeSpec, o as Expr, F as FilterSpec, G as GradientSpec, p as IrisTransition, L as LightSequenceSpec, q as LineShapeSpec, r as LumaTransition, s as MaskRefSpec, t as MatteRefSpec, u as MusicDrum, v as MusicDrums, w as MusicInstrument, x as MusicLevel, y as MusicTrack, N as NullSequenceSpec, P as PathShapeSpec, z as PolygonShapeSpec, H as PropValue, J as Props, R as RectShapeSpec, O as SfxKnobs, Q as SfxOptions, U as SfxPreset, V as SfxVoice, W as ShaderSequenceSpec, X as ShapeSequenceSpec, Y as SlideTransition, Z as Stop, _ as StopSpec, $ as TextSpan, a0 as TransitionCommon, a1 as TransitionSpec, a2 as VideoSequenceSpec, a3 as WipeTransition, a4 as ZoomTransition } from './types-CavnUvd5.cjs';
5
- import { S as Sequence } from './Base-pNq6sVGl.cjs';
1
+ import { M as MusicEvents } from './Movie-Bh1HPdKw.cjs';
2
+ export { A as AudioInspectOptions, a as AudioReport, b as AudioSourceReport, C as ContactSheetOptions, F as FontReport, c as FrameEvent, I as InspectOptions, d as InspectReport, e as IssueGroup, L as LayerInfo, f as LoadProgressState, g as LoadStage, h as LoaderOption, i as LoopEvent, j as MotionBlurOptions, k as MotionBlurSpec, l as Movie, m as MovieErrorEvent, n as MovieOptions, o as MusicDrumEvents, p as MusicDrumHit, q as MusicNote, r as MusicSummary, s as MusicTrackEvents, O as OnionSkinOptions, P as ProgressEvent, R as RebuildOptions, t as RebuildReason, u as RebuildReport, v as RenderOptions, w as ReviewOptions, x as ReviewReport, S as SnapshotOptions, y as SoundMeasure, T as ThumbnailOptions, z as TimelineData, B as TimelineHtmlOptions, D as TimelineRow, E as TimelineSvgOptions, G as TimelineTransition, V as VolumeEvent, H as dismissLoader, J as failLoader, K as resolveLoader } from './Movie-Bh1HPdKw.cjs';
3
+ import { S as SequenceSpec, C as CompositionShape, I as ImageSequenceSpec, a as CameraSequenceSpec, K as Keyframe, T as TextSequenceSpec, M as MusicOptions, b as SequenceCommon } from './types-B6zDTU2V.cjs';
4
+ export { A as ArcShapeSpec, c as AssetSpec, d as AudioAssetSpec, e as AudioMusicSpec, f as AudioSequenceSpec, g as AudioSfxSpec, B as BlendModeName, h as ChromaKeyFilterSpec, i as CircleShapeSpec, j as CompositionSequenceSpec, k as CompositionSpec, l as CrossfadeTransition, m as CustomFilterSpec, D as DipTransition, n as DissolveTransition, E as EllipseShapeSpec, o as Expr, F as FilterSpec, G as GradientSpec, p as IrisTransition, L as LightSequenceSpec, q as LineShapeSpec, r as LumaTransition, s as MaskRefSpec, t as MatteRefSpec, u as MusicDrum, v as MusicDrums, w as MusicInstrument, x as MusicLevel, y as MusicTrack, N as NullSequenceSpec, P as PathShapeSpec, z as PolygonShapeSpec, H as PropValue, J as Props, R as RectShapeSpec, O as SfxKnobs, Q as SfxOptions, U as SfxPreset, V as SfxVoice, W as ShaderSequenceSpec, X as ShapeSequenceSpec, Y as SlideTransition, Z as Stop, _ as StopSpec, $ as TextSpan, a0 as TransitionCommon, a1 as TransitionSpec, a2 as VideoSequenceSpec, a3 as WipeTransition, a4 as ZoomTransition } from './types-B6zDTU2V.cjs';
5
+ import { S as Sequence } from './Base-CmiFiwQM.cjs';
6
6
  import 'pixi.js';
7
7
 
8
8
  /** A text style as written in a `text` layer's `style` (numbers, not expressions, for the fields that size the text). */
@@ -22,6 +22,13 @@ interface SplitOptions {
22
22
  y?: number;
23
23
  /** How each line sits on `x`. Default `'left'`. */
24
24
  align?: 'left' | 'center' | 'right';
25
+ /**
26
+ * Vertical writing (tategaki): characters go down a column, a new line is a new column to the left. `x` is then the RIGHT edge of the first column and `y` the top
27
+ * of a column (`align`: `'left'` keeps y at the top, `'center'` at the middle, `'right'` at the bottom). Only `by: 'chars'` has a vertical form. Each piece also gets
28
+ * `cx` / `cy` (the centre of its cell, punctuation already moved to the upper right) and `rotation` (degrees: the long vowel mark, dashes, waves and brackets are turned
29
+ * a quarter clockwise); place a text layer with `anchorX: 0.5, anchorY: 0.5` at `cx` / `cy` and `rotation: p.rotation`. `animateText(..., { vertical: true })` does all of it.
30
+ */
31
+ vertical?: boolean;
25
32
  }
26
33
  interface TextPiece {
27
34
  text: string;
@@ -34,6 +41,11 @@ interface TextPiece {
34
41
  index: number;
35
42
  /** Which line (0-based) the piece is on. */
36
43
  line: number;
44
+ /** Vertical text only: where the centre of the piece goes (a text layer with `anchorX: 0.5, anchorY: 0.5`). */
45
+ cx?: number;
46
+ cy?: number;
47
+ /** Vertical text only: the turn in degrees the piece needs (0 or 90). */
48
+ rotation?: number;
37
49
  }
38
50
  /**
39
51
  * How big `text` is drawn with `style`: the same numbers a text layer with that style has, measured with the font the
@@ -403,7 +415,7 @@ type TextPreset = keyof typeof PRESETS;
403
415
  interface AnimateTextTween {
404
416
  /** Start from this preset and change what you give below. */
405
417
  preset?: TextPreset;
406
- /** The hidden state: `x` / `y` are offsets from the resting place, everything else (`alpha`, `scale`, `rotation`, …) is absolute. */
418
+ /** The hidden state: `x` / `y` are offsets from the resting place, everything else (`alpha`, `scale`, …) is absolute; `rotation` too, from the resting rotation (0, except the turned glyphs of vertical text, which rest at 90°). */
407
419
  from?: Record<string, number>;
408
420
  /** The resting state, for properties that have none of their own (`alpha` 1, `scale` 1, `rotation` 0 and the place are known). */
409
421
  to?: Record<string, number>;
@@ -422,6 +434,8 @@ interface AnimateTextOptions {
422
434
  /** Top of the first line. */
423
435
  y?: number;
424
436
  align?: 'left' | 'center' | 'right';
437
+ /** Vertical writing: characters go down a column, a new line is a new column to the left, and `x` is the RIGHT edge of the first column (see `splitText`). Punctuation and the long vowel mark are placed and turned the way tategaki wants. */
438
+ vertical?: boolean;
425
439
  /** Layers are named `<name>-0`, `<name>-1`, … (default `'text'`). */
426
440
  name?: string;
427
441
  /** When the first piece starts, in composition seconds. Default 0. */
package/dist/index.d.ts CHANGED
@@ -1,8 +1,8 @@
1
- import { M as MusicEvents } from './Movie-f0tQG9y5.js';
2
- export { A as AudioInspectOptions, a as AudioReport, b as AudioSourceReport, C as ContactSheetOptions, F as FontReport, c as FrameEvent, I as InspectOptions, d as InspectReport, e as IssueGroup, L as LayerInfo, f as LoadProgressState, g as LoadStage, h as LoaderOption, i as LoopEvent, j as MotionBlurOptions, k as MotionBlurSpec, l as Movie, m as MovieErrorEvent, n as MovieOptions, o as MusicDrumEvents, p as MusicDrumHit, q as MusicNote, r as MusicSummary, s as MusicTrackEvents, O as OnionSkinOptions, P as ProgressEvent, R as RebuildOptions, t as RebuildReason, u as RebuildReport, v as RenderOptions, w as ReviewOptions, x as ReviewReport, S as SnapshotOptions, y as SoundMeasure, T as TimelineData, z as TimelineHtmlOptions, B as TimelineRow, D as TimelineSvgOptions, E as TimelineTransition, V as VolumeEvent, G as dismissLoader, H as failLoader, J as resolveLoader } from './Movie-f0tQG9y5.js';
3
- import { S as SequenceSpec, C as CompositionShape, I as ImageSequenceSpec, a as CameraSequenceSpec, K as Keyframe, T as TextSequenceSpec, M as MusicOptions, b as SequenceCommon } from './types-CavnUvd5.js';
4
- export { A as ArcShapeSpec, c as AssetSpec, d as AudioAssetSpec, e as AudioMusicSpec, f as AudioSequenceSpec, g as AudioSfxSpec, B as BlendModeName, h as ChromaKeyFilterSpec, i as CircleShapeSpec, j as CompositionSequenceSpec, k as CompositionSpec, l as CrossfadeTransition, m as CustomFilterSpec, D as DipTransition, n as DissolveTransition, E as EllipseShapeSpec, o as Expr, F as FilterSpec, G as GradientSpec, p as IrisTransition, L as LightSequenceSpec, q as LineShapeSpec, r as LumaTransition, s as MaskRefSpec, t as MatteRefSpec, u as MusicDrum, v as MusicDrums, w as MusicInstrument, x as MusicLevel, y as MusicTrack, N as NullSequenceSpec, P as PathShapeSpec, z as PolygonShapeSpec, H as PropValue, J as Props, R as RectShapeSpec, O as SfxKnobs, Q as SfxOptions, U as SfxPreset, V as SfxVoice, W as ShaderSequenceSpec, X as ShapeSequenceSpec, Y as SlideTransition, Z as Stop, _ as StopSpec, $ as TextSpan, a0 as TransitionCommon, a1 as TransitionSpec, a2 as VideoSequenceSpec, a3 as WipeTransition, a4 as ZoomTransition } from './types-CavnUvd5.js';
5
- import { S as Sequence } from './Base-CXsMdmJB.js';
1
+ import { M as MusicEvents } from './Movie-D5WFfZdZ.js';
2
+ export { A as AudioInspectOptions, a as AudioReport, b as AudioSourceReport, C as ContactSheetOptions, F as FontReport, c as FrameEvent, I as InspectOptions, d as InspectReport, e as IssueGroup, L as LayerInfo, f as LoadProgressState, g as LoadStage, h as LoaderOption, i as LoopEvent, j as MotionBlurOptions, k as MotionBlurSpec, l as Movie, m as MovieErrorEvent, n as MovieOptions, o as MusicDrumEvents, p as MusicDrumHit, q as MusicNote, r as MusicSummary, s as MusicTrackEvents, O as OnionSkinOptions, P as ProgressEvent, R as RebuildOptions, t as RebuildReason, u as RebuildReport, v as RenderOptions, w as ReviewOptions, x as ReviewReport, S as SnapshotOptions, y as SoundMeasure, T as ThumbnailOptions, z as TimelineData, B as TimelineHtmlOptions, D as TimelineRow, E as TimelineSvgOptions, G as TimelineTransition, V as VolumeEvent, H as dismissLoader, J as failLoader, K as resolveLoader } from './Movie-D5WFfZdZ.js';
3
+ import { S as SequenceSpec, C as CompositionShape, I as ImageSequenceSpec, a as CameraSequenceSpec, K as Keyframe, T as TextSequenceSpec, M as MusicOptions, b as SequenceCommon } from './types-B6zDTU2V.js';
4
+ export { A as ArcShapeSpec, c as AssetSpec, d as AudioAssetSpec, e as AudioMusicSpec, f as AudioSequenceSpec, g as AudioSfxSpec, B as BlendModeName, h as ChromaKeyFilterSpec, i as CircleShapeSpec, j as CompositionSequenceSpec, k as CompositionSpec, l as CrossfadeTransition, m as CustomFilterSpec, D as DipTransition, n as DissolveTransition, E as EllipseShapeSpec, o as Expr, F as FilterSpec, G as GradientSpec, p as IrisTransition, L as LightSequenceSpec, q as LineShapeSpec, r as LumaTransition, s as MaskRefSpec, t as MatteRefSpec, u as MusicDrum, v as MusicDrums, w as MusicInstrument, x as MusicLevel, y as MusicTrack, N as NullSequenceSpec, P as PathShapeSpec, z as PolygonShapeSpec, H as PropValue, J as Props, R as RectShapeSpec, O as SfxKnobs, Q as SfxOptions, U as SfxPreset, V as SfxVoice, W as ShaderSequenceSpec, X as ShapeSequenceSpec, Y as SlideTransition, Z as Stop, _ as StopSpec, $ as TextSpan, a0 as TransitionCommon, a1 as TransitionSpec, a2 as VideoSequenceSpec, a3 as WipeTransition, a4 as ZoomTransition } from './types-B6zDTU2V.js';
5
+ import { S as Sequence } from './Base-CdF7AJop.js';
6
6
  import 'pixi.js';
7
7
 
8
8
  /** A text style as written in a `text` layer's `style` (numbers, not expressions, for the fields that size the text). */
@@ -22,6 +22,13 @@ interface SplitOptions {
22
22
  y?: number;
23
23
  /** How each line sits on `x`. Default `'left'`. */
24
24
  align?: 'left' | 'center' | 'right';
25
+ /**
26
+ * Vertical writing (tategaki): characters go down a column, a new line is a new column to the left. `x` is then the RIGHT edge of the first column and `y` the top
27
+ * of a column (`align`: `'left'` keeps y at the top, `'center'` at the middle, `'right'` at the bottom). Only `by: 'chars'` has a vertical form. Each piece also gets
28
+ * `cx` / `cy` (the centre of its cell, punctuation already moved to the upper right) and `rotation` (degrees: the long vowel mark, dashes, waves and brackets are turned
29
+ * a quarter clockwise); place a text layer with `anchorX: 0.5, anchorY: 0.5` at `cx` / `cy` and `rotation: p.rotation`. `animateText(..., { vertical: true })` does all of it.
30
+ */
31
+ vertical?: boolean;
25
32
  }
26
33
  interface TextPiece {
27
34
  text: string;
@@ -34,6 +41,11 @@ interface TextPiece {
34
41
  index: number;
35
42
  /** Which line (0-based) the piece is on. */
36
43
  line: number;
44
+ /** Vertical text only: where the centre of the piece goes (a text layer with `anchorX: 0.5, anchorY: 0.5`). */
45
+ cx?: number;
46
+ cy?: number;
47
+ /** Vertical text only: the turn in degrees the piece needs (0 or 90). */
48
+ rotation?: number;
37
49
  }
38
50
  /**
39
51
  * How big `text` is drawn with `style`: the same numbers a text layer with that style has, measured with the font the
@@ -403,7 +415,7 @@ type TextPreset = keyof typeof PRESETS;
403
415
  interface AnimateTextTween {
404
416
  /** Start from this preset and change what you give below. */
405
417
  preset?: TextPreset;
406
- /** The hidden state: `x` / `y` are offsets from the resting place, everything else (`alpha`, `scale`, `rotation`, …) is absolute. */
418
+ /** The hidden state: `x` / `y` are offsets from the resting place, everything else (`alpha`, `scale`, …) is absolute; `rotation` too, from the resting rotation (0, except the turned glyphs of vertical text, which rest at 90°). */
407
419
  from?: Record<string, number>;
408
420
  /** The resting state, for properties that have none of their own (`alpha` 1, `scale` 1, `rotation` 0 and the place are known). */
409
421
  to?: Record<string, number>;
@@ -422,6 +434,8 @@ interface AnimateTextOptions {
422
434
  /** Top of the first line. */
423
435
  y?: number;
424
436
  align?: 'left' | 'center' | 'right';
437
+ /** Vertical writing: characters go down a column, a new line is a new column to the left, and `x` is the RIGHT edge of the first column (see `splitText`). Punctuation and the long vowel mark are placed and turned the way tategaki wants. */
438
+ vertical?: boolean;
425
439
  /** Layers are named `<name>-0`, `<name>-1`, … (default `'text'`). */
426
440
  name?: string;
427
441
  /** When the first piece starts, in composition seconds. Default 0. */
package/dist/index.js CHANGED
@@ -1,8 +1,8 @@
1
- export { Movie, animateText, audioEnvelope, bpmEnvelope, cameraPath, computeEnvelope, dismissLoader, parseEase as ease, failLoader, followPath, kenBurns, measureText, musicBuffer, musicEnvelope, musicEvents, orbit, particles, react, resolveLoader, splitText, stagger, wiggle, withFade } from './chunk-67LG7MN5.js';
2
- import './chunk-XV4L72FW.js';
3
- export { Sequence, noise, rand, random, registerSequenceType } from './chunk-Y253JQOH.js';
4
- import './chunk-RTJD3ZSW.js';
5
- import './chunk-ITDYFBB6.js';
1
+ export { Movie, animateText, audioEnvelope, bpmEnvelope, cameraPath, computeEnvelope, dismissLoader, parseEase as ease, failLoader, followPath, kenBurns, measureText, musicBuffer, musicEnvelope, musicEvents, orbit, particles, react, resolveLoader, splitText, stagger, wiggle, withFade } from './chunk-VF56PRAA.js';
2
+ import './chunk-AZ6AINSW.js';
3
+ export { Sequence, noise, rand, random, registerSequenceType } from './chunk-G3DX65HO.js';
4
+ import './chunk-WIIU42FH.js';
5
+ import './chunk-2JFFKHXO.js';
6
6
  export { warnUnknownOptions } from './chunk-BXOCEZ4U.js';
7
7
  //# sourceMappingURL=index.js.map
8
8
  //# sourceMappingURL=index.js.map
package/dist/loader.css CHANGED
@@ -1,7 +1,7 @@
1
1
  /*
2
2
  * pixi-effects loader: one line of HTML on the page, a few lines of CSS, no JavaScript of your own.
3
3
  *
4
- * <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/pixi-effects@0.34.0/dist/loader.css">
4
+ * <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/pixi-effects@0.36.0/dist/loader.css">
5
5
  * <div class="stage" style="position: relative"> <!-- any box the canvas sits in, with position: relative -->
6
6
  * <canvas id="stage" width="1280" height="720"></canvas>
7
7
  * <div class="pe-loader" data-label="LOADING"></div> <!-- or class="pe-loader pe-loader--bar" -->
package/dist/present.cjs CHANGED
@@ -1,10 +1,10 @@
1
1
  'use strict';
2
2
 
3
- require('./chunk-FVZEBYLW.cjs');
4
- require('./chunk-5IJENZOG.cjs');
5
- require('./chunk-LEECUCTG.cjs');
6
- require('./chunk-IMQJVRMT.cjs');
7
- require('./chunk-GL2AQFLQ.cjs');
3
+ require('./chunk-CC7SUR7Q.cjs');
4
+ require('./chunk-Y5UWFH6Y.cjs');
5
+ require('./chunk-KJSEQVBV.cjs');
6
+ require('./chunk-NEUPEENI.cjs');
7
+ require('./chunk-42ML5AKN.cjs');
8
8
  var chunkTWZNOUM7_cjs = require('./chunk-TWZNOUM7.cjs');
9
9
 
10
10
  // src/present/stops.ts
@@ -1,5 +1,5 @@
1
- import { l as Movie, k as MotionBlurSpec } from './Movie-CL6SsAWe.cjs';
2
- import { S as SequenceSpec, J as Props, K as Keyframe, a1 as TransitionSpec, k as CompositionSpec, Z as Stop } from './types-CavnUvd5.cjs';
1
+ import { l as Movie, k as MotionBlurSpec } from './Movie-Bh1HPdKw.cjs';
2
+ import { S as SequenceSpec, J as Props, K as Keyframe, a1 as TransitionSpec, k as CompositionSpec, Z as Stop } from './types-B6zDTU2V.cjs';
3
3
  import 'pixi.js';
4
4
 
5
5
  interface PresenterOptions {
package/dist/present.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { l as Movie, k as MotionBlurSpec } from './Movie-f0tQG9y5.js';
2
- import { S as SequenceSpec, J as Props, K as Keyframe, a1 as TransitionSpec, k as CompositionSpec, Z as Stop } from './types-CavnUvd5.js';
1
+ import { l as Movie, k as MotionBlurSpec } from './Movie-D5WFfZdZ.js';
2
+ import { S as SequenceSpec, J as Props, K as Keyframe, a1 as TransitionSpec, k as CompositionSpec, Z as Stop } from './types-B6zDTU2V.js';
3
3
  import 'pixi.js';
4
4
 
5
5
  interface PresenterOptions {
package/dist/present.js CHANGED
@@ -1,8 +1,8 @@
1
- import './chunk-67LG7MN5.js';
2
- import './chunk-XV4L72FW.js';
3
- import './chunk-Y253JQOH.js';
4
- import './chunk-RTJD3ZSW.js';
5
- import './chunk-ITDYFBB6.js';
1
+ import './chunk-VF56PRAA.js';
2
+ import './chunk-AZ6AINSW.js';
3
+ import './chunk-G3DX65HO.js';
4
+ import './chunk-WIIU42FH.js';
5
+ import './chunk-2JFFKHXO.js';
6
6
  import { warnUnknownOptions } from './chunk-BXOCEZ4U.js';
7
7
 
8
8
  // src/present/stops.ts
package/dist/three.cjs CHANGED
@@ -1,12 +1,12 @@
1
1
  'use strict';
2
2
 
3
- var chunkLEECUCTG_cjs = require('./chunk-LEECUCTG.cjs');
4
- require('./chunk-GL2AQFLQ.cjs');
3
+ var chunkKJSEQVBV_cjs = require('./chunk-KJSEQVBV.cjs');
4
+ require('./chunk-42ML5AKN.cjs');
5
5
  require('./chunk-TWZNOUM7.cjs');
6
6
  var pixi_js = require('pixi.js');
7
7
  var three$1 = require('three');
8
8
 
9
- var ThreeSequence = class extends chunkLEECUCTG_cjs.Sequence {
9
+ var ThreeSequence = class extends chunkKJSEQVBV_cjs.Sequence {
10
10
  _renderer = null;
11
11
  _scene = null;
12
12
  _camera = null;
@@ -25,7 +25,7 @@ var ThreeSequence = class extends chunkLEECUCTG_cjs.Sequence {
25
25
  if (this.duration === void 0) {
26
26
  this.duration = this.parent?.duration ?? this.root.duration;
27
27
  }
28
- const scope = chunkLEECUCTG_cjs.buildScope(this, this.parent, this.root);
28
+ const scope = chunkKJSEQVBV_cjs.buildScope(this, this.parent, this.root);
29
29
  const width = resolveDim(spec.width, scope) ?? this.parent?.width ?? this.root.width;
30
30
  const height = resolveDim(spec.height, scope) ?? this.parent?.height ?? this.root.height;
31
31
  const resolution = spec.resolution ?? 1;
@@ -145,12 +145,12 @@ var ThreeSequence = class extends chunkLEECUCTG_cjs.Sequence {
145
145
  function resolveDim(v, scope) {
146
146
  if (v === void 0) return void 0;
147
147
  if (typeof v === "number") return v;
148
- return chunkLEECUCTG_cjs.evaluateExpr(v, scope);
148
+ return chunkKJSEQVBV_cjs.evaluateExpr(v, scope);
149
149
  }
150
150
 
151
151
  // src/three/index.ts
152
152
  function registerThree() {
153
- chunkLEECUCTG_cjs.registerSequenceType("three", ThreeSequence);
153
+ chunkKJSEQVBV_cjs.registerSequenceType("three", ThreeSequence);
154
154
  }
155
155
  function three(spec) {
156
156
  return spec;
package/dist/three.d.cts CHANGED
@@ -1,6 +1,6 @@
1
- import { b as SequenceCommon, H as PropValue, S as SequenceSpec } from './types-CavnUvd5.cjs';
1
+ import { b as SequenceCommon, H as PropValue, S as SequenceSpec } from './types-B6zDTU2V.cjs';
2
2
  import { Scene, Camera, WebGLRenderer } from 'three';
3
- import { S as Sequence, P as PathRouters } from './Base-pNq6sVGl.cjs';
3
+ import { S as Sequence, P as PathRouters } from './Base-CmiFiwQM.cjs';
4
4
  import 'pixi.js';
5
5
 
6
6
  /** Handed to setup / update / dispose. One per three sequence. */
package/dist/three.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- import { b as SequenceCommon, H as PropValue, S as SequenceSpec } from './types-CavnUvd5.js';
1
+ import { b as SequenceCommon, H as PropValue, S as SequenceSpec } from './types-B6zDTU2V.js';
2
2
  import { Scene, Camera, WebGLRenderer } from 'three';
3
- import { S as Sequence, P as PathRouters } from './Base-CXsMdmJB.js';
3
+ import { S as Sequence, P as PathRouters } from './Base-CdF7AJop.js';
4
4
  import 'pixi.js';
5
5
 
6
6
  /** Handed to setup / update / dispose. One per three sequence. */
package/dist/three.js CHANGED
@@ -1,5 +1,5 @@
1
- import { Sequence, buildScope, evaluateExpr, registerSequenceType } from './chunk-Y253JQOH.js';
2
- import './chunk-ITDYFBB6.js';
1
+ import { Sequence, buildScope, evaluateExpr, registerSequenceType } from './chunk-G3DX65HO.js';
2
+ import './chunk-2JFFKHXO.js';
3
3
  import './chunk-BXOCEZ4U.js';
4
4
  import { Texture, Sprite } from 'pixi.js';
5
5
  import { WebGLRenderer, Scene, PerspectiveCamera } from 'three';
@@ -292,6 +292,11 @@ interface ImageSequenceSpec extends SequenceCommon {
292
292
  * as on `shape`.
293
293
  */
294
294
  colorSpace?: 'rgb' | 'oklab' | 'oklch';
295
+ /**
296
+ * How the picture is filtered when it is drawn larger or smaller than it is: `'linear'` (smooth, the default) or `'nearest'` (hard square pixels: pixel art, a sprite
297
+ * sheet). It belongs to the asset, so every layer that shows the same asset gets it.
298
+ */
299
+ scaleMode?: 'nearest' | 'linear';
295
300
  }
296
301
  /**
297
302
  * One piece of a text layer's `spans`: its text and what it changes of the layer's `style`: only what keeps the baseline (colours, weight, slant,
@@ -330,11 +335,17 @@ interface TextSequenceSpec extends SequenceCommon {
330
335
  * `style.align` places the lines (default `'center'`, like every text). Measuring needs the fonts loaded (`await document.fonts.load(...)`).
331
336
  */
332
337
  spans?: Array<string | TextSpan>;
338
+ /**
339
+ * Draw the text at this many times the pixels (1 to 8; default: the renderer's own). A text is drawn once at its own size and then stretched by its parents, so text inside a
340
+ * group that is scaled up (a zoom, a pull-in) is soft: give it `resolution: 3` for a group that grows to 3x. The cost is the memory of a picture that much larger.
341
+ */
342
+ resolution?: number;
333
343
  /** How `{value}` is printed: `decimals` (default 0) and thousands `grouping` (default false). */
334
344
  format?: {
335
345
  decimals?: number;
336
346
  grouping?: boolean; /** Zero-pad the whole part to this many digits (`pad: 2` → `05`; a clock `18:42:05`). */
337
- pad?: number;
347
+ pad?: number; /** Print `value` (seconds) as a clock: `'hh:mm:ss'`, `'h:mm:ss'`, `'hh:mm'`, `'mm:ss'` or `'m:ss'` (the seconds are cut, not rounded; the hours do not wrap). */
348
+ time?: 'hh:mm:ss' | 'h:mm:ss' | 'hh:mm' | 'mm:ss' | 'm:ss';
338
349
  };
339
350
  /** Subset of PIXI v8 TextStyleOptions. String values may be exprs (e.g. fontSize: 'GW * 0.05'). */
340
351
  style?: Record<string, PropValue | {
@@ -674,6 +685,8 @@ interface PathShapeSpec extends ShapeBase, Trimmable {
674
685
  shape: 'path';
675
686
  /** SVG path data (`d` attribute). Goes through PIXI's GraphicsContext.svg(). */
676
687
  d: string;
688
+ /** `'evenodd'`: with several sub-paths in `d` the biggest one is the shape and the others are holes (a donut is two circles). Default `'nonzero'`: each sub-path is filled on its own, no holes. */
689
+ fillRule?: 'nonzero' | 'evenodd';
677
690
  /**
678
691
  * A second outline (SVG path data, in the same canvas coordinates as `d`) to morph into. Animate `morph` from 0 (the
679
692
  * `d` outline) to 1 (this one); in between the layer is drawn as a polygon of points half way from one to the other.
@@ -292,6 +292,11 @@ interface ImageSequenceSpec extends SequenceCommon {
292
292
  * as on `shape`.
293
293
  */
294
294
  colorSpace?: 'rgb' | 'oklab' | 'oklch';
295
+ /**
296
+ * How the picture is filtered when it is drawn larger or smaller than it is: `'linear'` (smooth, the default) or `'nearest'` (hard square pixels: pixel art, a sprite
297
+ * sheet). It belongs to the asset, so every layer that shows the same asset gets it.
298
+ */
299
+ scaleMode?: 'nearest' | 'linear';
295
300
  }
296
301
  /**
297
302
  * One piece of a text layer's `spans`: its text and what it changes of the layer's `style`: only what keeps the baseline (colours, weight, slant,
@@ -330,11 +335,17 @@ interface TextSequenceSpec extends SequenceCommon {
330
335
  * `style.align` places the lines (default `'center'`, like every text). Measuring needs the fonts loaded (`await document.fonts.load(...)`).
331
336
  */
332
337
  spans?: Array<string | TextSpan>;
338
+ /**
339
+ * Draw the text at this many times the pixels (1 to 8; default: the renderer's own). A text is drawn once at its own size and then stretched by its parents, so text inside a
340
+ * group that is scaled up (a zoom, a pull-in) is soft: give it `resolution: 3` for a group that grows to 3x. The cost is the memory of a picture that much larger.
341
+ */
342
+ resolution?: number;
333
343
  /** How `{value}` is printed: `decimals` (default 0) and thousands `grouping` (default false). */
334
344
  format?: {
335
345
  decimals?: number;
336
346
  grouping?: boolean; /** Zero-pad the whole part to this many digits (`pad: 2` → `05`; a clock `18:42:05`). */
337
- pad?: number;
347
+ pad?: number; /** Print `value` (seconds) as a clock: `'hh:mm:ss'`, `'h:mm:ss'`, `'hh:mm'`, `'mm:ss'` or `'m:ss'` (the seconds are cut, not rounded; the hours do not wrap). */
348
+ time?: 'hh:mm:ss' | 'h:mm:ss' | 'hh:mm' | 'mm:ss' | 'm:ss';
338
349
  };
339
350
  /** Subset of PIXI v8 TextStyleOptions. String values may be exprs (e.g. fontSize: 'GW * 0.05'). */
340
351
  style?: Record<string, PropValue | {
@@ -674,6 +685,8 @@ interface PathShapeSpec extends ShapeBase, Trimmable {
674
685
  shape: 'path';
675
686
  /** SVG path data (`d` attribute). Goes through PIXI's GraphicsContext.svg(). */
676
687
  d: string;
688
+ /** `'evenodd'`: with several sub-paths in `d` the biggest one is the shape and the others are holes (a donut is two circles). Default `'nonzero'`: each sub-path is filled on its own, no holes. */
689
+ fillRule?: 'nonzero' | 'evenodd';
677
690
  /**
678
691
  * A second outline (SVG path data, in the same canvas coordinates as `d`) to morph into. Animate `morph` from 0 (the
679
692
  * `d` outline) to 1 (this one); in between the layer is drawn as a polygon of points half way from one to the other.
package/docs/api.md CHANGED
@@ -107,6 +107,15 @@ snapshot(frame?: number, options?: { scale?: number; type?: 'image/png' | 'image
107
107
 
108
108
  A picture of one frame: **the canvas only** (the player bar is not in it). Seeks to `frame` (default: the current frame) and stays there. `as: 'dataURL'` returns a `data:` URL string, `as: 'imageData'` the pixels as an `ImageData` (to measure or compare; `quality` is the JPEG quality, 0 to 1), handy when a script can only return text. Use it to look at what you built.
109
109
 
110
+ ### `movie.thumbnail(frame, options?): Promise<HTMLCanvasElement | null>`
111
+
112
+ ```ts
113
+ thumbnail(frame: number, options?: { width?: number; scale?: number }): Promise<HTMLCanvasElement | null> // width default 160 px
114
+ movie.thumbnailsOff // null, 'video' or 'variables'
115
+ ```
116
+
117
+ A small picture of any frame, made **without moving the playhead or changing what is on the canvas**, also while the movie plays (a seek-bar preview): the scene is put on that moment, drawn, copied and put back inside one task, so the browser never shows the in-between. It is the picture `snapshot()` gives at that size, without motion blur. It gives `null` when it cannot be made right now (a seek, a render or another thumbnail is running; ask again) and always when `movie.thumbnailsOff` is set: `'video'` for a movie with a video layer (the footage would have to seek twice), `'variables'` for a movie that declares variables. The player bar uses it for `thumbnails: true`.
118
+
110
119
  ### `movie.contactSheet(options?): Promise<Blob | string>`
111
120
 
112
121
  ```ts
@@ -128,13 +137,13 @@ Text layout helpers, exported from `pixi-effects`. They measure with the rendere
128
137
  ```ts
129
138
  measureText('Hello', { fontSize: 72 }); // { width: 187.4, height: 81.3, lines: 1 }
130
139
 
131
- interface SplitOptions { by?: 'chars' | 'words' | 'lines'; x?: number; y?: number; align?: 'left' | 'center' | 'right' }
132
- interface TextPiece { text: string; x: number; y: number; width: number; index: number; line: number }
140
+ interface SplitOptions { by?: 'chars' | 'words' | 'lines'; x?: number; y?: number; align?: 'left' | 'center' | 'right'; vertical?: boolean }
141
+ interface TextPiece { text: string; x: number; y: number; width: number; index: number; line: number; cx?: number; cy?: number; rotation?: number } // cx / cy / rotation: vertical text only
133
142
  splitText('Hello world', style, { by: 'words', x: 640, y: 300, align: 'center' });
134
143
  // → [{ text: 'Hello', x, y, width, index: 0, line: 0 }, { text: 'world', ... }]
135
144
  ```
136
145
 
137
- `x` is the line's left edge, or its centre / right edge with `align` (each line of a `\n` text is aligned on its own width); `y` is the top of the first line, and further lines go down by the line height. Whitespace takes its room but is not returned as a piece. Use one text layer per piece with the same `style` and the default top-left anchor: `{ type: 'text', text: p.text, style, initial: { x: p.x, y: p.y } }`. Kerning is kept (a piece's x is the measured width of everything before it).
146
+ `x` is the line's left edge, or its centre / right edge with `align` (each line of a `\n` text is aligned on its own width); `y` is the top of the first line, and further lines go down by the line height. **Vertical writing (`vertical: true`, tategaki):** characters go down a column and a new line is a new column to the LEFT; `x` is the RIGHT edge of the first column, `y` the top of a column (`align: 'center'` / `'right'` put `y` at its middle / bottom). Each piece also has `cx` / `cy` (the centre of its cell, with 、。 moved to the upper right and small kana a little up and right) and `rotation` (90 for the long vowel mark, dashes, waves, leaders and brackets, which lie turned in vertical text): put a text layer with `anchorX: 0.5, anchorY: 0.5` at `cx` / `cy` with `rotation: p.rotation`, or call `animateText(..., { vertical: true })`, which does it. Latin letters and digits stay upright, one per cell (no tate-chu-yoko), and a line is only cut at `\n` (no kinsoku). Only `by: 'chars'` has a vertical form. Whitespace takes its room but is not returned as a piece. Use one text layer per piece with the same `style` and the default top-left anchor: `{ type: 'text', text: p.text, style, initial: { x: p.x, y: p.y } }`. Kerning is kept (a piece's x is the measured width of everything before it).
138
147
 
139
148
  ### `movie.inspect(frame?, options?): Promise<InspectReport>`
140
149
 
@@ -144,7 +153,7 @@ Where every layer is drawn at `frame`, as JSON — for checking layout without e
144
153
  interface InspectReport {
145
154
  frame: number; time: number; canvas: { width: number; height: number };
146
155
  summary: { layers: number; visible: number };
147
- issues: string[]; // text off the canvas / cut by an edge / empty / overlapping other text — read this first
156
+ issues: string[]; // text off the canvas / cut by an edge / empty / overlapping other text / partly hidden by an opaque shape or image drawn over it / struck through by a thin line drawn over it — read this first
148
157
  layers: Array<{
149
158
  path: string; // names (or type#index) from the root, joined by '/'
150
159
  name?: string; type: string; threeD: boolean;
@@ -169,6 +178,7 @@ interface AudioReport {
169
178
  start: number; end: number; // movie time
170
179
  peakDb: number; // the MIX while it plays
171
180
  sound: { length: number; peakDb: number; loudestAt: number; brightnessHz: number }; // the sound alone
181
+ belowMixDb?: number; // how far below the loudest sample of the mix (itself included) around its own loudest moment it is; short sfx more than 12 dB under get ONE note, "buried", naming the worst
172
182
  }>;
173
183
  windows: Array<{ t: number; rmsDb: number; peakDb: number; brightnessHz: number }>;
174
184
  issues: string[]; // limited mix, inaudible layer, sfx cut off by the end, no audio — read first
@@ -481,6 +491,7 @@ interface ControllerOptions {
481
491
  keyboard?: boolean; // default true
482
492
  presentButton?: boolean; // default true: a movie with stops gets a Present button (marks on the seek bar are always shown)
483
493
  loopButton?: boolean; // default true: a loop button next to the volume (and the L key) that flips movie.loop
494
+ thumbnails?: boolean; // default false: a small picture of the frame under the pointer above the seek bar (movie.thumbnail()); off by itself with video footage or variables
484
495
  pauseAtStops?: boolean; // default true: with stops, play / Space / click on the picture play to the next stop and pause; false plays straight through
485
496
  clickToPlay?: boolean; // default true: a click / tap on the picture plays / pauses (like <video>); false leaves the canvas alone
486
497
  className?: string; // default 'movie-controller'