@usefy/screen-recorder 0.1.3

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/dist/index.js ADDED
@@ -0,0 +1,3473 @@
1
+ "use client";
2
+ "use strict";
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
+
21
+ // src/index.ts
22
+ var index_exports = {};
23
+ __export(index_exports, {
24
+ CheckIcon: () => CheckIcon,
25
+ CloseIcon: () => CloseIcon,
26
+ Countdown: () => Countdown,
27
+ DEFAULT_OPTIONS: () => DEFAULT_OPTIONS,
28
+ DownloadIcon: () => DownloadIcon,
29
+ ERROR_MESSAGES: () => ERROR_MESSAGES,
30
+ ErrorMessage: () => ErrorMessage,
31
+ PauseIcon: () => PauseIcon,
32
+ PlayIcon: () => PlayIcon,
33
+ PreviewModal: () => PreviewModal,
34
+ QUALITY_PRESETS: () => QUALITY_PRESETS,
35
+ RecordingControls: () => RecordingControls,
36
+ RecordingIcon: () => RecordingIcon,
37
+ RefreshIcon: () => RefreshIcon,
38
+ SUPPORTED_MIME_TYPES: () => SUPPORTED_MIME_TYPES,
39
+ ScreenRecorder: () => ScreenRecorder,
40
+ StatusBadge: () => StatusBadge,
41
+ StopIcon: () => StopIcon,
42
+ TIMER_CRITICAL_THRESHOLD: () => TIMER_CRITICAL_THRESHOLD,
43
+ TIMER_WARNING_THRESHOLD: () => TIMER_WARNING_THRESHOLD,
44
+ Timer: () => Timer,
45
+ Trigger: () => Trigger,
46
+ TriggerIcon: () => TriggerIcon,
47
+ VideoPlayer: () => VideoPlayer,
48
+ WarningIcon: () => WarningIcon,
49
+ checkBrowserSupport: () => checkBrowserSupport,
50
+ cn: () => cn,
51
+ downloadBlob: () => downloadBlob,
52
+ formatBytes: () => formatBytes,
53
+ formatTime: () => formatTime,
54
+ generateFilename: () => generateFilename,
55
+ getBestMimeType: () => getBestMimeType,
56
+ useBrowserSupport: () => useBrowserSupport,
57
+ useCountdown: () => useCountdown,
58
+ useDisplayMedia: () => useDisplayMedia,
59
+ useMediaRecorder: () => useMediaRecorder,
60
+ useScreenRecorder: () => useScreenRecorder,
61
+ useTimer: () => useTimer
62
+ });
63
+ module.exports = __toCommonJS(index_exports);
64
+
65
+ // src/ScreenRecorder.tsx
66
+ var import_react9 = require("react");
67
+ var import_react_dom2 = require("react-dom");
68
+
69
+ // src/constants.ts
70
+ var QUALITY_PRESETS = {
71
+ low: {
72
+ videoBitsPerSecond: 1e6,
73
+ // 1 Mbps
74
+ frameRate: 15
75
+ },
76
+ medium: {
77
+ videoBitsPerSecond: 25e5,
78
+ // 2.5 Mbps
79
+ frameRate: 30
80
+ },
81
+ high: {
82
+ videoBitsPerSecond: 5e6,
83
+ // 5 Mbps
84
+ frameRate: 60
85
+ }
86
+ };
87
+ var DEFAULT_OPTIONS = {
88
+ /** Default maximum recording duration (5 minutes) */
89
+ maxDuration: 300,
90
+ /** Default countdown duration (3 seconds) */
91
+ countdown: 3,
92
+ /** Default audio setting */
93
+ audio: false,
94
+ /** Default quality preset */
95
+ quality: "medium",
96
+ /** Default output format */
97
+ format: "webm",
98
+ /** Default MIME type for recording */
99
+ mimeType: "video/webm;codecs=vp9",
100
+ /** Default filename */
101
+ filename: "screen-recording",
102
+ /** Default position */
103
+ position: "bottom-right",
104
+ /** Default z-index */
105
+ zIndex: 9999,
106
+ /** Default theme */
107
+ theme: "system",
108
+ /** Default render mode */
109
+ renderMode: "portal",
110
+ /** Default show preview */
111
+ showPreview: true,
112
+ /** Default show timer */
113
+ showTimer: true,
114
+ /** Default auto download */
115
+ autoDownload: false
116
+ };
117
+ var SUPPORTED_MIME_TYPES = [
118
+ "video/webm;codecs=vp9,opus",
119
+ "video/webm;codecs=vp9",
120
+ "video/webm;codecs=vp8,opus",
121
+ "video/webm;codecs=vp8",
122
+ "video/webm",
123
+ "video/mp4"
124
+ ];
125
+ var ERROR_MESSAGES = {
126
+ PERMISSION_DENIED: "Screen recording permission was denied. Please allow screen sharing when prompted.",
127
+ NOT_SUPPORTED: "Screen recording is not supported in your browser. Please use Chrome, Edge, or Firefox on desktop.",
128
+ MEDIA_RECORDER_ERROR: "An error occurred while recording. Please try again.",
129
+ STREAM_ENDED: "Screen sharing was stopped. The recording has been saved.",
130
+ NO_STREAM: "No screen stream is available. Please try starting a new recording.",
131
+ ENCODING_ERROR: "Failed to encode the video. Please try again.",
132
+ UNKNOWN: "An unknown error occurred. Please try again."
133
+ };
134
+ var TIMER_WARNING_THRESHOLD = 30;
135
+ var TIMER_CRITICAL_THRESHOLD = 10;
136
+ var TIMER_INTERVAL = 1e3;
137
+ var MEDIA_RECORDER_TIMESLICE = 1e3;
138
+ var STORAGE_KEY_PREFIX = "usefy_screen_recorder_";
139
+ var STORAGE_KEYS = {
140
+ theme: `${STORAGE_KEY_PREFIX}theme`,
141
+ quality: `${STORAGE_KEY_PREFIX}quality`,
142
+ countdown: `${STORAGE_KEY_PREFIX}countdown`
143
+ };
144
+ var ARIA_LABELS = {
145
+ triggerButton: "Start screen recording",
146
+ stopButton: "Stop recording",
147
+ pauseButton: "Pause recording",
148
+ resumeButton: "Resume recording",
149
+ downloadButton: "Download recording",
150
+ reRecordButton: "Discard and record again",
151
+ closePreview: "Close preview",
152
+ timer: "Recording duration",
153
+ countdown: "Recording starts in",
154
+ previewModal: "Recording preview",
155
+ videoPlayer: "Recorded video preview"
156
+ };
157
+ var BASE_CLASS = "usefy-screen-recorder";
158
+ var CSS_CLASSES = {
159
+ root: BASE_CLASS,
160
+ trigger: `${BASE_CLASS}__trigger`,
161
+ controls: `${BASE_CLASS}__controls`,
162
+ timer: `${BASE_CLASS}__timer`,
163
+ countdown: `${BASE_CLASS}__countdown`,
164
+ preview: `${BASE_CLASS}__preview`,
165
+ status: `${BASE_CLASS}__status`,
166
+ error: `${BASE_CLASS}__error`
167
+ };
168
+ var POSITION_CLASSES = {
169
+ "top-left": "top-4 left-4",
170
+ "top-right": "top-4 right-4",
171
+ "bottom-left": "bottom-4 left-4",
172
+ "bottom-right": "bottom-4 right-4"
173
+ };
174
+
175
+ // src/useScreenRecorder.ts
176
+ var import_react6 = require("react");
177
+
178
+ // src/hooks/useBrowserSupport.ts
179
+ var import_react = require("react");
180
+ function checkDisplayMediaSupport() {
181
+ if (typeof window === "undefined") return false;
182
+ if (typeof navigator === "undefined") return false;
183
+ if (!navigator.mediaDevices) return false;
184
+ return typeof navigator.mediaDevices.getDisplayMedia === "function";
185
+ }
186
+ function checkMediaRecorderSupport() {
187
+ if (typeof window === "undefined") return false;
188
+ return typeof MediaRecorder !== "undefined";
189
+ }
190
+ function getSupportedMimeTypes() {
191
+ if (!checkMediaRecorderSupport()) return [];
192
+ return SUPPORTED_MIME_TYPES.filter((mimeType) => {
193
+ try {
194
+ return MediaRecorder.isTypeSupported(mimeType);
195
+ } catch {
196
+ return false;
197
+ }
198
+ });
199
+ }
200
+ function getBestMimeType() {
201
+ const supported = getSupportedMimeTypes();
202
+ return supported.length > 0 ? supported[0] : null;
203
+ }
204
+ function useBrowserSupport() {
205
+ const [support, setSupport] = (0, import_react.useState)(() => ({
206
+ isSupported: false,
207
+ hasDisplayMedia: false,
208
+ hasMediaRecorder: false,
209
+ supportedMimeTypes: []
210
+ }));
211
+ (0, import_react.useEffect)(() => {
212
+ const hasDisplayMedia = checkDisplayMediaSupport();
213
+ const hasMediaRecorder = checkMediaRecorderSupport();
214
+ const supportedMimeTypes = getSupportedMimeTypes();
215
+ setSupport({
216
+ isSupported: hasDisplayMedia && hasMediaRecorder && supportedMimeTypes.length > 0,
217
+ hasDisplayMedia,
218
+ hasMediaRecorder,
219
+ supportedMimeTypes
220
+ });
221
+ }, []);
222
+ return support;
223
+ }
224
+ function checkBrowserSupport() {
225
+ const hasDisplayMedia = checkDisplayMediaSupport();
226
+ const hasMediaRecorder = checkMediaRecorderSupport();
227
+ const supportedMimeTypes = getSupportedMimeTypes();
228
+ return {
229
+ isSupported: hasDisplayMedia && hasMediaRecorder && supportedMimeTypes.length > 0,
230
+ hasDisplayMedia,
231
+ hasMediaRecorder,
232
+ supportedMimeTypes
233
+ };
234
+ }
235
+
236
+ // src/hooks/useDisplayMedia.ts
237
+ var import_react2 = require("react");
238
+ function getAudioConstraints(audio) {
239
+ if (audio === false || audio === void 0) {
240
+ return false;
241
+ }
242
+ if (audio === true) {
243
+ return true;
244
+ }
245
+ const config = audio;
246
+ if (config.system || config.microphone) {
247
+ return true;
248
+ }
249
+ return false;
250
+ }
251
+ function createError(error) {
252
+ if (error instanceof DOMException) {
253
+ if (error.name === "NotAllowedError" || error.name === "PermissionDeniedError") {
254
+ return {
255
+ code: "PERMISSION_DENIED",
256
+ message: "Screen recording permission was denied. Please allow screen sharing when prompted.",
257
+ originalError: error
258
+ };
259
+ }
260
+ if (error.name === "NotSupportedError") {
261
+ return {
262
+ code: "NOT_SUPPORTED",
263
+ message: "Screen recording is not supported in your browser.",
264
+ originalError: error
265
+ };
266
+ }
267
+ if (error.name === "AbortError") {
268
+ return {
269
+ code: "PERMISSION_DENIED",
270
+ message: "Screen selection was cancelled.",
271
+ originalError: error
272
+ };
273
+ }
274
+ }
275
+ const originalError = error instanceof Error ? error : new Error(String(error));
276
+ return {
277
+ code: "UNKNOWN",
278
+ message: originalError.message || "An unknown error occurred.",
279
+ originalError
280
+ };
281
+ }
282
+ function useDisplayMedia(options = {}) {
283
+ const { audio, onError, onStreamEnded } = options;
284
+ const [stream, setStream] = (0, import_react2.useState)(null);
285
+ const [error, setError] = (0, import_react2.useState)(null);
286
+ const [hasAudio, setHasAudio] = (0, import_react2.useState)(false);
287
+ const streamRef = (0, import_react2.useRef)(null);
288
+ const handleTrackEnded = (0, import_react2.useCallback)(() => {
289
+ onStreamEnded?.();
290
+ setStream(null);
291
+ streamRef.current = null;
292
+ }, [onStreamEnded]);
293
+ const stopStream = (0, import_react2.useCallback)(() => {
294
+ if (streamRef.current) {
295
+ streamRef.current.getTracks().forEach((track) => {
296
+ track.removeEventListener("ended", handleTrackEnded);
297
+ track.stop();
298
+ });
299
+ streamRef.current = null;
300
+ }
301
+ setStream(null);
302
+ setHasAudio(false);
303
+ }, [handleTrackEnded]);
304
+ const requestStream = (0, import_react2.useCallback)(async () => {
305
+ if (typeof navigator === "undefined" || !navigator.mediaDevices || !navigator.mediaDevices.getDisplayMedia) {
306
+ const error2 = {
307
+ code: "NOT_SUPPORTED",
308
+ message: "Screen recording is not supported in your browser."
309
+ };
310
+ setError(error2);
311
+ onError?.(error2);
312
+ return null;
313
+ }
314
+ stopStream();
315
+ setError(null);
316
+ try {
317
+ const displayMediaOptions = {
318
+ video: true,
319
+ audio: getAudioConstraints(audio)
320
+ };
321
+ const mediaStream = await navigator.mediaDevices.getDisplayMedia(
322
+ displayMediaOptions
323
+ );
324
+ const videoTrack = mediaStream.getVideoTracks()[0];
325
+ if (videoTrack) {
326
+ videoTrack.addEventListener("ended", handleTrackEnded);
327
+ }
328
+ const audioTracks = mediaStream.getAudioTracks();
329
+ setHasAudio(audioTracks.length > 0);
330
+ streamRef.current = mediaStream;
331
+ setStream(mediaStream);
332
+ return mediaStream;
333
+ } catch (err) {
334
+ const screenRecorderError = createError(err);
335
+ setError(screenRecorderError);
336
+ onError?.(screenRecorderError);
337
+ return null;
338
+ }
339
+ }, [audio, stopStream, handleTrackEnded, onError]);
340
+ (0, import_react2.useEffect)(() => {
341
+ return () => {
342
+ if (streamRef.current) {
343
+ streamRef.current.getTracks().forEach((track) => track.stop());
344
+ streamRef.current = null;
345
+ }
346
+ };
347
+ }, []);
348
+ return {
349
+ stream,
350
+ isStreaming: stream !== null,
351
+ requestStream,
352
+ stopStream,
353
+ error,
354
+ hasAudio
355
+ };
356
+ }
357
+
358
+ // src/hooks/useMediaRecorder.ts
359
+ var import_react3 = require("react");
360
+ function getQualityConfig(quality) {
361
+ if (!quality) {
362
+ return QUALITY_PRESETS[DEFAULT_OPTIONS.quality];
363
+ }
364
+ if (typeof quality === "string") {
365
+ return QUALITY_PRESETS[quality] || QUALITY_PRESETS.medium;
366
+ }
367
+ return quality;
368
+ }
369
+ function useMediaRecorder(stream, options = {}) {
370
+ const {
371
+ quality,
372
+ mimeType: preferredMimeType,
373
+ timeslice = MEDIA_RECORDER_TIMESLICE,
374
+ onDataAvailable,
375
+ onStart,
376
+ onStop,
377
+ onError
378
+ } = options;
379
+ const [recorder, setRecorder] = (0, import_react3.useState)(null);
380
+ const [state, setState] = (0, import_react3.useState)("inactive");
381
+ const [blob, setBlob] = (0, import_react3.useState)(null);
382
+ const [error, setError] = (0, import_react3.useState)(null);
383
+ const chunksRef = (0, import_react3.useRef)([]);
384
+ const actualMimeType = preferredMimeType || getBestMimeType() || DEFAULT_OPTIONS.mimeType;
385
+ (0, import_react3.useEffect)(() => {
386
+ if (!stream) {
387
+ setRecorder(null);
388
+ setState("inactive");
389
+ return;
390
+ }
391
+ if (typeof MediaRecorder === "undefined") {
392
+ const err = {
393
+ code: "NOT_SUPPORTED",
394
+ message: "MediaRecorder is not supported in your browser."
395
+ };
396
+ setError(err);
397
+ onError?.(err);
398
+ return;
399
+ }
400
+ let mimeToUse = actualMimeType;
401
+ if (!MediaRecorder.isTypeSupported(mimeToUse)) {
402
+ const bestMime = getBestMimeType();
403
+ if (bestMime) {
404
+ mimeToUse = bestMime;
405
+ } else {
406
+ const err = {
407
+ code: "ENCODING_ERROR",
408
+ message: `The MIME type ${actualMimeType} is not supported.`
409
+ };
410
+ setError(err);
411
+ onError?.(err);
412
+ return;
413
+ }
414
+ }
415
+ const qualityConfig = getQualityConfig(quality);
416
+ try {
417
+ const mediaRecorder = new MediaRecorder(stream, {
418
+ mimeType: mimeToUse,
419
+ videoBitsPerSecond: qualityConfig.videoBitsPerSecond
420
+ });
421
+ mediaRecorder.ondataavailable = (event) => {
422
+ if (event.data && event.data.size > 0) {
423
+ chunksRef.current.push(event.data);
424
+ onDataAvailable?.(event.data);
425
+ }
426
+ };
427
+ mediaRecorder.onstart = () => {
428
+ setState("recording");
429
+ chunksRef.current = [];
430
+ setBlob(null);
431
+ setError(null);
432
+ onStart?.();
433
+ };
434
+ mediaRecorder.onstop = () => {
435
+ setState("inactive");
436
+ const recordedBlob = new Blob(chunksRef.current, { type: mimeToUse });
437
+ setBlob(recordedBlob);
438
+ onStop?.(recordedBlob);
439
+ };
440
+ mediaRecorder.onpause = () => {
441
+ setState("paused");
442
+ };
443
+ mediaRecorder.onresume = () => {
444
+ setState("recording");
445
+ };
446
+ mediaRecorder.onerror = (event) => {
447
+ const err = {
448
+ code: "MEDIA_RECORDER_ERROR",
449
+ message: "An error occurred during recording.",
450
+ originalError: event instanceof ErrorEvent ? event.error : new Error("MediaRecorder error")
451
+ };
452
+ setError(err);
453
+ setState("inactive");
454
+ onError?.(err);
455
+ };
456
+ setRecorder(mediaRecorder);
457
+ setError(null);
458
+ } catch (err) {
459
+ const screenRecorderError = {
460
+ code: "MEDIA_RECORDER_ERROR",
461
+ message: err instanceof Error ? err.message : "Failed to create MediaRecorder.",
462
+ originalError: err instanceof Error ? err : void 0
463
+ };
464
+ setError(screenRecorderError);
465
+ onError?.(screenRecorderError);
466
+ }
467
+ return () => {
468
+ if (recorder && recorder.state !== "inactive") {
469
+ recorder.stop();
470
+ }
471
+ };
472
+ }, [stream, actualMimeType, quality]);
473
+ const start = (0, import_react3.useCallback)(() => {
474
+ if (!recorder) {
475
+ const err = {
476
+ code: "NO_STREAM",
477
+ message: "No stream available. Please start screen capture first."
478
+ };
479
+ setError(err);
480
+ onError?.(err);
481
+ return;
482
+ }
483
+ if (recorder.state !== "inactive") {
484
+ return;
485
+ }
486
+ chunksRef.current = [];
487
+ recorder.start(timeslice);
488
+ }, [recorder, timeslice, onError]);
489
+ const stop = (0, import_react3.useCallback)(() => {
490
+ if (!recorder || recorder.state === "inactive") {
491
+ return;
492
+ }
493
+ recorder.stop();
494
+ }, [recorder]);
495
+ const pause = (0, import_react3.useCallback)(() => {
496
+ if (!recorder || recorder.state !== "recording") {
497
+ return;
498
+ }
499
+ recorder.pause();
500
+ }, [recorder]);
501
+ const resume = (0, import_react3.useCallback)(() => {
502
+ if (!recorder || recorder.state !== "paused") {
503
+ return;
504
+ }
505
+ recorder.resume();
506
+ }, [recorder]);
507
+ return {
508
+ recorder,
509
+ state,
510
+ start,
511
+ stop,
512
+ pause,
513
+ resume,
514
+ blob,
515
+ mimeType: actualMimeType,
516
+ error
517
+ };
518
+ }
519
+
520
+ // src/hooks/useTimer.ts
521
+ var import_react4 = require("react");
522
+ function formatTime(seconds) {
523
+ const mins = Math.floor(seconds / 60);
524
+ const secs = Math.floor(seconds % 60);
525
+ return `${mins.toString().padStart(2, "0")}:${secs.toString().padStart(2, "0")}`;
526
+ }
527
+ function useTimer(options = {}) {
528
+ const { maxDuration, onTick, onComplete } = options;
529
+ const [elapsed, setElapsed] = (0, import_react4.useState)(0);
530
+ const [isRunning, setIsRunning] = (0, import_react4.useState)(false);
531
+ const [isPaused, setIsPaused] = (0, import_react4.useState)(false);
532
+ const startTimeRef = (0, import_react4.useRef)(null);
533
+ const pausedAtRef = (0, import_react4.useRef)(0);
534
+ const intervalRef = (0, import_react4.useRef)(null);
535
+ const remaining = maxDuration != null && isFinite(maxDuration) ? Math.max(0, maxDuration - elapsed) : null;
536
+ const elapsedFormatted = formatTime(elapsed);
537
+ const clearTimerInterval = (0, import_react4.useCallback)(() => {
538
+ if (intervalRef.current) {
539
+ clearInterval(intervalRef.current);
540
+ intervalRef.current = null;
541
+ }
542
+ }, []);
543
+ const start = (0, import_react4.useCallback)(() => {
544
+ if (isRunning && !isPaused) return;
545
+ const now = Date.now();
546
+ if (isPaused) {
547
+ const pauseDuration = now - pausedAtRef.current;
548
+ if (startTimeRef.current) {
549
+ startTimeRef.current += pauseDuration;
550
+ }
551
+ setIsPaused(false);
552
+ } else {
553
+ startTimeRef.current = now;
554
+ setElapsed(0);
555
+ }
556
+ setIsRunning(true);
557
+ clearTimerInterval();
558
+ intervalRef.current = setInterval(() => {
559
+ if (startTimeRef.current === null) return;
560
+ const currentElapsed = Math.floor((Date.now() - startTimeRef.current) / 1e3);
561
+ setElapsed(currentElapsed);
562
+ const currentRemaining = maxDuration != null && isFinite(maxDuration) ? maxDuration - currentElapsed : null;
563
+ onTick?.(currentElapsed, currentRemaining);
564
+ if (maxDuration != null && isFinite(maxDuration) && currentElapsed >= maxDuration) {
565
+ clearTimerInterval();
566
+ setIsRunning(false);
567
+ onComplete?.();
568
+ }
569
+ }, TIMER_INTERVAL);
570
+ }, [isRunning, isPaused, maxDuration, clearTimerInterval, onTick, onComplete]);
571
+ const stop = (0, import_react4.useCallback)(() => {
572
+ clearTimerInterval();
573
+ setIsRunning(false);
574
+ setIsPaused(false);
575
+ }, [clearTimerInterval]);
576
+ const pause = (0, import_react4.useCallback)(() => {
577
+ if (!isRunning || isPaused) return;
578
+ clearTimerInterval();
579
+ pausedAtRef.current = Date.now();
580
+ setIsPaused(true);
581
+ }, [isRunning, isPaused, clearTimerInterval]);
582
+ const resume = (0, import_react4.useCallback)(() => {
583
+ if (!isPaused) return;
584
+ start();
585
+ }, [isPaused, start]);
586
+ const reset = (0, import_react4.useCallback)(() => {
587
+ clearTimerInterval();
588
+ setIsRunning(false);
589
+ setIsPaused(false);
590
+ setElapsed(0);
591
+ startTimeRef.current = null;
592
+ pausedAtRef.current = 0;
593
+ }, [clearTimerInterval]);
594
+ (0, import_react4.useEffect)(() => {
595
+ return () => {
596
+ clearTimerInterval();
597
+ };
598
+ }, [clearTimerInterval]);
599
+ return {
600
+ elapsed,
601
+ elapsedFormatted,
602
+ remaining,
603
+ isRunning,
604
+ start,
605
+ stop,
606
+ pause,
607
+ resume,
608
+ reset
609
+ };
610
+ }
611
+
612
+ // src/hooks/useCountdown.ts
613
+ var import_react5 = require("react");
614
+ function useCountdown(options = {}) {
615
+ const { initialValue = 3, onComplete, onTick } = options;
616
+ const [value, setValue] = (0, import_react5.useState)(null);
617
+ const [isActive, setIsActive] = (0, import_react5.useState)(false);
618
+ const onCompleteRef = (0, import_react5.useRef)(onComplete);
619
+ const runtimeOnCompleteRef = (0, import_react5.useRef)(void 0);
620
+ const intervalRef = (0, import_react5.useRef)(null);
621
+ (0, import_react5.useEffect)(() => {
622
+ onCompleteRef.current = onComplete;
623
+ }, [onComplete]);
624
+ const clearCountdownInterval = (0, import_react5.useCallback)(() => {
625
+ if (intervalRef.current) {
626
+ clearInterval(intervalRef.current);
627
+ intervalRef.current = null;
628
+ }
629
+ }, []);
630
+ const start = (0, import_react5.useCallback)(
631
+ (runtimeOnComplete) => {
632
+ runtimeOnCompleteRef.current = runtimeOnComplete;
633
+ clearCountdownInterval();
634
+ setValue(initialValue);
635
+ setIsActive(true);
636
+ onTick?.(initialValue);
637
+ let currentValue = initialValue;
638
+ intervalRef.current = setInterval(() => {
639
+ currentValue -= 1;
640
+ if (currentValue <= 0) {
641
+ clearCountdownInterval();
642
+ setValue(null);
643
+ setIsActive(false);
644
+ runtimeOnCompleteRef.current?.();
645
+ onCompleteRef.current?.();
646
+ } else {
647
+ setValue(currentValue);
648
+ onTick?.(currentValue);
649
+ }
650
+ }, 1e3);
651
+ },
652
+ [initialValue, clearCountdownInterval, onTick]
653
+ );
654
+ const cancel = (0, import_react5.useCallback)(() => {
655
+ clearCountdownInterval();
656
+ setValue(null);
657
+ setIsActive(false);
658
+ runtimeOnCompleteRef.current = void 0;
659
+ }, [clearCountdownInterval]);
660
+ (0, import_react5.useEffect)(() => {
661
+ return () => {
662
+ clearCountdownInterval();
663
+ };
664
+ }, [clearCountdownInterval]);
665
+ return {
666
+ value,
667
+ isActive,
668
+ start,
669
+ cancel
670
+ };
671
+ }
672
+
673
+ // src/useScreenRecorder.ts
674
+ function useScreenRecorder(options = {}) {
675
+ const {
676
+ maxDuration = DEFAULT_OPTIONS.maxDuration,
677
+ countdown: countdownSeconds = DEFAULT_OPTIONS.countdown,
678
+ audio = DEFAULT_OPTIONS.audio,
679
+ quality = DEFAULT_OPTIONS.quality,
680
+ mimeType = DEFAULT_OPTIONS.mimeType,
681
+ onError
682
+ } = options;
683
+ const [state, setState] = (0, import_react6.useState)("idle");
684
+ const [result, setResult] = (0, import_react6.useState)(null);
685
+ const [error, setError] = (0, import_react6.useState)(null);
686
+ const blobUrlRef = (0, import_react6.useRef)(null);
687
+ const recordingStartTimeRef = (0, import_react6.useRef)(null);
688
+ const pendingImmediateStartRef = (0, import_react6.useRef)(false);
689
+ const browserSupport = useBrowserSupport();
690
+ const displayMedia = useDisplayMedia({
691
+ audio,
692
+ onError: (err) => {
693
+ setState("error");
694
+ setError(err);
695
+ onError?.(err);
696
+ },
697
+ onStreamEnded: () => {
698
+ if (state === "recording" || state === "paused") {
699
+ handleStop();
700
+ }
701
+ }
702
+ });
703
+ const mediaRecorder = useMediaRecorder(displayMedia.stream, {
704
+ quality,
705
+ mimeType,
706
+ onStart: () => {
707
+ recordingStartTimeRef.current = Date.now();
708
+ },
709
+ onStop: (blob) => {
710
+ const duration = recordingStartTimeRef.current ? Math.floor((Date.now() - recordingStartTimeRef.current) / 1e3) : timer.elapsed;
711
+ if (blobUrlRef.current) {
712
+ URL.revokeObjectURL(blobUrlRef.current);
713
+ }
714
+ const url = URL.createObjectURL(blob);
715
+ blobUrlRef.current = url;
716
+ const recordingResult = {
717
+ blob,
718
+ url,
719
+ duration,
720
+ size: blob.size,
721
+ mimeType: blob.type || mimeType,
722
+ timestamp: /* @__PURE__ */ new Date(),
723
+ hasAudio: displayMedia.hasAudio
724
+ };
725
+ setResult(recordingResult);
726
+ setState("stopped");
727
+ timer.stop();
728
+ },
729
+ onError: (err) => {
730
+ setState("error");
731
+ setError(err);
732
+ onError?.(err);
733
+ }
734
+ });
735
+ const timer = useTimer({
736
+ maxDuration,
737
+ onTick: (elapsed, remaining2) => {
738
+ if (remaining2 !== null && remaining2 <= 0) {
739
+ handleStop();
740
+ }
741
+ }
742
+ });
743
+ const countdown = useCountdown({
744
+ initialValue: typeof countdownSeconds === "number" ? countdownSeconds : 3,
745
+ onComplete: () => {
746
+ mediaRecorder.start();
747
+ timer.start();
748
+ setState("recording");
749
+ }
750
+ });
751
+ const handleStop = (0, import_react6.useCallback)(() => {
752
+ mediaRecorder.stop();
753
+ displayMedia.stopStream();
754
+ timer.stop();
755
+ }, [mediaRecorder, displayMedia, timer]);
756
+ (0, import_react6.useEffect)(() => {
757
+ if (pendingImmediateStartRef.current && mediaRecorder.recorder && displayMedia.stream) {
758
+ pendingImmediateStartRef.current = false;
759
+ mediaRecorder.start();
760
+ timer.start();
761
+ setState("recording");
762
+ }
763
+ }, [mediaRecorder.recorder, mediaRecorder, displayMedia.stream, timer]);
764
+ const start = (0, import_react6.useCallback)(async () => {
765
+ setError(null);
766
+ setResult(null);
767
+ if (blobUrlRef.current) {
768
+ URL.revokeObjectURL(blobUrlRef.current);
769
+ blobUrlRef.current = null;
770
+ }
771
+ setState("requesting");
772
+ const stream = await displayMedia.requestStream();
773
+ if (!stream) {
774
+ setState("error");
775
+ return;
776
+ }
777
+ if (countdownSeconds !== false && countdownSeconds > 0) {
778
+ setState("countdown");
779
+ countdown.start();
780
+ } else {
781
+ pendingImmediateStartRef.current = true;
782
+ setState("countdown");
783
+ }
784
+ }, [displayMedia, countdownSeconds, countdown]);
785
+ const stop = (0, import_react6.useCallback)(() => {
786
+ pendingImmediateStartRef.current = false;
787
+ if (state === "countdown") {
788
+ countdown.cancel();
789
+ displayMedia.stopStream();
790
+ setState("idle");
791
+ return;
792
+ }
793
+ if (state === "recording" || state === "paused") {
794
+ handleStop();
795
+ }
796
+ }, [state, countdown, displayMedia, handleStop]);
797
+ const pause = (0, import_react6.useCallback)(() => {
798
+ if (state !== "recording") return;
799
+ mediaRecorder.pause();
800
+ timer.pause();
801
+ setState("paused");
802
+ }, [state, mediaRecorder, timer]);
803
+ const resume = (0, import_react6.useCallback)(() => {
804
+ if (state !== "paused") return;
805
+ mediaRecorder.resume();
806
+ timer.resume();
807
+ setState("recording");
808
+ }, [state, mediaRecorder, timer]);
809
+ const togglePause = (0, import_react6.useCallback)(() => {
810
+ if (state === "recording") {
811
+ pause();
812
+ } else if (state === "paused") {
813
+ resume();
814
+ }
815
+ }, [state, pause, resume]);
816
+ const download = (0, import_react6.useCallback)(
817
+ (filename) => {
818
+ if (!result) return;
819
+ const downloadFilename = filename || `screen-recording-${Date.now()}.webm`;
820
+ const link = document.createElement("a");
821
+ link.href = result.url;
822
+ link.download = downloadFilename.endsWith(".webm") ? downloadFilename : `${downloadFilename}.webm`;
823
+ document.body.appendChild(link);
824
+ link.click();
825
+ document.body.removeChild(link);
826
+ },
827
+ [result]
828
+ );
829
+ const reset = (0, import_react6.useCallback)(() => {
830
+ pendingImmediateStartRef.current = false;
831
+ if (state === "recording" || state === "paused") {
832
+ mediaRecorder.stop();
833
+ }
834
+ displayMedia.stopStream();
835
+ timer.reset();
836
+ countdown.cancel();
837
+ if (blobUrlRef.current) {
838
+ URL.revokeObjectURL(blobUrlRef.current);
839
+ blobUrlRef.current = null;
840
+ }
841
+ setState("idle");
842
+ setResult(null);
843
+ setError(null);
844
+ }, [state, mediaRecorder, displayMedia, timer, countdown]);
845
+ const getPreviewUrl = (0, import_react6.useCallback)(() => {
846
+ return blobUrlRef.current;
847
+ }, []);
848
+ const revokePreviewUrl = (0, import_react6.useCallback)(() => {
849
+ if (blobUrlRef.current) {
850
+ URL.revokeObjectURL(blobUrlRef.current);
851
+ blobUrlRef.current = null;
852
+ }
853
+ }, []);
854
+ (0, import_react6.useEffect)(() => {
855
+ return () => {
856
+ if (blobUrlRef.current) {
857
+ URL.revokeObjectURL(blobUrlRef.current);
858
+ }
859
+ };
860
+ }, []);
861
+ const isRecording = state === "recording";
862
+ const isPaused = state === "paused";
863
+ const isCountingDown = state === "countdown" && !pendingImmediateStartRef.current;
864
+ const remaining = maxDuration != null && isFinite(maxDuration) ? Math.max(0, maxDuration - timer.elapsed) : null;
865
+ return {
866
+ // State
867
+ state,
868
+ isRecording,
869
+ isPaused,
870
+ isCountingDown,
871
+ countdownValue: pendingImmediateStartRef.current ? null : countdown.value,
872
+ elapsed: timer.elapsed,
873
+ remaining,
874
+ elapsedFormatted: timer.elapsedFormatted,
875
+ result,
876
+ error: error || displayMedia.error || mediaRecorder.error,
877
+ isSupported: browserSupport.isSupported,
878
+ // Actions
879
+ start,
880
+ stop,
881
+ pause,
882
+ resume,
883
+ togglePause,
884
+ download,
885
+ reset,
886
+ getPreviewUrl,
887
+ revokePreviewUrl
888
+ };
889
+ }
890
+
891
+ // src/utils/cn.ts
892
+ var import_clsx = require("clsx");
893
+ var import_tailwind_merge = require("tailwind-merge");
894
+ function cn(...inputs) {
895
+ return (0, import_tailwind_merge.twMerge)((0, import_clsx.clsx)(inputs));
896
+ }
897
+
898
+ // src/components/Trigger/TriggerIcon.tsx
899
+ var import_jsx_runtime = require("react/jsx-runtime");
900
+ var TriggerIcon = ({ className }) => {
901
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
902
+ "svg",
903
+ {
904
+ xmlns: "http://www.w3.org/2000/svg",
905
+ viewBox: "0 0 24 24",
906
+ fill: "none",
907
+ stroke: "currentColor",
908
+ strokeWidth: 2,
909
+ strokeLinecap: "round",
910
+ strokeLinejoin: "round",
911
+ className,
912
+ "aria-hidden": "true",
913
+ children: [
914
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "2", y: "6", width: "14", height: "12", rx: "2", ry: "2" }),
915
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("polygon", { points: "22,8 16,12 22,16", fill: "currentColor", stroke: "none" }),
916
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "9", cy: "12", r: "2", fill: "currentColor", stroke: "none" })
917
+ ]
918
+ }
919
+ );
920
+ };
921
+ var RecordingIcon = ({ className }) => {
922
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
923
+ "svg",
924
+ {
925
+ xmlns: "http://www.w3.org/2000/svg",
926
+ viewBox: "0 0 24 24",
927
+ fill: "currentColor",
928
+ className,
929
+ "aria-hidden": "true",
930
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "12", cy: "12", r: "8" })
931
+ }
932
+ );
933
+ };
934
+ var StopIcon = ({ className }) => {
935
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
936
+ "svg",
937
+ {
938
+ xmlns: "http://www.w3.org/2000/svg",
939
+ viewBox: "0 0 24 24",
940
+ fill: "currentColor",
941
+ className,
942
+ "aria-hidden": "true",
943
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "6", y: "6", width: "12", height: "12", rx: "2" })
944
+ }
945
+ );
946
+ };
947
+ var PauseIcon = ({ className }) => {
948
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
949
+ "svg",
950
+ {
951
+ xmlns: "http://www.w3.org/2000/svg",
952
+ viewBox: "0 0 24 24",
953
+ fill: "currentColor",
954
+ className,
955
+ "aria-hidden": "true",
956
+ children: [
957
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "6", y: "5", width: "4", height: "14", rx: "1" }),
958
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "14", y: "5", width: "4", height: "14", rx: "1" })
959
+ ]
960
+ }
961
+ );
962
+ };
963
+ var PlayIcon = ({ className }) => {
964
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
965
+ "svg",
966
+ {
967
+ xmlns: "http://www.w3.org/2000/svg",
968
+ viewBox: "0 0 24 24",
969
+ fill: "currentColor",
970
+ className,
971
+ "aria-hidden": "true",
972
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("polygon", { points: "8,5 19,12 8,19" })
973
+ }
974
+ );
975
+ };
976
+ var DownloadIcon = ({ className }) => {
977
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
978
+ "svg",
979
+ {
980
+ xmlns: "http://www.w3.org/2000/svg",
981
+ viewBox: "0 0 24 24",
982
+ fill: "none",
983
+ stroke: "currentColor",
984
+ strokeWidth: 2,
985
+ strokeLinecap: "round",
986
+ strokeLinejoin: "round",
987
+ className,
988
+ "aria-hidden": "true",
989
+ children: [
990
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4" }),
991
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("polyline", { points: "7 10 12 15 17 10" }),
992
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "12", y1: "15", x2: "12", y2: "3" })
993
+ ]
994
+ }
995
+ );
996
+ };
997
+ var CloseIcon = ({ className }) => {
998
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
999
+ "svg",
1000
+ {
1001
+ xmlns: "http://www.w3.org/2000/svg",
1002
+ viewBox: "0 0 24 24",
1003
+ fill: "none",
1004
+ stroke: "currentColor",
1005
+ strokeWidth: 2,
1006
+ strokeLinecap: "round",
1007
+ strokeLinejoin: "round",
1008
+ className,
1009
+ "aria-hidden": "true",
1010
+ children: [
1011
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
1012
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
1013
+ ]
1014
+ }
1015
+ );
1016
+ };
1017
+ var RefreshIcon = ({ className }) => {
1018
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1019
+ "svg",
1020
+ {
1021
+ xmlns: "http://www.w3.org/2000/svg",
1022
+ viewBox: "0 0 24 24",
1023
+ fill: "none",
1024
+ stroke: "currentColor",
1025
+ strokeWidth: 2,
1026
+ strokeLinecap: "round",
1027
+ strokeLinejoin: "round",
1028
+ className,
1029
+ "aria-hidden": "true",
1030
+ children: [
1031
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("polyline", { points: "23 4 23 10 17 10" }),
1032
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M20.49 15a9 9 0 11-2.12-9.36L23 10" })
1033
+ ]
1034
+ }
1035
+ );
1036
+ };
1037
+ var CheckIcon = ({ className }) => {
1038
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1039
+ "svg",
1040
+ {
1041
+ xmlns: "http://www.w3.org/2000/svg",
1042
+ viewBox: "0 0 24 24",
1043
+ fill: "none",
1044
+ stroke: "currentColor",
1045
+ strokeWidth: 2,
1046
+ strokeLinecap: "round",
1047
+ strokeLinejoin: "round",
1048
+ className,
1049
+ "aria-hidden": "true",
1050
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("polyline", { points: "20 6 9 17 4 12" })
1051
+ }
1052
+ );
1053
+ };
1054
+ var WarningIcon = ({ className }) => {
1055
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1056
+ "svg",
1057
+ {
1058
+ xmlns: "http://www.w3.org/2000/svg",
1059
+ viewBox: "0 0 24 24",
1060
+ fill: "none",
1061
+ stroke: "currentColor",
1062
+ strokeWidth: 2,
1063
+ strokeLinecap: "round",
1064
+ strokeLinejoin: "round",
1065
+ className,
1066
+ "aria-hidden": "true",
1067
+ children: [
1068
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z" }),
1069
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "12", y1: "9", x2: "12", y2: "13" }),
1070
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "12", y1: "17", x2: "12.01", y2: "17" })
1071
+ ]
1072
+ }
1073
+ );
1074
+ };
1075
+
1076
+ // src/components/Trigger/Trigger.tsx
1077
+ var import_jsx_runtime2 = require("react/jsx-runtime");
1078
+ var Trigger = ({
1079
+ position = "bottom-right",
1080
+ children,
1081
+ onClick,
1082
+ disabled = false,
1083
+ className,
1084
+ zIndex = 9999
1085
+ }) => {
1086
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1087
+ "button",
1088
+ {
1089
+ type: "button",
1090
+ onClick,
1091
+ disabled,
1092
+ "aria-label": ARIA_LABELS.triggerButton,
1093
+ className: cn(
1094
+ // Base styles
1095
+ "fixed flex items-center gap-2 px-4 py-2.5 rounded-full",
1096
+ "font-medium text-sm shadow-lg",
1097
+ // Colors
1098
+ "bg-red-600 text-white",
1099
+ "hover:bg-red-700 active:bg-red-800",
1100
+ // Focus styles
1101
+ "focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2",
1102
+ // Disabled styles
1103
+ "disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:bg-red-600",
1104
+ // Transitions
1105
+ "transition-all duration-200 ease-out",
1106
+ "hover:scale-105 active:scale-95",
1107
+ // Position
1108
+ POSITION_CLASSES[position],
1109
+ className
1110
+ ),
1111
+ style: { zIndex },
1112
+ children: children ?? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
1113
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(TriggerIcon, { className: "w-5 h-5" }),
1114
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: "Record" })
1115
+ ] })
1116
+ }
1117
+ );
1118
+ };
1119
+
1120
+ // src/components/Controls/Timer.tsx
1121
+ var import_jsx_runtime3 = require("react/jsx-runtime");
1122
+ var Timer = ({
1123
+ elapsed,
1124
+ elapsedFormatted,
1125
+ remaining,
1126
+ maxDuration,
1127
+ isPaused = false,
1128
+ className
1129
+ }) => {
1130
+ const getTimerColor = () => {
1131
+ if (isPaused) {
1132
+ return "text-amber-600 dark:text-amber-400";
1133
+ }
1134
+ if (remaining != null) {
1135
+ if (remaining <= TIMER_CRITICAL_THRESHOLD) {
1136
+ return "text-red-600 dark:text-red-400";
1137
+ }
1138
+ if (remaining <= TIMER_WARNING_THRESHOLD) {
1139
+ return "text-amber-600 dark:text-amber-400";
1140
+ }
1141
+ }
1142
+ return "text-gray-700 dark:text-gray-300";
1143
+ };
1144
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1145
+ "div",
1146
+ {
1147
+ role: "timer",
1148
+ "aria-live": "polite",
1149
+ "aria-label": `${ARIA_LABELS.timer}: ${elapsedFormatted}`,
1150
+ className: cn(
1151
+ "font-mono text-sm font-medium tabular-nums",
1152
+ getTimerColor(),
1153
+ className
1154
+ ),
1155
+ children: [
1156
+ elapsedFormatted,
1157
+ maxDuration != null && isFinite(maxDuration) && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "text-gray-400 dark:text-gray-500", children: [
1158
+ " / ",
1159
+ formatDuration(maxDuration)
1160
+ ] })
1161
+ ]
1162
+ }
1163
+ );
1164
+ };
1165
+ function formatDuration(seconds) {
1166
+ const mins = Math.floor(seconds / 60);
1167
+ const secs = Math.floor(seconds % 60);
1168
+ return `${mins.toString().padStart(2, "0")}:${secs.toString().padStart(2, "0")}`;
1169
+ }
1170
+
1171
+ // src/components/Controls/RecordingControls.tsx
1172
+ var import_jsx_runtime4 = require("react/jsx-runtime");
1173
+ var RecordingControls = ({
1174
+ elapsed,
1175
+ elapsedFormatted,
1176
+ remaining,
1177
+ maxDuration,
1178
+ isPaused = false,
1179
+ isRecording = true,
1180
+ onPause,
1181
+ onResume,
1182
+ onStop,
1183
+ position = "bottom-right",
1184
+ className,
1185
+ zIndex = 9999
1186
+ }) => {
1187
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
1188
+ "div",
1189
+ {
1190
+ className: cn(
1191
+ // Base styles
1192
+ "fixed flex items-center gap-3 px-4 py-2.5 rounded-full",
1193
+ "bg-white dark:bg-gray-800 shadow-lg border border-gray-200 dark:border-gray-700",
1194
+ // Animation
1195
+ "animate-slide-up",
1196
+ // Position
1197
+ POSITION_CLASSES[position],
1198
+ className
1199
+ ),
1200
+ style: { zIndex },
1201
+ children: [
1202
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "flex items-center gap-2", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1203
+ "div",
1204
+ {
1205
+ className: cn(
1206
+ "flex items-center gap-1.5",
1207
+ isPaused ? "text-amber-500" : "text-red-500"
1208
+ ),
1209
+ children: isPaused ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
1210
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(PauseIcon, { className: "w-3 h-3" }),
1211
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "text-xs font-semibold uppercase", children: "Paused" })
1212
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
1213
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(RecordingIcon, { className: "w-3 h-3 animate-pulse-record" }),
1214
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "text-xs font-semibold uppercase", children: "Rec" })
1215
+ ] })
1216
+ }
1217
+ ) }),
1218
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1219
+ Timer,
1220
+ {
1221
+ elapsed,
1222
+ elapsedFormatted,
1223
+ remaining,
1224
+ maxDuration,
1225
+ isPaused
1226
+ }
1227
+ ),
1228
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex items-center gap-1 ml-2", children: [
1229
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1230
+ "button",
1231
+ {
1232
+ type: "button",
1233
+ onClick: isPaused ? onResume : onPause,
1234
+ "aria-label": isPaused ? ARIA_LABELS.resumeButton : ARIA_LABELS.pauseButton,
1235
+ className: cn(
1236
+ "p-2 rounded-full transition-colors",
1237
+ "hover:bg-gray-100 dark:hover:bg-gray-700",
1238
+ "focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2"
1239
+ ),
1240
+ children: isPaused ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(PlayIcon, { className: "w-4 h-4 text-gray-600 dark:text-gray-300" }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(PauseIcon, { className: "w-4 h-4 text-gray-600 dark:text-gray-300" })
1241
+ }
1242
+ ),
1243
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1244
+ "button",
1245
+ {
1246
+ type: "button",
1247
+ onClick: onStop,
1248
+ "aria-label": ARIA_LABELS.stopButton,
1249
+ className: cn(
1250
+ "p-2 rounded-full transition-colors",
1251
+ "hover:bg-red-100 dark:hover:bg-red-900/30",
1252
+ "focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2"
1253
+ ),
1254
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(StopIcon, { className: "w-4 h-4 text-red-600 dark:text-red-400" })
1255
+ }
1256
+ )
1257
+ ] })
1258
+ ]
1259
+ }
1260
+ );
1261
+ };
1262
+
1263
+ // src/components/Countdown/Countdown.tsx
1264
+ var import_jsx_runtime5 = require("react/jsx-runtime");
1265
+ var Countdown = ({
1266
+ value,
1267
+ onCancel,
1268
+ className,
1269
+ zIndex = 1e4
1270
+ }) => {
1271
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1272
+ "div",
1273
+ {
1274
+ className: cn(
1275
+ // Fullscreen overlay
1276
+ "fixed inset-0 flex flex-col items-center justify-center",
1277
+ "bg-black/50 backdrop-blur-sm",
1278
+ "animate-fade-in",
1279
+ className
1280
+ ),
1281
+ style: { zIndex },
1282
+ role: "alert",
1283
+ "aria-live": "assertive",
1284
+ "aria-label": `${ARIA_LABELS.countdown} ${value}`,
1285
+ children: [
1286
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1287
+ "div",
1288
+ {
1289
+ className: cn(
1290
+ "flex items-center justify-center",
1291
+ "w-32 h-32 rounded-full",
1292
+ "bg-white/10 border-4 border-white/30",
1293
+ "text-white text-7xl font-bold",
1294
+ "animate-countdown-scale"
1295
+ ),
1296
+ children: value
1297
+ },
1298
+ value
1299
+ ),
1300
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "mt-6 text-white/80 text-lg font-medium", children: "Recording starts in..." }),
1301
+ onCancel && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1302
+ "button",
1303
+ {
1304
+ type: "button",
1305
+ onClick: onCancel,
1306
+ className: cn(
1307
+ "mt-8 px-6 py-2 rounded-full",
1308
+ "bg-white/10 hover:bg-white/20",
1309
+ "text-white text-sm font-medium",
1310
+ "transition-colors duration-200",
1311
+ "focus:outline-none focus:ring-2 focus:ring-white/50"
1312
+ ),
1313
+ children: "Cancel"
1314
+ }
1315
+ )
1316
+ ]
1317
+ }
1318
+ );
1319
+ };
1320
+
1321
+ // src/components/Preview/VideoPlayer.tsx
1322
+ var import_react7 = require("react");
1323
+ var import_jsx_runtime6 = require("react/jsx-runtime");
1324
+ var VideoPlayer = (0, import_react7.forwardRef)(
1325
+ ({ src, className, showControls = true, autoPlay = false, knownDuration }, ref) => {
1326
+ const videoRef = (0, import_react7.useRef)(null);
1327
+ const [isPlaying, setIsPlaying] = (0, import_react7.useState)(false);
1328
+ const [currentTime, setCurrentTime] = (0, import_react7.useState)(0);
1329
+ const [duration, setDuration] = (0, import_react7.useState)(knownDuration || 0);
1330
+ (0, import_react7.useImperativeHandle)(ref, () => ({
1331
+ play: () => videoRef.current?.play(),
1332
+ pause: () => videoRef.current?.pause(),
1333
+ togglePlay: () => {
1334
+ if (isPlaying) {
1335
+ videoRef.current?.pause();
1336
+ } else {
1337
+ videoRef.current?.play();
1338
+ }
1339
+ }
1340
+ }));
1341
+ const handlePlay = (0, import_react7.useCallback)(() => setIsPlaying(true), []);
1342
+ const handlePause = (0, import_react7.useCallback)(() => setIsPlaying(false), []);
1343
+ const handleEnded = (0, import_react7.useCallback)(() => setIsPlaying(false), []);
1344
+ const handleTimeUpdate = (0, import_react7.useCallback)(() => {
1345
+ if (videoRef.current) {
1346
+ setCurrentTime(videoRef.current.currentTime);
1347
+ const videoDuration = videoRef.current.duration;
1348
+ if (isFinite(videoDuration) && videoDuration > 0) {
1349
+ setDuration(videoDuration);
1350
+ }
1351
+ }
1352
+ }, []);
1353
+ const handleLoadedMetadata = (0, import_react7.useCallback)(() => {
1354
+ if (videoRef.current) {
1355
+ const videoDuration = videoRef.current.duration;
1356
+ if (isFinite(videoDuration) && videoDuration > 0) {
1357
+ setDuration(videoDuration);
1358
+ }
1359
+ }
1360
+ }, []);
1361
+ const handleDurationChange = (0, import_react7.useCallback)(() => {
1362
+ if (videoRef.current) {
1363
+ const videoDuration = videoRef.current.duration;
1364
+ if (isFinite(videoDuration) && videoDuration > 0) {
1365
+ setDuration(videoDuration);
1366
+ }
1367
+ }
1368
+ }, []);
1369
+ (0, import_react7.useEffect)(() => {
1370
+ if (knownDuration && knownDuration > 0 && (!duration || duration === 0)) {
1371
+ setDuration(knownDuration);
1372
+ }
1373
+ }, [knownDuration, duration]);
1374
+ const handleSeek = (0, import_react7.useCallback)((e) => {
1375
+ const time = parseFloat(e.target.value);
1376
+ if (videoRef.current && isFinite(time)) {
1377
+ videoRef.current.currentTime = time;
1378
+ setCurrentTime(time);
1379
+ }
1380
+ }, []);
1381
+ const togglePlay = (0, import_react7.useCallback)(() => {
1382
+ if (isPlaying) {
1383
+ videoRef.current?.pause();
1384
+ } else {
1385
+ videoRef.current?.play();
1386
+ }
1387
+ }, [isPlaying]);
1388
+ const formatTime3 = (seconds) => {
1389
+ if (!isFinite(seconds) || isNaN(seconds)) {
1390
+ return "00:00";
1391
+ }
1392
+ const mins = Math.floor(seconds / 60);
1393
+ const secs = Math.floor(seconds % 60);
1394
+ return `${mins.toString().padStart(2, "0")}:${secs.toString().padStart(2, "0")}`;
1395
+ };
1396
+ const effectiveDuration = isFinite(duration) && duration > 0 ? duration : knownDuration || 0;
1397
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: cn("relative bg-black rounded-lg overflow-hidden", className), children: [
1398
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1399
+ "video",
1400
+ {
1401
+ ref: videoRef,
1402
+ src,
1403
+ className: "w-full h-auto",
1404
+ onPlay: handlePlay,
1405
+ onPause: handlePause,
1406
+ onEnded: handleEnded,
1407
+ onTimeUpdate: handleTimeUpdate,
1408
+ onLoadedMetadata: handleLoadedMetadata,
1409
+ onDurationChange: handleDurationChange,
1410
+ autoPlay,
1411
+ playsInline: true,
1412
+ "aria-label": ARIA_LABELS.videoPlayer
1413
+ }
1414
+ ),
1415
+ showControls && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1416
+ "div",
1417
+ {
1418
+ className: cn(
1419
+ "absolute bottom-0 left-0 right-0",
1420
+ "bg-gradient-to-t from-black/80 to-transparent",
1421
+ "p-3"
1422
+ ),
1423
+ children: [
1424
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "flex items-center gap-2 mb-2", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1425
+ "input",
1426
+ {
1427
+ type: "range",
1428
+ min: 0,
1429
+ max: effectiveDuration || 100,
1430
+ value: currentTime,
1431
+ onChange: handleSeek,
1432
+ className: cn(
1433
+ "flex-1 h-1 rounded-full appearance-none cursor-pointer",
1434
+ "bg-white/30",
1435
+ "[&::-webkit-slider-thumb]:appearance-none",
1436
+ "[&::-webkit-slider-thumb]:w-3 [&::-webkit-slider-thumb]:h-3",
1437
+ "[&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:bg-white"
1438
+ ),
1439
+ "aria-label": "Video progress"
1440
+ }
1441
+ ) }),
1442
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "flex items-center justify-between", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "flex items-center gap-2", children: [
1443
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1444
+ "button",
1445
+ {
1446
+ type: "button",
1447
+ onClick: togglePlay,
1448
+ className: cn(
1449
+ "p-1.5 rounded-full",
1450
+ "hover:bg-white/20",
1451
+ "transition-colors duration-150",
1452
+ "focus:outline-none focus:ring-2 focus:ring-white/50"
1453
+ ),
1454
+ "aria-label": isPlaying ? "Pause" : "Play",
1455
+ children: isPlaying ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(PauseIcon, { className: "w-4 h-4 text-white" }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(PlayIcon, { className: "w-4 h-4 text-white" })
1456
+ }
1457
+ ),
1458
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: "text-white/80 text-xs font-mono tabular-nums", children: [
1459
+ formatTime3(currentTime),
1460
+ " / ",
1461
+ formatTime3(effectiveDuration)
1462
+ ] })
1463
+ ] }) })
1464
+ ]
1465
+ }
1466
+ )
1467
+ ] });
1468
+ }
1469
+ );
1470
+ VideoPlayer.displayName = "VideoPlayer";
1471
+
1472
+ // src/components/Preview/PreviewModal.tsx
1473
+ var import_react8 = require("react");
1474
+ var import_react_dom = require("react-dom");
1475
+
1476
+ // src/utils/downloadBlob.ts
1477
+ function downloadBlob(blob, filename) {
1478
+ const url = URL.createObjectURL(blob);
1479
+ try {
1480
+ const link = document.createElement("a");
1481
+ link.href = url;
1482
+ link.download = filename;
1483
+ link.style.display = "none";
1484
+ document.body.appendChild(link);
1485
+ link.click();
1486
+ document.body.removeChild(link);
1487
+ } finally {
1488
+ setTimeout(() => {
1489
+ URL.revokeObjectURL(url);
1490
+ }, 100);
1491
+ }
1492
+ }
1493
+ function generateFilename(timestamp = /* @__PURE__ */ new Date(), extension = "webm") {
1494
+ const year = timestamp.getFullYear();
1495
+ const month = String(timestamp.getMonth() + 1).padStart(2, "0");
1496
+ const day = String(timestamp.getDate()).padStart(2, "0");
1497
+ const hours = String(timestamp.getHours()).padStart(2, "0");
1498
+ const minutes = String(timestamp.getMinutes()).padStart(2, "0");
1499
+ const seconds = String(timestamp.getSeconds()).padStart(2, "0");
1500
+ return `screen-recording-${year}-${month}-${day}-${hours}${minutes}${seconds}.${extension}`;
1501
+ }
1502
+ function formatBytes(bytes, decimals = 2) {
1503
+ if (bytes === 0) return "0 Bytes";
1504
+ const k = 1024;
1505
+ const dm = decimals < 0 ? 0 : decimals;
1506
+ const sizes = ["Bytes", "KB", "MB", "GB"];
1507
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
1508
+ const index = Math.min(i, sizes.length - 1);
1509
+ return `${parseFloat((bytes / Math.pow(k, index)).toFixed(dm))} ${sizes[index]}`;
1510
+ }
1511
+
1512
+ // src/components/Preview/PreviewModal.tsx
1513
+ var import_jsx_runtime7 = require("react/jsx-runtime");
1514
+ var PreviewModal = ({
1515
+ result,
1516
+ isOpen,
1517
+ onDownload,
1518
+ onReRecord,
1519
+ onClose,
1520
+ className,
1521
+ zIndex = 1e4,
1522
+ usePortal = true,
1523
+ theme = "system"
1524
+ }) => {
1525
+ const modalRef = (0, import_react8.useRef)(null);
1526
+ (0, import_react8.useEffect)(() => {
1527
+ if (!isOpen) return;
1528
+ const handleKeyDown = (e) => {
1529
+ if (e.key === "Escape") {
1530
+ onClose?.();
1531
+ }
1532
+ };
1533
+ document.addEventListener("keydown", handleKeyDown);
1534
+ return () => document.removeEventListener("keydown", handleKeyDown);
1535
+ }, [isOpen, onClose]);
1536
+ (0, import_react8.useEffect)(() => {
1537
+ if (!isOpen || !modalRef.current) return;
1538
+ const previousActiveElement = document.activeElement;
1539
+ modalRef.current.focus();
1540
+ return () => {
1541
+ previousActiveElement?.focus();
1542
+ };
1543
+ }, [isOpen]);
1544
+ if (!isOpen) return null;
1545
+ const isDark = theme === "dark" || theme === "system" && typeof window !== "undefined" && window.matchMedia?.("(prefers-color-scheme: dark)").matches;
1546
+ const modalContent = /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1547
+ "div",
1548
+ {
1549
+ className: cn(
1550
+ // Dark mode wrapper for portal
1551
+ isDark && "dark",
1552
+ // Fullscreen overlay
1553
+ "fixed inset-0 flex items-center justify-center p-4",
1554
+ "bg-black/60 backdrop-blur-sm",
1555
+ "animate-fade-in",
1556
+ className
1557
+ ),
1558
+ style: { zIndex },
1559
+ onClick: (e) => {
1560
+ if (e.target === e.currentTarget) {
1561
+ onClose?.();
1562
+ }
1563
+ },
1564
+ role: "dialog",
1565
+ "aria-modal": "true",
1566
+ "aria-labelledby": "preview-title",
1567
+ "aria-describedby": "preview-description",
1568
+ children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
1569
+ "div",
1570
+ {
1571
+ ref: modalRef,
1572
+ className: cn(
1573
+ // Modal container
1574
+ "relative w-full max-w-2xl",
1575
+ "bg-white dark:bg-gray-800 rounded-xl shadow-2xl",
1576
+ "animate-slide-up",
1577
+ "focus:outline-none"
1578
+ ),
1579
+ tabIndex: -1,
1580
+ children: [
1581
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex items-center justify-between px-6 py-4 border-b border-gray-200 dark:border-gray-700", children: [
1582
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1583
+ "h2",
1584
+ {
1585
+ id: "preview-title",
1586
+ className: "text-lg font-semibold text-gray-900 dark:text-white",
1587
+ children: "Recording Complete"
1588
+ }
1589
+ ),
1590
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1591
+ "button",
1592
+ {
1593
+ type: "button",
1594
+ onClick: onClose,
1595
+ "aria-label": ARIA_LABELS.closePreview,
1596
+ className: cn(
1597
+ "p-2 rounded-full",
1598
+ "hover:bg-gray-100 dark:hover:bg-gray-700",
1599
+ "transition-colors duration-150",
1600
+ "focus:outline-none focus:ring-2 focus:ring-blue-500"
1601
+ ),
1602
+ children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(CloseIcon, { className: "w-5 h-5 text-gray-500 dark:text-gray-400" })
1603
+ }
1604
+ )
1605
+ ] }),
1606
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "p-6", children: [
1607
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1608
+ VideoPlayer,
1609
+ {
1610
+ src: result.url,
1611
+ className: "w-full aspect-video",
1612
+ showControls: true,
1613
+ knownDuration: result.duration
1614
+ }
1615
+ ),
1616
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
1617
+ "p",
1618
+ {
1619
+ id: "preview-description",
1620
+ className: "mt-4 text-sm text-gray-500 dark:text-gray-400 text-center",
1621
+ children: [
1622
+ "Duration: ",
1623
+ formatDuration2(result.duration),
1624
+ " \u2022 Size:",
1625
+ " ",
1626
+ formatBytes(result.size),
1627
+ " \u2022 ",
1628
+ getFormatLabel(result.mimeType),
1629
+ result.hasAudio && " \u2022 With audio"
1630
+ ]
1631
+ }
1632
+ )
1633
+ ] }),
1634
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex items-center justify-center gap-3 px-6 py-4 border-t border-gray-200 dark:border-gray-700", children: [
1635
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
1636
+ "button",
1637
+ {
1638
+ type: "button",
1639
+ onClick: onDownload,
1640
+ "aria-label": ARIA_LABELS.downloadButton,
1641
+ className: cn(
1642
+ "flex items-center gap-2 px-4 py-2 rounded-lg",
1643
+ "bg-blue-600 hover:bg-blue-700 text-white",
1644
+ "font-medium text-sm",
1645
+ "transition-colors duration-150",
1646
+ "focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2",
1647
+ "dark:focus:ring-offset-gray-800"
1648
+ ),
1649
+ children: [
1650
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(DownloadIcon, { className: "w-4 h-4" }),
1651
+ "Download"
1652
+ ]
1653
+ }
1654
+ ),
1655
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
1656
+ "button",
1657
+ {
1658
+ type: "button",
1659
+ onClick: onReRecord,
1660
+ "aria-label": ARIA_LABELS.reRecordButton,
1661
+ className: cn(
1662
+ "flex items-center gap-2 px-4 py-2 rounded-lg",
1663
+ "bg-gray-100 dark:bg-gray-700 hover:bg-gray-200 dark:hover:bg-gray-600",
1664
+ "text-gray-700 dark:text-gray-300",
1665
+ "font-medium text-sm",
1666
+ "transition-colors duration-150",
1667
+ "focus:outline-none focus:ring-2 focus:ring-gray-500 focus:ring-offset-2",
1668
+ "dark:focus:ring-offset-gray-800"
1669
+ ),
1670
+ children: [
1671
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(RefreshIcon, { className: "w-4 h-4" }),
1672
+ "Re-record"
1673
+ ]
1674
+ }
1675
+ ),
1676
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
1677
+ "button",
1678
+ {
1679
+ type: "button",
1680
+ onClick: onClose,
1681
+ className: cn(
1682
+ "flex items-center gap-2 px-4 py-2 rounded-lg",
1683
+ "bg-green-600 hover:bg-green-700 text-white",
1684
+ "font-medium text-sm",
1685
+ "transition-colors duration-150",
1686
+ "focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-2",
1687
+ "dark:focus:ring-offset-gray-800"
1688
+ ),
1689
+ children: [
1690
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(CheckIcon, { className: "w-4 h-4" }),
1691
+ "Done"
1692
+ ]
1693
+ }
1694
+ )
1695
+ ] })
1696
+ ]
1697
+ }
1698
+ )
1699
+ }
1700
+ );
1701
+ if (usePortal && typeof document !== "undefined") {
1702
+ return (0, import_react_dom.createPortal)(modalContent, document.body);
1703
+ }
1704
+ return modalContent;
1705
+ };
1706
+ function formatDuration2(seconds) {
1707
+ const mins = Math.floor(seconds / 60);
1708
+ const secs = Math.floor(seconds % 60);
1709
+ return `${mins}m ${secs}s`;
1710
+ }
1711
+ function getFormatLabel(mimeType) {
1712
+ if (mimeType.includes("webm")) return "WebM";
1713
+ if (mimeType.includes("mp4")) return "MP4";
1714
+ return "Video";
1715
+ }
1716
+
1717
+ // src/components/Status/StatusBadge.tsx
1718
+ var import_jsx_runtime8 = require("react/jsx-runtime");
1719
+ var StatusBadge = ({ state, className }) => {
1720
+ const getStatusConfig = () => {
1721
+ switch (state) {
1722
+ case "recording":
1723
+ return {
1724
+ label: "REC",
1725
+ dotClass: "bg-red-500 animate-pulse-record",
1726
+ textClass: "text-red-600 dark:text-red-400",
1727
+ bgClass: "bg-red-50 dark:bg-red-900/20"
1728
+ };
1729
+ case "paused":
1730
+ return {
1731
+ label: "PAUSED",
1732
+ dotClass: "bg-amber-500",
1733
+ textClass: "text-amber-600 dark:text-amber-400",
1734
+ bgClass: "bg-amber-50 dark:bg-amber-900/20"
1735
+ };
1736
+ case "requesting":
1737
+ return {
1738
+ label: "REQUESTING",
1739
+ dotClass: "bg-blue-500 animate-pulse",
1740
+ textClass: "text-blue-600 dark:text-blue-400",
1741
+ bgClass: "bg-blue-50 dark:bg-blue-900/20"
1742
+ };
1743
+ case "countdown":
1744
+ return {
1745
+ label: "STARTING",
1746
+ dotClass: "bg-amber-500 animate-pulse",
1747
+ textClass: "text-amber-600 dark:text-amber-400",
1748
+ bgClass: "bg-amber-50 dark:bg-amber-900/20"
1749
+ };
1750
+ case "stopped":
1751
+ return {
1752
+ label: "STOPPED",
1753
+ dotClass: "bg-gray-500",
1754
+ textClass: "text-gray-600 dark:text-gray-400",
1755
+ bgClass: "bg-gray-50 dark:bg-gray-800"
1756
+ };
1757
+ case "error":
1758
+ return {
1759
+ label: "ERROR",
1760
+ dotClass: "bg-red-500",
1761
+ textClass: "text-red-600 dark:text-red-400",
1762
+ bgClass: "bg-red-50 dark:bg-red-900/20"
1763
+ };
1764
+ default:
1765
+ return {
1766
+ label: "IDLE",
1767
+ dotClass: "bg-gray-400",
1768
+ textClass: "text-gray-500 dark:text-gray-400",
1769
+ bgClass: "bg-gray-50 dark:bg-gray-800"
1770
+ };
1771
+ }
1772
+ };
1773
+ const config = getStatusConfig();
1774
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1775
+ "div",
1776
+ {
1777
+ className: cn(
1778
+ "inline-flex items-center gap-1.5 px-2 py-1 rounded-full",
1779
+ config.bgClass,
1780
+ className
1781
+ ),
1782
+ role: "status",
1783
+ "aria-live": "polite",
1784
+ children: [
1785
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: cn("w-2 h-2 rounded-full", config.dotClass) }),
1786
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: cn("text-xs font-semibold uppercase", config.textClass), children: config.label })
1787
+ ]
1788
+ }
1789
+ );
1790
+ };
1791
+
1792
+ // src/components/Status/ErrorMessage.tsx
1793
+ var import_jsx_runtime9 = require("react/jsx-runtime");
1794
+ var ErrorMessage = ({
1795
+ error,
1796
+ onRetry,
1797
+ onDismiss,
1798
+ position = "bottom-right",
1799
+ className,
1800
+ zIndex = 9999
1801
+ }) => {
1802
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
1803
+ "div",
1804
+ {
1805
+ className: cn(
1806
+ // Base styles
1807
+ "fixed flex flex-col gap-3 p-4 max-w-sm rounded-lg shadow-lg",
1808
+ "bg-red-50 dark:bg-red-900/30 border border-red-200 dark:border-red-800",
1809
+ "animate-slide-up",
1810
+ // Position
1811
+ POSITION_CLASSES[position],
1812
+ className
1813
+ ),
1814
+ style: { zIndex },
1815
+ role: "alert",
1816
+ "aria-live": "assertive",
1817
+ children: [
1818
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex items-start gap-3", children: [
1819
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(WarningIcon, { className: "w-5 h-5 text-red-500 dark:text-red-400 flex-shrink-0 mt-0.5" }),
1820
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex-1", children: [
1821
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("h3", { className: "font-semibold text-red-800 dark:text-red-200", children: getErrorTitle(error.code) }),
1822
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { className: "mt-1 text-sm text-red-600 dark:text-red-300", children: error.message })
1823
+ ] }),
1824
+ onDismiss && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1825
+ "button",
1826
+ {
1827
+ type: "button",
1828
+ onClick: onDismiss,
1829
+ className: cn(
1830
+ "p-1 rounded-full",
1831
+ "hover:bg-red-100 dark:hover:bg-red-800/50",
1832
+ "transition-colors duration-150",
1833
+ "focus:outline-none focus:ring-2 focus:ring-red-500"
1834
+ ),
1835
+ "aria-label": "Dismiss error",
1836
+ children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(CloseIcon, { className: "w-4 h-4 text-red-500 dark:text-red-400" })
1837
+ }
1838
+ )
1839
+ ] }),
1840
+ onRetry && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1841
+ "button",
1842
+ {
1843
+ type: "button",
1844
+ onClick: onRetry,
1845
+ className: cn(
1846
+ "w-full py-2 rounded-md",
1847
+ "bg-red-100 dark:bg-red-800/50 hover:bg-red-200 dark:hover:bg-red-800",
1848
+ "text-red-700 dark:text-red-200 font-medium text-sm",
1849
+ "transition-colors duration-150",
1850
+ "focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2"
1851
+ ),
1852
+ children: "Try Again"
1853
+ }
1854
+ )
1855
+ ]
1856
+ }
1857
+ );
1858
+ };
1859
+ function getErrorTitle(code) {
1860
+ switch (code) {
1861
+ case "PERMISSION_DENIED":
1862
+ return "Permission Denied";
1863
+ case "NOT_SUPPORTED":
1864
+ return "Not Supported";
1865
+ case "MEDIA_RECORDER_ERROR":
1866
+ return "Recording Error";
1867
+ case "STREAM_ENDED":
1868
+ return "Screen Sharing Stopped";
1869
+ case "NO_STREAM":
1870
+ return "No Stream";
1871
+ case "ENCODING_ERROR":
1872
+ return "Encoding Error";
1873
+ default:
1874
+ return "Error";
1875
+ }
1876
+ }
1877
+
1878
+ // src/ScreenRecorder.tsx
1879
+ var import_jsx_runtime10 = require("react/jsx-runtime");
1880
+ var ScreenRecorder = (0, import_react9.forwardRef)(
1881
+ (props, ref) => {
1882
+ const {
1883
+ // Recording options
1884
+ maxDuration = DEFAULT_OPTIONS.maxDuration,
1885
+ countdown = DEFAULT_OPTIONS.countdown,
1886
+ audio = DEFAULT_OPTIONS.audio,
1887
+ quality = DEFAULT_OPTIONS.quality,
1888
+ mimeType = DEFAULT_OPTIONS.mimeType,
1889
+ // UI options
1890
+ position = DEFAULT_OPTIONS.position,
1891
+ triggerContent,
1892
+ showPreview = DEFAULT_OPTIONS.showPreview,
1893
+ showTimer = DEFAULT_OPTIONS.showTimer,
1894
+ zIndex = DEFAULT_OPTIONS.zIndex,
1895
+ theme = DEFAULT_OPTIONS.theme,
1896
+ className,
1897
+ filename = DEFAULT_OPTIONS.filename,
1898
+ // Callbacks
1899
+ onRecordingStart,
1900
+ onRecordingStop,
1901
+ onPause,
1902
+ onResume,
1903
+ onDownload,
1904
+ onError,
1905
+ onPermissionDenied,
1906
+ onTick,
1907
+ // Advanced
1908
+ autoDownload = DEFAULT_OPTIONS.autoDownload,
1909
+ disabled = false,
1910
+ renderMode = DEFAULT_OPTIONS.renderMode
1911
+ } = props;
1912
+ const prevStateRef = (0, import_react9.useRef)("idle");
1913
+ const prevElapsedRef = (0, import_react9.useRef)(0);
1914
+ const autoDownloadTriggeredRef = (0, import_react9.useRef)(false);
1915
+ const recorder = useScreenRecorder({
1916
+ maxDuration,
1917
+ countdown,
1918
+ audio,
1919
+ quality,
1920
+ mimeType,
1921
+ onError: (error) => {
1922
+ if (error.code === "PERMISSION_DENIED") {
1923
+ onPermissionDenied?.();
1924
+ }
1925
+ onError?.(error);
1926
+ }
1927
+ });
1928
+ (0, import_react9.useEffect)(() => {
1929
+ if (recorder.state === "recording" && prevStateRef.current !== "recording" && prevStateRef.current !== "paused") {
1930
+ onRecordingStart?.();
1931
+ }
1932
+ prevStateRef.current = recorder.state;
1933
+ }, [recorder.state, onRecordingStart]);
1934
+ (0, import_react9.useEffect)(() => {
1935
+ if ((recorder.isRecording || recorder.isPaused) && recorder.elapsed !== prevElapsedRef.current) {
1936
+ onTick?.(recorder.elapsed, recorder.remaining);
1937
+ prevElapsedRef.current = recorder.elapsed;
1938
+ }
1939
+ }, [
1940
+ recorder.elapsed,
1941
+ recorder.remaining,
1942
+ recorder.isRecording,
1943
+ recorder.isPaused,
1944
+ onTick
1945
+ ]);
1946
+ (0, import_react9.useEffect)(() => {
1947
+ if (recorder.state === "stopped" && recorder.result && !showPreview && !autoDownloadTriggeredRef.current) {
1948
+ autoDownloadTriggeredRef.current = true;
1949
+ onRecordingStop?.(recorder.result);
1950
+ if (autoDownload) {
1951
+ const downloadFilename = typeof filename === "function" ? filename(recorder.result.timestamp) : generateFilename(recorder.result.timestamp);
1952
+ recorder.download(downloadFilename);
1953
+ onDownload?.(recorder.result);
1954
+ }
1955
+ setTimeout(() => {
1956
+ recorder.reset();
1957
+ autoDownloadTriggeredRef.current = false;
1958
+ }, 100);
1959
+ }
1960
+ }, [
1961
+ recorder.state,
1962
+ recorder.result,
1963
+ showPreview,
1964
+ autoDownload,
1965
+ filename,
1966
+ recorder,
1967
+ onRecordingStop,
1968
+ onDownload
1969
+ ]);
1970
+ (0, import_react9.useEffect)(() => {
1971
+ if (recorder.state === "idle") {
1972
+ autoDownloadTriggeredRef.current = false;
1973
+ }
1974
+ }, [recorder.state]);
1975
+ const handleStart = (0, import_react9.useCallback)(async () => {
1976
+ await recorder.start();
1977
+ }, [recorder]);
1978
+ const handleStop = (0, import_react9.useCallback)(() => {
1979
+ recorder.stop();
1980
+ }, [recorder]);
1981
+ const handlePause = (0, import_react9.useCallback)(() => {
1982
+ recorder.pause();
1983
+ onPause?.();
1984
+ }, [recorder, onPause]);
1985
+ const handleResume = (0, import_react9.useCallback)(() => {
1986
+ recorder.resume();
1987
+ onResume?.();
1988
+ }, [recorder, onResume]);
1989
+ const handleDownload = (0, import_react9.useCallback)(() => {
1990
+ if (!recorder.result) return;
1991
+ const downloadFilename = typeof filename === "function" ? filename(recorder.result.timestamp) : generateFilename(recorder.result.timestamp);
1992
+ recorder.download(downloadFilename);
1993
+ onDownload?.(recorder.result);
1994
+ }, [recorder, filename, onDownload]);
1995
+ const handleReRecord = (0, import_react9.useCallback)(async () => {
1996
+ recorder.reset();
1997
+ await recorder.start();
1998
+ }, [recorder]);
1999
+ const handlePreviewClose = (0, import_react9.useCallback)(() => {
2000
+ if (recorder.result) {
2001
+ onRecordingStop?.(recorder.result);
2002
+ }
2003
+ recorder.reset();
2004
+ }, [recorder, onRecordingStop]);
2005
+ const handleCountdownCancel = (0, import_react9.useCallback)(() => {
2006
+ recorder.stop();
2007
+ }, [recorder]);
2008
+ const handleErrorDismiss = (0, import_react9.useCallback)(() => {
2009
+ recorder.reset();
2010
+ }, [recorder]);
2011
+ const handleErrorRetry = (0, import_react9.useCallback)(() => {
2012
+ recorder.reset();
2013
+ handleStart();
2014
+ }, [recorder, handleStart]);
2015
+ const renderContent = () => {
2016
+ if (!recorder.isSupported) {
2017
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2018
+ ErrorMessage,
2019
+ {
2020
+ error: {
2021
+ code: "NOT_SUPPORTED",
2022
+ message: "Screen recording is not supported in your browser. Please use Chrome, Edge, or Firefox on desktop."
2023
+ },
2024
+ position,
2025
+ zIndex
2026
+ }
2027
+ );
2028
+ }
2029
+ if (recorder.error && recorder.state === "error") {
2030
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2031
+ ErrorMessage,
2032
+ {
2033
+ error: recorder.error,
2034
+ onRetry: handleErrorRetry,
2035
+ onDismiss: handleErrorDismiss,
2036
+ position,
2037
+ zIndex
2038
+ }
2039
+ );
2040
+ }
2041
+ if (recorder.isCountingDown && recorder.countdownValue != null) {
2042
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2043
+ Countdown,
2044
+ {
2045
+ value: recorder.countdownValue,
2046
+ onCancel: handleCountdownCancel,
2047
+ zIndex: zIndex + 1
2048
+ }
2049
+ );
2050
+ }
2051
+ if (recorder.isRecording || recorder.isPaused) {
2052
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2053
+ RecordingControls,
2054
+ {
2055
+ elapsed: recorder.elapsed,
2056
+ elapsedFormatted: recorder.elapsedFormatted,
2057
+ remaining: recorder.remaining,
2058
+ maxDuration,
2059
+ isPaused: recorder.isPaused,
2060
+ isRecording: recorder.isRecording,
2061
+ onPause: handlePause,
2062
+ onResume: handleResume,
2063
+ onStop: handleStop,
2064
+ position,
2065
+ zIndex
2066
+ }
2067
+ );
2068
+ }
2069
+ if (recorder.state === "stopped" && recorder.result && showPreview) {
2070
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2071
+ PreviewModal,
2072
+ {
2073
+ result: recorder.result,
2074
+ isOpen: true,
2075
+ onDownload: handleDownload,
2076
+ onReRecord: handleReRecord,
2077
+ onClose: handlePreviewClose,
2078
+ zIndex: zIndex + 1,
2079
+ usePortal: renderMode === "portal",
2080
+ theme
2081
+ }
2082
+ );
2083
+ }
2084
+ if (recorder.state === "requesting") {
2085
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2086
+ Trigger,
2087
+ {
2088
+ position,
2089
+ disabled: true,
2090
+ zIndex,
2091
+ className: cn("opacity-75", className),
2092
+ children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { className: "flex items-center gap-2", children: [
2093
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
2094
+ "svg",
2095
+ {
2096
+ className: "animate-spin h-4 w-4",
2097
+ fill: "none",
2098
+ viewBox: "0 0 24 24",
2099
+ children: [
2100
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2101
+ "circle",
2102
+ {
2103
+ className: "opacity-25",
2104
+ cx: "12",
2105
+ cy: "12",
2106
+ r: "10",
2107
+ stroke: "currentColor",
2108
+ strokeWidth: "4"
2109
+ }
2110
+ ),
2111
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2112
+ "path",
2113
+ {
2114
+ className: "opacity-75",
2115
+ fill: "currentColor",
2116
+ d: "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
2117
+ }
2118
+ )
2119
+ ]
2120
+ }
2121
+ ),
2122
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { children: "Selecting..." })
2123
+ ] })
2124
+ }
2125
+ );
2126
+ }
2127
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2128
+ Trigger,
2129
+ {
2130
+ position,
2131
+ onClick: handleStart,
2132
+ disabled,
2133
+ zIndex,
2134
+ className,
2135
+ children: triggerContent
2136
+ }
2137
+ );
2138
+ };
2139
+ const content = renderContent();
2140
+ const containerContent = /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2141
+ "div",
2142
+ {
2143
+ ref,
2144
+ className: cn("usefy-screen-recorder", theme === "dark" && "dark"),
2145
+ "data-state": recorder.state,
2146
+ children: content
2147
+ }
2148
+ );
2149
+ if (renderMode === "portal" && typeof document !== "undefined" && recorder.state !== "stopped") {
2150
+ return (0, import_react_dom2.createPortal)(containerContent, document.body);
2151
+ }
2152
+ return containerContent;
2153
+ }
2154
+ );
2155
+ ScreenRecorder.displayName = "ScreenRecorder";
2156
+ // Annotate the CommonJS export names for ESM import in node:
2157
+ 0 && (module.exports = {
2158
+ CheckIcon,
2159
+ CloseIcon,
2160
+ Countdown,
2161
+ DEFAULT_OPTIONS,
2162
+ DownloadIcon,
2163
+ ERROR_MESSAGES,
2164
+ ErrorMessage,
2165
+ PauseIcon,
2166
+ PlayIcon,
2167
+ PreviewModal,
2168
+ QUALITY_PRESETS,
2169
+ RecordingControls,
2170
+ RecordingIcon,
2171
+ RefreshIcon,
2172
+ SUPPORTED_MIME_TYPES,
2173
+ ScreenRecorder,
2174
+ StatusBadge,
2175
+ StopIcon,
2176
+ TIMER_CRITICAL_THRESHOLD,
2177
+ TIMER_WARNING_THRESHOLD,
2178
+ Timer,
2179
+ Trigger,
2180
+ TriggerIcon,
2181
+ VideoPlayer,
2182
+ WarningIcon,
2183
+ checkBrowserSupport,
2184
+ cn,
2185
+ downloadBlob,
2186
+ formatBytes,
2187
+ formatTime,
2188
+ generateFilename,
2189
+ getBestMimeType,
2190
+ useBrowserSupport,
2191
+ useCountdown,
2192
+ useDisplayMedia,
2193
+ useMediaRecorder,
2194
+ useScreenRecorder,
2195
+ useTimer
2196
+ });
2197
+ //# sourceMappingURL=index.js.map
2198
+ ;(function() {
2199
+ if (typeof document === 'undefined') return;
2200
+ var id = '__usefy_screen_recorder_styles__';
2201
+ if (document.getElementById(id)) return;
2202
+ var style = document.createElement('style');
2203
+ style.id = id;
2204
+ style.textContent = `*, ::before, ::after {
2205
+ --tw-border-spacing-x: 0;
2206
+ --tw-border-spacing-y: 0;
2207
+ --tw-translate-x: 0;
2208
+ --tw-translate-y: 0;
2209
+ --tw-rotate: 0;
2210
+ --tw-skew-x: 0;
2211
+ --tw-skew-y: 0;
2212
+ --tw-scale-x: 1;
2213
+ --tw-scale-y: 1;
2214
+ --tw-pan-x: ;
2215
+ --tw-pan-y: ;
2216
+ --tw-pinch-zoom: ;
2217
+ --tw-scroll-snap-strictness: proximity;
2218
+ --tw-gradient-from-position: ;
2219
+ --tw-gradient-via-position: ;
2220
+ --tw-gradient-to-position: ;
2221
+ --tw-ordinal: ;
2222
+ --tw-slashed-zero: ;
2223
+ --tw-numeric-figure: ;
2224
+ --tw-numeric-spacing: ;
2225
+ --tw-numeric-fraction: ;
2226
+ --tw-ring-inset: ;
2227
+ --tw-ring-offset-width: 0px;
2228
+ --tw-ring-offset-color: #fff;
2229
+ --tw-ring-color: rgb(59 130 246 / 0.5);
2230
+ --tw-ring-offset-shadow: 0 0 #0000;
2231
+ --tw-ring-shadow: 0 0 #0000;
2232
+ --tw-shadow: 0 0 #0000;
2233
+ --tw-shadow-colored: 0 0 #0000;
2234
+ --tw-blur: ;
2235
+ --tw-brightness: ;
2236
+ --tw-contrast: ;
2237
+ --tw-grayscale: ;
2238
+ --tw-hue-rotate: ;
2239
+ --tw-invert: ;
2240
+ --tw-saturate: ;
2241
+ --tw-sepia: ;
2242
+ --tw-drop-shadow: ;
2243
+ --tw-backdrop-blur: ;
2244
+ --tw-backdrop-brightness: ;
2245
+ --tw-backdrop-contrast: ;
2246
+ --tw-backdrop-grayscale: ;
2247
+ --tw-backdrop-hue-rotate: ;
2248
+ --tw-backdrop-invert: ;
2249
+ --tw-backdrop-opacity: ;
2250
+ --tw-backdrop-saturate: ;
2251
+ --tw-backdrop-sepia: ;
2252
+ --tw-contain-size: ;
2253
+ --tw-contain-layout: ;
2254
+ --tw-contain-paint: ;
2255
+ --tw-contain-style: ;
2256
+ }
2257
+
2258
+ ::backdrop {
2259
+ --tw-border-spacing-x: 0;
2260
+ --tw-border-spacing-y: 0;
2261
+ --tw-translate-x: 0;
2262
+ --tw-translate-y: 0;
2263
+ --tw-rotate: 0;
2264
+ --tw-skew-x: 0;
2265
+ --tw-skew-y: 0;
2266
+ --tw-scale-x: 1;
2267
+ --tw-scale-y: 1;
2268
+ --tw-pan-x: ;
2269
+ --tw-pan-y: ;
2270
+ --tw-pinch-zoom: ;
2271
+ --tw-scroll-snap-strictness: proximity;
2272
+ --tw-gradient-from-position: ;
2273
+ --tw-gradient-via-position: ;
2274
+ --tw-gradient-to-position: ;
2275
+ --tw-ordinal: ;
2276
+ --tw-slashed-zero: ;
2277
+ --tw-numeric-figure: ;
2278
+ --tw-numeric-spacing: ;
2279
+ --tw-numeric-fraction: ;
2280
+ --tw-ring-inset: ;
2281
+ --tw-ring-offset-width: 0px;
2282
+ --tw-ring-offset-color: #fff;
2283
+ --tw-ring-color: rgb(59 130 246 / 0.5);
2284
+ --tw-ring-offset-shadow: 0 0 #0000;
2285
+ --tw-ring-shadow: 0 0 #0000;
2286
+ --tw-shadow: 0 0 #0000;
2287
+ --tw-shadow-colored: 0 0 #0000;
2288
+ --tw-blur: ;
2289
+ --tw-brightness: ;
2290
+ --tw-contrast: ;
2291
+ --tw-grayscale: ;
2292
+ --tw-hue-rotate: ;
2293
+ --tw-invert: ;
2294
+ --tw-saturate: ;
2295
+ --tw-sepia: ;
2296
+ --tw-drop-shadow: ;
2297
+ --tw-backdrop-blur: ;
2298
+ --tw-backdrop-brightness: ;
2299
+ --tw-backdrop-contrast: ;
2300
+ --tw-backdrop-grayscale: ;
2301
+ --tw-backdrop-hue-rotate: ;
2302
+ --tw-backdrop-invert: ;
2303
+ --tw-backdrop-opacity: ;
2304
+ --tw-backdrop-saturate: ;
2305
+ --tw-backdrop-sepia: ;
2306
+ --tw-contain-size: ;
2307
+ --tw-contain-layout: ;
2308
+ --tw-contain-paint: ;
2309
+ --tw-contain-style: ;
2310
+ }/*
2311
+ ! tailwindcss v3.4.19 | MIT License | https://tailwindcss.com
2312
+ *//*
2313
+ 1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4)
2314
+ 2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116)
2315
+ */
2316
+
2317
+ *,
2318
+ ::before,
2319
+ ::after {
2320
+ box-sizing: border-box; /* 1 */
2321
+ border-width: 0; /* 2 */
2322
+ border-style: solid; /* 2 */
2323
+ border-color: #e5e7eb; /* 2 */
2324
+ }
2325
+
2326
+ ::before,
2327
+ ::after {
2328
+ --tw-content: '';
2329
+ }
2330
+
2331
+ /*
2332
+ 1. Use a consistent sensible line-height in all browsers.
2333
+ 2. Prevent adjustments of font size after orientation changes in iOS.
2334
+ 3. Use a more readable tab size.
2335
+ 4. Use the user's configured \`sans\` font-family by default.
2336
+ 5. Use the user's configured \`sans\` font-feature-settings by default.
2337
+ 6. Use the user's configured \`sans\` font-variation-settings by default.
2338
+ 7. Disable tap highlights on iOS
2339
+ */
2340
+
2341
+ html,
2342
+ :host {
2343
+ line-height: 1.5; /* 1 */
2344
+ -webkit-text-size-adjust: 100%; /* 2 */
2345
+ -moz-tab-size: 4; /* 3 */
2346
+ -o-tab-size: 4;
2347
+ tab-size: 4; /* 3 */
2348
+ font-family: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; /* 4 */
2349
+ font-feature-settings: normal; /* 5 */
2350
+ font-variation-settings: normal; /* 6 */
2351
+ -webkit-tap-highlight-color: transparent; /* 7 */
2352
+ }
2353
+
2354
+ /*
2355
+ 1. Remove the margin in all browsers.
2356
+ 2. Inherit line-height from \`html\` so users can set them as a class directly on the \`html\` element.
2357
+ */
2358
+
2359
+ body {
2360
+ margin: 0; /* 1 */
2361
+ line-height: inherit; /* 2 */
2362
+ }
2363
+
2364
+ /*
2365
+ 1. Add the correct height in Firefox.
2366
+ 2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655)
2367
+ 3. Ensure horizontal rules are visible by default.
2368
+ */
2369
+
2370
+ hr {
2371
+ height: 0; /* 1 */
2372
+ color: inherit; /* 2 */
2373
+ border-top-width: 1px; /* 3 */
2374
+ }
2375
+
2376
+ /*
2377
+ Add the correct text decoration in Chrome, Edge, and Safari.
2378
+ */
2379
+
2380
+ abbr:where([title]) {
2381
+ -webkit-text-decoration: underline dotted;
2382
+ text-decoration: underline dotted;
2383
+ }
2384
+
2385
+ /*
2386
+ Remove the default font size and weight for headings.
2387
+ */
2388
+
2389
+ h1,
2390
+ h2,
2391
+ h3,
2392
+ h4,
2393
+ h5,
2394
+ h6 {
2395
+ font-size: inherit;
2396
+ font-weight: inherit;
2397
+ }
2398
+
2399
+ /*
2400
+ Reset links to optimize for opt-in styling instead of opt-out.
2401
+ */
2402
+
2403
+ a {
2404
+ color: inherit;
2405
+ text-decoration: inherit;
2406
+ }
2407
+
2408
+ /*
2409
+ Add the correct font weight in Edge and Safari.
2410
+ */
2411
+
2412
+ b,
2413
+ strong {
2414
+ font-weight: bolder;
2415
+ }
2416
+
2417
+ /*
2418
+ 1. Use the user's configured \`mono\` font-family by default.
2419
+ 2. Use the user's configured \`mono\` font-feature-settings by default.
2420
+ 3. Use the user's configured \`mono\` font-variation-settings by default.
2421
+ 4. Correct the odd \`em\` font sizing in all browsers.
2422
+ */
2423
+
2424
+ code,
2425
+ kbd,
2426
+ samp,
2427
+ pre {
2428
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; /* 1 */
2429
+ font-feature-settings: normal; /* 2 */
2430
+ font-variation-settings: normal; /* 3 */
2431
+ font-size: 1em; /* 4 */
2432
+ }
2433
+
2434
+ /*
2435
+ Add the correct font size in all browsers.
2436
+ */
2437
+
2438
+ small {
2439
+ font-size: 80%;
2440
+ }
2441
+
2442
+ /*
2443
+ Prevent \`sub\` and \`sup\` elements from affecting the line height in all browsers.
2444
+ */
2445
+
2446
+ sub,
2447
+ sup {
2448
+ font-size: 75%;
2449
+ line-height: 0;
2450
+ position: relative;
2451
+ vertical-align: baseline;
2452
+ }
2453
+
2454
+ sub {
2455
+ bottom: -0.25em;
2456
+ }
2457
+
2458
+ sup {
2459
+ top: -0.5em;
2460
+ }
2461
+
2462
+ /*
2463
+ 1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297)
2464
+ 2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016)
2465
+ 3. Remove gaps between table borders by default.
2466
+ */
2467
+
2468
+ table {
2469
+ text-indent: 0; /* 1 */
2470
+ border-color: inherit; /* 2 */
2471
+ border-collapse: collapse; /* 3 */
2472
+ }
2473
+
2474
+ /*
2475
+ 1. Change the font styles in all browsers.
2476
+ 2. Remove the margin in Firefox and Safari.
2477
+ 3. Remove default padding in all browsers.
2478
+ */
2479
+
2480
+ button,
2481
+ input,
2482
+ optgroup,
2483
+ select,
2484
+ textarea {
2485
+ font-family: inherit; /* 1 */
2486
+ font-feature-settings: inherit; /* 1 */
2487
+ font-variation-settings: inherit; /* 1 */
2488
+ font-size: 100%; /* 1 */
2489
+ font-weight: inherit; /* 1 */
2490
+ line-height: inherit; /* 1 */
2491
+ letter-spacing: inherit; /* 1 */
2492
+ color: inherit; /* 1 */
2493
+ margin: 0; /* 2 */
2494
+ padding: 0; /* 3 */
2495
+ }
2496
+
2497
+ /*
2498
+ Remove the inheritance of text transform in Edge and Firefox.
2499
+ */
2500
+
2501
+ button,
2502
+ select {
2503
+ text-transform: none;
2504
+ }
2505
+
2506
+ /*
2507
+ 1. Correct the inability to style clickable types in iOS and Safari.
2508
+ 2. Remove default button styles.
2509
+ */
2510
+
2511
+ button,
2512
+ input:where([type='button']),
2513
+ input:where([type='reset']),
2514
+ input:where([type='submit']) {
2515
+ -webkit-appearance: button; /* 1 */
2516
+ background-color: transparent; /* 2 */
2517
+ background-image: none; /* 2 */
2518
+ }
2519
+
2520
+ /*
2521
+ Use the modern Firefox focus style for all focusable elements.
2522
+ */
2523
+
2524
+ :-moz-focusring {
2525
+ outline: auto;
2526
+ }
2527
+
2528
+ /*
2529
+ Remove the additional \`:invalid\` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737)
2530
+ */
2531
+
2532
+ :-moz-ui-invalid {
2533
+ box-shadow: none;
2534
+ }
2535
+
2536
+ /*
2537
+ Add the correct vertical alignment in Chrome and Firefox.
2538
+ */
2539
+
2540
+ progress {
2541
+ vertical-align: baseline;
2542
+ }
2543
+
2544
+ /*
2545
+ Correct the cursor style of increment and decrement buttons in Safari.
2546
+ */
2547
+
2548
+ ::-webkit-inner-spin-button,
2549
+ ::-webkit-outer-spin-button {
2550
+ height: auto;
2551
+ }
2552
+
2553
+ /*
2554
+ 1. Correct the odd appearance in Chrome and Safari.
2555
+ 2. Correct the outline style in Safari.
2556
+ */
2557
+
2558
+ [type='search'] {
2559
+ -webkit-appearance: textfield; /* 1 */
2560
+ outline-offset: -2px; /* 2 */
2561
+ }
2562
+
2563
+ /*
2564
+ Remove the inner padding in Chrome and Safari on macOS.
2565
+ */
2566
+
2567
+ ::-webkit-search-decoration {
2568
+ -webkit-appearance: none;
2569
+ }
2570
+
2571
+ /*
2572
+ 1. Correct the inability to style clickable types in iOS and Safari.
2573
+ 2. Change font properties to \`inherit\` in Safari.
2574
+ */
2575
+
2576
+ ::-webkit-file-upload-button {
2577
+ -webkit-appearance: button; /* 1 */
2578
+ font: inherit; /* 2 */
2579
+ }
2580
+
2581
+ /*
2582
+ Add the correct display in Chrome and Safari.
2583
+ */
2584
+
2585
+ summary {
2586
+ display: list-item;
2587
+ }
2588
+
2589
+ /*
2590
+ Removes the default spacing and border for appropriate elements.
2591
+ */
2592
+
2593
+ blockquote,
2594
+ dl,
2595
+ dd,
2596
+ h1,
2597
+ h2,
2598
+ h3,
2599
+ h4,
2600
+ h5,
2601
+ h6,
2602
+ hr,
2603
+ figure,
2604
+ p,
2605
+ pre {
2606
+ margin: 0;
2607
+ }
2608
+
2609
+ fieldset {
2610
+ margin: 0;
2611
+ padding: 0;
2612
+ }
2613
+
2614
+ legend {
2615
+ padding: 0;
2616
+ }
2617
+
2618
+ ol,
2619
+ ul,
2620
+ menu {
2621
+ list-style: none;
2622
+ margin: 0;
2623
+ padding: 0;
2624
+ }
2625
+
2626
+ /*
2627
+ Reset default styling for dialogs.
2628
+ */
2629
+ dialog {
2630
+ padding: 0;
2631
+ }
2632
+
2633
+ /*
2634
+ Prevent resizing textareas horizontally by default.
2635
+ */
2636
+
2637
+ textarea {
2638
+ resize: vertical;
2639
+ }
2640
+
2641
+ /*
2642
+ 1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300)
2643
+ 2. Set the default placeholder color to the user's configured gray 400 color.
2644
+ */
2645
+
2646
+ input::-moz-placeholder, textarea::-moz-placeholder {
2647
+ opacity: 1; /* 1 */
2648
+ color: #9ca3af; /* 2 */
2649
+ }
2650
+
2651
+ input::placeholder,
2652
+ textarea::placeholder {
2653
+ opacity: 1; /* 1 */
2654
+ color: #9ca3af; /* 2 */
2655
+ }
2656
+
2657
+ /*
2658
+ Set the default cursor for buttons.
2659
+ */
2660
+
2661
+ button,
2662
+ [role="button"] {
2663
+ cursor: pointer;
2664
+ }
2665
+
2666
+ /*
2667
+ Make sure disabled buttons don't get the pointer cursor.
2668
+ */
2669
+ :disabled {
2670
+ cursor: default;
2671
+ }
2672
+
2673
+ /*
2674
+ 1. Make replaced elements \`display: block\` by default. (https://github.com/mozdevs/cssremedy/issues/14)
2675
+ 2. Add \`vertical-align: middle\` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210)
2676
+ This can trigger a poorly considered lint error in some tools but is included by design.
2677
+ */
2678
+
2679
+ img,
2680
+ svg,
2681
+ video,
2682
+ canvas,
2683
+ audio,
2684
+ iframe,
2685
+ embed,
2686
+ object {
2687
+ display: block; /* 1 */
2688
+ vertical-align: middle; /* 2 */
2689
+ }
2690
+
2691
+ /*
2692
+ Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14)
2693
+ */
2694
+
2695
+ img,
2696
+ video {
2697
+ max-width: 100%;
2698
+ height: auto;
2699
+ }
2700
+
2701
+ /* Make elements with the HTML hidden attribute stay hidden by default */
2702
+ [hidden]:where(:not([hidden="until-found"])) {
2703
+ display: none;
2704
+ }
2705
+ .container {
2706
+ width: 100%;
2707
+ }
2708
+ @media (min-width: 640px) {
2709
+
2710
+ .container {
2711
+ max-width: 640px;
2712
+ }
2713
+ }
2714
+ @media (min-width: 768px) {
2715
+
2716
+ .container {
2717
+ max-width: 768px;
2718
+ }
2719
+ }
2720
+ @media (min-width: 1024px) {
2721
+
2722
+ .container {
2723
+ max-width: 1024px;
2724
+ }
2725
+ }
2726
+ @media (min-width: 1280px) {
2727
+
2728
+ .container {
2729
+ max-width: 1280px;
2730
+ }
2731
+ }
2732
+ @media (min-width: 1536px) {
2733
+
2734
+ .container {
2735
+ max-width: 1536px;
2736
+ }
2737
+ }
2738
+ .fixed {
2739
+ position: fixed;
2740
+ }
2741
+ .absolute {
2742
+ position: absolute;
2743
+ }
2744
+ .relative {
2745
+ position: relative;
2746
+ }
2747
+ .inset-0 {
2748
+ inset: 0px;
2749
+ }
2750
+ .bottom-0 {
2751
+ bottom: 0px;
2752
+ }
2753
+ .bottom-4 {
2754
+ bottom: 1rem;
2755
+ }
2756
+ .left-0 {
2757
+ left: 0px;
2758
+ }
2759
+ .left-4 {
2760
+ left: 1rem;
2761
+ }
2762
+ .right-0 {
2763
+ right: 0px;
2764
+ }
2765
+ .right-4 {
2766
+ right: 1rem;
2767
+ }
2768
+ .top-4 {
2769
+ top: 1rem;
2770
+ }
2771
+ .mb-2 {
2772
+ margin-bottom: 0.5rem;
2773
+ }
2774
+ .ml-2 {
2775
+ margin-left: 0.5rem;
2776
+ }
2777
+ .mt-0\\.5 {
2778
+ margin-top: 0.125rem;
2779
+ }
2780
+ .mt-1 {
2781
+ margin-top: 0.25rem;
2782
+ }
2783
+ .mt-4 {
2784
+ margin-top: 1rem;
2785
+ }
2786
+ .mt-6 {
2787
+ margin-top: 1.5rem;
2788
+ }
2789
+ .mt-8 {
2790
+ margin-top: 2rem;
2791
+ }
2792
+ .inline {
2793
+ display: inline;
2794
+ }
2795
+ .flex {
2796
+ display: flex;
2797
+ }
2798
+ .inline-flex {
2799
+ display: inline-flex;
2800
+ }
2801
+ .aspect-video {
2802
+ aspect-ratio: 16 / 9;
2803
+ }
2804
+ .h-1 {
2805
+ height: 0.25rem;
2806
+ }
2807
+ .h-2 {
2808
+ height: 0.5rem;
2809
+ }
2810
+ .h-3 {
2811
+ height: 0.75rem;
2812
+ }
2813
+ .h-32 {
2814
+ height: 8rem;
2815
+ }
2816
+ .h-4 {
2817
+ height: 1rem;
2818
+ }
2819
+ .h-5 {
2820
+ height: 1.25rem;
2821
+ }
2822
+ .h-auto {
2823
+ height: auto;
2824
+ }
2825
+ .w-2 {
2826
+ width: 0.5rem;
2827
+ }
2828
+ .w-3 {
2829
+ width: 0.75rem;
2830
+ }
2831
+ .w-32 {
2832
+ width: 8rem;
2833
+ }
2834
+ .w-4 {
2835
+ width: 1rem;
2836
+ }
2837
+ .w-5 {
2838
+ width: 1.25rem;
2839
+ }
2840
+ .w-full {
2841
+ width: 100%;
2842
+ }
2843
+ .max-w-2xl {
2844
+ max-width: 42rem;
2845
+ }
2846
+ .max-w-sm {
2847
+ max-width: 24rem;
2848
+ }
2849
+ .flex-1 {
2850
+ flex: 1 1 0%;
2851
+ }
2852
+ .flex-shrink-0 {
2853
+ flex-shrink: 0;
2854
+ }
2855
+ @keyframes countdown-scale {
2856
+
2857
+ 0% {
2858
+ transform: scale(0.5);
2859
+ opacity: 0;
2860
+ }
2861
+
2862
+ 50% {
2863
+ transform: scale(1.2);
2864
+ opacity: 1;
2865
+ }
2866
+
2867
+ 100% {
2868
+ transform: scale(1);
2869
+ opacity: 1;
2870
+ }
2871
+ }
2872
+ .animate-countdown-scale {
2873
+ animation: countdown-scale 0.5s ease-out;
2874
+ }
2875
+ @keyframes fade-in {
2876
+
2877
+ 0% {
2878
+ opacity: 0;
2879
+ }
2880
+
2881
+ 100% {
2882
+ opacity: 1;
2883
+ }
2884
+ }
2885
+ .animate-fade-in {
2886
+ animation: fade-in 0.2s ease-out;
2887
+ }
2888
+ @keyframes pulse {
2889
+
2890
+ 50% {
2891
+ opacity: .5;
2892
+ }
2893
+ }
2894
+ .animate-pulse {
2895
+ animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
2896
+ }
2897
+ @keyframes pulse-record {
2898
+
2899
+ 0%, 100% {
2900
+ opacity: 1;
2901
+ }
2902
+
2903
+ 50% {
2904
+ opacity: 0.5;
2905
+ }
2906
+ }
2907
+ .animate-pulse-record {
2908
+ animation: pulse-record 1s ease-in-out infinite;
2909
+ }
2910
+ @keyframes slide-up {
2911
+
2912
+ 0% {
2913
+ transform: translateY(10px);
2914
+ opacity: 0;
2915
+ }
2916
+
2917
+ 100% {
2918
+ transform: translateY(0);
2919
+ opacity: 1;
2920
+ }
2921
+ }
2922
+ .animate-slide-up {
2923
+ animation: slide-up 0.3s ease-out;
2924
+ }
2925
+ @keyframes spin {
2926
+
2927
+ to {
2928
+ transform: rotate(360deg);
2929
+ }
2930
+ }
2931
+ .animate-spin {
2932
+ animation: spin 1s linear infinite;
2933
+ }
2934
+ .cursor-pointer {
2935
+ cursor: pointer;
2936
+ }
2937
+ .appearance-none {
2938
+ -webkit-appearance: none;
2939
+ -moz-appearance: none;
2940
+ appearance: none;
2941
+ }
2942
+ .flex-col {
2943
+ flex-direction: column;
2944
+ }
2945
+ .items-start {
2946
+ align-items: flex-start;
2947
+ }
2948
+ .items-center {
2949
+ align-items: center;
2950
+ }
2951
+ .justify-center {
2952
+ justify-content: center;
2953
+ }
2954
+ .justify-between {
2955
+ justify-content: space-between;
2956
+ }
2957
+ .gap-1 {
2958
+ gap: 0.25rem;
2959
+ }
2960
+ .gap-1\\.5 {
2961
+ gap: 0.375rem;
2962
+ }
2963
+ .gap-2 {
2964
+ gap: 0.5rem;
2965
+ }
2966
+ .gap-3 {
2967
+ gap: 0.75rem;
2968
+ }
2969
+ .overflow-hidden {
2970
+ overflow: hidden;
2971
+ }
2972
+ .rounded-full {
2973
+ border-radius: 9999px;
2974
+ }
2975
+ .rounded-lg {
2976
+ border-radius: 0.5rem;
2977
+ }
2978
+ .rounded-md {
2979
+ border-radius: 0.375rem;
2980
+ }
2981
+ .rounded-xl {
2982
+ border-radius: 0.75rem;
2983
+ }
2984
+ .border {
2985
+ border-width: 1px;
2986
+ }
2987
+ .border-4 {
2988
+ border-width: 4px;
2989
+ }
2990
+ .border-b {
2991
+ border-bottom-width: 1px;
2992
+ }
2993
+ .border-t {
2994
+ border-top-width: 1px;
2995
+ }
2996
+ .border-gray-200 {
2997
+ --tw-border-opacity: 1;
2998
+ border-color: rgb(229 231 235 / var(--tw-border-opacity, 1));
2999
+ }
3000
+ .border-red-200 {
3001
+ --tw-border-opacity: 1;
3002
+ border-color: rgb(254 202 202 / var(--tw-border-opacity, 1));
3003
+ }
3004
+ .border-white\\/30 {
3005
+ border-color: rgb(255 255 255 / 0.3);
3006
+ }
3007
+ .bg-amber-50 {
3008
+ --tw-bg-opacity: 1;
3009
+ background-color: rgb(255 251 235 / var(--tw-bg-opacity, 1));
3010
+ }
3011
+ .bg-amber-500 {
3012
+ --tw-bg-opacity: 1;
3013
+ background-color: rgb(245 158 11 / var(--tw-bg-opacity, 1));
3014
+ }
3015
+ .bg-black {
3016
+ --tw-bg-opacity: 1;
3017
+ background-color: rgb(0 0 0 / var(--tw-bg-opacity, 1));
3018
+ }
3019
+ .bg-black\\/50 {
3020
+ background-color: rgb(0 0 0 / 0.5);
3021
+ }
3022
+ .bg-black\\/60 {
3023
+ background-color: rgb(0 0 0 / 0.6);
3024
+ }
3025
+ .bg-blue-50 {
3026
+ --tw-bg-opacity: 1;
3027
+ background-color: rgb(239 246 255 / var(--tw-bg-opacity, 1));
3028
+ }
3029
+ .bg-blue-500 {
3030
+ --tw-bg-opacity: 1;
3031
+ background-color: rgb(59 130 246 / var(--tw-bg-opacity, 1));
3032
+ }
3033
+ .bg-blue-600 {
3034
+ --tw-bg-opacity: 1;
3035
+ background-color: rgb(37 99 235 / var(--tw-bg-opacity, 1));
3036
+ }
3037
+ .bg-gray-100 {
3038
+ --tw-bg-opacity: 1;
3039
+ background-color: rgb(243 244 246 / var(--tw-bg-opacity, 1));
3040
+ }
3041
+ .bg-gray-400 {
3042
+ --tw-bg-opacity: 1;
3043
+ background-color: rgb(156 163 175 / var(--tw-bg-opacity, 1));
3044
+ }
3045
+ .bg-gray-50 {
3046
+ --tw-bg-opacity: 1;
3047
+ background-color: rgb(249 250 251 / var(--tw-bg-opacity, 1));
3048
+ }
3049
+ .bg-gray-500 {
3050
+ --tw-bg-opacity: 1;
3051
+ background-color: rgb(107 114 128 / var(--tw-bg-opacity, 1));
3052
+ }
3053
+ .bg-green-600 {
3054
+ --tw-bg-opacity: 1;
3055
+ background-color: rgb(22 163 74 / var(--tw-bg-opacity, 1));
3056
+ }
3057
+ .bg-red-100 {
3058
+ --tw-bg-opacity: 1;
3059
+ background-color: rgb(254 226 226 / var(--tw-bg-opacity, 1));
3060
+ }
3061
+ .bg-red-50 {
3062
+ --tw-bg-opacity: 1;
3063
+ background-color: rgb(254 242 242 / var(--tw-bg-opacity, 1));
3064
+ }
3065
+ .bg-red-500 {
3066
+ --tw-bg-opacity: 1;
3067
+ background-color: rgb(239 68 68 / var(--tw-bg-opacity, 1));
3068
+ }
3069
+ .bg-red-600 {
3070
+ --tw-bg-opacity: 1;
3071
+ background-color: rgb(220 38 38 / var(--tw-bg-opacity, 1));
3072
+ }
3073
+ .bg-white {
3074
+ --tw-bg-opacity: 1;
3075
+ background-color: rgb(255 255 255 / var(--tw-bg-opacity, 1));
3076
+ }
3077
+ .bg-white\\/10 {
3078
+ background-color: rgb(255 255 255 / 0.1);
3079
+ }
3080
+ .bg-white\\/30 {
3081
+ background-color: rgb(255 255 255 / 0.3);
3082
+ }
3083
+ .bg-gradient-to-t {
3084
+ background-image: linear-gradient(to top, var(--tw-gradient-stops));
3085
+ }
3086
+ .from-black\\/80 {
3087
+ --tw-gradient-from: rgb(0 0 0 / 0.8) var(--tw-gradient-from-position);
3088
+ --tw-gradient-to: rgb(0 0 0 / 0) var(--tw-gradient-to-position);
3089
+ --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
3090
+ }
3091
+ .to-transparent {
3092
+ --tw-gradient-to: transparent var(--tw-gradient-to-position);
3093
+ }
3094
+ .p-1 {
3095
+ padding: 0.25rem;
3096
+ }
3097
+ .p-1\\.5 {
3098
+ padding: 0.375rem;
3099
+ }
3100
+ .p-2 {
3101
+ padding: 0.5rem;
3102
+ }
3103
+ .p-3 {
3104
+ padding: 0.75rem;
3105
+ }
3106
+ .p-4 {
3107
+ padding: 1rem;
3108
+ }
3109
+ .p-6 {
3110
+ padding: 1.5rem;
3111
+ }
3112
+ .px-2 {
3113
+ padding-left: 0.5rem;
3114
+ padding-right: 0.5rem;
3115
+ }
3116
+ .px-4 {
3117
+ padding-left: 1rem;
3118
+ padding-right: 1rem;
3119
+ }
3120
+ .px-6 {
3121
+ padding-left: 1.5rem;
3122
+ padding-right: 1.5rem;
3123
+ }
3124
+ .py-1 {
3125
+ padding-top: 0.25rem;
3126
+ padding-bottom: 0.25rem;
3127
+ }
3128
+ .py-2 {
3129
+ padding-top: 0.5rem;
3130
+ padding-bottom: 0.5rem;
3131
+ }
3132
+ .py-2\\.5 {
3133
+ padding-top: 0.625rem;
3134
+ padding-bottom: 0.625rem;
3135
+ }
3136
+ .py-4 {
3137
+ padding-top: 1rem;
3138
+ padding-bottom: 1rem;
3139
+ }
3140
+ .text-center {
3141
+ text-align: center;
3142
+ }
3143
+ .font-mono {
3144
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
3145
+ }
3146
+ .text-7xl {
3147
+ font-size: 4.5rem;
3148
+ line-height: 1;
3149
+ }
3150
+ .text-lg {
3151
+ font-size: 1.125rem;
3152
+ line-height: 1.75rem;
3153
+ }
3154
+ .text-sm {
3155
+ font-size: 0.875rem;
3156
+ line-height: 1.25rem;
3157
+ }
3158
+ .text-xs {
3159
+ font-size: 0.75rem;
3160
+ line-height: 1rem;
3161
+ }
3162
+ .font-bold {
3163
+ font-weight: 700;
3164
+ }
3165
+ .font-medium {
3166
+ font-weight: 500;
3167
+ }
3168
+ .font-semibold {
3169
+ font-weight: 600;
3170
+ }
3171
+ .uppercase {
3172
+ text-transform: uppercase;
3173
+ }
3174
+ .tabular-nums {
3175
+ --tw-numeric-spacing: tabular-nums;
3176
+ font-variant-numeric: var(--tw-ordinal) var(--tw-slashed-zero) var(--tw-numeric-figure) var(--tw-numeric-spacing) var(--tw-numeric-fraction);
3177
+ }
3178
+ .text-amber-500 {
3179
+ --tw-text-opacity: 1;
3180
+ color: rgb(245 158 11 / var(--tw-text-opacity, 1));
3181
+ }
3182
+ .text-amber-600 {
3183
+ --tw-text-opacity: 1;
3184
+ color: rgb(217 119 6 / var(--tw-text-opacity, 1));
3185
+ }
3186
+ .text-blue-600 {
3187
+ --tw-text-opacity: 1;
3188
+ color: rgb(37 99 235 / var(--tw-text-opacity, 1));
3189
+ }
3190
+ .text-gray-400 {
3191
+ --tw-text-opacity: 1;
3192
+ color: rgb(156 163 175 / var(--tw-text-opacity, 1));
3193
+ }
3194
+ .text-gray-500 {
3195
+ --tw-text-opacity: 1;
3196
+ color: rgb(107 114 128 / var(--tw-text-opacity, 1));
3197
+ }
3198
+ .text-gray-600 {
3199
+ --tw-text-opacity: 1;
3200
+ color: rgb(75 85 99 / var(--tw-text-opacity, 1));
3201
+ }
3202
+ .text-gray-700 {
3203
+ --tw-text-opacity: 1;
3204
+ color: rgb(55 65 81 / var(--tw-text-opacity, 1));
3205
+ }
3206
+ .text-gray-900 {
3207
+ --tw-text-opacity: 1;
3208
+ color: rgb(17 24 39 / var(--tw-text-opacity, 1));
3209
+ }
3210
+ .text-red-500 {
3211
+ --tw-text-opacity: 1;
3212
+ color: rgb(239 68 68 / var(--tw-text-opacity, 1));
3213
+ }
3214
+ .text-red-600 {
3215
+ --tw-text-opacity: 1;
3216
+ color: rgb(220 38 38 / var(--tw-text-opacity, 1));
3217
+ }
3218
+ .text-red-700 {
3219
+ --tw-text-opacity: 1;
3220
+ color: rgb(185 28 28 / var(--tw-text-opacity, 1));
3221
+ }
3222
+ .text-red-800 {
3223
+ --tw-text-opacity: 1;
3224
+ color: rgb(153 27 27 / var(--tw-text-opacity, 1));
3225
+ }
3226
+ .text-white {
3227
+ --tw-text-opacity: 1;
3228
+ color: rgb(255 255 255 / var(--tw-text-opacity, 1));
3229
+ }
3230
+ .text-white\\/80 {
3231
+ color: rgb(255 255 255 / 0.8);
3232
+ }
3233
+ .opacity-25 {
3234
+ opacity: 0.25;
3235
+ }
3236
+ .opacity-75 {
3237
+ opacity: 0.75;
3238
+ }
3239
+ .shadow-2xl {
3240
+ --tw-shadow: 0 25px 50px -12px rgb(0 0 0 / 0.25);
3241
+ --tw-shadow-colored: 0 25px 50px -12px var(--tw-shadow-color);
3242
+ box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
3243
+ }
3244
+ .shadow-lg {
3245
+ --tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
3246
+ --tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);
3247
+ box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
3248
+ }
3249
+ .filter {
3250
+ filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
3251
+ }
3252
+ .backdrop-blur-sm {
3253
+ --tw-backdrop-blur: blur(4px);
3254
+ backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);
3255
+ }
3256
+ .transition {
3257
+ transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter;
3258
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
3259
+ transition-duration: 150ms;
3260
+ }
3261
+ .transition-all {
3262
+ transition-property: all;
3263
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
3264
+ transition-duration: 150ms;
3265
+ }
3266
+ .transition-colors {
3267
+ transition-property: color, background-color, border-color, text-decoration-color, fill, stroke;
3268
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
3269
+ transition-duration: 150ms;
3270
+ }
3271
+ .duration-150 {
3272
+ transition-duration: 150ms;
3273
+ }
3274
+ .duration-200 {
3275
+ transition-duration: 200ms;
3276
+ }
3277
+ .ease-out {
3278
+ transition-timing-function: cubic-bezier(0, 0, 0.2, 1);
3279
+ }
3280
+ .hover\\:scale-105:hover {
3281
+ --tw-scale-x: 1.05;
3282
+ --tw-scale-y: 1.05;
3283
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
3284
+ }
3285
+ .hover\\:bg-blue-700:hover {
3286
+ --tw-bg-opacity: 1;
3287
+ background-color: rgb(29 78 216 / var(--tw-bg-opacity, 1));
3288
+ }
3289
+ .hover\\:bg-gray-100:hover {
3290
+ --tw-bg-opacity: 1;
3291
+ background-color: rgb(243 244 246 / var(--tw-bg-opacity, 1));
3292
+ }
3293
+ .hover\\:bg-gray-200:hover {
3294
+ --tw-bg-opacity: 1;
3295
+ background-color: rgb(229 231 235 / var(--tw-bg-opacity, 1));
3296
+ }
3297
+ .hover\\:bg-green-700:hover {
3298
+ --tw-bg-opacity: 1;
3299
+ background-color: rgb(21 128 61 / var(--tw-bg-opacity, 1));
3300
+ }
3301
+ .hover\\:bg-red-100:hover {
3302
+ --tw-bg-opacity: 1;
3303
+ background-color: rgb(254 226 226 / var(--tw-bg-opacity, 1));
3304
+ }
3305
+ .hover\\:bg-red-200:hover {
3306
+ --tw-bg-opacity: 1;
3307
+ background-color: rgb(254 202 202 / var(--tw-bg-opacity, 1));
3308
+ }
3309
+ .hover\\:bg-red-700:hover {
3310
+ --tw-bg-opacity: 1;
3311
+ background-color: rgb(185 28 28 / var(--tw-bg-opacity, 1));
3312
+ }
3313
+ .hover\\:bg-white\\/20:hover {
3314
+ background-color: rgb(255 255 255 / 0.2);
3315
+ }
3316
+ .focus\\:outline-none:focus {
3317
+ outline: 2px solid transparent;
3318
+ outline-offset: 2px;
3319
+ }
3320
+ .focus\\:ring-2:focus {
3321
+ --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
3322
+ --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);
3323
+ box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
3324
+ }
3325
+ .focus\\:ring-blue-500:focus {
3326
+ --tw-ring-opacity: 1;
3327
+ --tw-ring-color: rgb(59 130 246 / var(--tw-ring-opacity, 1));
3328
+ }
3329
+ .focus\\:ring-gray-500:focus {
3330
+ --tw-ring-opacity: 1;
3331
+ --tw-ring-color: rgb(107 114 128 / var(--tw-ring-opacity, 1));
3332
+ }
3333
+ .focus\\:ring-green-500:focus {
3334
+ --tw-ring-opacity: 1;
3335
+ --tw-ring-color: rgb(34 197 94 / var(--tw-ring-opacity, 1));
3336
+ }
3337
+ .focus\\:ring-red-500:focus {
3338
+ --tw-ring-opacity: 1;
3339
+ --tw-ring-color: rgb(239 68 68 / var(--tw-ring-opacity, 1));
3340
+ }
3341
+ .focus\\:ring-white\\/50:focus {
3342
+ --tw-ring-color: rgb(255 255 255 / 0.5);
3343
+ }
3344
+ .focus\\:ring-offset-2:focus {
3345
+ --tw-ring-offset-width: 2px;
3346
+ }
3347
+ .active\\:scale-95:active {
3348
+ --tw-scale-x: .95;
3349
+ --tw-scale-y: .95;
3350
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
3351
+ }
3352
+ .active\\:bg-red-800:active {
3353
+ --tw-bg-opacity: 1;
3354
+ background-color: rgb(153 27 27 / var(--tw-bg-opacity, 1));
3355
+ }
3356
+ .disabled\\:cursor-not-allowed:disabled {
3357
+ cursor: not-allowed;
3358
+ }
3359
+ .disabled\\:opacity-50:disabled {
3360
+ opacity: 0.5;
3361
+ }
3362
+ .disabled\\:hover\\:bg-red-600:hover:disabled {
3363
+ --tw-bg-opacity: 1;
3364
+ background-color: rgb(220 38 38 / var(--tw-bg-opacity, 1));
3365
+ }
3366
+ .dark\\:border-gray-700:is(.dark *) {
3367
+ --tw-border-opacity: 1;
3368
+ border-color: rgb(55 65 81 / var(--tw-border-opacity, 1));
3369
+ }
3370
+ .dark\\:border-red-800:is(.dark *) {
3371
+ --tw-border-opacity: 1;
3372
+ border-color: rgb(153 27 27 / var(--tw-border-opacity, 1));
3373
+ }
3374
+ .dark\\:bg-amber-900\\/20:is(.dark *) {
3375
+ background-color: rgb(120 53 15 / 0.2);
3376
+ }
3377
+ .dark\\:bg-blue-900\\/20:is(.dark *) {
3378
+ background-color: rgb(30 58 138 / 0.2);
3379
+ }
3380
+ .dark\\:bg-gray-700:is(.dark *) {
3381
+ --tw-bg-opacity: 1;
3382
+ background-color: rgb(55 65 81 / var(--tw-bg-opacity, 1));
3383
+ }
3384
+ .dark\\:bg-gray-800:is(.dark *) {
3385
+ --tw-bg-opacity: 1;
3386
+ background-color: rgb(31 41 55 / var(--tw-bg-opacity, 1));
3387
+ }
3388
+ .dark\\:bg-red-800\\/50:is(.dark *) {
3389
+ background-color: rgb(153 27 27 / 0.5);
3390
+ }
3391
+ .dark\\:bg-red-900\\/20:is(.dark *) {
3392
+ background-color: rgb(127 29 29 / 0.2);
3393
+ }
3394
+ .dark\\:bg-red-900\\/30:is(.dark *) {
3395
+ background-color: rgb(127 29 29 / 0.3);
3396
+ }
3397
+ .dark\\:text-amber-400:is(.dark *) {
3398
+ --tw-text-opacity: 1;
3399
+ color: rgb(251 191 36 / var(--tw-text-opacity, 1));
3400
+ }
3401
+ .dark\\:text-blue-400:is(.dark *) {
3402
+ --tw-text-opacity: 1;
3403
+ color: rgb(96 165 250 / var(--tw-text-opacity, 1));
3404
+ }
3405
+ .dark\\:text-gray-300:is(.dark *) {
3406
+ --tw-text-opacity: 1;
3407
+ color: rgb(209 213 219 / var(--tw-text-opacity, 1));
3408
+ }
3409
+ .dark\\:text-gray-400:is(.dark *) {
3410
+ --tw-text-opacity: 1;
3411
+ color: rgb(156 163 175 / var(--tw-text-opacity, 1));
3412
+ }
3413
+ .dark\\:text-gray-500:is(.dark *) {
3414
+ --tw-text-opacity: 1;
3415
+ color: rgb(107 114 128 / var(--tw-text-opacity, 1));
3416
+ }
3417
+ .dark\\:text-red-200:is(.dark *) {
3418
+ --tw-text-opacity: 1;
3419
+ color: rgb(254 202 202 / var(--tw-text-opacity, 1));
3420
+ }
3421
+ .dark\\:text-red-300:is(.dark *) {
3422
+ --tw-text-opacity: 1;
3423
+ color: rgb(252 165 165 / var(--tw-text-opacity, 1));
3424
+ }
3425
+ .dark\\:text-red-400:is(.dark *) {
3426
+ --tw-text-opacity: 1;
3427
+ color: rgb(248 113 113 / var(--tw-text-opacity, 1));
3428
+ }
3429
+ .dark\\:text-white:is(.dark *) {
3430
+ --tw-text-opacity: 1;
3431
+ color: rgb(255 255 255 / var(--tw-text-opacity, 1));
3432
+ }
3433
+ .dark\\:hover\\:bg-gray-600:hover:is(.dark *) {
3434
+ --tw-bg-opacity: 1;
3435
+ background-color: rgb(75 85 99 / var(--tw-bg-opacity, 1));
3436
+ }
3437
+ .dark\\:hover\\:bg-gray-700:hover:is(.dark *) {
3438
+ --tw-bg-opacity: 1;
3439
+ background-color: rgb(55 65 81 / var(--tw-bg-opacity, 1));
3440
+ }
3441
+ .dark\\:hover\\:bg-red-800:hover:is(.dark *) {
3442
+ --tw-bg-opacity: 1;
3443
+ background-color: rgb(153 27 27 / var(--tw-bg-opacity, 1));
3444
+ }
3445
+ .dark\\:hover\\:bg-red-800\\/50:hover:is(.dark *) {
3446
+ background-color: rgb(153 27 27 / 0.5);
3447
+ }
3448
+ .dark\\:hover\\:bg-red-900\\/30:hover:is(.dark *) {
3449
+ background-color: rgb(127 29 29 / 0.3);
3450
+ }
3451
+ .dark\\:focus\\:ring-offset-gray-800:focus:is(.dark *) {
3452
+ --tw-ring-offset-color: #1f2937;
3453
+ }
3454
+ .\\[\\&\\:\\:-webkit-slider-thumb\\]\\:h-3::-webkit-slider-thumb {
3455
+ height: 0.75rem;
3456
+ }
3457
+ .\\[\\&\\:\\:-webkit-slider-thumb\\]\\:w-3::-webkit-slider-thumb {
3458
+ width: 0.75rem;
3459
+ }
3460
+ .\\[\\&\\:\\:-webkit-slider-thumb\\]\\:appearance-none::-webkit-slider-thumb {
3461
+ -webkit-appearance: none;
3462
+ appearance: none;
3463
+ }
3464
+ .\\[\\&\\:\\:-webkit-slider-thumb\\]\\:rounded-full::-webkit-slider-thumb {
3465
+ border-radius: 9999px;
3466
+ }
3467
+ .\\[\\&\\:\\:-webkit-slider-thumb\\]\\:bg-white::-webkit-slider-thumb {
3468
+ --tw-bg-opacity: 1;
3469
+ background-color: rgb(255 255 255 / var(--tw-bg-opacity, 1));
3470
+ }
3471
+ `;
3472
+ document.head.appendChild(style);
3473
+ })();