pixi-effects 0.39.0 → 0.40.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 (66) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/README.md +16 -30
  3. package/ai/chat-template.html +3 -3
  4. package/ai/tools/check.mjs +7 -5
  5. package/ai/tools/gltfProbe.mjs +122 -0
  6. package/ai/tools/media.mjs +8 -1
  7. package/ai/tools/pixi-effects.mjs +1 -1
  8. package/dist/{Base-BKKwxavW.d.cts → Base-DQWIJ6bJ.d.cts} +1 -1
  9. package/dist/{Base-ioh_AjLB.d.ts → Base-Jgkud1Aw.d.ts} +1 -1
  10. package/dist/Composition-C3UEVHQG.cjs +16 -0
  11. package/dist/{Composition-3MNLNT2E.cjs.map → Composition-C3UEVHQG.cjs.map} +1 -1
  12. package/dist/Composition-KRWH72MA.js +7 -0
  13. package/dist/{Composition-P37AD4GN.js.map → Composition-KRWH72MA.js.map} +1 -1
  14. package/dist/Controller.d.cts +2 -2
  15. package/dist/Controller.d.ts +2 -2
  16. package/dist/{Movie-BtwFI2ne.d.ts → Movie-CG6Jy1tx.d.ts} +6 -1
  17. package/dist/{Movie-CSWUTnMd.d.cts → Movie-CMq4KFmc.d.cts} +6 -1
  18. package/dist/{Spans-F6FBBCD3.cjs → Spans-APNJHCYO.cjs} +9 -9
  19. package/dist/{Spans-F6FBBCD3.cjs.map → Spans-APNJHCYO.cjs.map} +1 -1
  20. package/dist/{Spans-PLBQOSMV.js → Spans-TWO4VN5H.js} +4 -4
  21. package/dist/{Spans-PLBQOSMV.js.map → Spans-TWO4VN5H.js.map} +1 -1
  22. package/dist/{chunk-UHCVO5TH.js → chunk-26Z544SH.js} +24 -13
  23. package/dist/chunk-26Z544SH.js.map +1 -0
  24. package/dist/{chunk-SJFDSHBP.cjs → chunk-27PYJ4JY.cjs} +83 -72
  25. package/dist/chunk-27PYJ4JY.cjs.map +1 -0
  26. package/dist/{chunk-HX73S7FD.cjs → chunk-7EI2N7WO.cjs} +4 -4
  27. package/dist/{chunk-HX73S7FD.cjs.map → chunk-7EI2N7WO.cjs.map} +1 -1
  28. package/dist/{chunk-F2ZHDUPL.js → chunk-CT5CZVT5.js} +12 -6
  29. package/dist/chunk-CT5CZVT5.js.map +1 -0
  30. package/dist/{chunk-FV25Y5OG.cjs → chunk-LEL7UNYQ.cjs} +47 -41
  31. package/dist/chunk-LEL7UNYQ.cjs.map +1 -0
  32. package/dist/{chunk-JCPZJJNW.js → chunk-LMWXR5OC.js} +4 -4
  33. package/dist/{chunk-JCPZJJNW.js.map → chunk-LMWXR5OC.js.map} +1 -1
  34. package/dist/index.cjs +33 -33
  35. package/dist/index.d.cts +4 -4
  36. package/dist/index.d.ts +4 -4
  37. package/dist/index.js +3 -3
  38. package/dist/loader.css +1 -1
  39. package/dist/present.cjs +3 -3
  40. package/dist/present.d.cts +2 -2
  41. package/dist/present.d.ts +2 -2
  42. package/dist/present.js +3 -3
  43. package/dist/three.cjs +505 -12
  44. package/dist/three.cjs.map +1 -1
  45. package/dist/three.d.cts +139 -7
  46. package/dist/three.d.ts +139 -7
  47. package/dist/three.js +501 -10
  48. package/dist/three.js.map +1 -1
  49. package/dist/{types-BXi-0iZS.d.ts → types-DIh93lFb.d.cts} +82 -1
  50. package/dist/{types-BXi-0iZS.d.cts → types-DIh93lFb.d.ts} +82 -1
  51. package/docs/api.md +20 -10
  52. package/docs/dsl.md +73 -5
  53. package/docs/media.md +1 -1
  54. package/llms-full.txt +167 -20
  55. package/llms.txt +1 -1
  56. package/package.json +11 -12
  57. package/skills/pixi-effects/SKILL.md +2 -2
  58. package/skills/pixi-effects/reference/cheatsheet.md +2 -2
  59. package/skills/pixi-effects/reference/recipes.md +69 -0
  60. package/skills/pixi-effects/template.html +9 -8
  61. package/dist/Composition-3MNLNT2E.cjs +0 -16
  62. package/dist/Composition-P37AD4GN.js +0 -7
  63. package/dist/chunk-F2ZHDUPL.js.map +0 -1
  64. package/dist/chunk-FV25Y5OG.cjs.map +0 -1
  65. package/dist/chunk-SJFDSHBP.cjs.map +0 -1
  66. package/dist/chunk-UHCVO5TH.js.map +0 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.40.0
4
+
5
+ The `three` layer grows up: it can live in the composition's space, it loads glTF models, `check` holds it to the library's contract, and it can use three's WebGPU renderer. Nothing else changed; one type-only break (the last entry).
6
+
7
+ - **New: a `three` layer can live in the composition's space (`followCamera`, `followLights`, `followFog`).** `followCamera: true` puts the layer's `PerspectiveCamera` where the composition's `camera` layer is, every frame (position, look-at point, vertical fov, the handheld offsets), so one `cameraPath()` or `orbit()` flies through the flat layers and the three scene together. The scene is in composition pixels with y flipped (the composition's `(x, y, z)` is `(x, -y, z)` in three); a point lands on the same pixel as the composition's own projection (to 1e-6 px; against the composition's cards in the real picture: mean 0.14–0.19 of 255, edges only). `followLights: true` makes the composition's `light` layers three lights (ambient, point, spot, parallel; position, colour, intensity, cone, falloff; the intensity times π, because three divides a diffuse surface by π), moved every frame; **a light with `castsShadows` casts in three** (the layer's own meshes choose who casts and receives: a card in front of a wall under a spot throws the same shadow as the composition's cards, mean 0.2–0.35 of 255, WebGL and WebGPU, at 480 × 270 and at 1280 × 720). With `MeshLambertMaterial` and three's colour management off the planes shade like the cards (mean 0.02–0.2 of 255); with three's defaults close (0.8–1.4). `followFog: true` makes the camera's fog the scene's `Fog`: close, not equal (three fades by `smoothstep`, the composition linearly, and has no `fogAmount` below 1, which warns once; mean 0.5–1.8 of 255 where the fog's own effect is 2–7). `ctx.space` gives the raw camera, lights and fog. A composition whose only consumer is such a layer no longer warns that its camera or light has no effect, or that no layer casts a shadow. Pinned by `tests/films/three-space` against the composition's own cards. See `docs/dsl.md` → three.
8
+ - **New: glTF models in the free `three` layer: `threeGltf`, `threeFrameCamera`, `pixi-effects probe model.glb`.** `threeGltf(url, { animation, speed, offset, loop })` returns `{ object, clips, size, pose(t), fit(px), dispose() }`: the group's origin is the centre of the model, `pose(t)` is `AnimationMixer.setTime` (a function of the time: a seek, an export and a repeat give the same frame), `fit(px)` scales the longest side to pixels, and skinned meshes are drawn always (three culls them by a bounding sphere that does not follow them: a moving, shadow-casting Fox drew its shadow depending on the frames drawn before it). `threeFrameCamera(camera, object, { direction, margin })` puts a camera where the whole model fits (a three-quarter view, from the corners of the bounding box rather than a sphere). `probe model.glb` (or `.gltf`) reads in Node, with no browser and no three.js, the clips with their lengths, the size (equal to what `threeGltf(...).size` measures; Khronos Fox: 25.185 × 79.029 × 154.72) and notes (Draco / KTX2 / Meshopt need a decoder that `threeGltf` does not set up; no animation; files to keep beside the model). The template's import map has `three/addons/`. Recipes `gltf-in-space` and `gltf-shadow-room` (a model running through a sweeping spotlight with its shadow on a wall and a lit flat caption), written and corrected after three fresh-AI trials on the real Khronos Fox. Tests: `tests/three/gltf.test.ts`, `tests/films/gltf-model`, `tests/tools/probeGltf.test.ts`.
9
+ - **New: `check` holds a `three` layer to "a function of the time", as it does a `canvas` layer.** It draws the scene at four moments in two orders (one draw thrown away first), reads the picture from the renderer's canvas and compares: a clock, `Math.random`, a mixer advanced with `mixer.update(dt)` or a speed that builds up from call to call is reported with the moments and the fix (`mixer.setTime(t)` / `threeGltf(url).pose(t)`); `Math.random` / `Date.now` / `performance.now` / `new Date` in `update` also warns at build. `check` prints a `three` line when every layer passed (WebGL and WebGPU), the canvas heading says "layer(s) (canvas / three)", and `movie.canvasReport()` rows have a `kind`. `tests/tools/threeDeterminism.test.ts`.
10
+ - **New: a page with too many `three` layers says so instead of going blank.** A browser keeps about 16 live WebGL contexts for a page (about 32 WebGPU: measured on Chrome); layers beyond that draw nothing and raise no error. The 13th live WebGL layer (the 29th WebGPU one) now warns once with the count and the way out, and a layer whose WebGL context the browser took away names itself. `docs/dsl.md` had said "roughly 8–16". `tests/films/three-budget` (20 layers in real Chrome).
11
+ - **New: a `three` layer can draw with three's WebGPU renderer (`renderer: 'webgpu'`), `antialias`, and a page-level default `registerThree({ renderer })`.** The layer used `WebGLRenderer` always, in its own offscreen context. `renderer: 'webgpu'` uses `WebGPURenderer` from `three/webgpu` (three 0.17x or later); the default stays `'webgl'` (a design review by two independent reviewers, with measurements on four large GLSL clips, rejected a silent fallback that runs `setup` twice: a second `setup` can draw a different video). The same lit scene draws within 0.06 of 255 per channel of the WebGL picture and each renderer is a function of the time (`tests/films/three-renderers`); the render alone is about the same for a box and about twice as fast on WebGPU for 2,880 instanced rods; an MP4 export is limited by the encode and took the same time. `antialias` (default `true`): WebGPURenderer blends fog and resolves multisampling in linear light before tone mapping, WebGLRenderer after it, so fog and thin hot edges differ by design; with `antialias: false` and no fog the two agree to 0.04 of 255. On `'webgpu'`, a `ShaderMaterial` / `RawShaderMaterial` / `onBeforeCompile` material stops the build with the fix (it used to draw a broken picture) and a `PMREMGenerator` taken from `three` is explained; where the browser has no WebGPU three's WebGL2 backend draws the same picture (`ThreeSequence.renderer` / `.backend` say what was used, and `movie.inspect()` lists them). Three's WebGPU renderer updates light uniforms once per animation-loop frame, so a seek or an export showed stale lights: the layer now advances three's node frame before each render and gives `time` the layer's own time. See `docs/dsl.md` → three.
12
+ - **Changed (types only): `ThreeContext.renderer` is `WebGLRenderer | WebGPURenderer`.** TypeScript code that used WebGL-only members of `ctx.renderer` has to narrow it (`ctx.renderer instanceof WebGLRenderer`); `setClearColor`, `toneMapping` and `toneMappingExposure` exist on both. `docs/api.md` → `pixi-effects/three` lists the whole entry.
13
+
3
14
  ## 0.39.0
4
15
 
5
16
  **The tools tell the truth, and a file can be checked against what it must be**
package/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # pixi-effects
2
2
 
3
- > **Status**: experimental — current release `0.39.0`. The API may still change between minor versions; see the [CHANGELOG](./CHANGELOG.md) for what each release changed.
3
+ > **Status**: experimental — current release `0.40.0`. The API may still change between minor versions; see the [CHANGELOG](./CHANGELOG.md) for what each release changed.
4
4
 
5
- **[Guide →](https://yjmtmtk.github.io/pixi-effects/guide/)** · **[Gallery →](https://yjmtmtk.github.io/pixi-effects/examples/gallery/)** · 52 portfolio pieces written as plain data by AI models · **[Examples →](https://yjmtmtk.github.io/pixi-effects/examples/)** · 15 numbered examples, a music lab + an in-browser playground.
5
+ **[Gallery →](https://yjmtmtk.github.io/pixi-effects/examples/gallery/)** · 43 pieces written as plain data by AI models · **[For AI →](https://yjmtmtk.github.io/pixi-effects/for-ai.html)** · what an AI reads and runs · **[Playground →](https://yjmtmtk.github.io/pixi-effects/examples/playground.html)** in the browser.
6
6
 
7
7
  ## Make a video by asking an AI
8
8
 
@@ -21,11 +21,11 @@ Declarative composition and video rendering for the web. After Effects-style tim
21
21
  - **Draw-on strokes and changing text** — `trimEnd` 0 → 1 draws a line, border or SVG path on; a keyframe `set: { text }` swaps a string and `visibleChars` is a typewriter.
22
22
  - **Masks, transitions, chroma key** — inline masks; chroma key; eight scene transitions (`crossfade`, `wipe`, `iris`, `slide`, `dip`, `zoom`, `dissolve`, `luma`).
23
23
  - **Presets** — `kenBurns` for stills, `withFade` for fade-in/out; `wiggle`, `stagger`, `animateText`, `followPath`, `particles` and `react` return keyframes or layers (seeded, so every render is the same).
24
- - **Presentations** — `stops` in the composition pause the movie where you say; `Presenter` (`pixi-effects/present`) moves on with arrow keys, a click or a swipe, with a page overview (G), a presenter view with notes and a timer (P), and `deck()` to lay out pages and steps. Export a deck as PDF (`exportPDF(movie)`, `pixi-effects render talk.html -o talk.pdf`). See the [Presenting guide](https://yjmtmtk.github.io/pixi-effects/guide/presenting.html).
24
+ - **Presentations** — `stops` in the composition pause the movie where you say; `Presenter` (`pixi-effects/present`) moves on with arrow keys, a click or a swipe, with a page overview (G), a presenter view with notes and a timer (P), and `deck()` to lay out pages and steps. Export a deck as PDF (`exportPDF(movie)`, `pixi-effects render talk.html -o talk.pdf`).
25
25
  - **Music written as text** — `{ type: 'audio', music: { bpm: 96, tracks: [{ inst: 'keys', notes: 'Cmaj7:4 Am7:4' }], drums: { kick: 'x...x...' } } }`: a tune with no audio file, played by a small built-in synthesiser (keys, pluck, pad, bass, lead, bell, music box, drums; chords by name, swing, fades, a ritardando). Deterministic, in the exported file at the same moment; an AI writes it as easily as a title.
26
26
  - **Audio-reactive visuals** — `audioEnvelope('music.mp3')` analyses loudness, bass / mid / treble and the beats per frame; `react()` turns them into keyframes, so the picture follows the music and still renders identically.
27
27
  - **2.5D layers & camera** — add `threeD: true`, `z`, `rotationX/Y` and a `{ type: 'camera' }` layer for parallax, flips and dolly zooms; no three.js needed.
28
- - **three.js layer** *(optional)* — drop a real three.js scene in as a layer via `pixi-effects/three`; keyframes drive its objects (`three.cube.rotation.y`).
28
+ - **three.js layer** *(optional)* — drop a real three.js scene in as a layer via `pixi-effects/three`; keyframes drive its objects (`three.cube.rotation.y`). It can live in the composition's space: the composition's camera, lights, shadows and fog drive the scene, so one `cameraPath()` flies through the flat layers and a glTF model together (`threeGltf`).
29
29
  - **Built-in player UI — or your own** — a drop-in HTML5-`<video>`-style overlay controller (play, scrub, mute, volume, fullscreen, export-to-file) whose colours are one option (`theme: { accent: '#ff4d6d' }`); or build a player yourself on the movie's `<video>`-named events (`play pause ended seeking seeked volumechange error`).
30
30
  - **MP4 / WebM / MOV export** — pick container and quality from the controller, call `movie.render()` from code, or render headless from the command line (`pixi-effects render`).
31
31
  - **A timeline you can read and scrub** — `movie.timelineChart()` draws every layer as a bar on a time axis; `pixi-effects view` opens the page with that timeline under it, zoomable, with a playhead you can drag.
@@ -49,8 +49,8 @@ Drop the imports into an [importmap](https://developer.mozilla.org/docs/Web/HTML
49
49
  "imports": {
50
50
  "pixi.js": "https://esm.sh/pixi.js@8.22.0?bundle-deps",
51
51
  "mediabunny": "https://esm.sh/mediabunny",
52
- "pixi-effects": "https://cdn.jsdelivr.net/npm/pixi-effects@0.39.0/dist/index.js",
53
- "pixi-effects/controller": "https://cdn.jsdelivr.net/npm/pixi-effects@0.39.0/dist/Controller.js"
52
+ "pixi-effects": "https://cdn.jsdelivr.net/npm/pixi-effects@0.40.0/dist/index.js",
53
+ "pixi-effects/controller": "https://cdn.jsdelivr.net/npm/pixi-effects@0.40.0/dist/Controller.js"
54
54
  }
55
55
  }
56
56
  </script>
@@ -61,7 +61,7 @@ Drop the imports into an [importmap](https://developer.mozilla.org/docs/Web/HTML
61
61
  </script>
62
62
  ```
63
63
 
64
- Load the `dist/` files **as they are** (jsDelivr's `/npm/…/dist/…`, or unpkg's `https://unpkg.com/pixi-effects@0.39.0/dist/index.js`) rather than a CDN-rebundled build such as `esm.sh/pixi-effects` or jsDelivr's `+esm`: the entries (`pixi-effects`, `…/controller`, `…/three`) share internal chunks, which only works when each file is served untouched.
64
+ Load the `dist/` files **as they are** (jsDelivr's `/npm/…/dist/…`, or unpkg's `https://unpkg.com/pixi-effects@0.40.0/dist/index.js`) rather than a CDN-rebundled build such as `esm.sh/pixi-effects` or jsDelivr's `+esm`: the entries (`pixi-effects`, `…/controller`, `…/three`) share internal chunks, which only works when each file is served untouched.
65
65
 
66
66
  > **Using three.js?** Add two more entries (`three` and `pixi-effects/three`) to this importmap — see [Adding three.js](#adding-threejs-optional) below.
67
67
  >
@@ -69,7 +69,7 @@ Load the `dist/` files **as they are** (jsDelivr's `/npm/…/dist/…`, or unpkg
69
69
 
70
70
  #### Adding three.js (optional)
71
71
 
72
- Add two more entries to the importmap above: three.js itself, and the `pixi-effects/three` entry.
72
+ Add two more entries to the importmap above: three.js itself, and the `pixi-effects/three` entry (and `three/addons/` if you load a glTF model with `threeGltf`).
73
73
 
74
74
  ```html
75
75
  <script type="importmap">
@@ -77,7 +77,8 @@ Add two more entries to the importmap above: three.js itself, and the `pixi-effe
77
77
  "imports": {
78
78
  "...": "(everything from the importmap above)",
79
79
  "three": "https://esm.sh/three@0.178.0",
80
- "pixi-effects/three": "https://cdn.jsdelivr.net/npm/pixi-effects@0.39.0/dist/three.js"
80
+ "three/addons/": "https://cdn.jsdelivr.net/npm/three@0.178.0/examples/jsm/",
81
+ "pixi-effects/three": "https://cdn.jsdelivr.net/npm/pixi-effects@0.40.0/dist/three.js"
81
82
  }
82
83
  }
83
84
  </script>
@@ -112,7 +113,7 @@ Always route `three` through the importmap — both your code and `pixi-effects/
112
113
  </script>
113
114
  ```
114
115
 
115
- The layer is a normal sprite once built, so 2D keyframes, masks and filters work on it. Full spec, rules and caveats (one WebGL context per layer, `update(t)` must be a pure function of `t`): [DSL reference § three](./docs/dsl.md#three) and [API reference](./docs/api.md#pixi-effectsthree). Runnable: [`examples/10-three.html`](./examples/10-three.html).
116
+ The layer is a normal sprite once built, so 2D keyframes, masks and filters work on it. Full spec, rules and caveats (one WebGL context per layer, `update(t)` must be a pure function of `t`): [DSL reference § three](./docs/dsl.md#three) and [API reference](./docs/api.md#pixi-effectsthree).
116
117
 
117
118
  With a bundler, `import { registerThree, three } from 'pixi-effects/three'` works the same once you've installed `three`.
118
119
 
@@ -164,7 +165,7 @@ sequences: [
164
165
  ]
165
166
  ```
166
167
 
167
- `+z` is toward the viewer, rotations are degrees (CSS signs), and with `z = 0` and the default camera a `threeD` layer looks identical to a 2D one. Add `{ type: 'light', kind: 'spot', … }` layers for shading and shadows, and `fogNear` / `fogFar` on the camera for depth fog (nothing changes in a composition with neither). See [DSL reference § 3D layers & camera](./docs/dsl.md#3d-layers--camera) and [`examples/11-depth.html`](./examples/11-depth.html).
168
+ `+z` is toward the viewer, rotations are degrees (CSS signs), and with `z = 0` and the default camera a `threeD` layer looks identical to a 2D one. Add `{ type: 'light', kind: 'spot', … }` layers for shading and shadows, and `fogNear` / `fogFar` on the camera for depth fog (nothing changes in a composition with neither). See [DSL reference § 3D layers & camera](./docs/dsl.md#3d-layers--camera).
168
169
 
169
170
  ## For AI agents
170
171
 
@@ -187,25 +188,10 @@ They ship in the npm package (`node_modules/pixi-effects/ai/`).
187
188
 
188
189
  - [**DSL reference**](./docs/dsl.md) — composition, sequences, 3D layers & camera, three.js layer, keyframes, expressions, filters
189
190
  - [**API reference**](./docs/api.md) — `Movie`, `Controller`, `pixi-effects/present`, `pixi-effects/three`, events, render options
190
- - [**Interactive movies**](./examples/interactive/) — nine live samples of a movie you change while it plays (variables, `setVars`, `rebuild`, loop): rock paper scissors, a scrolling city, a tune from four chords, a card for anyone, choose your ending … ([the guide chapter](./site/guide/interactive.md))
191
- - [**Examples**](./examples/) — runnable HTML files (read in order):
192
- - `01-hello.html` — minimum viable composition
193
- - `02-keyframes.html` — keyframes, easings, expressions
194
- - `03-shapes.html` — every shape primitive
195
- - `04-media.html` — image / video / audio
196
- - `05-composition-mask.html` — nested compositions and masks
197
- - `06-filters.html` — built-in and pixi-filters (as `{ type: 'custom' }`; most can now be written by name, see `docs/dsl.md` → Named filters)
198
- - `07-transitions.html` — all seven transition kinds in one timeline
199
- - `08-presets-export.html` — `kenBurns` preset and `movie.render()` from code
200
- - `09-audio.html` — multi-track audio mixing with BGM ducking and SFX cues
201
- - `10-three.html` — a three.js scene as a layer (`pixi-effects/three`)
202
- - `11-depth.html` — 2.5D layers and camera: parallax, spin, dolly zoom
203
- - `12-title-motion.html` — a staggered 2.5D title: letters fly in from depth, particles, orbiting camera
204
- - `13-sfx.html` — synthesised sound effects (no audio files), a riser into a hit, a custom voice
205
- - `15-custom-player.html` — a complete player of your own in about 40 lines (play, seek, volume, replay) using the movie's events
206
- - `14-draw-on.html` — draw-on strokes (`trimStart` / `trimEnd`) and text that changes over time (`visibleChars`, `set: { text }`)
207
- - `music-lab.html` — eight tunes written as text (no audio files): press play, read each score; `musicBuffer()` plays music with no movie
208
- - `playground.html` — in-browser editor on the chat template's edit block: twelve examples, a problems panel from `movie.review()`, share links, save as HTML, and WebMCP tools for an AI agent
191
+ - [**Interactive movies**](./examples/interactive/) — nine live samples of a movie you change while it plays (variables, `setVars`, `rebuild`, loop): rock paper scissors, a scrolling city, a tune from four chords, a card for anyone, choose your ending … ([docs/api.md](./docs/api.md))
192
+ - [**Gallery**](./examples/gallery/) — 43 complete pieces, each one HTML file of data
193
+ - [**Recipes**](./skills/pixi-effects/reference/recipes.md) — tested code you can copy (`npx pixi-effects recipe`)
194
+ - [**Playground**](./examples/playground.html) — in-browser editor on the chat template's edit block: fourteen examples (the tested recipes), a problems panel from `movie.review()`, share links, save as HTML, and WebMCP tools for an AI agent
209
195
 
210
196
  ## Browser support
211
197
 
@@ -59,9 +59,9 @@
59
59
  "imports": {
60
60
  "pixi.js": "https://cdn.jsdelivr.net/npm/pixi.js@8.22.0/dist/pixi.min.mjs",
61
61
  "mediabunny": "https://cdn.jsdelivr.net/npm/mediabunny@1.61.3/+esm",
62
- "pixi-effects": "https://cdn.jsdelivr.net/npm/pixi-effects@0.39.0/dist/index.js",
63
- "pixi-effects/controller": "https://cdn.jsdelivr.net/npm/pixi-effects@0.39.0/dist/Controller.js",
64
- "pixi-effects/present": "https://cdn.jsdelivr.net/npm/pixi-effects@0.39.0/dist/present.js"
62
+ "pixi-effects": "https://cdn.jsdelivr.net/npm/pixi-effects@0.40.0/dist/index.js",
63
+ "pixi-effects/controller": "https://cdn.jsdelivr.net/npm/pixi-effects@0.40.0/dist/Controller.js",
64
+ "pixi-effects/present": "https://cdn.jsdelivr.net/npm/pixi-effects@0.40.0/dist/present.js"
65
65
  }
66
66
  }
67
67
  </script>
@@ -435,7 +435,7 @@ export async function runCheck(opts, log = console.log) {
435
435
  const layoutProblems = rv.problems.filter(g => g.kind !== 'canvas'), canvasProblems = rv.problems.filter(g => g.kind === 'canvas');
436
436
  report.inspect = { checkedFrames: rv.frames, issues: layoutProblems, review: rv.review, canvas: canvasProblems.map(g => g.message), canvasLayers: rv.canvas ?? [] };
437
437
  if (layoutProblems.length) report.problems.push(`${layoutProblems.length} kind(s) of layout issue from movie.inspect`);
438
- if (canvasProblems.length) report.problems.push(`${canvasProblems.length} canvas layer(s) that do not draw the same picture for the same time (movie.canvasProblems)`);
438
+ if (canvasProblems.length) report.problems.push(`${canvasProblems.length} layer(s) (canvas / three) that do not draw the same picture for the same time (movie.canvasProblems)`);
439
439
  // fonts: a web font whose file did not load is a problem (the library lists a layer with no available font for review)
440
440
  report.fonts = rv.fonts;
441
441
  if (rv.fonts.failed.length) report.problems.push(`${rv.fonts.failed.length} web font(s) failed to load: ${rv.fonts.failed.join(', ')} (the layers that use them are drawn in a fallback)`);
@@ -682,11 +682,13 @@ function finish(report, outDir, log) {
682
682
  if (g.length > 8) L.push(` … ${g.length - 8} more (report.json)`);
683
683
  const cv = report.inspect.canvas ?? [];
684
684
  const rows = report.inspect.canvasLayers ?? [];
685
- if (cv.length) { L.push(` canvas ${cv.length} canvas layer(s) that do not draw the same picture for the same time:`); for (const m of cv.slice(0, 4)) L.push(` - ${m}`); }
686
- if (rows.length) { // silence must not be the only sign that the check ran
687
- const good = rows.filter(r => r.ok), slow = [...rows].sort((a, b) => b.drawMs - a.drawMs)[0];
685
+ if (cv.length) { L.push(` canvas ${cv.length} layer(s) (canvas / three) that do not draw the same picture for the same time:`); for (const m of cv.slice(0, 4)) L.push(` - ${m}`); }
686
+ for (const kind of ['canvas', 'three']) { // silence must not be the only sign that the check ran
687
+ const mine = rows.filter(r => (r.kind ?? 'canvas') === kind);
688
+ if (!mine.length) continue;
689
+ const good = mine.filter(r => r.ok), slow = [...mine].sort((a, b) => b.drawMs - a.drawMs)[0];
688
690
  const ms = x => x < 10 ? x.toFixed(1) : String(Math.round(x));
689
- if (good.length) L.push(` canvas ${cv.length ? 'the other ' : ''}${good.length} canvas layer(s) drew the same picture twice at ${good[0].moments.length} moments (two orders): deterministic · slowest draw ${ms(slow.drawMs)} ms on the CPU (${slow.name})`);
691
+ if (good.length) L.push(` ${kind.padEnd(9)} ${cv.length ? 'the other ' : ''}${good.length} ${kind} layer(s) drew the same picture twice at ${good[0].moments.length} moments (two orders): deterministic · slowest draw ${ms(slow.drawMs)} ms${kind === 'canvas' ? ' on the CPU' : ''} (${slow.name})`);
690
692
  }
691
693
  const rv = report.inspect.review;
692
694
  if (rv.length) {
@@ -0,0 +1,122 @@
1
+ /**
2
+ * `pixi-effects probe model.glb` — what is in a glTF model, read in Node with no browser and no three.js: the clips (name and length), the size of the model in the file's own units
3
+ * (the bounding box of the meshes through the transforms of the nodes: the pose the file is stored in, which is what `threeGltf(...).size` measures), what it holds, and the notes an
4
+ * author trips on (a file that needs a decoder, a model with no clip).
5
+ */
6
+ import fs from 'node:fs';
7
+ import path from 'node:path';
8
+
9
+ const GLB_MAGIC = 0x46546c67, JSON_CHUNK = 0x4e4f534a;
10
+ const round = (v, d = 3) => Math.round(v * 10 ** d) / 10 ** d;
11
+ export const isGltfFile = (file) => /\.(glb|gltf)(\?.*)?$/i.test(file);
12
+ const isUrl = (f) => /^https?:\/\//i.test(f);
13
+
14
+ /** The JSON of a .glb (its first chunk) or a .gltf. */
15
+ function parseJson(file, buf) {
16
+ const name = isUrl(file) ? file : path.basename(file);
17
+ if (buf.length >= 12 && buf.readUInt32LE(0) === GLB_MAGIC) {
18
+ const version = buf.readUInt32LE(4);
19
+ if (version !== 2) throw new Error(`${name}: glTF version ${version} (only 2.0 is read)`);
20
+ let at = 12;
21
+ while (at + 8 <= buf.length) {
22
+ const len = buf.readUInt32LE(at), type = buf.readUInt32LE(at + 4);
23
+ if (type === JSON_CHUNK) return { json: JSON.parse(buf.subarray(at + 8, at + 8 + len).toString('utf8')), container: 'glb' };
24
+ at += 8 + len + ((4 - (len % 4)) % 4);
25
+ }
26
+ throw new Error(`${name}: a .glb with no JSON chunk`);
27
+ }
28
+ try { return { json: JSON.parse(buf.toString('utf8')), container: 'gltf' }; }
29
+ catch { throw new Error(`${name} is not a glTF model (it is neither a .glb, which starts with "glTF", nor glTF JSON)`); }
30
+ }
31
+
32
+ // 4x4 matrices, column-major as glTF stores them.
33
+ const identity = () => [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];
34
+ const multiply = (a, b) => { const o = new Array(16).fill(0); for (let c = 0; c < 4; c++) for (let r = 0; r < 4; r++) for (let k = 0; k < 4; k++) o[c * 4 + r] += a[k * 4 + r] * b[c * 4 + k]; return o; };
35
+ function local(node) {
36
+ if (node.matrix?.length === 16) return node.matrix;
37
+ const [tx, ty, tz] = node.translation ?? [0, 0, 0], [x, y, z, w] = node.rotation ?? [0, 0, 0, 1], [sx, sy, sz] = node.scale ?? [1, 1, 1];
38
+ return [
39
+ (1 - 2 * (y * y + z * z)) * sx, 2 * (x * y + z * w) * sx, 2 * (x * z - y * w) * sx, 0,
40
+ 2 * (x * y - z * w) * sy, (1 - 2 * (x * x + z * z)) * sy, 2 * (y * z + x * w) * sy, 0,
41
+ 2 * (x * z + y * w) * sz, 2 * (y * z - x * w) * sz, (1 - 2 * (x * x + y * y)) * sz, 0,
42
+ tx, ty, tz, 1,
43
+ ];
44
+ }
45
+ const apply = (m, [x, y, z]) => [m[0] * x + m[4] * y + m[8] * z + m[12], m[1] * x + m[5] * y + m[9] * z + m[13], m[2] * x + m[6] * y + m[10] * z + m[14]];
46
+
47
+ /** The report of a glTF JSON. */
48
+ export function describeGltf(json, { file, bytes, container }) {
49
+ const nodes = json.nodes ?? [], meshes = json.meshes ?? [], accessors = json.accessors ?? [];
50
+ const notes = [];
51
+ const lo = [Infinity, Infinity, Infinity], hi = [-Infinity, -Infinity, -Infinity];
52
+ let measured = 0, unmeasured = 0;
53
+ const seen = new Set();
54
+ const visit = (i, parent) => {
55
+ if (seen.has(i) || !nodes[i]) return; // a node reached twice is a broken file, not a reason to loop
56
+ seen.add(i);
57
+ const world = multiply(parent, local(nodes[i]));
58
+ const mesh = meshes[nodes[i].mesh];
59
+ for (const prim of mesh?.primitives ?? []) {
60
+ const acc = accessors[prim.attributes?.POSITION];
61
+ if (!acc?.min || !acc?.max) { unmeasured++; continue; }
62
+ measured++;
63
+ for (const x of [acc.min[0], acc.max[0]]) for (const y of [acc.min[1], acc.max[1]]) for (const z of [acc.min[2], acc.max[2]]) {
64
+ const p = apply(world, [x, y, z]);
65
+ for (let k = 0; k < 3; k++) { if (p[k] < lo[k]) lo[k] = p[k]; if (p[k] > hi[k]) hi[k] = p[k]; }
66
+ }
67
+ }
68
+ for (const c of nodes[i].children ?? []) visit(c, world);
69
+ };
70
+ const scene = json.scenes?.[json.scene ?? 0];
71
+ const roots = scene?.nodes ?? nodes.map((_, i) => i).filter((i) => !nodes.some((n) => n.children?.includes(i)));
72
+ for (const r of roots) visit(r, identity());
73
+ const size = measured ? [0, 1, 2].map((k) => hi[k] - lo[k]) : null;
74
+ if (!size) notes.push('the size of the model could not be read (its position accessors have no min / max)');
75
+ else if (unmeasured) notes.push(`${unmeasured} mesh part(s) have no position min / max: the size leaves them out`);
76
+
77
+ const animations = (json.animations ?? []).map((a, index) => {
78
+ let duration = 0;
79
+ for (const s of a.samplers ?? []) { const t = accessors[s.input]; if (t?.max?.[0] > duration) duration = t.max[0]; }
80
+ return { index, name: a.name ?? `(unnamed ${index})`, duration: round(duration) };
81
+ });
82
+ if (!animations.length) notes.push('no animation: threeGltf poses nothing; the model stays in the pose it is stored in');
83
+ const required = json.extensionsRequired ?? [];
84
+ const decoders = required.filter((e) => /draco|meshopt|basisu|ktx2|webp/i.test(e));
85
+ if (decoders.length) notes.push(`needs ${decoders.join(', ')}: three's GLTFLoader needs a decoder for it (DRACOLoader / KTX2Loader / MeshoptDecoder), and threeGltf does not set one up: re-export the model without that compression, or load it yourself`);
86
+ const external = [...(json.buffers ?? []), ...(json.images ?? [])].filter((x) => x.uri && !/^data:/i.test(x.uri));
87
+ if (external.length) notes.push(`${external.length} file(s) are loaded next to the model (${external.slice(0, 3).map((x) => x.uri).join(', ')}${external.length > 3 ? ', …' : ''}): keep them beside it`);
88
+ return {
89
+ file, container: `glTF ${json.asset?.version ?? '2.0'} (${container})`, bytes,
90
+ counts: { nodes: nodes.length, meshes: meshes.length, skins: (json.skins ?? []).length, materials: (json.materials ?? []).length, textures: (json.textures ?? []).length },
91
+ size: size ? size.map((v) => round(v, 6)) : null, longest: size ? round(Math.max(...size), 6) : null,
92
+ animations, extensions: json.extensionsUsed ?? [], notes,
93
+ };
94
+ }
95
+
96
+ /** Read a local file or a URL and describe it. */
97
+ export async function probeGltf(file) {
98
+ let buf;
99
+ if (isUrl(file)) {
100
+ const res = await fetch(file);
101
+ if (!res.ok) throw new Error(`could not read ${file}: HTTP ${res.status}`);
102
+ buf = Buffer.from(await res.arrayBuffer());
103
+ } else {
104
+ if (!fs.existsSync(file)) throw new Error(`${file}: no such file`);
105
+ buf = fs.readFileSync(file);
106
+ }
107
+ const { json, container } = parseJson(file, buf);
108
+ return describeGltf(json, { file, bytes: isUrl(file) ? null : buf.length, container });
109
+ }
110
+
111
+ const num = (v) => String(round(v, 3));
112
+ const plural = (n, w) => `${n} ${w}${n === 1 ? '' : 's'}`;
113
+ export function gltfText(r) {
114
+ const kb = (n) => (n >= 1048576 ? `${(n / 1048576).toFixed(2)} MB` : `${Math.max(1, Math.round(n / 1024))} KB`);
115
+ const L = [r.file, ` ${r.container}${r.bytes != null ? ` · ${kb(r.bytes)}` : ''}`];
116
+ const c = r.counts;
117
+ L.push(` model ${plural(c.nodes, 'node')} · ${plural(c.meshes, 'mesh').replace('meshs', 'meshes')} · ${plural(c.skins, 'skin')} · ${plural(c.materials, 'material')} · ${plural(c.textures, 'texture')}`);
118
+ if (r.size) L.push(` size ${r.size.map(num).join(' × ')} (x × y × z in the file's units, as stored: the longest side is ${num(r.longest)}; threeGltf's fit(px) scales that side to px)`);
119
+ for (const a of r.animations) L.push(` clip ${a.index} "${a.name}" ${num(a.duration)} s`);
120
+ for (const n of r.notes) L.push(` note ${n}`);
121
+ return L.join('\n');
122
+ }
@@ -2,7 +2,7 @@
2
2
  /**
3
3
  * pixi-effects probe | trim | frames | sound — look at, listen to and cut footage, with no ffmpeg installed.
4
4
  *
5
- * npx pixi-effects probe <file|url> [--keyframes] [--json] what is in a file: container, duration, tracks, codecs, size, notes
5
+ * npx pixi-effects probe <file|url> [--keyframes] [--json] what is in a file: container, duration, tracks, codecs, size, notes (a .glb / .gltf model: clips and size)
6
6
  * npx pixi-effects trim <file|url> <start> <end> -o <out> [--exact] [--force] [--json]
7
7
  * cut [start, end] (seconds, or 1:02.5): by COPY when it can (no re-encode, no loss),
8
8
  * and it says the real times; --exact re-encodes so the start is the frame asked for
@@ -22,6 +22,7 @@ import os from 'node:os';
22
22
  import path from 'node:path';
23
23
  import { createRequire } from 'node:module';
24
24
  import { fileURLToPath, pathToFileURL } from 'node:url';
25
+ import { gltfText, isGltfFile, probeGltf } from './gltfProbe.mjs';
25
26
 
26
27
  const HERE = path.dirname(fileURLToPath(import.meta.url));
27
28
  const PACKAGE_ROOT = path.resolve(HERE, '../..');
@@ -314,6 +315,12 @@ export function checkExpect(report, items) {
314
315
  function bitrateWanted(op, raw) { const n = expectNumber('bitrate', raw); return `${op}${Math.round(n / 1e5) / 10} Mbps`; }
315
316
 
316
317
  commands.probe = async ({ file, opts }, io) => {
318
+ if (isGltfFile(file)) { // a model, not footage: read in Node, no container library, no browser
319
+ if (opts.expect !== undefined || opts.keyframes) throw new UsageError('probe on a glTF model takes no --expect or --keyframes (they are for video and audio files); it prints the clips and the size');
320
+ const r = await probeGltf(file);
321
+ io.log(opts.json ? JSON.stringify(r, null, 2) : gltfText(r));
322
+ return 0;
323
+ }
317
324
  const items = opts.expect !== undefined ? parseExpect(opts.expect) : null; // a bad expectation is a usage error before any file is read
318
325
  const r = await probeFile(file, { keyframes: !!opts.keyframes, exact: !!items });
319
326
  const results = items ? checkExpect(r, items) : null;
@@ -22,7 +22,7 @@ const USAGE = `usage:
22
22
  pixi-effects check <page.html> [--at 3,6] [--out dir] review a video page: warnings, layout, sound, contact sheet, real export
23
23
  pixi-effects render <page.html> -o <file.mp4> export the video
24
24
  pixi-effects view <page.html> look at it in a browser, with a timeline
25
- pixi-effects probe <file|url> what is in a video or audio file (codecs, size, duration), with no ffmpeg
25
+ pixi-effects probe <file|url> what is in a video or audio file (codecs, size, duration), with no ffmpeg; or a .glb / .gltf model (its clips and its size)
26
26
  pixi-effects trim <file|url> <start> <end> -o <out> cut a file (by copy when it can) and say the real times
27
27
  pixi-effects frames <file|url> [--at 1,50%,end] [--sheet] pictures of a video at those times
28
28
  pixi-effects sound <file|url> [--waveform] loudness, peak, silences of a file's sound
@@ -1,5 +1,5 @@
1
1
  import { Container } from 'pixi.js';
2
- import { a as SequenceSpec, C as CompositionShape } from './types-BXi-0iZS.cjs';
2
+ import { S as SequenceSpec, C as CompositionShape } from './types-DIh93lFb.cjs';
3
3
 
4
4
  /**
5
5
  * Resolves a routed keyframe path to a concrete tween target.
@@ -1,5 +1,5 @@
1
1
  import { Container } from 'pixi.js';
2
- import { a as SequenceSpec, C as CompositionShape } from './types-BXi-0iZS.js';
2
+ import { S as SequenceSpec, C as CompositionShape } from './types-DIh93lFb.js';
3
3
 
4
4
  /**
5
5
  * Resolves a routed keyframe path to a concrete tween target.
@@ -0,0 +1,16 @@
1
+ 'use strict';
2
+
3
+ var chunk27PYJ4JY_cjs = require('./chunk-27PYJ4JY.cjs');
4
+ require('./chunk-7EI2N7WO.cjs');
5
+ require('./chunk-JN7JHNZR.cjs');
6
+ require('./chunk-3SAS3ZE5.cjs');
7
+ require('./chunk-TWZNOUM7.cjs');
8
+
9
+
10
+
11
+ Object.defineProperty(exports, "CompositionSequence", {
12
+ enumerable: true,
13
+ get: function () { return chunk27PYJ4JY_cjs.CompositionSequence; }
14
+ });
15
+ //# sourceMappingURL=Composition-C3UEVHQG.cjs.map
16
+ //# sourceMappingURL=Composition-C3UEVHQG.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":[],"names":[],"mappings":"","file":"Composition-3MNLNT2E.cjs"}
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"Composition-C3UEVHQG.cjs"}
@@ -0,0 +1,7 @@
1
+ export { CompositionSequence } from './chunk-26Z544SH.js';
2
+ import './chunk-LMWXR5OC.js';
3
+ import './chunk-VNP7QCJE.js';
4
+ import './chunk-32MMNDDN.js';
5
+ import './chunk-BXOCEZ4U.js';
6
+ //# sourceMappingURL=Composition-KRWH72MA.js.map
7
+ //# sourceMappingURL=Composition-KRWH72MA.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":[],"names":[],"mappings":"","file":"Composition-P37AD4GN.js"}
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"Composition-KRWH72MA.js"}
@@ -1,7 +1,7 @@
1
- import { M as Movie } from './Movie-CSWUTnMd.cjs';
1
+ import { M as Movie } from './Movie-CMq4KFmc.cjs';
2
2
  import 'pixi.js';
3
3
  import './soundEntry-DT13DNt8.cjs';
4
- import './types-BXi-0iZS.cjs';
4
+ import './types-DIh93lFb.cjs';
5
5
 
6
6
  /**
7
7
  * The look of the bar. Each key sets a CSS custom property on the bar (so you can also set `--mc-accent` … in your own CSS, on the
@@ -1,7 +1,7 @@
1
- import { M as Movie } from './Movie-BtwFI2ne.js';
1
+ import { M as Movie } from './Movie-CG6Jy1tx.js';
2
2
  import 'pixi.js';
3
3
  import './soundEntry-DT13DNt8.js';
4
- import './types-BXi-0iZS.js';
4
+ import './types-DIh93lFb.js';
5
5
 
6
6
  /**
7
7
  * The look of the bar. Each key sets a CSS custom property on the bar (so you can also set `--mc-accent` … in your own CSS, on the
@@ -1,6 +1,6 @@
1
1
  import { Application } from 'pixi.js';
2
2
  import { A as AudioReport, a as AudioInspectOptions } from './soundEntry-DT13DNt8.js';
3
- import { d as Stop, A as AssetSpec, c as CompositionSpec } from './types-BXi-0iZS.js';
3
+ import { b as Stop, A as AssetSpec, a as CompositionSpec } from './types-DIh93lFb.js';
4
4
 
5
5
  type EaseFn = (p: number) => number;
6
6
 
@@ -149,6 +149,8 @@ interface CanvasRow {
149
149
  ok: boolean;
150
150
  /** Why not, in a sentence (empty when ok). */
151
151
  problem: string;
152
+ /** Which kind of layer drew it: a `canvas` layer or a `three` layer (both are held to "a function of the time"). */
153
+ kind: 'canvas' | 'three';
152
154
  }
153
155
 
154
156
  /** One kind of issue, with how many frames it was seen in. Issues that differ only by numbers are one kind. */
@@ -208,6 +210,9 @@ interface LayerInfo {
208
210
  depthBlur?: number;
209
211
  /** Light (threeD layers that receive lights): how lit the layer is this frame, 0..1+ (the brightness of the light falling on its middle; 1 = as with no light, 0 = black). Left out when there is no light. */
210
212
  light?: number;
213
+ /** A `three` layer: the renderer it draws with (`'webgl'` / `'webgpu'`) and the backend under it (`'webgl2'`: three's WebGPURenderer found no WebGPU and used its own WebGL2 backend: the same picture). */
214
+ renderer?: 'webgl' | 'webgpu';
215
+ backend?: 'webgl' | 'webgpu' | 'webgl2';
211
216
  }
212
217
  interface InspectReport {
213
218
  frame: number;
@@ -1,6 +1,6 @@
1
1
  import { Application } from 'pixi.js';
2
2
  import { A as AudioReport, a as AudioInspectOptions } from './soundEntry-DT13DNt8.cjs';
3
- import { d as Stop, A as AssetSpec, c as CompositionSpec } from './types-BXi-0iZS.cjs';
3
+ import { b as Stop, A as AssetSpec, a as CompositionSpec } from './types-DIh93lFb.cjs';
4
4
 
5
5
  type EaseFn = (p: number) => number;
6
6
 
@@ -149,6 +149,8 @@ interface CanvasRow {
149
149
  ok: boolean;
150
150
  /** Why not, in a sentence (empty when ok). */
151
151
  problem: string;
152
+ /** Which kind of layer drew it: a `canvas` layer or a `three` layer (both are held to "a function of the time"). */
153
+ kind: 'canvas' | 'three';
152
154
  }
153
155
 
154
156
  /** One kind of issue, with how many frames it was seen in. Issues that differ only by numbers are one kind. */
@@ -208,6 +210,9 @@ interface LayerInfo {
208
210
  depthBlur?: number;
209
211
  /** Light (threeD layers that receive lights): how lit the layer is this frame, 0..1+ (the brightness of the light falling on its middle; 1 = as with no light, 0 = black). Left out when there is no light. */
210
212
  light?: number;
213
+ /** A `three` layer: the renderer it draws with (`'webgl'` / `'webgpu'`) and the backend under it (`'webgl2'`: three's WebGPURenderer found no WebGPU and used its own WebGL2 backend: the same picture). */
214
+ renderer?: 'webgl' | 'webgpu';
215
+ backend?: 'webgl' | 'webgpu' | 'webgl2';
211
216
  }
212
217
  interface InspectReport {
213
218
  frame: number;
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
- var chunkSJFDSHBP_cjs = require('./chunk-SJFDSHBP.cjs');
4
- var chunkHX73S7FD_cjs = require('./chunk-HX73S7FD.cjs');
3
+ var chunk27PYJ4JY_cjs = require('./chunk-27PYJ4JY.cjs');
4
+ var chunk7EI2N7WO_cjs = require('./chunk-7EI2N7WO.cjs');
5
5
  require('./chunk-JN7JHNZR.cjs');
6
6
  require('./chunk-3SAS3ZE5.cjs');
7
7
  require('./chunk-TWZNOUM7.cjs');
@@ -12,12 +12,12 @@ var strip = (props) => {
12
12
  const { visibleChars: _gone, ...rest } = props;
13
13
  return rest;
14
14
  };
15
- var SpansSequence = class extends chunkSJFDSHBP_cjs.CompositionSequence {
15
+ var SpansSequence = class extends chunk27PYJ4JY_cjs.CompositionSequence {
16
16
  _meta;
17
17
  _count = { value: 0, visibleChars: 0 };
18
18
  constructor(spec, parent, root) {
19
19
  super(spec, parent, root);
20
- this._meta = chunkHX73S7FD_cjs.spansMeta.get(spec);
20
+ this._meta = chunk7EI2N7WO_cjs.spansMeta.get(spec);
21
21
  }
22
22
  /** `visibleChars` is the group's own counter, not a property to write on the container. */
23
23
  displayProps() {
@@ -33,8 +33,8 @@ var SpansSequence = class extends chunkSJFDSHBP_cjs.CompositionSequence {
33
33
  if (!this._meta?.typed) return;
34
34
  const scope = this.scope();
35
35
  const first = this.spec.initial?.visibleChars;
36
- this._count.visibleChars = first === void 0 ? 0 : chunkHX73S7FD_cjs.resolveNumber(first, scope);
37
- chunkHX73S7FD_cjs.bindCounterKeyframes(timeline, this._count, "visibleChars", () => this._distribute(), this.spec.keyframes ?? [], this.duration, offset + this.at, scope);
36
+ this._count.visibleChars = first === void 0 ? 0 : chunk7EI2N7WO_cjs.resolveNumber(first, scope);
37
+ chunk7EI2N7WO_cjs.bindCounterKeyframes(timeline, this._count, "visibleChars", () => this._distribute(), this.spec.keyframes ?? [], this.duration, offset + this.at, scope);
38
38
  this._distribute();
39
39
  }
40
40
  /** Every piece shows its part of the first N characters of the whole text. */
@@ -43,7 +43,7 @@ var SpansSequence = class extends chunkSJFDSHBP_cjs.CompositionSequence {
43
43
  const total = Math.max(0, Math.floor(this._count.visibleChars));
44
44
  let i = 0;
45
45
  for (const child of this._children) {
46
- if (!(child instanceof chunkHX73S7FD_cjs.TextSequence)) continue;
46
+ if (!(child instanceof chunk7EI2N7WO_cjs.TextSequence)) continue;
47
47
  const p = pieces[i++];
48
48
  if (p) child.setVisibleChars(Math.min(p.length, Math.max(0, total - p.start)));
49
49
  }
@@ -51,5 +51,5 @@ var SpansSequence = class extends chunkSJFDSHBP_cjs.CompositionSequence {
51
51
  };
52
52
 
53
53
  exports.SpansSequence = SpansSequence;
54
- //# sourceMappingURL=Spans-F6FBBCD3.cjs.map
55
- //# sourceMappingURL=Spans-F6FBBCD3.cjs.map
54
+ //# sourceMappingURL=Spans-APNJHCYO.cjs.map
55
+ //# sourceMappingURL=Spans-APNJHCYO.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/sequences/Spans.ts"],"names":["CompositionSequence","spansMeta","resolveNumber","bindCounterKeyframes","TextSequence"],"mappings":";;;;;;;;;AAQA,IAAM,KAAA,GAAQ,CAAC,KAAA,KAAgD;AAC7D,EAAA,IAAI,CAAC,KAAA,IAAS,EAAE,cAAA,IAAkB,QAAQ,OAAO,KAAA;AACjD,EAAA,MAAM,EAAE,YAAA,EAAc,KAAA,EAAO,GAAG,MAAK,GAAI,KAAA;AACzC,EAAA,OAAO,IAAA;AACT,CAAA;AAEO,IAAM,aAAA,GAAN,cAA4BA,qCAAA,CAAoB;AAAA,EACpC,KAAA;AAAA,EACA,MAAA,GAAuB,EAAE,KAAA,EAAO,CAAA,EAAG,cAAc,CAAA,EAAE;AAAA,EAEpE,WAAA,CAAY,IAAA,EAAoB,MAAA,EAAiC,IAAA,EAAwB;AACvF,IAAA,KAAA,CAAM,IAAA,EAAM,QAAQ,IAAI,CAAA;AACxB,IAAA,IAAA,CAAK,KAAA,GAAQC,2BAAA,CAAU,GAAA,CAAI,IAAI,CAAA;AAAA,EACjC;AAAA;AAAA,EAGmB,YAAA,GAAyH;AAC1I,IAAA,MAAM,CAAA,GAAI,MAAM,YAAA,EAAa;AAC7B,IAAA,IAAI,CAAC,IAAA,CAAK,KAAA,EAAO,KAAA,EAAO,OAAO,CAAA;AAC/B,IAAA,OAAO;AAAA,MACL,OAAA,EAAS,KAAA,CAAM,CAAA,CAAE,OAA4B,CAAA;AAAA,MAC7C,SAAA,EAAW,EAAE,SAAA,EAAW,GAAA,CAAI,SAAO,EAAE,GAAG,EAAA,EAAI,GAAA,EAAK,KAAA,CAAM,EAAA,CAAG,GAAG,CAAA,EAAG,EAAA,EAAI,KAAA,CAAM,EAAA,CAAG,EAAE,CAAA,EAAG,MAAM,KAAA,CAAM,EAAA,CAAG,IAAI,CAAA,EAAE,CAAE;AAAA,KAC3G;AAAA,EACF;AAAA,EAES,YAAA,CAAa,QAAA,EAAoB,MAAA,GAAS,CAAA,EAAS;AAC1D,IAAA,KAAA,CAAM,YAAA,CAAa,UAAU,MAAM,CAAA;AACnC,IAAA,IAAI,CAAC,IAAA,CAAK,KAAA,EAAO,KAAA,EAAO;AACxB,IAAA,MAAM,KAAA,GAAQ,KAAK,KAAA,EAAM;AACzB,IAAA,MAAM,KAAA,GAAS,IAAA,CAAK,IAAA,CAAK,OAAA,EAAiD,YAAA;AAC1E,IAAA,IAAA,CAAK,OAAO,YAAA,GAAe,KAAA,KAAU,SAAY,CAAA,GAAIC,+BAAA,CAAc,OAAO,KAAK,CAAA;AAC/E,IAAAC,sCAAA,CAAqB,UAAU,IAAA,CAAK,MAAA,EAAQ,gBAAgB,MAAM,IAAA,CAAK,aAAY,EAAG,IAAA,CAAK,IAAA,CAAK,SAAA,IAAa,EAAC,EAAG,IAAA,CAAK,UAAW,MAAA,GAAS,IAAA,CAAK,IAAI,KAAK,CAAA;AACxJ,IAAA,IAAA,CAAK,WAAA,EAAY;AAAA,EACnB;AAAA;AAAA,EAGQ,WAAA,GAAoB;AAC1B,IAAA,MAAM,MAAA,GAAS,KAAK,KAAA,CAAO,MAAA;AAC3B,IAAA,MAAM,KAAA,GAAQ,KAAK,GAAA,CAAI,CAAA,EAAG,KAAK,KAAA,CAAM,IAAA,CAAK,MAAA,CAAO,YAAY,CAAC,CAAA;AAC9D,IAAA,IAAI,CAAA,GAAI,CAAA;AACR,IAAA,KAAA,MAAW,KAAA,IAAS,KAAK,SAAA,EAAW;AAClC,MAAA,IAAI,EAAE,iBAAiBC,8BAAA,CAAA,EAAe;AACtC,MAAA,MAAM,CAAA,GAAI,OAAO,CAAA,EAAG,CAAA;AACpB,MAAA,IAAI,CAAA,EAAG,KAAA,CAAM,eAAA,CAAgB,IAAA,CAAK,IAAI,CAAA,CAAE,MAAA,EAAQ,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,KAAA,GAAQ,CAAA,CAAE,KAAK,CAAC,CAAC,CAAA;AAAA,IAC/E;AAAA,EACF;AACF","file":"Spans-F6FBBCD3.cjs","sourcesContent":["// The sequence behind a text layer with `spans`: a composition of ordinary text layers (see src/text/spans.ts) that also owns the typewriter\n// (`visibleChars`) of the whole text: it counts over every character of every piece, and tells each piece how many of its own to show.\nimport { CompositionSequence } from './Composition';\nimport { TextSequence, bindCounterKeyframes, resolveNumber, type CounterState } from './Text';\nimport { spansMeta, type SpansMeta } from '../text/spans';\nimport type { CompositionShape, SequenceSpec, Props } from '../types';\nimport type { Timeline } from '../core/timelineEngine';\n\nconst strip = (props: Props | undefined): Props | undefined => {\n if (!props || !('visibleChars' in props)) return props;\n const { visibleChars: _gone, ...rest } = props as Record<string, unknown>;\n return rest as unknown as Props;\n};\n\nexport class SpansSequence extends CompositionSequence {\n private readonly _meta: SpansMeta | undefined;\n private readonly _count: CounterState = { value: 0, visibleChars: 0 };\n\n constructor(spec: SequenceSpec, parent: CompositionShape | null, root: CompositionShape) {\n super(spec, parent, root);\n this._meta = spansMeta.get(spec);\n }\n\n /** `visibleChars` is the group's own counter, not a property to write on the container. */\n protected override displayProps(): { initial: CompositionSequence['spec']['initial']; keyframes: CompositionSequence['spec']['keyframes'] } {\n const p = super.displayProps();\n if (!this._meta?.typed) return p;\n return {\n initial: strip(p.initial as Props | undefined) as never,\n keyframes: p.keyframes?.map(kf => ({ ...kf, set: strip(kf.set), to: strip(kf.to), from: strip(kf.from) })),\n };\n }\n\n override bindTimeline(timeline: Timeline, offset = 0): void {\n super.bindTimeline(timeline, offset);\n if (!this._meta?.typed) return;\n const scope = this.scope();\n const first = (this.spec.initial as Record<string, unknown> | undefined)?.visibleChars;\n this._count.visibleChars = first === undefined ? 0 : resolveNumber(first, scope); // like a text: it starts at 0 once it is animated\n bindCounterKeyframes(timeline, this._count, 'visibleChars', () => this._distribute(), this.spec.keyframes ?? [], this.duration!, offset + this.at, scope);\n this._distribute();\n }\n\n /** Every piece shows its part of the first N characters of the whole text. */\n private _distribute(): void {\n const pieces = this._meta!.pieces;\n const total = Math.max(0, Math.floor(this._count.visibleChars));\n let i = 0;\n for (const child of this._children) {\n if (!(child instanceof TextSequence)) continue;\n const p = pieces[i++];\n if (p) child.setVisibleChars(Math.min(p.length, Math.max(0, total - p.start)));\n }\n }\n}\n"]}
1
+ {"version":3,"sources":["../src/sequences/Spans.ts"],"names":["CompositionSequence","spansMeta","resolveNumber","bindCounterKeyframes","TextSequence"],"mappings":";;;;;;;;;AAQA,IAAM,KAAA,GAAQ,CAAC,KAAA,KAAgD;AAC7D,EAAA,IAAI,CAAC,KAAA,IAAS,EAAE,cAAA,IAAkB,QAAQ,OAAO,KAAA;AACjD,EAAA,MAAM,EAAE,YAAA,EAAc,KAAA,EAAO,GAAG,MAAK,GAAI,KAAA;AACzC,EAAA,OAAO,IAAA;AACT,CAAA;AAEO,IAAM,aAAA,GAAN,cAA4BA,qCAAA,CAAoB;AAAA,EACpC,KAAA;AAAA,EACA,MAAA,GAAuB,EAAE,KAAA,EAAO,CAAA,EAAG,cAAc,CAAA,EAAE;AAAA,EAEpE,WAAA,CAAY,IAAA,EAAoB,MAAA,EAAiC,IAAA,EAAwB;AACvF,IAAA,KAAA,CAAM,IAAA,EAAM,QAAQ,IAAI,CAAA;AACxB,IAAA,IAAA,CAAK,KAAA,GAAQC,2BAAA,CAAU,GAAA,CAAI,IAAI,CAAA;AAAA,EACjC;AAAA;AAAA,EAGmB,YAAA,GAAyH;AAC1I,IAAA,MAAM,CAAA,GAAI,MAAM,YAAA,EAAa;AAC7B,IAAA,IAAI,CAAC,IAAA,CAAK,KAAA,EAAO,KAAA,EAAO,OAAO,CAAA;AAC/B,IAAA,OAAO;AAAA,MACL,OAAA,EAAS,KAAA,CAAM,CAAA,CAAE,OAA4B,CAAA;AAAA,MAC7C,SAAA,EAAW,EAAE,SAAA,EAAW,GAAA,CAAI,SAAO,EAAE,GAAG,EAAA,EAAI,GAAA,EAAK,KAAA,CAAM,EAAA,CAAG,GAAG,CAAA,EAAG,EAAA,EAAI,KAAA,CAAM,EAAA,CAAG,EAAE,CAAA,EAAG,MAAM,KAAA,CAAM,EAAA,CAAG,IAAI,CAAA,EAAE,CAAE;AAAA,KAC3G;AAAA,EACF;AAAA,EAES,YAAA,CAAa,QAAA,EAAoB,MAAA,GAAS,CAAA,EAAS;AAC1D,IAAA,KAAA,CAAM,YAAA,CAAa,UAAU,MAAM,CAAA;AACnC,IAAA,IAAI,CAAC,IAAA,CAAK,KAAA,EAAO,KAAA,EAAO;AACxB,IAAA,MAAM,KAAA,GAAQ,KAAK,KAAA,EAAM;AACzB,IAAA,MAAM,KAAA,GAAS,IAAA,CAAK,IAAA,CAAK,OAAA,EAAiD,YAAA;AAC1E,IAAA,IAAA,CAAK,OAAO,YAAA,GAAe,KAAA,KAAU,SAAY,CAAA,GAAIC,+BAAA,CAAc,OAAO,KAAK,CAAA;AAC/E,IAAAC,sCAAA,CAAqB,UAAU,IAAA,CAAK,MAAA,EAAQ,gBAAgB,MAAM,IAAA,CAAK,aAAY,EAAG,IAAA,CAAK,IAAA,CAAK,SAAA,IAAa,EAAC,EAAG,IAAA,CAAK,UAAW,MAAA,GAAS,IAAA,CAAK,IAAI,KAAK,CAAA;AACxJ,IAAA,IAAA,CAAK,WAAA,EAAY;AAAA,EACnB;AAAA;AAAA,EAGQ,WAAA,GAAoB;AAC1B,IAAA,MAAM,MAAA,GAAS,KAAK,KAAA,CAAO,MAAA;AAC3B,IAAA,MAAM,KAAA,GAAQ,KAAK,GAAA,CAAI,CAAA,EAAG,KAAK,KAAA,CAAM,IAAA,CAAK,MAAA,CAAO,YAAY,CAAC,CAAA;AAC9D,IAAA,IAAI,CAAA,GAAI,CAAA;AACR,IAAA,KAAA,MAAW,KAAA,IAAS,KAAK,SAAA,EAAW;AAClC,MAAA,IAAI,EAAE,iBAAiBC,8BAAA,CAAA,EAAe;AACtC,MAAA,MAAM,CAAA,GAAI,OAAO,CAAA,EAAG,CAAA;AACpB,MAAA,IAAI,CAAA,EAAG,KAAA,CAAM,eAAA,CAAgB,IAAA,CAAK,IAAI,CAAA,CAAE,MAAA,EAAQ,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,KAAA,GAAQ,CAAA,CAAE,KAAK,CAAC,CAAC,CAAA;AAAA,IAC/E;AAAA,EACF;AACF","file":"Spans-APNJHCYO.cjs","sourcesContent":["// The sequence behind a text layer with `spans`: a composition of ordinary text layers (see src/text/spans.ts) that also owns the typewriter\n// (`visibleChars`) of the whole text: it counts over every character of every piece, and tells each piece how many of its own to show.\nimport { CompositionSequence } from './Composition';\nimport { TextSequence, bindCounterKeyframes, resolveNumber, type CounterState } from './Text';\nimport { spansMeta, type SpansMeta } from '../text/spans';\nimport type { CompositionShape, SequenceSpec, Props } from '../types';\nimport type { Timeline } from '../core/timelineEngine';\n\nconst strip = (props: Props | undefined): Props | undefined => {\n if (!props || !('visibleChars' in props)) return props;\n const { visibleChars: _gone, ...rest } = props as Record<string, unknown>;\n return rest as unknown as Props;\n};\n\nexport class SpansSequence extends CompositionSequence {\n private readonly _meta: SpansMeta | undefined;\n private readonly _count: CounterState = { value: 0, visibleChars: 0 };\n\n constructor(spec: SequenceSpec, parent: CompositionShape | null, root: CompositionShape) {\n super(spec, parent, root);\n this._meta = spansMeta.get(spec);\n }\n\n /** `visibleChars` is the group's own counter, not a property to write on the container. */\n protected override displayProps(): { initial: CompositionSequence['spec']['initial']; keyframes: CompositionSequence['spec']['keyframes'] } {\n const p = super.displayProps();\n if (!this._meta?.typed) return p;\n return {\n initial: strip(p.initial as Props | undefined) as never,\n keyframes: p.keyframes?.map(kf => ({ ...kf, set: strip(kf.set), to: strip(kf.to), from: strip(kf.from) })),\n };\n }\n\n override bindTimeline(timeline: Timeline, offset = 0): void {\n super.bindTimeline(timeline, offset);\n if (!this._meta?.typed) return;\n const scope = this.scope();\n const first = (this.spec.initial as Record<string, unknown> | undefined)?.visibleChars;\n this._count.visibleChars = first === undefined ? 0 : resolveNumber(first, scope); // like a text: it starts at 0 once it is animated\n bindCounterKeyframes(timeline, this._count, 'visibleChars', () => this._distribute(), this.spec.keyframes ?? [], this.duration!, offset + this.at, scope);\n this._distribute();\n }\n\n /** Every piece shows its part of the first N characters of the whole text. */\n private _distribute(): void {\n const pieces = this._meta!.pieces;\n const total = Math.max(0, Math.floor(this._count.visibleChars));\n let i = 0;\n for (const child of this._children) {\n if (!(child instanceof TextSequence)) continue;\n const p = pieces[i++];\n if (p) child.setVisibleChars(Math.min(p.length, Math.max(0, total - p.start)));\n }\n }\n}\n"]}
@@ -1,5 +1,5 @@
1
- import { CompositionSequence } from './chunk-UHCVO5TH.js';
2
- import { spansMeta, resolveNumber, bindCounterKeyframes, TextSequence } from './chunk-JCPZJJNW.js';
1
+ import { CompositionSequence } from './chunk-26Z544SH.js';
2
+ import { spansMeta, resolveNumber, bindCounterKeyframes, TextSequence } from './chunk-LMWXR5OC.js';
3
3
  import './chunk-VNP7QCJE.js';
4
4
  import './chunk-32MMNDDN.js';
5
5
  import './chunk-BXOCEZ4U.js';
@@ -49,5 +49,5 @@ var SpansSequence = class extends CompositionSequence {
49
49
  };
50
50
 
51
51
  export { SpansSequence };
52
- //# sourceMappingURL=Spans-PLBQOSMV.js.map
53
- //# sourceMappingURL=Spans-PLBQOSMV.js.map
52
+ //# sourceMappingURL=Spans-TWO4VN5H.js.map
53
+ //# sourceMappingURL=Spans-TWO4VN5H.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/sequences/Spans.ts"],"names":[],"mappings":";;;;;;;AAQA,IAAM,KAAA,GAAQ,CAAC,KAAA,KAAgD;AAC7D,EAAA,IAAI,CAAC,KAAA,IAAS,EAAE,cAAA,IAAkB,QAAQ,OAAO,KAAA;AACjD,EAAA,MAAM,EAAE,YAAA,EAAc,KAAA,EAAO,GAAG,MAAK,GAAI,KAAA;AACzC,EAAA,OAAO,IAAA;AACT,CAAA;AAEO,IAAM,aAAA,GAAN,cAA4B,mBAAA,CAAoB;AAAA,EACpC,KAAA;AAAA,EACA,MAAA,GAAuB,EAAE,KAAA,EAAO,CAAA,EAAG,cAAc,CAAA,EAAE;AAAA,EAEpE,WAAA,CAAY,IAAA,EAAoB,MAAA,EAAiC,IAAA,EAAwB;AACvF,IAAA,KAAA,CAAM,IAAA,EAAM,QAAQ,IAAI,CAAA;AACxB,IAAA,IAAA,CAAK,KAAA,GAAQ,SAAA,CAAU,GAAA,CAAI,IAAI,CAAA;AAAA,EACjC;AAAA;AAAA,EAGmB,YAAA,GAAyH;AAC1I,IAAA,MAAM,CAAA,GAAI,MAAM,YAAA,EAAa;AAC7B,IAAA,IAAI,CAAC,IAAA,CAAK,KAAA,EAAO,KAAA,EAAO,OAAO,CAAA;AAC/B,IAAA,OAAO;AAAA,MACL,OAAA,EAAS,KAAA,CAAM,CAAA,CAAE,OAA4B,CAAA;AAAA,MAC7C,SAAA,EAAW,EAAE,SAAA,EAAW,GAAA,CAAI,SAAO,EAAE,GAAG,EAAA,EAAI,GAAA,EAAK,KAAA,CAAM,EAAA,CAAG,GAAG,CAAA,EAAG,EAAA,EAAI,KAAA,CAAM,EAAA,CAAG,EAAE,CAAA,EAAG,MAAM,KAAA,CAAM,EAAA,CAAG,IAAI,CAAA,EAAE,CAAE;AAAA,KAC3G;AAAA,EACF;AAAA,EAES,YAAA,CAAa,QAAA,EAAoB,MAAA,GAAS,CAAA,EAAS;AAC1D,IAAA,KAAA,CAAM,YAAA,CAAa,UAAU,MAAM,CAAA;AACnC,IAAA,IAAI,CAAC,IAAA,CAAK,KAAA,EAAO,KAAA,EAAO;AACxB,IAAA,MAAM,KAAA,GAAQ,KAAK,KAAA,EAAM;AACzB,IAAA,MAAM,KAAA,GAAS,IAAA,CAAK,IAAA,CAAK,OAAA,EAAiD,YAAA;AAC1E,IAAA,IAAA,CAAK,OAAO,YAAA,GAAe,KAAA,KAAU,SAAY,CAAA,GAAI,aAAA,CAAc,OAAO,KAAK,CAAA;AAC/E,IAAA,oBAAA,CAAqB,UAAU,IAAA,CAAK,MAAA,EAAQ,gBAAgB,MAAM,IAAA,CAAK,aAAY,EAAG,IAAA,CAAK,IAAA,CAAK,SAAA,IAAa,EAAC,EAAG,IAAA,CAAK,UAAW,MAAA,GAAS,IAAA,CAAK,IAAI,KAAK,CAAA;AACxJ,IAAA,IAAA,CAAK,WAAA,EAAY;AAAA,EACnB;AAAA;AAAA,EAGQ,WAAA,GAAoB;AAC1B,IAAA,MAAM,MAAA,GAAS,KAAK,KAAA,CAAO,MAAA;AAC3B,IAAA,MAAM,KAAA,GAAQ,KAAK,GAAA,CAAI,CAAA,EAAG,KAAK,KAAA,CAAM,IAAA,CAAK,MAAA,CAAO,YAAY,CAAC,CAAA;AAC9D,IAAA,IAAI,CAAA,GAAI,CAAA;AACR,IAAA,KAAA,MAAW,KAAA,IAAS,KAAK,SAAA,EAAW;AAClC,MAAA,IAAI,EAAE,iBAAiB,YAAA,CAAA,EAAe;AACtC,MAAA,MAAM,CAAA,GAAI,OAAO,CAAA,EAAG,CAAA;AACpB,MAAA,IAAI,CAAA,EAAG,KAAA,CAAM,eAAA,CAAgB,IAAA,CAAK,IAAI,CAAA,CAAE,MAAA,EAAQ,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,KAAA,GAAQ,CAAA,CAAE,KAAK,CAAC,CAAC,CAAA;AAAA,IAC/E;AAAA,EACF;AACF","file":"Spans-PLBQOSMV.js","sourcesContent":["// The sequence behind a text layer with `spans`: a composition of ordinary text layers (see src/text/spans.ts) that also owns the typewriter\n// (`visibleChars`) of the whole text: it counts over every character of every piece, and tells each piece how many of its own to show.\nimport { CompositionSequence } from './Composition';\nimport { TextSequence, bindCounterKeyframes, resolveNumber, type CounterState } from './Text';\nimport { spansMeta, type SpansMeta } from '../text/spans';\nimport type { CompositionShape, SequenceSpec, Props } from '../types';\nimport type { Timeline } from '../core/timelineEngine';\n\nconst strip = (props: Props | undefined): Props | undefined => {\n if (!props || !('visibleChars' in props)) return props;\n const { visibleChars: _gone, ...rest } = props as Record<string, unknown>;\n return rest as unknown as Props;\n};\n\nexport class SpansSequence extends CompositionSequence {\n private readonly _meta: SpansMeta | undefined;\n private readonly _count: CounterState = { value: 0, visibleChars: 0 };\n\n constructor(spec: SequenceSpec, parent: CompositionShape | null, root: CompositionShape) {\n super(spec, parent, root);\n this._meta = spansMeta.get(spec);\n }\n\n /** `visibleChars` is the group's own counter, not a property to write on the container. */\n protected override displayProps(): { initial: CompositionSequence['spec']['initial']; keyframes: CompositionSequence['spec']['keyframes'] } {\n const p = super.displayProps();\n if (!this._meta?.typed) return p;\n return {\n initial: strip(p.initial as Props | undefined) as never,\n keyframes: p.keyframes?.map(kf => ({ ...kf, set: strip(kf.set), to: strip(kf.to), from: strip(kf.from) })),\n };\n }\n\n override bindTimeline(timeline: Timeline, offset = 0): void {\n super.bindTimeline(timeline, offset);\n if (!this._meta?.typed) return;\n const scope = this.scope();\n const first = (this.spec.initial as Record<string, unknown> | undefined)?.visibleChars;\n this._count.visibleChars = first === undefined ? 0 : resolveNumber(first, scope); // like a text: it starts at 0 once it is animated\n bindCounterKeyframes(timeline, this._count, 'visibleChars', () => this._distribute(), this.spec.keyframes ?? [], this.duration!, offset + this.at, scope);\n this._distribute();\n }\n\n /** Every piece shows its part of the first N characters of the whole text. */\n private _distribute(): void {\n const pieces = this._meta!.pieces;\n const total = Math.max(0, Math.floor(this._count.visibleChars));\n let i = 0;\n for (const child of this._children) {\n if (!(child instanceof TextSequence)) continue;\n const p = pieces[i++];\n if (p) child.setVisibleChars(Math.min(p.length, Math.max(0, total - p.start)));\n }\n }\n}\n"]}
1
+ {"version":3,"sources":["../src/sequences/Spans.ts"],"names":[],"mappings":";;;;;;;AAQA,IAAM,KAAA,GAAQ,CAAC,KAAA,KAAgD;AAC7D,EAAA,IAAI,CAAC,KAAA,IAAS,EAAE,cAAA,IAAkB,QAAQ,OAAO,KAAA;AACjD,EAAA,MAAM,EAAE,YAAA,EAAc,KAAA,EAAO,GAAG,MAAK,GAAI,KAAA;AACzC,EAAA,OAAO,IAAA;AACT,CAAA;AAEO,IAAM,aAAA,GAAN,cAA4B,mBAAA,CAAoB;AAAA,EACpC,KAAA;AAAA,EACA,MAAA,GAAuB,EAAE,KAAA,EAAO,CAAA,EAAG,cAAc,CAAA,EAAE;AAAA,EAEpE,WAAA,CAAY,IAAA,EAAoB,MAAA,EAAiC,IAAA,EAAwB;AACvF,IAAA,KAAA,CAAM,IAAA,EAAM,QAAQ,IAAI,CAAA;AACxB,IAAA,IAAA,CAAK,KAAA,GAAQ,SAAA,CAAU,GAAA,CAAI,IAAI,CAAA;AAAA,EACjC;AAAA;AAAA,EAGmB,YAAA,GAAyH;AAC1I,IAAA,MAAM,CAAA,GAAI,MAAM,YAAA,EAAa;AAC7B,IAAA,IAAI,CAAC,IAAA,CAAK,KAAA,EAAO,KAAA,EAAO,OAAO,CAAA;AAC/B,IAAA,OAAO;AAAA,MACL,OAAA,EAAS,KAAA,CAAM,CAAA,CAAE,OAA4B,CAAA;AAAA,MAC7C,SAAA,EAAW,EAAE,SAAA,EAAW,GAAA,CAAI,SAAO,EAAE,GAAG,EAAA,EAAI,GAAA,EAAK,KAAA,CAAM,EAAA,CAAG,GAAG,CAAA,EAAG,EAAA,EAAI,KAAA,CAAM,EAAA,CAAG,EAAE,CAAA,EAAG,MAAM,KAAA,CAAM,EAAA,CAAG,IAAI,CAAA,EAAE,CAAE;AAAA,KAC3G;AAAA,EACF;AAAA,EAES,YAAA,CAAa,QAAA,EAAoB,MAAA,GAAS,CAAA,EAAS;AAC1D,IAAA,KAAA,CAAM,YAAA,CAAa,UAAU,MAAM,CAAA;AACnC,IAAA,IAAI,CAAC,IAAA,CAAK,KAAA,EAAO,KAAA,EAAO;AACxB,IAAA,MAAM,KAAA,GAAQ,KAAK,KAAA,EAAM;AACzB,IAAA,MAAM,KAAA,GAAS,IAAA,CAAK,IAAA,CAAK,OAAA,EAAiD,YAAA;AAC1E,IAAA,IAAA,CAAK,OAAO,YAAA,GAAe,KAAA,KAAU,SAAY,CAAA,GAAI,aAAA,CAAc,OAAO,KAAK,CAAA;AAC/E,IAAA,oBAAA,CAAqB,UAAU,IAAA,CAAK,MAAA,EAAQ,gBAAgB,MAAM,IAAA,CAAK,aAAY,EAAG,IAAA,CAAK,IAAA,CAAK,SAAA,IAAa,EAAC,EAAG,IAAA,CAAK,UAAW,MAAA,GAAS,IAAA,CAAK,IAAI,KAAK,CAAA;AACxJ,IAAA,IAAA,CAAK,WAAA,EAAY;AAAA,EACnB;AAAA;AAAA,EAGQ,WAAA,GAAoB;AAC1B,IAAA,MAAM,MAAA,GAAS,KAAK,KAAA,CAAO,MAAA;AAC3B,IAAA,MAAM,KAAA,GAAQ,KAAK,GAAA,CAAI,CAAA,EAAG,KAAK,KAAA,CAAM,IAAA,CAAK,MAAA,CAAO,YAAY,CAAC,CAAA;AAC9D,IAAA,IAAI,CAAA,GAAI,CAAA;AACR,IAAA,KAAA,MAAW,KAAA,IAAS,KAAK,SAAA,EAAW;AAClC,MAAA,IAAI,EAAE,iBAAiB,YAAA,CAAA,EAAe;AACtC,MAAA,MAAM,CAAA,GAAI,OAAO,CAAA,EAAG,CAAA;AACpB,MAAA,IAAI,CAAA,EAAG,KAAA,CAAM,eAAA,CAAgB,IAAA,CAAK,IAAI,CAAA,CAAE,MAAA,EAAQ,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,KAAA,GAAQ,CAAA,CAAE,KAAK,CAAC,CAAC,CAAA;AAAA,IAC/E;AAAA,EACF;AACF","file":"Spans-TWO4VN5H.js","sourcesContent":["// The sequence behind a text layer with `spans`: a composition of ordinary text layers (see src/text/spans.ts) that also owns the typewriter\n// (`visibleChars`) of the whole text: it counts over every character of every piece, and tells each piece how many of its own to show.\nimport { CompositionSequence } from './Composition';\nimport { TextSequence, bindCounterKeyframes, resolveNumber, type CounterState } from './Text';\nimport { spansMeta, type SpansMeta } from '../text/spans';\nimport type { CompositionShape, SequenceSpec, Props } from '../types';\nimport type { Timeline } from '../core/timelineEngine';\n\nconst strip = (props: Props | undefined): Props | undefined => {\n if (!props || !('visibleChars' in props)) return props;\n const { visibleChars: _gone, ...rest } = props as Record<string, unknown>;\n return rest as unknown as Props;\n};\n\nexport class SpansSequence extends CompositionSequence {\n private readonly _meta: SpansMeta | undefined;\n private readonly _count: CounterState = { value: 0, visibleChars: 0 };\n\n constructor(spec: SequenceSpec, parent: CompositionShape | null, root: CompositionShape) {\n super(spec, parent, root);\n this._meta = spansMeta.get(spec);\n }\n\n /** `visibleChars` is the group's own counter, not a property to write on the container. */\n protected override displayProps(): { initial: CompositionSequence['spec']['initial']; keyframes: CompositionSequence['spec']['keyframes'] } {\n const p = super.displayProps();\n if (!this._meta?.typed) return p;\n return {\n initial: strip(p.initial as Props | undefined) as never,\n keyframes: p.keyframes?.map(kf => ({ ...kf, set: strip(kf.set), to: strip(kf.to), from: strip(kf.from) })),\n };\n }\n\n override bindTimeline(timeline: Timeline, offset = 0): void {\n super.bindTimeline(timeline, offset);\n if (!this._meta?.typed) return;\n const scope = this.scope();\n const first = (this.spec.initial as Record<string, unknown> | undefined)?.visibleChars;\n this._count.visibleChars = first === undefined ? 0 : resolveNumber(first, scope); // like a text: it starts at 0 once it is animated\n bindCounterKeyframes(timeline, this._count, 'visibleChars', () => this._distribute(), this.spec.keyframes ?? [], this.duration!, offset + this.at, scope);\n this._distribute();\n }\n\n /** Every piece shows its part of the first N characters of the whole text. */\n private _distribute(): void {\n const pieces = this._meta!.pieces;\n const total = Math.max(0, Math.floor(this._count.visibleChars));\n let i = 0;\n for (const child of this._children) {\n if (!(child instanceof TextSequence)) continue;\n const p = pieces[i++];\n if (p) child.setVisibleChars(Math.min(p.length, Math.max(0, total - p.start)));\n }\n }\n}\n"]}