@codefilm/recorder 3.0.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.
@@ -0,0 +1,1504 @@
1
+ import { createCameraRecordingOverlay as I, createCustomRecordingOverlay as D } from "./recording-overlays.js";
2
+ const O = [
3
+ { label: "YouTube HD (16:9)", w: 1920, h: 1080 },
4
+ { label: "Twitter / X (16:9)", w: 1200, h: 675 },
5
+ { label: "Instagram Post (4:5)", w: 1080, h: 1350 },
6
+ { label: "Instagram Reel / Story (9:16)", w: 1080, h: 1920 },
7
+ { label: "TikTok (9:16)", w: 1080, h: 1920 }
8
+ ], g = 24, F = 6, z = 1e3, B = 12, H = 0.08, $ = 0.015, N = 3e3, G = 500, x = 56, K = 2, j = "j", U = 12e4, _ = 3e3, W = Object.freeze({
9
+ cps: 24,
10
+ jitter: 0.22,
11
+ punctuationPauseMs: 120,
12
+ mistakeRate: 0.012
13
+ }), V = Object.freeze({
14
+ cps: 24,
15
+ jitter: 0.22,
16
+ punctuationPauseMs: 120,
17
+ mistakeRate: 0.012
18
+ });
19
+ class Q {
20
+ options;
21
+ listeners = /* @__PURE__ */ new Set();
22
+ state;
23
+ videoEl;
24
+ canvasEl;
25
+ recorder = null;
26
+ stream = null;
27
+ chunks = [];
28
+ lastRecording = null;
29
+ lastRecordingError = null;
30
+ recordingStartedAt = 0;
31
+ timelineMetadata = null;
32
+ activeFilmTitle = null;
33
+ destroyed = !1;
34
+ cameraRafId = 0;
35
+ renderRafId = 0;
36
+ targetRect;
37
+ liveRect;
38
+ mousePosGlobal = { x: 0, y: 0 };
39
+ holdTimer = null;
40
+ zoomOutTimer = null;
41
+ scrollPausedUntil = 0;
42
+ anchorX = 0;
43
+ anchorY = 0;
44
+ pointerEl = null;
45
+ pointerX = 0;
46
+ pointerY = 0;
47
+ tourRafId = 0;
48
+ tourAbortController = null;
49
+ activeFilmScriptContext = null;
50
+ activeFilmScriptOptions = null;
51
+ activeExecutionPromise = null;
52
+ finalizationPromise = null;
53
+ pendingActionGates = [];
54
+ activeTypingRandom = Math.random;
55
+ ignoredElements = /* @__PURE__ */ new Map();
56
+ overlays = /* @__PURE__ */ new Map();
57
+ constructor(e = {}) {
58
+ const t = {
59
+ fps: e.fps ?? 60,
60
+ resolutions: e.resolutions ?? O,
61
+ defaultResolutionIndex: e.defaultResolutionIndex ?? 0,
62
+ autoZoom: e.autoZoom ?? !0,
63
+ pauseDuringScroll: e.pauseDuringScroll ?? !0,
64
+ showGrid: e.showGrid ?? !0,
65
+ showOutline: e.showOutline ?? !1,
66
+ enableShortcuts: e.enableShortcuts ?? !0,
67
+ elementSelector: e.elementSelector ?? null,
68
+ elementPadding: e.elementPadding ?? 1.1,
69
+ fixedRecordingRegion: e.fixedRecordingRegion ?? null,
70
+ movePointerToSelector: e.movePointerToSelector ?? null,
71
+ pointerClassName: e.pointerClassName ?? "",
72
+ pointerFillColor: e.pointerFillColor ?? "#ffffff",
73
+ pointerBorderColor: e.pointerBorderColor ?? "#18181b",
74
+ pointerRippleColor: e.pointerRippleColor ?? "#64748b",
75
+ disableRipple: e.disableRipple ?? !1,
76
+ ignoreSelector: e.ignoreSelector ?? null,
77
+ overlays: e.overlays ?? [],
78
+ autoDownload: e.autoDownload ?? !0,
79
+ includeTimelineMetadata: e.includeTimelineMetadata ?? !1,
80
+ selectorTimeoutMs: e.selectorTimeoutMs ?? _,
81
+ typingDefaults: this.resolveTypingOptions(e.typingDefaults),
82
+ typingSeed: e.typingSeed ?? "",
83
+ onRecordingStart: e.onRecordingStart ?? (() => {
84
+ }),
85
+ onRecordingStop: e.onRecordingStop ?? (() => {
86
+ }),
87
+ onRecordingError: e.onRecordingError ?? (() => {
88
+ }),
89
+ onTourStart: e.onTourStart ?? (() => {
90
+ }),
91
+ onTourStop: e.onTourStop ?? (() => {
92
+ }),
93
+ onReadyToEnd: e.onReadyToEnd ?? (() => {
94
+ }),
95
+ onTourError: e.onTourError ?? (() => {
96
+ }),
97
+ onSceneStart: e.onSceneStart ?? (() => {
98
+ }),
99
+ onSceneEnd: e.onSceneEnd ?? (() => {
100
+ }),
101
+ onActionStart: e.onActionStart ?? (() => {
102
+ }),
103
+ onActionEnd: e.onActionEnd ?? (() => {
104
+ })
105
+ };
106
+ this.options = t;
107
+ const i = this.options.resolutions[this.options.defaultResolutionIndex] || O[0], o = {
108
+ w: typeof window < "u" ? window.innerWidth : 1280,
109
+ h: typeof window < "u" ? window.innerHeight : 720
110
+ }, n = this.computeGrid(o.w, o.h);
111
+ let r;
112
+ if (this.options.elementSelector) {
113
+ const s = this.calculateElementRect(
114
+ this.options.elementSelector,
115
+ this.options.elementPadding,
116
+ i.w / i.h
117
+ );
118
+ if (s)
119
+ r = s;
120
+ else {
121
+ const a = this.getRecordingRegionBox(
122
+ i.w / i.h,
123
+ g,
124
+ n.rows,
125
+ g
126
+ ), c = Math.floor((g - a.w) / 2), h = Math.floor((n.rows - a.h) / 2);
127
+ r = { col: c, row: h, w: a.w, h: a.h };
128
+ }
129
+ } else {
130
+ const s = this.getRecordingRegionBox(
131
+ i.w / i.h,
132
+ g,
133
+ n.rows,
134
+ g
135
+ ), a = Math.floor((g - s.w) / 2), c = Math.floor((n.rows - s.h) / 2);
136
+ r = { col: a, row: c, w: s.w, h: s.h };
137
+ }
138
+ this.targetRect = {
139
+ col: r.col,
140
+ row: r.row,
141
+ w: r.w,
142
+ h: r.h
143
+ }, this.liveRect = { ...this.targetRect }, this.state = {
144
+ recording: !1,
145
+ showGrid: this.options.showGrid,
146
+ autoZoom: this.options.autoZoom,
147
+ pauseDuringScroll: this.options.pauseDuringScroll,
148
+ resolution: i,
149
+ mousePaused: !1,
150
+ viewport: o,
151
+ displayRect: { ...this.liveRect },
152
+ showOutline: this.options.showOutline,
153
+ elementSelector: this.options.elementSelector,
154
+ elementPadding: this.options.elementPadding,
155
+ fixedRecordingRegion: this.options.fixedRecordingRegion,
156
+ movePointerToSelector: this.options.movePointerToSelector,
157
+ pointerClassName: this.options.pointerClassName,
158
+ pointerFillColor: this.options.pointerFillColor,
159
+ pointerBorderColor: this.options.pointerBorderColor,
160
+ pointerRippleColor: this.options.pointerRippleColor,
161
+ disableRipple: this.options.disableRipple,
162
+ ignoreSelector: this.options.ignoreSelector,
163
+ tourState: "idle",
164
+ executionState: "idle",
165
+ currentScene: null
166
+ }, typeof window < "u" ? (this.videoEl = document.createElement("video"), this.videoEl.muted = !0, this.videoEl.playsInline = !0, this.videoEl.style.display = "none", document.body.appendChild(this.videoEl), this.canvasEl = document.createElement("canvas"), this.canvasEl.style.display = "none", document.body.appendChild(this.canvasEl), this.setupEventListeners(), this.startCameraLoop(), this.options.overlays.forEach((s) => {
167
+ s.type === "camera" && this.createCameraOverlay(s);
168
+ })) : (this.videoEl = {}, this.canvasEl = {});
169
+ }
170
+ // Grid and Aspect Helper Methods
171
+ computeGrid(e, t) {
172
+ const i = e / g, o = i * 9 / 16, n = Math.max(0, t - x), r = Math.max(1, Math.floor(n / o));
173
+ return { cellW: i, cellH: o, rows: r };
174
+ }
175
+ getAspectBox(e, t, i, o) {
176
+ let n = 1, r = 1, s = 1 / 0;
177
+ const a = Math.min(t, o);
178
+ for (let c = 1; c <= a; c++) {
179
+ const h = c * 16 / (9 * e), d = [Math.floor(h), Math.ceil(h)].filter((p) => p >= 1 && p <= i);
180
+ for (const p of d) {
181
+ const u = c / p * 1.7777777777777777, m = Math.abs(u - e);
182
+ (m < s - 1e-6 || Math.abs(m - s) < 1e-6 && c > n) && (s = m, n = c, r = p);
183
+ }
184
+ }
185
+ return { w: n, h: r };
186
+ }
187
+ getRecordingRegionBox(e, t, i, o) {
188
+ return this.options.fixedRecordingRegion === "smallest-16:9" ? this.getAspectBox(16 / 9, t, i, F) : this.getAspectBox(e, t, i, o);
189
+ }
190
+ clampRect(e, t) {
191
+ const i = Math.min(g, Math.max(1, e.w)), o = Math.min(t, Math.max(1, e.h)), n = Math.min(g - i, Math.max(0, e.col)), r = Math.min(t - o, Math.max(0, e.row));
192
+ return { col: n, row: r, w: i, h: o };
193
+ }
194
+ centeredRectOnPoint(e, t, i, o, n) {
195
+ const { cellW: r, cellH: s, rows: a } = this.computeGrid(i, o), c = Math.max(0, t - x), h = Math.min(g - 1, Math.max(0, Math.floor(e / r))), d = Math.min(a - 1, Math.max(0, Math.floor(c / s))), { w: p, h: u } = this.getRecordingRegionBox(n, g, a, F);
196
+ return this.clampRect(
197
+ {
198
+ col: Math.round(h - p / 2 + 0.5),
199
+ row: Math.round(d - u / 2 + 0.5),
200
+ w: p,
201
+ h: u
202
+ },
203
+ a
204
+ );
205
+ }
206
+ // Event Handlers
207
+ setupEventListeners() {
208
+ window.addEventListener("resize", this.handleResize), window.addEventListener("mousemove", this.handleMouseMove), window.addEventListener("wheel", this.handleScrollOrWheel, { passive: !0 }), window.addEventListener("scroll", this.handleScrollOrWheel, { passive: !0 }), this.options.enableShortcuts && window.addEventListener("keydown", this.handleKeyDown);
209
+ }
210
+ cleanupEventListeners() {
211
+ window.removeEventListener("resize", this.handleResize), window.removeEventListener("mousemove", this.handleMouseMove), window.removeEventListener("wheel", this.handleScrollOrWheel), window.removeEventListener("scroll", this.handleScrollOrWheel), window.removeEventListener("keydown", this.handleKeyDown);
212
+ }
213
+ handleResize = () => {
214
+ const e = window.innerWidth, t = window.innerHeight;
215
+ if (this.updateState({ viewport: { w: e, h: t } }), this.options.elementSelector) return;
216
+ const i = this.computeGrid(e, t), o = this.state.resolution.w / this.state.resolution.h, { w: n, h: r } = this.getRecordingRegionBox(o, g, i.rows, g), s = Math.floor((g - n) / 2), a = Math.floor((i.rows - r) / 2);
217
+ this.targetRect = { col: s, row: a, w: n, h: r };
218
+ };
219
+ handleMouseMove = (e) => {
220
+ this.state.mousePaused || this.options.elementSelector || (this.mousePosGlobal = { x: e.clientX, y: e.clientY }, Math.hypot(e.clientX - this.anchorX, e.clientY - this.anchorY) > B && (this.armHold(e.clientX, e.clientY), this.zoomOutTimer && (clearTimeout(this.zoomOutTimer), this.zoomOutTimer = null)));
221
+ };
222
+ handleScrollOrWheel = () => {
223
+ this.options.elementSelector || (this.scrollPausedUntil = performance.now() + G, this.holdTimer && clearTimeout(this.holdTimer));
224
+ };
225
+ handleKeyDown = (e) => {
226
+ if (!(e.metaKey || e.ctrlKey)) return;
227
+ const i = e.key.toLowerCase();
228
+ i === j ? (e.preventDefault(), this.state.recording ? this.stopRecording() : this.startRecording()) : i === "l" ? (e.preventDefault(), this.setMousePaused(!this.state.mousePaused)) : e.key === "ArrowUp" ? (e.preventDefault(), this.options.elementSelector ? this.setElementSelector(null) : this.zoomTop()) : e.key === "ArrowDown" && (e.preventDefault(), this.options.elementSelector || this.zoomBottom());
229
+ };
230
+ armHold(e, t) {
231
+ this.holdTimer && clearTimeout(this.holdTimer), this.anchorX = e, this.anchorY = t, this.holdTimer = setTimeout(() => {
232
+ this.state.autoZoom && (this.state.pauseDuringScroll && performance.now() < this.scrollPausedUntil || (this.zoomToMouse(), this.scheduleIdleZoomOut()));
233
+ }, z);
234
+ }
235
+ scheduleIdleZoomOut() {
236
+ this.zoomOutTimer && clearTimeout(this.zoomOutTimer), this.zoomOutTimer = setTimeout(() => {
237
+ this.zoomOutTimer = null, !(!this.state.autoZoom || this.options.elementSelector) && (this.state.pauseDuringScroll && performance.now() < this.scrollPausedUntil || this.resetFullScreen());
238
+ }, N);
239
+ }
240
+ calculateElementRect(e, t = 1.1, i) {
241
+ if (typeof document > "u") return null;
242
+ const o = document.querySelector(e);
243
+ if (!o) return null;
244
+ const n = o.getBoundingClientRect(), r = window.innerWidth, s = window.innerHeight, a = this.computeGrid(r, s), c = n.left + n.width / 2, h = n.top + n.height / 2, d = c / a.cellW, p = (h - x) / a.cellH, u = n.width / a.cellW, m = n.height / a.cellH, l = i ?? this.state.resolution.w / this.state.resolution.h, f = a.cellH / a.cellW, v = l * f;
245
+ if (this.options.fixedRecordingRegion) {
246
+ const { w: C, h: R } = this.getRecordingRegionBox(l, g, a.rows, F);
247
+ return this.clampRect(
248
+ {
249
+ col: d - C / 2,
250
+ row: p - R / 2,
251
+ w: C,
252
+ h: R
253
+ },
254
+ a.rows
255
+ );
256
+ }
257
+ const w = u * t, y = m * t;
258
+ let E = Math.max(w, y * v), b = E / v;
259
+ const A = d - E / 2, P = p - b / 2;
260
+ return this.clampRect({ col: A, row: P, w: E, h: b }, a.rows);
261
+ }
262
+ // Camera Zoom Loop
263
+ startCameraLoop() {
264
+ const e = () => {
265
+ if (this.options.elementSelector) {
266
+ const r = this.calculateElementRect(
267
+ this.options.elementSelector,
268
+ this.options.elementPadding
269
+ );
270
+ r && (this.targetRect = r);
271
+ }
272
+ const t = this.targetRect, i = this.liveRect, n = t.w > i.w ? $ : H;
273
+ this.liveRect = {
274
+ col: i.col + (t.col - i.col) * n,
275
+ row: i.row + (t.row - i.row) * n,
276
+ w: i.w + (t.w - i.w) * n,
277
+ h: i.h + (t.h - i.h) * n
278
+ }, this.updateState({ displayRect: { ...this.liveRect } }), this.cameraRafId = requestAnimationFrame(e);
279
+ };
280
+ this.cameraRafId = requestAnimationFrame(e);
281
+ }
282
+ // Public Actions
283
+ zoomToMouse() {
284
+ const e = this.state.resolution.w / this.state.resolution.h;
285
+ this.targetRect = this.centeredRectOnPoint(
286
+ this.mousePosGlobal.x,
287
+ this.mousePosGlobal.y,
288
+ window.innerWidth,
289
+ window.innerHeight,
290
+ e
291
+ );
292
+ }
293
+ getFullFrameRect(e) {
294
+ const t = this.state.resolution.w / this.state.resolution.h, i = this.computeGrid(window.innerWidth, window.innerHeight), { w: o, h: n } = this.getRecordingRegionBox(t, g, i.rows, g), r = e === "top" ? 0 : e === "bottom" ? i.rows - n : Math.floor((i.rows - n) / 2);
295
+ return {
296
+ col: Math.floor((g - o) / 2),
297
+ row: r,
298
+ w: o,
299
+ h: n
300
+ };
301
+ }
302
+ setFullFrameRect(e, t = !1) {
303
+ const i = this.getFullFrameRect(e);
304
+ this.targetRect = i, t && (this.liveRect = { ...i }, this.updateState({ displayRect: { ...i } }));
305
+ }
306
+ zoomTop(e = !1) {
307
+ this.setFullFrameRect("top", e);
308
+ }
309
+ zoomCenter(e = !1) {
310
+ this.setFullFrameRect("center", e);
311
+ }
312
+ zoomBottom(e = !1) {
313
+ this.setFullFrameRect("bottom", e);
314
+ }
315
+ zoomBottomFold() {
316
+ this.zoomBottom();
317
+ }
318
+ zoomTopFold(e = !1) {
319
+ this.zoomTop(e);
320
+ }
321
+ resetFullScreen() {
322
+ this.zoomCenter();
323
+ }
324
+ zoomTopFullFrame(e = !1) {
325
+ this.zoomTop(e);
326
+ }
327
+ // Recording API
328
+ getRecordingElapsedMs() {
329
+ return this.recordingStartedAt ? Math.max(0, Math.round(performance.now() - this.recordingStartedAt)) : 0;
330
+ }
331
+ beginTimelineMetadata() {
332
+ if (!this.options.includeTimelineMetadata) {
333
+ this.timelineMetadata = null;
334
+ return;
335
+ }
336
+ this.timelineMetadata = {
337
+ schema: "code-film.timeline.v1",
338
+ title: this.activeFilmTitle,
339
+ fps: this.options.fps,
340
+ width: this.state.resolution.w,
341
+ height: this.state.resolution.h,
342
+ scenes: [],
343
+ actions: [],
344
+ markers: []
345
+ }, this.recordTimelineMarker("recording:start");
346
+ }
347
+ finishTimelineMetadata() {
348
+ if (this.timelineMetadata)
349
+ return this.recordTimelineMarker("recording:stop"), {
350
+ ...this.timelineMetadata,
351
+ scenes: this.timelineMetadata.scenes.map((e) => ({ ...e })),
352
+ actions: this.timelineMetadata.actions.map((e) => ({ ...e })),
353
+ markers: this.timelineMetadata.markers.map((e) => ({ ...e }))
354
+ };
355
+ }
356
+ recordTimelineMarker(e, t = {}) {
357
+ this.timelineMetadata && this.timelineMetadata.markers.push({
358
+ timeMs: this.getRecordingElapsedMs(),
359
+ name: e,
360
+ ...t
361
+ });
362
+ }
363
+ recordOverlayEvent(e) {
364
+ this.recordTimelineMarker(`overlay:${e.type}`, {
365
+ data: {
366
+ id: e.id,
367
+ ...e.data
368
+ }
369
+ });
370
+ }
371
+ renderRecordingOverlays(e) {
372
+ this.overlays.forEach((t) => {
373
+ t.includeInCapture && t.draw(e, this.state.resolution.w, this.state.resolution.h);
374
+ });
375
+ }
376
+ createCameraOverlay(e) {
377
+ if (typeof document > "u")
378
+ throw new Error("[FilmRecorder] createCameraOverlay requires a browser document.");
379
+ const t = I(e, (i) => {
380
+ this.recordOverlayEvent(i);
381
+ });
382
+ return this.overlays.set(t.id, t), t;
383
+ }
384
+ registerOverlay(e) {
385
+ if (typeof document > "u")
386
+ throw new Error("[FilmRecorder] registerOverlay requires a browser document.");
387
+ const t = D(e, (i) => {
388
+ this.recordOverlayEvent(i);
389
+ });
390
+ return this.overlays.set(t.id, t), t;
391
+ }
392
+ getOverlay(e) {
393
+ return this.overlays.get(e) ?? null;
394
+ }
395
+ removeOverlay(e) {
396
+ const t = this.overlays.get(e);
397
+ t && (t.destroy(), this.overlays.delete(e));
398
+ }
399
+ setOverlays(e) {
400
+ this.overlays.forEach((t) => t.destroy()), this.overlays.clear(), e.forEach((t) => {
401
+ t.type === "camera" && this.createCameraOverlay(t);
402
+ });
403
+ }
404
+ startTimelineScene(e, t) {
405
+ if (!this.timelineMetadata) return null;
406
+ const i = {
407
+ id: `scene-${t + 1}`,
408
+ name: e.name,
409
+ declaredStartMs: Math.round(e.startTime * 1e3),
410
+ declaredEndMs: Math.round(e.endTime * 1e3),
411
+ actualStartMs: this.getRecordingElapsedMs(),
412
+ actualEndMs: null
413
+ };
414
+ return this.timelineMetadata.scenes.push(i), this.recordTimelineMarker("scene:start", {
415
+ data: { label: e.name },
416
+ scene: e.name
417
+ }), i;
418
+ }
419
+ endTimelineScene(e) {
420
+ e && (e.actualEndMs = this.getRecordingElapsedMs(), this.recordTimelineMarker("scene:end", {
421
+ data: { label: e.name },
422
+ scene: e.name
423
+ }));
424
+ }
425
+ startTimelineAction(e, t, i) {
426
+ if (!this.timelineMetadata || !e) return null;
427
+ const o = {
428
+ type: t.type,
429
+ startMs: this.getRecordingElapsedMs(),
430
+ endMs: null,
431
+ commandIndex: i,
432
+ scene: e.name,
433
+ selector: t.selector,
434
+ to: t.to,
435
+ padding: t.padding,
436
+ text: t.text,
437
+ durationMs: t.durationMs
438
+ };
439
+ return this.timelineMetadata.actions.push(o), this.recordTimelineMarker(`command:${t.type}:start`, {
440
+ data: { label: t.selector ?? t.to ?? t.text ?? t.type },
441
+ scene: e.name,
442
+ commandIndex: i
443
+ }), o;
444
+ }
445
+ endTimelineAction(e, t) {
446
+ !e || !t || (t.endMs = this.getRecordingElapsedMs(), this.recordTimelineMarker(`command:${t.type}:end`, {
447
+ data: { label: t.selector ?? t.to ?? t.text ?? t.type },
448
+ scene: e.name,
449
+ commandIndex: t.commandIndex
450
+ }));
451
+ }
452
+ getLastRecording() {
453
+ return this.lastRecording;
454
+ }
455
+ clearLastRecording() {
456
+ this.lastRecording && (URL.revokeObjectURL(this.lastRecording.url), this.lastRecording = null);
457
+ }
458
+ getLastRecordingError() {
459
+ return this.lastRecordingError;
460
+ }
461
+ normalizeRecordingError(e) {
462
+ return e instanceof Error ? e : new Error(typeof e == "string" ? e : "Unknown recording error");
463
+ }
464
+ handleRecordingError(e) {
465
+ const t = this.normalizeRecordingError(e);
466
+ return this.lastRecordingError = t, this.updateState({ showGrid: this.options.showGrid }), this.options.onRecordingError?.(t), t;
467
+ }
468
+ async requestCaptureStream() {
469
+ return navigator.mediaDevices.getDisplayMedia({
470
+ video: { frameRate: this.options.fps, width: { ideal: 3840 }, height: { ideal: 2160 } },
471
+ audio: !0,
472
+ // @ts-ignore
473
+ preferCurrentTab: !0
474
+ });
475
+ }
476
+ async attachCaptureStream(e) {
477
+ this.stream = e, this.videoEl.srcObject = e, await this.videoEl.play(), await this.waitForCaptureVideoFrame();
478
+ }
479
+ async waitForCaptureVideoFrame(e = 3e3) {
480
+ this.videoEl.videoWidth > 0 && this.videoEl.videoHeight > 0 || await new Promise((t, i) => {
481
+ let o = 0;
482
+ const n = window.setTimeout(() => {
483
+ r(), i(new Error("Screen capture started, but no video frame was available."));
484
+ }, e), r = () => {
485
+ window.clearTimeout(n), o && cancelAnimationFrame(o), this.videoEl.removeEventListener("loadedmetadata", s), this.videoEl.removeEventListener("canplay", s);
486
+ }, s = () => {
487
+ if (this.videoEl.videoWidth > 0 && this.videoEl.videoHeight > 0) {
488
+ r(), t();
489
+ return;
490
+ }
491
+ o = requestAnimationFrame(s);
492
+ };
493
+ this.videoEl.addEventListener("loadedmetadata", s), this.videoEl.addEventListener("canplay", s), s();
494
+ });
495
+ }
496
+ stopCaptureStream() {
497
+ this.stream?.getTracks().forEach((e) => e.stop()), this.stream = null, this.videoEl.srcObject = null;
498
+ }
499
+ hideIgnoredElements() {
500
+ this.restoreIgnoredElements(), !(!this.options.ignoreSelector || typeof document > "u") && document.querySelectorAll(this.options.ignoreSelector).forEach((e) => {
501
+ e instanceof HTMLElement && (e.closest(".cfr-settings-bar, .cfr-recording-overlay") || (this.ignoredElements.set(e, {
502
+ visibility: e.style.getPropertyValue("visibility"),
503
+ priority: e.style.getPropertyPriority("visibility")
504
+ }), e.style.setProperty("visibility", "hidden", "important")));
505
+ });
506
+ }
507
+ restoreIgnoredElements() {
508
+ this.ignoredElements.forEach((e, t) => {
509
+ t.style.setProperty("visibility", e.visibility, e.priority);
510
+ }), this.ignoredElements.clear();
511
+ }
512
+ getActiveCaptureStream() {
513
+ return this.stream && this.stream.getTracks().some((e) => e.readyState === "live") ? this.stream : null;
514
+ }
515
+ async prepareCapture() {
516
+ if (this.getActiveCaptureStream()) return !0;
517
+ try {
518
+ const e = await this.requestCaptureStream();
519
+ return await this.attachCaptureStream(e), this.lastRecordingError = null, !0;
520
+ } catch (e) {
521
+ throw this.stopCaptureStream(), this.handleRecordingError(e);
522
+ }
523
+ }
524
+ async startRecording() {
525
+ if (this.state.recording) return !1;
526
+ try {
527
+ const e = this.getActiveCaptureStream();
528
+ e ? await this.attachCaptureStream(e) : await this.prepareCapture();
529
+ const t = this.getActiveCaptureStream();
530
+ if (!t)
531
+ throw new Error("No active capture stream is available.");
532
+ this.options.elementSelector && (this.options.elementSelector = null, this.updateState({ elementSelector: null })), this.zoomTopFullFrame(!0), this.resetPointerEntryPosition(), this.hideIgnoredElements(), this.updateState({ showGrid: !1 });
533
+ const i = this.canvasEl.getContext("2d");
534
+ i.imageSmoothingEnabled = !0, i.imageSmoothingQuality = "high", this.canvasEl.width = this.state.resolution.w, this.canvasEl.height = this.state.resolution.h;
535
+ const o = () => {
536
+ const a = window.innerWidth, c = window.innerHeight, h = this.computeGrid(a, c), d = this.liveRect, p = this.videoEl.videoWidth / a, u = this.videoEl.videoHeight / c;
537
+ i.drawImage(
538
+ this.videoEl,
539
+ d.col * h.cellW * p,
540
+ (x + d.row * h.cellH) * u,
541
+ d.w * h.cellW * p,
542
+ d.h * h.cellH * u,
543
+ 0,
544
+ 0,
545
+ this.state.resolution.w,
546
+ this.state.resolution.h
547
+ ), this.renderRecordingOverlays(i), this.renderRafId = requestAnimationFrame(o);
548
+ };
549
+ o();
550
+ const n = this.canvasEl.captureStream(this.options.fps);
551
+ t.getAudioTracks().forEach((a) => n.addTrack(a));
552
+ const r = [
553
+ "video/webm;codecs=vp9",
554
+ "video/webm;codecs=vp8",
555
+ "video/webm",
556
+ "video/mp4"
557
+ ].find((a) => MediaRecorder.isTypeSupported(a)), s = new MediaRecorder(n, {
558
+ ...r && { mimeType: r },
559
+ videoBitsPerSecond: 1e7
560
+ });
561
+ return this.chunks = [], this.recordingStartedAt = performance.now(), this.beginTimelineMetadata(), s.ondataavailable = (a) => {
562
+ a.data.size && this.chunks.push(a.data);
563
+ }, s.onstop = () => {
564
+ const a = performance.now(), c = new Blob(this.chunks, { type: s.mimeType }), h = URL.createObjectURL(c), d = s.mimeType.includes("mp4") ? "capture.mp4" : "capture.webm", p = Math.max(0, Math.round(a - this.recordingStartedAt)), u = {
565
+ blob: c,
566
+ url: h,
567
+ mimeType: s.mimeType,
568
+ filename: d,
569
+ durationMs: p,
570
+ startedAt: this.recordingStartedAt,
571
+ stoppedAt: a,
572
+ timeline: this.finishTimelineMetadata(),
573
+ webm: s.mimeType.includes("webm") ? {
574
+ seekable: !1,
575
+ hasDurationMetadata: !1,
576
+ durationMs: p,
577
+ sizeBytes: c.size
578
+ } : void 0
579
+ };
580
+ this.clearLastRecording(), this.lastRecording = u;
581
+ try {
582
+ this.options.onRecordingStop?.(u);
583
+ } finally {
584
+ if (this.options.autoDownload) {
585
+ const m = document.createElement("a");
586
+ m.href = u.url, m.download = u.filename, m.click();
587
+ }
588
+ this.destroyed && this.clearLastRecording(), this.timelineMetadata = null, this.recordingStartedAt = 0, this.activeFilmTitle = null;
589
+ }
590
+ }, s.start(1e3), this.recorder = s, this.lastRecordingError = null, this.updateState({ recording: !0 }), this.options.onRecordingStart?.(), this.options.movePointerToSelector && setTimeout(() => {
591
+ this.options.movePointerToSelector && this.state.recording && this.playPointerTour(this.options.movePointerToSelector);
592
+ }, 500), !0;
593
+ } catch (e) {
594
+ throw e === this.lastRecordingError ? e : (cancelAnimationFrame(this.renderRafId), this.renderRafId = 0, this.stopCaptureStream(), this.restoreIgnoredElements(), this.recorder = null, this.handleRecordingError(e));
595
+ }
596
+ }
597
+ stopRecording() {
598
+ this.state.recording && (cancelAnimationFrame(this.renderRafId), this.recorder?.stop(), this.stopCaptureStream(), this.restoreIgnoredElements(), this.recorder = null, this.pointerEl && (this.pointerEl.style.display = "none", this.pointerEl.classList.remove("cfr-holding")), this.tourRafId && (cancelAnimationFrame(this.tourRafId), this.tourRafId = 0), this.updateState({
599
+ recording: !1,
600
+ showGrid: this.options.showGrid
601
+ }));
602
+ }
603
+ captureBitmap() {
604
+ this.state.recording && this.canvasEl.toBlob((e) => {
605
+ if (!e) return;
606
+ const t = document.createElement("a");
607
+ t.href = URL.createObjectURL(e), t.download = "capture.png", t.click(), setTimeout(() => URL.revokeObjectURL(t.href), 1e3);
608
+ }, "image/png");
609
+ }
610
+ ensurePointerEl() {
611
+ typeof window > "u" || typeof document > "u" || (this.pointerEl || (this.pointerEl = document.createElement("div"), this.pointerX = window.innerWidth / 2, this.pointerY = window.innerHeight + 40, this.pointerEl.style.left = `${this.pointerX}px`, this.pointerEl.style.top = `${this.pointerY}px`, document.body.appendChild(this.pointerEl)), this.applyPointerClassName(), this.applyPointerColors(), this.pointerEl.style.display = "block");
612
+ }
613
+ applyPointerClassName() {
614
+ if (!this.pointerEl) return;
615
+ const e = this.pointerEl.classList.contains("cfr-holding"), t = this.options.pointerClassName.split(/\s+/).filter(Boolean);
616
+ this.pointerEl.className = ["cfr-simulated-pointer", ...t].join(" "), e && this.pointerEl.classList.add("cfr-holding"), this.pointerEl.classList.toggle("cfr-ripple-disabled", this.options.disableRipple);
617
+ }
618
+ validatePointerColor(e, t) {
619
+ if (!/^#[0-9a-f]{6}$/i.test(e))
620
+ throw new TypeError(`[FilmRecorder] ${t} must be a 6-digit hex color.`);
621
+ return e;
622
+ }
623
+ applyPointerColors() {
624
+ if (!this.pointerEl) return;
625
+ const e = this.validatePointerColor(this.options.pointerFillColor, "pointerFillColor"), t = this.validatePointerColor(this.options.pointerBorderColor, "pointerBorderColor"), i = this.validatePointerColor(this.options.pointerRippleColor, "pointerRippleColor"), o = `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="30" viewBox="0 0 24 30" fill="none"><path d="M2 2.25V23.15L7.55 17.83L11.65 27.4L16.05 25.52L12.08 16.25H20.2L2 2.25Z" fill="${e}" stroke="${t}" stroke-width="2.2" stroke-linejoin="round"/></svg>`;
626
+ this.pointerEl.style.backgroundImage = `url("data:image/svg+xml,${encodeURIComponent(o)}")`, this.pointerEl.style.setProperty("--cfr-pointer-ripple-color", i);
627
+ }
628
+ resetPointerEntryPosition() {
629
+ this.ensurePointerEl(), this.pointerEl && (this.pointerX = window.innerWidth / 2, this.pointerY = window.innerHeight + 40, this.pointerEl.style.left = `${this.pointerX}px`, this.pointerEl.style.top = `${this.pointerY}px`, this.pointerEl.classList.remove("cfr-holding"));
630
+ }
631
+ async getTargetElement(e, t, i, o = this.options.selectorTimeoutMs) {
632
+ if (typeof e != "string")
633
+ return e;
634
+ const n = performance.now();
635
+ for (; ; ) {
636
+ if (i?.aborted)
637
+ throw new DOMException("Aborted", "AbortError");
638
+ const r = document.querySelector(e);
639
+ if (r)
640
+ return r;
641
+ if (performance.now() - n > o)
642
+ throw this.createSelectorError(e, t);
643
+ await new Promise((s) => setTimeout(s, 50));
644
+ }
645
+ }
646
+ async animatePointerTo(e, t = 800, i, o = this.options.selectorTimeoutMs) {
647
+ if (typeof window > "u" || typeof document > "u") return;
648
+ if (this.ensurePointerEl(), i?.aborted)
649
+ throw new DOMException("Aborted", "AbortError");
650
+ const n = await this.getTargetElement(
651
+ e,
652
+ "animatePointerTo",
653
+ i,
654
+ o
655
+ ), r = n.getBoundingClientRect(), s = r.left + r.width / 2, a = r.top + r.height / 2, c = this.pointerX, h = this.pointerY, d = performance.now();
656
+ return this.tourRafId && cancelAnimationFrame(this.tourRafId), new Promise((p, u) => {
657
+ const m = () => {
658
+ cancelAnimationFrame(this.tourRafId), u(new DOMException("Aborted", "AbortError"));
659
+ };
660
+ i && i.addEventListener("abort", m);
661
+ const l = (f) => {
662
+ if (i?.aborted) return;
663
+ const v = f - d, w = Math.min(v / t, 1), y = w < 0.5 ? 2 * w * w : 1 - Math.pow(-2 * w + 2, 2) / 2, E = c + (s - c) * y, b = h + (a - h) * y;
664
+ this.pointerX = E, this.pointerY = b, this.pointerEl && (this.pointerEl.style.left = `${E}px`, this.pointerEl.style.top = `${b}px`), w < 1 ? this.tourRafId = requestAnimationFrame(l) : (this.dispatchPointerHover(n, s, a), i && i.removeEventListener("abort", m), p());
665
+ };
666
+ this.tourRafId = requestAnimationFrame(l);
667
+ });
668
+ }
669
+ dispatchPointerHover(e, t, i) {
670
+ const o = {
671
+ bubbles: !0,
672
+ cancelable: !0,
673
+ view: window,
674
+ clientX: t,
675
+ clientY: i
676
+ };
677
+ e.dispatchEvent(new MouseEvent("mouseover", o)), e.dispatchEvent(new MouseEvent("mouseenter", { ...o, bubbles: !1 })), e.dispatchEvent(new MouseEvent("mousemove", o));
678
+ }
679
+ async simulateClick(e, t, i = this.options.selectorTimeoutMs) {
680
+ if (typeof window > "u" || typeof document > "u") return;
681
+ if (t?.aborted)
682
+ throw new DOMException("Aborted", "AbortError");
683
+ if (await this.animatePointerTo(e, 600, t, i), t?.aborted)
684
+ throw new DOMException("Aborted", "AbortError");
685
+ this.pointerEl && this.pointerEl.classList.add("cfr-holding");
686
+ let o;
687
+ try {
688
+ o = await this.getTargetElement(e, "simulateClick", t, i);
689
+ } catch (a) {
690
+ throw this.pointerEl && this.pointerEl.classList.remove("cfr-holding"), a;
691
+ }
692
+ const n = new MouseEvent("mousedown", { bubbles: !0, cancelable: !0, view: window }), r = new MouseEvent("mouseup", { bubbles: !0, cancelable: !0, view: window }), s = new MouseEvent("click", { bubbles: !0, cancelable: !0, view: window });
693
+ o.dispatchEvent(n), o.dispatchEvent(r), o instanceof HTMLElement ? o.click() : o.dispatchEvent(s), await new Promise((a, c) => {
694
+ const h = () => {
695
+ clearTimeout(d), this.pointerEl && this.pointerEl.classList.remove("cfr-holding"), c(new DOMException("Aborted", "AbortError"));
696
+ };
697
+ t && t.addEventListener("abort", h);
698
+ const d = setTimeout(() => {
699
+ t && t.removeEventListener("abort", h), this.pointerEl && this.pointerEl.classList.remove("cfr-holding"), a();
700
+ }, 250);
701
+ });
702
+ }
703
+ isFormTypingTarget(e) {
704
+ return typeof HTMLInputElement < "u" && typeof HTMLTextAreaElement < "u" && (e instanceof HTMLInputElement || e instanceof HTMLTextAreaElement) ? !0 : "value" in e && typeof e.value == "string";
705
+ }
706
+ isContentEditableTypingTarget(e) {
707
+ return e instanceof HTMLElement && e.isContentEditable;
708
+ }
709
+ dispatchKeyboardInputEvents(e, t, i = "insertText") {
710
+ const o = new KeyboardEvent("keydown", {
711
+ key: t,
712
+ charCode: t.length === 1 ? t.charCodeAt(0) : 0,
713
+ bubbles: !0
714
+ }), n = new KeyboardEvent("keypress", {
715
+ key: t,
716
+ charCode: t.length === 1 ? t.charCodeAt(0) : 0,
717
+ bubbles: !0
718
+ }), r = new InputEvent("input", {
719
+ data: i === "insertText" ? t : null,
720
+ inputType: i,
721
+ bubbles: !0
722
+ }), s = new KeyboardEvent("keyup", {
723
+ key: t,
724
+ charCode: t.length === 1 ? t.charCodeAt(0) : 0,
725
+ bubbles: !0
726
+ });
727
+ e.dispatchEvent(o), i === "insertText" && e.dispatchEvent(n), e.dispatchEvent(r), e.dispatchEvent(s);
728
+ }
729
+ typeChar(e, t) {
730
+ if (!this.isFormTypingTarget(e)) {
731
+ this.typeContentEditableChar(e, t);
732
+ return;
733
+ }
734
+ const i = e.selectionStart ?? e.value.length, o = e.selectionEnd ?? e.value.length, n = e.value;
735
+ e.value = n.substring(0, i) + t + n.substring(o), e.selectionStart = e.selectionEnd = i + 1;
736
+ const r = e._valueTracker;
737
+ r && r.setValue(n), this.dispatchKeyboardInputEvents(e, t), e.dispatchEvent(new Event("change", { bubbles: !0 }));
738
+ }
739
+ typeContentEditableChar(e, t) {
740
+ const i = window.getSelection();
741
+ if (!i) return;
742
+ if (!i.rangeCount || !e.contains(i.anchorNode)) {
743
+ const r = document.createRange();
744
+ r.selectNodeContents(e), r.collapse(!1), i.removeAllRanges(), i.addRange(r);
745
+ }
746
+ const o = i.getRangeAt(0);
747
+ o.deleteContents();
748
+ const n = document.createTextNode(t);
749
+ o.insertNode(n), o.setStartAfter(n), o.collapse(!0), i.removeAllRanges(), i.addRange(o), this.dispatchKeyboardInputEvents(e, t), e.dispatchEvent(new Event("change", { bubbles: !0 }));
750
+ }
751
+ simulateBackspace(e) {
752
+ if (!this.isFormTypingTarget(e)) {
753
+ this.simulateContentEditableBackspace(e);
754
+ return;
755
+ }
756
+ const t = e.value;
757
+ if (t.length === 0) return;
758
+ const i = e.selectionStart ?? t.length, o = e.selectionEnd ?? t.length;
759
+ let n = t, r = i;
760
+ i !== o ? n = t.substring(0, i) + t.substring(o) : i > 0 && (n = t.substring(0, i - 1) + t.substring(i), r = i - 1), e.value = n, e.selectionStart = e.selectionEnd = r;
761
+ const s = e._valueTracker;
762
+ s && s.setValue(t), this.dispatchKeyboardInputEvents(e, "Backspace", "deleteContentBackward"), e.dispatchEvent(new Event("change", { bubbles: !0 }));
763
+ }
764
+ simulateContentEditableBackspace(e) {
765
+ const t = window.getSelection();
766
+ if (!t) return;
767
+ if (!t.rangeCount || !e.contains(t.anchorNode)) {
768
+ const o = document.createRange();
769
+ o.selectNodeContents(e), o.collapse(!1), t.removeAllRanges(), t.addRange(o);
770
+ }
771
+ const i = t.getRangeAt(0);
772
+ if (!i.collapsed)
773
+ i.deleteContents();
774
+ else if (i.startContainer.nodeType === Node.TEXT_NODE && i.startOffset > 0) {
775
+ const o = i.startContainer;
776
+ o.textContent = (o.textContent ?? "").slice(0, i.startOffset - 1) + (o.textContent ?? "").slice(i.startOffset), i.setStart(o, i.startOffset - 1), i.collapse(!0);
777
+ } else
778
+ return;
779
+ t.removeAllRanges(), t.addRange(i), this.dispatchKeyboardInputEvents(e, "Backspace", "deleteContentBackward"), e.dispatchEvent(new Event("change", { bubbles: !0 }));
780
+ }
781
+ async focusTypingTarget(e, t, i = this.options.selectorTimeoutMs) {
782
+ const o = await this.getTargetElement(e, "simulateType", t, i);
783
+ if (!(o instanceof HTMLInputElement || o instanceof HTMLTextAreaElement || this.isContentEditableTypingTarget(o)))
784
+ throw new Error(
785
+ "[FilmRecorder] Target element for simulateType must be an input, textarea, or contenteditable element."
786
+ );
787
+ return await this.simulateClick(o, t, i), o.focus(), o;
788
+ }
789
+ async simulateType(e, t, i, o) {
790
+ await this.simulateTypeWithRandom(
791
+ e,
792
+ t,
793
+ i,
794
+ o,
795
+ Math.random,
796
+ this.options.selectorTimeoutMs
797
+ );
798
+ }
799
+ async simulateTypeWithRandom(e, t, i, o, n, r = this.options.selectorTimeoutMs) {
800
+ if (typeof window > "u" || typeof document > "u") return;
801
+ this.throwIfAborted(o);
802
+ const s = this.resolveTypingOptions(
803
+ typeof i == "number" ? { cps: i } : i
804
+ ), { cps: a, jitter: c, punctuationPauseMs: h, mistakeRate: d } = s, p = await this.focusTypingTarget(e, o, r), u = 1e3 / a, m = (l, f, v = !1) => {
805
+ if (c <= 0)
806
+ return u;
807
+ let w = 1;
808
+ if (!v && l > 0) {
809
+ const T = t[l - 1], M = f;
810
+ M === T && /[a-zA-Z0-9]/.test(M) ? w = 0.4 : /[a-zA-Z0-9]/.test(T) && /[a-zA-Z0-9]/.test(M) ? w = 0.75 : M === " " ? w = 1.5 : T === " " && /[a-zA-Z0-9]/.test(M) && (w = 1.25);
811
+ }
812
+ const y = /[A-Z]/.test(f), E = l > 0 && /[A-Z]/.test(t[l - 1]);
813
+ y && !E && !v && (w += 0.4);
814
+ const b = n(), A = n(), P = Math.sqrt(-2 * Math.log(b || 1e-4)) * Math.cos(2 * Math.PI * A), C = Math.exp(P * c * 0.8);
815
+ let R = u * w * C;
816
+ if (!v && l > 0 && l % 18 === 0 && n() < 0.6) {
817
+ const T = u * (1.5 + n() * 2) * c;
818
+ R += T;
819
+ }
820
+ const k = Math.max(10, u * 0.25), L = u * 4;
821
+ return Math.min(L, Math.max(k, R));
822
+ };
823
+ for (let l = 0; l < t.length; l++) {
824
+ this.throwIfAborted(o);
825
+ const f = t[l];
826
+ if (d > 0 && n() < d && /[a-z0-9]/i.test(f)) {
827
+ const w = this.getPlausibleTypo(f, n);
828
+ this.typeChar(p, w);
829
+ const y = m(l, w, !0) * 1.3;
830
+ await this.delay(y, o), this.throwIfAborted(o), this.simulateBackspace(p);
831
+ const E = m(l, "Backspace", !0) * 1.1;
832
+ await this.delay(E, o);
833
+ }
834
+ this.typeChar(p, f);
835
+ let v = m(l, f);
836
+ /[.,!?;;:]/.test(f) && (v += h), await this.delay(v, o);
837
+ }
838
+ }
839
+ getPlausibleTypo(e, t) {
840
+ const i = {
841
+ a: "sqwz",
842
+ b: "vghn",
843
+ c: "xdfv",
844
+ d: "ersfcx",
845
+ e: "wsdr",
846
+ f: "rtgdvc",
847
+ g: "tyfhvb",
848
+ h: "yugjbn",
849
+ i: "ujko",
850
+ j: "uikhmn",
851
+ k: "ijolm",
852
+ l: "kop",
853
+ m: "njk",
854
+ n: "bhjm",
855
+ o: "iklp",
856
+ p: "ol",
857
+ q: "wa",
858
+ r: "edft",
859
+ s: "wedxza",
860
+ t: "rfgy",
861
+ u: "yhji",
862
+ v: "cfgb",
863
+ w: "qase",
864
+ x: "zsdc",
865
+ y: "tghu",
866
+ z: "asx"
867
+ }, o = e.toLowerCase(), n = i[o] ?? "abcdefghijklmnopqrstuvwxyz", r = n[Math.floor(t() * n.length)];
868
+ return e === e.toUpperCase() ? r.toUpperCase() : r;
869
+ }
870
+ playPointerTour(e, t) {
871
+ if (typeof window > "u" || typeof document > "u") return Promise.resolve();
872
+ const i = t?.moveDuration ?? 1e3, o = t?.holdDuration ?? 2e3;
873
+ if (this.ensurePointerEl(), !this.pointerEl) return Promise.resolve();
874
+ this.pointerEl.style.display = "block", this.pointerEl.classList.remove("cfr-holding"), this.resetFullScreen();
875
+ const n = document.querySelector(e);
876
+ if (!n)
877
+ return console.warn(`[FilmRecorder] Pointer tour target element not found: ${e}`), Promise.resolve();
878
+ const r = n.getBoundingClientRect(), s = r.left + r.width / 2, a = r.top + r.height / 2, c = this.pointerX, h = this.pointerY, d = performance.now();
879
+ return this.tourRafId && cancelAnimationFrame(this.tourRafId), new Promise((p) => {
880
+ const u = (m) => {
881
+ const l = m - d, f = Math.min(l / i, 1), v = f < 0.5 ? 2 * f * f : 1 - Math.pow(-2 * f + 2, 2) / 2, w = c + (s - c) * v, y = h + (a - h) * v;
882
+ if (this.pointerX = w, this.pointerY = y, this.pointerEl && (this.pointerEl.style.left = `${w}px`, this.pointerEl.style.top = `${y}px`), f < 1)
883
+ this.tourRafId = requestAnimationFrame(u);
884
+ else {
885
+ this.pointerEl && this.pointerEl.classList.add("cfr-holding"), this.mousePosGlobal = { x: s, y: a };
886
+ const E = this.state.resolution.w / this.state.resolution.h;
887
+ this.targetRect = this.centeredRectOnPoint(
888
+ s,
889
+ a,
890
+ window.innerWidth,
891
+ window.innerHeight,
892
+ E
893
+ ), setTimeout(() => {
894
+ this.pointerEl && this.pointerEl.classList.remove("cfr-holding"), this.resetFullScreen(), setTimeout(() => {
895
+ this.pointerEl && !this.pointerEl.classList.contains("cfr-holding") && (this.pointerEl.style.display = "none"), p();
896
+ }, 1e3);
897
+ }, o);
898
+ }
899
+ };
900
+ this.tourRafId = requestAnimationFrame(u);
901
+ });
902
+ }
903
+ // State Management
904
+ getState() {
905
+ return { ...this.state };
906
+ }
907
+ onStateChange(e) {
908
+ return this.listeners.add(e), e(this.getState()), () => this.listeners.delete(e);
909
+ }
910
+ updateState(e) {
911
+ this.state = { ...this.state, ...e }, this.listeners.forEach((t) => t(this.state));
912
+ }
913
+ // Getters/Setters for Option Controls
914
+ setResolution(e) {
915
+ if (this.state.recording) return;
916
+ this.updateState({ resolution: e });
917
+ const t = this.computeGrid(window.innerWidth, window.innerHeight), i = e.w / e.h, { w: o, h: n } = this.getRecordingRegionBox(i, g, t.rows, g), r = Math.floor((g - o) / 2), s = Math.floor((t.rows - n) / 2);
918
+ this.targetRect = { col: r, row: s, w: o, h: n }, this.liveRect = { ...this.targetRect };
919
+ }
920
+ setAutoZoom(e) {
921
+ this.updateState({ autoZoom: e });
922
+ }
923
+ setPauseDuringScroll(e) {
924
+ this.updateState({ pauseDuringScroll: e });
925
+ }
926
+ setShowGrid(e) {
927
+ this.updateState({ showGrid: e });
928
+ }
929
+ setShowOutline(e) {
930
+ this.updateState({ showOutline: e });
931
+ }
932
+ setMousePaused(e) {
933
+ this.updateState({ mousePaused: e });
934
+ }
935
+ setElementSelector(e) {
936
+ if (this.options.elementSelector = e, this.updateState({ elementSelector: e }), e) {
937
+ const t = this.calculateElementRect(e, this.options.elementPadding);
938
+ t && (this.targetRect = t);
939
+ } else
940
+ this.resetFullScreen();
941
+ }
942
+ setElementPadding(e) {
943
+ if (this.options.elementPadding = e, this.updateState({ elementPadding: e }), this.options.elementSelector) {
944
+ const t = this.calculateElementRect(this.options.elementSelector, e);
945
+ t && (this.targetRect = t);
946
+ }
947
+ }
948
+ setFixedRecordingRegion(e) {
949
+ if (this.options.fixedRecordingRegion = e, this.updateState({ fixedRecordingRegion: e }), this.options.elementSelector) {
950
+ const t = this.calculateElementRect(
951
+ this.options.elementSelector,
952
+ this.options.elementPadding
953
+ );
954
+ t && (this.targetRect = t);
955
+ } else
956
+ this.resetFullScreen();
957
+ }
958
+ setMovePointerToSelector(e) {
959
+ this.options.movePointerToSelector = e, this.updateState({ movePointerToSelector: e });
960
+ }
961
+ setPointerClassName(e) {
962
+ this.options.pointerClassName = e, this.updateState({ pointerClassName: e }), this.applyPointerClassName();
963
+ }
964
+ setPointerFillColor(e) {
965
+ this.options.pointerFillColor = this.validatePointerColor(e, "pointerFillColor"), this.updateState({ pointerFillColor: this.options.pointerFillColor }), this.applyPointerColors();
966
+ }
967
+ setPointerBorderColor(e) {
968
+ this.options.pointerBorderColor = this.validatePointerColor(e, "pointerBorderColor"), this.updateState({ pointerBorderColor: this.options.pointerBorderColor }), this.applyPointerColors();
969
+ }
970
+ setPointerRippleColor(e) {
971
+ this.options.pointerRippleColor = this.validatePointerColor(e, "pointerRippleColor"), this.updateState({ pointerRippleColor: this.options.pointerRippleColor }), this.applyPointerColors();
972
+ }
973
+ setDisableRipple(e) {
974
+ this.options.disableRipple = e, this.updateState({ disableRipple: e }), this.applyPointerClassName();
975
+ }
976
+ setIgnoreSelector(e) {
977
+ this.restoreIgnoredElements(), this.options.ignoreSelector = e, this.updateState({ ignoreSelector: e }), this.state.recording && this.hideIgnoredElements();
978
+ }
979
+ setTypingDefaults(e) {
980
+ this.options.typingDefaults = this.resolveTypingOptions(e);
981
+ }
982
+ resolveSelectorTimeoutMs(e) {
983
+ const t = e ?? this.options.selectorTimeoutMs;
984
+ if (!Number.isFinite(t) || t < 0)
985
+ throw new TypeError("[FilmRecorder] selectorTimeoutMs must be a finite number >= 0.");
986
+ return t;
987
+ }
988
+ resolveTypingOptions(...e) {
989
+ const t = { ...W };
990
+ for (const i of e)
991
+ if (i)
992
+ for (const o of Object.keys(i)) {
993
+ const n = i[o];
994
+ n !== void 0 && (t[o] = n);
995
+ }
996
+ return this.validateTypingOptions(t), { ...t };
997
+ }
998
+ validateTypingOptions(e, t = "[FilmRecorder] Invalid typing options") {
999
+ const i = [
1000
+ ["cps", e.cps, "must be a finite number greater than 0"],
1001
+ ["jitter", e.jitter, "must be between 0 and 1"],
1002
+ [
1003
+ "punctuationPauseMs",
1004
+ e.punctuationPauseMs,
1005
+ "must be a finite number greater than or equal to 0"
1006
+ ],
1007
+ ["mistakeRate", e.mistakeRate, "must be between 0 and 1"]
1008
+ ];
1009
+ for (const [o, n, r] of i) {
1010
+ if (n === void 0) continue;
1011
+ if (!(Number.isFinite(n) && (o === "cps" ? n > 0 : o === "punctuationPauseMs" ? n >= 0 : n >= 0 && n <= 1)))
1012
+ throw new TypeError(`${t}: ${o} ${r}; received ${String(n)}.`);
1013
+ }
1014
+ }
1015
+ // Helper for abortable delay
1016
+ delay(e, t) {
1017
+ return new Promise((i, o) => {
1018
+ if (t?.aborted)
1019
+ return o(new DOMException("Aborted", "AbortError"));
1020
+ const n = () => {
1021
+ clearTimeout(r), o(new DOMException("Aborted", "AbortError"));
1022
+ };
1023
+ t && t.addEventListener("abort", n);
1024
+ const r = setTimeout(() => {
1025
+ t && t.removeEventListener("abort", n), i();
1026
+ }, e);
1027
+ });
1028
+ }
1029
+ // Text element finding & actions
1030
+ findElementByText(e) {
1031
+ if (typeof document > "u") return null;
1032
+ try {
1033
+ const i = `//*[text()='${e}' or contains(text(), '${e}')]`, n = document.evaluate(i, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
1034
+ if (n) return n;
1035
+ } catch {
1036
+ }
1037
+ const t = Array.from(document.querySelectorAll("button, a, label, span, div, h1, h2, h3, h4, p"));
1038
+ for (const i of t)
1039
+ if (i.textContent?.trim() === e)
1040
+ return i;
1041
+ for (const i of t)
1042
+ if (i.textContent?.includes(e))
1043
+ return i;
1044
+ return null;
1045
+ }
1046
+ async findElementByTextAsync(e, t, i = 3e3) {
1047
+ const o = performance.now();
1048
+ for (; ; ) {
1049
+ if (t?.aborted)
1050
+ throw new DOMException("Aborted", "AbortError");
1051
+ const n = this.findElementByText(e);
1052
+ if (n)
1053
+ return n;
1054
+ if (performance.now() - o > i)
1055
+ return null;
1056
+ await new Promise((r) => setTimeout(r, 50));
1057
+ }
1058
+ }
1059
+ async clickText(e, t) {
1060
+ const i = await this.findElementByTextAsync(e, t, this.options.selectorTimeoutMs);
1061
+ if (!i)
1062
+ throw this.createSelectorError(e, "clickText");
1063
+ await this.simulateClick(i, t);
1064
+ }
1065
+ async focusText(e, t) {
1066
+ const i = await this.findElementByTextAsync(e, t, this.options.selectorTimeoutMs);
1067
+ if (!i)
1068
+ throw this.createSelectorError(e, "focusText");
1069
+ const o = "data-cfr-temp-focus";
1070
+ i.setAttribute(o, "true"), this.setElementSelector(`[${o}="true"]`), await this.animatePointerTo(i, 800, t);
1071
+ }
1072
+ createSelectorError(e, t) {
1073
+ const i = this.state.currentScene ? ` in scene "${this.state.currentScene}"` : "";
1074
+ return new Error(
1075
+ `[FilmRecorder] Selector safety check failed for "${e}" during action "${t}"${i}.
1076
+ Suggested fix: Ensure the element is mounted in the DOM, or add a stable attribute like data-film-target="${e}" or data-testid.`
1077
+ );
1078
+ }
1079
+ validateFilmScript(e) {
1080
+ const t = Y(e), i = [];
1081
+ t.length === 0 && i.push({
1082
+ line: 1,
1083
+ scene: null,
1084
+ commandIndex: null,
1085
+ message: "No film script scenes were found."
1086
+ });
1087
+ for (const o of t)
1088
+ o.commands.forEach((n, r) => {
1089
+ if (["camera", "type", "click", "pointer", "move"].includes(n.type) && n.type !== "camera" && !n.selector && i.push({
1090
+ line: n.sourceLine ?? 1,
1091
+ scene: o.name,
1092
+ commandIndex: r,
1093
+ message: `${n.type} requires a non-empty selector.`
1094
+ }), n.type === "type" && n.text === void 0 && i.push({
1095
+ line: n.sourceLine ?? 1,
1096
+ scene: o.name,
1097
+ commandIndex: r,
1098
+ message: "type requires text."
1099
+ }), n.type === "type")
1100
+ try {
1101
+ this.validateTypingOptions(
1102
+ {
1103
+ cps: n.cps,
1104
+ jitter: n.jitter,
1105
+ punctuationPauseMs: n.punctuationPauseMs,
1106
+ mistakeRate: n.mistakeRate
1107
+ },
1108
+ `[FilmRecorder] Invalid type command in scene "${o.name}"`
1109
+ );
1110
+ } catch (s) {
1111
+ i.push({
1112
+ line: n.sourceLine ?? 1,
1113
+ scene: o.name,
1114
+ commandIndex: r,
1115
+ message: s instanceof Error ? s.message : String(s)
1116
+ });
1117
+ }
1118
+ });
1119
+ return {
1120
+ valid: i.length === 0,
1121
+ scenes: t,
1122
+ diagnostics: i
1123
+ };
1124
+ }
1125
+ createScriptValidationError(e) {
1126
+ const t = e.map((i) => {
1127
+ const o = i.scene ? ` scene "${i.scene}"` : "", n = i.commandIndex === null ? "" : ` command ${i.commandIndex}`;
1128
+ return `line ${i.line}${o}${n}: ${i.message}`;
1129
+ }).join(`
1130
+ `);
1131
+ return new Error(`[FilmRecorder] Invalid film script:
1132
+ ${t}`);
1133
+ }
1134
+ // Tour State Updates
1135
+ updateTourState(e, t = null) {
1136
+ this.updateState({ tourState: e, currentScene: t });
1137
+ }
1138
+ // Stop Tour & All Semantics
1139
+ async stopTour() {
1140
+ this.tourAbortController && (this.tourAbortController.abort(), this.tourAbortController = null), this.activeExecutionPromise = null, this.finalizationPromise = null, this.pendingActionGates = [], this.activeFilmScriptContext = null, this.activeFilmScriptOptions = null, this.updateState({
1141
+ tourState: "stopped",
1142
+ executionState: "finished",
1143
+ currentScene: null
1144
+ }), await this.options.onTourStop?.();
1145
+ }
1146
+ async stopAll() {
1147
+ await this.stopTour(), this.stopRecording();
1148
+ }
1149
+ finishRecording(e) {
1150
+ const t = typeof e == "function" ? { onReadyToEnd: e } : e ?? {};
1151
+ if (this.finalizationPromise && !t.force)
1152
+ return this.finalizationPromise;
1153
+ const i = this.finalizeRecording(t);
1154
+ return t.force || (this.finalizationPromise = i), i;
1155
+ }
1156
+ async finalizeRecording(e) {
1157
+ const t = this.activeFilmScriptContext ?? {
1158
+ recorder: this,
1159
+ signal: new AbortController().signal,
1160
+ scenes: []
1161
+ };
1162
+ try {
1163
+ e.force ? this.tourAbortController?.abort() : (await this.activeExecutionPromise, this.updateState({ executionState: "waiting" }), await this.awaitActionGates(t)), this.updateState({ executionState: "finalizing" }), await e.onReadyToEnd?.(t), await this.activeFilmScriptOptions?.onReadyToEnd?.(t), await this.options.onReadyToEnd?.(t), this.options.elementSelector && (this.options.elementSelector = null, this.updateState({ elementSelector: null })), this.zoomTopFullFrame(), await this.delay(1e3, e.force ? void 0 : t.signal), this.state.recording && this.stopRecording(), await this.completeActiveFilmScript(t);
1164
+ } catch (i) {
1165
+ throw await this.failActiveFilmScript(t, i), i;
1166
+ }
1167
+ }
1168
+ executeFilmScript(e, t = {}) {
1169
+ this.activeFilmTitle = X(e), this.timelineMetadata && !this.timelineMetadata.title && (this.timelineMetadata.title = this.activeFilmTitle);
1170
+ const i = this.validateFilmScript(e);
1171
+ if (!i.valid)
1172
+ return Promise.reject(this.createScriptValidationError(i.diagnostics));
1173
+ try {
1174
+ this.resolveTypingOptions(this.options.typingDefaults, t.typingDefaults);
1175
+ } catch (n) {
1176
+ return Promise.reject(n);
1177
+ }
1178
+ this.tourAbortController && this.tourAbortController.abort(), this.pendingActionGates = [], this.finalizationPromise = null;
1179
+ const o = this.executeFilmScriptScenes(i.scenes, t);
1180
+ return this.activeExecutionPromise = o, o.finally(() => {
1181
+ this.activeExecutionPromise === o && (this.activeExecutionPromise = null), this.state.executionState === "executing" && this.updateState({ executionState: "waiting" });
1182
+ });
1183
+ }
1184
+ async executeFilmScriptScenes(e, t) {
1185
+ const i = this.resolveTypingOptions(
1186
+ this.options.typingDefaults,
1187
+ t.typingDefaults
1188
+ );
1189
+ this.activeTypingRandom = this.createSeededRandom(
1190
+ t.typingSeed ?? this.options.typingSeed
1191
+ );
1192
+ const o = this.resolveSelectorTimeoutMs(t.selectorTimeoutMs);
1193
+ this.tourAbortController = new AbortController();
1194
+ const n = t.signal ? this.linkAbortSignals(t.signal, this.tourAbortController.signal) : this.tourAbortController.signal, r = {
1195
+ recorder: this,
1196
+ signal: n,
1197
+ scenes: e
1198
+ };
1199
+ this.activeFilmScriptContext = r, this.activeFilmScriptOptions = t, this.updateState({
1200
+ tourState: "running",
1201
+ executionState: "executing",
1202
+ currentScene: null
1203
+ });
1204
+ try {
1205
+ await this.options.onTourStart?.();
1206
+ for (const [s, a] of e.entries()) {
1207
+ this.throwIfAborted(n);
1208
+ const c = performance.now(), h = this.startTimelineScene(a, s);
1209
+ this.updateTourState("running", a.name), await t.onSceneStart?.(a), await this.options.onSceneStart?.(a);
1210
+ for (const [m, l] of a.commands.entries()) {
1211
+ this.throwIfAborted(n);
1212
+ const f = performance.now(), v = this.startTimelineAction(h, l, m), w = (E) => {
1213
+ const b = Promise.resolve(E).then(() => {
1214
+ });
1215
+ b.catch(() => {
1216
+ }), this.pendingActionGates.push({
1217
+ promise: b,
1218
+ scene: a,
1219
+ command: l,
1220
+ commandIndex: m
1221
+ });
1222
+ }, y = () => ({
1223
+ ...r,
1224
+ scene: a,
1225
+ command: l,
1226
+ commandIndex: m,
1227
+ elapsedMs: performance.now() - f,
1228
+ waitUntil: w
1229
+ });
1230
+ switch (await t.onActionStart?.(y()), await this.options.onActionStart?.(y()), l.type) {
1231
+ case "camera":
1232
+ l.selector ? (this.setElementSelector(l.selector), this.setElementPadding(l.padding ?? 1.1)) : (this.setElementSelector(null), this.zoomCenter());
1233
+ break;
1234
+ case "zoom":
1235
+ this.setElementSelector(null), l.to === "top" ? this.zoomTop() : l.to === "bottom" ? this.zoomBottom() : this.zoomCenter();
1236
+ break;
1237
+ case "type":
1238
+ if (l.selector && l.text) {
1239
+ const E = this.resolveTypingOptions(i, {
1240
+ cps: l.cps,
1241
+ jitter: l.jitter,
1242
+ punctuationPauseMs: l.punctuationPauseMs,
1243
+ mistakeRate: l.mistakeRate
1244
+ });
1245
+ await this.simulateTypeWithRandom(
1246
+ l.selector,
1247
+ l.text,
1248
+ E,
1249
+ n,
1250
+ this.activeTypingRandom,
1251
+ o
1252
+ );
1253
+ }
1254
+ break;
1255
+ case "click":
1256
+ l.selector && await this.simulateClick(l.selector, n, o);
1257
+ break;
1258
+ case "pointer":
1259
+ case "move":
1260
+ l.selector && await this.animatePointerTo(
1261
+ l.selector,
1262
+ l.durationMs ?? 800,
1263
+ n,
1264
+ o
1265
+ );
1266
+ break;
1267
+ case "wait":
1268
+ l.durationMs && await this.delay(l.durationMs, n);
1269
+ break;
1270
+ }
1271
+ await t.onActionEnd?.(y()), await this.options.onActionEnd?.(y()), this.endTimelineAction(h, v);
1272
+ }
1273
+ const d = (a.endTime - a.startTime) * 1e3, p = performance.now() - c, u = d - p;
1274
+ u > 0 && await this.delay(u, n), await t.onSceneEnd?.(a), await this.options.onSceneEnd?.(a), this.endTimelineScene(h);
1275
+ }
1276
+ } catch (s) {
1277
+ throw await this.failActiveFilmScript(r, s), s;
1278
+ }
1279
+ }
1280
+ // Deterministic Script Runner
1281
+ async runFilmScript(e, t = {}) {
1282
+ try {
1283
+ await this.executeFilmScript(e, t);
1284
+ const i = this.activeFilmScriptContext;
1285
+ if (!i) return;
1286
+ if (t.waitForCompletion) {
1287
+ const o = Promise.resolve(t.waitForCompletion(i)).then(() => {
1288
+ });
1289
+ o.catch(() => {
1290
+ }), this.pendingActionGates.push({
1291
+ promise: o,
1292
+ scene: i.scenes[i.scenes.length - 1] ?? {
1293
+ name: "completion",
1294
+ startTime: 0,
1295
+ endTime: 0,
1296
+ commands: []
1297
+ },
1298
+ command: { type: "wait" },
1299
+ commandIndex: -1
1300
+ });
1301
+ }
1302
+ await this.finishRecording();
1303
+ } catch (i) {
1304
+ const o = this.activeFilmScriptContext;
1305
+ if (o && await this.failActiveFilmScript(o, i), !this.isAbortError(i))
1306
+ throw i;
1307
+ }
1308
+ }
1309
+ async awaitActionGates(e) {
1310
+ const t = this.activeFilmScriptOptions?.completionTimeoutMs ?? U;
1311
+ for (let i = 0; i < this.pendingActionGates.length; i++) {
1312
+ const o = this.pendingActionGates[i];
1313
+ try {
1314
+ await this.waitForCompletion(o.promise, e.signal, t);
1315
+ } catch (n) {
1316
+ if (this.isAbortError(n)) throw n;
1317
+ const r = n instanceof Error ? n.message : String(n);
1318
+ throw new Error(
1319
+ `[FilmRecorder] Completion gate failed in scene "${o.scene.name}", command ${o.commandIndex} (${o.command.type}): ${r}`
1320
+ );
1321
+ }
1322
+ }
1323
+ }
1324
+ async waitForCompletion(e, t, i) {
1325
+ if (!Number.isFinite(i) || i <= 0)
1326
+ throw new Error("[FilmRecorder] completionTimeoutMs must be a positive finite number.");
1327
+ this.throwIfAborted(t);
1328
+ let o, n;
1329
+ const r = new Promise((a, c) => {
1330
+ o = setTimeout(() => {
1331
+ c(
1332
+ new Error(
1333
+ `[FilmRecorder] waitForCompletion timed out after ${i}ms.`
1334
+ )
1335
+ );
1336
+ }, i);
1337
+ }), s = new Promise((a, c) => {
1338
+ n = () => c(new DOMException("Aborted", "AbortError")), t.addEventListener("abort", n, { once: !0 });
1339
+ });
1340
+ try {
1341
+ await Promise.race([Promise.resolve(e), r, s]);
1342
+ } finally {
1343
+ o && clearTimeout(o), n && t.removeEventListener("abort", n);
1344
+ }
1345
+ }
1346
+ throwIfAborted(e) {
1347
+ if (e?.aborted)
1348
+ throw new DOMException("Aborted", "AbortError");
1349
+ }
1350
+ createSeededRandom(e) {
1351
+ if (e === void 0 || e === "") return Math.random;
1352
+ let t = 2166136261;
1353
+ for (const i of String(e))
1354
+ t ^= i.charCodeAt(0), t = Math.imul(t, 16777619);
1355
+ return () => {
1356
+ t += 1831565813;
1357
+ let i = t;
1358
+ return i = Math.imul(i ^ i >>> 15, i | 1), i ^= i + Math.imul(i ^ i >>> 7, i | 61), ((i ^ i >>> 14) >>> 0) / 4294967296;
1359
+ };
1360
+ }
1361
+ isAbortError(e) {
1362
+ return typeof e == "object" && e !== null && "name" in e && e.name === "AbortError";
1363
+ }
1364
+ async completeActiveFilmScript(e) {
1365
+ if (!this.activeFilmScriptContext && e.scenes.length === 0) {
1366
+ this.updateState({ executionState: "finished" });
1367
+ return;
1368
+ }
1369
+ this.activeFilmScriptContext === e && (this.activeFilmScriptContext = null, this.activeFilmScriptOptions = null, this.tourAbortController = null, this.activeExecutionPromise = null, this.pendingActionGates = [], this.updateState({
1370
+ tourState: "stopped",
1371
+ executionState: "finished",
1372
+ currentScene: null
1373
+ }), await this.options.onTourStop?.());
1374
+ }
1375
+ async failActiveFilmScript(e, t) {
1376
+ if (this.activeFilmScriptContext !== e) return;
1377
+ if (this.tourAbortController?.abort(), this.activeFilmScriptContext = null, this.activeFilmScriptOptions = null, this.tourAbortController = null, this.activeExecutionPromise = null, this.pendingActionGates = [], this.isAbortError(t)) {
1378
+ this.updateState({
1379
+ tourState: "stopped",
1380
+ executionState: "finished",
1381
+ currentScene: null
1382
+ }), await this.options.onTourStop?.();
1383
+ return;
1384
+ }
1385
+ const i = t instanceof Error ? t : new Error(String(t));
1386
+ this.updateState({
1387
+ tourState: "error",
1388
+ executionState: "error",
1389
+ currentScene: null
1390
+ }), await this.options.onTourError?.(i);
1391
+ }
1392
+ // Links two abort signals together to produce a third linked signal
1393
+ linkAbortSignals(e, t) {
1394
+ const i = new AbortController(), o = () => i.abort();
1395
+ return e.aborted || t.aborted ? (i.abort(), i.signal) : (e.addEventListener("abort", o), t.addEventListener("abort", o), i.signal.addEventListener("abort", () => {
1396
+ e.removeEventListener("abort", o), t.removeEventListener("abort", o);
1397
+ }), i.signal);
1398
+ }
1399
+ // Cleanup
1400
+ destroy() {
1401
+ this.destroyed = !0, this.cleanupEventListeners(), cancelAnimationFrame(this.cameraRafId), cancelAnimationFrame(this.renderRafId), this.holdTimer && clearTimeout(this.holdTimer), this.zoomOutTimer && clearTimeout(this.zoomOutTimer), this.state.recording ? this.stopRecording() : (this.stopCaptureStream(), this.restoreIgnoredElements()), this.pointerEl && this.pointerEl.parentNode && (this.pointerEl.parentNode.removeChild(this.pointerEl), this.pointerEl = null), this.overlays.forEach((e) => e.destroy()), this.overlays.clear(), this.tourRafId && cancelAnimationFrame(this.tourRafId), this.videoEl && this.videoEl.parentNode && this.videoEl.parentNode.removeChild(this.videoEl), this.canvasEl && this.canvasEl.parentNode && this.canvasEl.parentNode.removeChild(this.canvasEl), this.clearLastRecording(), this.listeners.clear();
1402
+ }
1403
+ }
1404
+ function X(S) {
1405
+ const e = /\bfilm\s+"([^"]+)"/.exec(S);
1406
+ return e ? e[1] : null;
1407
+ }
1408
+ function Y(S) {
1409
+ const e = [], t = /scene\s+"([^"]+)"\s+@(\d+(?:\.\d+)?)s\s*->\s*(\d+(?:\.\d+)?)s\s*\{/g;
1410
+ let i;
1411
+ for (; (i = t.exec(S)) !== null; ) {
1412
+ const o = i[1], n = parseFloat(i[2]), r = parseFloat(i[3]);
1413
+ let s = 1, a = t.lastIndex;
1414
+ const c = a;
1415
+ for (; s > 0 && a < S.length; ) {
1416
+ const u = S[a];
1417
+ u === "{" ? s++ : u === "}" && s--, a++;
1418
+ }
1419
+ const h = S.substring(c, a - 1), d = S.slice(0, i.index).split(`
1420
+ `).length, p = q(h, d);
1421
+ e.push({
1422
+ name: o,
1423
+ startTime: n,
1424
+ endTime: r,
1425
+ commands: p
1426
+ });
1427
+ }
1428
+ return e;
1429
+ }
1430
+ function q(S, e) {
1431
+ const t = [], i = S.replace(/\/\/.*$/gm, (s) => " ".repeat(s.length)), o = (s, a) => {
1432
+ Object.defineProperty(s, "sourceLine", {
1433
+ value: a,
1434
+ enumerable: !1,
1435
+ configurable: !0
1436
+ }), t.push(s);
1437
+ }, n = /\b(camera|zoom|type|click|pointer|move)\s*\{([\s\S]*?)\}|\bwait\s+(\d+(?:\.\d+)?)(ms|s)/g;
1438
+ let r;
1439
+ for (; (r = n.exec(i)) !== null; ) {
1440
+ const s = e + S.slice(0, r.index).split(`
1441
+ `).length;
1442
+ if (r[3]) {
1443
+ const l = parseFloat(r[3]);
1444
+ o({
1445
+ type: "wait",
1446
+ durationMs: r[4] === "s" ? l * 1e3 : l
1447
+ }, s);
1448
+ continue;
1449
+ }
1450
+ const a = r[1], c = r[2], h = (l) => {
1451
+ const f = new RegExp(`${l}:\\s*(?:"([^"]*)"|'([^']*)')`).exec(
1452
+ c
1453
+ );
1454
+ return f ? f[1] ?? f[2] : void 0;
1455
+ }, d = (l) => {
1456
+ const f = new RegExp(`${l}:\\s*([^\\s}]+)`).exec(c);
1457
+ return f ? Number(f[1]) : void 0;
1458
+ }, p = () => {
1459
+ const l = h("to") ?? h("position");
1460
+ return l === "top" || l === "bottom" || l === "center" ? l : void 0;
1461
+ }, m = /selector:\s*(null|undefined)/.exec(c) ? null : h("selector");
1462
+ o(a === "camera" ? {
1463
+ type: a,
1464
+ selector: m ?? null,
1465
+ padding: d("padding") ?? 1.1
1466
+ } : a === "zoom" ? {
1467
+ type: a,
1468
+ to: p() ?? "center"
1469
+ } : a === "type" ? {
1470
+ type: a,
1471
+ selector: m,
1472
+ text: h("text"),
1473
+ cps: d("cps"),
1474
+ jitter: d("jitter"),
1475
+ punctuationPauseMs: d("punctuationPauseMs"),
1476
+ mistakeRate: d("mistakeRate")
1477
+ } : a === "pointer" || a === "move" ? {
1478
+ type: a,
1479
+ selector: m,
1480
+ durationMs: d("durationMs") ?? 800
1481
+ } : { type: a, selector: m }, s);
1482
+ }
1483
+ return t;
1484
+ }
1485
+ export {
1486
+ g as COLS,
1487
+ U as DEFAULT_COMPLETION_TIMEOUT_MS,
1488
+ O as DEFAULT_RESOLUTIONS,
1489
+ _ as DEFAULT_SELECTOR_TIMEOUT_MS,
1490
+ Q as FilmRecorder,
1491
+ x as HEADER_PX,
1492
+ z as HOLD_MS,
1493
+ B as HOLD_RADIUS_PX,
1494
+ V as HUMAN_TYPING_PRESET,
1495
+ H as LERP_IN,
1496
+ $ as LERP_OUT,
1497
+ F as MIN_BOX_COLS,
1498
+ K as OUTLINE_PX,
1499
+ G as SCROLL_PAUSE_MS,
1500
+ W as SDK_TYPING_DEFAULTS,
1501
+ j as SHORTCUT_KEY,
1502
+ N as ZOOM_OUT_AFTER_MOVE_MS,
1503
+ Y as parseFilmScript
1504
+ };