pixi-effects 0.12.0 → 0.13.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.
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.12.0/dist/loader.css">
4
+ * <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/pixi-effects@0.13.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/three.d.cts CHANGED
@@ -1,6 +1,6 @@
1
- import { d as SequenceCommon, s as PropValue, S as SequenceSpec } from './types-mpqQO8MJ.cjs';
1
+ import { d as SequenceCommon, s as PropValue, S as SequenceSpec } from './types-CBSiUKau.cjs';
2
2
  import { Scene, Camera, WebGLRenderer } from 'three';
3
- import { S as Sequence, P as PathRouters } from './Base-W3sQzGPO.cjs';
3
+ import { S as Sequence, P as PathRouters } from './Base-C6ZnJ9pD.cjs';
4
4
  import 'pixi.js';
5
5
  import 'gsap';
6
6
 
package/dist/three.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- import { d as SequenceCommon, s as PropValue, S as SequenceSpec } from './types-mpqQO8MJ.js';
1
+ import { d as SequenceCommon, s as PropValue, S as SequenceSpec } from './types-CBSiUKau.js';
2
2
  import { Scene, Camera, WebGLRenderer } from 'three';
3
- import { S as Sequence, P as PathRouters } from './Base-CYH9vPCH.js';
3
+ import { S as Sequence, P as PathRouters } from './Base-CQOlB2VH.js';
4
4
  import 'pixi.js';
5
5
  import 'gsap';
6
6
 
@@ -8,6 +8,11 @@ interface StopSpec {
8
8
  notes?: string;
9
9
  /** Move on by itself after this many seconds (a kiosk, a looping demo). */
10
10
  advance?: number;
11
+ /**
12
+ * Which picture stands for a page in a PDF (and in the page overview). `true`: this stop's picture IS the page's picture (use it when the
13
+ * last stop is mid-exit or a step you do not want on paper). `false`: never use this stop's picture for the PDF. Default: the page's last stop.
14
+ */
15
+ pdf?: boolean;
11
16
  }
12
17
  /** A stop, normalised: its frame, and which page it belongs to. */
13
18
  interface Stop {
@@ -22,6 +27,7 @@ interface Stop {
22
27
  pageStart: boolean;
23
28
  notes?: string;
24
29
  advance?: number;
30
+ pdf?: boolean;
25
31
  }
26
32
  /**
27
33
  * The stops of a composition, checked and in order. Numbers are stops; with no `page` anywhere every stop is a page of its
@@ -29,6 +35,17 @@ interface Stop {
29
35
  * two stops on the same frame are one; anything outside the movie or not a number is dropped, with a warning that names it.
30
36
  */
31
37
  declare function normalizeStops(raw: ReadonlyArray<number | StopSpec> | undefined, duration: number, frameRate: number): Stop[];
38
+ /**
39
+ * The stops whose pictures stand for the pages (`which: 'pages'`, default) or for every stop. A page's picture is, in order: the stop flagged
40
+ * `pdf: true` (the last such one, or the first with `pick: 'first'`), else the last (or first) stop not flagged `pdf: false`, else, for a page
41
+ * list, its last (or first) stop, and for a PDF (`pdf: true` here) nothing: the page is left out. With `which: 'stops'` a PDF leaves out the
42
+ * stops flagged `pdf: false`.
43
+ */
44
+ declare function pictureStops(stops: readonly Stop[], opts?: {
45
+ which?: 'pages' | 'stops';
46
+ pick?: 'last' | 'first';
47
+ pdf?: boolean;
48
+ }): Stop[];
32
49
 
33
50
  /**
34
51
  * pixi-effects public DSL types.
@@ -551,4 +568,4 @@ interface AudioDescriptor {
551
568
  }[];
552
569
  }
553
570
 
554
- export { type ArcShapeSpec as A, type StopSpec as B, type CompositionShape as C, type DipTransition as D, type EllipseShapeSpec as E, type FilterSpec as F, type GradientSpec as G, type TransitionCommon as H, type ImageSequenceSpec as I, normalizeStops as J, type Keyframe as K, type LineShapeSpec as L, type AudioDescriptor as M, type NullSequenceSpec as N, type Props as P, type RectShapeSpec as R, type SequenceSpec as S, type TextSequenceSpec as T, type VideoSequenceSpec as V, type WipeTransition as W, type ZoomTransition as Z, type CameraSequenceSpec as a, type TransitionSpec as b, type CompositionSpec as c, type SequenceCommon as d, type AssetSpec as e, type AudioAssetSpec as f, type AudioSequenceSpec as g, type AudioSfxSpec as h, type ChromaKeyFilterSpec as i, type CircleShapeSpec as j, type CompositionSequenceSpec as k, type CrossfadeTransition as l, type CustomFilterSpec as m, type DissolveTransition as n, type Expr as o, type IrisTransition as p, type PathShapeSpec as q, type PolygonShapeSpec as r, type PropValue as s, type SfxKnobs as t, type SfxOptions as u, type SfxPreset as v, type SfxVoice as w, type ShapeSequenceSpec as x, type SlideTransition as y, type Stop as z };
571
+ export { type ArcShapeSpec as A, type StopSpec as B, type CompositionShape as C, type DipTransition as D, type EllipseShapeSpec as E, type FilterSpec as F, type GradientSpec as G, type TransitionCommon as H, type ImageSequenceSpec as I, normalizeStops as J, type Keyframe as K, type LineShapeSpec as L, pictureStops as M, type NullSequenceSpec as N, type AudioDescriptor as O, type Props as P, type RectShapeSpec as R, type SequenceSpec as S, type TextSequenceSpec as T, type VideoSequenceSpec as V, type WipeTransition as W, type ZoomTransition as Z, type CameraSequenceSpec as a, type TransitionSpec as b, type CompositionSpec as c, type SequenceCommon as d, type AssetSpec as e, type AudioAssetSpec as f, type AudioSequenceSpec as g, type AudioSfxSpec as h, type ChromaKeyFilterSpec as i, type CircleShapeSpec as j, type CompositionSequenceSpec as k, type CrossfadeTransition as l, type CustomFilterSpec as m, type DissolveTransition as n, type Expr as o, type IrisTransition as p, type PathShapeSpec as q, type PolygonShapeSpec as r, type PropValue as s, type SfxKnobs as t, type SfxOptions as u, type SfxPreset as v, type SfxVoice as w, type ShapeSequenceSpec as x, type SlideTransition as y, type Stop as z };
@@ -8,6 +8,11 @@ interface StopSpec {
8
8
  notes?: string;
9
9
  /** Move on by itself after this many seconds (a kiosk, a looping demo). */
10
10
  advance?: number;
11
+ /**
12
+ * Which picture stands for a page in a PDF (and in the page overview). `true`: this stop's picture IS the page's picture (use it when the
13
+ * last stop is mid-exit or a step you do not want on paper). `false`: never use this stop's picture for the PDF. Default: the page's last stop.
14
+ */
15
+ pdf?: boolean;
11
16
  }
12
17
  /** A stop, normalised: its frame, and which page it belongs to. */
13
18
  interface Stop {
@@ -22,6 +27,7 @@ interface Stop {
22
27
  pageStart: boolean;
23
28
  notes?: string;
24
29
  advance?: number;
30
+ pdf?: boolean;
25
31
  }
26
32
  /**
27
33
  * The stops of a composition, checked and in order. Numbers are stops; with no `page` anywhere every stop is a page of its
@@ -29,6 +35,17 @@ interface Stop {
29
35
  * two stops on the same frame are one; anything outside the movie or not a number is dropped, with a warning that names it.
30
36
  */
31
37
  declare function normalizeStops(raw: ReadonlyArray<number | StopSpec> | undefined, duration: number, frameRate: number): Stop[];
38
+ /**
39
+ * The stops whose pictures stand for the pages (`which: 'pages'`, default) or for every stop. A page's picture is, in order: the stop flagged
40
+ * `pdf: true` (the last such one, or the first with `pick: 'first'`), else the last (or first) stop not flagged `pdf: false`, else, for a page
41
+ * list, its last (or first) stop, and for a PDF (`pdf: true` here) nothing: the page is left out. With `which: 'stops'` a PDF leaves out the
42
+ * stops flagged `pdf: false`.
43
+ */
44
+ declare function pictureStops(stops: readonly Stop[], opts?: {
45
+ which?: 'pages' | 'stops';
46
+ pick?: 'last' | 'first';
47
+ pdf?: boolean;
48
+ }): Stop[];
32
49
 
33
50
  /**
34
51
  * pixi-effects public DSL types.
@@ -551,4 +568,4 @@ interface AudioDescriptor {
551
568
  }[];
552
569
  }
553
570
 
554
- export { type ArcShapeSpec as A, type StopSpec as B, type CompositionShape as C, type DipTransition as D, type EllipseShapeSpec as E, type FilterSpec as F, type GradientSpec as G, type TransitionCommon as H, type ImageSequenceSpec as I, normalizeStops as J, type Keyframe as K, type LineShapeSpec as L, type AudioDescriptor as M, type NullSequenceSpec as N, type Props as P, type RectShapeSpec as R, type SequenceSpec as S, type TextSequenceSpec as T, type VideoSequenceSpec as V, type WipeTransition as W, type ZoomTransition as Z, type CameraSequenceSpec as a, type TransitionSpec as b, type CompositionSpec as c, type SequenceCommon as d, type AssetSpec as e, type AudioAssetSpec as f, type AudioSequenceSpec as g, type AudioSfxSpec as h, type ChromaKeyFilterSpec as i, type CircleShapeSpec as j, type CompositionSequenceSpec as k, type CrossfadeTransition as l, type CustomFilterSpec as m, type DissolveTransition as n, type Expr as o, type IrisTransition as p, type PathShapeSpec as q, type PolygonShapeSpec as r, type PropValue as s, type SfxKnobs as t, type SfxOptions as u, type SfxPreset as v, type SfxVoice as w, type ShapeSequenceSpec as x, type SlideTransition as y, type Stop as z };
571
+ export { type ArcShapeSpec as A, type StopSpec as B, type CompositionShape as C, type DipTransition as D, type EllipseShapeSpec as E, type FilterSpec as F, type GradientSpec as G, type TransitionCommon as H, type ImageSequenceSpec as I, normalizeStops as J, type Keyframe as K, type LineShapeSpec as L, pictureStops as M, type NullSequenceSpec as N, type AudioDescriptor as O, type Props as P, type RectShapeSpec as R, type SequenceSpec as S, type TextSequenceSpec as T, type VideoSequenceSpec as V, type WipeTransition as W, type ZoomTransition as Z, type CameraSequenceSpec as a, type TransitionSpec as b, type CompositionSpec as c, type SequenceCommon as d, type AssetSpec as e, type AudioAssetSpec as f, type AudioSequenceSpec as g, type AudioSfxSpec as h, type ChromaKeyFilterSpec as i, type CircleShapeSpec as j, type CompositionSequenceSpec as k, type CrossfadeTransition as l, type CustomFilterSpec as m, type DissolveTransition as n, type Expr as o, type IrisTransition as p, type PathShapeSpec as q, type PolygonShapeSpec as r, type PropValue as s, type SfxKnobs as t, type SfxOptions as u, type SfxPreset as v, type SfxVoice as w, type ShapeSequenceSpec as x, type SlideTransition as y, type Stop as z };
package/docs/api.md CHANGED
@@ -253,10 +253,10 @@ The names are the ones an HTML5 `<video>` uses, so a player written for `<video>
253
253
 
254
254
  ### Presentations: `stops`, `next()`, `prev()`, `Presenter`
255
255
 
256
- A composition can say where a presentation pauses: `composition: { stops: [...] }`. A stop is a time in seconds or `{ at, page?, notes?, advance? }`; a stop with `page` (a name, or `true`) begins a page, the others are steps of it; with no `page` anywhere each stop is a page; the first stop always begins one. A negative `at` counts back from the end; two stops on one frame are one; a stop outside the movie is ignored with a warning. Nothing changes for an ordinary player: playing a movie with stops just plays it.
256
+ A composition can say where a presentation pauses: `composition: { stops: [...] }`. A stop is a time in seconds or `{ at, page?, notes?, advance?, pdf? }`; a stop with `page` (a name, or `true`) begins a page, the others are steps of it; with no `page` anywhere each stop is a page; the first stop always begins one. A negative `at` counts back from the end; two stops on one frame are one; a stop outside the movie is ignored with a warning. `movie.play()` ignores the stops and plays to the end; the `Controller`'s play button, Space and a click on the picture play to the next stop and pause there (see `pauseAtStops`). `pdf: true` marks the stop whose picture stands for its page in a PDF (and in the page overview); `pdf: false` keeps a stop's picture out of the PDF (a last stop that is mid-exit, a step you do not want on paper). Without flags a page is pictured by its last stop.
257
257
 
258
258
  ```ts
259
- movie.stops // Stop[]: { index, at, frame, page: string | null, pageIndex, pageStart, notes?, advance? }
259
+ movie.stops // Stop[]: { index, at, frame, page: string | null, pageIndex, pageStart, notes?, advance?, pdf? }
260
260
  await movie.next(); // play to the next stop and pause exactly on its frame; pressed again while playing, skip to that stop at once;
261
261
  // after the last stop play to the end. Resolves when it has landed (or stopped for another reason).
262
262
  await movie.prev(); // jump back to the previous stop at once (no reverse playback); from the first stop, to the start
@@ -297,7 +297,7 @@ const pages = await movie.stopImages({ as: 'dataURL', type: 'image/jpeg', scale:
297
297
  const pdf = await movie.exportPDF({ title: 'My talk' }); // a Blob: one PDF page per page of the deck (JPEG at the canvas size)
298
298
  ```
299
299
 
300
- Both pause the movie and leave the playhead where it was; a movie with no stops returns an empty list / throws (a PDF needs pages). `exportPDF` options: `which`, `pick`, `scale`, `quality` (JPEG, default 0.92), `title`, `motionBlur`, `onImage`. A transparent background comes out black in a JPEG: give the movie a `background`. From a script, `npx pixi-effects-render my-talk.html -o my-talk.pdf` (add `--all-stops` for a page per stop).
300
+ Both pause the movie and leave the playhead where it was; a movie with no stops returns an empty list / throws (a PDF needs pages). The picture of a page is, in order: its last stop flagged `pdf: true`, else its last (with `pick: 'first'`, first) stop that is not `pdf: false`; a page whose stops are all `pdf: false` is left out of a PDF (a page list still shows it by its last stop); `which: 'stops'` makes a page of every stop that is not `pdf: false`. `exportPDF` options: `which`, `pick`, `scale`, `quality` (JPEG, default 0.92), `title`, `motionBlur`, `onImage`. A transparent background comes out black in a JPEG: give the movie a `background`. From a script, `npx pixi-effects-render my-talk.html -o my-talk.pdf` (add `--all-stops` for a page per stop).
301
301
 
302
302
  ### Audio-reactive: `audioEnvelope()`, `react()`, `bpmEnvelope()`
303
303
 
@@ -381,6 +381,7 @@ interface ControllerOptions {
381
381
  showExportButton?: boolean; // default true; hides ⬇ + popover
382
382
  enableKeyboardShortcuts?: boolean; // default true
383
383
  present?: boolean; // default true: a movie with stops gets a Present button (marks on the seek bar are always shown)
384
+ pauseAtStops?: boolean; // default true: with stops, play / Space / click on the picture play to the next stop and pause; false plays straight through
384
385
  clickToPlay?: boolean; // default true: a click / tap on the picture plays / pauses (like <video>); false leaves the canvas alone
385
386
  className?: string; // default 'movie-controller'
386
387
  theme?: ControllerTheme; // colours / thickness / font of the bar, see Theme
package/docs/dsl.md CHANGED
@@ -826,7 +826,7 @@ initial: { x: 'GW/2', y: 'GH * 0.96', anchorX: 0.5, anchorY: 1 }
826
826
 
827
827
  ## Stops (presentations)
828
828
 
829
- `composition.stops` says where a presentation pauses: seconds, or `{ at, page?, notes?, advance? }` (see [Presentations in the API reference](api.md#presentations-stops-next-prev-presenter)). A stop with `page` begins a page (a name, or `true`), the others are steps of it; with no `page` anywhere each stop is a page. A plain player ignores them (`Controller` shows marks on the seek bar); `movie.next()` and `Presenter` pause on them. [`deck()`](#deck) builds the whole thing from pages.
829
+ `composition.stops` says where a presentation pauses: seconds, or `{ at, page?, notes?, advance?, pdf? }` (see [Presentations in the API reference](api.md#presentations-stops-next-prev-presenter)). A stop with `page` begins a page (a name, or `true`), the others are steps of it; with no `page` anywhere each stop is a page. `movie.play()` ignores them; the `Controller` shows marks on the seek bar and its play button plays to the next stop (`pauseAtStops: false` plays through), and `movie.next()` and `Presenter` pause on them. **`pdf: true` / `pdf: false`** on a stop chooses which moment stands for a page in a PDF: `true` = this stop is the page's picture, `false` = never use it (default: the page's last stop). Use it when the last stop is mid-exit. [`deck()`](#deck) builds the whole thing from pages.
830
830
 
831
831
  ## Transitions
832
832
 
@@ -1279,7 +1279,7 @@ await movie.init({ canvas, width: 1280, height: 720, frameRate: 30, ...deck({
1279
1279
  | ------ | ----- |
1280
1280
  | `pages[]` | required: `{ name?, duration, sequences, stops?, notes?, advance?, initial?, keyframes?, filters?, … }` |
1281
1281
  | `page.duration` | seconds; **a page should last its last stop plus the transition** (after the last stop the next press plays the rest of the page and the slide) |
1282
- | `page.stops` | page-local times: the first is the page settled, the others are steps (a bullet, a growing chart: **a step is a layer that starts at the previous stop and ends at this one**). Default: where the transition begins (the end for the last page) |
1282
+ | `page.stops` | page-local times, or `{ at, pdf? }`: the first is the page settled, the others are steps (a bullet, a growing chart: **a step is a layer that starts at the previous stop and ends at this one**). Default: where the transition begins (the end for the last page) |
1283
1283
  | `page.notes`, `page.advance` | notes stay on the page's first stop; `advance` (seconds) on its last |
1284
1284
  | `transition` | `{ kind, duration, … }`: any [transition](#transitions) without `from` / `to` / `at`; the next page starts `duration` seconds early and the transition plays across the overlap. It must be shorter than every page |
1285
1285
  | movie options | `width`, `height`, `frameRate`, `background`, `canvas`, `assets`, `poster`, `motionBlur`, `loader` pass through |
package/llms-full.txt CHANGED
@@ -182,13 +182,13 @@ keyframes: followPath({ d: 'M 120 560 C 360 120 920 120 1160 560', duration: 4,
182
182
  ...particles({ count: 90, at: 2, emit: 0 /* 0 = burst; or seconds to spread births */, life: [1, 1.8], area: { x: 640, y: 330 /*, width, height */ }, angle: [0, 360] /* 0 right, 90 down, −90 up */,
183
183
  speed: [120, 420], gravity: 240, wind: 0, drag: 1.2, sway: { amp: 30, freq: 0.6 }, size: [2, 4.5], scale: [1, 0], fade: { in: 0.2, out: 0.6 },
184
184
  colors: ['#ffd166', '#ff6b9d'], shape: 'circle'|'rect'|'star', spin: [-200, 200], blendMode: 'add', seed: 1, name: 'burst', template: { type: 'image', asset } }) // seeded layers, paths baked into keyframes
185
- // a presentation: stops pause playback (`composition.stops: [2, { at: 5, page: 'Name', notes, advance }]`; a `page` stop begins a page, the others are steps); deck() lays pages out
185
+ // a presentation: stops pause playback (`composition.stops: [2, { at: 5, page: 'Name', notes, advance, pdf }]`; `pdf: true` = the stop that pictures its page in a PDF, `pdf: false` = keep it out; default the page's last stop; a `page` stop begins a page, the others are steps); deck() lays pages out
186
186
  await movie.init({ canvas, width, height, frameRate, ...deck({ transition: { kind: 'slide', direction: 'left', duration: 0.6 },
187
187
  pages: [{ name: 'Hello', duration: 3.2 /* last stop + transition */, stops: [2.6] /* page-local */, notes, sequences: [ /* page-local time; a step = a layer starting at the previous stop */ ] }] }) });
188
188
  new Presenter(movie, { canvas }); // import { Presenter } from 'pixi-effects/presenter': → ↓ Space Enter click = next, ← = back, Home / End, number+Enter, B / W, F, ?
189
- movie.next() / prev() / goToStop(i) / goToPage(n) / movie.stops / movie.on('stop', …) // Controller shows the stops on the seek bar + a Present button
189
+ movie.next() / prev() / goToStop(i) / goToPage(n) / movie.stops / movie.on('stop', …) // Controller: marks on the seek bar, play / Space / click = play to the next stop (pauseAtStops: false = straight through), a Present button, PDF in the download panel
190
190
  // Presenter extras: G = page overview (pictures), P = presenter view (a second window: live picture, next picture, notes, timer); start() makes the pictures before the audience sees anything
191
- await movie.stopImages({ as: 'dataURL', scale: 0.25 }) // [{ stop, page, image }] one per page (its last stop); { which: 'stops' } one per stop
191
+ await movie.stopImages({ as: 'dataURL', scale: 0.25 }) // [{ stop, page, image }] one per page (its last stop); { which: 'stops' } one per stop (a page's picture honours `pdf` flags)
192
192
  await movie.exportPDF({ title }) // Blob: a PDF page per page of the deck; CLI: pixi-effects-render talk.html -o talk.pdf (--all-stops)
193
193
  const env = await audioEnvelope('music.mp3', { frameRate: 30 }) // BEFORE movie.init: .series.level|bass|mid|treble (0–1 per frame), .beats [s], .bpm, .at(t, band); bands: { name: [Hz, Hz] }
194
194
  const env = bpmEnvelope(120, { duration: 12 }) // no audio file: kick / snare / hats exactly on a tempo
@@ -309,8 +309,8 @@ A good habit that catches most of these: after `init`, read `window.__logs` (see
309
309
  51. **Arcs and rings are `shape: 'arc'`** (`radius`, `innerRadius`, `startAngle`, `endAngle`, degrees; 0° = 3 o'clock, clockwise). A progress ring is an arc with `startAngle: -90` and an animated `endAngle` (−90 → 270); `strokeCap: 'round'` rounds BOTH ends. Fill it for a pie slice, add `innerRadius` for a donut slice (a 120-polygon donut is no longer needed). Style (`strokeColor`, `strokeWidth`, `fillColor`) goes in `initial`; `strokeCap` / `strokeJoin` may be top level. *(fixed)*
310
310
  52. **Measure and split text with `measureText` / `splitText`, not by hand.** `splitText(text, style, { by, x, y, align })` returns each piece's x / y / width in the renderer's own font metrics (kerning kept; verified: a whole line vs the same line in per-letter layers differs only by anti-aliasing). The pieces are for text layers with the default top-left anchor and the same `style`. Load a web font before measuring (`await document.fonts.load('24px "Name"')`) or the fallback font's widths are used. Size a pill from `measureText(...).width + padding` instead of `0.58 × fontSize × length`. *(fixed)*
311
311
  53. **Draw-on and typewriter need no masks or per-letter layers.** `trimStart` / `trimEnd` (0–1 of the outline, animatable, every shape but `arc`) draw a stroke on: `trimEnd: 0` plus a keyframe `to: { trimEnd: 1 }`. The fill is not trimmed, so fade `fillAlpha` in afterwards; use `strokeCap: 'round'`. Text changes over time with `set: { text }` (swap at a time) and `visibleChars` (typewriter; left-align it so it grows rightward). A `to: { text }` cannot tween a string and warns. *(fixed)*
312
- 54. **A deck page lasts until its last stop plus the transition.** `deck()` lays pages out one after another in page-local time and a `transition` overlaps them: a page shorter than its last stop (or shorter than the transition) loses its final step or flashes. A step is a layer that starts at the previous stop (`at: <previous stop>`), so the thing that appears on a press is fully visible when the movie stops. The transition option is `kind`, not `type`.
313
- 55. **Presenter and Controller both listen to click and keys.** Use the `Presenter` for a talk (or let the Controller's Present button start one); do not run both on the same canvas yourself. Ordinary `play()` ignores stops; `movie.next()` plays to the next stop (a second press skips to it), `prev()` jumps back (nothing plays in reverse).
312
+ 54. **A deck page lasts until its last stop plus the transition.** `deck()` lays pages out one after another in page-local time and a `transition` overlaps them: a page shorter than its last stop (or shorter than the transition) loses its final step or flashes. A step is a layer that starts at the previous stop (`at: <previous stop>`), so the thing that appears on a press is fully visible when the movie stops. The transition option is `kind`, not `type`. If the last stop is mid-exit on paper, flag the stop you want in the PDF: `{ at, pdf: true }` (in `deck()`: `stops: [1.2, { at: 3, pdf: true }]`), and `pdf: false` keeps one out.
313
+ 55. **Presenter and Controller both listen to click and keys.** Use the `Presenter` for a talk (or let the Controller's Present button start one); do not run both on the same canvas yourself. `movie.play()` ignores stops, but the Controller's play button / Space / click-on-picture play to the next stop and pause (`pauseAtStops: false` plays through); `movie.next()` plays to the next stop (a second press skips to it), `prev()` jumps back (nothing plays in reverse).
314
314
  56. **Analyse audio before `init`, and give it the movie's frame rate.** `const env = await audioEnvelope('music.mp3', { frameRate })` is async; `react()` is pure and bakes the keyframes, so there is no per-frame callback. A custom `bands` list replaces the defaults (include `bass` if you want it; beats are picked there). No file yet: `bpmEnvelope(120, { duration })` is a stand-in with a kick on every beat. Offset the music with `audioOffset` when the audio layer does not start at 0.
315
315
  57. **Review a deck by its stops, not by its timeline.** `pixi-effects-check` writes `stops.png` (one picture per stop, in order) for a page with `stops`; the picture at a stop is what the audience reads while it waits.
316
316
 
@@ -1863,7 +1863,7 @@ initial: { x: 'GW/2', y: 'GH * 0.96', anchorX: 0.5, anchorY: 1 }
1863
1863
 
1864
1864
  ## Stops (presentations)
1865
1865
 
1866
- `composition.stops` says where a presentation pauses: seconds, or `{ at, page?, notes?, advance? }` (see [Presentations in the API reference](api.md#presentations-stops-next-prev-presenter)). A stop with `page` begins a page (a name, or `true`), the others are steps of it; with no `page` anywhere each stop is a page. A plain player ignores them (`Controller` shows marks on the seek bar); `movie.next()` and `Presenter` pause on them. [`deck()`](#deck) builds the whole thing from pages.
1866
+ `composition.stops` says where a presentation pauses: seconds, or `{ at, page?, notes?, advance?, pdf? }` (see [Presentations in the API reference](api.md#presentations-stops-next-prev-presenter)). A stop with `page` begins a page (a name, or `true`), the others are steps of it; with no `page` anywhere each stop is a page. `movie.play()` ignores them; the `Controller` shows marks on the seek bar and its play button plays to the next stop (`pauseAtStops: false` plays through), and `movie.next()` and `Presenter` pause on them. **`pdf: true` / `pdf: false`** on a stop chooses which moment stands for a page in a PDF: `true` = this stop is the page's picture, `false` = never use it (default: the page's last stop). Use it when the last stop is mid-exit. [`deck()`](#deck) builds the whole thing from pages.
1867
1867
 
1868
1868
  ## Transitions
1869
1869
 
@@ -2316,7 +2316,7 @@ await movie.init({ canvas, width: 1280, height: 720, frameRate: 30, ...deck({
2316
2316
  | ------ | ----- |
2317
2317
  | `pages[]` | required: `{ name?, duration, sequences, stops?, notes?, advance?, initial?, keyframes?, filters?, … }` |
2318
2318
  | `page.duration` | seconds; **a page should last its last stop plus the transition** (after the last stop the next press plays the rest of the page and the slide) |
2319
- | `page.stops` | page-local times: the first is the page settled, the others are steps (a bullet, a growing chart: **a step is a layer that starts at the previous stop and ends at this one**). Default: where the transition begins (the end for the last page) |
2319
+ | `page.stops` | page-local times, or `{ at, pdf? }`: the first is the page settled, the others are steps (a bullet, a growing chart: **a step is a layer that starts at the previous stop and ends at this one**). Default: where the transition begins (the end for the last page) |
2320
2320
  | `page.notes`, `page.advance` | notes stay on the page's first stop; `advance` (seconds) on its last |
2321
2321
  | `transition` | `{ kind, duration, … }`: any [transition](#transitions) without `from` / `to` / `at`; the next page starts `duration` seconds early and the transition plays across the overlap. It must be shorter than every page |
2322
2322
  | movie options | `width`, `height`, `frameRate`, `background`, `canvas`, `assets`, `poster`, `motionBlur`, `loader` pass through |
@@ -2624,10 +2624,10 @@ The names are the ones an HTML5 `<video>` uses, so a player written for `<video>
2624
2624
 
2625
2625
  ### Presentations: `stops`, `next()`, `prev()`, `Presenter`
2626
2626
 
2627
- A composition can say where a presentation pauses: `composition: { stops: [...] }`. A stop is a time in seconds or `{ at, page?, notes?, advance? }`; a stop with `page` (a name, or `true`) begins a page, the others are steps of it; with no `page` anywhere each stop is a page; the first stop always begins one. A negative `at` counts back from the end; two stops on one frame are one; a stop outside the movie is ignored with a warning. Nothing changes for an ordinary player: playing a movie with stops just plays it.
2627
+ A composition can say where a presentation pauses: `composition: { stops: [...] }`. A stop is a time in seconds or `{ at, page?, notes?, advance?, pdf? }`; a stop with `page` (a name, or `true`) begins a page, the others are steps of it; with no `page` anywhere each stop is a page; the first stop always begins one. A negative `at` counts back from the end; two stops on one frame are one; a stop outside the movie is ignored with a warning. `movie.play()` ignores the stops and plays to the end; the `Controller`'s play button, Space and a click on the picture play to the next stop and pause there (see `pauseAtStops`). `pdf: true` marks the stop whose picture stands for its page in a PDF (and in the page overview); `pdf: false` keeps a stop's picture out of the PDF (a last stop that is mid-exit, a step you do not want on paper). Without flags a page is pictured by its last stop.
2628
2628
 
2629
2629
  ```ts
2630
- movie.stops // Stop[]: { index, at, frame, page: string | null, pageIndex, pageStart, notes?, advance? }
2630
+ movie.stops // Stop[]: { index, at, frame, page: string | null, pageIndex, pageStart, notes?, advance?, pdf? }
2631
2631
  await movie.next(); // play to the next stop and pause exactly on its frame; pressed again while playing, skip to that stop at once;
2632
2632
  // after the last stop play to the end. Resolves when it has landed (or stopped for another reason).
2633
2633
  await movie.prev(); // jump back to the previous stop at once (no reverse playback); from the first stop, to the start
@@ -2668,7 +2668,7 @@ const pages = await movie.stopImages({ as: 'dataURL', type: 'image/jpeg', scale:
2668
2668
  const pdf = await movie.exportPDF({ title: 'My talk' }); // a Blob: one PDF page per page of the deck (JPEG at the canvas size)
2669
2669
  ```
2670
2670
 
2671
- Both pause the movie and leave the playhead where it was; a movie with no stops returns an empty list / throws (a PDF needs pages). `exportPDF` options: `which`, `pick`, `scale`, `quality` (JPEG, default 0.92), `title`, `motionBlur`, `onImage`. A transparent background comes out black in a JPEG: give the movie a `background`. From a script, `npx pixi-effects-render my-talk.html -o my-talk.pdf` (add `--all-stops` for a page per stop).
2671
+ Both pause the movie and leave the playhead where it was; a movie with no stops returns an empty list / throws (a PDF needs pages). The picture of a page is, in order: its last stop flagged `pdf: true`, else its last (with `pick: 'first'`, first) stop that is not `pdf: false`; a page whose stops are all `pdf: false` is left out of a PDF (a page list still shows it by its last stop); `which: 'stops'` makes a page of every stop that is not `pdf: false`. `exportPDF` options: `which`, `pick`, `scale`, `quality` (JPEG, default 0.92), `title`, `motionBlur`, `onImage`. A transparent background comes out black in a JPEG: give the movie a `background`. From a script, `npx pixi-effects-render my-talk.html -o my-talk.pdf` (add `--all-stops` for a page per stop).
2672
2672
 
2673
2673
  ### Audio-reactive: `audioEnvelope()`, `react()`, `bpmEnvelope()`
2674
2674
 
@@ -2752,6 +2752,7 @@ interface ControllerOptions {
2752
2752
  showExportButton?: boolean; // default true; hides ⬇ + popover
2753
2753
  enableKeyboardShortcuts?: boolean; // default true
2754
2754
  present?: boolean; // default true: a movie with stops gets a Present button (marks on the seek bar are always shown)
2755
+ pauseAtStops?: boolean; // default true: with stops, play / Space / click on the picture play to the next stop and pause; false plays straight through
2755
2756
  clickToPlay?: boolean; // default true: a click / tap on the picture plays / pauses (like <video>); false leaves the canvas alone
2756
2757
  className?: string; // default 'movie-controller'
2757
2758
  theme?: ControllerTheme; // colours / thickness / font of the bar, see Theme
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pixi-effects",
3
- "version": "0.12.0",
3
+ "version": "0.13.0",
4
4
  "description": "Declarative composition and video rendering for the web. Built on PixiJS v8.",
5
5
  "type": "module",
6
6
  "exports": {