agent-screenplay 0.1.0 → 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.
package/README.md CHANGED
@@ -34,7 +34,7 @@ Requirements: Node 20+. ffmpeg and the drawing library ship with the package.
34
34
  ```json
35
35
  {
36
36
  "url": "https://app.example.com/inbox",
37
- "style": { "background": "ocean" },
37
+ "style": { "background": "aurora" },
38
38
  "steps": [
39
39
  { "title": "Human handoff, in one click", "subtitle": "New in 2.4" },
40
40
  { "click": "text=Camille Laurent", "say": "When the AI agent gets stuck, the conversation lands in your inbox." },
@@ -51,7 +51,7 @@ Requirements: Node 20+. ffmpeg and the drawing library ship with the package.
51
51
  - **Targets** name elements the way people see them: `text=…`, `role=button[name='…']`, `label=…`, `placeholder=…`, `testid=…`, `css=…` (web only), `at=x,y` (desktop apps without accessible elements only).
52
52
  - **Actions**: `goto`, `click`, `type`, `press`, `hover`, `scroll`, `select`, `wait`, `pause`.
53
53
  - **Direction**: `title` and `end` cards, `callout` (a bubble pointing at an element), `highlight` (spotlight), `zoomTo`, and on any step `say` (caption and voice), `zoom`, `hold`.
54
- - **Style**: background preset (`sunset`, `ocean`, `lavender`, `forest`, `midnight`, `mono`, `paper`), your own gradient, a color or an image; padding, corner radius, shadow, cursor style, maximum zoom, browser frame, accent color. See [Style](#style).
54
+ - **Style**: background preset (mesh gradients: `aurora`, `sky`, `dusk`, `ember`, `graphite`, `lagoon`, `lilac`, `peach`, `linen`), your own gradient, a color or an image; padding, corner radius, shadow, cursor style, maximum zoom, browser frame, accent color. See [Style](#style).
55
55
  - **Output**: 1920×1080 at 30 fps by default; any size up to 4K, 24 to 60 fps, optional GIF.
56
56
 
57
57
  The full format is the JSON schema in [`schema/scenario.schema.json`](schema/scenario.schema.json) (`npx agent-screenplay schema`), and the agent guide in [`skills/screenplay/SKILL.md`](skills/screenplay/SKILL.md). Examples are in [`examples/`](examples).
@@ -60,7 +60,7 @@ The full format is the JSON schema in [`schema/scenario.schema.json`](schema/sce
60
60
 
61
61
  ## Style
62
62
 
63
- Set the look by eye: `npx agent-screenplay style demo.json` opens an editor in your browser, on a frame of the last recording. Pick a gradient (or make your own), a color or an image (drop it on the page), set the margin, corners and shadow, then save to **this video** or to **all videos**, and render without filming again.
63
+ Set the look by eye: `npx agent-screenplay style demo.json` opens an editor in your browser, on a frame of the last recording. Pick a gradient (or make your own), a color or an image (drop it on the page), set the padding, corners and shadow, then save to **this video** or to **all videos**, and render without filming again.
64
64
 
65
65
  ![The style editor](docs/style-editor.png)
66
66
 
@@ -77,7 +77,7 @@ Set the look by eye: `npx agent-screenplay style demo.json` opens an editor in y
77
77
  }
78
78
  ```
79
79
 
80
- `background` is a preset name, a color (`"#0f172a"`), an image path (relative to the file that sets it), or `{ "gradient": [2 to 4 colors], "angle": degrees }`.
80
+ `background` is a preset name (`aurora` by default; the first presets, `ocean`, `sunset` and so on, still work), a color (`"#0f172a"`), an image path (relative to the file that sets it), or `{ "gradient": [2 to 4 colors], "angle": degrees }`.
81
81
 
82
82
  ## Signing in
83
83
 
@@ -31,6 +31,7 @@ button:focus-visible, input:focus-visible { outline: 2px solid rgba(255,255,255,
31
31
  .frame { position: relative; border-radius: 12px; overflow: hidden; box-shadow: 0 0 0 1px var(--line), 0 30px 80px rgba(0,0,0,.5); }
32
32
  .comp { position: absolute; left: 0; top: 0; transform-origin: 0 0; overflow: hidden; }
33
33
  .bg { position: absolute; inset: 0; background-size: cover; background-position: center; transition: opacity 320ms var(--ease-out); }
34
+ .grain { position: absolute; inset: 0; opacity: 0; background-size: 128px 128px; transition: opacity 320ms var(--ease-out); pointer-events: none; }
34
35
  .glow { position: absolute; inset: 0; background: radial-gradient(circle 1536px at 20% 10%, rgba(255,255,255,.18), rgba(255,255,255,0)); transition: opacity 320ms var(--ease-out); }
35
36
  .win { position: absolute; overflow: hidden; background: #fff;
36
37
  transition: left 280ms var(--ease-out), top 280ms var(--ease-out), width 280ms var(--ease-out), height 280ms var(--ease-out), border-radius 280ms var(--ease-out), box-shadow 280ms var(--ease-out); }
@@ -45,12 +46,27 @@ button:focus-visible, input:focus-visible { outline: 2px solid rgba(255,255,255,
45
46
  .video { position: absolute; inset: 0; width: 100%; height: 100%; background: #000; opacity: 0; transform: scale(.985); pointer-events: none; transition: opacity 240ms var(--ease-out), transform 240ms var(--ease-out); }
46
47
  .showVideo .video { opacity: 1; transform: none; pointer-events: auto; }
47
48
 
48
- .under { display: flex; justify-content: center; gap: 12px; align-items: center; padding: 0 24px 20px; font-size: 12px; color: var(--muted); min-height: 52px; }
49
+ .under { display: flex; justify-content: center; padding: 0 24px 20px; }
50
+ /* The caption describes the preview: right under the frame, and only on the Preview tab. */
51
+ .frameWrap { position: relative; }
52
+ .hint { position: absolute; left: 0; right: 0; top: calc(100% + 12px); text-align: center; font-size: 12px; color: var(--muted); transition: opacity 180ms ease, transform 180ms var(--ease-out); }
53
+ .videoMode .hint { opacity: 0; transform: translateY(-2px); }
49
54
 
50
55
  /* Sidebar */
51
56
  .side { background: var(--panel); border-left: 1px solid var(--line); display: grid; grid-template-rows: 1fr auto; min-height: 0; }
52
57
  .scroll { overflow-y: auto; padding: 20px 20px 8px; }
53
- .section { padding: 4px 0 20px; animation: rise 420ms var(--ease-out) both; }
58
+ .section { padding: 18px 0 20px; border-top: 1px solid var(--line); animation: rise 420ms var(--ease-out) both; }
59
+ .head + .section { border-top: 0; padding-top: 4px; }
60
+ .head { display: flex; align-items: center; justify-content: space-between; margin: -4px 0 12px; font-size: 13px; font-weight: 600; }
61
+ .reset { display: inline-flex; align-items: center; gap: 6px; height: 26px; padding: 0 9px; border-radius: 7px; font-size: 12px; font-weight: 500; color: var(--muted); transition: color 160ms ease, background 160ms ease, opacity 160ms ease, transform 160ms var(--ease-out); }
62
+ .reset:hover:not(:disabled) { color: var(--text); background: var(--raised); }
63
+ .reset:active:not(:disabled) { transform: scale(.96); }
64
+ .reset:disabled { opacity: .35; cursor: default; }
65
+ .reset.spun svg { animation: unwind 420ms var(--ease-out); }
66
+ @keyframes unwind { from { transform: rotate(0); } to { transform: rotate(-360deg); } }
67
+ .custom { margin-top: 12px; padding: 12px; border-radius: var(--r); background: var(--bg); border: 1px solid var(--line); }
68
+ .custom .stops { margin-top: 0; }
69
+ .custom .stop { background: var(--panel); }
54
70
  .section:nth-child(2) { animation-delay: 50ms; } .section:nth-child(3) { animation-delay: 100ms; }
55
71
  @keyframes rise { from { opacity: 0; transform: translateY(6px); } }
56
72
  .label { font-size: 12px; font-weight: 500; color: var(--muted); margin: 0 0 10px; display: flex; justify-content: space-between; align-items: baseline; }
@@ -103,6 +119,8 @@ button:focus-visible, input:focus-visible { outline: 2px solid rgba(255,255,255,
103
119
  .range input::-webkit-slider-thumb { -webkit-appearance: none; width: 16px; height: 16px; margin-top: -6px; border-radius: 50%; background: #fff; box-shadow: 0 1px 4px rgba(0,0,0,.5); transition: transform 140ms var(--ease-out); }
104
120
  .range input:active::-webkit-slider-thumb { transform: scale(1.15); }
105
121
  .range input::-moz-range-thumb { width: 16px; height: 16px; border: 0; border-radius: 50%; background: #fff; }
122
+ .note { margin: 8px 0 0; font-size: 12px; line-height: 1.45; color: var(--faint); display: none; }
123
+ .note.on { display: block; animation: paneIn 200ms var(--ease-out); }
106
124
  .row { display: flex; align-items: center; justify-content: space-between; font-size: 13px; }
107
125
  .dial { width: 18px; height: 18px; border-radius: 50%; border: 1px solid var(--line-2); position: relative; }
108
126
  .dial::after { content: ""; position: absolute; left: 50%; top: 2px; width: 1.5px; height: 7px; margin-left: -.75px; border-radius: 1px; background: #fff; transform-origin: 50% 6px; transform: rotate(var(--a)); }
@@ -125,8 +143,15 @@ button:focus-visible, input:focus-visible { outline: 2px solid rgba(255,255,255,
125
143
  .btn.swapping .txt { opacity: 0; filter: blur(3px); }
126
144
  .dirty { width: 6px; height: 6px; border-radius: 50%; background: #0a0a0b; opacity: 0; transform: scale(.5); transition: opacity 180ms var(--ease-out), transform 180ms var(--ease-out); }
127
145
  .isDirty .dirty { opacity: .55; transform: none; }
128
- .spin { width: 13px; height: 13px; border-radius: 50%; border: 2px solid currentColor; border-right-color: transparent; animation: spin 600ms linear infinite; }
129
- @keyframes spin { to { transform: rotate(360deg); } }
146
+ /* macOS activity indicator: eight bars, the brightest leading, turning in eight steps. */
147
+ .mspin { position: relative; flex: none; width: 14px; height: 14px; animation: mspin 800ms steps(8) infinite; }
148
+ .mspin i { position: absolute; left: 6.25px; top: 0; width: 1.5px; height: 4.2px; border-radius: 1px; background: currentColor; transform-origin: .75px 7px; }
149
+ @keyframes mspin { to { transform: rotate(360deg); } }
150
+ /* While rendering, the button fills up with the progress and is not dimmed. */
151
+ .btn.busy:disabled { opacity: 1; cursor: progress; }
152
+ .btn .fill { position: absolute; inset: 0; border-radius: inherit; background: rgba(255,255,255,.09); transform-origin: left; transform: scaleX(0); transition: transform 240ms linear, opacity 200ms ease; pointer-events: none; }
153
+ .btn .txt { position: relative; display: inline-flex; align-items: center; gap: 8px; }
154
+ .pct { font-variant-numeric: tabular-nums; }
130
155
  kbd { font: 11px ui-monospace, Menlo, monospace; color: var(--faint); }
131
156
 
132
157
  .toast { position: fixed; left: calc((100% - 320px) / 2); top: 14px; transform: translate(-50%, -8px); opacity: 0; padding: 9px 14px; border-radius: 10px; background: var(--raised); box-shadow: 0 0 0 1px var(--line-2), 0 10px 30px rgba(0,0,0,.5); font-size: 13px; pointer-events: none; transition: opacity 200ms var(--ease-out), transform 200ms var(--ease-out); max-width: 60vw; }
@@ -149,23 +174,33 @@ kbd { font: 11px ui-monospace, Menlo, monospace; color: var(--faint); }
149
174
  <div class="main">
150
175
  <div class="top"><b>Screenplay</b><span class="sep">/</span><span id="name"></span></div>
151
176
  <div class="stage"><div class="fit" id="fit">
177
+ <div class="frameWrap">
152
178
  <div class="frame" id="frame">
153
179
  <div class="comp" id="comp">
154
180
  <div class="bg" id="bgA"></div><div class="bg" id="bgB" style="opacity:0"></div>
155
181
  <div class="glow" id="glow"></div>
182
+ <div class="grain" id="grain"></div>
156
183
  <div class="win" id="win"></div>
157
184
  </div>
158
185
  <video class="video" id="video" controls playsinline></video>
159
186
  </div>
187
+ <span class="hint" id="hint"></span>
188
+ </div>
160
189
  </div></div>
161
190
  <div class="under">
162
191
  <div class="tabs"><button id="tabPreview" aria-pressed="true">Preview</button><button id="tabVideo" disabled>Video</button></div>
163
- <span id="hint"></span>
164
192
  </div>
165
193
  </div>
166
194
 
167
195
  <aside class="side">
168
196
  <div class="scroll">
197
+ <div class="head">
198
+ <span>Style</span>
199
+ <button class="reset" id="reset" title="Back to Screenplay's defaults">
200
+ <svg width="13" height="13" viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M2.5 7a4.5 4.5 0 1 0 1.3-3.2"/><path d="M2.5 1.8v2.4h2.4"/></svg>
201
+ Reset
202
+ </button>
203
+ </div>
169
204
  <div class="section">
170
205
  <p class="label">Background</p>
171
206
  <div class="seg" id="bgKind"><span class="pill"></span>
@@ -173,6 +208,7 @@ kbd { font: 11px ui-monospace, Menlo, monospace; color: var(--faint); }
173
208
  </div>
174
209
  <div class="pane" data-pane="gradient">
175
210
  <div class="swatches" id="presets"></div>
211
+ <div class="custom">
176
212
  <div class="stops">
177
213
  <label class="stop"><i id="s1i"></i><span id="s1t"></span><input type="color" id="s1"></label>
178
214
  <button class="swap" id="swap" title="Swap colors" aria-label="Swap colors">
@@ -180,10 +216,11 @@ kbd { font: 11px ui-monospace, Menlo, monospace; color: var(--faint); }
180
216
  </button>
181
217
  <label class="stop"><i id="s2i"></i><span id="s2t"></span><input type="color" id="s2"></label>
182
218
  </div>
183
- <div style="margin-top:16px">
184
- <p class="label"><span>Direction</span><span style="display:flex;gap:8px;align-items:center"><span class="dial" id="dial"></span><output id="angleV"></output></span></p>
219
+ <div style="margin-top:14px">
220
+ <p class="label"><span>Gradient angle</span><span style="display:flex;gap:8px;align-items:center"><span class="dial" id="dial"></span><output id="angleV"></output></span></p>
185
221
  <div class="range"><input type="range" id="angle" min="0" max="360" step="5"></div>
186
222
  </div>
223
+ </div>
187
224
  </div>
188
225
  <div class="pane" data-pane="color">
189
226
  <div class="swatches" id="colors"></div>
@@ -200,8 +237,9 @@ kbd { font: 11px ui-monospace, Menlo, monospace; color: var(--faint); }
200
237
  </div>
201
238
 
202
239
  <div class="section">
203
- <p class="label"><span>Margin</span><output id="padV"></output></p>
240
+ <p class="label"><span>Padding</span><output id="padV"></output></p>
204
241
  <div class="range"><input type="range" id="pad" min="0" max="240" step="2"></div>
242
+ <p class="note" id="shapeNote"></p>
205
243
  <p class="label" style="margin-top:18px"><span>Corners</span><output id="radV"></output></p>
206
244
  <div class="range"><input type="range" id="rad" min="0" max="48" step="1"></div>
207
245
  </div>
@@ -218,7 +256,7 @@ kbd { font: 11px ui-monospace, Menlo, monospace; color: var(--faint); }
218
256
  </div>
219
257
  <div class="dest" id="dest"></div>
220
258
  <div class="actions">
221
- <button class="btn ghost" id="renderBtn"><span class="txt">Render video</span></button>
259
+ <button class="btn ghost" id="renderBtn"><span class="fill" id="renderFill"></span><span class="txt">Render video</span></button>
222
260
  <button class="btn primary" id="saveBtn"><span class="txt">Save</span><span class="dirty"></span></button>
223
261
  </div>
224
262
  </div>
@@ -274,16 +312,40 @@ function layout() {
274
312
 
275
313
  function fitStage() {
276
314
  const L = layout(), box = $("fit").getBoundingClientRect();
277
- const sc = Math.min(box.width / L.OW, box.height / L.OH);
315
+ const sc = Math.min(box.width / L.OW, (box.height - 32) / L.OH); // room for the caption under the frame
278
316
  $("frame").style.width = L.OW * sc + "px"; $("frame").style.height = L.OH * sc + "px";
279
317
  $("comp").style.width = L.OW + "px"; $("comp").style.height = L.OH + "px";
280
318
  $("comp").style.transform = `scale(${sc})`;
281
319
  }
282
320
 
321
+ /* Same drawing as the renderer (src/render/presets.ts, paintMesh): color fields over a base. */
322
+ const rgba = (hex, a) => { const n = parseInt(hex.slice(1), 16); return `rgba(${(n >> 16) & 255},${(n >> 8) & 255},${n & 255},${Math.max(0, Math.min(1, a)).toFixed(3)})`; };
323
+ function meshCss(p, w, h) {
324
+ const side = Math.max(w, h);
325
+ const layers = p.blobs.map((b) => `radial-gradient(circle ${b.r * side}px at ${b.x * w}px ${b.y * h}px, ${rgba(b.color, b.alpha)} 0%, ${rgba(b.color, b.alpha * 0.6)} 40%, ${rgba(b.color, 0)} 100%)`);
326
+ return [...layers.reverse(), p.base].join(", ");
327
+ }
328
+ function presetCss(p, w, h) {
329
+ return p.kind === "mesh" ? meshCss(p, w, h) : `linear-gradient(${p.angle}deg, ${p.stops.join(", ")})`;
330
+ }
331
+ /* The grain tile: the renderer's seeded noise (mulberry32, seed 7), 128×128. */
332
+ const grainUrl = (() => {
333
+ const N = 128, c = document.createElement("canvas"); c.width = c.height = N;
334
+ const ctx = c.getContext("2d"), img = ctx.createImageData(N, N);
335
+ let a = 7;
336
+ for (let i = 0; i < N * N; i++) {
337
+ a = (a + 0x6d2b79f5) >>> 0; let t = a;
338
+ t = Math.imul(t ^ (t >>> 15), t | 1); t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
339
+ const v = ((t ^ (t >>> 14)) >>> 0) % 256;
340
+ img.data[i * 4] = img.data[i * 4 + 1] = img.data[i * 4 + 2] = v; img.data[i * 4 + 3] = 255;
341
+ }
342
+ ctx.putImageData(img, 0, 0);
343
+ return c.toDataURL();
344
+ })();
283
345
  function bgCss(bg) {
284
346
  if (typeof bg === "object") return `linear-gradient(${bg.angle ?? 135}deg, ${bg.gradient.join(", ")})`;
285
347
  const p = S.presets[bg];
286
- if (p) return `linear-gradient(${p.angle}deg, ${p.stops.join(", ")})`;
348
+ if (p) return presetCss(p, S.output.width, S.output.height);
287
349
  if (/^#/.test(bg)) return bg;
288
350
  if (imageRef) return `url("/api/image?t=${TOKEN}&from=${imageRef.from}&path=${encodeURIComponent(imageRef.path)}")`;
289
351
  return "#111";
@@ -300,7 +362,14 @@ function draw(initial) {
300
362
  if (initial) { $(back).style.transition = "none"; requestAnimationFrame(() => $(back).style.transition = ""); }
301
363
  $(back).style.opacity = 1; $(front).style.opacity = 0; front = back; lastBg = css;
302
364
  }
303
- $("glow").style.opacity = kindOf(style.background) === "gradient" ? 1 : 0;
365
+ const preset = typeof style.background === "string" ? S.presets[style.background] : null;
366
+ // The light from the top left belongs to linear gradients; mesh presets have their grain.
367
+ $("glow").style.opacity = kindOf(style.background) === "gradient" && !(preset && preset.kind === "mesh") ? 1 : 0;
368
+ $("grain").style.backgroundImage = `url(${grainUrl})`;
369
+ // Same tile size as the renderer: 128 px, doubled at 4K.
370
+ const tile = 128 * Math.max(1, Math.round(S.output.height / 1080));
371
+ $("grain").style.backgroundSize = `${tile}px ${tile}px`;
372
+ $("grain").style.opacity = preset && preset.kind === "mesh" ? preset.grain : 0;
304
373
  Object.assign(win.style, { left: L.x + "px", top: L.y + "px", width: L.w + "px", height: L.h + "px", borderRadius: style.radius * L.k + "px",
305
374
  boxShadow: style.shadow ? `0 ${24 * L.k}px ${60 * L.k}px rgba(0,0,0,.35), 0 ${2 * L.k}px ${8 * L.k}px rgba(0,0,0,.18)` : "none" });
306
375
  if (!win.dataset.built) {
@@ -332,7 +401,7 @@ function setKind(k, initial) {
332
401
  if (initial) return;
333
402
  const cur = style.background, curKind = kindOf(cur);
334
403
  if (k === curKind) return;
335
- if (k === "gradient") style.background = lastGradient || "ocean";
404
+ if (k === "gradient") style.background = lastGradient || S.defaults.background;
336
405
  if (k === "color") style.background = lastColor || "#0f172a";
337
406
  if (k === "image") { if (imageRef) style.background = imageRef.path; else { sync(); return; } }
338
407
  change();
@@ -341,10 +410,12 @@ let lastGradient = null, lastColor = null;
341
410
 
342
411
  function buildSwatches() {
343
412
  const pre = $("presets");
344
- for (const [name, p] of Object.entries(S.presets)) {
413
+ for (const name of S.offered) {
414
+ const p = S.presets[name];
345
415
  const b = document.createElement("button");
346
416
  b.className = "sw"; b.dataset.v = name; b.title = name; b.setAttribute("aria-label", name);
347
- b.style.background = `linear-gradient(${p.angle}deg, ${p.stops.join(", ")})`;
417
+ // A swatch is the same mesh, drawn on a square.
418
+ b.style.background = presetCss(p, 60, 60);
348
419
  b.onclick = () => { style.background = name; change(); };
349
420
  pre.appendChild(b);
350
421
  }
@@ -364,8 +435,8 @@ function buildSwatches() {
364
435
  function currentStops() {
365
436
  const bg = style.background;
366
437
  if (typeof bg === "object") return { stops: bg.gradient, angle: bg.angle ?? 135 };
367
- const p = S.presets[bg] || S.presets.ocean;
368
- return { stops: [p.stops[0], p.stops[p.stops.length - 1]], angle: p.angle };
438
+ const p = S.presets[bg] || S.presets[S.defaults.background];
439
+ return { stops: p.colors, angle: p.kind === "linear" ? p.angle : 135 };
369
440
  }
370
441
  function customGradient() {
371
442
  if (typeof style.background === "object") return style.background;
@@ -386,6 +457,14 @@ $("hex").onchange = (e) => { const v = e.target.value.trim(); if (/^#([0-9a-f]{3
386
457
  $("pad").oninput = (e) => { live(); style.padding = +e.target.value; change(); };
387
458
  $("rad").oninput = (e) => { live(); style.radius = +e.target.value; change(); };
388
459
  $("shadow").onclick = () => { style.shadow = !style.shadow; change(); };
460
+ $("reset").onclick = () => {
461
+ style = { ...S.defaults };
462
+ setKind(kindOf(style.background), true);
463
+ change();
464
+ const b = $("reset"); b.classList.remove("spun"); void b.offsetWidth; b.classList.add("spun");
465
+ setTimeout(() => b.classList.remove("spun"), 400);
466
+ toast("Back to the defaults · Save to keep them");
467
+ };
389
468
  $("bgKind").querySelectorAll("button").forEach((b) => (b.onclick = () => setKind(b.dataset.k)));
390
469
  $("target").querySelectorAll("button").forEach((b) => (b.onclick = () => { target = b.dataset.k; setSeg($("target"), target); sync(); }));
391
470
 
@@ -449,6 +528,15 @@ function sync() {
449
528
  $(id + "V").textContent = v + unit;
450
529
  }
451
530
  $("shadow").setAttribute("aria-checked", String(style.shadow));
531
+ $("reset").disabled = JSON.stringify(style) === JSON.stringify({ ...S.defaults });
532
+ // The recording keeps its shape: when it is not the video's, the background shows on two sides even with no padding.
533
+ const cap = S.capture || { width: 1440, height: 900 }, r = cap.width / cap.height, R = S.output.width / S.output.height;
534
+ const off = Math.abs(r - R) > 0.01;
535
+ $("shapeNote").classList.toggle("on", off && style.padding < 24);
536
+ const shape = Math.abs(R - 16 / 9) < 0.01 ? "16:9" : `${S.output.width}×${S.output.height}`;
537
+ const fit = S.defaultViewport;
538
+ $("shapeNote").textContent = !off ? "" : `The ${S.url ? "recording" : "window"} is ${cap.width}×${cap.height}, not ${shape} like the video, so the background shows on the ${r < R ? "sides" : "top and bottom"}.` +
539
+ (S.url ? ` Record with a ${shape} viewport (such as ${fit.width}×${fit.height}, the default) to fill the frame.` : "");
452
540
  const file = target === "project" ? S.configFile : S.scenarioFile;
453
541
  $("dest").textContent = (target === "project" ? (S.configExists ? "Saves to " : "Creates ") : "Saves to ") + file.split(/[\\/]/).pop();
454
542
  $("dest").title = file;
@@ -496,11 +584,34 @@ $("renderBtn").onclick = async () => {
496
584
  if (JSON.stringify(style) !== saved && !(await save())) return;
497
585
  if (JSON.stringify(style) !== saved) return toast("Save your latest changes first", true);
498
586
  btn.disabled = true;
499
- await swapText(btn, '<span class="spin"></span>Rendering…');
500
- const r = await api("/api/render", { method: "POST" });
501
- const j = await r.json();
587
+ btn.classList.add("busy");
588
+ const fill = $("renderFill");
589
+ fill.style.opacity = 1; fill.style.transform = "scaleX(0)";
590
+ const spinner = '<span class="mspin">' + [0, 1, 2, 3, 4, 5, 6, 7].map((n) => `<i style="transform:rotate(${n * 45}deg);opacity:${(1 - ((8 - n) % 8) * 0.11).toFixed(2)}"></i>`).join("") + "</span>";
591
+ await swapText(btn, `${spinner}Rendering <span class="pct" id="pct">0%</span>`);
592
+ // The server reports the editing progress; the button fills up with it.
593
+ const poll = setInterval(async () => {
594
+ try {
595
+ const p = await (await api("/api/progress")).json();
596
+ if (!p.rendering) return;
597
+ fill.style.transform = `scaleX(${p.progress})`;
598
+ if ($("pct")) $("pct").textContent = Math.round(p.progress * 100) + "%";
599
+ } catch {}
600
+ }, 250);
601
+ let r, j;
602
+ try {
603
+ r = await api("/api/render", { method: "POST" });
604
+ j = await r.json();
605
+ } finally {
606
+ clearInterval(poll);
607
+ }
608
+ fill.style.transform = "scaleX(1)";
609
+ await new Promise((res) => setTimeout(res, 250));
610
+ fill.style.opacity = 0;
502
611
  btn.disabled = false;
612
+ btn.classList.remove("busy");
503
613
  await swapText(btn, "Render video");
614
+ setTimeout(() => (fill.style.transform = "scaleX(0)"), 220);
504
615
  if (!r.ok) return toast(j.error || "Render failed", true);
505
616
  $("video").src = `/api/video?t=${TOKEN}&v=${j.at}`;
506
617
  $("tabVideo").disabled = false;
@@ -511,6 +622,7 @@ $("renderBtn").onclick = async () => {
511
622
 
512
623
  function showVideo(on) {
513
624
  $("frame").classList.toggle("showVideo", on);
625
+ document.body.classList.toggle("videoMode", on);
514
626
  $("tabPreview").setAttribute("aria-pressed", String(!on));
515
627
  $("tabVideo").setAttribute("aria-pressed", String(on));
516
628
  if (!on) $("video").pause();
@@ -518,8 +630,10 @@ function showVideo(on) {
518
630
  }
519
631
  $("tabPreview").onclick = () => showVideo(false);
520
632
  $("tabVideo").onclick = () => showVideo(true);
521
- document.addEventListener("input", () => showVideo(false), true);
522
- document.addEventListener("click", (e) => { if (e.target.closest(".side") && !e.target.closest(".foot")) showVideo(false); }, true);
633
+ // Changing a setting shows the preview again; using the video's own controls (seek, volume) does not.
634
+ const editsStyle = (e) => e.target instanceof Element && e.target.closest(".side") && !e.target.closest(".foot");
635
+ document.addEventListener("input", (e) => { if (editsStyle(e)) showVideo(false); }, true);
636
+ document.addEventListener("click", (e) => { if (editsStyle(e)) showVideo(false); }, true);
523
637
 
524
638
  let toastT;
525
639
  function toast(msg, err) {
package/dist/cli.js CHANGED
@@ -64,7 +64,7 @@ program
64
64
  program
65
65
  .command("style")
66
66
  .argument("<scenario>", "scenario JSON file")
67
- .description("Open the style editor in your browser: background, margin, corners and shadow, on a frame of the recording.")
67
+ .description("Open the style editor in your browser: background, padding, corners and shadow, on a frame of the recording.")
68
68
  .option("--port <port>", "port to listen on (default: any free port)")
69
69
  .option("--no-open", "print the link instead of opening the browser")
70
70
  .action(async (file, o) => {
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAClD,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACnD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAEvC,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAC9B,OAAO;KACJ,IAAI,CAAC,YAAY,CAAC;KAClB,WAAW,CAAC,wDAAwD,CAAC;KACrE,OAAO,CAAC,OAAO,CAAC,CAAC;AAEpB,MAAM,GAAG,GAAG,CAAC,GAAW,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,GAAG,IAAI,CAAC,CAAC;AAE9D,SAAS,IAAI,CAAC,CAAU;IACtB,MAAM,GAAG,GAAG,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IACvD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC;IACrC,IAAI,OAAO,CAAC,GAAG,CAAC,gBAAgB,IAAI,CAAC,YAAY,KAAK,IAAI,CAAC,CAAC,CAAC,YAAY,aAAa,CAAC;QAAE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC;IAC9H,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED,OAAO;KACJ,OAAO,CAAC,QAAQ,CAAC;KACjB,QAAQ,CAAC,YAAY,EAAE,oBAAoB,CAAC;KAC5C,WAAW,CAAC,0DAA0D,CAAC;KACvE,MAAM,CAAC,YAAY,EAAE,qDAAqD,CAAC;KAC3E,MAAM,CAAC,OAAO,EAAE,kBAAkB,CAAC;KACnC,MAAM,CAAC,QAAQ,EAAE,0BAA0B,CAAC;KAC5C,MAAM,CAAC,KAAK,EAAE,IAAY,EAAE,CAAoD,EAAE,EAAE;IACnF,IAAI,CAAC;QACH,MAAM,CAAC,GAAG,MAAM,MAAM,CAAC,IAAI,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC;QACrE,IAAI,CAAC,CAAC,IAAI;YAAE,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;aAC/C,CAAC;YACJ,GAAG,CAAC,OAAO,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,KAAK,qBAAqB,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;YACtG,GAAG,CAAC,iBAAiB,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC;YACrC,IAAI,CAAC,CAAC,GAAG;gBAAE,GAAG,CAAC,UAAU,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC;QACpC,CAAC;IACH,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,IAAI,CAAC,CAAC,CAAC,CAAC;IACV,CAAC;AACH,CAAC,CAAC,CAAC;AAEL,OAAO;KACJ,OAAO,CAAC,QAAQ,CAAC;KACjB,QAAQ,CAAC,YAAY,EAAE,oBAAoB,CAAC;KAC5C,WAAW,CAAC,mFAAmF,CAAC;KAChG,MAAM,CAAC,YAAY,EAAE,qDAAqD,CAAC;KAC3E,MAAM,CAAC,OAAO,EAAE,kBAAkB,CAAC;KACnC,MAAM,CAAC,QAAQ,EAAE,0BAA0B,CAAC;KAC5C,MAAM,CAAC,KAAK,EAAE,IAAY,EAAE,CAAoD,EAAE,EAAE;IACnF,IAAI,CAAC;QACH,MAAM,CAAC,GAAG,MAAM,MAAM,CAAC,IAAI,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC;QACrE,IAAI,CAAC,CAAC,IAAI;YAAE,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;;YAC/C,GAAG,CAAC,OAAO,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,qBAAqB,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC;IACtF,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,IAAI,CAAC,CAAC,CAAC,CAAC;IACV,CAAC;AACH,CAAC,CAAC,CAAC;AAEL,OAAO;KACJ,OAAO,CAAC,OAAO,CAAC;KAChB,QAAQ,CAAC,YAAY,EAAE,oBAAoB,CAAC;KAC5C,WAAW,CAAC,8GAA8G,CAAC;KAC3H,MAAM,CAAC,eAAe,EAAE,4CAA4C,CAAC;KACrE,MAAM,CAAC,WAAW,EAAE,+CAA+C,CAAC;KACpE,MAAM,CAAC,KAAK,EAAE,IAAY,EAAE,CAAmC,EAAE,EAAE;IAClE,IAAI,CAAC;QACH,MAAM,EAAE,gBAAgB,EAAE,GAAG,MAAM,MAAM,CAAC,oBAAoB,CAAC,CAAC;QAChE,MAAM,gBAAgB,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC;QAC/F,GAAG,CAAC,uBAAuB,CAAC,CAAC;IAC/B,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,IAAI,CAAC,CAAC,CAAC,CAAC;IACV,CAAC;AACH,CAAC,CAAC,CAAC;AAEL,OAAO;KACJ,OAAO,CAAC,OAAO,CAAC;KAChB,QAAQ,CAAC,YAAY,EAAE,oBAAoB,CAAC;KAC5C,WAAW,CAAC,yFAAyF,CAAC;KACtG,MAAM,CAAC,QAAQ,EAAE,0BAA0B,CAAC;KAC5C,MAAM,CAAC,KAAK,EAAE,IAAY,EAAE,CAAqB,EAAE,EAAE;IACpD,IAAI,CAAC;QACH,MAAM,CAAC,GAAG,MAAM,KAAK,CAAC,IAAI,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC;QAC/D,IAAI,CAAC,CAAC,IAAI;YAAE,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;aAC/C,CAAC;YACJ,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,CAAC;gBACxB,GAAG,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,KAAK,GAAG,CAAC,KAAK,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,KAAK,KAAK,aAAa,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;gBACtH,IAAI,CAAC,CAAC,UAAU,EAAE,MAAM;oBAAE,GAAG,CAAC,wBAAwB,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YAChG,CAAC;YACD,IAAI,CAAC,CAAC,UAAU;gBAAE,GAAG,CAAC,kCAAkC,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC;YACxE,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,oCAAoC,CAAC,CAAC,CAAC,wCAAwC,CAAC,CAAC;QAC9F,CAAC;QACD,IAAI,CAAC,CAAC,CAAC,EAAE;YAAE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC7B,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,IAAI,CAAC,CAAC,CAAC,CAAC;IACV,CAAC;AACH,CAAC,CAAC,CAAC;AAEL,OAAO;KACJ,OAAO,CAAC,MAAM,CAAC;KACf,WAAW,CAAC,+GAA+G,CAAC;KAC5H,MAAM,CAAC,cAAc,EAAE,0BAA0B,CAAC;KAClD,MAAM,CAAC,UAAU,EAAE,qCAAqC,CAAC;KACzD,MAAM,CAAC,YAAY,EAAE,qCAAqC,CAAC;KAC3D,MAAM,CAAC,WAAW,EAAE,wBAAwB,CAAC;KAC7C,MAAM,CAAC,KAAK,EAAE,CAAuE,EAAE,EAAE;IACxF,IAAI,CAAC;QACH,MAAM,CAAC,GAAG,MAAM,IAAI,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC;QAChG,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI;YAAE,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QACtC,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,OAAO;YAAE,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QACzC,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,QAAQ;YAAE,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QAC1C,GAAG,CAAC,sFAAsF,CAAC,CAAC;IAC9F,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,IAAI,CAAC,CAAC,CAAC,CAAC;IACV,CAAC;AACH,CAAC,CAAC,CAAC;AAEL,OAAO;KACJ,OAAO,CAAC,OAAO,CAAC;KAChB,QAAQ,CAAC,OAAO,EAAE,oBAAoB,CAAC;KACvC,MAAM,CAAC,kBAAkB,EAAE,2BAA2B,EAAE,uBAAuB,CAAC;KAChF,WAAW,CAAC,yFAAyF,CAAC;KACtG,MAAM,CAAC,KAAK,EAAE,GAAW,EAAE,CAAkB,EAAE,EAAE;IAChD,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;QAC1C,GAAG,CAAC,sBAAsB,IAAI,4CAA4C,CAAC,CAAC,GAAG,0BAA0B,CAAC,CAAC;IAC7G,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,IAAI,CAAC,CAAC,CAAC,CAAC;IACV,CAAC;AACH,CAAC,CAAC,CAAC;AAEL,OAAO;KACJ,OAAO,CAAC,KAAK,CAAC;KACd,WAAW,CAAC,2CAA2C,CAAC;KACxD,MAAM,CAAC,KAAK,IAAI,EAAE;IACjB,MAAM,EAAE,cAAc,EAAE,GAAG,MAAM,MAAM,CAAC,iBAAiB,CAAC,CAAC;IAC3D,MAAM,cAAc,EAAE,CAAC;AACzB,CAAC,CAAC,CAAC;AAEL,OAAO;KACJ,OAAO,CAAC,OAAO,CAAC;KAChB,WAAW,CAAC,4CAA4C,CAAC;KACzD,MAAM,CAAC,GAAG,EAAE;IACX,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,SAAS,EAAE,CAAC,CAAC;AACpC,CAAC,CAAC,CAAC;AAEL,OAAO;KACJ,OAAO,CAAC,QAAQ,CAAC;KACjB,WAAW,CAAC,qCAAqC,CAAC;KAClD,MAAM,CAAC,GAAG,EAAE;IACX,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,UAAU,EAAE,CAAC,CAAC;AACrC,CAAC,CAAC,CAAC;AAEL,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC"}
package/dist/core.d.ts CHANGED
@@ -5,6 +5,8 @@ export interface RunOptions {
5
5
  noVoice?: boolean;
6
6
  gif?: boolean;
7
7
  log?: (msg: string) => void;
8
+ /** Editing progress, 0 to 1, a few times a second. */
9
+ onProgress?: (fraction: number) => void;
8
10
  }
9
11
  export interface RecordResult extends RenderResult {
10
12
  take: string;
package/dist/core.js CHANGED
@@ -55,7 +55,7 @@ export async function render(file, opts = {}) {
55
55
  const loaded = loadScenario(file);
56
56
  const take = readTake(loaded.takeDir);
57
57
  const audio = await narration(loaded, opts);
58
- return renderTake(loaded, take, { audio, gif: opts.gif, log: opts.log });
58
+ return renderTake(loaded, take, { audio, gif: opts.gif, log: opts.log, onProgress: opts.onProgress });
59
59
  }
60
60
  export async function check(file, opts = {}) {
61
61
  const loaded = loadScenario(file);
package/dist/core.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"core.js","sourceRoot":"","sources":["../src/core.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"core.js","sourceRoot":"","sources":["../src/core.ts"],"names":[],"mappings":"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"}
@@ -22,6 +22,18 @@ export interface CheckReport {
22
22
  screenshot?: string;
23
23
  url?: string;
24
24
  }
25
+ /**
26
+ * The browser size when the scenario sets none: the video's shape with a longer side of 1440
27
+ * (1440x810 for 16:9, 810x1440 for a vertical video), so with no padding the page fills the
28
+ * whole frame.
29
+ */
30
+ export declare function defaultViewport(output?: {
31
+ width?: number;
32
+ height?: number;
33
+ }): {
34
+ width: number;
35
+ height: number;
36
+ };
25
37
  export declare class StepError extends Error {
26
38
  index: number;
27
39
  step: string;
@@ -4,7 +4,17 @@ import { describeStep, stepKind } from "../scenario/schema.js";
4
4
  import { parseTarget } from "../scenario/target.js";
5
5
  import { launchBrowser } from "./browser.js";
6
6
  import { cardDuration, travelDuration, typingDelays } from "./pace.js";
7
- const DEFAULT_VIEWPORT = { width: 1440, height: 900 };
7
+ /**
8
+ * The browser size when the scenario sets none: the video's shape with a longer side of 1440
9
+ * (1440x810 for 16:9, 810x1440 for a vertical video), so with no padding the page fills the
10
+ * whole frame.
11
+ */
12
+ export function defaultViewport(output) {
13
+ const ow = output?.width ?? 1920;
14
+ const oh = output?.height ?? 1080;
15
+ const even = (n) => Math.round(n / 2) * 2;
16
+ return ow >= oh ? { width: 1440, height: even((1440 * oh) / ow) } : { width: even((1440 * ow) / oh), height: 1440 };
17
+ }
8
18
  const ACTION_TIMEOUT_MS = 15_000;
9
19
  export class StepError extends Error {
10
20
  index;
@@ -21,7 +31,7 @@ export async function runWeb(loaded, opts) {
21
31
  const { scenario } = loaded;
22
32
  const record = opts.mode === "record";
23
33
  const log = opts.log ?? (() => { });
24
- const viewport = scenario.viewport ?? DEFAULT_VIEWPORT;
34
+ const viewport = scenario.viewport ?? defaultViewport(scenario.output);
25
35
  const scale = record ? (scenario.scale ?? 2) : 1;
26
36
  const baseUrl = scenario.url;
27
37
  const browser = await launchBrowser();