versioncam 0.5.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -6,6 +6,28 @@ 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.1 (2026-10-11)
10
+
11
+ A patch version: two clips that came out wrong now record and render as
12
+ they should. The config and the clip DSL are as they were.
13
+
14
+ ### Fixed
15
+
16
+ - A settle whose page did not change, a menu already open or a form already
17
+ filled, no longer dims the whole picture almost to black for a tenth of a
18
+ second after it. The renderer dissolved from the page into itself, drawing
19
+ it see-through over the background. Where two settles come within the
20
+ dissolve of each other, the dissolve now follows the later one, rather than
21
+ fading the new page in, a little back out, and in again.
22
+ - A CSS animation that has run its course now finishes, so its `finished`
23
+ promise resolves, as in a browser nobody is filming. Components that
24
+ unmount once their exit animation has finished, Base UI's Dialog, Popover,
25
+ Menu and Tooltip among them, stayed mounted after they closed: a closed
26
+ dialog's invisible backdrop stayed over the page and took the next click,
27
+ so a link seemed to do nothing and the step after it failed with
28
+ `Nothing matched`. A clip that finished the animations itself to get past
29
+ this no longer needs to.
30
+
9
31
  ## 0.5.0 (2026-10-07)
10
32
 
11
33
  A minor version: commands for version.cam, and the skill learns to use them.
package/README.md CHANGED
@@ -801,7 +801,10 @@ its CSS motion:
801
801
  - **Animations are scrubbed** by the clip's time: a spinner turns at the rate
802
802
  the video plays, whatever the machine is doing. The scrub counts from the
803
803
  clip's start, not the animation's, so a one-off animation that starts
804
- partway through a clip shows its end.
804
+ partway through a clip shows its end. Once a one-off animation's time is
805
+ up it finishes, as in a browser nobody is filming: its `finished` promise
806
+ resolves and `animationend` fires, so a dialog that unmounts after its exit
807
+ animation goes, and does not sit invisibly over the page.
805
808
  - **Not on the clock:** animations a script makes with `element.animate()`,
806
809
  Chromium's smooth scrolling of a mouse wheel, and transitions inside an
807
810
  iframe, which stay zeroed since nothing drives an iframe's clock. A clip
@@ -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;
@@ -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)
@@ -1 +1 @@
1
- {"version":3,"file":"compose.js","sourceRoot":"","sources":["../../src/core/compose.ts"],"names":[],"mappings":"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"}
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,\n // and is no longer running when the animations are counted.\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})();";
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, for the driver's waits
51
- * that move the clock without making a frame. The frame probe and the settle
52
- * loop sync as part of what they already ask the page.
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 = "window.__recorderTransitions ? window.__recorderTransitions.sync() : 0";
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 finished here,
26
- // and is no longer running when the animations are counted.
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, for the driver's waits
136
- * that move the clock without making a frame. The frame probe and the settle
137
- * loop sync as part of what they already ask the page.
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.__recorderTransitions ? window.__recorderTransitions.sync() : 0";
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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;MA0DjC,CAAC;AAEP;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;MA4BjC,CAAC;AAEP;;;;GAIG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAC3B,wEAAwE,CAAC;AAE3E;;;;;;;;;;;;;;;;;;;;;;;;;;;;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"}
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"}
@@ -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 moves the page's transitions to its clock, which
33
- // the last tick advanced, and says how many are still running.
34
- const probe = await page.evaluate(() => ({
35
- mutations: window.__recorder?.mutations ?? 0,
36
- fontsLoaded: document.fonts ? document.fonts.status === "loaded" : true,
37
- transitions: window.__recorderTransitions?.sync() ?? 0,
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":"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"}
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($);
@@ -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-fZtacWyp.js"></script>
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.0",
3
+ "version": "0.5.1",
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",
@@ -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($);