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