versioncam 0.1.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.
Files changed (182) hide show
  1. package/CHANGELOG.md +94 -0
  2. package/LICENSE.md +105 -0
  3. package/README.md +463 -0
  4. package/bin/versioncam.js +29 -0
  5. package/dist/.types-render/render-page/draw.d.ts +28 -0
  6. package/dist/.types-render/render-page/main.d.ts +24 -0
  7. package/dist/.types-render/render-page/theme.d.ts +37 -0
  8. package/dist/app-server.d.ts +59 -0
  9. package/dist/app-server.js +328 -0
  10. package/dist/app-server.js.map +1 -0
  11. package/dist/cli/app.d.ts +13 -0
  12. package/dist/cli/app.js +21 -0
  13. package/dist/cli/app.js.map +1 -0
  14. package/dist/cli/commands/check.d.ts +8 -0
  15. package/dist/cli/commands/check.js +51 -0
  16. package/dist/cli/commands/check.js.map +1 -0
  17. package/dist/cli/commands/doctor.d.ts +8 -0
  18. package/dist/cli/commands/doctor.js +130 -0
  19. package/dist/cli/commands/doctor.js.map +1 -0
  20. package/dist/cli/commands/dsl.d.ts +16 -0
  21. package/dist/cli/commands/dsl.js +22 -0
  22. package/dist/cli/commands/dsl.js.map +1 -0
  23. package/dist/cli/commands/frame.d.ts +8 -0
  24. package/dist/cli/commands/frame.js +72 -0
  25. package/dist/cli/commands/frame.js.map +1 -0
  26. package/dist/cli/commands/init.d.ts +23 -0
  27. package/dist/cli/commands/init.js +109 -0
  28. package/dist/cli/commands/init.js.map +1 -0
  29. package/dist/cli/commands/inspect.d.ts +1 -0
  30. package/dist/cli/commands/inspect.js +32 -0
  31. package/dist/cli/commands/inspect.js.map +1 -0
  32. package/dist/cli/commands/install.d.ts +33 -0
  33. package/dist/cli/commands/install.js +66 -0
  34. package/dist/cli/commands/install.js.map +1 -0
  35. package/dist/cli/commands/login.d.ts +10 -0
  36. package/dist/cli/commands/login.js +49 -0
  37. package/dist/cli/commands/login.js.map +1 -0
  38. package/dist/cli/commands/measure.d.ts +1 -0
  39. package/dist/cli/commands/measure.js +36 -0
  40. package/dist/cli/commands/measure.js.map +1 -0
  41. package/dist/cli/commands/open-app.d.ts +14 -0
  42. package/dist/cli/commands/open-app.js +45 -0
  43. package/dist/cli/commands/open-app.js.map +1 -0
  44. package/dist/cli/commands/preview.d.ts +8 -0
  45. package/dist/cli/commands/preview.js +55 -0
  46. package/dist/cli/commands/preview.js.map +1 -0
  47. package/dist/cli/commands/record.d.ts +10 -0
  48. package/dist/cli/commands/record.js +86 -0
  49. package/dist/cli/commands/record.js.map +1 -0
  50. package/dist/cli/commands/render.d.ts +1 -0
  51. package/dist/cli/commands/render.js +93 -0
  52. package/dist/cli/commands/render.js.map +1 -0
  53. package/dist/cli/commands/review.d.ts +6 -0
  54. package/dist/cli/commands/review.js +89 -0
  55. package/dist/cli/commands/review.js.map +1 -0
  56. package/dist/cli/commands/sheet.d.ts +1 -0
  57. package/dist/cli/commands/sheet.js +48 -0
  58. package/dist/cli/commands/sheet.js.map +1 -0
  59. package/dist/cli/commands/stability.d.ts +14 -0
  60. package/dist/cli/commands/stability.js +110 -0
  61. package/dist/cli/commands/stability.js.map +1 -0
  62. package/dist/cli/main.d.ts +2 -0
  63. package/dist/cli/main.js +69 -0
  64. package/dist/cli/main.js.map +1 -0
  65. package/dist/cli/usage.d.ts +10 -0
  66. package/dist/cli/usage.js +46 -0
  67. package/dist/cli/usage.js.map +1 -0
  68. package/dist/config.d.ts +250 -0
  69. package/dist/config.js +154 -0
  70. package/dist/config.js.map +1 -0
  71. package/dist/core/camera.d.ts +30 -0
  72. package/dist/core/camera.js +94 -0
  73. package/dist/core/camera.js.map +1 -0
  74. package/dist/core/compose.d.ts +38 -0
  75. package/dist/core/compose.js +81 -0
  76. package/dist/core/compose.js.map +1 -0
  77. package/dist/core/cursor.d.ts +38 -0
  78. package/dist/core/cursor.js +103 -0
  79. package/dist/core/cursor.js.map +1 -0
  80. package/dist/core/easing.d.ts +15 -0
  81. package/dist/core/easing.js +33 -0
  82. package/dist/core/easing.js.map +1 -0
  83. package/dist/core/loop.d.ts +30 -0
  84. package/dist/core/loop.js +96 -0
  85. package/dist/core/loop.js.map +1 -0
  86. package/dist/core/motion-defaults.d.ts +61 -0
  87. package/dist/core/motion-defaults.js +62 -0
  88. package/dist/core/motion-defaults.js.map +1 -0
  89. package/dist/core/rng.d.ts +13 -0
  90. package/dist/core/rng.js +27 -0
  91. package/dist/core/rng.js.map +1 -0
  92. package/dist/core/sse.d.ts +15 -0
  93. package/dist/core/sse.js +16 -0
  94. package/dist/core/sse.js.map +1 -0
  95. package/dist/core/timeline.d.ts +146 -0
  96. package/dist/core/timeline.js +81 -0
  97. package/dist/core/timeline.js.map +1 -0
  98. package/dist/core/timing.d.ts +31 -0
  99. package/dist/core/timing.js +29 -0
  100. package/dist/core/timing.js.map +1 -0
  101. package/dist/core/typing.d.ts +12 -0
  102. package/dist/core/typing.js +35 -0
  103. package/dist/core/typing.js.map +1 -0
  104. package/dist/driver/clip.d.ts +71 -0
  105. package/dist/driver/clip.js +120 -0
  106. package/dist/driver/clip.js.map +1 -0
  107. package/dist/driver/compare.d.ts +34 -0
  108. package/dist/driver/compare.js +40 -0
  109. package/dist/driver/compare.js.map +1 -0
  110. package/dist/driver/gate.d.ts +36 -0
  111. package/dist/driver/gate.js +27 -0
  112. package/dist/driver/gate.js.map +1 -0
  113. package/dist/driver/launch.d.ts +43 -0
  114. package/dist/driver/launch.js +47 -0
  115. package/dist/driver/launch.js.map +1 -0
  116. package/dist/driver/page-hooks.d.ts +72 -0
  117. package/dist/driver/page-hooks.js +129 -0
  118. package/dist/driver/page-hooks.js.map +1 -0
  119. package/dist/driver/reports.d.ts +34 -0
  120. package/dist/driver/reports.js +42 -0
  121. package/dist/driver/reports.js.map +1 -0
  122. package/dist/driver/session.d.ts +285 -0
  123. package/dist/driver/session.js +773 -0
  124. package/dist/driver/session.js.map +1 -0
  125. package/dist/driver/settle.d.ts +41 -0
  126. package/dist/driver/settle.js +82 -0
  127. package/dist/driver/settle.js.map +1 -0
  128. package/dist/env.d.ts +11 -0
  129. package/dist/env.js +41 -0
  130. package/dist/env.js.map +1 -0
  131. package/dist/fixtures.d.ts +13 -0
  132. package/dist/fixtures.js +13 -0
  133. package/dist/fixtures.js.map +1 -0
  134. package/dist/index.d.ts +30 -0
  135. package/dist/index.js +16 -0
  136. package/dist/index.js.map +1 -0
  137. package/dist/inspect/inspect.d.ts +70 -0
  138. package/dist/inspect/inspect.js +176 -0
  139. package/dist/inspect/inspect.js.map +1 -0
  140. package/dist/inspect/measure.d.ts +40 -0
  141. package/dist/inspect/measure.js +107 -0
  142. package/dist/inspect/measure.js.map +1 -0
  143. package/dist/loader.d.ts +28 -0
  144. package/dist/loader.js +143 -0
  145. package/dist/loader.js.map +1 -0
  146. package/dist/page/assets/index-DUom5amc.js +1 -0
  147. package/dist/page/index.html +18 -0
  148. package/dist/render/encode.d.ts +40 -0
  149. package/dist/render/encode.js +183 -0
  150. package/dist/render/encode.js.map +1 -0
  151. package/dist/render/ffmpeg.d.ts +13 -0
  152. package/dist/render/ffmpeg.js +72 -0
  153. package/dist/render/ffmpeg.js.map +1 -0
  154. package/dist/render/presentation.d.ts +19 -0
  155. package/dist/render/presentation.js +27 -0
  156. package/dist/render/presentation.js.map +1 -0
  157. package/dist/render/render.d.ts +59 -0
  158. package/dist/render/render.js +144 -0
  159. package/dist/render/render.js.map +1 -0
  160. package/dist/render/sampling.d.ts +47 -0
  161. package/dist/render/sampling.js +129 -0
  162. package/dist/render/sampling.js.map +1 -0
  163. package/dist/render/sequence.d.ts +24 -0
  164. package/dist/render/sequence.js +105 -0
  165. package/dist/render/sequence.js.map +1 -0
  166. package/dist/render/serve.d.ts +35 -0
  167. package/dist/render/serve.js +124 -0
  168. package/dist/render/serve.js.map +1 -0
  169. package/dist/review/review.d.ts +54 -0
  170. package/dist/review/review.js +229 -0
  171. package/dist/review/review.js.map +1 -0
  172. package/dist/scene.d.ts +23 -0
  173. package/dist/scene.js +2 -0
  174. package/dist/scene.js.map +1 -0
  175. package/dsl.md +119 -0
  176. package/package.json +76 -0
  177. package/plugin/.claude-plugin/plugin.json +9 -0
  178. package/plugin/README.md +105 -0
  179. package/plugin/agents/versioncam-reviewer.md +63 -0
  180. package/plugin/skills/versioncam/SKILL.md +235 -0
  181. package/plugin/skills/versioncam/authoring.md +226 -0
  182. package/plugin/skills/versioncam/onboarding.md +199 -0
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Where the things matching a selector actually are.
3
+ *
4
+ * A drag or a lasso is authored as fractions of a target's box, and fractions
5
+ * chosen by guesswork enclose nothing — which is silent, because an empty
6
+ * selection is a legal state that simply fails to render whatever depended on
7
+ * it. Measuring first is what made every working stroke in the prototype work.
8
+ */
9
+ /**
10
+ * Above this many matches, a list of boxes is a wall of numbers and the grid
11
+ * is the answer. Below it, the boxes are the answer: a handful of markers is
12
+ * exactly the case where an author needs to know where each one is.
13
+ */
14
+ const MAX_LISTED = 12;
15
+ export async function measureSelector(page, selector) {
16
+ return page.evaluate(({ sel, maxListed }) => {
17
+ const cols = 12;
18
+ const rows = 8;
19
+ const grid = Array.from({ length: rows }, () => new Array(cols).fill(0));
20
+ const viewport = { width: window.innerWidth, height: window.innerHeight };
21
+ const matches = Array.from(document.querySelectorAll(sel));
22
+ const boxes = matches.map((el) => {
23
+ const r = el.getBoundingClientRect();
24
+ return { x: r.x, y: r.y, w: r.width, h: r.height };
25
+ });
26
+ // `innerText` where there is one: it is the text as rendered, so a hidden
27
+ // span does not speak and a line break is a space. SVG has only
28
+ // `textContent`. Long text is cut, because a match can be a whole list.
29
+ const texts = matches.map((el) => {
30
+ const raw = el.innerText ?? el.textContent ?? "";
31
+ const text = raw.replace(/\s+/g, " ").trim();
32
+ return text.length > 80 ? `${text.slice(0, 79)}…` : text;
33
+ });
34
+ if (boxes.length === 0) {
35
+ return {
36
+ selector: sel,
37
+ count: 0,
38
+ boxes: [],
39
+ texts: [],
40
+ bounds: null,
41
+ fractions: null,
42
+ viewport,
43
+ grid,
44
+ };
45
+ }
46
+ // Over boxes, not over centres. Centres collapse a single match to a
47
+ // point — bounds of zero size, from which no framing can be planned —
48
+ // and understate a cluster by half an element on every side.
49
+ const bounds = {
50
+ x0: Math.min(...boxes.map((b) => b.x)),
51
+ y0: Math.min(...boxes.map((b) => b.y)),
52
+ x1: Math.max(...boxes.map((b) => b.x + b.w)),
53
+ y1: Math.max(...boxes.map((b) => b.y + b.h)),
54
+ };
55
+ // The grid stays a count of centres: it answers "where is the mass",
56
+ // and a box spanning cells would smear that.
57
+ for (const b of boxes) {
58
+ const c = Math.min(cols - 1, Math.max(0, Math.floor(((b.x + b.w / 2) / viewport.width) * cols)));
59
+ const r = Math.min(rows - 1, Math.max(0, Math.floor(((b.y + b.h / 2) / viewport.height) * rows)));
60
+ grid[r][c] += 1;
61
+ }
62
+ return {
63
+ selector: sel,
64
+ count: boxes.length,
65
+ boxes: boxes.length <= maxListed ? boxes : [],
66
+ texts: boxes.length <= maxListed ? texts : [],
67
+ bounds,
68
+ fractions: {
69
+ x0: bounds.x0 / viewport.width,
70
+ y0: bounds.y0 / viewport.height,
71
+ x1: bounds.x1 / viewport.width,
72
+ y1: bounds.y1 / viewport.height,
73
+ },
74
+ viewport,
75
+ grid,
76
+ };
77
+ }, { sel: selector, maxListed: MAX_LISTED });
78
+ }
79
+ export function formatMeasurement(m) {
80
+ if (m.count === 0)
81
+ return `${m.selector}: nothing matched.`;
82
+ const b = m.bounds;
83
+ const f = m.fractions;
84
+ const lines = [
85
+ `${m.selector}: ${m.count} match(es) in ${m.viewport.width}x${m.viewport.height}`,
86
+ ` bounds px x ${b.x0.toFixed(0)}–${b.x1.toFixed(0)} y ${b.y0.toFixed(0)}–${b.y1.toFixed(0)}`,
87
+ ` bounds frac x ${f.x0.toFixed(2)}–${f.x1.toFixed(2)} y ${f.y0.toFixed(2)}–${f.y1.toFixed(2)}`,
88
+ ];
89
+ if (m.boxes.length > 0) {
90
+ lines.push(" boxes:");
91
+ for (const [index, box] of m.boxes.entries()) {
92
+ lines.push(` ${String(index).padStart(2)} @ ${box.x.toFixed(0)},${box.y.toFixed(0)} ` +
93
+ `${box.w.toFixed(0)}x${box.h.toFixed(0)} text=${JSON.stringify(m.texts[index] ?? "")}`);
94
+ }
95
+ return lines.join("\n");
96
+ }
97
+ lines.push(" density (rows = y, cols = x):");
98
+ for (const [r, row] of m.grid.entries()) {
99
+ const y = ((r + 0.5) / m.grid.length).toFixed(2);
100
+ lines.push(` y=${y} |` +
101
+ row.map((v) => (v === 0 ? " ." : String(v).padStart(5))).join(""));
102
+ }
103
+ lines.push(" x =" +
104
+ Array.from({ length: 12 }, (_, c) => ((c + 0.5) / 12).toFixed(2).slice(1).padStart(5)).join(""));
105
+ return lines.join("\n");
106
+ }
107
+ //# sourceMappingURL=measure.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"measure.js","sourceRoot":"","sources":["../../src/inspect/measure.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ import type { ResolvedConfig } from "./config.js";
2
+ import type { ClipDefinition } from "./driver/clip.js";
3
+ export declare function bundleModule(entry: string, platform: "node" | "browser", cacheDir: string): Promise<string>;
4
+ /** Import a bundle fresh, defeating the ESM module cache between runs. */
5
+ export declare function importFresh<T>(file: string): Promise<T>;
6
+ /**
7
+ * What to do about a missing config, wherever one is found missing.
8
+ *
9
+ * It is the first thing someone new meets — `doctor` in a fresh app, or any
10
+ * command run before the skill has — and the skill's first run writes a
11
+ * better config than a person starting from the type would, so that comes
12
+ * first; by hand is the way for anyone not using the skill.
13
+ */
14
+ export declare const NO_CONFIG: string;
15
+ export declare function findConfigFile(cwd: string): string | null;
16
+ export declare function loadConfig(cwd: string): Promise<ResolvedConfig>;
17
+ /**
18
+ * Every clip the config points at, in file order.
19
+ *
20
+ * `only` filters by substring against the clip id, which is how
21
+ * `versioncam record 05` works: an id is long and nobody wants to type one in
22
+ * full.
23
+ */
24
+ export declare function loadClips(config: ResolvedConfig, only?: string[]): Promise<ClipDefinition[]>;
25
+ /** The app's scenes, bundled for the browser. Null when it has none. */
26
+ export declare function bundleScenes(config: ResolvedConfig): Promise<string | null>;
27
+ /** Scene ids, without running a browser: needed to list and to filter. */
28
+ export declare function loadSceneIds(config: ResolvedConfig): Promise<string[]>;
package/dist/loader.js ADDED
@@ -0,0 +1,143 @@
1
+ import { build } from "esbuild";
2
+ import { createHash } from "node:crypto";
3
+ import { existsSync } from "node:fs";
4
+ import { mkdir } from "node:fs/promises";
5
+ import { basename, dirname, join, resolve } from "node:path";
6
+ import { pathToFileURL } from "node:url";
7
+ import { resolveConfig } from "./config.js";
8
+ /**
9
+ * Loads the TypeScript an app writes: its config, its clips, its scenes.
10
+ *
11
+ * One mechanism for all three, which is esbuild. Node's native type stripping
12
+ * was the tempting alternative — no dependency at all — but it requires
13
+ * explicit `.ts` extensions on every relative import, which is not how anyone
14
+ * writes an import, and it would pin apps to a Node minor. A bundler also
15
+ * gives the scenes their browser build for free, which they need anyway.
16
+ */
17
+ const CACHE_VERSION = 1;
18
+ export async function bundleModule(entry, platform, cacheDir) {
19
+ await mkdir(cacheDir, { recursive: true });
20
+ const key = createHash("sha1")
21
+ .update(`${CACHE_VERSION}:${platform}:${entry}`)
22
+ .digest("hex")
23
+ .slice(0, 12);
24
+ const outfile = join(cacheDir, `${basename(entry, ".ts")}.${key}.mjs`);
25
+ await build({
26
+ entryPoints: [entry],
27
+ outfile,
28
+ bundle: true,
29
+ format: "esm",
30
+ platform,
31
+ target: "es2023",
32
+ sourcemap: "inline",
33
+ logLevel: "silent",
34
+ // Node builds keep their imports live so a clip gets the same package
35
+ // instance the CLI is running, and the same Playwright.
36
+ external: platform === "node"
37
+ ? ["versioncam", "versioncam/*", "playwright", "node:*"]
38
+ : [],
39
+ });
40
+ return outfile;
41
+ }
42
+ /** Import a bundle fresh, defeating the ESM module cache between runs. */
43
+ export async function importFresh(file) {
44
+ const url = `${pathToFileURL(file).href}?t=${Date.now()}`;
45
+ return (await import(url));
46
+ }
47
+ const CONFIG_NAMES = [
48
+ "versioncam.config.ts",
49
+ "versioncam.config.js",
50
+ "versioncam.config.mjs",
51
+ ];
52
+ /**
53
+ * What to do about a missing config, wherever one is found missing.
54
+ *
55
+ * It is the first thing someone new meets — `doctor` in a fresh app, or any
56
+ * command run before the skill has — and the skill's first run writes a
57
+ * better config than a person starting from the type would, so that comes
58
+ * first; by hand is the way for anyone not using the skill.
59
+ */
60
+ export const NO_CONFIG = "`npx versioncam init`, then `/versioncam`: its first run writes one from the repository.\n" +
61
+ "Or by hand: a default export from defineRecorder(), as in the README's Configure section.";
62
+ export function findConfigFile(cwd) {
63
+ for (const name of CONFIG_NAMES) {
64
+ const candidate = resolve(cwd, name);
65
+ if (existsSync(candidate))
66
+ return candidate;
67
+ }
68
+ return null;
69
+ }
70
+ export async function loadConfig(cwd) {
71
+ const file = findConfigFile(cwd);
72
+ if (!file) {
73
+ throw new Error(`No versioncam.config.ts in ${cwd}.\n${NO_CONFIG}`);
74
+ }
75
+ // The cache directory comes from the config we have not read yet, so the
76
+ // bundle goes next to the config until we know better.
77
+ const bootstrapCache = resolve(dirname(file), ".versioncam", ".cache");
78
+ const bundle = await bundleModule(file, "node", bootstrapCache);
79
+ const module = await importFresh(bundle);
80
+ if (!module.default) {
81
+ throw new Error(`${file} must have a default export from defineRecorder().`);
82
+ }
83
+ return resolveConfig(module.default, dirname(file));
84
+ }
85
+ /**
86
+ * Every clip the config points at, in file order.
87
+ *
88
+ * `only` filters by substring against the clip id, which is how
89
+ * `versioncam record 05` works: an id is long and nobody wants to type one in
90
+ * full.
91
+ */
92
+ export async function loadClips(config, only = []) {
93
+ const { glob } = await import("node:fs/promises");
94
+ const files = [];
95
+ for (const pattern of config.clips) {
96
+ for await (const match of glob(pattern, { cwd: config.root })) {
97
+ files.push(resolve(config.root, match));
98
+ }
99
+ }
100
+ files.sort();
101
+ const clips = [];
102
+ for (const file of files) {
103
+ const bundle = await bundleModule(file, "node", config.cacheDir);
104
+ const module = await importFresh(bundle);
105
+ if (!module.default) {
106
+ throw new Error(`${file} must default-export a clip (or an array of them).`);
107
+ }
108
+ for (const clip of [module.default].flat()) {
109
+ if (typeof clip?.id !== "string" || typeof clip?.body !== "function") {
110
+ throw new Error(`${file} default-exports something that is not a clip.`);
111
+ }
112
+ clips.push(clip);
113
+ }
114
+ }
115
+ const seen = new Set();
116
+ for (const clip of clips) {
117
+ if (seen.has(clip.id)) {
118
+ throw new Error(`Two clips share the id "${clip.id}".`);
119
+ }
120
+ seen.add(clip.id);
121
+ }
122
+ return only.length === 0
123
+ ? clips
124
+ : clips.filter((clip) => only.some((needle) => clip.id.includes(needle)));
125
+ }
126
+ /** The app's scenes, bundled for the browser. Null when it has none. */
127
+ export async function bundleScenes(config) {
128
+ if (!config.scenes)
129
+ return null;
130
+ if (!existsSync(config.scenes)) {
131
+ throw new Error(`versioncam config: \`scenes\` points at a missing file: ${config.scenes}`);
132
+ }
133
+ return bundleModule(config.scenes, "browser", config.cacheDir);
134
+ }
135
+ /** Scene ids, without running a browser: needed to list and to filter. */
136
+ export async function loadSceneIds(config) {
137
+ if (!config.scenes)
138
+ return [];
139
+ const bundle = await bundleModule(config.scenes, "node", config.cacheDir);
140
+ const module = await importFresh(bundle);
141
+ return Object.keys(module.default ?? {});
142
+ }
143
+ //# sourceMappingURL=loader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loader.js","sourceRoot":"","sources":["../src/loader.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAChC,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACzC,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC7D,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAI5C;;;;;;;;GAQG;AAEH,MAAM,aAAa,GAAG,CAAC,CAAC;AAExB,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,KAAa,EACb,QAA4B,EAC5B,QAAgB;IAEhB,MAAM,KAAK,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC3C,MAAM,GAAG,GAAG,UAAU,CAAC,MAAM,CAAC;SAC3B,MAAM,CAAC,GAAG,aAAa,IAAI,QAAQ,IAAI,KAAK,EAAE,CAAC;SAC/C,MAAM,CAAC,KAAK,CAAC;SACb,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAChB,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,EAAE,GAAG,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC,IAAI,GAAG,MAAM,CAAC,CAAC;IAEvE,MAAM,KAAK,CAAC;QACV,WAAW,EAAE,CAAC,KAAK,CAAC;QACpB,OAAO;QACP,MAAM,EAAE,IAAI;QACZ,MAAM,EAAE,KAAK;QACb,QAAQ;QACR,MAAM,EAAE,QAAQ;QAChB,SAAS,EAAE,QAAQ;QACnB,QAAQ,EAAE,QAAQ;QAClB,sEAAsE;QACtE,wDAAwD;QACxD,QAAQ,EACN,QAAQ,KAAK,MAAM;YACjB,CAAC,CAAC,CAAC,YAAY,EAAE,cAAc,EAAE,YAAY,EAAE,QAAQ,CAAC;YACxD,CAAC,CAAC,EAAE;KACT,CAAC,CAAC;IAEH,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,0EAA0E;AAC1E,MAAM,CAAC,KAAK,UAAU,WAAW,CAAI,IAAY;IAC/C,MAAM,GAAG,GAAG,GAAG,aAAa,CAAC,IAAI,CAAC,CAAC,IAAI,MAAM,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC;IAC1D,OAAO,CAAC,MAAM,MAAM,CAAC,GAAG,CAAC,CAAM,CAAC;AAClC,CAAC;AAED,MAAM,YAAY,GAAG;IACnB,sBAAsB;IACtB,sBAAsB;IACtB,uBAAuB;CACxB,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,SAAS,GACpB,4FAA4F;IAC5F,2FAA2F,CAAC;AAE9F,MAAM,UAAU,cAAc,CAAC,GAAW;IACxC,KAAK,MAAM,IAAI,IAAI,YAAY,EAAE,CAAC;QAChC,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;QACrC,IAAI,UAAU,CAAC,SAAS,CAAC;YAAE,OAAO,SAAS,CAAC;IAC9C,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,GAAW;IAC1C,MAAM,IAAI,GAAG,cAAc,CAAC,GAAG,CAAC,CAAC;IACjC,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,MAAM,IAAI,KAAK,CAAC,8BAA8B,GAAG,MAAM,SAAS,EAAE,CAAC,CAAC;IACtE,CAAC;IACD,yEAAyE;IACzE,uDAAuD;IACvD,MAAM,cAAc,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,aAAa,EAAE,QAAQ,CAAC,CAAC;IACvE,MAAM,MAAM,GAAG,MAAM,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,cAAc,CAAC,CAAC;IAChE,MAAM,MAAM,GAAG,MAAM,WAAW,CAA+B,MAAM,CAAC,CAAC;IACvE,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACpB,MAAM,IAAI,KAAK,CACb,GAAG,IAAI,oDAAoD,CAC5D,CAAC;IACJ,CAAC;IACD,OAAO,aAAa,CAAC,MAAM,CAAC,OAAO,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;AACtD,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,SAAS,CAC7B,MAAsB,EACtB,OAAiB,EAAE;IAEnB,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,MAAM,CAAC,kBAAkB,CAAC,CAAC;IAClD,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,KAAK,MAAM,OAAO,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;QACnC,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,IAAI,CAAC,OAAO,EAAE,EAAE,GAAG,EAAE,MAAM,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC;YAC9D,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC;QAC1C,CAAC;IACH,CAAC;IACD,KAAK,CAAC,IAAI,EAAE,CAAC;IAEb,MAAM,KAAK,GAAqB,EAAE,CAAC;IACnC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,MAAM,GAAG,MAAM,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC;QACjE,MAAM,MAAM,GAAG,MAAM,WAAW,CAE7B,MAAM,CAAC,CAAC;QACX,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YACpB,MAAM,IAAI,KAAK,CACb,GAAG,IAAI,oDAAoD,CAC5D,CAAC;QACJ,CAAC;QACD,KAAK,MAAM,IAAI,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC;YAC3C,IAAI,OAAO,IAAI,EAAE,EAAE,KAAK,QAAQ,IAAI,OAAO,IAAI,EAAE,IAAI,KAAK,UAAU,EAAE,CAAC;gBACrE,MAAM,IAAI,KAAK,CACb,GAAG,IAAI,gDAAgD,CACxD,CAAC;YACJ,CAAC;YACD,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACnB,CAAC;IACH,CAAC;IAED,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC;YACtB,MAAM,IAAI,KAAK,CAAC,2BAA2B,IAAI,CAAC,EAAE,IAAI,CAAC,CAAC;QAC1D,CAAC;QACD,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACpB,CAAC;IAED,OAAO,IAAI,CAAC,MAAM,KAAK,CAAC;QACtB,CAAC,CAAC,KAAK;QACP,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;AAC9E,CAAC;AAED,wEAAwE;AACxE,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,MAAsB;IAEtB,IAAI,CAAC,MAAM,CAAC,MAAM;QAAE,OAAO,IAAI,CAAC;IAChC,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC;QAC/B,MAAM,IAAI,KAAK,CACb,2DAA2D,MAAM,CAAC,MAAM,EAAE,CAC3E,CAAC;IACJ,CAAC;IACD,OAAO,YAAY,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC;AACjE,CAAC;AAED,0EAA0E;AAC1E,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,MAAsB;IACvD,IAAI,CAAC,MAAM,CAAC,MAAM;QAAE,OAAO,EAAE,CAAC;IAC9B,MAAM,MAAM,GAAG,MAAM,YAAY,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC;IAC1E,MAAM,MAAM,GAAG,MAAM,WAAW,CAC9B,MAAM,CACP,CAAC;IACF,OAAO,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC;AAC3C,CAAC"}
@@ -0,0 +1 @@
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(t,n){if(t.length===0)throw Error(`evalCamera called with no keys`);if(n<=t[0].t)return t[0].rect;for(let i=1;i<t.length;i+=1){let a=t[i-1],o=t[i];if(n>=o.t)continue;if(o.ease===`hold`)return a.rect;let s=o.t-a.t,c=s<=0?1:(n-a.t)/s;return r(a.rect,o.rect,e(c))}return t[t.length-1].rect}function a(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 o(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 a(r.from,r.ctrl,r.to,e(i))}let i=t[t.length-1];return i.kind===`move`?i.to:i.at}function s(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 c(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 l(e,t){return{x:e.x*t,y:e.y*t,w:e.w*t,h:e.h*t}}function u(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 d(e,n,r){return r<=0?1:t((e-n+1)/r)}function f(e,n,r,i){return e<=n||e>=r?0:i<=0?1:Math.min(t((e-n)/i),t((r-e)/i))}function p(e,t){return e/t}function m(e,t,n){let r=n?.padding??0;return!n||r<=0?{x:0,y:0,w:e.width,h:e.height}:u(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`}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 _(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 v(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 y(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 b(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:d(t,r+1,n)};return{current:e.frames[t],previous:null,alpha:1}}function x(e,t,n,r,i,a,o){let s=l(i,t.viewport.dpr),{current:c,previous:u,alpha:d}=b(t,r,o.motion.crossfadeFrames),f=o.theme.presentation?.frame??!1;e.save(),f&&(v(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))};u!==null&&u!==c&&p(u,1),p(c,u===null?1:d),e.globalAlpha=1,e.restore()}function S(e,t){e.save(),e.shadowColor=g.shadow,e.shadowBlur=g.shadowBlur,e.shadowOffsetY=g.shadowOffsetY,e.fillStyle=`rgba(0,0,0,1)`,v(e,t,g.radius),e.fill(),e.restore()}function C(e,t){e.save(),e.strokeStyle=g.hairline,e.lineWidth=1,v(e,t,g.radius),e.stroke(),e.restore()}function w(e,t){return{x:e.x-t,y:e.y-t,w:e.w+t*2,h:e.h+t*2}}function T(e,t,n,r,i,a){let o=a.theme.presentation?.frame??!1;for(let s of t.highlights){let t=f(n,s.t0,s.t1,.25);if(t<=0)continue;let l=c(s.rect,r,i);e.save(),e.globalAlpha=t,s.style===`spotlight`?(e.save(),v(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`,v(e,w(l,8),g.ringRadius),e.fill(),e.restore()):(e.strokeStyle=a.theme.accent,e.lineWidth=g.ringWidth,e.shadowColor=_(a.theme.accent,.35),e.shadowBlur=18,v(e,w(l,6),g.ringRadius),e.stroke()),e.restore()}}function E(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 D(e,t,n,r,i){for(let a of t){let t=a.age/(i.motion.rippleMs/1e3),[o,c]=s(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,c,6+t*i.motion.rippleRadius,0,Math.PI*2),e.stroke(),e.restore()}}function O(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 k(e,t,n,r,i,a){let o=h(a.theme.presentation);if(o!==`none`)for(let s of t.captions){let t=f(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=o===`overlay`?40:r.x,l=o===`overlay`?i.height-g.captionScrimHeight/2+4:(r.y+r.h+i.height)/2;e.fillText(s.text,c,l),e.restore()}}function A(e,t,n,r,a,c){let l=p(r,t.fps),u=i(t.camera,l),d=c.theme.presentation,f=m(a,t.viewport,d);d?.backdrop&&y(e,a,d.backdrop),d?.frame&&S(e,f),x(e,t,n,r,u,f,c),d?.frame&&C(e,f),T(e,t,l,u,f,c);let{scale:h,ripples:g}=E(t,l,c);D(e,g,u,f,c),O(e,s(o(t.cursor,l),u,f),h,c.theme.cursorScale),k(e,t,l,f,a,c)}var j=`modulepreload`,M=function(e,t){return new URL(e,t).href},N={},P=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=M(t,n),t=s(t),t in N)return;N[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`:j,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)})},F=new URLSearchParams(location.search),I=F.has(`recording`),L=F.get(`scene`),R=F.get(`headless`)===`1`;R&&document.body.classList.add(`headless`);var z=document.getElementById(`stage`),B=document.getElementById(`scrub`),V=document.getElementById(`status`),H=document.getElementById(`play`),U=document.getElementById(`error`);function W(e){throw U.textContent=e,Error(e)}async function G(e,t){let n=await fetch(e);return n.ok||W(`Could not load ${t} (${e}): ${n.status}`),await n.json()}async function K(e){let t=await fetch(e);return t.ok||W(`Could not load ${e}: ${t.status}`),createImageBitmap(await t.blob())}function q(e){z.width=e.output.width,z.height=e.output.height,R?(z.style.width=`${e.output.width}px`,z.style.height=`${e.output.height}px`):(z.style.width=`min(100%, 1280px)`,z.style.height=`auto`);let t=z.getContext(`2d`,{alpha:!1});return t||W(`No 2D context`),t.imageSmoothingEnabled=!0,t.imageSmoothingQuality=`high`,t}function J(e,t,n,r){let i=!1;H.addEventListener(`click`,()=>{i=!i,H.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 Y(e,t,n,r){e.durationFrames=t,e.fps=n,e.setFrame=e=>new Promise(t=>{r(e),requestAnimationFrame(()=>requestAnimationFrame(()=>t()))})}async function X(e,t){let n=await G(`/recording/timeline.json`,`the recording`),r=q(t),i={images:await Promise.all(n.states.map(e=>K(`/recording/states/${e}`)))};await document.fonts.ready;let a=-1,o=e=>{let o=Math.max(0,Math.min(n.durationFrames-1,e));A(r,n,i,o,z,t),a=o,R||(B.value=String(o),V.textContent=`${o+1} / ${n.durationFrames} · ${(o/n.fps).toFixed(2)}s · state ${n.frames[o]}`)};B.max=String(n.durationFrames-1),B.addEventListener(`input`,()=>o(Number(B.value))),J(n.fps,n.durationFrames,()=>a,o),o(Number(F.get(`frame`)??0)),Y(e,n.durationFrames,n.fps,o)}async function Z(e,t,n){let r=(await P(()=>import(`/scenes.js`),[],import.meta.url)).default,i=r?.[n];i||W(`No scene "${n}". The scenes module exports: ${Object.keys(r??{}).join(`, `)||`(nothing)`}.`);let a=q(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,z),o=t,R||(B.value=String(t),V.textContent=`${t+1} / ${i.durationFrames} · ${(t/i.fps).toFixed(2)}s`)};B.max=String(i.durationFrames-1),B.addEventListener(`input`,()=>s(Number(B.value))),J(i.fps,i.durationFrames,()=>o,s),s(Number(F.get(`frame`)??0)),Y(e,i.durationFrames,i.fps,s)}async function Q(e){let t=await G(`/render-config.json`,`the render config`);if(L)return Z(e,t,L);if(I)return X(e,t);W(`Pass ?recording or ?scene=<id>.`)}var $={ready:Promise.resolve(),setFrame:async()=>{},durationFrames:0,fps:60};window.__render=$,$.ready=Q($);
@@ -0,0 +1,18 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <title>versioncam — render</title>
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-DUom5amc.js"></script>
8
+ </head>
9
+ <body>
10
+ <canvas id="stage"></canvas>
11
+ <div id="controls">
12
+ <button id="play" type="button">Play</button>
13
+ <input id="scrub" type="range" min="0" max="0" step="1" value="0" />
14
+ <span id="status">–</span>
15
+ </div>
16
+ <pre id="error"></pre>
17
+ </body>
18
+ </html>
@@ -0,0 +1,40 @@
1
+ import type { Page } from "playwright";
2
+ export type EncodeResult = {
3
+ frames: number;
4
+ mp4: string;
5
+ webm: string;
6
+ poster: string;
7
+ };
8
+ export type EncodeOptions = {
9
+ id: string;
10
+ fps: number;
11
+ durationFrames: number;
12
+ /** Frame used for the poster image; defaults to a third of the way in. */
13
+ posterFrame?: number;
14
+ outDir: string;
15
+ };
16
+ /**
17
+ * Walk the render page frame by frame, screenshotting each one into both
18
+ * encoders. The page is a pure function of the frame number, so this loop can
19
+ * be re-run, interrupted or parallelised without changing its output.
20
+ */
21
+ export declare function encodeFromPage(page: Page, options: EncodeOptions): Promise<EncodeResult>;
22
+ /** A strip of frames, for reviewing a clip in a pull request. */
23
+ export declare function contactSheet(page: Page, options: {
24
+ id: string;
25
+ durationFrames: number;
26
+ count?: number;
27
+ /** Exactly which frames to show. Evenly spaced when absent. */
28
+ frames?: number[];
29
+ outDir: string;
30
+ }): Promise<string>;
31
+ declare global {
32
+ interface Window {
33
+ __render?: {
34
+ ready: Promise<void>;
35
+ setFrame: (frame: number) => Promise<void>;
36
+ durationFrames: number;
37
+ fps: number;
38
+ };
39
+ }
40
+ }
@@ -0,0 +1,183 @@
1
+ import { spawn } from "node:child_process";
2
+ import { mkdirSync, writeFileSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { ffmpegPath } from "./ffmpeg.js";
5
+ import { uniformFrames } from "./sampling.js";
6
+ /**
7
+ * Pipe PNG frames into ffmpeg twice — once for H.264, once for VP9.
8
+ *
9
+ * Two encoders rather than a transcode because both come from the same
10
+ * lossless frames; transcoding the mp4 into a webm would compound artefacts
11
+ * for no saving worth having on a ten-second clip.
12
+ */
13
+ function startEncoder(args) {
14
+ const child = spawn(ffmpegPath(), args, {
15
+ stdio: ["pipe", "ignore", "pipe"],
16
+ });
17
+ let stderr = "";
18
+ child.stderr?.on("data", (chunk) => {
19
+ stderr += chunk.toString();
20
+ });
21
+ child.on("error", (error) => {
22
+ throw new Error(`ffmpeg could not be started (${error.message}). Install it, or add ` +
23
+ "the optional ffmpeg-static dependency.");
24
+ });
25
+ child.on("close", (code) => {
26
+ if (code !== 0) {
27
+ console.error(stderr.split("\n").slice(-25).join("\n"));
28
+ throw new Error(`ffmpeg exited with ${code}`);
29
+ }
30
+ });
31
+ return child;
32
+ }
33
+ function mp4Args(fps, out) {
34
+ return [
35
+ "-y",
36
+ "-f",
37
+ "image2pipe",
38
+ "-framerate",
39
+ String(fps),
40
+ "-c:v",
41
+ "png",
42
+ "-i",
43
+ "-",
44
+ "-an",
45
+ "-c:v",
46
+ "libx264",
47
+ "-preset",
48
+ "slow",
49
+ "-crf",
50
+ "18",
51
+ "-pix_fmt",
52
+ "yuv420p",
53
+ "-profile:v",
54
+ "high",
55
+ // Lets a browser start playing before the whole file has downloaded.
56
+ "-movflags",
57
+ "+faststart",
58
+ out,
59
+ ];
60
+ }
61
+ function webmArgs(fps, out) {
62
+ return [
63
+ "-y",
64
+ "-f",
65
+ "image2pipe",
66
+ "-framerate",
67
+ String(fps),
68
+ "-c:v",
69
+ "png",
70
+ "-i",
71
+ "-",
72
+ "-an",
73
+ "-c:v",
74
+ "libvpx-vp9",
75
+ "-crf",
76
+ "30",
77
+ "-b:v",
78
+ "0",
79
+ "-row-mt",
80
+ "1",
81
+ out,
82
+ ];
83
+ }
84
+ function writeAll(stream, buffer) {
85
+ return new Promise((resolveWrite, rejectWrite) => {
86
+ // Respect backpressure: ffmpeg is slower than the screenshot loop for the
87
+ // first few seconds and an unbounded queue would hold every frame of the
88
+ // clip in memory at once.
89
+ const ok = stream.write(buffer, (error) => error ? rejectWrite(error) : undefined);
90
+ if (ok)
91
+ resolveWrite();
92
+ else
93
+ stream.once("drain", () => resolveWrite());
94
+ });
95
+ }
96
+ function closeEncoder(child) {
97
+ return new Promise((resolveClose, rejectClose) => {
98
+ child.on("close", (code) => code === 0
99
+ ? resolveClose()
100
+ : rejectClose(new Error(`ffmpeg exited ${code}`)));
101
+ child.stdin?.end();
102
+ });
103
+ }
104
+ /**
105
+ * Walk the render page frame by frame, screenshotting each one into both
106
+ * encoders. The page is a pure function of the frame number, so this loop can
107
+ * be re-run, interrupted or parallelised without changing its output.
108
+ */
109
+ export async function encodeFromPage(page, options) {
110
+ const outDir = options.outDir;
111
+ mkdirSync(outDir, { recursive: true });
112
+ const mp4 = join(outDir, `${options.id}.mp4`);
113
+ const webm = join(outDir, `${options.id}.webm`);
114
+ const poster = join(outDir, `${options.id}-poster.png`);
115
+ const posterFrame = options.posterFrame ?? Math.floor(options.durationFrames / 3);
116
+ const h264 = startEncoder(mp4Args(options.fps, mp4));
117
+ const vp9 = startEncoder(webmArgs(options.fps, webm));
118
+ const stage = page.locator("#stage");
119
+ for (let frame = 0; frame < options.durationFrames; frame += 1) {
120
+ await page.evaluate((n) => window.__render?.setFrame(n), frame);
121
+ const buffer = await stage.screenshot({
122
+ type: "png",
123
+ animations: "disabled",
124
+ });
125
+ if (frame === posterFrame)
126
+ writeFileSync(poster, buffer);
127
+ await writeAll(h264.stdin, buffer);
128
+ await writeAll(vp9.stdin, buffer);
129
+ }
130
+ await Promise.all([closeEncoder(h264), closeEncoder(vp9)]);
131
+ return { frames: options.durationFrames, mp4, webm, poster };
132
+ }
133
+ /** A strip of frames, for reviewing a clip in a pull request. */
134
+ export async function contactSheet(page, options) {
135
+ const outDir = options.outDir;
136
+ mkdirSync(outDir, { recursive: true });
137
+ const frames = options.frames ?? uniformFrames(options.durationFrames, options.count);
138
+ const tileDir = join(outDir, `${options.id}-sheet`);
139
+ mkdirSync(tileDir, { recursive: true });
140
+ const stage = page.locator("#stage");
141
+ const tiles = [];
142
+ for (const [index, frame] of frames.entries()) {
143
+ await page.evaluate((n) => window.__render?.setFrame(n), frame);
144
+ const file = join(tileDir, `${String(index).padStart(2, "0")}.png`);
145
+ writeFileSync(file, await stage.screenshot({ type: "png" }));
146
+ tiles.push(file);
147
+ }
148
+ const sheet = join(outDir, `${options.id}-sheet.png`);
149
+ await new Promise((resolveSheet, rejectSheet) => {
150
+ // `tile` composites frames of ONE stream, so the tiles are fed in as a
151
+ // numbered image sequence rather than as N separate inputs.
152
+ // Two rows once there are enough tiles to fill them, one row before that.
153
+ // `tile` pads a grid it cannot fill, so three frames in a 2x2 gave a fourth
154
+ // cell holding a repeat of the third — a tile a reviewer has to work out is
155
+ // not evidence, which is exactly the work a contact sheet exists to save.
156
+ const rows = frames.length > 4 ? 2 : 1;
157
+ const columns = Math.ceil(frames.length / rows);
158
+ const child = spawn(ffmpegPath(), [
159
+ "-y",
160
+ "-framerate",
161
+ "1",
162
+ "-i",
163
+ join(tileDir, "%02d.png"),
164
+ "-vf",
165
+ `scale=960:-1,tile=${columns}x${rows}`,
166
+ "-frames:v",
167
+ "1",
168
+ sheet,
169
+ ], { stdio: ["ignore", "ignore", "pipe"] });
170
+ let stderr = "";
171
+ child.stderr.on("data", (c) => {
172
+ stderr += c.toString();
173
+ });
174
+ child.on("close", (code) => {
175
+ if (code === 0)
176
+ resolveSheet();
177
+ else
178
+ rejectSheet(new Error(`contact sheet failed:\n${stderr.slice(-800)}`));
179
+ });
180
+ });
181
+ return sheet;
182
+ }
183
+ //# sourceMappingURL=encode.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"encode.js","sourceRoot":"","sources":["../../src/render/encode.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ export declare function ffmpegPath(): string;
2
+ /**
3
+ * ffprobe, which `ffmpeg-static` does not ship — it is a separate package.
4
+ * Only the system one is looked for; without it, duration checks are
5
+ * unavailable rather than wrong.
6
+ */
7
+ export declare function ffprobePath(): string;
8
+ export declare function ffprobeSync(args: string[]): string;
9
+ /** For `versioncam doctor`: report rather than throw. */
10
+ export declare function ffmpegStatus(): {
11
+ ffmpeg: string | null;
12
+ ffprobe: string | null;
13
+ };