@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/README.md +631 -0
- package/dist/index.d.mts +1092 -0
- package/dist/index.d.ts +1092 -0
- package/dist/index.js +3473 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +3410 -0
- package/dist/index.mjs.map +1 -0
- package/dist/styles.css +1267 -0
- package/package.json +80 -0
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
|
+
})();
|