pixi-effects 0.25.0 → 0.26.1
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/CHANGELOG.md +22 -0
- package/README.md +13 -13
- package/ai/CHAT.md +2 -2
- package/ai/chat-template.html +3 -3
- package/ai/tools/check.mjs +15 -3
- package/ai/tools/pixi-effects.mjs +48 -0
- package/ai/tools/render.mjs +3 -3
- package/ai/tools/view.mjs +3 -3
- package/dist/{Base-BgnRXbtJ.d.ts → Base-CpdDs4HT.d.ts} +1 -1
- package/dist/{Base-DHVKqA46.d.cts → Base-TbBhHPrG.d.cts} +1 -1
- package/dist/Composition-THQPG4E5.cjs +16 -0
- package/dist/{Composition-2GJSCHPP.cjs.map → Composition-THQPG4E5.cjs.map} +1 -1
- package/dist/Composition-Z2VY3KWZ.js +7 -0
- package/dist/{Composition-C4FSWJZF.js.map → Composition-Z2VY3KWZ.js.map} +1 -1
- package/dist/Controller.d.cts +2 -2
- package/dist/Controller.d.ts +2 -2
- package/dist/{Movie-FM5m67V0.d.ts → Movie-DVeDTdnc.d.ts} +25 -2
- package/dist/{Movie-CTpN1wYL.d.cts → Movie-i05Oh9mJ.d.cts} +25 -2
- package/dist/Presenter.d.cts +2 -2
- package/dist/Presenter.d.ts +2 -2
- package/dist/{chunk-DP37TTMO.cjs → chunk-EABL3UKJ.cjs} +5 -3
- package/dist/chunk-EABL3UKJ.cjs.map +1 -0
- package/dist/{chunk-SY27HZC5.cjs → chunk-H3RHEH5W.cjs} +59 -59
- package/dist/{chunk-SY27HZC5.cjs.map → chunk-H3RHEH5W.cjs.map} +1 -1
- package/dist/{chunk-PRZ4U6JN.js → chunk-JHJNEIDI.js} +5 -3
- package/dist/chunk-JHJNEIDI.js.map +1 -0
- package/dist/{chunk-XS6GSJCN.js → chunk-YEVAUQ6Y.js} +3 -3
- package/dist/{chunk-XS6GSJCN.js.map → chunk-YEVAUQ6Y.js.map} +1 -1
- package/dist/index.cjs +193 -32
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +169 -8
- package/dist/index.js.map +1 -1
- package/dist/loader.css +35 -2
- package/dist/three.cjs +5 -5
- package/dist/three.d.cts +2 -2
- package/dist/three.d.ts +2 -2
- package/dist/three.js +1 -1
- package/dist/{types-CoT0ypcx.d.cts → types-DSGwN5w1.d.cts} +2 -0
- package/dist/{types-CoT0ypcx.d.ts → types-DSGwN5w1.d.ts} +2 -0
- package/docs/api.md +4 -3
- package/docs/dsl.md +2 -2
- package/llms-full.txt +34 -25
- package/llms.txt +6 -5
- package/package.json +3 -3
- package/{ai → skills/pixi-effects}/SKILL.md +11 -9
- package/{ai → skills/pixi-effects}/reference/cheatsheet.md +3 -3
- package/{ai → skills/pixi-effects}/reference/pitfalls.md +9 -3
- package/{ai → skills/pixi-effects}/reference/recipes.md +2 -2
- package/{ai/tools → skills/pixi-effects/scripts}/save-image.py +1 -1
- package/{ai → skills/pixi-effects}/template.html +7 -6
- package/dist/Composition-2GJSCHPP.cjs +0 -16
- package/dist/Composition-C4FSWJZF.js +0 -7
- package/dist/chunk-DP37TTMO.cjs.map +0 -1
- package/dist/chunk-PRZ4U6JN.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.26.1
|
|
4
|
+
|
|
5
|
+
**Fixed**
|
|
6
|
+
|
|
7
|
+
- The skill's `description` had a colon-space, which the `skills` CLI refuses ("Nested mappings are not allowed": no skill found); fixed, and a test reads the frontmatter as a plain YAML scalar.
|
|
8
|
+
|
|
9
|
+
**Added**
|
|
10
|
+
|
|
11
|
+
- **Loading progress.** `movie.init()` reports how far it is: a `loadprogress` event (`{ stage: 'assets' | 'build' | 'sound' | 'frames', loaded, total, progress }`, `progress` 0 to 1) and `movie.loadStages` (milliseconds per stage). The page's `.pe-loader` shows it by itself: a bar along the bottom (`--pe-progress`) and a label such as `BUILDING LAYERS 62%`; the build now lets the page paint every 50 ms. `pixi-effects check` prints a `loading` line with the time of each stage and names the slowest when the load took a second or more. The template links the library with `modulepreload`, so the download overlaps the page parse.
|
|
12
|
+
|
|
13
|
+
## 0.26.0
|
|
14
|
+
|
|
15
|
+
**Added**
|
|
16
|
+
|
|
17
|
+
- **Agent skill.** `npx skills add yjmtmtk/pixi-effects` registers pixi-effects as a skill in Claude Code, Codex, Cursor, Gemini CLI, Copilot, OpenCode and about 70 more agents; its description starts from the task (a video, motion graphics, an animated title, a lower third, a promo, a chart), so an agent can reach it without being told the library's name. Claude Code also installs it as a plugin (`/plugin install pixi-effects --marketplace yjmtmtk/pixi-effects`).
|
|
18
|
+
- **`pixi-effects` bin** (named like the package): `npx pixi-effects check my-video.html`, `npx pixi-effects render … -o x.mp4`, `npx pixi-effects view …` run the same three tools with nothing installed, and a mistyped command says which one you meant. The three `pixi-effects-check|render|view` bins stay; `npx pixi-effects-check` on its own never worked (npm looks for a package of that name), the docs now say `npx pixi-effects check`. **Fixed:** the three tools printed nothing and exited 0 when npm started them through its symlink (`./node_modules/.bin/pixi-effects-check`); they now run.
|
|
19
|
+
- **`movie.audioBlocked`**: true while the browser keeps the movie's sound silent until a tap (iOS Safari); a player can then ask for the tap. The landing page does: the reel and the gallery pieces start with one tap inside the frame when the sound is locked.
|
|
20
|
+
|
|
21
|
+
**Changed**
|
|
22
|
+
|
|
23
|
+
- The skill moved from `ai/` to `skills/pixi-effects/` (`SKILL.md`, `template.html`, `reference/`, `scripts/save-image.py`); `ai/` keeps the chat guide and the tools. Old links to `ai/SKILL.md`, `ai/reference/*` and `ai/template.html` no longer exist. The skill's commands are `npx pixi-effects-check|render|view`.
|
|
24
|
+
|
|
3
25
|
## 0.25.0
|
|
4
26
|
|
|
5
27
|
One release holds three milestones that were built one after the other: **composition** (named mattes, the luma wipe), **light** (lights, shadows, fog) and **freedom of drawing** (the shader layer, the `warp` filter). Wipe, iris and dissolve transitions now also work on WebGL (they drew nothing there before).
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# pixi-effects
|
|
2
2
|
|
|
3
|
-
> **Status**: experimental — current release `0.
|
|
3
|
+
> **Status**: experimental — current release `0.26.1`. The API may still change between minor versions; see the [CHANGELOG](./CHANGELOG.md) for what each release changed.
|
|
4
4
|
|
|
5
5
|
**[Guide →](https://yjmtmtk.github.io/pixi-effects/guide/)** · **[Gallery →](https://yjmtmtk.github.io/pixi-effects/examples/gallery/)** · 50 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.
|
|
6
6
|
|
|
@@ -10,7 +10,7 @@ Give any AI (ChatGPT, Claude, Codex, Cursor, Claude Code …) one sentence:
|
|
|
10
10
|
|
|
11
11
|
> Use https://github.com/yjmtmtk/pixi-effects to make a video: a 10-second title for a bakery, warm colours, a soft chime at the end.
|
|
12
12
|
|
|
13
|
-
That is all it needs. **If you are the AI** that was asked to do this, do not read the whole repository: if you can run commands (a shell, Node 22, Chrome), read [`AGENTS.md`](./AGENTS.md) and [`
|
|
13
|
+
That is all it needs. **To register it as a skill** in your agent (Claude Code, Codex, Cursor, Gemini CLI, Copilot, OpenCode, …): `npx skills add yjmtmtk/pixi-effects` (update with `npx skills update`). In Claude Code you can also run `/plugin install pixi-effects --marketplace yjmtmtk/pixi-effects`. **If you are the AI** that was asked to do this, do not read the whole repository: if you can run commands (a shell, Node 22, Chrome), read [`AGENTS.md`](./AGENTS.md) and [`skills/pixi-effects/SKILL.md`](./skills/pixi-effects/SKILL.md); if you can only write text (a browser chat), fetch https://raw.githubusercontent.com/yjmtmtk/pixi-effects/main/ai/CHAT.md and follow it. You will write the video as plain data in one HTML file, check it (the check tool writes a contact sheet to look at), and hand it over; the person opens the file and downloads the MP4 with the button in the player bar.
|
|
14
14
|
|
|
15
15
|
Declarative composition and video rendering for the web. After Effects-style timelines on top of [PixiJS v8](https://pixijs.com/) and [GSAP](https://gsap.com/), with strict TypeScript types. Render to MP4 / WebM / MOV via [mediabunny](https://mediabunny.dev/).
|
|
16
16
|
|
|
@@ -19,7 +19,7 @@ Declarative composition and video rendering for the web. After Effects-style tim
|
|
|
19
19
|
- **Expression language** — sprinkle `'GW * 0.5'` or `'min(W, H) / 2'` anywhere a number goes. Resolved at runtime against a sequence-relative scope.
|
|
20
20
|
- **Filters by name** — `filters: [{ type: 'glow', outerStrength: 3 }]`, no import: `blur noise alpha colorMatrix` (pixi.js) and the 38 filters of [`pixi-filters`](https://github.com/pixijs/filters) (`dropShadow crt rgbSplit oldFilm glitch pixelate twist …`), animated with `'filters.<name>.<option>'`. Any other PixiJS `Filter` goes in as `{ type: 'custom' }`.
|
|
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
|
-
- **Masks, transitions, chroma key** — inline masks; chroma key;
|
|
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
24
|
- **Presentations** — `stops` in the composition pause the movie where you say; `Presenter` (`pixi-effects/presenter`) 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 (`movie.exportPDF()`, `pixi-effects-render talk.html -o talk.pdf`). See the [Presenting guide](https://yjmtmtk.github.io/pixi-effects/guide/presenting.html).
|
|
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.
|
|
@@ -51,8 +51,8 @@ Drop the imports into an [importmap](https://developer.mozilla.org/docs/Web/HTML
|
|
|
51
51
|
"gsap": "https://esm.sh/gsap@3.12.5",
|
|
52
52
|
"gsap/PixiPlugin": "https://esm.sh/gsap@3.12.5/PixiPlugin",
|
|
53
53
|
"mediabunny": "https://esm.sh/mediabunny",
|
|
54
|
-
"pixi-effects": "https://cdn.jsdelivr.net/npm/pixi-effects@0.
|
|
55
|
-
"pixi-effects/controller": "https://cdn.jsdelivr.net/npm/pixi-effects@0.
|
|
54
|
+
"pixi-effects": "https://cdn.jsdelivr.net/npm/pixi-effects@0.26.1/dist/index.js",
|
|
55
|
+
"pixi-effects/controller": "https://cdn.jsdelivr.net/npm/pixi-effects@0.26.1/dist/Controller.js"
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
58
|
</script>
|
|
@@ -63,7 +63,7 @@ Drop the imports into an [importmap](https://developer.mozilla.org/docs/Web/HTML
|
|
|
63
63
|
</script>
|
|
64
64
|
```
|
|
65
65
|
|
|
66
|
-
Load the `dist/` files **as they are** (jsDelivr's `/npm/…/dist/…`, or unpkg's `https://unpkg.com/pixi-effects@0.
|
|
66
|
+
Load the `dist/` files **as they are** (jsDelivr's `/npm/…/dist/…`, or unpkg's `https://unpkg.com/pixi-effects@0.26.1/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.
|
|
67
67
|
|
|
68
68
|
> **Using three.js?** Add two more entries (`three` and `pixi-effects/three`) to this importmap — see [Adding three.js](#adding-threejs-optional) below.
|
|
69
69
|
>
|
|
@@ -79,7 +79,7 @@ Add two more entries to the importmap above: three.js itself, and the `pixi-effe
|
|
|
79
79
|
"imports": {
|
|
80
80
|
"...": "(everything from the importmap above)",
|
|
81
81
|
"three": "https://esm.sh/three@0.178.0",
|
|
82
|
-
"pixi-effects/three": "https://cdn.jsdelivr.net/npm/pixi-effects@0.
|
|
82
|
+
"pixi-effects/three": "https://cdn.jsdelivr.net/npm/pixi-effects@0.26.1/dist/three.js"
|
|
83
83
|
}
|
|
84
84
|
}
|
|
85
85
|
</script>
|
|
@@ -173,13 +173,13 @@ sequences: [
|
|
|
173
173
|
This library is designed to be written by AI: a video is plain data, and every mistake we found while having AI sessions build test animations is either warned about at runtime or written down.
|
|
174
174
|
|
|
175
175
|
- [`llms.txt`](./llms.txt) / [`llms-full.txt`](./llms-full.txt) — index and full text for LLM tooling (generated from the files below).
|
|
176
|
-
- [`
|
|
177
|
-
- [`
|
|
176
|
+
- [`skills/pixi-effects/SKILL.md`](./skills/pixi-effects/SKILL.md) — a [skill](https://docs.claude.com/en/docs/claude-code/skills) (workflow, rules, verification loop). Copy the `ai/` folder to `~/.claude/skills/pixi-effects/` (or your project's `.claude/skills/`) to have Claude load it automatically when you ask for a video.
|
|
177
|
+
- [`skills/pixi-effects/reference/cheatsheet.md`](./skills/pixi-effects/reference/cheatsheet.md), [`recipes.md`](./skills/pixi-effects/reference/recipes.md) (tested), [`pitfalls.md`](./skills/pixi-effects/reference/pitfalls.md), and a starter [`skills/pixi-effects/template.html`](./skills/pixi-effects/template.html).
|
|
178
178
|
|
|
179
|
-
- **Look at a page with its timeline, and scrub one with the other:** `npx pixi-effects
|
|
180
|
-
- **Render a page to a video file, headless:** `npx pixi-effects
|
|
181
|
-
- **A talk as a PDF:** `npx pixi-effects
|
|
182
|
-
- **One-command review for the AI that wrote the page:** `npx pixi-effects
|
|
179
|
+
- **Look at a page with its timeline, and scrub one with the other:** `npx pixi-effects view my-video.html` (or `node ai/tools/view.mjs …`) opens your browser on a viewer: the page on top, the timeline of every layer under it with a playhead that follows the movie. Click or drag the timeline to seek, click a layer's name to jump to where it starts, Space plays, ← / → step a frame, + / − (or Ctrl/⌘ + wheel) zoom, the names stay in place. For the human who wants to see what the AI made.
|
|
180
|
+
- **Render a page to a video file, headless:** `npx pixi-effects render my-video.html -o my-video.mp4` (or `node ai/tools/render.mjs …`; Node ≥ 22 and Chrome installed, no dependencies). The container follows the extension (`mp4 webm mov mkv`); `--quality very-low…very-high`, `--query lang=ja` (added to the page URL), `--fail-on-warn` (exit 1 when the page logged a warning), `--quiet`. It waits for `window.__ready`, runs `movie.render()` and streams the file to disk; exit 0 = written, 1 = the page or the render failed (no file). For scripts, CI and batches; `pixi-effects-check` below is the review.
|
|
181
|
+
- **A talk as a PDF:** `npx pixi-effects render my-talk.html -o my-talk.pdf` (one page per page of a `deck()` / `stops` movie; `--all-stops` makes a page of every step). The review tool writes `stops.png` for such a page, one picture per stop.
|
|
182
|
+
- **One-command review for the AI that wrote the page:** `npx pixi-effects check my-video.html` (or `node ai/tools/check.mjs my-video.html`; Node ≥ 22 and Chrome installed, no dependencies). It opens the page in its own headless Chrome and reports warnings, layout (`movie.inspect` over the whole timeline), the soundtrack (`movie.inspectAudio`) and a real export decoded again, and writes a contact sheet PNG and a `timeline.html` (every layer as a bar on a time axis) to look at. Exit code 0 / 1.
|
|
183
183
|
|
|
184
184
|
They ship in the npm package (`node_modules/pixi-effects/ai/`).
|
|
185
185
|
|
package/ai/CHAT.md
CHANGED
|
@@ -60,7 +60,7 @@ That is a complete, working video (the block between the two markers). Change th
|
|
|
60
60
|
- **Scenes.** Stack layers with `at` / `duration` over an opaque background rect. Do not remove layers.
|
|
61
61
|
- **Sound with no files:** `{ type: 'audio', sfx: 'swoosh', at: 2 }`. Presets: `click pop swoosh swipe hit riser chime beep coin glitch typewriter`; optional `pitch`, `brightness`. A `riser` is loudest at its **end**: start it `duration` seconds before the hit. This page cannot load music or images from your own files; use shapes, text, sfx and `music` (below).
|
|
62
62
|
- **Repeat with code, never by copying:** `Array.from({ length: 12 }, (_, i) => ({ type: 'shape', shape: 'circle', radius: 20, at: i * 0.15, duration: DURATION - i * 0.15, initial: { x: 120 + i * 90, y: 360, fillColor: '#ffd166' }, keyframes: [{ at: 0, from: { scale: 0 }, to: { scale: 1 }, duration: 0.4, ease: 'back.out(2)' }] }))` returns twelve layers: spread them into `sequences` with `...`.
|
|
63
|
-
- **Helpers already imported** in the template: `kenBurns` (slow zoom for an image layer), `withFade`, `wiggle` (seeded shake as keyframes), `stagger`, `animateText` (per letter / word entrances), `followPath`, `particles` (seeded confetti / snow / sparks as layers), `deck` (slides). Each returns keyframes or layers: `keyframes: wiggle(...)`, `...particles(...)`. The full signatures are in `https://raw.githubusercontent.com/yjmtmtk/pixi-effects/main/
|
|
63
|
+
- **Helpers already imported** in the template: `kenBurns` (slow zoom for an image layer), `withFade`, `wiggle` (seeded shake as keyframes), `stagger`, `animateText` (per letter / word entrances), `followPath`, `particles` (seeded confetti / snow / sparks as layers), `deck` (slides). Each returns keyframes or layers: `keyframes: wiggle(...)`, `...particles(...)`. The full signatures are in `https://raw.githubusercontent.com/yjmtmtk/pixi-effects/main/skills/pixi-effects/reference/cheatsheet.md` (22 KB; read it only if you need a helper).
|
|
64
64
|
- **Advanced:** `const INIT = { assets: [...], composition: { transitions: [...] } }` in the edit block adds `movie.init` options (the page merges it); most videos need nothing here.
|
|
65
65
|
- **Not available on this page:** named filters beyond `blur`, `noise`, `alpha`, `colorMatrix`, `grain` (the other 38 need the `pixi-filters` package; film grain is `{ type: 'grain' }`), three.js layers, your own image / video / music files. Say so rather than guessing.
|
|
66
66
|
|
|
@@ -107,4 +107,4 @@ Read your file once for these: every layer has a `type`; every keyframe `at` is
|
|
|
107
107
|
|
|
108
108
|
A blank preview with no red box usually means the chat's preview window blocks external scripts or does not allow `eval`. Tell the person to **save the file as `video.html` and open it in a browser** (Chrome, Edge or Firefox on a computer); that always works.
|
|
109
109
|
|
|
110
|
-
More: the full skill (`
|
|
110
|
+
More: the full skill (`skills/pixi-effects/SKILL.md`) and pitfalls (`skills/pixi-effects/reference/pitfalls.md`) are for an AI with a shell and a browser; this page is the short version for a chat.
|
package/ai/chat-template.html
CHANGED
|
@@ -61,9 +61,9 @@
|
|
|
61
61
|
"gsap": "https://cdn.jsdelivr.net/npm/gsap@3.12.5/+esm",
|
|
62
62
|
"gsap/PixiPlugin": "https://cdn.jsdelivr.net/npm/gsap@3.12.5/PixiPlugin/+esm",
|
|
63
63
|
"mediabunny": "https://cdn.jsdelivr.net/npm/mediabunny@1.61.3/+esm",
|
|
64
|
-
"pixi-effects": "https://cdn.jsdelivr.net/npm/pixi-effects@0.
|
|
65
|
-
"pixi-effects/controller": "https://cdn.jsdelivr.net/npm/pixi-effects@0.
|
|
66
|
-
"pixi-effects/presenter": "https://cdn.jsdelivr.net/npm/pixi-effects@0.
|
|
64
|
+
"pixi-effects": "https://cdn.jsdelivr.net/npm/pixi-effects@0.26.1/dist/index.js",
|
|
65
|
+
"pixi-effects/controller": "https://cdn.jsdelivr.net/npm/pixi-effects@0.26.1/dist/Controller.js",
|
|
66
|
+
"pixi-effects/presenter": "https://cdn.jsdelivr.net/npm/pixi-effects@0.26.1/dist/Presenter.js"
|
|
67
67
|
}
|
|
68
68
|
}
|
|
69
69
|
</script>
|
package/ai/tools/check.mjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* pixi-effects check — review a composition page in ONE command, without driving a browser by hand.
|
|
4
4
|
*
|
|
5
|
-
* node ai/tools/check.mjs my-video.html (or: npx pixi-effects
|
|
5
|
+
* node ai/tools/check.mjs my-video.html (or: npx pixi-effects check my-video.html)
|
|
6
6
|
*
|
|
7
7
|
* It starts a private static server and a private headless Chrome (nothing shared with other sessions), opens the page,
|
|
8
8
|
* waits for `window.__ready === true`, then checks: console warnings / errors, `movie.inspect` over the whole timeline
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* (`sheet.png`, `<name>.<format>`, `report.json`; a presentation with `stops` also gets `stops.png`, one picture per stop) and a short summary is printed. Exit code 0 = nothing to fix, 1 = problems.
|
|
12
12
|
*
|
|
13
13
|
* Needs: Node >= 22 (built-in WebSocket), Chrome / Chromium installed (or --chrome PATH / CHROME=PATH). No npm dependencies.
|
|
14
|
-
* The page must follow
|
|
14
|
+
* The page must follow skills/pixi-effects/template.html: it exposes `window.movie` and sets `window.__ready = true` (and `window.__logs`).
|
|
15
15
|
*
|
|
16
16
|
* Options: --draft (the export is a draft: half size, low quality, no motion blur: a much smaller file; the drawing itself is not faster) · --at LIST (pictures at moments you name: 3.5, 50%, f120, title@end → frames/*.png and at.png) ·
|
|
17
17
|
* --query "a=1&b=2" (added to the page address: for a page that reads it, such as one video of a batch) ·
|
|
@@ -28,6 +28,16 @@ import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
|
28
28
|
|
|
29
29
|
// ───────────────────────────── pure helpers (unit-tested) ─────────────────────────────
|
|
30
30
|
|
|
31
|
+
/** `assets 0.1 s · build 2.4 s (slowest) · sound 0.6 s · frames 0.2 s` from `movie.loadStages` (milliseconds); the slowest stage is named only when the load took 1 s or more. */
|
|
32
|
+
export function formatLoadStages(stages) {
|
|
33
|
+
if (!stages) return '';
|
|
34
|
+
const names = ['assets', 'build', 'sound', 'frames'];
|
|
35
|
+
const total = names.reduce((n, k) => n + (stages[k] ?? 0), 0);
|
|
36
|
+
const slowest = total >= 1000 ? names.reduce((a, k) => ((stages[k] ?? 0) > (stages[a] ?? 0) ? k : a), 'assets') : null;
|
|
37
|
+
const sec = ms => `${Math.round((ms ?? 0) / 100) / 10} s`;
|
|
38
|
+
return names.map(k => `${k} ${sec(stages[k])}${k === slowest ? ' (slowest)' : ''}`).join(' · ');
|
|
39
|
+
}
|
|
40
|
+
|
|
31
41
|
/** Chrome / Chromium executable: $CHROME, then the usual install paths. Null if none is found. */
|
|
32
42
|
export function findChrome(env = process.env, exists = fs.existsSync, platform = process.platform) {
|
|
33
43
|
if (env.CHROME && exists(env.CHROME)) return env.CHROME;
|
|
@@ -296,6 +306,7 @@ export async function runCheck(opts, log = console.log) {
|
|
|
296
306
|
info = await cdp.eval(INFO).catch(() => info) ?? info;
|
|
297
307
|
report.ready = !!info?.hasMovie && (await cdp.eval('window.__ready === true').catch(() => false));
|
|
298
308
|
report.readySeconds = Math.round((Date.now() - t0) / 100) / 10;
|
|
309
|
+
report.loadStages = await cdp.eval('(window.movie && window.movie.loadStages) || null').catch(() => null);
|
|
299
310
|
report.logs = [...new Set([...(info?.logs ?? []), ...consoleLines])];
|
|
300
311
|
if (!report.ready) {
|
|
301
312
|
report.problems.push(`the page did not become ready (window.__ready !== true within 60 s${info?.hasMovie ? '' : '; window.movie is missing'})`);
|
|
@@ -464,6 +475,7 @@ function finish(report, outDir, log) {
|
|
|
464
475
|
L.push(`pixi-effects check — ${report.page}`);
|
|
465
476
|
if (report.ready) L.push(` page ready in ${report.readySeconds} s · ${report.width}×${report.height} · ${report.frameRate} fps · ${report.totalFrames} frames (${report.duration} s)${report.hasAudio ? ' · audio' : ''}`);
|
|
466
477
|
else L.push(' page NOT READY');
|
|
478
|
+
if (report.loadStages) L.push(` loading ${formatLoadStages(report.loadStages)}`);
|
|
467
479
|
L.push(` warnings ${report.logs?.length ? report.logs.length + ' — ' + report.logs.slice(0, 5).map(l => l.length > 220 ? l.slice(0, 217) + '…' : l).join('\n ') + (report.logs.length > 5 ? `\n … ${report.logs.length - 5} more (report.json)` : '') : 'none'}`);
|
|
468
480
|
if (report.inspect) {
|
|
469
481
|
const g = report.inspect.issues;
|
|
@@ -514,7 +526,7 @@ function finish(report, outDir, log) {
|
|
|
514
526
|
|
|
515
527
|
// ───────────────────────────── CLI ─────────────────────────────
|
|
516
528
|
|
|
517
|
-
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
529
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(fs.realpathSync(process.argv[1])).href) { // the real path: npm starts a bin through a symlink
|
|
518
530
|
try {
|
|
519
531
|
if (typeof WebSocket === 'undefined') throw new Error('Node >= 22 is needed (built-in WebSocket)');
|
|
520
532
|
const opts = parseArgs(process.argv.slice(2));
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* pixi-effects — one command for the three tools, named like the package so `npx pixi-effects check my-video.html` works with nothing installed.
|
|
4
|
+
*
|
|
5
|
+
* npx pixi-effects check my-video.html [--at 3,6] [--out dir] review it: warnings, layout, sound, a contact sheet, a real export
|
|
6
|
+
* npx pixi-effects render my-video.html -o my-video.mp4 export the file
|
|
7
|
+
* npx pixi-effects view my-video.html open it in a browser with a timeline
|
|
8
|
+
*
|
|
9
|
+
* Everything after the command goes to the tool unchanged (`check.mjs`, `render.mjs`, `view.mjs`; their own headers list every option), and so do the
|
|
10
|
+
* exit code and the output. The three older bins (`pixi-effects-check|render|view`) still work.
|
|
11
|
+
*/
|
|
12
|
+
import { spawn } from 'node:child_process';
|
|
13
|
+
import { realpathSync } from 'node:fs';
|
|
14
|
+
import { dirname, resolve } from 'node:path';
|
|
15
|
+
import { fileURLToPath } from 'node:url';
|
|
16
|
+
|
|
17
|
+
const TOOLS = { check: 'check.mjs', render: 'render.mjs', view: 'view.mjs' };
|
|
18
|
+
const USAGE = `usage:
|
|
19
|
+
pixi-effects check <page.html> [--at 3,6] [--out dir] review a video page: warnings, layout, sound, contact sheet, real export
|
|
20
|
+
pixi-effects render <page.html> -o <file.mp4> export the video
|
|
21
|
+
pixi-effects view <page.html> look at it in a browser, with a timeline
|
|
22
|
+
(every option of a command is listed in the header of its file: node_modules/pixi-effects/ai/tools/<command>.mjs)`;
|
|
23
|
+
|
|
24
|
+
function nearest(word) {
|
|
25
|
+
const d = (a, b) => { const m = Array.from({ length: a.length + 1 }, (_, i) => [i, ...Array(b.length).fill(0)]); for (let j = 1; j <= b.length; j++) m[0][j] = j;
|
|
26
|
+
for (let i = 1; i <= a.length; i++) for (let j = 1; j <= b.length; j++) m[i][j] = Math.min(m[i - 1][j] + 1, m[i][j - 1] + 1, m[i - 1][j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1)); return m[a.length][b.length]; };
|
|
27
|
+
const best = Object.keys(TOOLS).map(k => [k, d(word, k)]).sort((x, y) => x[1] - y[1])[0];
|
|
28
|
+
return best[1] <= 2 ? best[0] : null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Which tool a command line asks for: `{ tool, args }`, or `{ usage }` (no command / --help), or `{ error }`. */
|
|
32
|
+
export function route(argv) {
|
|
33
|
+
const [cmd, ...args] = argv;
|
|
34
|
+
if (!cmd || cmd === '--help' || cmd === '-h' || cmd === 'help') return { usage: USAGE };
|
|
35
|
+
if (TOOLS[cmd]) return { tool: TOOLS[cmd], args };
|
|
36
|
+
const guess = /\.html?$/i.test(cmd) ? 'check' : nearest(cmd);
|
|
37
|
+
return { error: `pixi-effects: unknown command "${cmd}"${guess ? `; did you mean "${guess}"?` : ''} The commands are: ${Object.keys(TOOLS).join(', ')}.\n${USAGE}` };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const self = fileURLToPath(import.meta.url);
|
|
41
|
+
if (process.argv[1] && realpathSync(resolve(process.argv[1])) === realpathSync(self)) {
|
|
42
|
+
const r = route(process.argv.slice(2));
|
|
43
|
+
if (r.usage) { console.log(r.usage); process.exit(process.argv.length > 2 ? 0 : 2); }
|
|
44
|
+
if (r.error) { console.error(r.error); process.exit(2); }
|
|
45
|
+
const child = spawn(process.execPath, [resolve(dirname(self), r.tool), ...r.args], { stdio: 'inherit' });
|
|
46
|
+
for (const sig of ['SIGINT', 'SIGTERM', 'SIGHUP']) process.on(sig, () => child.kill(sig)); // a harness that stops this process stops the tool (and its Chrome) too
|
|
47
|
+
child.on('exit', (code, signal) => process.exit(code ?? (signal ? 128 : 1)));
|
|
48
|
+
}
|
package/ai/tools/render.mjs
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* pixi-effects render — turn a composition page into a video file, headless, in ONE command.
|
|
4
4
|
*
|
|
5
|
-
* node ai/tools/render.mjs my-video.html -o my-video.mp4 (or: npx pixi-effects
|
|
5
|
+
* node ai/tools/render.mjs my-video.html -o my-video.mp4 (or: npx pixi-effects render my-video.html -o out.mp4)
|
|
6
6
|
*
|
|
7
7
|
* It starts a private static server and a private headless Chrome, opens the page, waits for `window.__ready === true`
|
|
8
|
-
* (the contract of
|
|
8
|
+
* (the contract of skills/pixi-effects/template.html), runs `movie.render()` in the page and streams the file to disk. Nothing is decoded or
|
|
9
9
|
* reviewed: that is `pixi-effects-check`'s job. Use this one to produce the file (a script, CI, a batch).
|
|
10
10
|
*
|
|
11
11
|
* Needs: Node >= 22 (built-in WebSocket), Chrome / Chromium installed (or CHROME=PATH / --chrome PATH). No npm dependencies.
|
|
@@ -200,7 +200,7 @@ export async function runRender(opts, log = console.log, progress = () => {}) {
|
|
|
200
200
|
|
|
201
201
|
// ───────────────────────────── CLI ─────────────────────────────
|
|
202
202
|
|
|
203
|
-
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
203
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(fs.realpathSync(process.argv[1])).href) { // the real path: npm starts a bin through a symlink
|
|
204
204
|
try {
|
|
205
205
|
if (typeof WebSocket === 'undefined') throw new Error('Node >= 22 is needed (built-in WebSocket)');
|
|
206
206
|
const opts = parseRenderArgs(process.argv.slice(2));
|
package/ai/tools/view.mjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* pixi-effects view — look at a composition page WITH its timeline, and scrub the one with the other.
|
|
4
4
|
*
|
|
5
|
-
* node ai/tools/view.mjs my-video.html (or: npx pixi-effects
|
|
5
|
+
* node ai/tools/view.mjs my-video.html (or: npx pixi-effects view my-video.html)
|
|
6
6
|
*
|
|
7
7
|
* It serves the page's folder on a private port and opens your browser on a viewer: the page itself on top (its own player
|
|
8
8
|
* works as usual) and, under it, the timeline of every layer (`movie.timelineSvg()`) with a playhead that follows the movie.
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* ← / → step a frame (Shift: a second), Home / End jump to the ends. Stop it with Ctrl-C.
|
|
11
11
|
*
|
|
12
12
|
* Needs: Node >= 18. A browser (the default one is opened; --no-open prints the address instead). The page must follow
|
|
13
|
-
*
|
|
13
|
+
* skills/pixi-effects/template.html (`window.movie`, `window.__ready = true`) and use a pixi-effects that has `movie.timelineSvg()` (0.7+).
|
|
14
14
|
* Options: --port N (default: a free one) · --no-open · --query "a=1" (added to the page URL) · --root DIR (default: the nearest folder above the page with dist/)
|
|
15
15
|
*/
|
|
16
16
|
import http from 'node:http';
|
|
@@ -252,7 +252,7 @@ export async function startViewer(opts) {
|
|
|
252
252
|
return { server, url: `http://127.0.0.1:${port}${VIEWER}` };
|
|
253
253
|
}
|
|
254
254
|
|
|
255
|
-
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
255
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(fs.realpathSync(process.argv[1])).href) { // the real path: npm starts a bin through a symlink
|
|
256
256
|
try {
|
|
257
257
|
const opts = parseViewArgs(process.argv.slice(2));
|
|
258
258
|
if (!opts.page) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Filter, Container, RenderTexture, Matrix } from 'pixi.js';
|
|
2
|
-
import { S as SequenceSpec, C as CompositionShape, a6 as AudioDescriptor } from './types-
|
|
2
|
+
import { S as SequenceSpec, C as CompositionShape, a6 as AudioDescriptor } from './types-DSGwN5w1.js';
|
|
3
3
|
import { gsap } from 'gsap';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Filter, Container, RenderTexture, Matrix } from 'pixi.js';
|
|
2
|
-
import { S as SequenceSpec, C as CompositionShape, a6 as AudioDescriptor } from './types-
|
|
2
|
+
import { S as SequenceSpec, C as CompositionShape, a6 as AudioDescriptor } from './types-DSGwN5w1.cjs';
|
|
3
3
|
import { gsap } from 'gsap';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var chunkH3RHEH5W_cjs = require('./chunk-H3RHEH5W.cjs');
|
|
4
|
+
require('./chunk-EABL3UKJ.cjs');
|
|
5
|
+
require('./chunk-3DLYAPK7.cjs');
|
|
6
|
+
require('./chunk-ECA2WNRX.cjs');
|
|
7
|
+
require('./chunk-TWZNOUM7.cjs');
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
Object.defineProperty(exports, "CompositionSequence", {
|
|
12
|
+
enumerable: true,
|
|
13
|
+
get: function () { return chunkH3RHEH5W_cjs.CompositionSequence; }
|
|
14
|
+
});
|
|
15
|
+
//# sourceMappingURL=Composition-THQPG4E5.cjs.map
|
|
16
|
+
//# sourceMappingURL=Composition-THQPG4E5.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":[],"names":[],"mappings":"","file":"Composition-
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"Composition-THQPG4E5.cjs"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { CompositionSequence } from './chunk-YEVAUQ6Y.js';
|
|
2
|
+
import './chunk-JHJNEIDI.js';
|
|
3
|
+
import './chunk-AXZUKNXF.js';
|
|
4
|
+
import './chunk-TLYTYBWX.js';
|
|
5
|
+
import './chunk-BXOCEZ4U.js';
|
|
6
|
+
//# sourceMappingURL=Composition-Z2VY3KWZ.js.map
|
|
7
|
+
//# sourceMappingURL=Composition-Z2VY3KWZ.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":[],"names":[],"mappings":"","file":"Composition-
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"Composition-Z2VY3KWZ.js"}
|
package/dist/Controller.d.cts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { j as Movie } from './Movie-i05Oh9mJ.cjs';
|
|
2
2
|
import 'pixi.js';
|
|
3
3
|
import 'gsap';
|
|
4
|
-
import './types-
|
|
4
|
+
import './types-DSGwN5w1.cjs';
|
|
5
5
|
|
|
6
6
|
/** The bar's default stylesheet. Its colours and sizes are CSS custom properties (`--mc-accent`, `--mc-fg`, `--mc-track`, `--mc-bar-bg`, `--mc-track-height`, `--mc-font`): set them in your page CSS, or with the `theme` option. */
|
|
7
7
|
declare const CONTROLLER_CSS = "\n.movie-controller-wrap { position: relative; display: inline-block; line-height: 0; }\n.movie-controller {\n position: absolute;\n pointer-events: none;\n font-family: var(--mc-font, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif);\n display: flex;\n flex-direction: column;\n justify-content: flex-end;\n opacity: 1;\n transition: opacity 200ms ease;\n}\n.movie-controller[data-state=\"hidden\"] { opacity: 0; }\n.movie-controller > * { pointer-events: auto; }\n\n.mc-progress {\n position: relative;\n width: 100%;\n height: 16px;\n cursor: pointer;\n display: flex;\n align-items: center;\n outline: none;\n}\n.mc-progress:focus-visible {\n outline: 2px solid var(--mc-accent, #007AFF);\n outline-offset: 2px;\n}\n.mc-progress::before {\n content: \"\"; position: absolute; left: 12px; right: 12px;\n height: var(--mc-track-height, 3px); background: var(--mc-track, rgba(255,255,255,0.25));\n transition: height 120ms ease;\n}\n.mc-progress:hover::before, .mc-progress.mc-scrubbing::before { height: calc(var(--mc-track-height, 3px) + 2px); }\n.mc-stops { position: absolute; left: 12px; right: 12px; top: 0; bottom: 0; pointer-events: none; }\n.mc-stop-tick { position: absolute; top: 50%; width: 7px; height: 7px; transform: translate(-50%, -50%); border-radius: 50%; background: var(--mc-fg, #fff); box-shadow: 0 0 0 1.5px rgba(0,0,0,0.5); }\n.mc-stop-page { width: 11px; height: 11px; box-shadow: 0 0 0 2.5px var(--mc-accent, #007AFF), 0 0 0 4px rgba(0,0,0,0.35); }\n.mc-progress-fill {\n position: absolute; left: 12px; top: 50%;\n height: var(--mc-track-height, 3px);\n width: calc((100% - 24px) * var(--mc-fill, 0));\n background: var(--mc-accent, #007AFF);\n transform: translateY(-50%);\n transition: height 120ms ease;\n pointer-events: none;\n}\n.mc-progress:hover .mc-progress-fill,\n.mc-progress.mc-scrubbing .mc-progress-fill { height: calc(var(--mc-track-height, 3px) + 2px); }\n.mc-progress-thumb {\n position: absolute; top: 50%;\n left: calc(12px + (100% - 24px) * var(--mc-fill, 0));\n width: 12px; height: 12px; border-radius: 50%;\n background: var(--mc-accent, #007AFF);\n transform: translate(-50%, -50%) scale(0);\n transition: transform 120ms ease;\n pointer-events: none;\n}\n.mc-progress:hover .mc-progress-thumb,\n.mc-progress.mc-scrubbing .mc-progress-thumb { transform: translate(-50%, -50%) scale(1); }\n\n.mc-bar {\n position: relative;\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 6px 12px 8px 12px;\n background: var(--mc-bar-bg, linear-gradient(to top, rgba(0,0,0,0.75) 0%, rgba(0,0,0,0) 100%));\n color: var(--mc-fg, #fff);\n line-height: 1;\n}\n.mc-btn {\n background: none; border: 0; padding: 0; margin: 0;\n width: 28px; height: 28px;\n display: inline-flex; align-items: center; justify-content: center;\n color: var(--mc-fg, #fff); opacity: 0.85; cursor: pointer;\n transition: opacity 120ms ease;\n}\n.mc-btn:hover { opacity: 1; }\n.mc-btn:disabled { opacity: 0.4; cursor: not-allowed; }\n.mc-time {\n font-size: 12px; font-family: Menlo, Monaco, monospace;\n color: var(--mc-fg, #fff); opacity: 0.85;\n min-width: 90px;\n}\n.mc-spacer { flex: 1; }\n\n.mc-volume { display: flex; align-items: center; }\n.mc-vol-slider {\n position: relative;\n width: 0;\n height: 28px;\n display: flex;\n align-items: center;\n cursor: pointer;\n margin-left: 0;\n outline: none;\n transition: width 200ms ease, margin-left 200ms ease;\n overflow: hidden;\n}\n.mc-volume:hover .mc-vol-slider,\n.mc-volume:focus-within .mc-vol-slider,\n.mc-vol-slider.mc-scrubbing {\n width: 70px;\n margin-left: 6px;\n}\n.mc-vol-slider:focus-visible { outline: 2px solid var(--mc-accent, #007AFF); outline-offset: 2px; }\n.mc-vol-slider::before {\n content: \"\"; position: absolute; left: 4px; right: 4px;\n height: 3px; background: var(--mc-track, rgba(255,255,255,0.25)); border-radius: 2px;\n}\n.mc-vol-fill {\n position: absolute; left: 4px; top: 50%;\n height: 3px;\n width: calc((100% - 8px) * var(--mc-volume, 1));\n background: var(--mc-fg, #fff);\n transform: translateY(-50%);\n border-radius: 2px;\n pointer-events: none;\n}\n.mc-vol-thumb {\n position: absolute; top: 50%;\n left: calc(4px + (100% - 8px) * var(--mc-volume, 1));\n width: 10px; height: 10px; border-radius: 50%;\n background: var(--mc-fg, #fff);\n transform: translate(-50%, -50%);\n pointer-events: none;\n}\n\n[data-mc-wrap]:fullscreen {\n width: 100vw; height: 100vh;\n display: flex; align-items: center; justify-content: center;\n background: #000;\n}\n[data-mc-wrap]:fullscreen > canvas {\n width: 100% !important;\n height: 100% !important;\n max-width: none !important;\n max-height: none !important;\n object-fit: contain;\n border-radius: 0;\n}\n\n.mc-settings-popover {\n position: absolute;\n bottom: calc(100% + 8px);\n right: 12px;\n background: rgba(20, 20, 20, 0.96);\n border-radius: 8px;\n padding: 10px 12px;\n min-width: 180px;\n color: #fff;\n font-size: 12px;\n box-shadow: 0 4px 16px rgba(0,0,0,0.4);\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n.mc-settings-popover[data-open=\"false\"] { display: none; }\n.mc-settings-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n}\n.mc-settings-row > span { color: rgba(255,255,255,0.85); }\n.mc-settings-row > select {\n font: inherit;\n background: rgba(255,255,255,0.1);\n color: #fff;\n border: 1px solid rgba(255,255,255,0.18);\n border-radius: 4px;\n padding: 2px 6px;\n cursor: pointer;\n}\n.mc-export-confirm {\n margin-top: 4px;\n width: 100%;\n background: var(--mc-accent, #007AFF);\n color: #fff;\n border: 0;\n border-radius: 4px;\n padding: 6px 10px;\n font: inherit;\n font-weight: 600;\n cursor: pointer;\n transition: background 120ms ease;\n}\n.mc-export-confirm:hover { background: #0066d6; }\n.mc-export-confirm:disabled { background: rgba(255,255,255,0.18); cursor: not-allowed; }\n\n.mc-export-overlay {\n position: absolute; inset: 0;\n background: rgba(0,0,0,0.7);\n display: flex; justify-content: center; align-items: center;\n z-index: 2;\n}\n.mc-export-panel {\n background: rgba(20,20,20,0.96); color: #fff;\n border-radius: 8px; padding: 16px 20px;\n text-align: center; min-width: 200px; max-width: 80%;\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif;\n}\n.mc-export-title { font-size: 13px; margin-bottom: 10px; opacity: 0.9; }\n.mc-export-track {\n width: 100%; height: 6px;\n background: rgba(255,255,255,0.15); border-radius: 3px;\n margin-bottom: 8px; overflow: hidden;\n}\n.mc-export-fill {\n height: 100%; width: 0%;\n background: linear-gradient(90deg, var(--mc-accent, #007AFF), var(--mc-accent, #0056CC));\n transition: width 0.3s ease; border-radius: 3px;\n}\n.mc-export-text {\n color: rgba(255,255,255,0.7); font-size: 11px;\n font-family: Menlo, Monaco, monospace;\n}\n";
|
package/dist/Controller.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { j as Movie } from './Movie-DVeDTdnc.js';
|
|
2
2
|
import 'pixi.js';
|
|
3
3
|
import 'gsap';
|
|
4
|
-
import './types-
|
|
4
|
+
import './types-DSGwN5w1.js';
|
|
5
5
|
|
|
6
6
|
/** The bar's default stylesheet. Its colours and sizes are CSS custom properties (`--mc-accent`, `--mc-fg`, `--mc-track`, `--mc-bar-bg`, `--mc-track-height`, `--mc-font`): set them in your page CSS, or with the `theme` option. */
|
|
7
7
|
declare const CONTROLLER_CSS = "\n.movie-controller-wrap { position: relative; display: inline-block; line-height: 0; }\n.movie-controller {\n position: absolute;\n pointer-events: none;\n font-family: var(--mc-font, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif);\n display: flex;\n flex-direction: column;\n justify-content: flex-end;\n opacity: 1;\n transition: opacity 200ms ease;\n}\n.movie-controller[data-state=\"hidden\"] { opacity: 0; }\n.movie-controller > * { pointer-events: auto; }\n\n.mc-progress {\n position: relative;\n width: 100%;\n height: 16px;\n cursor: pointer;\n display: flex;\n align-items: center;\n outline: none;\n}\n.mc-progress:focus-visible {\n outline: 2px solid var(--mc-accent, #007AFF);\n outline-offset: 2px;\n}\n.mc-progress::before {\n content: \"\"; position: absolute; left: 12px; right: 12px;\n height: var(--mc-track-height, 3px); background: var(--mc-track, rgba(255,255,255,0.25));\n transition: height 120ms ease;\n}\n.mc-progress:hover::before, .mc-progress.mc-scrubbing::before { height: calc(var(--mc-track-height, 3px) + 2px); }\n.mc-stops { position: absolute; left: 12px; right: 12px; top: 0; bottom: 0; pointer-events: none; }\n.mc-stop-tick { position: absolute; top: 50%; width: 7px; height: 7px; transform: translate(-50%, -50%); border-radius: 50%; background: var(--mc-fg, #fff); box-shadow: 0 0 0 1.5px rgba(0,0,0,0.5); }\n.mc-stop-page { width: 11px; height: 11px; box-shadow: 0 0 0 2.5px var(--mc-accent, #007AFF), 0 0 0 4px rgba(0,0,0,0.35); }\n.mc-progress-fill {\n position: absolute; left: 12px; top: 50%;\n height: var(--mc-track-height, 3px);\n width: calc((100% - 24px) * var(--mc-fill, 0));\n background: var(--mc-accent, #007AFF);\n transform: translateY(-50%);\n transition: height 120ms ease;\n pointer-events: none;\n}\n.mc-progress:hover .mc-progress-fill,\n.mc-progress.mc-scrubbing .mc-progress-fill { height: calc(var(--mc-track-height, 3px) + 2px); }\n.mc-progress-thumb {\n position: absolute; top: 50%;\n left: calc(12px + (100% - 24px) * var(--mc-fill, 0));\n width: 12px; height: 12px; border-radius: 50%;\n background: var(--mc-accent, #007AFF);\n transform: translate(-50%, -50%) scale(0);\n transition: transform 120ms ease;\n pointer-events: none;\n}\n.mc-progress:hover .mc-progress-thumb,\n.mc-progress.mc-scrubbing .mc-progress-thumb { transform: translate(-50%, -50%) scale(1); }\n\n.mc-bar {\n position: relative;\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 6px 12px 8px 12px;\n background: var(--mc-bar-bg, linear-gradient(to top, rgba(0,0,0,0.75) 0%, rgba(0,0,0,0) 100%));\n color: var(--mc-fg, #fff);\n line-height: 1;\n}\n.mc-btn {\n background: none; border: 0; padding: 0; margin: 0;\n width: 28px; height: 28px;\n display: inline-flex; align-items: center; justify-content: center;\n color: var(--mc-fg, #fff); opacity: 0.85; cursor: pointer;\n transition: opacity 120ms ease;\n}\n.mc-btn:hover { opacity: 1; }\n.mc-btn:disabled { opacity: 0.4; cursor: not-allowed; }\n.mc-time {\n font-size: 12px; font-family: Menlo, Monaco, monospace;\n color: var(--mc-fg, #fff); opacity: 0.85;\n min-width: 90px;\n}\n.mc-spacer { flex: 1; }\n\n.mc-volume { display: flex; align-items: center; }\n.mc-vol-slider {\n position: relative;\n width: 0;\n height: 28px;\n display: flex;\n align-items: center;\n cursor: pointer;\n margin-left: 0;\n outline: none;\n transition: width 200ms ease, margin-left 200ms ease;\n overflow: hidden;\n}\n.mc-volume:hover .mc-vol-slider,\n.mc-volume:focus-within .mc-vol-slider,\n.mc-vol-slider.mc-scrubbing {\n width: 70px;\n margin-left: 6px;\n}\n.mc-vol-slider:focus-visible { outline: 2px solid var(--mc-accent, #007AFF); outline-offset: 2px; }\n.mc-vol-slider::before {\n content: \"\"; position: absolute; left: 4px; right: 4px;\n height: 3px; background: var(--mc-track, rgba(255,255,255,0.25)); border-radius: 2px;\n}\n.mc-vol-fill {\n position: absolute; left: 4px; top: 50%;\n height: 3px;\n width: calc((100% - 8px) * var(--mc-volume, 1));\n background: var(--mc-fg, #fff);\n transform: translateY(-50%);\n border-radius: 2px;\n pointer-events: none;\n}\n.mc-vol-thumb {\n position: absolute; top: 50%;\n left: calc(4px + (100% - 8px) * var(--mc-volume, 1));\n width: 10px; height: 10px; border-radius: 50%;\n background: var(--mc-fg, #fff);\n transform: translate(-50%, -50%);\n pointer-events: none;\n}\n\n[data-mc-wrap]:fullscreen {\n width: 100vw; height: 100vh;\n display: flex; align-items: center; justify-content: center;\n background: #000;\n}\n[data-mc-wrap]:fullscreen > canvas {\n width: 100% !important;\n height: 100% !important;\n max-width: none !important;\n max-height: none !important;\n object-fit: contain;\n border-radius: 0;\n}\n\n.mc-settings-popover {\n position: absolute;\n bottom: calc(100% + 8px);\n right: 12px;\n background: rgba(20, 20, 20, 0.96);\n border-radius: 8px;\n padding: 10px 12px;\n min-width: 180px;\n color: #fff;\n font-size: 12px;\n box-shadow: 0 4px 16px rgba(0,0,0,0.4);\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n.mc-settings-popover[data-open=\"false\"] { display: none; }\n.mc-settings-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n}\n.mc-settings-row > span { color: rgba(255,255,255,0.85); }\n.mc-settings-row > select {\n font: inherit;\n background: rgba(255,255,255,0.1);\n color: #fff;\n border: 1px solid rgba(255,255,255,0.18);\n border-radius: 4px;\n padding: 2px 6px;\n cursor: pointer;\n}\n.mc-export-confirm {\n margin-top: 4px;\n width: 100%;\n background: var(--mc-accent, #007AFF);\n color: #fff;\n border: 0;\n border-radius: 4px;\n padding: 6px 10px;\n font: inherit;\n font-weight: 600;\n cursor: pointer;\n transition: background 120ms ease;\n}\n.mc-export-confirm:hover { background: #0066d6; }\n.mc-export-confirm:disabled { background: rgba(255,255,255,0.18); cursor: not-allowed; }\n\n.mc-export-overlay {\n position: absolute; inset: 0;\n background: rgba(0,0,0,0.7);\n display: flex; justify-content: center; align-items: center;\n z-index: 2;\n}\n.mc-export-panel {\n background: rgba(20,20,20,0.96); color: #fff;\n border-radius: 8px; padding: 16px 20px;\n text-align: center; min-width: 200px; max-width: 80%;\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif;\n}\n.mc-export-title { font-size: 13px; margin-bottom: 10px; opacity: 0.9; }\n.mc-export-track {\n width: 100%; height: 6px;\n background: rgba(255,255,255,0.15); border-radius: 3px;\n margin-bottom: 8px; overflow: hidden;\n}\n.mc-export-fill {\n height: 100%; width: 0%;\n background: linear-gradient(90deg, var(--mc-accent, #007AFF), var(--mc-accent, #0056CC));\n transition: width 0.3s ease; border-radius: 3px;\n}\n.mc-export-text {\n color: rgba(255,255,255,0.7); font-size: 11px;\n font-family: Menlo, Monaco, monospace;\n}\n";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Application } from 'pixi.js';
|
|
2
2
|
import { gsap } from 'gsap';
|
|
3
|
-
import { _ as Stop, e as AssetSpec, c as CompositionSpec } from './types-
|
|
3
|
+
import { _ as Stop, e as AssetSpec, c as CompositionSpec } from './types-DSGwN5w1.js';
|
|
4
4
|
|
|
5
5
|
interface AudioInspectOptions {
|
|
6
6
|
/** Width of each loudness window in seconds. Default 0.1. */
|
|
@@ -217,10 +217,26 @@ interface TimelineSvgOptions {
|
|
|
217
217
|
labels?: boolean;
|
|
218
218
|
}
|
|
219
219
|
|
|
220
|
+
/**
|
|
221
|
+
* How far `movie.init()` is: one number from 0 to 1 for a movie that takes seconds to build, the stage it is in and how many of that stage are done.
|
|
222
|
+
* The stages run in a fixed order and carry a share of the whole (the shares come from timings of the reel and the gallery); a stage that is never
|
|
223
|
+
* begun (no assets, no sound) counts as done when a later one starts, and the number never goes down.
|
|
224
|
+
*/
|
|
225
|
+
type LoadStage = 'assets' | 'build' | 'sound' | 'frames';
|
|
226
|
+
interface LoadProgressState {
|
|
227
|
+
stage: LoadStage;
|
|
228
|
+
/** Items of this stage that are done, and how many there are (layers, assets, sounds). */
|
|
229
|
+
loaded: number;
|
|
230
|
+
total: number;
|
|
231
|
+
/** The whole init, 0 to 1. */
|
|
232
|
+
progress: number;
|
|
233
|
+
}
|
|
234
|
+
|
|
220
235
|
/**
|
|
221
236
|
* The page's loader (see `src/loader.css`): a `.pe-loader` element that is on screen before any script runs. `movie.init()` finds it,
|
|
222
237
|
* fades it out when the movie is ready and, if init fails, stops it and says so.
|
|
223
238
|
*/
|
|
239
|
+
|
|
224
240
|
type LoaderOption = HTMLElement | string | false;
|
|
225
241
|
/** The loader element for a canvas: the one named (element or selector), none (`false`), or a `.pe-loader` near the canvas (its box or a few levels up). */
|
|
226
242
|
declare function resolveLoader(option: LoaderOption | undefined, canvas: HTMLCanvasElement | null): HTMLElement | null;
|
|
@@ -440,11 +456,18 @@ declare class Movie {
|
|
|
440
456
|
on(event: 'volumechange', fn: (e: VolumeEvent) => void): this;
|
|
441
457
|
on(event: 'error', fn: (e: MovieErrorEvent) => void): this;
|
|
442
458
|
on(event: 'progress', fn: (e: ProgressEvent) => void): this;
|
|
459
|
+
/** How far `init()` is while it builds the movie: `{ stage: 'assets' | 'build' | 'sound' | 'frames', loaded, total, progress }`, `progress` 0 to 1. The page's `.pe-loader` shows it by itself. */
|
|
460
|
+
on(event: 'loadprogress', fn: (e: LoadProgressState) => void): this;
|
|
443
461
|
on(event: 'play', fn: () => void): this;
|
|
444
462
|
on(event: 'pause', fn: () => void): this;
|
|
445
463
|
off(event: string, fn: Listener): this;
|
|
446
464
|
emit(event: string, ...args: unknown[]): void;
|
|
447
465
|
get isReady(): boolean;
|
|
466
|
+
/** Milliseconds `init()` spent in each stage (assets, build, sound, frames), or null before it has finished: `check` says which stage is the slow one. */
|
|
467
|
+
get loadStages(): Record<LoadStage, number> | null;
|
|
468
|
+
private _loadStages;
|
|
469
|
+
/** True while the movie has sound but the browser keeps it silent until the viewer taps (iOS Safari, a page nobody has touched): a player can show "tap for sound"; the sound starts at a tap or key press that arrives while play() is pending, or at the next play() made from a tap. */
|
|
470
|
+
get audioBlocked(): boolean;
|
|
448
471
|
/** The poster time as a frame number, or null. */
|
|
449
472
|
get posterFrame(): number | null;
|
|
450
473
|
/** Draw the poster frame on the canvas and leave the playhead where it is (frame 0): the picture before the movie plays. */
|
|
@@ -648,4 +671,4 @@ declare class Movie {
|
|
|
648
671
|
destroy(): Promise<void>;
|
|
649
672
|
}
|
|
650
673
|
|
|
651
|
-
export { type AudioInspectOptions as A, type ContactSheetOptions as C, type FontReport as F, type InspectOptions as I, type LayerInfo as L, type MotionBlurOptions as M, type OnionSkinOptions as O, type ProgressEvent as P, type RenderOptions as R, type SnapshotOptions as S, type TimelineData as T, type VolumeEvent as V, type AudioReport as a, type AudioSourceReport as b, type FrameEvent as c, type InspectReport as d, type IssueGroup as e, type
|
|
674
|
+
export { type AudioInspectOptions as A, type ContactSheetOptions as C, type FontReport as F, type InspectOptions as I, type LayerInfo as L, type MotionBlurOptions as M, type OnionSkinOptions as O, type ProgressEvent as P, type RenderOptions as R, type SnapshotOptions as S, type TimelineData as T, type VolumeEvent as V, type AudioReport as a, type AudioSourceReport as b, type FrameEvent as c, type InspectReport as d, type IssueGroup as e, type LoadProgressState as f, type LoadStage as g, type LoaderOption as h, type MotionBlurSpec as i, Movie as j, type MovieErrorEvent as k, type MovieOptions as l, type ReviewOptions as m, type ReviewReport as n, type SoundMeasure as o, type StopEvent as p, type StopImage as q, type StopImagesOptions as r, type TimelineHtmlOptions as s, type TimelineRow as t, type TimelineSvgOptions as u, type TimelineTransition as v, dismissLoader as w, failLoader as x, resolveLoader as y };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Application } from 'pixi.js';
|
|
2
2
|
import { gsap } from 'gsap';
|
|
3
|
-
import { _ as Stop, e as AssetSpec, c as CompositionSpec } from './types-
|
|
3
|
+
import { _ as Stop, e as AssetSpec, c as CompositionSpec } from './types-DSGwN5w1.cjs';
|
|
4
4
|
|
|
5
5
|
interface AudioInspectOptions {
|
|
6
6
|
/** Width of each loudness window in seconds. Default 0.1. */
|
|
@@ -217,10 +217,26 @@ interface TimelineSvgOptions {
|
|
|
217
217
|
labels?: boolean;
|
|
218
218
|
}
|
|
219
219
|
|
|
220
|
+
/**
|
|
221
|
+
* How far `movie.init()` is: one number from 0 to 1 for a movie that takes seconds to build, the stage it is in and how many of that stage are done.
|
|
222
|
+
* The stages run in a fixed order and carry a share of the whole (the shares come from timings of the reel and the gallery); a stage that is never
|
|
223
|
+
* begun (no assets, no sound) counts as done when a later one starts, and the number never goes down.
|
|
224
|
+
*/
|
|
225
|
+
type LoadStage = 'assets' | 'build' | 'sound' | 'frames';
|
|
226
|
+
interface LoadProgressState {
|
|
227
|
+
stage: LoadStage;
|
|
228
|
+
/** Items of this stage that are done, and how many there are (layers, assets, sounds). */
|
|
229
|
+
loaded: number;
|
|
230
|
+
total: number;
|
|
231
|
+
/** The whole init, 0 to 1. */
|
|
232
|
+
progress: number;
|
|
233
|
+
}
|
|
234
|
+
|
|
220
235
|
/**
|
|
221
236
|
* The page's loader (see `src/loader.css`): a `.pe-loader` element that is on screen before any script runs. `movie.init()` finds it,
|
|
222
237
|
* fades it out when the movie is ready and, if init fails, stops it and says so.
|
|
223
238
|
*/
|
|
239
|
+
|
|
224
240
|
type LoaderOption = HTMLElement | string | false;
|
|
225
241
|
/** The loader element for a canvas: the one named (element or selector), none (`false`), or a `.pe-loader` near the canvas (its box or a few levels up). */
|
|
226
242
|
declare function resolveLoader(option: LoaderOption | undefined, canvas: HTMLCanvasElement | null): HTMLElement | null;
|
|
@@ -440,11 +456,18 @@ declare class Movie {
|
|
|
440
456
|
on(event: 'volumechange', fn: (e: VolumeEvent) => void): this;
|
|
441
457
|
on(event: 'error', fn: (e: MovieErrorEvent) => void): this;
|
|
442
458
|
on(event: 'progress', fn: (e: ProgressEvent) => void): this;
|
|
459
|
+
/** How far `init()` is while it builds the movie: `{ stage: 'assets' | 'build' | 'sound' | 'frames', loaded, total, progress }`, `progress` 0 to 1. The page's `.pe-loader` shows it by itself. */
|
|
460
|
+
on(event: 'loadprogress', fn: (e: LoadProgressState) => void): this;
|
|
443
461
|
on(event: 'play', fn: () => void): this;
|
|
444
462
|
on(event: 'pause', fn: () => void): this;
|
|
445
463
|
off(event: string, fn: Listener): this;
|
|
446
464
|
emit(event: string, ...args: unknown[]): void;
|
|
447
465
|
get isReady(): boolean;
|
|
466
|
+
/** Milliseconds `init()` spent in each stage (assets, build, sound, frames), or null before it has finished: `check` says which stage is the slow one. */
|
|
467
|
+
get loadStages(): Record<LoadStage, number> | null;
|
|
468
|
+
private _loadStages;
|
|
469
|
+
/** True while the movie has sound but the browser keeps it silent until the viewer taps (iOS Safari, a page nobody has touched): a player can show "tap for sound"; the sound starts at a tap or key press that arrives while play() is pending, or at the next play() made from a tap. */
|
|
470
|
+
get audioBlocked(): boolean;
|
|
448
471
|
/** The poster time as a frame number, or null. */
|
|
449
472
|
get posterFrame(): number | null;
|
|
450
473
|
/** Draw the poster frame on the canvas and leave the playhead where it is (frame 0): the picture before the movie plays. */
|
|
@@ -648,4 +671,4 @@ declare class Movie {
|
|
|
648
671
|
destroy(): Promise<void>;
|
|
649
672
|
}
|
|
650
673
|
|
|
651
|
-
export { type AudioInspectOptions as A, type ContactSheetOptions as C, type FontReport as F, type InspectOptions as I, type LayerInfo as L, type MotionBlurOptions as M, type OnionSkinOptions as O, type ProgressEvent as P, type RenderOptions as R, type SnapshotOptions as S, type TimelineData as T, type VolumeEvent as V, type AudioReport as a, type AudioSourceReport as b, type FrameEvent as c, type InspectReport as d, type IssueGroup as e, type
|
|
674
|
+
export { type AudioInspectOptions as A, type ContactSheetOptions as C, type FontReport as F, type InspectOptions as I, type LayerInfo as L, type MotionBlurOptions as M, type OnionSkinOptions as O, type ProgressEvent as P, type RenderOptions as R, type SnapshotOptions as S, type TimelineData as T, type VolumeEvent as V, type AudioReport as a, type AudioSourceReport as b, type FrameEvent as c, type InspectReport as d, type IssueGroup as e, type LoadProgressState as f, type LoadStage as g, type LoaderOption as h, type MotionBlurSpec as i, Movie as j, type MovieErrorEvent as k, type MovieOptions as l, type ReviewOptions as m, type ReviewReport as n, type SoundMeasure as o, type StopEvent as p, type StopImage as q, type StopImagesOptions as r, type TimelineHtmlOptions as s, type TimelineRow as t, type TimelineSvgOptions as u, type TimelineTransition as v, dismissLoader as w, failLoader as x, resolveLoader as y };
|
package/dist/Presenter.d.cts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { j as Movie } from './Movie-i05Oh9mJ.cjs';
|
|
2
2
|
import 'pixi.js';
|
|
3
3
|
import 'gsap';
|
|
4
|
-
import './types-
|
|
4
|
+
import './types-DSGwN5w1.cjs';
|
|
5
5
|
|
|
6
6
|
interface PresenterOptions {
|
|
7
7
|
canvas: HTMLCanvasElement;
|
package/dist/Presenter.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { j as Movie } from './Movie-DVeDTdnc.js';
|
|
2
2
|
import 'pixi.js';
|
|
3
3
|
import 'gsap';
|
|
4
|
-
import './types-
|
|
4
|
+
import './types-DSGwN5w1.js';
|
|
5
5
|
|
|
6
6
|
interface PresenterOptions {
|
|
7
7
|
canvas: HTMLCanvasElement;
|
|
@@ -4365,7 +4365,7 @@ function resolveDim(v, scope) {
|
|
|
4365
4365
|
var _CompositionSequence = null;
|
|
4366
4366
|
async function getCompositionSequence() {
|
|
4367
4367
|
if (!_CompositionSequence) {
|
|
4368
|
-
_CompositionSequence = (await import('./Composition-
|
|
4368
|
+
_CompositionSequence = (await import('./Composition-THQPG4E5.cjs')).CompositionSequence;
|
|
4369
4369
|
}
|
|
4370
4370
|
return _CompositionSequence;
|
|
4371
4371
|
}
|
|
@@ -4393,10 +4393,12 @@ async function buildSequenceTree(specs, parent, root) {
|
|
|
4393
4393
|
}
|
|
4394
4394
|
if (!Cls) {
|
|
4395
4395
|
console.warn(`pixi-effects: unknown sequence type "${spec.type}"`);
|
|
4396
|
+
await root.onLayerBuilt?.();
|
|
4396
4397
|
continue;
|
|
4397
4398
|
}
|
|
4398
4399
|
const seq = new Cls(spec, parent, root);
|
|
4399
4400
|
await seq.build();
|
|
4401
|
+
await root.onLayerBuilt?.();
|
|
4400
4402
|
if (seq.duration === void 0) {
|
|
4401
4403
|
seq.duration = parent?.duration ?? root.duration;
|
|
4402
4404
|
}
|
|
@@ -4448,5 +4450,5 @@ exports.registerSequenceType = registerSequenceType;
|
|
|
4448
4450
|
exports.remapOf = remapOf;
|
|
4449
4451
|
exports.resolveAt = resolveAt;
|
|
4450
4452
|
exports.timeRemapOf = timeRemapOf;
|
|
4451
|
-
//# sourceMappingURL=chunk-
|
|
4452
|
-
//# sourceMappingURL=chunk-
|
|
4453
|
+
//# sourceMappingURL=chunk-EABL3UKJ.cjs.map
|
|
4454
|
+
//# sourceMappingURL=chunk-EABL3UKJ.cjs.map
|