versioncam 0.1.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +196 -46
- package/README.md +486 -243
- package/bin/versioncam.js +1 -1
- package/dist/app-server.js +2 -2
- package/dist/app-server.js.map +1 -1
- package/dist/cli/commands/doctor.js +41 -4
- package/dist/cli/commands/doctor.js.map +1 -1
- package/dist/cli/commands/init.js +26 -1
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/preview.js +10 -1
- package/dist/cli/commands/preview.js.map +1 -1
- package/dist/cli/commands/record.js +3 -2
- package/dist/cli/commands/record.js.map +1 -1
- package/dist/cli/commands/render.d.ts +17 -0
- package/dist/cli/commands/render.js +79 -22
- package/dist/cli/commands/render.js.map +1 -1
- package/dist/cli/commands/stability.js +4 -4
- package/dist/cli/commands/stability.js.map +1 -1
- package/dist/cli/cwd.d.ts +46 -0
- package/dist/cli/cwd.js +102 -0
- package/dist/cli/cwd.js.map +1 -0
- package/dist/cli/main.js +11 -2
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/usage.d.ts +10 -5
- package/dist/cli/usage.js +42 -24
- package/dist/cli/usage.js.map +1 -1
- package/dist/config.d.ts +24 -0
- package/dist/config.js +25 -0
- package/dist/config.js.map +1 -1
- package/dist/core/camera-track.d.ts +63 -0
- package/dist/core/camera-track.js +230 -0
- package/dist/core/camera-track.js.map +1 -0
- package/dist/core/camera.d.ts +14 -1
- package/dist/core/camera.js +18 -2
- package/dist/core/camera.js.map +1 -1
- package/dist/core/follow.d.ts +129 -0
- package/dist/core/follow.js +151 -0
- package/dist/core/follow.js.map +1 -0
- package/dist/core/motion-defaults.d.ts +29 -0
- package/dist/core/motion-defaults.js +29 -0
- package/dist/core/motion-defaults.js.map +1 -1
- package/dist/core/timeline.d.ts +32 -2
- package/dist/core/timeline.js +10 -0
- package/dist/core/timeline.js.map +1 -1
- package/dist/driver/clip.d.ts +4 -1
- package/dist/driver/clip.js +9 -1
- package/dist/driver/clip.js.map +1 -1
- package/dist/driver/session.d.ts +35 -1
- package/dist/driver/session.js +81 -12
- package/dist/driver/session.js.map +1 -1
- package/dist/driver/settle.js +2 -2
- package/dist/driver/settle.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/inspect/inspect.js +1 -1
- package/dist/loader.d.ts +7 -1
- package/dist/loader.js +23 -8
- package/dist/loader.js.map +1 -1
- package/dist/page/assets/index-fZtacWyp.js +2 -0
- package/dist/page/index.html +2 -2
- package/dist/render/encode.d.ts +12 -1
- package/dist/render/encode.js +94 -35
- package/dist/render/encode.js.map +1 -1
- package/dist/render/presentation.d.ts +12 -0
- package/dist/render/presentation.js +15 -0
- package/dist/render/presentation.js.map +1 -1
- package/dist/render/render.js +19 -2
- package/dist/render/render.js.map +1 -1
- package/dist/render/sampling.d.ts +13 -0
- package/dist/render/sampling.js +48 -20
- package/dist/render/sampling.js.map +1 -1
- package/dist/render/scenes.d.ts +16 -0
- package/dist/render/scenes.js +108 -0
- package/dist/render/scenes.js.map +1 -0
- package/dist/render/sequence.d.ts +18 -0
- package/dist/render/sequence.js +67 -13
- package/dist/render/sequence.js.map +1 -1
- package/dist/review/review.js +48 -23
- package/dist/review/review.js.map +1 -1
- package/dsl.md +267 -92
- package/package.json +2 -2
- package/plugin/README.md +58 -47
- package/plugin/agents/versioncam-reviewer.md +3 -2
- package/plugin/skills/versioncam/SKILL.md +60 -14
- package/plugin/skills/versioncam/authoring.md +6 -1
- package/plugin/skills/versioncam/onboarding.md +17 -17
- package/dist/page/assets/index-DUom5amc.js +0 -1
|
@@ -32,19 +32,63 @@ Follow these steps in order. Two more files sit beside this one, in this
|
|
|
32
32
|
skill's directory: `onboarding.md` and `authoring.md`. They are briefs, read
|
|
33
33
|
when a step says to.
|
|
34
34
|
|
|
35
|
-
## 0.
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
`
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
35
|
+
## 0. Where, and which phase
|
|
36
|
+
|
|
37
|
+
Everything below runs in **the app's directory**: the one that holds
|
|
38
|
+
`versioncam.config.ts` (or `.js`, `.mjs`). That need not be the working
|
|
39
|
+
directory. In a monorepo, a session started at the repository root can run
|
|
40
|
+
this skill from an app's `.claude/skills/` further down, and one started
|
|
41
|
+
inside an app can run a skill installed above it. So before deciding
|
|
42
|
+
anything, find the config, in the whole repository rather than only here.
|
|
43
|
+
|
|
44
|
+
**Find it.** Glob `**/versioncam.config.{ts,js,mjs}` from the top of the
|
|
45
|
+
repository (the directory holding `.git`), and leave out anything under
|
|
46
|
+
`node_modules/`, `.versioncam/` or `.claude/worktrees/`, which is another
|
|
47
|
+
checkout. Files on disk are what count, committed or not: a first run's
|
|
48
|
+
config may not be committed yet, and a deleted one is gone.
|
|
49
|
+
|
|
50
|
+
- **One** → its directory is the app's directory.
|
|
51
|
+
- **Several** → ask which app with `AskUserQuestion`, listing their
|
|
52
|
+
directories, unless the request already names one. With no one to ask,
|
|
53
|
+
stop and list them.
|
|
54
|
+
- **None** → this is the first run, and the config goes where
|
|
55
|
+
`versioncam init` put this skill. This skill's directory is
|
|
56
|
+
`${CLAUDE_SKILL_DIR}` (if that is not a path, it is the directory you read
|
|
57
|
+
this file from). If it is `<dir>/.claude/skills/versioncam` inside this
|
|
58
|
+
repository, the app's directory is `<dir>`. Otherwise it is the working
|
|
59
|
+
directory.
|
|
60
|
+
|
|
61
|
+
**Check versioncam is installed** where the app will find it: `ls -d
|
|
62
|
+
<the app's directory>/node_modules/versioncam`, then the same in each
|
|
63
|
+
directory above it up to the repository root, since a workspace hoists it.
|
|
64
|
+
If none has it, **stop** and say it must be installed in the app's directory
|
|
65
|
+
first (`npm i -D versioncam`, then `npx versioncam install`). Every step below
|
|
66
|
+
runs `npx versioncam`, and `npx` with nothing installed downloads the latest
|
|
67
|
+
published version: not necessarily the one this repository will pin, and
|
|
44
68
|
with no Chromium to record with.
|
|
45
69
|
|
|
46
|
-
|
|
47
|
-
directory
|
|
70
|
+
**Go there**, if the app's directory is not the working directory, and say in
|
|
71
|
+
one line which directory it is:
|
|
72
|
+
|
|
73
|
+
- **Below where this session started** (a session at the repository root):
|
|
74
|
+
`cd` into it by its absolute path. The shell stays there for every later
|
|
75
|
+
command, and a subagent you spawn starts there too. Do not reach it with
|
|
76
|
+
`--cwd` from above: `npx` looks for versioncam in the directory it runs in
|
|
77
|
+
and in those above it, never below, so it would not run the app's own.
|
|
78
|
+
- **Anywhere else** (a session started inside the app, below its config): a
|
|
79
|
+
session cannot `cd` out of the directory it started in. Put `--cwd <the
|
|
80
|
+
app's directory>` on the end of every `npx versioncam` command instead.
|
|
81
|
+
`npx` then finds the app's versioncam by looking up from where you are,
|
|
82
|
+
and each command works as if run in the app's directory, so a relative path
|
|
83
|
+
you give it, or it prints, is that directory's. Your own `mkdir`, `cp` and
|
|
84
|
+
files take absolute paths.
|
|
85
|
+
|
|
86
|
+
If a `cd` does not stay (a later `pwd` prints where you started), begin each
|
|
87
|
+
command with `cd <the app's directory> && ` instead. Either way, tell each
|
|
88
|
+
subagent you spawn the app's directory and how you are running commands
|
|
89
|
+
there.
|
|
90
|
+
|
|
91
|
+
Then, did you find a config?
|
|
48
92
|
|
|
49
93
|
- **No** → this is the repository's first run. Read `onboarding.md` and do all
|
|
50
94
|
of it, then continue with step 1.
|
|
@@ -91,7 +135,8 @@ directories; do not summarise it away. Copy `beats.json` beside it.
|
|
|
91
135
|
host makes subagents cheap, hand the authoring to a subagent instead of doing
|
|
92
136
|
it here. Spawn a plain one (general-purpose) whose prompt is the full text of
|
|
93
137
|
`authoring.md`, followed by the intent, the clip id, the clips directory (the
|
|
94
|
-
config's `clips`), the `inspect /` output inline,
|
|
138
|
+
config's `clips`), the `inspect /` output inline, the beats path, and the
|
|
139
|
+
app's directory, with the `--cwd` flag if step 0 had you use it. **Keep the
|
|
95
140
|
agent id it returns**: that id is how you continue the same author in later
|
|
96
141
|
rounds, with `SendMessage`, everything it learned about the page still in its
|
|
97
142
|
context — spawning a new one would start from nothing and repeat the same
|
|
@@ -114,8 +159,9 @@ The reviewer is given only:
|
|
|
114
159
|
- the sheet path,
|
|
115
160
|
- **the frame numbers `sheet --at-marks` just printed**, in order, one per tile,
|
|
116
161
|
- the contents of `.versioncam/author/<id>/beats.json`,
|
|
117
|
-
- the clip id
|
|
118
|
-
directory, which you name if a subagent on your host starts somewhere
|
|
162
|
+
- the clip id, so it can call `npx versioncam frame <id> <n>`: from this
|
|
163
|
+
directory, which you name if a subagent on your host starts somewhere
|
|
164
|
+
else, and with the `--cwd` flag on the end if step 0 had you use one.
|
|
119
165
|
|
|
120
166
|
The frame numbers are not optional. A reviewer given only the id assumed tile
|
|
121
167
|
*n* was frame *n*, pulled frames 1 to 16 — every one of them before the beat
|
|
@@ -9,7 +9,9 @@ each thing it claims to show.
|
|
|
9
9
|
You have: the intent, the clip id, the clips directory, and the output of
|
|
10
10
|
`npx versioncam inspect /` for the start page — whether you are the session
|
|
11
11
|
running the skill or a subagent it handed this brief to. Work from the
|
|
12
|
-
directory that holds `versioncam.config.ts`.
|
|
12
|
+
directory that holds `versioncam.config.ts`. If you were given a `--cwd <dir>`
|
|
13
|
+
to use instead, put it on the end of every `npx versioncam` command, and write
|
|
14
|
+
files by their absolute paths.
|
|
13
15
|
|
|
14
16
|
## Look before you write
|
|
15
17
|
|
|
@@ -196,6 +198,8 @@ happened again the first time an agent used it.
|
|
|
196
198
|
| drag or lasso selected nothing | `measure`, re-author geometry as fractions of the real box |
|
|
197
199
|
| duration drift | adjust holds, never the beats |
|
|
198
200
|
| tooltip / menu open in a frame it should not be | move the cursor start; add a hold before the next action |
|
|
201
|
+
| the caption covers something a beat points at | `theme.presentation.captionInset: { right: 40 }` in the config moves it to the bottom-right corner; it moves every clip's caption, so say so in what you write down |
|
|
202
|
+
| the camera zooms in and out, or stops and starts between targets | one `s.camera.follow()` where the clip starts moving around the page, instead of a `zoom` before each thing the cursor uses; `s.camera.reset()` or `release()` where it should stop |
|
|
199
203
|
|
|
200
204
|
Two more, from the tools rather than the app:
|
|
201
205
|
|
|
@@ -203,6 +207,7 @@ Two more, from the tools rather than the app:
|
|
|
203
207
|
|---|---|
|
|
204
208
|
| a locator "matched N elements" | the text appears in more than one place — use `byRole` with a name, or a test id |
|
|
205
209
|
| review warns that a track never rendered | you wrote a camera, highlight or caption with no frames after it; add a hold |
|
|
210
|
+
| review warns that a click is outside what the camera shows | the camera was framing something else when the button went down: zoom to what the click is on before it, or follow the cursor |
|
|
206
211
|
|
|
207
212
|
## What you write down
|
|
208
213
|
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
# The first run: setting a repository up
|
|
2
2
|
|
|
3
|
-
There is no `versioncam.config.ts`
|
|
4
|
-
repository has to be taught three things: how its app starts,
|
|
5
|
-
in, and what "the page is done" means. Then one question has
|
|
6
|
-
before anything is authored: **does this app record the same
|
|
7
|
-
clip is only as deterministic as the app it films, and one
|
|
8
|
-
app that is not fails CI for reasons nobody wrote.
|
|
3
|
+
There is no `versioncam.config.ts` in this repository, so before any clip can
|
|
4
|
+
be written the repository has to be taught three things: how its app starts,
|
|
5
|
+
whether it signs in, and what "the page is done" means. Then one question has
|
|
6
|
+
to be answered before anything is authored: **does this app record the same
|
|
7
|
+
way twice?** A clip is only as deterministic as the app it films, and one
|
|
8
|
+
written against an app that is not fails CI for reasons nobody wrote.
|
|
9
9
|
|
|
10
10
|
You do not run the app yourself — not with `npm run dev`, not in the
|
|
11
11
|
background, not ever in this phase. You write what you learn into the config,
|
|
@@ -13,11 +13,10 @@ and `versioncam` starts the app from it, every time, and stops it again. If
|
|
|
13
13
|
you find yourself wanting to start a server by hand, the config is wrong;
|
|
14
14
|
fix the config.
|
|
15
15
|
|
|
16
|
-
Work in the directory the
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
would split the repository in two.
|
|
16
|
+
Work in the app's directory, the one step 0 of the skill chose: the config
|
|
17
|
+
goes there, and every `versioncam` command reads it from there. Step 0 found
|
|
18
|
+
no `versioncam.config.*` anywhere in the repository, so this is the only one.
|
|
19
|
+
A second would split the repository in two.
|
|
21
20
|
|
|
22
21
|
## 1. How the app runs
|
|
23
22
|
|
|
@@ -82,7 +81,7 @@ say what the person needs to do: nothing below works on a sign-in screen.
|
|
|
82
81
|
|
|
83
82
|
## 3. Write the config
|
|
84
83
|
|
|
85
|
-
`versioncam.config.ts` in
|
|
84
|
+
`versioncam.config.ts` in the app's directory, with only what the app needs:
|
|
86
85
|
|
|
87
86
|
```ts
|
|
88
87
|
import { defineRecorder } from "versioncam";
|
|
@@ -101,11 +100,12 @@ for most apps, and a field nobody needed is a question the next person has to
|
|
|
101
100
|
answer. Say in a comment above each field you did write why it has the value
|
|
102
101
|
it has — the port came from the dev script, the command from the README.
|
|
103
102
|
|
|
104
|
-
Then: create `demos/clips
|
|
105
|
-
recordings, renders and a saved sign-in go there.
|
|
106
|
-
|
|
107
|
-
leave it; otherwise add the line to the
|
|
108
|
-
|
|
103
|
+
Then: create `demos/clips/` in the app's directory, and make sure
|
|
104
|
+
`.versioncam/` is ignored — recordings, renders and a saved sign-in go there.
|
|
105
|
+
If a `.gitignore` in the app's directory, or in a directory above it up to
|
|
106
|
+
the repository root, already says so, leave it; otherwise add the line to the
|
|
107
|
+
nearest one, creating one in the app's directory if there is none, and leave
|
|
108
|
+
the rest of the file alone.
|
|
109
109
|
|
|
110
110
|
## 4. `npx versioncam doctor`
|
|
111
111
|
|
|
@@ -1 +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(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($);
|