versioncam 0.5.0 → 0.5.2
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 +45 -0
- package/README.md +48 -3
- package/dist/cli/commands/doctor.js +14 -11
- package/dist/cli/commands/doctor.js.map +1 -1
- package/dist/cli/commands/install.d.ts +45 -6
- package/dist/cli/commands/install.js +78 -18
- package/dist/cli/commands/install.js.map +1 -1
- package/dist/cli/commands/login.js +5 -0
- package/dist/cli/commands/login.js.map +1 -1
- package/dist/cli/commands/preview.js +6 -0
- package/dist/cli/commands/preview.js.map +1 -1
- package/dist/cli/usage.d.ts +1 -1
- package/dist/cli/usage.js +2 -1
- package/dist/cli/usage.js.map +1 -1
- package/dist/core/compose.d.ts +26 -1
- package/dist/core/compose.js +39 -0
- package/dist/core/compose.js.map +1 -1
- package/dist/driver/page-hooks.d.ts +7 -5
- package/dist/driver/page-hooks.js +38 -6
- package/dist/driver/page-hooks.js.map +1 -1
- package/dist/driver/settle.js +12 -7
- package/dist/driver/settle.js.map +1 -1
- package/dist/page/assets/index-BtUbUEdN.js +2 -0
- package/dist/page/index.html +1 -1
- package/package.json +4 -3
- package/plugin/.claude-plugin/plugin.json +7 -1
- package/plugin/README.md +11 -0
- package/dist/page/assets/index-fZtacWyp.js +0 -2
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,51 @@ and versions follow [semantic versioning](https://semver.org/). While the
|
|
|
6
6
|
major version is 0, a minor version may change the config or the clip DSL,
|
|
7
7
|
and its entry here says how.
|
|
8
8
|
|
|
9
|
+
## 0.5.2 (2026-10-29)
|
|
10
|
+
|
|
11
|
+
A patch version: the config and the clip DSL are as they were. The README
|
|
12
|
+
now speaks to coding agents, the skill installs from the repository's own
|
|
13
|
+
marketplace, and the browser install is a third of what it was.
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
|
|
17
|
+
- The README has a section for coding agents: when versioncam fits, the exact
|
|
18
|
+
commands, and what to tell the person about the licence. Bugs and
|
|
19
|
+
questions go to github.com/versioncam/versioncam, where the source is
|
|
20
|
+
public since 2026-10-28.
|
|
21
|
+
- The skill installs as a Claude Code plugin from that repository's own
|
|
22
|
+
marketplace, `/plugin install versioncam --marketplace versioncam/versioncam`,
|
|
23
|
+
beside `versioncam init` and `--plugin-dir`; the plugin's manifest carries
|
|
24
|
+
the package's version.
|
|
25
|
+
- `versioncam install` fetches the headless shell alone, the Chromium that
|
|
26
|
+
records: 190 MB on a Mac, where both builds were 528. `login` and `preview`,
|
|
27
|
+
the two commands that open a window, fetch the full browser the first time
|
|
28
|
+
they run and say so first; `versioncam install --full` fetches both at once.
|
|
29
|
+
`doctor` reports the shell as `chromium`, and under `window` whether the
|
|
30
|
+
full browser is there, which is never a failure.
|
|
31
|
+
|
|
32
|
+
## 0.5.1 (2026-10-11)
|
|
33
|
+
|
|
34
|
+
A patch version: two clips that came out wrong now record and render as
|
|
35
|
+
they should. The config and the clip DSL are as they were.
|
|
36
|
+
|
|
37
|
+
### Fixed
|
|
38
|
+
|
|
39
|
+
- A settle whose page did not change, a menu already open or a form already
|
|
40
|
+
filled, no longer dims the whole picture almost to black for a tenth of a
|
|
41
|
+
second after it. The renderer dissolved from the page into itself, drawing
|
|
42
|
+
it see-through over the background. Where two settles come within the
|
|
43
|
+
dissolve of each other, the dissolve now follows the later one, rather than
|
|
44
|
+
fading the new page in, a little back out, and in again.
|
|
45
|
+
- A CSS animation that has run its course now finishes, so its `finished`
|
|
46
|
+
promise resolves, as in a browser nobody is filming. Components that
|
|
47
|
+
unmount once their exit animation has finished, Base UI's Dialog, Popover,
|
|
48
|
+
Menu and Tooltip among them, stayed mounted after they closed: a closed
|
|
49
|
+
dialog's invisible backdrop stayed over the page and took the next click,
|
|
50
|
+
so a link seemed to do nothing and the step after it failed with
|
|
51
|
+
`Nothing matched`. A clip that finished the animations itself to get past
|
|
52
|
+
this no longer needs to.
|
|
53
|
+
|
|
9
54
|
## 0.5.0 (2026-10-07)
|
|
10
55
|
|
|
11
56
|
A minor version: commands for version.cam, and the skill learns to use them.
|
package/README.md
CHANGED
|
@@ -43,6 +43,12 @@ Playwright exactly, because a new Chromium changes every captured pixel. Your
|
|
|
43
43
|
app may have a Playwright of its own at another version, and
|
|
44
44
|
`npx playwright install` fetches that one's Chromium.
|
|
45
45
|
|
|
46
|
+
`versioncam install` fetches the headless shell, the Chromium that records:
|
|
47
|
+
190 MB on a Mac. `login` and `preview` open a window, which needs the full
|
|
48
|
+
browser, 340 MB more; they fetch it the first time they run, or
|
|
49
|
+
`npx versioncam install --full` fetches both now, for a machine that will be
|
|
50
|
+
offline then.
|
|
51
|
+
|
|
46
52
|
### In CI
|
|
47
53
|
|
|
48
54
|
The same install, then `check`. It records every clip and fails the job on the
|
|
@@ -66,6 +72,40 @@ job in the Playwright image for the version versioncam pins,
|
|
|
66
72
|
`mcr.microsoft.com/playwright:v1.59.1-noble`. Chromium and its libraries are
|
|
67
73
|
already installed there.
|
|
68
74
|
|
|
75
|
+
## If you are a coding agent
|
|
76
|
+
|
|
77
|
+
You were probably asked for a product demo, a README or landing-page video, a
|
|
78
|
+
GIF, or docs screenshots that stay current. versioncam fits when the subject
|
|
79
|
+
is a web app that starts from a command and whose controls can be reached by
|
|
80
|
+
test id, role, label or placeholder, on Node 22 or later, on macOS or Linux.
|
|
81
|
+
It does not fit native mobile apps, terminal demos (VHS does those),
|
|
82
|
+
interactive HTML tours, narrated screencasts, or a UI that will never change
|
|
83
|
+
again.
|
|
84
|
+
|
|
85
|
+
Install it in the directory that holds the app's `package.json`, with exactly
|
|
86
|
+
these commands:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
npm i -D versioncam
|
|
90
|
+
npx versioncam install
|
|
91
|
+
npx versioncam init
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Use `versioncam install`, never `npx playwright install`: versioncam pins its
|
|
95
|
+
Playwright exactly, and the app's own Playwright, if it has one, fetches a
|
|
96
|
+
different Chromium. Then `npx versioncam doctor`, and read `npx versioncam dsl`
|
|
97
|
+
before writing a clip. If your host runs skills,
|
|
98
|
+
`/versioncam "<what the clip should show>"` does the rest, including setting
|
|
99
|
+
the repository up on its first run; otherwise `init --into <dir>` puts the
|
|
100
|
+
skill where your host reads it, and the clips are TypeScript you can write
|
|
101
|
+
from `inspect` and `measure`. Target by test id or role, never by coordinates.
|
|
102
|
+
Do not edit files while a clip records. Add `.versioncam/` to `.gitignore`
|
|
103
|
+
and commit the clip. In CI, `uses: versioncam/action@v1` after `npm ci`
|
|
104
|
+
records every clip and fails the job, with a comment on the pull request,
|
|
105
|
+
when one no longer matches the app. Tell the person what the licence is
|
|
106
|
+
(FSL-1.1-ALv2: free in their own CI, source-available, Apache 2.0 after two
|
|
107
|
+
years) and that the hosted layer at version.cam is optional and separate.
|
|
108
|
+
|
|
69
109
|
## Configure
|
|
70
110
|
|
|
71
111
|
`versioncam.config.ts` sits beside your clips. It is the one place your app
|
|
@@ -290,8 +330,9 @@ prints the same list.
|
|
|
290
330
|
Once per machine, and once per repository.
|
|
291
331
|
|
|
292
332
|
```
|
|
293
|
-
versioncam install the Chromium this version of versioncam records with
|
|
333
|
+
versioncam install the headless Chromium this version of versioncam records with
|
|
294
334
|
--with-deps and its system libraries (Linux CI)
|
|
335
|
+
--full and the full browser, which login and preview open
|
|
295
336
|
versioncam init install the authoring skill into .claude/
|
|
296
337
|
--into <dir> into <dir>/versioncam/ instead, for another host
|
|
297
338
|
--force overwrite files that are already there
|
|
@@ -801,7 +842,10 @@ its CSS motion:
|
|
|
801
842
|
- **Animations are scrubbed** by the clip's time: a spinner turns at the rate
|
|
802
843
|
the video plays, whatever the machine is doing. The scrub counts from the
|
|
803
844
|
clip's start, not the animation's, so a one-off animation that starts
|
|
804
|
-
partway through a clip shows its end.
|
|
845
|
+
partway through a clip shows its end. Once a one-off animation's time is
|
|
846
|
+
up it finishes, as in a browser nobody is filming: its `finished` promise
|
|
847
|
+
resolves and `animationend` fires, so a dialog that unmounts after its exit
|
|
848
|
+
animation goes, and does not sit invisibly over the page.
|
|
805
849
|
- **Not on the clock:** animations a script makes with `element.animate()`,
|
|
806
850
|
Chromium's smooth scrolling of a mouse wheel, and transitions inside an
|
|
807
851
|
iframe, which stay zeroed since nothing drives an iframe's clock. A clip
|
|
@@ -901,7 +945,8 @@ The words these pages use in a particular sense.
|
|
|
901
945
|
## Help
|
|
902
946
|
|
|
903
947
|
Bugs, questions, and apps it cannot record go to the issue tracker at
|
|
904
|
-
[github.com/versioncam/versioncam
|
|
948
|
+
[github.com/versioncam/versioncam](https://github.com/versioncam/versioncam/issues),
|
|
949
|
+
where the source is.
|
|
905
950
|
A clip that will not reproduce is easiest to help with alongside the two
|
|
906
951
|
pictures `versioncam stability` saves.
|
|
907
952
|
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { existsSync } from "node:fs";
|
|
1
|
+
import { browserPaths } from "./install.js";
|
|
3
2
|
import { ffmpegStatus } from "../../render/ffmpeg.js";
|
|
4
3
|
import { ensureApp, probe } from "../../app-server.js";
|
|
5
4
|
import { NO_CONFIG, findConfigFile, loadConfig, loadSceneIds, } from "../../loader.js";
|
|
@@ -112,19 +111,23 @@ export async function doctor() {
|
|
|
112
111
|
detail: `v${process.versions.node}`,
|
|
113
112
|
fix: "Install Node 22 or newer.",
|
|
114
113
|
});
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
catch {
|
|
120
|
-
chromiumPath = "";
|
|
121
|
-
}
|
|
114
|
+
// The headless shell is what records; `install` fetches it and nothing
|
|
115
|
+
// else. The full browser only `login` and `preview` need, and they fetch it
|
|
116
|
+
// themselves, so its absence is reported and is not a failure.
|
|
117
|
+
const browsers = browserPaths();
|
|
122
118
|
checks.push({
|
|
123
119
|
name: "chromium",
|
|
124
|
-
ok:
|
|
125
|
-
detail:
|
|
120
|
+
ok: browsers.shell !== null,
|
|
121
|
+
detail: browsers.shell ?? "not installed",
|
|
126
122
|
fix: "npx versioncam install",
|
|
127
123
|
});
|
|
124
|
+
checks.push({
|
|
125
|
+
name: "window",
|
|
126
|
+
ok: true,
|
|
127
|
+
detail: browsers.full
|
|
128
|
+
? `full Chromium at ${browsers.full}`
|
|
129
|
+
: "full Chromium not fetched yet; login and preview fetch it on first use, or `npx versioncam install --full`",
|
|
130
|
+
});
|
|
128
131
|
const { ffmpeg, ffprobe } = ffmpegStatus();
|
|
129
132
|
checks.push({
|
|
130
133
|
name: "ffmpeg",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"doctor.js","sourceRoot":"","sources":["../../../src/cli/commands/doctor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"doctor.js","sourceRoot":"","sources":["../../../src/cli/commands/doctor.ts"],"names":[],"mappings":"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"}
|
|
@@ -17,17 +17,56 @@ export declare function pinnedPlaywright(): {
|
|
|
17
17
|
/**
|
|
18
18
|
* The command `versioncam install` runs, as argv.
|
|
19
19
|
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
20
|
+
* The headless shell alone, unless asked for more. It is the Chromium that
|
|
21
|
+
* records: Playwright launches it for every headless run, and every recorder
|
|
22
|
+
* command but two is headless. It is also 190 MB where the full browser is
|
|
23
|
+
* 340 MB more, and a stranger's first ten minutes with the package were
|
|
24
|
+
* measured on 2026-10-28: on a slow line the two builds together took nine of
|
|
25
|
+
* them. `login` and `preview` open a window, which the shell cannot, so they
|
|
26
|
+
* fetch the full browser the first time they run; `--full` fetches both now,
|
|
27
|
+
* for a machine that will be offline then.
|
|
23
28
|
*/
|
|
24
|
-
export declare function installArgv(node: string, cli: string,
|
|
29
|
+
export declare function installArgv(node: string, cli: string, options?: {
|
|
30
|
+
withDeps?: boolean;
|
|
31
|
+
full?: boolean;
|
|
32
|
+
}): string[];
|
|
33
|
+
/** The command that fetches the full browser alone, the shell being there. */
|
|
34
|
+
export declare function fullBrowserArgv(node: string, cli: string): string[];
|
|
35
|
+
/**
|
|
36
|
+
* Where the two Chromium builds this version uses are, or null for one that
|
|
37
|
+
* is not there.
|
|
38
|
+
*
|
|
39
|
+
* Asked of Playwright's own registry, which knows the build numbers and
|
|
40
|
+
* honours PLAYWRIGHT_BROWSERS_PATH; the public API names only the full
|
|
41
|
+
* browser's path. `lib/server` is not public API, which the exact pin on
|
|
42
|
+
* Playwright makes bearable: an upgrade is an event, and the tests ask the
|
|
43
|
+
* registry this question too.
|
|
44
|
+
*/
|
|
45
|
+
export declare function browserPaths(): {
|
|
46
|
+
shell: string | null;
|
|
47
|
+
full: string | null;
|
|
48
|
+
};
|
|
49
|
+
/** Playwright's installer, with its output on this terminal; its exit code. */
|
|
50
|
+
export declare function runInstaller(command: string, args: string[]): Promise<number>;
|
|
51
|
+
/** What `ensureFullBrowser` touches, so a test can stand in for all of it. */
|
|
52
|
+
export type InstallerIO = {
|
|
53
|
+
has: () => boolean;
|
|
54
|
+
run: (command: string, args: string[]) => Promise<number>;
|
|
55
|
+
write: (text: string) => void;
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* Before a command opens a window: the full browser, fetched once if `install`
|
|
59
|
+
* left it out. It says what it is doing first, since a download of a third of
|
|
60
|
+
* a gigabyte with no word on the terminal looks like a hang.
|
|
61
|
+
*/
|
|
62
|
+
export declare function ensureFullBrowser(io?: InstallerIO): Promise<boolean>;
|
|
25
63
|
/**
|
|
26
64
|
* Install the Chromium this version of versioncam records with.
|
|
27
65
|
*
|
|
28
66
|
* `--with-deps` adds the system libraries Chromium needs on a bare Linux
|
|
29
67
|
* image, which is the usual state of a CI runner and asks for root there.
|
|
30
|
-
*
|
|
31
|
-
*
|
|
68
|
+
* `--full` adds the browser `login` and `preview` open. The command it runs
|
|
69
|
+
* is printed first: in a CI log, which Playwright installed which browser is
|
|
70
|
+
* the first question when something differs.
|
|
32
71
|
*/
|
|
33
72
|
export declare function install(argv: string[]): Promise<number>;
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
2
3
|
import { createRequire } from "node:module";
|
|
3
4
|
import { dirname, join } from "node:path";
|
|
4
5
|
import { parseArgs } from "node:util";
|
|
6
|
+
import { registry } from "playwright-core/lib/server";
|
|
5
7
|
/**
|
|
6
8
|
* The Playwright this package drives: its version, and the CLI that ships
|
|
7
9
|
* with it.
|
|
@@ -23,44 +25,102 @@ export function pinnedPlaywright() {
|
|
|
23
25
|
/**
|
|
24
26
|
* The command `versioncam install` runs, as argv.
|
|
25
27
|
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
28
|
+
* The headless shell alone, unless asked for more. It is the Chromium that
|
|
29
|
+
* records: Playwright launches it for every headless run, and every recorder
|
|
30
|
+
* command but two is headless. It is also 190 MB where the full browser is
|
|
31
|
+
* 340 MB more, and a stranger's first ten minutes with the package were
|
|
32
|
+
* measured on 2026-10-28: on a slow line the two builds together took nine of
|
|
33
|
+
* them. `login` and `preview` open a window, which the shell cannot, so they
|
|
34
|
+
* fetch the full browser the first time they run; `--full` fetches both now,
|
|
35
|
+
* for a machine that will be offline then.
|
|
29
36
|
*/
|
|
30
|
-
export function installArgv(node, cli,
|
|
37
|
+
export function installArgv(node, cli, options = {}) {
|
|
31
38
|
return [
|
|
32
39
|
node,
|
|
33
40
|
cli,
|
|
34
41
|
"install",
|
|
35
|
-
...(withDeps ? ["--with-deps"] : []),
|
|
42
|
+
...(options.withDeps ? ["--with-deps"] : []),
|
|
43
|
+
...(options.full ? [] : ["--only-shell"]),
|
|
36
44
|
"chromium",
|
|
37
45
|
];
|
|
38
46
|
}
|
|
47
|
+
/** The command that fetches the full browser alone, the shell being there. */
|
|
48
|
+
export function fullBrowserArgv(node, cli) {
|
|
49
|
+
return [node, cli, "install", "--no-shell", "chromium"];
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Where the two Chromium builds this version uses are, or null for one that
|
|
53
|
+
* is not there.
|
|
54
|
+
*
|
|
55
|
+
* Asked of Playwright's own registry, which knows the build numbers and
|
|
56
|
+
* honours PLAYWRIGHT_BROWSERS_PATH; the public API names only the full
|
|
57
|
+
* browser's path. `lib/server` is not public API, which the exact pin on
|
|
58
|
+
* Playwright makes bearable: an upgrade is an event, and the tests ask the
|
|
59
|
+
* registry this question too.
|
|
60
|
+
*/
|
|
61
|
+
export function browserPaths() {
|
|
62
|
+
const where = (name) => {
|
|
63
|
+
const found = registry.findExecutable(name)?.executablePath();
|
|
64
|
+
return found && existsSync(found) ? found : null;
|
|
65
|
+
};
|
|
66
|
+
return { shell: where("chromium-headless-shell"), full: where("chromium") };
|
|
67
|
+
}
|
|
68
|
+
/** Playwright's installer, with its output on this terminal; its exit code. */
|
|
69
|
+
export function runInstaller(command, args) {
|
|
70
|
+
return new Promise((resolve) => {
|
|
71
|
+
const child = spawn(command, args, { stdio: "inherit" });
|
|
72
|
+
child.on("error", (error) => {
|
|
73
|
+
process.stderr.write(`Could not run the installer: ${error.message}\n`);
|
|
74
|
+
resolve(1);
|
|
75
|
+
});
|
|
76
|
+
child.on("exit", (code) => resolve(code ?? 1));
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Before a command opens a window: the full browser, fetched once if `install`
|
|
81
|
+
* left it out. It says what it is doing first, since a download of a third of
|
|
82
|
+
* a gigabyte with no word on the terminal looks like a hang.
|
|
83
|
+
*/
|
|
84
|
+
export async function ensureFullBrowser(io = {
|
|
85
|
+
has: () => browserPaths().full !== null,
|
|
86
|
+
run: runInstaller,
|
|
87
|
+
write: (text) => process.stdout.write(text),
|
|
88
|
+
}) {
|
|
89
|
+
if (io.has())
|
|
90
|
+
return true;
|
|
91
|
+
const playwright = pinnedPlaywright();
|
|
92
|
+
const [command, ...args] = fullBrowserArgv(process.execPath, playwright.cli);
|
|
93
|
+
io.write("This opens a window, which needs the full Chromium; `versioncam install` " +
|
|
94
|
+
"fetched only the headless shell that records.\n" +
|
|
95
|
+
`Fetching it once, for Playwright ${playwright.version}:\n` +
|
|
96
|
+
` ${[command, ...args].join(" ")}\n`);
|
|
97
|
+
return (await io.run(command, args)) === 0;
|
|
98
|
+
}
|
|
39
99
|
/**
|
|
40
100
|
* Install the Chromium this version of versioncam records with.
|
|
41
101
|
*
|
|
42
102
|
* `--with-deps` adds the system libraries Chromium needs on a bare Linux
|
|
43
103
|
* image, which is the usual state of a CI runner and asks for root there.
|
|
44
|
-
*
|
|
45
|
-
*
|
|
104
|
+
* `--full` adds the browser `login` and `preview` open. The command it runs
|
|
105
|
+
* is printed first: in a CI log, which Playwright installed which browser is
|
|
106
|
+
* the first question when something differs.
|
|
46
107
|
*/
|
|
47
108
|
export async function install(argv) {
|
|
48
109
|
const { values } = parseArgs({
|
|
49
110
|
args: argv,
|
|
50
|
-
options: { "with-deps": { type: "boolean" } },
|
|
111
|
+
options: { "with-deps": { type: "boolean" }, full: { type: "boolean" } },
|
|
51
112
|
strict: false,
|
|
52
113
|
});
|
|
53
114
|
const playwright = pinnedPlaywright();
|
|
54
|
-
const
|
|
55
|
-
process.
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
const child = spawn(command, args, { stdio: "inherit" });
|
|
59
|
-
child.on("error", (error) => {
|
|
60
|
-
process.stderr.write(`Could not run the installer: ${error.message}\n`);
|
|
61
|
-
resolve(1);
|
|
62
|
-
});
|
|
63
|
-
child.on("exit", (code) => resolve(code ?? 1));
|
|
115
|
+
const full = values.full === true;
|
|
116
|
+
const [command, ...args] = installArgv(process.execPath, playwright.cli, {
|
|
117
|
+
withDeps: values["with-deps"] === true,
|
|
118
|
+
full,
|
|
64
119
|
});
|
|
120
|
+
process.stdout.write((full
|
|
121
|
+
? `Both Chromium builds for Playwright ${playwright.version}: the headless shell versioncam records with, and the browser login and preview open:\n`
|
|
122
|
+
: `The headless Chromium versioncam records with, for Playwright ${playwright.version}:\n`) +
|
|
123
|
+
` ${[command, ...args].join(" ")}\n`);
|
|
124
|
+
return runInstaller(command, args);
|
|
65
125
|
}
|
|
66
126
|
//# sourceMappingURL=install.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"install.js","sourceRoot":"","sources":["../../../src/cli/commands/install.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAC3C,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"install.js","sourceRoot":"","sources":["../../../src/cli/commands/install.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { launchBrowser } from "../../driver/launch.js";
|
|
2
|
+
import { ensureFullBrowser } from "./install.js";
|
|
2
3
|
import { mkdir } from "node:fs/promises";
|
|
3
4
|
import { dirname } from "node:path";
|
|
4
5
|
import { loadConfig } from "../../loader.js";
|
|
@@ -21,6 +22,10 @@ export async function login() {
|
|
|
21
22
|
return 1;
|
|
22
23
|
}
|
|
23
24
|
const storageState = auth.storageState;
|
|
25
|
+
// A window needs the full browser, which `install` leaves to the first
|
|
26
|
+
// command that opens one; fetched before the app is started, not under it.
|
|
27
|
+
if (!(await ensureFullBrowser()))
|
|
28
|
+
return 1;
|
|
24
29
|
return drivingApp(config, async () => {
|
|
25
30
|
const browser = await launchBrowser({ headless: false });
|
|
26
31
|
try {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"login.js","sourceRoot":"","sources":["../../../src/cli/commands/login.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AACvD,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAEvC;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,KAAK;IACzB,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;IAC/C,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC;IAEzB,IAAI,CAAC,IAAI,EAAE,YAAY,EAAE,CAAC;QACxB,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,2EAA2E;YACzE,wEAAwE,CAC3E,CAAC;QACF,OAAO,CAAC,CAAC;IACX,CAAC;IAED,MAAM,YAAY,GAAG,IAAI,CAAC,YAAY,CAAC;IACvC,OAAO,UAAU,CAAC,MAAM,EAAE,KAAK,IAAI,EAAE;QACnC,MAAM,OAAO,GAAG,MAAM,aAAa,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAC;QACzD,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,UAAU,CAAC,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC;YACxE,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,OAAO,EAAE,CAAC;YACrC,MAAM,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;YAEhC,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,cAAc,MAAM,CAAC,OAAO,+BAA+B;gBACzD,sCAAsC,CACzC,CAAC;YACF,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE;gBAClC,OAAO,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC;gBACvB,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE;oBAC9B,OAAO,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;oBACtB,OAAO,EAAE,CAAC;gBACZ,CAAC,CAAC,CAAC;YACL,CAAC,CAAC,CAAC;YAEH,MAAM,KAAK,CAAC,OAAO,CAAC,YAAY,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;YACxD,MAAM,OAAO,CAAC,YAAY,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,CAAC,CAAC;QACrD,CAAC;gBAAS,CAAC;YACT,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;QACxB,CAAC;QAED,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,0BAA0B,YAAY,IAAI,CAAC,CAAC;QACjE,OAAO,CAAC,CAAC;IACX,CAAC,CAAC,CAAC;AACL,CAAC"}
|
|
1
|
+
{"version":3,"file":"login.js","sourceRoot":"","sources":["../../../src/cli/commands/login.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AACvD,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAEvC;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,KAAK;IACzB,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;IAC/C,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC;IAEzB,IAAI,CAAC,IAAI,EAAE,YAAY,EAAE,CAAC;QACxB,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,2EAA2E;YACzE,wEAAwE,CAC3E,CAAC;QACF,OAAO,CAAC,CAAC;IACX,CAAC;IAED,MAAM,YAAY,GAAG,IAAI,CAAC,YAAY,CAAC;IACvC,uEAAuE;IACvE,2EAA2E;IAC3E,IAAI,CAAC,CAAC,MAAM,iBAAiB,EAAE,CAAC;QAAE,OAAO,CAAC,CAAC;IAC3C,OAAO,UAAU,CAAC,MAAM,EAAE,KAAK,IAAI,EAAE;QACnC,MAAM,OAAO,GAAG,MAAM,aAAa,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAC;QACzD,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,UAAU,CAAC,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC;YACxE,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,OAAO,EAAE,CAAC;YACrC,MAAM,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;YAEhC,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,cAAc,MAAM,CAAC,OAAO,+BAA+B;gBACzD,sCAAsC,CACzC,CAAC;YACF,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE;gBAClC,OAAO,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC;gBACvB,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE;oBAC9B,OAAO,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;oBACtB,OAAO,EAAE,CAAC;gBACZ,CAAC,CAAC,CAAC;YACL,CAAC,CAAC,CAAC;YAEH,MAAM,KAAK,CAAC,OAAO,CAAC,YAAY,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;YACxD,MAAM,OAAO,CAAC,YAAY,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,CAAC,CAAC;QACrD,CAAC;gBAAS,CAAC;YACT,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;QACxB,CAAC;QAED,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,0BAA0B,YAAY,IAAI,CAAC,CAAC;QACjE,OAAO,CAAC,CAAC;IACX,CAAC,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -2,6 +2,7 @@ import { parseArgs } from "node:util";
|
|
|
2
2
|
import { existsSync } from "node:fs";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { launchBrowser } from "../../driver/launch.js";
|
|
5
|
+
import { ensureFullBrowser } from "./install.js";
|
|
5
6
|
import { loadConfig } from "../../loader.js";
|
|
6
7
|
import { renderConfigOf } from "../../config.js";
|
|
7
8
|
import { bundleScenes, loadSceneIds } from "../../loader.js";
|
|
@@ -41,6 +42,11 @@ export async function preview(argv) {
|
|
|
41
42
|
return 1;
|
|
42
43
|
}
|
|
43
44
|
}
|
|
45
|
+
// A window needs the full browser, which `install` leaves to the first
|
|
46
|
+
// command that opens one; after the checks above, so nothing is fetched
|
|
47
|
+
// for a recording that is not there.
|
|
48
|
+
if (!(await ensureFullBrowser()))
|
|
49
|
+
return 1;
|
|
44
50
|
const browser = await launchBrowser({ headless: false });
|
|
45
51
|
const context = await browser.newContext({
|
|
46
52
|
viewport: { width: 1400, height: 1000 },
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"preview.js","sourceRoot":"","sources":["../../../src/cli/commands/preview.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"preview.js","sourceRoot":"","sources":["../../../src/cli/commands/preview.ts"],"names":[],"mappings":"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"}
|
package/dist/cli/usage.d.ts
CHANGED
|
@@ -13,4 +13,4 @@
|
|
|
13
13
|
* is what comes after publishing: `versioncam cloud`, whose own usage is that
|
|
14
14
|
* group. The last group is not a stage but a way to run any of them.
|
|
15
15
|
*/
|
|
16
|
-
export declare const USAGE = "versioncam: record and render demo clips of a real app\n\nSet up\n versioncam install the Chromium this version of versioncam records with\n --with-deps and its system libraries (Linux CI)\n versioncam init install the authoring skill into .claude/\n --into <dir> into <dir>/versioncam/ instead, for another host\n --force overwrite files that are already there\n versioncam doctor check this machine can record, and that the app starts\n versioncam login sign in once and save the browser session\n\nWrite\n versioncam inspect <path> what a clip can click, and where each name came from\n --json machine-readable\n versioncam measure <path> <sel> where a selector's matches are, and what they say\n versioncam dsl the clip DSL: every method, its options and defaults\n\nRecord\n versioncam record [id\u2026] drive the app and capture frames\n --draft 15fps at CSS scale: cheap, for writing clips\n versioncam check [id\u2026] record every clip; exit 1 if any breaks\n --report <file> and write each clip's result there, as JSON\n versioncam stability [id\u2026] record each clip twice the same way; stable or not\n --runs <n> how many times (default 2)\n --full at full quality rather than draft\n\nReview\n versioncam sheet <id> an eight-frame contact sheet\n --at-marks sample the beats instead of even spacing\n versioncam frame <id> <n> one frame at output size, to a png\n --t <seconds> pick the frame by time instead\n --at <label> or by the beat it shows\n versioncam review <id> the checks that need no eyes; PASS/FAIL, exit 0/1\n --beats <file> [{ label, expect }] the clip promised\n --min <s> --max <s> duration bounds (default 9 and 11)\n versioncam preview <id> open the scrubber in a real browser\n --scene <id> preview a scene instead\n --frame <n> start at this frame\n\nOutput\n versioncam render [id\u2026] composite and encode recordings and scenes\n no ids: every one, stale recordings included\n --scene <id> and this scene, whatever the ids say (repeatable)\n --sequence stitch the results into one piece\n --report <file> and write what it made there, as JSON\n\nPublish\n versioncam publish [id\u2026] send the recordings and their checks to version.cam\n --dry-run print what would be sent, and send nothing\n --report <file> and write the living URLs there, as JSON\n\nManage\n versioncam cloud login sign in to version.cam, with a code you approve in a browser\n --label <name> what the approval page calls this machine\n versioncam cloud logout sign out: revoke the token, and delete it\n versioncam cloud status [project] each clip, current or broken, and what plays meanwhile\n versioncam cloud failure <clip> why a clip broke, and its picture, into .versioncam/failures/\n versioncam cloud ack <clip> \"I know, keep the old one\": the reminders stop\n versioncam cloud embed <clip> the snippets that embed a clip, and its files\n versioncam cloud set <name> <val> change a setting, once you approve it in a browser:\n visibility public|unlisted|private, retention <days>|off,\n viewers on|off, alerts email|slack <address>|off,\n alerts issues on|off\n versioncam cloud plan [account] the plan, and the pages to upgrade and pay on\n --project <owner/repo> which project, when not this repository's\n --json machine-readable, for status, failure, embed and plan\n\nFrom another directory\n versioncam <command> --cwd <dir> as if run in <dir>, which holds versioncam.config.ts\n versioncam --version which versioncam this is\n\nRun from a directory containing versioncam.config.ts, or name it with --cwd.\n";
|
|
16
|
+
export declare const USAGE = "versioncam: record and render demo clips of a real app\n\nSet up\n versioncam install the headless Chromium this version of versioncam records with\n --with-deps and its system libraries (Linux CI)\n --full and the full browser, which login and preview open\n versioncam init install the authoring skill into .claude/\n --into <dir> into <dir>/versioncam/ instead, for another host\n --force overwrite files that are already there\n versioncam doctor check this machine can record, and that the app starts\n versioncam login sign in once and save the browser session\n\nWrite\n versioncam inspect <path> what a clip can click, and where each name came from\n --json machine-readable\n versioncam measure <path> <sel> where a selector's matches are, and what they say\n versioncam dsl the clip DSL: every method, its options and defaults\n\nRecord\n versioncam record [id\u2026] drive the app and capture frames\n --draft 15fps at CSS scale: cheap, for writing clips\n versioncam check [id\u2026] record every clip; exit 1 if any breaks\n --report <file> and write each clip's result there, as JSON\n versioncam stability [id\u2026] record each clip twice the same way; stable or not\n --runs <n> how many times (default 2)\n --full at full quality rather than draft\n\nReview\n versioncam sheet <id> an eight-frame contact sheet\n --at-marks sample the beats instead of even spacing\n versioncam frame <id> <n> one frame at output size, to a png\n --t <seconds> pick the frame by time instead\n --at <label> or by the beat it shows\n versioncam review <id> the checks that need no eyes; PASS/FAIL, exit 0/1\n --beats <file> [{ label, expect }] the clip promised\n --min <s> --max <s> duration bounds (default 9 and 11)\n versioncam preview <id> open the scrubber in a real browser\n --scene <id> preview a scene instead\n --frame <n> start at this frame\n\nOutput\n versioncam render [id\u2026] composite and encode recordings and scenes\n no ids: every one, stale recordings included\n --scene <id> and this scene, whatever the ids say (repeatable)\n --sequence stitch the results into one piece\n --report <file> and write what it made there, as JSON\n\nPublish\n versioncam publish [id\u2026] send the recordings and their checks to version.cam\n --dry-run print what would be sent, and send nothing\n --report <file> and write the living URLs there, as JSON\n\nManage\n versioncam cloud login sign in to version.cam, with a code you approve in a browser\n --label <name> what the approval page calls this machine\n versioncam cloud logout sign out: revoke the token, and delete it\n versioncam cloud status [project] each clip, current or broken, and what plays meanwhile\n versioncam cloud failure <clip> why a clip broke, and its picture, into .versioncam/failures/\n versioncam cloud ack <clip> \"I know, keep the old one\": the reminders stop\n versioncam cloud embed <clip> the snippets that embed a clip, and its files\n versioncam cloud set <name> <val> change a setting, once you approve it in a browser:\n visibility public|unlisted|private, retention <days>|off,\n viewers on|off, alerts email|slack <address>|off,\n alerts issues on|off\n versioncam cloud plan [account] the plan, and the pages to upgrade and pay on\n --project <owner/repo> which project, when not this repository's\n --json machine-readable, for status, failure, embed and plan\n\nFrom another directory\n versioncam <command> --cwd <dir> as if run in <dir>, which holds versioncam.config.ts\n versioncam --version which versioncam this is\n\nRun from a directory containing versioncam.config.ts, or name it with --cwd.\n";
|
package/dist/cli/usage.js
CHANGED
|
@@ -16,8 +16,9 @@
|
|
|
16
16
|
export const USAGE = `versioncam: record and render demo clips of a real app
|
|
17
17
|
|
|
18
18
|
Set up
|
|
19
|
-
versioncam install the Chromium this version of versioncam records with
|
|
19
|
+
versioncam install the headless Chromium this version of versioncam records with
|
|
20
20
|
--with-deps and its system libraries (Linux CI)
|
|
21
|
+
--full and the full browser, which login and preview open
|
|
21
22
|
versioncam init install the authoring skill into .claude/
|
|
22
23
|
--into <dir> into <dir>/versioncam/ instead, for another host
|
|
23
24
|
--force overwrite files that are already there
|
package/dist/cli/usage.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"usage.js","sourceRoot":"","sources":["../../src/cli/usage.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,KAAK,GAAG
|
|
1
|
+
{"version":3,"file":"usage.js","sourceRoot":"","sources":["../../src/cli/usage.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,KAAK,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAyEpB,CAAC"}
|
package/dist/core/compose.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Rect, Vec2 } from "./timeline.js";
|
|
1
|
+
import type { Rect, Timeline, Vec2 } from "./timeline.js";
|
|
2
2
|
/**
|
|
3
3
|
* Mapping from the captured page into the output frame.
|
|
4
4
|
*
|
|
@@ -34,5 +34,30 @@ export declare function fitFrameArea(canvas: {
|
|
|
34
34
|
* a cut never shows a duplicated still.
|
|
35
35
|
*/
|
|
36
36
|
export declare function crossfadeAlpha(frame: number, cutFrame: number, length: number): number;
|
|
37
|
+
/**
|
|
38
|
+
* Which state to show at `frame`, and what to crossfade it with.
|
|
39
|
+
*
|
|
40
|
+
* Only a settle produces a crossfade. Everywhere else a change between
|
|
41
|
+
* consecutive frames is a real one-frame change and must stay crisp —
|
|
42
|
+
* blurring those would smear the cursor's own motion.
|
|
43
|
+
*
|
|
44
|
+
* A settle whose page did not change (a menu already open, a form already
|
|
45
|
+
* filled) has the same state on both sides of its cut, and that is no
|
|
46
|
+
* crossfade at all. Reporting it as one had the renderer skip the previous
|
|
47
|
+
* state, which equals the current, and draw the current at the crossfade's
|
|
48
|
+
* alpha over the backdrop: about a sixth on the frame after the cut, a dip
|
|
49
|
+
* almost to black and back over six frames (0.5.0, found recording Vpogled's
|
|
50
|
+
* clips).
|
|
51
|
+
*
|
|
52
|
+
* Where two settles' windows overlap, the frame follows the later one, whose
|
|
53
|
+
* change it is showing. Following the earlier ran its dissolve to the end and
|
|
54
|
+
* then the later one's from part way, so the new state faded in, back out a
|
|
55
|
+
* little, and in again.
|
|
56
|
+
*/
|
|
57
|
+
export declare function stateForFrame(timeline: Pick<Timeline, "cuts" | "frames">, frame: number, crossfadeFrames: number): {
|
|
58
|
+
current: number;
|
|
59
|
+
previous: number | null;
|
|
60
|
+
alpha: number;
|
|
61
|
+
};
|
|
37
62
|
/** Linear fade in/out envelope, in seconds, for captions and highlights. */
|
|
38
63
|
export declare function fadeEnvelope(t: number, t0: number, t1: number, fade: number): number;
|
package/dist/core/compose.js
CHANGED
|
@@ -70,6 +70,45 @@ export function crossfadeAlpha(frame, cutFrame, length) {
|
|
|
70
70
|
return 1;
|
|
71
71
|
return clamp01((frame - cutFrame + 1) / length);
|
|
72
72
|
}
|
|
73
|
+
/**
|
|
74
|
+
* Which state to show at `frame`, and what to crossfade it with.
|
|
75
|
+
*
|
|
76
|
+
* Only a settle produces a crossfade. Everywhere else a change between
|
|
77
|
+
* consecutive frames is a real one-frame change and must stay crisp —
|
|
78
|
+
* blurring those would smear the cursor's own motion.
|
|
79
|
+
*
|
|
80
|
+
* A settle whose page did not change (a menu already open, a form already
|
|
81
|
+
* filled) has the same state on both sides of its cut, and that is no
|
|
82
|
+
* crossfade at all. Reporting it as one had the renderer skip the previous
|
|
83
|
+
* state, which equals the current, and draw the current at the crossfade's
|
|
84
|
+
* alpha over the backdrop: about a sixth on the frame after the cut, a dip
|
|
85
|
+
* almost to black and back over six frames (0.5.0, found recording Vpogled's
|
|
86
|
+
* clips).
|
|
87
|
+
*
|
|
88
|
+
* Where two settles' windows overlap, the frame follows the later one, whose
|
|
89
|
+
* change it is showing. Following the earlier ran its dissolve to the end and
|
|
90
|
+
* then the later one's from part way, so the new state faded in, back out a
|
|
91
|
+
* little, and in again.
|
|
92
|
+
*/
|
|
93
|
+
export function stateForFrame(timeline, frame, crossfadeFrames) {
|
|
94
|
+
const current = timeline.frames[frame];
|
|
95
|
+
let from = null;
|
|
96
|
+
for (const cut of timeline.cuts) {
|
|
97
|
+
if (frame > cut &&
|
|
98
|
+
frame <= cut + crossfadeFrames &&
|
|
99
|
+
timeline.frames[cut] !== current &&
|
|
100
|
+
(from === null || cut > from)) {
|
|
101
|
+
from = cut;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
if (from === null)
|
|
105
|
+
return { current, previous: null, alpha: 1 };
|
|
106
|
+
return {
|
|
107
|
+
current,
|
|
108
|
+
previous: timeline.frames[from],
|
|
109
|
+
alpha: crossfadeAlpha(frame, from + 1, crossfadeFrames),
|
|
110
|
+
};
|
|
111
|
+
}
|
|
73
112
|
/** Linear fade in/out envelope, in seconds, for captions and highlights. */
|
|
74
113
|
export function fadeEnvelope(t, t0, t1, fade) {
|
|
75
114
|
if (t <= t0 || t >= t1)
|
package/dist/core/compose.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compose.js","sourceRoot":"","sources":["../../src/core/compose.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAGtC;;;;;;;;;;;;GAYG;AAEH,MAAM,UAAU,cAAc,CAC5B,KAAW,EACX,MAAY,EACZ,SAAe;IAEf,MAAM,KAAK,GAAG,SAAS,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC;IACrC,OAAO;QACL,SAAS,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,GAAG,KAAK;QAC3C,SAAS,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,GAAG,KAAK;KAC5C,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,IAAU,EAAE,MAAY,EAAE,SAAe;IACpE,MAAM,KAAK,GAAG,SAAS,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC;IACrC,OAAO;QACL,CAAC,EAAE,SAAS,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,GAAG,KAAK;QAC5C,CAAC,EAAE,SAAS,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,GAAG,KAAK;QAC5C,CAAC,EAAE,IAAI,CAAC,CAAC,GAAG,KAAK;QACjB,CAAC,EAAE,IAAI,CAAC,CAAC,GAAG,KAAK;KAClB,CAAC;AACJ,CAAC;AAED,0EAA0E;AAC1E,MAAM,UAAU,mBAAmB,CAAC,MAAY,EAAE,GAAW;IAC3D,OAAO;QACL,CAAC,EAAE,MAAM,CAAC,CAAC,GAAG,GAAG;QACjB,CAAC,EAAE,MAAM,CAAC,CAAC,GAAG,GAAG;QACjB,CAAC,EAAE,MAAM,CAAC,CAAC,GAAG,GAAG;QACjB,CAAC,EAAE,MAAM,CAAC,CAAC,GAAG,GAAG;KAClB,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,YAAY,CAC1B,MAAyC,EACzC,QAA2C,EAC3C,OAAe;IAEf,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC;IAC9D,MAAM,UAAU,GAAG,MAAM,CAAC,KAAK,GAAG,KAAK,GAAG,CAAC,CAAC;IAC5C,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,GAAG,KAAK,GAAG,CAAC,CAAC;IAC7C,MAAM,MAAM,GAAG,QAAQ,CAAC,KAAK,GAAG,QAAQ,CAAC,MAAM,CAAC;IAEhD,IAAI,CAAC,GAAG,UAAU,CAAC;IACnB,IAAI,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC;IACnB,IAAI,CAAC,GAAG,UAAU,EAAE,CAAC;QACnB,CAAC,GAAG,UAAU,CAAC;QACf,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC;IACjB,CAAC;IACD,OAAO;QACL,CAAC,EAAE,CAAC,MAAM,CAAC,KAAK,GAAG,CAAC,CAAC,GAAG,CAAC;QACzB,CAAC,EAAE,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,GAAG,CAAC;QAC1B,CAAC;QACD,CAAC;KACF,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAC5B,KAAa,EACb,QAAgB,EAChB,MAAc;IAEd,IAAI,MAAM,IAAI,CAAC;QAAE,OAAO,CAAC,CAAC;IAC1B,OAAO,OAAO,CAAC,CAAC,KAAK,GAAG,QAAQ,GAAG,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC;AAClD,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,YAAY,CAC1B,CAAS,EACT,EAAU,EACV,EAAU,EACV,IAAY;IAEZ,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE;QAAE,OAAO,CAAC,CAAC;IACjC,IAAI,IAAI,IAAI,CAAC;QAAE,OAAO,CAAC,CAAC;IACxB,OAAO,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,GAAG,IAAI,CAAC,EAAE,OAAO,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC;AACtE,CAAC"}
|
|
1
|
+
{"version":3,"file":"compose.js","sourceRoot":"","sources":["../../src/core/compose.ts"],"names":[],"mappings":"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"}
|
|
@@ -10,7 +10,7 @@ import type { Transitions } from "../config.js";
|
|
|
10
10
|
* from "the page has finished". Counting is far cheaper than diffing and
|
|
11
11
|
* needs no cooperation from the app.
|
|
12
12
|
*/
|
|
13
|
-
export declare const MUTATION_COUNTER_SCRIPT = "(() => {\n const state = {\n mutations: 0,\n ids: new WeakMap(),\n nextId: 1,\n idOf(el) {\n if (!el) return 0;\n let id = state.ids.get(el);\n if (!id) { id = state.nextId++; state.ids.set(el, id); }\n return id;\n },\n probe(t, x, y) {\n document.documentElement.style.setProperty(\"--recorder-t\", t);\n // Before the counts: a transition whose time is up is finished here
|
|
13
|
+
export declare const MUTATION_COUNTER_SCRIPT = "(() => {\n const state = {\n mutations: 0,\n ids: new WeakMap(),\n nextId: 1,\n idOf(el) {\n if (!el) return 0;\n let id = state.ids.get(el);\n if (!id) { id = state.nextId++; state.ids.set(el, id); }\n return id;\n },\n // A CSS animation is paused and scrubbed to authored time (freezeCss), and\n // a paused animation never finishes by itself: its `finished` promise\n // never resolves. Components that unmount once their exit animation has\n // finished, Base UI's dialogs, popovers, menus and tooltips among them,\n // waited on it for ever, and a closed dialog's backdrop, scrubbed to\n // opacity 0, stayed over the page and took the next click (0.5.0). So a\n // finite animation whose time is up is finished, as it would be in a\n // browser nobody is filming; one still under way keeps its scrub, and\n // one that repeats for ever never reaches an end. Transitions have their\n // clock (TRANSITION_CLOCK_SCRIPT), and animations a script made with\n // `element.animate()` are the app's own.\n finishEnded() {\n let finished = 0;\n for (const animation of document.getAnimations()) {\n if (!(\"animationName\" in animation) || !animation.effect) continue;\n if (animation.playState === \"finished\") continue;\n const timing = animation.effect.getComputedTiming();\n if (!Number.isFinite(timing.endTime)) continue;\n if (timing.localTime === null || timing.localTime < timing.endTime) continue;\n try {\n animation.finish();\n finished += 1;\n } catch {\n // An animation the page cancelled meanwhile: nothing to finish.\n }\n }\n return finished;\n },\n probe(t, x, y) {\n document.documentElement.style.setProperty(\"--recorder-t\", t);\n // Before the counts: an animation or a transition whose time is up is\n // finished here, and is no longer running when the animations are\n // counted.\n state.finishEnded();\n const clock = window.__recorderTransitions;\n const transitions = clock ? clock.sync() : 0;\n return {\n mutations: state.mutations,\n hovered: state.idOf(document.elementFromPoint(x, y)),\n animations: document.getAnimations().length,\n transitions,\n };\n },\n };\n window.__recorder = state;\n\n // The driver writes --recorder-t to <html style> on every single frame. Left\n // alone that write is a mutation like any other, so the counter would move\n // every frame and anything built on it \u2014 a settle that waits for quiet, a\n // gate that skips an unchanged frame \u2014 would be reading its own footprint.\n // Anything else that touches <html style>, a scroll lock or a theme switch,\n // still counts.\n const withoutScrub = (value) =>\n (value || \"\").replace(/--recorder-t\\s*:[^;]*;?/g, \"\").trim();\n\n const start = () => {\n const root = document.documentElement;\n const isOurScrub = (record) =>\n record.type === \"attributes\" &&\n record.target === root &&\n record.attributeName === \"style\" &&\n withoutScrub(record.oldValue) === withoutScrub(root.getAttribute(\"style\"));\n\n new MutationObserver((records) => {\n for (const record of records) {\n if (!isOurScrub(record)) state.mutations += 1;\n }\n }).observe(root || document, {\n childList: true,\n attributes: true,\n attributeOldValue: true,\n characterData: true,\n subtree: true,\n });\n };\n if (document.documentElement) start();\n else document.addEventListener(\"DOMContentLoaded\", start, { once: true });\n})();";
|
|
14
14
|
/**
|
|
15
15
|
* Plays the page's CSS transitions on the page's own clock.
|
|
16
16
|
*
|
|
@@ -47,11 +47,12 @@ export declare const MUTATION_COUNTER_SCRIPT = "(() => {\n const state = {\n
|
|
|
47
47
|
*/
|
|
48
48
|
export declare const TRANSITION_CLOCK_SCRIPT = "(() => {\n if (window !== window.top) return;\n const started = new WeakMap();\n window.__recorderTransitions = {\n sync() {\n const now = performance.now();\n let running = 0;\n for (const animation of document.getAnimations()) {\n if (!(\"transitionProperty\" in animation)) continue;\n let start = started.get(animation);\n if (start === undefined) {\n start = now;\n started.set(animation, start);\n }\n const end = animation.effect\n ? animation.effect.getComputedTiming().endTime\n : 0;\n if (now - start >= end) {\n animation.finish();\n continue;\n }\n animation.pause();\n animation.currentTime = now - start;\n running += 1;\n }\n return running;\n },\n };\n})();";
|
|
49
49
|
/**
|
|
50
|
-
* Moves every running transition to the page's clock,
|
|
51
|
-
*
|
|
52
|
-
*
|
|
50
|
+
* Moves every running transition to the page's clock, and finishes every CSS
|
|
51
|
+
* animation whose time is up, for the driver's waits that move the clock
|
|
52
|
+
* without making a frame. The frame probe and the settle loop do both as
|
|
53
|
+
* part of what they already ask the page.
|
|
53
54
|
*/
|
|
54
|
-
export declare const SYNC_TRANSITIONS
|
|
55
|
+
export declare const SYNC_TRANSITIONS: string;
|
|
55
56
|
/**
|
|
56
57
|
* Puts the page's CSS motion under the recorder's control.
|
|
57
58
|
*
|
|
@@ -116,6 +117,7 @@ declare global {
|
|
|
116
117
|
interface Window {
|
|
117
118
|
__recorder?: {
|
|
118
119
|
mutations: number;
|
|
120
|
+
finishEnded(): number;
|
|
119
121
|
probe(t: string, x: number, y: number): FrameProbe;
|
|
120
122
|
};
|
|
121
123
|
__recorderTransitions?: {
|
|
@@ -20,10 +20,40 @@ export const MUTATION_COUNTER_SCRIPT = `(() => {
|
|
|
20
20
|
if (!id) { id = state.nextId++; state.ids.set(el, id); }
|
|
21
21
|
return id;
|
|
22
22
|
},
|
|
23
|
+
// A CSS animation is paused and scrubbed to authored time (freezeCss), and
|
|
24
|
+
// a paused animation never finishes by itself: its \`finished\` promise
|
|
25
|
+
// never resolves. Components that unmount once their exit animation has
|
|
26
|
+
// finished, Base UI's dialogs, popovers, menus and tooltips among them,
|
|
27
|
+
// waited on it for ever, and a closed dialog's backdrop, scrubbed to
|
|
28
|
+
// opacity 0, stayed over the page and took the next click (0.5.0). So a
|
|
29
|
+
// finite animation whose time is up is finished, as it would be in a
|
|
30
|
+
// browser nobody is filming; one still under way keeps its scrub, and
|
|
31
|
+
// one that repeats for ever never reaches an end. Transitions have their
|
|
32
|
+
// clock (TRANSITION_CLOCK_SCRIPT), and animations a script made with
|
|
33
|
+
// \`element.animate()\` are the app's own.
|
|
34
|
+
finishEnded() {
|
|
35
|
+
let finished = 0;
|
|
36
|
+
for (const animation of document.getAnimations()) {
|
|
37
|
+
if (!("animationName" in animation) || !animation.effect) continue;
|
|
38
|
+
if (animation.playState === "finished") continue;
|
|
39
|
+
const timing = animation.effect.getComputedTiming();
|
|
40
|
+
if (!Number.isFinite(timing.endTime)) continue;
|
|
41
|
+
if (timing.localTime === null || timing.localTime < timing.endTime) continue;
|
|
42
|
+
try {
|
|
43
|
+
animation.finish();
|
|
44
|
+
finished += 1;
|
|
45
|
+
} catch {
|
|
46
|
+
// An animation the page cancelled meanwhile: nothing to finish.
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return finished;
|
|
50
|
+
},
|
|
23
51
|
probe(t, x, y) {
|
|
24
52
|
document.documentElement.style.setProperty("--recorder-t", t);
|
|
25
|
-
// Before the counts: a transition whose time is up is
|
|
26
|
-
// and is no longer running when the animations are
|
|
53
|
+
// Before the counts: an animation or a transition whose time is up is
|
|
54
|
+
// finished here, and is no longer running when the animations are
|
|
55
|
+
// counted.
|
|
56
|
+
state.finishEnded();
|
|
27
57
|
const clock = window.__recorderTransitions;
|
|
28
58
|
const transitions = clock ? clock.sync() : 0;
|
|
29
59
|
return {
|
|
@@ -132,11 +162,13 @@ export const TRANSITION_CLOCK_SCRIPT = `(() => {
|
|
|
132
162
|
};
|
|
133
163
|
})();`;
|
|
134
164
|
/**
|
|
135
|
-
* Moves every running transition to the page's clock,
|
|
136
|
-
*
|
|
137
|
-
*
|
|
165
|
+
* Moves every running transition to the page's clock, and finishes every CSS
|
|
166
|
+
* animation whose time is up, for the driver's waits that move the clock
|
|
167
|
+
* without making a frame. The frame probe and the settle loop do both as
|
|
168
|
+
* part of what they already ask the page.
|
|
138
169
|
*/
|
|
139
|
-
export const SYNC_TRANSITIONS = "window.
|
|
170
|
+
export const SYNC_TRANSITIONS = "(window.__recorder ? window.__recorder.finishEnded() : 0) + " +
|
|
171
|
+
"(window.__recorderTransitions ? window.__recorderTransitions.sync() : 0)";
|
|
140
172
|
/**
|
|
141
173
|
* Puts the page's CSS motion under the recorder's control.
|
|
142
174
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"page-hooks.js","sourceRoot":"","sources":["../../src/driver/page-hooks.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH;;;;GAIG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG
|
|
1
|
+
{"version":3,"file":"page-hooks.js","sourceRoot":"","sources":["../../src/driver/page-hooks.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH;;;;GAIG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;MAwFjC,CAAC;AAEP;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;MA4BjC,CAAC;AAEP;;;;;GAKG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAC3B,8DAA8D;IAC9D,0EAA0E,CAAC;AAE7E;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,MAAM,UAAU,SAAS,CAAC,eAAwB;IAChD,MAAM,WAAW,GAAG,eAAe;QACjC,CAAC,CAAC,6EAA6E;QAC/E,CAAC,CAAC,EAAE,CAAC;IACP,OAAO;EACP,WAAW;;;;EAIX,CAAC;AACH,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,WAAwB;IACtD,OAAO;iBACQ,WAAW,KAAK,MAAM;uBAChB,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,MAAM,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;;;;;;;;;MAStF,CAAC;AACP,CAAC;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,OAAe,EAAE,CAAS,EAAE,CAAS,EAAU,EAAE,CAC3E,4BAA4B,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC"}
|
package/dist/driver/settle.js
CHANGED
|
@@ -29,13 +29,18 @@ export async function settlePage(page, inFlightRequests, options = {}) {
|
|
|
29
29
|
let lastMutations = -1;
|
|
30
30
|
let lastBlocker = "none";
|
|
31
31
|
for (;;) {
|
|
32
|
-
// The same round trip
|
|
33
|
-
//
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
32
|
+
// The same round trip finishes the CSS animations whose time is up, so
|
|
33
|
+
// whatever waits on them (a closed dialog unmounting) runs, and shows as
|
|
34
|
+
// DOM work on the next tick; moves the page's transitions to its clock,
|
|
35
|
+
// which the last tick advanced; and says how many are still running.
|
|
36
|
+
const probe = await page.evaluate(() => {
|
|
37
|
+
window.__recorder?.finishEnded();
|
|
38
|
+
return {
|
|
39
|
+
mutations: window.__recorder?.mutations ?? 0,
|
|
40
|
+
fontsLoaded: document.fonts ? document.fonts.status === "loaded" : true,
|
|
41
|
+
transitions: window.__recorderTransitions?.sync() ?? 0,
|
|
42
|
+
};
|
|
43
|
+
});
|
|
39
44
|
const requests = inFlightRequests();
|
|
40
45
|
const hookOk = options.hook ? await options.hook() : true;
|
|
41
46
|
// A running transition touches no DOM and no network, so the other
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"settle.js","sourceRoot":"","sources":["../../src/driver/settle.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,IAAI,KAAK,EAAE,MAAM,sBAAsB,CAAC;AA0B3D,4EAA4E;AAC5E,MAAM,CAAC,MAAM,OAAO,GAAG,EAAE,CAAC;AAC1B,MAAM,gBAAgB,GAAG,EAAE,CAAC;AAE5B;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,IAAU,EACV,gBAA8B,EAC9B,UAAyB,EAAE;IAE3B,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,GAAG,CAAC;IACvC,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,MAAM,CAAC;IAC9C,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,QAAQ,CAAC;IACxC,MAAM,WAAW,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC,CAAC,CAAC;IAE9D,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC3B,IAAI,UAAU,GAAG,CAAC,CAAC;IACnB,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,IAAI,aAAa,GAAG,CAAC,CAAC,CAAC;IACvB,IAAI,WAAW,GAAG,MAAM,CAAC;IAEzB,SAAS,CAAC;QACR,uEAAuE;QACvE
|
|
1
|
+
{"version":3,"file":"settle.js","sourceRoot":"","sources":["../../src/driver/settle.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
(function(){let e=document.createElement(`link`).relList;if(e&&e.supports&&e.supports(`modulepreload`))return;for(let e of document.querySelectorAll(`link[rel="modulepreload"]`))n(e);new MutationObserver(e=>{for(let t of e)if(t.type===`childList`)for(let e of t.addedNodes)e.tagName===`LINK`&&e.rel===`modulepreload`&&n(e)}).observe(document,{childList:!0,subtree:!0});function t(e){let t={};return e.integrity&&(t.integrity=e.integrity),e.referrerPolicy&&(t.referrerPolicy=e.referrerPolicy),t.credentials=e.crossOrigin===`use-credentials`?`include`:e.crossOrigin===`anonymous`?`omit`:`same-origin`,t}function n(e){if(e.ep)return;e.ep=!0;let n=t(e);fetch(e.href,n)}})();function e(e){let n=t(e);return n*n*n*(10+n*(-15+6*n))}function t(e){return e<=0?0:e>=1?1:e}function n(e,t,n){return e+(t-e)*n}function r(e,t,r){let i=e.x+e.w/2,a=e.y+e.h/2,o=t.x+t.w/2,s=t.y+t.h/2,c=e.w*(t.w/e.w)**+r,l=e.h*(t.h/e.h)**+r,u=n(i,o,r),d=n(a,s,r);return{x:u-c/2,y:d-l/2,w:c,h:l}}function i(e,t,r){return{x:n(e.x,t.x,r),y:n(e.y,t.y,r),w:n(e.w,t.w,r),h:n(e.h,t.h,r)}}function a(t,n){if(t.length===0)throw Error(`evalCamera called with no keys`);if(n<=t[0].t)return t[0].rect;for(let a=1;a<t.length;a+=1){let o=t[a-1],s=t[a];if(n>=s.t)continue;if(s.ease===`hold`)return o.rect;let c=s.t-o.t,l=c<=0?1:(n-o.t)/c;return s.ease===`linear`?i(o.rect,s.rect,l):r(o.rect,s.rect,e(l))}return t[t.length-1].rect}function o(e,t,n,r){let i=1-r,a=i*i,o=2*i*r,s=r*r;return[a*e[0]+o*t[0]+s*n[0],a*e[1]+o*t[1]+s*n[1]]}function s(t,n){if(t.length===0)return[0,0];let r=t[0];if(n<=r.t0)return r.kind===`move`?r.from:r.at;for(let r of t){if(n>r.t1)continue;if(r.kind===`hold`)return r.at;let t=r.t1-r.t0,i=t<=0?1:(n-r.t0)/t;return o(r.from,r.ctrl,r.to,e(i))}let i=t[t.length-1];return i.kind===`move`?i.to:i.at}function c(e,t,n){let r=n.w/t.w;return[n.x+(e[0]-t.x)*r,n.y+(e[1]-t.y)*r]}function l(e,t,n){let r=n.w/t.w;return{x:n.x+(e.x-t.x)*r,y:n.y+(e.y-t.y)*r,w:e.w*r,h:e.h*r}}function u(e,t){return{x:e.x*t,y:e.y*t,w:e.w*t,h:e.h*t}}function d(e,t,n){let r=Math.min(e.width,e.height)*n,i=e.width-r*2,a=e.height-r*2,o=t.width/t.height,s=i,c=s/o;return c>a&&(c=a,s=c*o),{x:(e.width-s)/2,y:(e.height-c)/2,w:s,h:c}}function f(e,n,r){return r<=0?1:t((e-n+1)/r)}function p(e,t,n){let r=e.frames[t],i=null;for(let a of e.cuts)t>a&&t<=a+n&&e.frames[a]!==r&&(i===null||a>i)&&(i=a);return i===null?{current:r,previous:null,alpha:1}:{current:r,previous:e.frames[i],alpha:f(t,i+1,n)}}function m(e,n,r,i){return e<=n||e>=r?0:i<=0?1:Math.min(t((e-n)/i),t((r-e)/i))}function ee(e,t){return e/t}function te(e,t,n){let r=n?.padding??0;return!n||r<=0?{x:0,y:0,w:e.width,h:e.height}:d(e,t,r)}function h(e){let t=e?.caption??`auto`;return t===`none`?`none`:t===`below`||t===`overlay`?t:(e?.padding??0)>0?`below`:`overlay`}function ne(e,t){let n=e?.captionInset;return n?.right===void 0?{x:n?.left??40,align:`left`}:{x:t-n.right,align:`right`}}var g={radius:16,hairline:`rgba(255, 255, 255, 0.16)`,shadow:`rgba(0, 0, 0, 0.45)`,shadowBlur:60,shadowOffsetY:18,captionFont:`600 30px "Helvetica Neue", Helvetica, Arial, "Liberation Sans", sans-serif`,captionColor:`rgba(255, 255, 255, 0.94)`,captionShadow:`rgba(0, 0, 0, 0.5)`,captionScrim:`rgba(0, 0, 0, 0.45)`,captionScrimHeight:86,cursorFill:`#ffffff`,cursorStroke:`rgba(0, 0, 0, 0.55)`,cursorShadow:`rgba(0, 0, 0, 0.35)`,ringWidth:3,ringRadius:10,spotlightDim:.55,spotlightTint:`6, 2, 16`};function re(e,t){let n=/^#([0-9a-f]{6})$/i.exec(e.trim());if(!n)return e;let r=Number.parseInt(n[1],16);return`rgba(${r>>16&255}, ${r>>8&255}, ${r&255}, ${t})`}function _(e,t,n){let r=Math.min(n,t.w/2,t.h/2);e.beginPath(),e.moveTo(t.x+r,t.y),e.arcTo(t.x+t.w,t.y,t.x+t.w,t.y+t.h,r),e.arcTo(t.x+t.w,t.y+t.h,t.x,t.y+t.h,r),e.arcTo(t.x,t.y+t.h,t.x,t.y,r),e.arcTo(t.x,t.y,t.x+t.w,t.y,r),e.closePath()}function ie(e,t,n){if(typeof n==`string`)e.fillStyle=n;else{let r=e.createLinearGradient(0,0,t.width,t.height);r.addColorStop(0,n[0]),r.addColorStop(1,n[1]),e.fillStyle=r}e.fillRect(0,0,t.width,t.height)}function v(e,t,n,r,i,a,o){let s=u(i,t.viewport.dpr),{current:c,previous:l,alpha:d}=p(t,r,o.motion.crossfadeFrames),f=o.theme.presentation?.frame??!1;e.save(),f&&(_(e,a,g.radius),e.clip());let m=(t,r)=>{let i=n.images[t];i&&(e.globalAlpha=r,e.drawImage(i,s.x,s.y,s.w,s.h,a.x,a.y,a.w,a.h))};l!==null&&m(l,1),m(c,l===null?1:d),e.globalAlpha=1,e.restore()}function y(e,t){e.save(),e.shadowColor=g.shadow,e.shadowBlur=g.shadowBlur,e.shadowOffsetY=g.shadowOffsetY,e.fillStyle=`rgba(0,0,0,1)`,_(e,t,g.radius),e.fill(),e.restore()}function b(e,t){e.save(),e.strokeStyle=g.hairline,e.lineWidth=1,_(e,t,g.radius),e.stroke(),e.restore()}function x(e,t){return{x:e.x-t,y:e.y-t,w:e.w+t*2,h:e.h+t*2}}function S(e,t,n,r,i,a){let o=a.theme.presentation?.frame??!1;for(let s of t.highlights){let t=m(n,s.t0,s.t1,.25);if(t<=0)continue;let c=l(s.rect,r,i);e.save(),e.globalAlpha=t,s.style===`spotlight`?(e.save(),_(e,i,o?g.radius:0),e.clip(),e.fillStyle=`rgba(${g.spotlightTint}, ${g.spotlightDim})`,e.fillRect(i.x,i.y,i.w,i.h),e.globalCompositeOperation=`destination-out`,_(e,x(c,8),g.ringRadius),e.fill(),e.restore()):(e.strokeStyle=a.theme.accent,e.lineWidth=g.ringWidth,e.shadowColor=re(a.theme.accent,.35),e.shadowBlur=18,_(e,x(c,6),g.ringRadius),e.stroke()),e.restore()}}function C(e,t,n){let r=!1,i=[],a=0;for(let o of e.pointer){if(o.t>t)break;if(o.kind===`down`)r=!0,a=o.t;else{r=!1;let e=t-o.t;e>=0&&e<=n.motion.rippleMs/1e3&&i.push({at:o.at,age:e})}}let o=r?t-a:0,s=r?Math.min(1,o/(n.motion.pressScaleMs/1e3)):0;return{scale:1-(1-n.motion.pressScale)*s,ripples:i}}function w(e,t,n,r,i){for(let a of t){let t=a.age/(i.motion.rippleMs/1e3),[o,s]=c(a.at,n,r);e.save(),e.globalAlpha=(1-t)*.55,e.strokeStyle=i.theme.accent,e.lineWidth=2.5,e.beginPath(),e.arc(o,s,6+t*i.motion.rippleRadius,0,Math.PI*2),e.stroke(),e.restore()}}function T(e,t,n,r){let i=r*n;e.save(),e.translate(t[0],t[1]),e.scale(i,i),e.shadowColor=g.cursorShadow,e.shadowBlur=6/i,e.shadowOffsetY=2/i,e.beginPath(),e.moveTo(0,0),e.lineTo(0,16.6),e.lineTo(4.1,12.9),e.lineTo(6.9,18.9),e.lineTo(9.6,17.6),e.lineTo(6.9,11.8),e.lineTo(12.1,11.5),e.closePath(),e.fillStyle=g.cursorFill,e.fill(),e.shadowColor=`transparent`,e.strokeStyle=g.cursorStroke,e.lineWidth=1/i,e.stroke(),e.restore()}function E(e,t,n,r,i,a){let o=h(a.theme.presentation);if(o!==`none`)for(let s of t.captions){let t=m(n,s.t0,s.t1,a.motion.captionFadeS);if(t<=0)continue;if(e.save(),e.globalAlpha=t,o===`overlay`){let t=g.captionScrimHeight,n=e.createLinearGradient(0,i.height-t,0,i.height);n.addColorStop(0,`rgba(0, 0, 0, 0)`),n.addColorStop(1,g.captionScrim),e.fillStyle=n,e.fillRect(0,i.height-t,i.width,t)}e.font=g.captionFont,e.fillStyle=g.captionColor,e.textBaseline=`middle`,e.shadowColor=g.captionShadow,e.shadowBlur=8;let c=r.x,l=(r.y+r.h+i.height)/2;if(o===`overlay`){let t=ne(a.theme.presentation,i.width);e.textAlign=t.align,c=t.x,l=i.height-g.captionScrimHeight/2+4}e.fillText(s.text,c,l),e.restore()}}function ae(e,t,n,r,i,o){let l=ee(r,t.fps),u=a(t.camera,l),d=o.theme.presentation,f=te(i,t.viewport,d);d?.backdrop&&ie(e,i,d.backdrop),d?.frame&&y(e,f),v(e,t,n,r,u,f,o),d?.frame&&b(e,f),S(e,t,l,u,f,o);let{scale:p,ripples:m}=C(t,l,o);w(e,m,u,f,o),T(e,c(s(t.cursor,l),u,f),p,o.theme.cursorScale),E(e,t,l,f,i,o)}var D="versioncam reads scenes from a module's default export: a Record<string, Scene> keyed by scene id, as in `export default { [intro.id]: intro }`.";function O(e){return e==null?String(e):Array.isArray(e)?`an array`:`a ${typeof e}`}function k(e){return typeof e==`string`?JSON.stringify(e):String(e)}function A(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function oe(e,t){if(!A(t))return`"${e}" is ${O(t)}, not a scene`;let{draw:n,durationFrames:r,fps:i}=t;return typeof n==`function`?!Number.isInteger(r)||r<1?`"${e}" has durationFrames ${k(r)}, where a whole number of frames, 1 or more, belongs`:typeof i!=`number`||!Number.isFinite(i)||i<=0?`"${e}" has fps ${k(i)}, where a number above 0 belongs`:null:`"${e}" has no draw(ctx, frame, canvas)`}function j(e){if(!A(e))return O(e);if(typeof e.draw==`function`)return`a single scene rather than a record of them`;for(let[t,n]of Object.entries(e)){let e=oe(t,n);if(e)return`a record in which ${e}`}return null}function M(e,t){if(e.default===void 0){let n=Object.keys(e).filter(e=>e!=="default"),r=[`${t} has no default export.`+(n.length>0?` It exports ${n.join(`, `)}.`:``),D],i=n.filter(t=>j(e[t])===null&&Object.keys(e[t]).length>0);return i.length===1&&r.push(`${i[0]} is one: add \`export default ${i[0]}\`.`),{error:r.join(`
|
|
2
|
+
`)}}let n=j(e.default);return n?{error:`${t} default-exports ${n}.\n${D}`}:{scenes:e.default}}function N(e,t,n){let r=e.map(e=>`"${e}"`).join(`, `),i=`No scene${e.length===1?``:`s`} called ${r}.`;return n===null?`${i} The config names no \`scenes\` module.`:`${i} ${n} defines ${t.length>0?t.join(`, `):`none`}.`}var P=`modulepreload`,F=function(e,t){return new URL(e,t).href},I={},L=function(e,t,n){let r=Promise.resolve();if(t&&t.length>0){let e=document.getElementsByTagName(`link`),i=document.querySelector(`meta[property=csp-nonce]`),a=i?.nonce||i?.getAttribute(`nonce`);function o(e){return Promise.all(e.map(e=>Promise.resolve(e).then(e=>({status:`fulfilled`,value:e}),e=>({status:`rejected`,reason:e}))))}function s(e){return import.meta.resolve?import.meta.resolve(e):new URL(e,import.meta.url).href}r=o(t.map(t=>{if(t=F(t,n),t=s(t),t in I)return;I[t]=!0;let r=t.endsWith(`.css`);for(let n=e.length-1;n>=0;n--){let i=e[n];if(i.href===t&&(!r||i.rel===`stylesheet`))return}let i=document.createElement(`link`);if(i.rel=r?`stylesheet`:P,r||(i.as=`script`),i.crossOrigin=``,i.href=t,a&&i.setAttribute(`nonce`,a),document.head.appendChild(i),r)return new Promise((e,n)=>{i.addEventListener(`load`,e),i.addEventListener(`error`,()=>n(Error(`Unable to preload CSS for ${t}`)))})}).filter(e=>e!==void 0))}function i(e){let t=new Event(`vite:preloadError`,{cancelable:!0});if(t.payload=e,window.dispatchEvent(t),!t.defaultPrevented)throw e}return r.then(t=>{for(let e of t||[])e.status===`rejected`&&i(e.reason);return e().catch(i)})},R=new URLSearchParams(location.search),z=R.has(`recording`),B=R.get(`scene`),V=R.get(`headless`)===`1`;V&&document.body.classList.add(`headless`);var H=document.getElementById(`stage`),U=document.getElementById(`scrub`),W=document.getElementById(`status`),G=document.getElementById(`play`),K=document.getElementById(`error`);function q(e){throw K.textContent=e,Error(e)}async function J(e,t){let n=await fetch(e);return n.ok||q(`Could not load ${t} (${e}): ${n.status}`),await n.json()}async function se(e){let t=await fetch(e);return t.ok||q(`Could not load ${e}: ${t.status}`),createImageBitmap(await t.blob())}function Y(e){H.width=e.output.width,H.height=e.output.height,V?(H.style.width=`${e.output.width}px`,H.style.height=`${e.output.height}px`):(H.style.width=`min(100%, 1280px)`,H.style.height=`auto`);let t=H.getContext(`2d`,{alpha:!1});return t||q(`No 2D context`),t.imageSmoothingEnabled=!0,t.imageSmoothingQuality=`high`,t}function X(e,t,n,r){let i=!1;G.addEventListener(`click`,()=>{i=!i,G.textContent=i?`Pause`:`Play`;let a=performance.now(),o=s=>{i&&(s-a>=1e3/e&&(a=s,r((n()+1)%t)),requestAnimationFrame(o))};requestAnimationFrame(o)})}function Z(e,t,n,r){e.durationFrames=t,e.fps=n,e.setFrame=e=>new Promise(t=>{r(e),requestAnimationFrame(()=>requestAnimationFrame(()=>t()))})}async function Q(e,t){let n=await J(`/recording/timeline.json`,`the recording`),r=Y(t),i={images:await Promise.all(n.states.map(e=>se(`/recording/states/${e}`)))};await document.fonts.ready;let a=-1,o=e=>{let o=Math.max(0,Math.min(n.durationFrames-1,e));ae(r,n,i,o,H,t),a=o,V||(U.value=String(o),W.textContent=`${o+1} / ${n.durationFrames} · ${(o/n.fps).toFixed(2)}s · state ${n.frames[o]}`)};U.max=String(n.durationFrames-1),U.addEventListener(`input`,()=>o(Number(U.value))),X(n.fps,n.durationFrames,()=>a,o),o(Number(R.get(`frame`)??0)),Z(e,n.durationFrames,n.fps,o)}async function ce(e,t,n){t.scenes===null&&q(N([n],[],null));let r=M(await L(()=>import(`/scenes.js`),[],import.meta.url),t.scenes);`error`in r&&q(r.error);let i=r.scenes[n];i||q(N([n],Object.keys(r.scenes),t.scenes));let a=Y(t);await document.fonts.ready;let o=-1,s=e=>{let t=Math.max(0,Math.min(i.durationFrames-1,e));i.draw(a,t,H),o=t,V||(U.value=String(t),W.textContent=`${t+1} / ${i.durationFrames} · ${(t/i.fps).toFixed(2)}s`)};U.max=String(i.durationFrames-1),U.addEventListener(`input`,()=>s(Number(U.value))),X(i.fps,i.durationFrames,()=>o,s),s(Number(R.get(`frame`)??0)),Z(e,i.durationFrames,i.fps,s)}async function le(e){let t=await J(`/render-config.json`,`the render config`);if(B)return ce(e,t,B);if(z)return Q(e,t);q(`Pass ?recording or ?scene=<id>.`)}var $={ready:Promise.resolve(),setFrame:async()=>{},durationFrames:0,fps:60};window.__render=$,$.ready=le($);
|
package/dist/page/index.html
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<meta charset="utf-8" />
|
|
5
5
|
<title>versioncam render</title>
|
|
6
6
|
<style>*{box-sizing:border-box;margin:0;padding:0;transition:none!important;animation:none!important}body{color:#e8e2f5;background:#0b0614;flex-direction:column;align-items:center;min-height:100vh;font-family:Helvetica Neue,Helvetica,Arial,sans-serif;display:flex}body.headless{background:0 0;display:block}#stage{display:block}body.headless #stage{position:fixed;inset:0}#controls{align-items:center;gap:14px;width:min(100%,1280px);padding:14px 18px 28px;font-size:13px;display:flex}body.headless #controls{display:none}#scrub{flex:1}#status{font-variant-numeric:tabular-nums;white-space:nowrap;opacity:.75}button{font:inherit;color:#fff;cursor:pointer;background:#2f6fed;border:0;border-radius:6px;padding:6px 14px}#error{color:#ff9a9a;white-space:pre-wrap;padding:24px;font-family:ui-monospace,Menlo,monospace}</style>
|
|
7
|
-
<script type="module" crossorigin src="./assets/index-
|
|
7
|
+
<script type="module" crossorigin src="./assets/index-BtUbUEdN.js"></script>
|
|
8
8
|
</head>
|
|
9
9
|
<body>
|
|
10
10
|
<canvas id="stage"></canvas>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "versioncam",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Record demo clips of a real web app: scripted, deterministic, re-recorded on every push.",
|
|
6
6
|
"license": "FSL-1.1-ALv2",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"url": "git+https://github.com/versioncam/versioncam.git",
|
|
12
12
|
"directory": "packages/recorder"
|
|
13
13
|
},
|
|
14
|
-
"bugs": "https://github.com/versioncam/versioncam
|
|
14
|
+
"bugs": "https://github.com/versioncam/versioncam/issues",
|
|
15
15
|
"keywords": [
|
|
16
16
|
"demo",
|
|
17
17
|
"video",
|
|
@@ -64,7 +64,8 @@
|
|
|
64
64
|
},
|
|
65
65
|
"dependencies": {
|
|
66
66
|
"esbuild": "^0.25.0",
|
|
67
|
-
"playwright": "1.59.1"
|
|
67
|
+
"playwright": "1.59.1",
|
|
68
|
+
"playwright-core": "1.59.1"
|
|
68
69
|
},
|
|
69
70
|
"optionalDependencies": {
|
|
70
71
|
"ffmpeg-static": "^5.2.0"
|
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "versioncam",
|
|
3
|
+
"version": "0.5.2",
|
|
3
4
|
"description": "Record a short, true demo clip of a web app from one sentence: a skill that sets the repository up on its first run, writes the clip, and has a reviewer pass it from a contact sheet.",
|
|
4
5
|
"author": {
|
|
5
6
|
"name": "Peter Bulovec"
|
|
6
7
|
},
|
|
7
8
|
"homepage": "https://version.cam",
|
|
8
|
-
"keywords": [
|
|
9
|
+
"keywords": [
|
|
10
|
+
"demo",
|
|
11
|
+
"recording",
|
|
12
|
+
"playwright",
|
|
13
|
+
"screencast"
|
|
14
|
+
]
|
|
9
15
|
}
|
package/plugin/README.md
CHANGED
|
@@ -30,6 +30,17 @@ Or, without copying anything, load the plugin directory as it is:
|
|
|
30
30
|
claude --plugin-dir node_modules/versioncam/plugin
|
|
31
31
|
```
|
|
32
32
|
|
|
33
|
+
Or install it as a plugin from the repository's own marketplace, in a Claude
|
|
34
|
+
Code session (2.1.275 or later):
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
/plugin install versioncam --marketplace versioncam/versioncam
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Loaded either of these two ways, the skill carries the plugin's name as a
|
|
41
|
+
prefix: `/versioncam:versioncam "<intent>"`. Installed by `init`, it is
|
|
42
|
+
`/versioncam`, as the examples below write it.
|
|
43
|
+
|
|
33
44
|
## Run it
|
|
34
45
|
|
|
35
46
|
```
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
(function(){let e=document.createElement(`link`).relList;if(e&&e.supports&&e.supports(`modulepreload`))return;for(let e of document.querySelectorAll(`link[rel="modulepreload"]`))n(e);new MutationObserver(e=>{for(let t of e)if(t.type===`childList`)for(let e of t.addedNodes)e.tagName===`LINK`&&e.rel===`modulepreload`&&n(e)}).observe(document,{childList:!0,subtree:!0});function t(e){let t={};return e.integrity&&(t.integrity=e.integrity),e.referrerPolicy&&(t.referrerPolicy=e.referrerPolicy),t.credentials=e.crossOrigin===`use-credentials`?`include`:e.crossOrigin===`anonymous`?`omit`:`same-origin`,t}function n(e){if(e.ep)return;e.ep=!0;let n=t(e);fetch(e.href,n)}})();function e(e){let n=t(e);return n*n*n*(10+n*(-15+6*n))}function t(e){return e<=0?0:e>=1?1:e}function n(e,t,n){return e+(t-e)*n}function r(e,t,r){let i=e.x+e.w/2,a=e.y+e.h/2,o=t.x+t.w/2,s=t.y+t.h/2,c=e.w*(t.w/e.w)**+r,l=e.h*(t.h/e.h)**+r,u=n(i,o,r),d=n(a,s,r);return{x:u-c/2,y:d-l/2,w:c,h:l}}function i(e,t,r){return{x:n(e.x,t.x,r),y:n(e.y,t.y,r),w:n(e.w,t.w,r),h:n(e.h,t.h,r)}}function a(t,n){if(t.length===0)throw Error(`evalCamera called with no keys`);if(n<=t[0].t)return t[0].rect;for(let a=1;a<t.length;a+=1){let o=t[a-1],s=t[a];if(n>=s.t)continue;if(s.ease===`hold`)return o.rect;let c=s.t-o.t,l=c<=0?1:(n-o.t)/c;return s.ease===`linear`?i(o.rect,s.rect,l):r(o.rect,s.rect,e(l))}return t[t.length-1].rect}function o(e,t,n,r){let i=1-r,a=i*i,o=2*i*r,s=r*r;return[a*e[0]+o*t[0]+s*n[0],a*e[1]+o*t[1]+s*n[1]]}function s(t,n){if(t.length===0)return[0,0];let r=t[0];if(n<=r.t0)return r.kind===`move`?r.from:r.at;for(let r of t){if(n>r.t1)continue;if(r.kind===`hold`)return r.at;let t=r.t1-r.t0,i=t<=0?1:(n-r.t0)/t;return o(r.from,r.ctrl,r.to,e(i))}let i=t[t.length-1];return i.kind===`move`?i.to:i.at}function c(e,t,n){let r=n.w/t.w;return[n.x+(e[0]-t.x)*r,n.y+(e[1]-t.y)*r]}function l(e,t,n){let r=n.w/t.w;return{x:n.x+(e.x-t.x)*r,y:n.y+(e.y-t.y)*r,w:e.w*r,h:e.h*r}}function u(e,t){return{x:e.x*t,y:e.y*t,w:e.w*t,h:e.h*t}}function d(e,t,n){let r=Math.min(e.width,e.height)*n,i=e.width-r*2,a=e.height-r*2,o=t.width/t.height,s=i,c=s/o;return c>a&&(c=a,s=c*o),{x:(e.width-s)/2,y:(e.height-c)/2,w:s,h:c}}function f(e,n,r){return r<=0?1:t((e-n+1)/r)}function p(e,n,r,i){return e<=n||e>=r?0:i<=0?1:Math.min(t((e-n)/i),t((r-e)/i))}function ee(e,t){return e/t}function te(e,t,n){let r=n?.padding??0;return!n||r<=0?{x:0,y:0,w:e.width,h:e.height}:d(e,t,r)}function m(e){let t=e?.caption??`auto`;return t===`none`?`none`:t===`below`||t===`overlay`?t:(e?.padding??0)>0?`below`:`overlay`}function h(e,t){let n=e?.captionInset;return n?.right===void 0?{x:n?.left??40,align:`left`}:{x:t-n.right,align:`right`}}var g={radius:16,hairline:`rgba(255, 255, 255, 0.16)`,shadow:`rgba(0, 0, 0, 0.45)`,shadowBlur:60,shadowOffsetY:18,captionFont:`600 30px "Helvetica Neue", Helvetica, Arial, "Liberation Sans", sans-serif`,captionColor:`rgba(255, 255, 255, 0.94)`,captionShadow:`rgba(0, 0, 0, 0.5)`,captionScrim:`rgba(0, 0, 0, 0.45)`,captionScrimHeight:86,cursorFill:`#ffffff`,cursorStroke:`rgba(0, 0, 0, 0.55)`,cursorShadow:`rgba(0, 0, 0, 0.35)`,ringWidth:3,ringRadius:10,spotlightDim:.55,spotlightTint:`6, 2, 16`};function ne(e,t){let n=/^#([0-9a-f]{6})$/i.exec(e.trim());if(!n)return e;let r=Number.parseInt(n[1],16);return`rgba(${r>>16&255}, ${r>>8&255}, ${r&255}, ${t})`}function _(e,t,n){let r=Math.min(n,t.w/2,t.h/2);e.beginPath(),e.moveTo(t.x+r,t.y),e.arcTo(t.x+t.w,t.y,t.x+t.w,t.y+t.h,r),e.arcTo(t.x+t.w,t.y+t.h,t.x,t.y+t.h,r),e.arcTo(t.x,t.y+t.h,t.x,t.y,r),e.arcTo(t.x,t.y,t.x+t.w,t.y,r),e.closePath()}function re(e,t,n){if(typeof n==`string`)e.fillStyle=n;else{let r=e.createLinearGradient(0,0,t.width,t.height);r.addColorStop(0,n[0]),r.addColorStop(1,n[1]),e.fillStyle=r}e.fillRect(0,0,t.width,t.height)}function ie(e,t,n){for(let r of e.cuts)if(t>r&&t<=r+n)return{current:e.frames[t],previous:e.frames[r],alpha:f(t,r+1,n)};return{current:e.frames[t],previous:null,alpha:1}}function v(e,t,n,r,i,a,o){let s=u(i,t.viewport.dpr),{current:c,previous:l,alpha:d}=ie(t,r,o.motion.crossfadeFrames),f=o.theme.presentation?.frame??!1;e.save(),f&&(_(e,a,g.radius),e.clip());let p=(t,r)=>{let i=n.images[t];i&&(e.globalAlpha=r,e.drawImage(i,s.x,s.y,s.w,s.h,a.x,a.y,a.w,a.h))};l!==null&&l!==c&&p(l,1),p(c,l===null?1:d),e.globalAlpha=1,e.restore()}function y(e,t){e.save(),e.shadowColor=g.shadow,e.shadowBlur=g.shadowBlur,e.shadowOffsetY=g.shadowOffsetY,e.fillStyle=`rgba(0,0,0,1)`,_(e,t,g.radius),e.fill(),e.restore()}function b(e,t){e.save(),e.strokeStyle=g.hairline,e.lineWidth=1,_(e,t,g.radius),e.stroke(),e.restore()}function x(e,t){return{x:e.x-t,y:e.y-t,w:e.w+t*2,h:e.h+t*2}}function S(e,t,n,r,i,a){let o=a.theme.presentation?.frame??!1;for(let s of t.highlights){let t=p(n,s.t0,s.t1,.25);if(t<=0)continue;let c=l(s.rect,r,i);e.save(),e.globalAlpha=t,s.style===`spotlight`?(e.save(),_(e,i,o?g.radius:0),e.clip(),e.fillStyle=`rgba(${g.spotlightTint}, ${g.spotlightDim})`,e.fillRect(i.x,i.y,i.w,i.h),e.globalCompositeOperation=`destination-out`,_(e,x(c,8),g.ringRadius),e.fill(),e.restore()):(e.strokeStyle=a.theme.accent,e.lineWidth=g.ringWidth,e.shadowColor=ne(a.theme.accent,.35),e.shadowBlur=18,_(e,x(c,6),g.ringRadius),e.stroke()),e.restore()}}function C(e,t,n){let r=!1,i=[],a=0;for(let o of e.pointer){if(o.t>t)break;if(o.kind===`down`)r=!0,a=o.t;else{r=!1;let e=t-o.t;e>=0&&e<=n.motion.rippleMs/1e3&&i.push({at:o.at,age:e})}}let o=r?t-a:0,s=r?Math.min(1,o/(n.motion.pressScaleMs/1e3)):0;return{scale:1-(1-n.motion.pressScale)*s,ripples:i}}function w(e,t,n,r,i){for(let a of t){let t=a.age/(i.motion.rippleMs/1e3),[o,s]=c(a.at,n,r);e.save(),e.globalAlpha=(1-t)*.55,e.strokeStyle=i.theme.accent,e.lineWidth=2.5,e.beginPath(),e.arc(o,s,6+t*i.motion.rippleRadius,0,Math.PI*2),e.stroke(),e.restore()}}function T(e,t,n,r){let i=r*n;e.save(),e.translate(t[0],t[1]),e.scale(i,i),e.shadowColor=g.cursorShadow,e.shadowBlur=6/i,e.shadowOffsetY=2/i,e.beginPath(),e.moveTo(0,0),e.lineTo(0,16.6),e.lineTo(4.1,12.9),e.lineTo(6.9,18.9),e.lineTo(9.6,17.6),e.lineTo(6.9,11.8),e.lineTo(12.1,11.5),e.closePath(),e.fillStyle=g.cursorFill,e.fill(),e.shadowColor=`transparent`,e.strokeStyle=g.cursorStroke,e.lineWidth=1/i,e.stroke(),e.restore()}function E(e,t,n,r,i,a){let o=m(a.theme.presentation);if(o!==`none`)for(let s of t.captions){let t=p(n,s.t0,s.t1,a.motion.captionFadeS);if(t<=0)continue;if(e.save(),e.globalAlpha=t,o===`overlay`){let t=g.captionScrimHeight,n=e.createLinearGradient(0,i.height-t,0,i.height);n.addColorStop(0,`rgba(0, 0, 0, 0)`),n.addColorStop(1,g.captionScrim),e.fillStyle=n,e.fillRect(0,i.height-t,i.width,t)}e.font=g.captionFont,e.fillStyle=g.captionColor,e.textBaseline=`middle`,e.shadowColor=g.captionShadow,e.shadowBlur=8;let c=r.x,l=(r.y+r.h+i.height)/2;if(o===`overlay`){let t=h(a.theme.presentation,i.width);e.textAlign=t.align,c=t.x,l=i.height-g.captionScrimHeight/2+4}e.fillText(s.text,c,l),e.restore()}}function ae(e,t,n,r,i,o){let l=ee(r,t.fps),u=a(t.camera,l),d=o.theme.presentation,f=te(i,t.viewport,d);d?.backdrop&&re(e,i,d.backdrop),d?.frame&&y(e,f),v(e,t,n,r,u,f,o),d?.frame&&b(e,f),S(e,t,l,u,f,o);let{scale:p,ripples:m}=C(t,l,o);w(e,m,u,f,o),T(e,c(s(t.cursor,l),u,f),p,o.theme.cursorScale),E(e,t,l,f,i,o)}var D="versioncam reads scenes from a module's default export: a Record<string, Scene> keyed by scene id, as in `export default { [intro.id]: intro }`.";function O(e){return e==null?String(e):Array.isArray(e)?`an array`:`a ${typeof e}`}function k(e){return typeof e==`string`?JSON.stringify(e):String(e)}function A(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function oe(e,t){if(!A(t))return`"${e}" is ${O(t)}, not a scene`;let{draw:n,durationFrames:r,fps:i}=t;return typeof n==`function`?!Number.isInteger(r)||r<1?`"${e}" has durationFrames ${k(r)}, where a whole number of frames, 1 or more, belongs`:typeof i!=`number`||!Number.isFinite(i)||i<=0?`"${e}" has fps ${k(i)}, where a number above 0 belongs`:null:`"${e}" has no draw(ctx, frame, canvas)`}function j(e){if(!A(e))return O(e);if(typeof e.draw==`function`)return`a single scene rather than a record of them`;for(let[t,n]of Object.entries(e)){let e=oe(t,n);if(e)return`a record in which ${e}`}return null}function M(e,t){if(e.default===void 0){let n=Object.keys(e).filter(e=>e!=="default"),r=[`${t} has no default export.`+(n.length>0?` It exports ${n.join(`, `)}.`:``),D],i=n.filter(t=>j(e[t])===null&&Object.keys(e[t]).length>0);return i.length===1&&r.push(`${i[0]} is one: add \`export default ${i[0]}\`.`),{error:r.join(`
|
|
2
|
-
`)}}let n=j(e.default);return n?{error:`${t} default-exports ${n}.\n${D}`}:{scenes:e.default}}function N(e,t,n){let r=e.map(e=>`"${e}"`).join(`, `),i=`No scene${e.length===1?``:`s`} called ${r}.`;return n===null?`${i} The config names no \`scenes\` module.`:`${i} ${n} defines ${t.length>0?t.join(`, `):`none`}.`}var P=`modulepreload`,F=function(e,t){return new URL(e,t).href},I={},L=function(e,t,n){let r=Promise.resolve();if(t&&t.length>0){let e=document.getElementsByTagName(`link`),i=document.querySelector(`meta[property=csp-nonce]`),a=i?.nonce||i?.getAttribute(`nonce`);function o(e){return Promise.all(e.map(e=>Promise.resolve(e).then(e=>({status:`fulfilled`,value:e}),e=>({status:`rejected`,reason:e}))))}function s(e){return import.meta.resolve?import.meta.resolve(e):new URL(e,import.meta.url).href}r=o(t.map(t=>{if(t=F(t,n),t=s(t),t in I)return;I[t]=!0;let r=t.endsWith(`.css`);for(let n=e.length-1;n>=0;n--){let i=e[n];if(i.href===t&&(!r||i.rel===`stylesheet`))return}let i=document.createElement(`link`);if(i.rel=r?`stylesheet`:P,r||(i.as=`script`),i.crossOrigin=``,i.href=t,a&&i.setAttribute(`nonce`,a),document.head.appendChild(i),r)return new Promise((e,n)=>{i.addEventListener(`load`,e),i.addEventListener(`error`,()=>n(Error(`Unable to preload CSS for ${t}`)))})}).filter(e=>e!==void 0))}function i(e){let t=new Event(`vite:preloadError`,{cancelable:!0});if(t.payload=e,window.dispatchEvent(t),!t.defaultPrevented)throw e}return r.then(t=>{for(let e of t||[])e.status===`rejected`&&i(e.reason);return e().catch(i)})},R=new URLSearchParams(location.search),z=R.has(`recording`),B=R.get(`scene`),V=R.get(`headless`)===`1`;V&&document.body.classList.add(`headless`);var H=document.getElementById(`stage`),U=document.getElementById(`scrub`),W=document.getElementById(`status`),G=document.getElementById(`play`),K=document.getElementById(`error`);function q(e){throw K.textContent=e,Error(e)}async function J(e,t){let n=await fetch(e);return n.ok||q(`Could not load ${t} (${e}): ${n.status}`),await n.json()}async function se(e){let t=await fetch(e);return t.ok||q(`Could not load ${e}: ${t.status}`),createImageBitmap(await t.blob())}function Y(e){H.width=e.output.width,H.height=e.output.height,V?(H.style.width=`${e.output.width}px`,H.style.height=`${e.output.height}px`):(H.style.width=`min(100%, 1280px)`,H.style.height=`auto`);let t=H.getContext(`2d`,{alpha:!1});return t||q(`No 2D context`),t.imageSmoothingEnabled=!0,t.imageSmoothingQuality=`high`,t}function X(e,t,n,r){let i=!1;G.addEventListener(`click`,()=>{i=!i,G.textContent=i?`Pause`:`Play`;let a=performance.now(),o=s=>{i&&(s-a>=1e3/e&&(a=s,r((n()+1)%t)),requestAnimationFrame(o))};requestAnimationFrame(o)})}function Z(e,t,n,r){e.durationFrames=t,e.fps=n,e.setFrame=e=>new Promise(t=>{r(e),requestAnimationFrame(()=>requestAnimationFrame(()=>t()))})}async function Q(e,t){let n=await J(`/recording/timeline.json`,`the recording`),r=Y(t),i={images:await Promise.all(n.states.map(e=>se(`/recording/states/${e}`)))};await document.fonts.ready;let a=-1,o=e=>{let o=Math.max(0,Math.min(n.durationFrames-1,e));ae(r,n,i,o,H,t),a=o,V||(U.value=String(o),W.textContent=`${o+1} / ${n.durationFrames} · ${(o/n.fps).toFixed(2)}s · state ${n.frames[o]}`)};U.max=String(n.durationFrames-1),U.addEventListener(`input`,()=>o(Number(U.value))),X(n.fps,n.durationFrames,()=>a,o),o(Number(R.get(`frame`)??0)),Z(e,n.durationFrames,n.fps,o)}async function ce(e,t,n){t.scenes===null&&q(N([n],[],null));let r=M(await L(()=>import(`/scenes.js`),[],import.meta.url),t.scenes);`error`in r&&q(r.error);let i=r.scenes[n];i||q(N([n],Object.keys(r.scenes),t.scenes));let a=Y(t);await document.fonts.ready;let o=-1,s=e=>{let t=Math.max(0,Math.min(i.durationFrames-1,e));i.draw(a,t,H),o=t,V||(U.value=String(t),W.textContent=`${t+1} / ${i.durationFrames} · ${(t/i.fps).toFixed(2)}s`)};U.max=String(i.durationFrames-1),U.addEventListener(`input`,()=>s(Number(U.value))),X(i.fps,i.durationFrames,()=>o,s),s(Number(R.get(`frame`)??0)),Z(e,i.durationFrames,i.fps,s)}async function le(e){let t=await J(`/render-config.json`,`the render config`);if(B)return ce(e,t,B);if(z)return Q(e,t);q(`Pass ?recording or ?scene=<id>.`)}var $={ready:Promise.resolve(),setFrame:async()=>{},durationFrames:0,fps:60};window.__render=$,$.ready=le($);
|