pixi-effects 0.35.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 (75) hide show
  1. package/CHANGELOG.md +18 -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.d.cts +2 -2
  13. package/dist/Controller.d.ts +2 -2
  14. package/dist/{Movie-VYtUN4TS.d.ts → Movie-Bh1HPdKw.d.cts} +6 -1
  15. package/dist/{Movie-Db4QnMIH.d.cts → Movie-D5WFfZdZ.d.ts} +6 -1
  16. package/dist/{Spans-QRMN2EZO.cjs → Spans-4IRBSOLI.cjs} +11 -11
  17. package/dist/{Spans-QRMN2EZO.cjs.map → Spans-4IRBSOLI.cjs.map} +1 -1
  18. package/dist/{Spans-QHHWC2PB.js → Spans-MWP2FX3T.js} +6 -6
  19. package/dist/{Spans-QHHWC2PB.js.map → Spans-MWP2FX3T.js.map} +1 -1
  20. package/dist/{blendModes-WIUCYIMC.cjs → blendModes-KU7XLV7U.cjs} +5 -5
  21. package/dist/{blendModes-WIUCYIMC.cjs.map → blendModes-KU7XLV7U.cjs.map} +1 -1
  22. package/dist/{blendModes-PP5NPBXY.js → blendModes-OOI3T6BE.js} +4 -4
  23. package/dist/{blendModes-PP5NPBXY.js.map → blendModes-OOI3T6BE.js.map} +1 -1
  24. package/dist/{chunk-ITDYFBB6.js → chunk-2JFFKHXO.js} +5 -5
  25. package/dist/chunk-2JFFKHXO.js.map +1 -0
  26. package/dist/{chunk-GL2AQFLQ.cjs → chunk-42ML5AKN.cjs} +5 -5
  27. package/dist/chunk-42ML5AKN.cjs.map +1 -0
  28. package/dist/{chunk-XV4L72FW.js → chunk-AZ6AINSW.js} +5 -5
  29. package/dist/{chunk-XV4L72FW.js.map → chunk-AZ6AINSW.js.map} +1 -1
  30. package/dist/{chunk-7TTCPHAV.cjs → chunk-CC7SUR7Q.cjs} +153 -48
  31. package/dist/chunk-CC7SUR7Q.cjs.map +1 -0
  32. package/dist/{chunk-Y253JQOH.js → chunk-G3DX65HO.js} +110 -10
  33. package/dist/chunk-G3DX65HO.js.map +1 -0
  34. package/dist/{chunk-LEECUCTG.cjs → chunk-KJSEQVBV.cjs} +129 -29
  35. package/dist/chunk-KJSEQVBV.cjs.map +1 -0
  36. package/dist/{chunk-IMQJVRMT.cjs → chunk-NEUPEENI.cjs} +5 -5
  37. package/dist/{chunk-IMQJVRMT.cjs.map → chunk-NEUPEENI.cjs.map} +1 -1
  38. package/dist/{chunk-2GNK6DPV.js → chunk-VF56PRAA.js} +121 -16
  39. package/dist/chunk-VF56PRAA.js.map +1 -0
  40. package/dist/{chunk-RTJD3ZSW.js → chunk-WIIU42FH.js} +4 -4
  41. package/dist/{chunk-RTJD3ZSW.js.map → chunk-WIIU42FH.js.map} +1 -1
  42. package/dist/{chunk-5IJENZOG.cjs → chunk-Y5UWFH6Y.cjs} +91 -91
  43. package/dist/{chunk-5IJENZOG.cjs.map → chunk-Y5UWFH6Y.cjs.map} +1 -1
  44. package/dist/index.cjs +33 -33
  45. package/dist/index.d.cts +20 -6
  46. package/dist/index.d.ts +20 -6
  47. package/dist/index.js +5 -5
  48. package/dist/loader.css +1 -1
  49. package/dist/present.cjs +5 -5
  50. package/dist/present.d.cts +2 -2
  51. package/dist/present.d.ts +2 -2
  52. package/dist/present.js +5 -5
  53. package/dist/three.cjs +6 -6
  54. package/dist/three.d.cts +2 -2
  55. package/dist/three.d.ts +2 -2
  56. package/dist/three.js +2 -2
  57. package/dist/{types-CavnUvd5.d.ts → types-B6zDTU2V.d.cts} +14 -1
  58. package/dist/{types-CavnUvd5.d.cts → types-B6zDTU2V.d.ts} +14 -1
  59. package/docs/api.md +5 -4
  60. package/docs/dsl.md +7 -5
  61. package/llms-full.txt +85 -18
  62. package/package.json +1 -1
  63. package/skills/pixi-effects/SKILL.md +3 -3
  64. package/skills/pixi-effects/reference/cheatsheet.md +7 -6
  65. package/skills/pixi-effects/reference/pitfalls.md +3 -0
  66. package/skills/pixi-effects/reference/recipes.md +60 -0
  67. package/skills/pixi-effects/template.html +6 -6
  68. package/dist/Composition-NDXKQHB4.js +0 -7
  69. package/dist/Composition-OLH7RC5Z.cjs +0 -16
  70. package/dist/chunk-2GNK6DPV.js.map +0 -1
  71. package/dist/chunk-7TTCPHAV.cjs.map +0 -1
  72. package/dist/chunk-GL2AQFLQ.cjs.map +0 -1
  73. package/dist/chunk-ITDYFBB6.js.map +0 -1
  74. package/dist/chunk-LEECUCTG.cjs.map +0 -1
  75. 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 chunk7TTCPHAV_cjs = require('./chunk-7TTCPHAV.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 chunk7TTCPHAV_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 chunk7TTCPHAV_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 chunk7TTCPHAV_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 chunk7TTCPHAV_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 chunk7TTCPHAV_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 chunk7TTCPHAV_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 chunk7TTCPHAV_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 chunk7TTCPHAV_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 chunk7TTCPHAV_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 chunk7TTCPHAV_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 chunk7TTCPHAV_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 chunk7TTCPHAV_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 chunk7TTCPHAV_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 chunk7TTCPHAV_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 chunk7TTCPHAV_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 chunk7TTCPHAV_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 chunk7TTCPHAV_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 chunk7TTCPHAV_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 chunk7TTCPHAV_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 chunk7TTCPHAV_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 chunk7TTCPHAV_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 chunk7TTCPHAV_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 chunk7TTCPHAV_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-Db4QnMIH.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-Db4QnMIH.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-VYtUN4TS.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-VYtUN4TS.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-2GNK6DPV.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.35.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-7TTCPHAV.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-Db4QnMIH.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-VYtUN4TS.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-2GNK6DPV.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
@@ -137,13 +137,13 @@ Text layout helpers, exported from `pixi-effects`. They measure with the rendere
137
137
  ```ts
138
138
  measureText('Hello', { fontSize: 72 }); // { width: 187.4, height: 81.3, lines: 1 }
139
139
 
140
- interface SplitOptions { by?: 'chars' | 'words' | 'lines'; x?: number; y?: number; align?: 'left' | 'center' | 'right' }
141
- 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
142
142
  splitText('Hello world', style, { by: 'words', x: 640, y: 300, align: 'center' });
143
143
  // → [{ text: 'Hello', x, y, width, index: 0, line: 0 }, { text: 'world', ... }]
144
144
  ```
145
145
 
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. 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).
147
147
 
148
148
  ### `movie.inspect(frame?, options?): Promise<InspectReport>`
149
149
 
@@ -153,7 +153,7 @@ Where every layer is drawn at `frame`, as JSON — for checking layout without e
153
153
  interface InspectReport {
154
154
  frame: number; time: number; canvas: { width: number; height: number };
155
155
  summary: { layers: number; visible: number };
156
- 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
157
157
  layers: Array<{
158
158
  path: string; // names (or type#index) from the root, joined by '/'
159
159
  name?: string; type: string; threeD: boolean;
@@ -178,6 +178,7 @@ interface AudioReport {
178
178
  start: number; end: number; // movie time
179
179
  peakDb: number; // the MIX while it plays
180
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
181
182
  }>;
182
183
  windows: Array<{ t: number; rmsDb: number; peakDb: number; brightnessHz: number }>;
183
184
  issues: string[]; // limited mix, inaudible layer, sfx cut off by the end, no audio — read first
package/docs/dsl.md CHANGED
@@ -131,7 +131,7 @@ A text layer has one animatable number, `value`, printed wherever the text conta
131
131
  }
132
132
  ```
133
133
 
134
- `format`: `decimals` (default 0), thousands `grouping` (default false) and `pad` (zero-pad the whole part to that many digits: `pad: 2` gives `05`, so a clock is `text: '18:42:{value}'` with `format: { pad: 2 }`). Put prefixes and suffixes in the text (`'${value}'`, `'{value}%'`). If `value` is animated but the text has no `{value}`, a warning says so.
134
+ `resolution` (1 to 8, default the renderer's own) draws the text at that many times the pixels: text is drawn once at its own size and then stretched by its parents, so text inside a group scaled up to 3x is soft unless it has `resolution: 3`. `format`: `decimals` (default 0), thousands `grouping` (default false) and `pad` (zero-pad the whole part to that many digits: `pad: 2` gives `05`, so a clock is `text: '18:42:{value}'` with `format: { pad: 2 }`) and `time` (print the value, in SECONDS, as a clock: `'hh:mm:ss'`, `'h:mm:ss'`, `'hh:mm'`, `'mm:ss'` or `'m:ss'`; the seconds are cut, not rounded, and the hours do not wrap, so a day clock keeps the value inside 0–86399 with `mod`: `{ value: 15000 }` with `format: { time: 'hh:mm' }` is `04:10`). Put prefixes and suffixes in the text (`'${value}'`, `'{value}%'`). If `value` is animated but the text has no `{value}`, a warning says so.
135
135
 
136
136
  **Colour inside one text layer: `spans`.** Instead of `text`, write an array of strings (drawn in the layer's own `style`) and `{ text, fill, … }` objects; they are shown one after the other, and `\n` inside a span starts a new line:
137
137
 
@@ -145,7 +145,7 @@ A span may change `fill`, `fontWeight`, `fontStyle`, `fontVariant`, `letterSpaci
145
145
 
146
146
  ### `image`
147
147
 
148
- Renders a [PIXI.Sprite](https://pixijs.com/8.x/guides/components/scene-objects/sprite/sprite) from a registered asset. Intrinsic `w`/`h` come from the loaded texture.
148
+ Renders a [PIXI.Sprite](https://pixijs.com/8.x/guides/components/scene-objects/sprite/sprite) from a registered asset. Intrinsic `w`/`h` come from the loaded texture. `scaleMode: 'nearest'` draws the pixels as hard squares when the picture is scaled up (pixel art, a sprite drawn small and shown big); the default `'linear'` smooths. It belongs to the asset: every layer that shows it gets it.
149
149
 
150
150
  ```ts
151
151
  {
@@ -268,7 +268,7 @@ A tune with no audio file: notes, chords and drum patterns written as strings, p
268
268
  } }
269
269
  ```
270
270
 
271
- **Layer.** `at` starts it, `volume` (and volume keyframes) scale it. `duration` defaults to the music's length plus its reverb tail (`tail`, 2.5 s), but never past the end of the composition (the music then ends with the movie, with a short fade). A shorter `duration` cuts it with a fade; a longer one is silent after the music unless **`loop: true`**, which repeats the score until the layer ends (default: the end of the composition). At `volume: 1` the music peaks at −6 dBFS (about −20 dB RMS), a bed that sits under speech and sound effects (an sfx peaks at −12 dBFS). Measured: a 4-bar tune (bell + bass + kick, a pluck and a hat on the repeat) at `volume: 0.8` reads −22 LUFS and −7.7 dBTP (`piano-roll`); web video is usually −14 to −16 LUFS, so 0.8 is not a limit: raise `volume` and watch `inspectAudio().loudness.truePeakDb`. `inspectAudio()` lists a music layer as ONE source (its start); to line a picture up with a note, take its time from [`musicEvents(music)`](api.md#musicevents) (seconds from the start of the music, the tempo map applied: add the layer's `at`), or build the bars and the score from one array so they cannot drift.
271
+ **Layer.** `at` starts it, `volume` (and volume keyframes) scale it. `duration` defaults to the music's length plus its reverb tail (`tail`, 2.5 s), but never past the end of the composition (the music then ends with the movie, with a short fade). A shorter `duration` cuts it with a fade; a longer one is silent after the music unless **`loop: true`**, which repeats the score until the layer ends (default: the end of the composition). At `volume: 1` the music peaks at −6 dBFS (about −20 dB RMS), a bed that sits under speech and sound effects (an sfx peaks at −12 dBFS). Measured: a 4-bar tune (bell + bass + kick, a pluck and a hat on the repeat) at `volume: 0.8` reads −22 LUFS and −7.7 dBTP; web video is usually −14 to −16 LUFS, so 0.8 is not a limit: raise `volume` and watch `inspectAudio().loudness.truePeakDb`. `inspectAudio()` lists a music layer as ONE source (its start); to line a picture up with a note, take its time from [`musicEvents(music)`](api.md#musicevents) (seconds from the start of the music, the tempo map applied: add the layer's `at`), or build the bars and the score from one array so they cannot drift.
272
272
 
273
273
  **Score** (`music`): `bpm` (a number, or `[beat, bpm]` points for a tempo change: `[[0, 96], [28, 96], [32, 60]]` slows down over beats 28–32, a ritardando), `tracks`, `drums`, `swing` (0–0.4: delays the off-beat eighths and sixteenths; 0.16 is a gentle lo-fi lilt), `reverb` (0–0.6, default 0.22), `humanize` (seconds of seeded timing jitter, default 0.012), `drumVol` (default 0.8), `grid` (drum steps per beat, default 4), `meter` (beats in a bar, default 4: it only names the bars in [`musicEvents()`](api.md#musicevents) and in the `check` read-back, playback does not use it), `transpose` (semitones, all pitched tracks), `tail`, `seed`. Misspelt names warn with "did you mean". The music's length is its longest track (drums repeat up to it), so a track of rests (`'_:16'`) sets a length.
274
274
 
@@ -348,7 +348,9 @@ Every primitive draws centred on its local origin (so `anchorX`/`anchorY` and `p
348
348
  | `arc` | `radius` | `innerRadius`, `startAngle` (0), `endAngle` (360) |
349
349
  | `line` | `from: [x,y]`, `to: [x,y]` (canvas coordinates; stroke in `initial`) | `trimStart`, `trimEnd` |
350
350
  | `polygon` | `points: [[x,y], …]` | `open` (default false), `trimStart`, `trimEnd` |
351
- | `path` | `d` (SVG path data) | `trimStart`, `trimEnd`, `morphTo`, `morph`, `morphPoints` |
351
+ | `path` | `d` (SVG path data) | `fillRule`, `trimStart`, `trimEnd`, `morphTo`, `morph`, `morphPoints` |
352
+
353
+ **Holes (`fillRule: 'evenodd'`) on a `path`.** Several sub-paths in one `d` are each filled on their own: a donut written as two circles is a solid disc. With `fillRule: 'evenodd'` the biggest sub-path is the shape and every other one is a hole cut out of it (the stroke goes round the shape and each hole): `{ shape: 'path', fillRule: 'evenodd', d: 'M 0 -100 A 100 100 0 1 1 0 100 A 100 100 0 1 1 0 -100 Z M 0 -50 A 50 50 0 1 0 0 50 A 50 50 0 1 0 0 -50 Z', initial: { x: 640, y: 360, fillColor: '#ffcc00' } }`. Write the sub-paths in absolute coordinates (each starts with `M`); an island inside a hole is not drawn, and a path that morphs (`morphTo`) has no holes.
352
354
 
353
355
  **`arc`** is an arc around its local origin (the centre). Angles are in **degrees: 0° is 3 o'clock and positive is clockwise**, so a progress ring starts at `startAngle: -90` (12 o'clock) and animates `endAngle` from `-90` to `270`. With a stroke and no fill it is an open arc line (`strokeCap: 'round'` for rounded ends); with a fill it is a sector (a pie slice), and with `innerRadius` a ring segment (a donut slice). A sweep of 360° or more is a full circle; `endAngle < startAngle` sweeps counter-clockwise. `radius`, `innerRadius`, `startAngle` and `endAngle` are all animatable.
354
356
 
@@ -366,7 +368,7 @@ Every primitive draws centred on its local origin (so `anchorX`/`anchorY` and `p
366
368
  keyframes: [{ at: 0, to: { trimEnd: 1 }, duration: 1, ease: 'power2.inOut' }] } // a check mark drawing itself
367
369
  ```
368
370
 
369
- **Morph (`morphTo` / `morph`).** A `path` can turn into another outline: give `morphTo` (SVG path data in the same canvas coordinates as `d`) and animate `morph` from 0 (the `d` outline) to 1 (the `morphTo` outline). In between it is drawn as a polygon of points half way from one to the other (`morphPoints`: 8–2048, default 128). Closed outlines are lined up (start point and direction) so they do not twist; sub-paths are paired in order, and an extra one grows from (or shrinks into) its own centre. Fill and stroke both follow, and `trimStart` / `trimEnd` walk the morphing outline. Write both outlines as `M … L … C … Z` in absolute coordinates (arcs and holes work too, but keep the same number of sub-paths on both sides).
371
+ **Morph (`morphTo` / `morph`).** (Two outlines with the **same number of corners**, such as two quads, are paired corner to corner, so every in-between picture is a straight-edged quad and no corner is rounded; `morphPoints` only matters when the corner counts differ or the outlines are curves.) A `path` can turn into another outline: give `morphTo` (SVG path data in the same canvas coordinates as `d`) and animate `morph` from 0 (the `d` outline) to 1 (the `morphTo` outline). In between it is drawn as a polygon of points half way from one to the other (`morphPoints`: 8–2048, default 128). Closed outlines are lined up (start point and direction) so they do not twist; sub-paths are paired in order, and an extra one grows from (or shrinks into) its own centre. Fill and stroke both follow, and `trimStart` / `trimEnd` walk the morphing outline. Write both outlines as `M … L … C … Z` in absolute coordinates (arcs and holes work too, but keep the same number of sub-paths on both sides).
370
372
 
371
373
  ```ts
372
374
  { type: 'shape', shape: 'path', d: 'M 640 160 C 780 160 860 260 860 360 C 860 470 770 560 640 560 C 510 560 420 470 420 360 C 420 250 500 160 640 160 Z',