{
+ if (isPlayingSegments) {
+ e.stopPropagation();
+ e.preventDefault();
+ }
+ }}
>
{/* First row with time adjustment buttons - same as segment tooltip */}
- You're about to replace the original video with this trimmed version. This can't be undone.
+ You're about to replace the original video with this trimmed version. This can't be
+ undone.
- You're about to save a new copy with your edits. The original video will stay the same. Find the new file in your My Media folder - named after the original file.
+ You're about to save a new copy with your edits. The original video will stay the
+ same. Find the new file in your My Media folder - named after the original file.
- You're about to save each segment as a separate video. Find the new files in your My Media folder - named after the original file.
+ You're about to save each segment as a separate video. Find the new files in your My
+ Media folder - named after the original file.
@@ -4115,9 +4694,11 @@ const TimelineControls = ({
{saveType === "segments"
? "You will be redirected to your "
: "You will be redirected to your "}
- media page
+
+ media page
+
{" in "}
- 10 seconds. {' '}
+ 10 seconds.{" "}
{saveType === "segments"
? "The new video(s) will soon be there."
: "Changes to the video might take a few minutes to be applied."}
@@ -4133,22 +4714,40 @@ const TimelineControls = ({
>
-
+
-
- {errorMessage}
-
+
{errorMessage}
setShowErrorModal(false)}
className="modal-choice-button centered-choice"
>
-
+
@@ -4174,4 +4773,4 @@ const TimelineControls = ({
);
};
-export default TimelineControls;
\ No newline at end of file
+export default TimelineControls;
diff --git a/frontend-tools/video-editor/client/src/components/VideoPlayer.tsx b/frontend-tools/video-editor/client/src/components/VideoPlayer.tsx
index 389cf5cf..842d5e5e 100644
--- a/frontend-tools/video-editor/client/src/components/VideoPlayer.tsx
+++ b/frontend-tools/video-editor/client/src/components/VideoPlayer.tsx
@@ -1,7 +1,7 @@
import React, { useRef, useEffect, useState } from "react";
import { formatTime, formatDetailedTime } from "@/lib/timeUtils";
-import logger from '../lib/logger';
-import '../styles/VideoPlayer.css';
+import logger from "../lib/logger";
+import "../styles/VideoPlayer.css";
interface VideoPlayerProps {
videoRef: React.RefObject;
@@ -32,37 +32,37 @@ const VideoPlayer: React.FC = ({
const isDraggingProgressRef = useRef(false);
const [tooltipPosition, setTooltipPosition] = useState({ x: 0 });
const [tooltipTime, setTooltipTime] = useState(0);
-
- const sampleVideoUrl = typeof window !== 'undefined' &&
- (window as any).MEDIA_DATA?.videoUrl ||
- "/videos/sample-video-37s.mp4";
-
+
+ const sampleVideoUrl =
+ (typeof window !== "undefined" && (window as any).MEDIA_DATA?.videoUrl) ||
+ "/videos/sample-video-10m.mp4";
+
// Detect iOS device
useEffect(() => {
const checkIOS = () => {
const userAgent = navigator.userAgent || navigator.vendor || (window as any).opera;
return /iPad|iPhone|iPod/.test(userAgent) && !(window as any).MSStream;
};
-
+
setIsIOS(checkIOS());
-
+
// Check if video was previously initialized
- if (typeof window !== 'undefined') {
- const wasInitialized = localStorage.getItem('video_initialized') === 'true';
+ if (typeof window !== "undefined") {
+ const wasInitialized = localStorage.getItem("video_initialized") === "true";
setHasInitialized(wasInitialized);
}
}, []);
-
+
// Update initialized state when video plays
useEffect(() => {
if (isPlaying && !hasInitialized) {
setHasInitialized(true);
- if (typeof window !== 'undefined') {
- localStorage.setItem('video_initialized', 'true');
+ if (typeof window !== "undefined") {
+ localStorage.setItem("video_initialized", "true");
}
}
}, [isPlaying, hasInitialized]);
-
+
// Add iOS-specific attributes to prevent fullscreen playback
useEffect(() => {
const video = videoRef.current;
@@ -70,15 +70,15 @@ const VideoPlayer: React.FC = ({
// These attributes need to be set directly on the DOM element
// for iOS Safari to respect inline playback
- video.setAttribute('playsinline', 'true');
- video.setAttribute('webkit-playsinline', 'true');
- video.setAttribute('x-webkit-airplay', 'allow');
+ video.setAttribute("playsinline", "true");
+ video.setAttribute("webkit-playsinline", "true");
+ video.setAttribute("x-webkit-airplay", "allow");
// Store the last known good position for iOS
const handleTimeUpdate = () => {
if (!isDraggingProgressRef.current) {
setLastPosition(video.currentTime);
- if (typeof window !== 'undefined') {
+ if (typeof window !== "undefined") {
window.lastSeekedPosition = video.currentTime;
}
}
@@ -86,33 +86,33 @@ const VideoPlayer: React.FC = ({
// Handle iOS-specific play/pause state
const handlePlay = () => {
- logger.debug('Video play event fired');
+ logger.debug("Video play event fired");
if (isIOS) {
setHasInitialized(true);
- localStorage.setItem('video_initialized', 'true');
+ localStorage.setItem("video_initialized", "true");
}
};
const handlePause = () => {
- logger.debug('Video pause event fired');
+ logger.debug("Video pause event fired");
};
- video.addEventListener('timeupdate', handleTimeUpdate);
- video.addEventListener('play', handlePlay);
- video.addEventListener('pause', handlePause);
+ video.addEventListener("timeupdate", handleTimeUpdate);
+ video.addEventListener("play", handlePlay);
+ video.addEventListener("pause", handlePause);
return () => {
- video.removeEventListener('timeupdate', handleTimeUpdate);
- video.removeEventListener('play', handlePlay);
- video.removeEventListener('pause', handlePause);
+ video.removeEventListener("timeupdate", handleTimeUpdate);
+ video.removeEventListener("play", handlePlay);
+ video.removeEventListener("pause", handlePause);
};
}, [videoRef, isIOS, isDraggingProgressRef]);
-
+
// Save current time to lastPosition when it changes (from external seeking)
useEffect(() => {
setLastPosition(currentTime);
}, [currentTime]);
-
+
// Jump 10 seconds forward
const handleForward = () => {
const newTime = Math.min(currentTime + 10, duration);
@@ -126,58 +126,58 @@ const VideoPlayer: React.FC = ({
onSeek(newTime);
setLastPosition(newTime);
};
-
+
// Calculate progress percentage
const progressPercentage = duration > 0 ? (currentTime / duration) * 100 : 0;
// Handle start of progress bar dragging
const handleProgressDragStart = (e: React.MouseEvent) => {
e.preventDefault();
-
+
setIsDraggingProgress(true);
isDraggingProgressRef.current = true;
-
+
// Get initial position
handleProgressDrag(e);
-
+
// Set up document-level event listeners for mouse movement and release
const handleMouseMove = (moveEvent: MouseEvent) => {
if (isDraggingProgressRef.current) {
handleProgressDrag(moveEvent);
}
};
-
+
const handleMouseUp = () => {
setIsDraggingProgress(false);
isDraggingProgressRef.current = false;
- document.removeEventListener('mousemove', handleMouseMove);
- document.removeEventListener('mouseup', handleMouseUp);
+ document.removeEventListener("mousemove", handleMouseMove);
+ document.removeEventListener("mouseup", handleMouseUp);
};
-
- document.addEventListener('mousemove', handleMouseMove);
- document.addEventListener('mouseup', handleMouseUp);
+
+ document.addEventListener("mousemove", handleMouseMove);
+ document.addEventListener("mouseup", handleMouseUp);
};
-
+
// Handle progress dragging for both mouse and touch events
const handleProgressDrag = (e: MouseEvent | React.MouseEvent) => {
if (!progressRef.current) return;
-
+
const rect = progressRef.current.getBoundingClientRect();
const clickPosition = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width));
const seekTime = duration * clickPosition;
-
+
// Update tooltip position and time
setTooltipPosition({ x: e.clientX });
setTooltipTime(seekTime);
-
+
// Store position locally for iOS Safari - critical for timeline seeking
setLastPosition(seekTime);
-
+
// Also store globally for integration with other components
- if (typeof window !== 'undefined') {
+ if (typeof window !== "undefined") {
(window as any).lastSeekedPosition = seekTime;
}
-
+
onSeek(seekTime);
};
@@ -185,59 +185,59 @@ const VideoPlayer: React.FC = ({
const handleProgressTouchStart = (e: React.TouchEvent) => {
if (!progressRef.current || !e.touches[0]) return;
e.preventDefault();
-
+
setIsDraggingProgress(true);
isDraggingProgressRef.current = true;
-
+
// Get initial position using touch
handleProgressTouchMove(e);
-
+
// Set up document-level event listeners for touch movement and release
const handleTouchMove = (moveEvent: TouchEvent) => {
if (isDraggingProgressRef.current) {
handleProgressTouchMove(moveEvent);
}
};
-
+
const handleTouchEnd = () => {
setIsDraggingProgress(false);
isDraggingProgressRef.current = false;
- document.removeEventListener('touchmove', handleTouchMove);
- document.removeEventListener('touchend', handleTouchEnd);
- document.removeEventListener('touchcancel', handleTouchEnd);
+ document.removeEventListener("touchmove", handleTouchMove);
+ document.removeEventListener("touchend", handleTouchEnd);
+ document.removeEventListener("touchcancel", handleTouchEnd);
};
-
- document.addEventListener('touchmove', handleTouchMove, { passive: false });
- document.addEventListener('touchend', handleTouchEnd);
- document.addEventListener('touchcancel', handleTouchEnd);
+
+ document.addEventListener("touchmove", handleTouchMove, { passive: false });
+ document.addEventListener("touchend", handleTouchEnd);
+ document.addEventListener("touchcancel", handleTouchEnd);
};
-
+
// Handle touch dragging on progress bar
const handleProgressTouchMove = (e: TouchEvent | React.TouchEvent) => {
if (!progressRef.current) return;
-
+
// Get the touch coordinates
- const touch = 'touches' in e ? e.touches[0] : null;
+ const touch = "touches" in e ? e.touches[0] : null;
if (!touch) return;
-
+
e.preventDefault(); // Prevent scrolling while dragging
-
+
const rect = progressRef.current.getBoundingClientRect();
const touchPosition = Math.max(0, Math.min(1, (touch.clientX - rect.left) / rect.width));
const seekTime = duration * touchPosition;
-
+
// Update tooltip position and time
setTooltipPosition({ x: touch.clientX });
setTooltipTime(seekTime);
-
+
// Store position for iOS Safari
setLastPosition(seekTime);
-
+
// Also store globally for integration with other components
- if (typeof window !== 'undefined') {
+ if (typeof window !== "undefined") {
(window as any).lastSeekedPosition = seekTime;
}
-
+
onSeek(seekTime);
};
@@ -245,20 +245,20 @@ const VideoPlayer: React.FC = ({
const handleProgressClick = (e: React.MouseEvent) => {
// If we're already dragging, don't handle the click
if (isDraggingProgress) return;
-
+
if (progressRef.current) {
const rect = progressRef.current.getBoundingClientRect();
const clickPosition = (e.clientX - rect.left) / rect.width;
const seekTime = duration * clickPosition;
-
+
// Store position locally for iOS Safari - critical for timeline seeking
setLastPosition(seekTime);
-
+
// Also store globally for integration with other components
- if (typeof window !== 'undefined') {
+ if (typeof window !== "undefined") {
(window as any).lastSeekedPosition = seekTime;
}
-
+
onSeek(seekTime);
}
};
@@ -278,38 +278,43 @@ const VideoPlayer: React.FC = ({
const handleVideoClick = () => {
const video = videoRef.current;
if (!video) return;
-
+
// If the video is paused, we want to play it
if (video.paused) {
// For iOS Safari: Before playing, explicitly seek to the remembered position
if (isIOS && lastPosition !== null && lastPosition > 0) {
logger.debug("iOS: Explicitly setting position before play:", lastPosition);
-
+
// First, seek to the position
video.currentTime = lastPosition;
-
+
// Use a small timeout to ensure seeking is complete before play
setTimeout(() => {
if (videoRef.current) {
// Try to play with proper promise handling
- videoRef.current.play()
+ videoRef.current
+ .play()
.then(() => {
- logger.debug("iOS: Play started successfully at position:", videoRef.current?.currentTime);
+ logger.debug(
+ "iOS: Play started successfully at position:",
+ videoRef.current?.currentTime
+ );
onPlayPause(); // Update parent state after successful play
})
- .catch(err => {
+ .catch((err) => {
console.error("iOS: Error playing video:", err);
});
}
}, 50);
} else {
// Normal play (non-iOS or no remembered position)
- video.play()
+ video
+ .play()
.then(() => {
logger.debug("Normal: Play started successfully");
onPlayPause(); // Update parent state after successful play
})
- .catch(err => {
+ .catch((err) => {
console.error("Error playing video:", err);
});
}
@@ -336,19 +341,17 @@ const VideoPlayer: React.FC = ({
Your browser doesn't support HTML5 video.
-
+
{/* iOS First-play indicator - only shown on first visit for iOS devices when not initialized */}
{isIOS && !hasInitialized && !isPlaying && (
-
- Tap Play to initialize video controls
-
+
Tap Play to initialize video controls
)}
-
+
{/* Play/Pause Indicator (shows based on current state) */}
-
-
+
+
{/* Video Controls Overlay */}
{/* Time and Duration */}
@@ -356,47 +359,52 @@ const VideoPlayer: React.FC = ({
{formatTime(currentTime)}/ {formatTime(duration)}
-
+
{/* Progress Bar with enhanced dragging */}
-
-
-
-
+
+
+
{/* Floating time tooltip when dragging */}
{isDraggingProgress && (
-
+
{formatDetailedTime(tooltipTime)}
)}
-
+
{/* Controls - Mute and Fullscreen buttons */}
diff --git a/frontend-tools/video-editor/client/src/hooks/useVideoTrimmer.tsx b/frontend-tools/video-editor/client/src/hooks/useVideoTrimmer.tsx
index e5cf9296..718ff86c 100644
--- a/frontend-tools/video-editor/client/src/hooks/useVideoTrimmer.tsx
+++ b/frontend-tools/video-editor/client/src/hooks/useVideoTrimmer.tsx
@@ -10,7 +10,7 @@ interface EditorState {
trimEnd: number;
splitPoints: number[];
clipSegments: Segment[];
- action?: string;
+ action?: string;
}
const useVideoTrimmer = () => {
@@ -20,89 +20,88 @@ const useVideoTrimmer = () => {
const [duration, setDuration] = useState(0);
const [isPlaying, setIsPlaying] = useState(false);
const [isMuted, setIsMuted] = useState(false);
-
- // Preview mode state for playing only segments
- const [isPreviewMode, setIsPreviewMode] = useState(false);
- const [previewSegmentIndex, setPreviewSegmentIndex] = useState(0);
-
+
// Timeline state
const [thumbnails, setThumbnails] = useState([]);
const [trimStart, setTrimStart] = useState(0);
const [trimEnd, setTrimEnd] = useState(0);
const [splitPoints, setSplitPoints] = useState([]);
const [zoomLevel, setZoomLevel] = useState(1); // Start with 1x zoom level
-
+
// Clip segments state
const [clipSegments, setClipSegments] = useState([]);
-
+
// History state for undo/redo
const [history, setHistory] = useState([]);
const [historyPosition, setHistoryPosition] = useState(-1);
-
+
// Track unsaved changes
const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false);
-
+
// State for playing segments
const [isPlayingSegments, setIsPlayingSegments] = useState(false);
const [currentSegmentIndex, setCurrentSegmentIndex] = useState(0);
-
+
// Monitor for history changes
useEffect(() => {
if (history.length > 0) {
// For debugging - moved to console.debug
- if (process.env.NODE_ENV === 'development') {
- console.debug(`History state updated: ${history.length} entries, position: ${historyPosition}`);
- // Log actions in history to help debug undo/redo
- const actions = history.map((state, idx) =>
- `${idx}: ${state.action || 'unknown'} (segments: ${state.clipSegments.length})`
+ if (process.env.NODE_ENV === "development") {
+ console.debug(
+ `History state updated: ${history.length} entries, position: ${historyPosition}`
);
- console.debug('History actions:', actions);
+ // Log actions in history to help debug undo/redo
+ const actions = history.map(
+ (state, idx) =>
+ `${idx}: ${state.action || "unknown"} (segments: ${state.clipSegments.length})`
+ );
+ console.debug("History actions:", actions);
}
-
+
// If there's at least one history entry and it wasn't a save operation, mark as having unsaved changes
- const lastAction = history[historyPosition]?.action || '';
- if (lastAction !== 'save' && lastAction !== 'save_copy' && lastAction !== 'save_segments') {
+ const lastAction = history[historyPosition]?.action || "";
+ if (lastAction !== "save" && lastAction !== "save_copy" && lastAction !== "save_segments") {
setHasUnsavedChanges(true);
}
}
}, [history, historyPosition]);
-
+
// Set up page unload warning
useEffect(() => {
// Event handler for beforeunload
const handleBeforeUnload = (e: BeforeUnloadEvent) => {
if (hasUnsavedChanges) {
// Standard way of showing a confirmation dialog before leaving
- const message = 'Your edits will get lost if you leave the page. Do you want to continue?';
+ const message = "Your edits will get lost if you leave the page. Do you want to continue?";
e.preventDefault();
e.returnValue = message; // Chrome requires returnValue to be set
return message; // For other browsers
}
};
-
+
// Add event listener
- window.addEventListener('beforeunload', handleBeforeUnload);
-
+ window.addEventListener("beforeunload", handleBeforeUnload);
+
// Clean up
return () => {
- window.removeEventListener('beforeunload', handleBeforeUnload);
+ window.removeEventListener("beforeunload", handleBeforeUnload);
};
}, [hasUnsavedChanges]);
-
+
// Initialize video event listeners
useEffect(() => {
const video = videoRef.current;
if (!video) return;
-
+
const handleLoadedMetadata = () => {
setDuration(video.duration);
setTrimEnd(video.duration);
-
+
// Generate placeholders and create initial segment
const initializeEditor = async () => {
// Generate thumbnail for initial segment
const segmentThumbnail = await generateThumbnail(video, video.duration / 2);
-
+
// Create an initial segment that spans the entire video
const initialSegment: Segment = {
id: 1,
@@ -111,7 +110,7 @@ const useVideoTrimmer = () => {
endTime: video.duration,
thumbnail: segmentThumbnail
};
-
+
// Initialize history state with the full-length segment
const initialState: EditorState = {
trimStart: 0,
@@ -119,79 +118,73 @@ const useVideoTrimmer = () => {
splitPoints: [],
clipSegments: [initialSegment]
};
-
+
setHistory([initialState]);
setHistoryPosition(0);
setClipSegments([initialSegment]);
-
+
// Generate timeline thumbnails
const count = 6;
const interval = video.duration / count;
const placeholders: string[] = [];
-
+
for (let i = 0; i < count; i++) {
const time = interval * i + interval / 2;
const thumbnail = await generateThumbnail(video, time);
placeholders.push(thumbnail);
}
-
+
setThumbnails(placeholders);
};
-
+
initializeEditor();
};
-
+
const handleTimeUpdate = () => {
setCurrentTime(video.currentTime);
};
-
+
const handlePlay = () => {
- // Only update isPlaying if we're not in preview mode
- if (!isPreviewMode) {
- setIsPlaying(true);
- setVideoInitialized(true);
- }
+ setIsPlaying(true);
+ setVideoInitialized(true);
};
-
+
const handlePause = () => {
- // Only update isPlaying if we're not in preview mode
- if (!isPreviewMode) {
- setIsPlaying(false);
- }
+ setIsPlaying(false);
};
-
+
const handleEnded = () => {
setIsPlaying(false);
video.currentTime = trimStart;
};
-
+
// Add event listeners
- video.addEventListener('loadedmetadata', handleLoadedMetadata);
- video.addEventListener('timeupdate', handleTimeUpdate);
- video.addEventListener('play', handlePlay);
- video.addEventListener('pause', handlePause);
- video.addEventListener('ended', handleEnded);
-
+ video.addEventListener("loadedmetadata", handleLoadedMetadata);
+ video.addEventListener("timeupdate", handleTimeUpdate);
+ video.addEventListener("play", handlePlay);
+ video.addEventListener("pause", handlePause);
+ video.addEventListener("ended", handleEnded);
+
return () => {
// Remove event listeners
- video.removeEventListener('loadedmetadata', handleLoadedMetadata);
- video.removeEventListener('timeupdate', handleTimeUpdate);
- video.removeEventListener('play', handlePlay);
- video.removeEventListener('pause', handlePause);
- video.removeEventListener('ended', handleEnded);
+ video.removeEventListener("loadedmetadata", handleLoadedMetadata);
+ video.removeEventListener("timeupdate", handleTimeUpdate);
+ video.removeEventListener("play", handlePlay);
+ video.removeEventListener("pause", handlePause);
+ video.removeEventListener("ended", handleEnded);
};
- }, [isPreviewMode]);
-
+ }, []);
+
// Play/pause video
const playPauseVideo = () => {
const video = videoRef.current;
if (!video) return;
-
+
if (isPlaying) {
video.pause();
} else {
// iOS Safari fix: Use the last seeked position if available
- if (!isPlaying && typeof window !== 'undefined' && window.lastSeekedPosition > 0) {
+ if (!isPlaying && typeof window !== "undefined" && window.lastSeekedPosition > 0) {
// Only apply this if the video is not at the same position already
// This avoids unnecessary seeking which might cause playback issues
if (Math.abs(video.currentTime - window.lastSeekedPosition) > 0.1) {
@@ -202,81 +195,56 @@ const useVideoTrimmer = () => {
else if (video.currentTime >= trimEnd) {
video.currentTime = trimStart;
}
-
- video.play()
+
+ video
+ .play()
.then(() => {
// Play started successfully
// Reset the last seeked position after successfully starting playback
- if (typeof window !== 'undefined') {
+ if (typeof window !== "undefined") {
window.lastSeekedPosition = 0;
}
})
- .catch(err => {
+ .catch((err) => {
console.error("Error starting playback:", err);
setIsPlaying(false); // Reset state if play failed
});
}
};
-
+
// Seek to a specific time
const seekVideo = (time: number) => {
const video = videoRef.current;
if (!video) return;
-
+
// Track if the video was playing before seeking
const wasPlaying = !video.paused;
-
- // Store current preview mode state to preserve it
- const wasInPreviewMode = isPreviewMode;
-
+
// Update the video position
video.currentTime = time;
setCurrentTime(time);
-
+
// Store the position in a global state accessible to iOS Safari
// This ensures when play is pressed later, it remembers the position
- if (typeof window !== 'undefined') {
+ if (typeof window !== "undefined") {
window.lastSeekedPosition = time;
}
-
- // Find segment at this position for preview mode playback
- if (wasInPreviewMode) {
- const segmentAtPosition = clipSegments.find(
- seg => time >= seg.startTime && time <= seg.endTime
- );
-
- if (segmentAtPosition) {
- // Update the active segment index in preview mode
- const orderedSegments = [...clipSegments].sort((a, b) => a.startTime - b.startTime);
- const newSegmentIndex = orderedSegments.findIndex(seg => seg.id === segmentAtPosition.id);
- if (newSegmentIndex !== -1) {
- setPreviewSegmentIndex(newSegmentIndex);
- }
- }
- }
-
- // Resume playback in two scenarios:
- // 1. If it was playing before (regular mode)
- // 2. If we're in preview mode (regardless of previous state)
- if (wasPlaying || wasInPreviewMode) {
- // Ensure preview mode stays on if it was on before
- if (wasInPreviewMode) {
- setIsPreviewMode(true);
- }
-
+
+ // Resume playback if it was playing before
+ if (wasPlaying) {
// Play immediately without delay
- video.play()
+ video
+ .play()
.then(() => {
setIsPlaying(true); // Update state to reflect we're playing
- // "Resumed playback after seeking in " + (wasInPreviewMode ? "preview" : "regular") + " mode"
})
- .catch(err => {
+ .catch((err) => {
console.error("Error resuming playback:", err);
setIsPlaying(false);
});
}
};
-
+
// Save the current state to history with a debounce buffer
// This helps prevent multiple rapid saves for small adjustments
const saveState = (action?: string) => {
@@ -286,51 +254,54 @@ const useVideoTrimmer = () => {
trimEnd,
splitPoints: [...splitPoints],
clipSegments: JSON.parse(JSON.stringify(clipSegments)), // Deep clone to avoid reference issues
- action: action || 'manual_save' // Track the action that triggered this save
+ action: action || "manual_save" // Track the action that triggered this save
};
-
+
// Check if state is significantly different from last saved state
const lastState = history[historyPosition];
-
+
// Helper function to compare segments deeply
const haveSegmentsChanged = () => {
if (!lastState || lastState.clipSegments.length !== newState.clipSegments.length) {
return true; // Different length means significant change
}
-
+
// Compare each segment's start and end times
for (let i = 0; i < newState.clipSegments.length; i++) {
const oldSeg = lastState.clipSegments[i];
const newSeg = newState.clipSegments[i];
-
+
if (!oldSeg || !newSeg) return true;
-
+
// Check if any time values changed by more than 0.001 seconds (1ms)
- if (Math.abs(oldSeg.startTime - newSeg.startTime) > 0.001 ||
- Math.abs(oldSeg.endTime - newSeg.endTime) > 0.001) {
+ if (
+ Math.abs(oldSeg.startTime - newSeg.startTime) > 0.001 ||
+ Math.abs(oldSeg.endTime - newSeg.endTime) > 0.001
+ ) {
return true;
}
}
-
+
return false; // No significant changes found
};
-
- const isSignificantChange = !lastState ||
- lastState.trimStart !== newState.trimStart ||
+
+ const isSignificantChange =
+ !lastState ||
+ lastState.trimStart !== newState.trimStart ||
lastState.trimEnd !== newState.trimEnd ||
lastState.splitPoints.length !== newState.splitPoints.length ||
haveSegmentsChanged();
-
+
// Additionally, check if there's an explicit action from a UI event
const hasExplicitActionFlag = newState.action !== undefined;
-
+
// Only proceed if this is a significant change or if explicitly requested
if (isSignificantChange || hasExplicitActionFlag) {
// Get the current position to avoid closure issues
const currentPosition = historyPosition;
-
+
// Use functional updates to ensure we're working with the latest state
- setHistory(prevHistory => {
+ setHistory((prevHistory) => {
// If we're not at the end of history, truncate
if (currentPosition < prevHistory.length - 1) {
const newHistory = prevHistory.slice(0, currentPosition + 1);
@@ -340,9 +311,9 @@ const useVideoTrimmer = () => {
return [...prevHistory, newState];
}
});
-
+
// Update position using functional update
- setHistoryPosition(prev => {
+ setHistoryPosition((prev) => {
const newPosition = prev + 1;
// "Saved state to history position", newPosition)
return newPosition;
@@ -351,36 +322,36 @@ const useVideoTrimmer = () => {
// logger.debug("Skipped non-significant state save");
}
};
-
+
// Listen for trim handle update events
useEffect(() => {
const handleTrimUpdate = (e: CustomEvent) => {
if (e.detail) {
const { time, isStart, recordHistory, action } = e.detail;
-
+
if (isStart) {
setTrimStart(time);
} else {
setTrimEnd(time);
}
-
+
// Only record in history if explicitly requested
if (recordHistory) {
// Use a small timeout to ensure the state is updated
setTimeout(() => {
- saveState(action || (isStart ? 'adjust_trim_start' : 'adjust_trim_end'));
+ saveState(action || (isStart ? "adjust_trim_start" : "adjust_trim_end"));
}, 10);
}
}
};
-
- document.addEventListener('update-trim', handleTrimUpdate as EventListener);
-
+
+ document.addEventListener("update-trim", handleTrimUpdate as EventListener);
+
return () => {
- document.removeEventListener('update-trim', handleTrimUpdate as EventListener);
+ document.removeEventListener("update-trim", handleTrimUpdate as EventListener);
};
}, []);
-
+
// Listen for segment update events and split-at-time events
useEffect(() => {
const handleUpdateSegments = (e: CustomEvent) => {
@@ -389,14 +360,16 @@ const useVideoTrimmer = () => {
// Default to true to ensure all segment changes are recorded
const isSignificantChange = e.detail.recordHistory !== false;
// Get the action type if provided
- const actionType = e.detail.action || 'update_segments';
-
+ const actionType = e.detail.action || "update_segments";
+
// Log the update details
- logger.debug(`Updating segments with action: ${actionType}, recordHistory: ${isSignificantChange ? "true" : "false"}`);
-
+ logger.debug(
+ `Updating segments with action: ${actionType}, recordHistory: ${isSignificantChange ? "true" : "false"}`
+ );
+
// Update segment state immediately for UI feedback
setClipSegments(e.detail.segments);
-
+
// Always save state to history for non-intermediate actions
if (isSignificantChange) {
// A slight delay helps avoid race conditions but we need to
@@ -404,7 +377,7 @@ const useVideoTrimmer = () => {
setTimeout(() => {
// Deep clone to ensure state is captured correctly
const segmentsClone = JSON.parse(JSON.stringify(e.detail.segments));
-
+
// Create a complete state snapshot
const stateWithAction: EditorState = {
trimStart,
@@ -413,12 +386,12 @@ const useVideoTrimmer = () => {
clipSegments: segmentsClone,
action: actionType // Store the action type in the state
};
-
+
// Get the current history position to ensure we're using the latest value
const currentHistoryPosition = historyPosition;
-
+
// Update history with the functional pattern to avoid stale closure issues
- setHistory(prevHistory => {
+ setHistory((prevHistory) => {
// If we're not at the end of the history, truncate
if (currentHistoryPosition < prevHistory.length - 1) {
const newHistory = prevHistory.slice(0, currentHistoryPosition + 1);
@@ -428,90 +401,95 @@ const useVideoTrimmer = () => {
return [...prevHistory, stateWithAction];
}
});
-
+
// Ensure the historyPosition is updated to the correct position
- setHistoryPosition(prev => {
+ setHistoryPosition((prev) => {
const newPosition = prev + 1;
- logger.debug(`Saved state with action: ${actionType} to history position ${newPosition}`);
+ logger.debug(
+ `Saved state with action: ${actionType} to history position ${newPosition}`
+ );
return newPosition;
});
}, 20); // Slightly increased delay to ensure state updates are complete
} else {
- logger.debug(`Skipped saving state to history for action: ${actionType} (recordHistory=false)`);
+ logger.debug(
+ `Skipped saving state to history for action: ${actionType} (recordHistory=false)`
+ );
}
}
};
-
+
const handleSplitSegment = async (e: Event) => {
const customEvent = e as CustomEvent;
- if (customEvent.detail &&
- typeof customEvent.detail.time === 'number' &&
- typeof customEvent.detail.segmentId === 'number') {
-
+ if (
+ customEvent.detail &&
+ typeof customEvent.detail.time === "number" &&
+ typeof customEvent.detail.segmentId === "number"
+ ) {
// Get the time and segment ID from the event
const timeToSplit = customEvent.detail.time;
const segmentId = customEvent.detail.segmentId;
-
+
// Move the current time to the split position
seekVideo(timeToSplit);
-
+
// Find the segment to split
- const segmentToSplit = clipSegments.find(seg => seg.id === segmentId);
+ const segmentToSplit = clipSegments.find((seg) => seg.id === segmentId);
if (!segmentToSplit) return;
-
+
// Make sure the split point is within the segment
if (timeToSplit <= segmentToSplit.startTime || timeToSplit >= segmentToSplit.endTime) {
return; // Can't split outside segment boundaries
}
-
+
// Create two new segments from the split
const newSegments = [...clipSegments];
-
+
// Remove the original segment
- const segmentIndex = newSegments.findIndex(seg => seg.id === segmentId);
+ const segmentIndex = newSegments.findIndex((seg) => seg.id === segmentId);
if (segmentIndex === -1) return;
-
+
newSegments.splice(segmentIndex, 1);
-
+
// Create first half of the split segment - no thumbnail needed
const firstHalf: Segment = {
id: Date.now(),
name: `${segmentToSplit.name}-A`,
startTime: segmentToSplit.startTime,
endTime: timeToSplit,
- thumbnail: '' // Empty placeholder - we'll use dynamic colors instead
+ thumbnail: "" // Empty placeholder - we'll use dynamic colors instead
};
-
+
// Create second half of the split segment - no thumbnail needed
const secondHalf: Segment = {
id: Date.now() + 1,
name: `${segmentToSplit.name}-B`,
startTime: timeToSplit,
endTime: segmentToSplit.endTime,
- thumbnail: '' // Empty placeholder - we'll use dynamic colors instead
+ thumbnail: "" // Empty placeholder - we'll use dynamic colors instead
};
-
+
// Add the new segments
newSegments.push(firstHalf, secondHalf);
-
+
// Sort segments by start time
newSegments.sort((a, b) => a.startTime - b.startTime);
-
+
// Update state
setClipSegments(newSegments);
- saveState('split_segment');
+ saveState("split_segment");
}
};
-
+
// Handle delete segment event
const handleDeleteSegment = async (e: Event) => {
const customEvent = e as CustomEvent;
- if (customEvent.detail && typeof customEvent.detail.segmentId === 'number') {
+ if (customEvent.detail && typeof customEvent.detail.segmentId === "number") {
const segmentId = customEvent.detail.segmentId;
-
+
// Find and remove the segment
- const newSegments = clipSegments.filter(segment => segment.id !== segmentId);
-
+ const newSegments = clipSegments.filter((segment) => segment.id !== segmentId);
+
if (newSegments.length !== clipSegments.length) {
// If all segments are deleted, create a new full video segment
if (newSegments.length === 0 && videoRef.current) {
@@ -522,9 +500,9 @@ const useVideoTrimmer = () => {
name: "segment",
startTime: 0,
endTime: videoRef.current.duration,
- thumbnail: '' // Empty placeholder - we'll use dynamic colors instead
+ thumbnail: "" // Empty placeholder - we'll use dynamic colors instead
};
-
+
// Reset the trim points as well
setTrimStart(0);
setTrimEnd(videoRef.current.duration);
@@ -534,197 +512,50 @@ const useVideoTrimmer = () => {
// Just update the segments normally
setClipSegments(newSegments);
}
- saveState('delete_segment');
+ saveState("delete_segment");
}
}
};
-
- document.addEventListener('update-segments', handleUpdateSegments as EventListener);
- document.addEventListener('split-segment', handleSplitSegment as EventListener);
- document.addEventListener('delete-segment', handleDeleteSegment as EventListener);
-
+
+ document.addEventListener("update-segments", handleUpdateSegments as EventListener);
+ document.addEventListener("split-segment", handleSplitSegment as EventListener);
+ document.addEventListener("delete-segment", handleDeleteSegment as EventListener);
+
return () => {
- document.removeEventListener('update-segments', handleUpdateSegments as EventListener);
- document.removeEventListener('split-segment', handleSplitSegment as EventListener);
- document.removeEventListener('delete-segment', handleDeleteSegment as EventListener);
+ document.removeEventListener("update-segments", handleUpdateSegments as EventListener);
+ document.removeEventListener("split-segment", handleSplitSegment as EventListener);
+ document.removeEventListener("delete-segment", handleDeleteSegment as EventListener);
};
}, [clipSegments, duration]);
-
- // Preview mode effect to handle playing only segments
- useEffect(() => {
- if (!isPreviewMode || !videoRef.current) return;
- // Sort segments by start time
- const orderedSegments = [...clipSegments].sort((a, b) => a.startTime - b.startTime);
- if (orderedSegments.length === 0) return;
-
- const video = videoRef.current;
-
- // Function to handle segment playback
- const handleSegmentPlayback = () => {
- if (!isPreviewMode || !video) return;
-
- const currentSegment = orderedSegments[previewSegmentIndex];
- if (!currentSegment) return;
-
- const currentTime = video.currentTime;
-
- // If we're before the current segment's start, jump to it
- if (currentTime < currentSegment.startTime) {
- video.currentTime = currentSegment.startTime;
- return;
- }
-
- // If we've reached the end of the current segment
- if (currentTime >= currentSegment.endTime - 0.01) { // Small threshold to ensure smooth transition
- // Move to the next segment if available
- if (previewSegmentIndex < orderedSegments.length - 1) {
- // Play next segment
- const nextSegment = orderedSegments[previewSegmentIndex + 1];
- video.currentTime = nextSegment.startTime;
- setPreviewSegmentIndex(previewSegmentIndex + 1);
-
- logger.debug("Preview: Moving to next segment", {
- from: formatDetailedTime(currentSegment.endTime),
- to: formatDetailedTime(nextSegment.startTime),
- segmentIndex: previewSegmentIndex + 1
- });
-
- } else {
- // Loop back to first segment
- logger.debug("Preview: Looping back to first segment");
- video.currentTime = orderedSegments[0].startTime;
- setPreviewSegmentIndex(0);
- }
-
- // Ensure playback continues
- video.play().catch(err => {
- console.error("Error continuing preview playback:", err);
- });
- }
- };
-
- // Add event listener for timeupdate to check segment boundaries
- video.addEventListener('timeupdate', handleSegmentPlayback);
-
- // Start playing if not already playing
- if (video.paused) {
- video.currentTime = orderedSegments[previewSegmentIndex].startTime;
- video.play().catch(err => {
- console.error("Error starting preview playback:", err);
- });
- }
-
- return () => {
- if (video) {
- video.removeEventListener('timeupdate', handleSegmentPlayback);
- }
- };
- }, [isPreviewMode, previewSegmentIndex, clipSegments]);
-
- // Handle starting preview mode
- const handleStartPreview = () => {
- const video = videoRef.current;
- if (!video || clipSegments.length === 0) return;
-
- // If preview is already active, do nothing
- if (isPreviewMode) {
- return;
- }
-
- // If normal playback is happening, pause it
- if (isPlaying) {
- video.pause();
- setIsPlaying(false);
- }
-
- // Sort segments by start time
- const orderedSegments = [...clipSegments].sort((a, b) => a.startTime - b.startTime);
- if (orderedSegments.length === 0) return;
-
- // Set the preview mode flag
- setIsPreviewMode(true);
- logger.debug("Entering preview mode");
-
- // Set the first segment as the current one in the preview sequence
- setPreviewSegmentIndex(0);
-
- // Move to the start of the first segment
- video.currentTime = orderedSegments[0].startTime;
- };
-
- // Handle playing/stopping preview mode
- const handlePreview = () => {
- const video = videoRef.current;
- if (!video || clipSegments.length === 0) return;
-
- // If preview is already active, turn it off
- if (isPreviewMode) {
- setIsPreviewMode(false);
-
- // Always pause the video when exiting preview mode
- video.pause();
- setIsPlaying(false);
-
- logger.debug("Exiting preview mode - video paused");
- return;
- }
-
- // Sort segments by start time
- const orderedSegments = [...clipSegments].sort((a, b) => a.startTime - b.startTime);
- if (orderedSegments.length === 0) return;
-
- // Set the preview mode flag
- setIsPreviewMode(true);
- logger.debug("Entering preview mode");
-
- // Set the first segment as the current one in the preview sequence
- setPreviewSegmentIndex(0);
-
- // Start preview mode by playing the first segment
- video.currentTime = orderedSegments[0].startTime;
-
- // Start playback
- video.play()
- .then(() => {
- setIsPlaying(true);
- logger.debug("Preview started successfully");
- })
- .catch(err => {
- console.error("Error starting preview:", err);
- setIsPreviewMode(false);
- setIsPlaying(false);
- });
- };
-
// Handle trim start change
const handleTrimStartChange = (time: number) => {
setTrimStart(time);
- saveState('adjust_trim_start');
+ saveState("adjust_trim_start");
};
-
+
// Handle trim end change
const handleTrimEndChange = (time: number) => {
setTrimEnd(time);
- saveState('adjust_trim_end');
+ saveState("adjust_trim_end");
};
-
+
// Handle split at current position
const handleSplit = async () => {
if (!videoRef.current) return;
-
+
// Add current time to split points if not already present
if (!splitPoints.includes(currentTime)) {
const newSplitPoints = [...splitPoints, currentTime].sort((a, b) => a - b);
setSplitPoints(newSplitPoints);
-
+
// Generate segments based on split points
const newSegments: Segment[] = [];
let startTime = 0;
-
+
for (let i = 0; i <= newSplitPoints.length; i++) {
const endTime = i < newSplitPoints.length ? newSplitPoints[i] : duration;
-
+
if (startTime < endTime) {
// No need to generate thumbnails - we'll use dynamic colors
newSegments.push({
@@ -732,51 +563,57 @@ const useVideoTrimmer = () => {
name: `Segment ${i + 1}`,
startTime,
endTime,
- thumbnail: '' // Empty placeholder - we'll use dynamic colors instead
+ thumbnail: "" // Empty placeholder - we'll use dynamic colors instead
});
-
+
startTime = endTime;
}
}
-
+
setClipSegments(newSegments);
- saveState('create_split_points');
+ saveState("create_split_points");
}
};
-
+
// Handle reset of all edits
const handleReset = async () => {
setTrimStart(0);
setTrimEnd(duration);
setSplitPoints([]);
-
+
// Create a new default segment that spans the entire video
if (!videoRef.current) return;
-
+
// No need to generate thumbnails - we'll use dynamic colors
const defaultSegment: Segment = {
id: Date.now(),
name: "segment",
startTime: 0,
endTime: duration,
- thumbnail: '' // Empty placeholder - we'll use dynamic colors instead
+ thumbnail: "" // Empty placeholder - we'll use dynamic colors instead
};
-
+
setClipSegments([defaultSegment]);
- saveState('reset_all');
+ saveState("reset_all");
};
-
+
// Handle undo
const handleUndo = () => {
if (historyPosition > 0) {
const previousState = history[historyPosition - 1];
- logger.debug(`** UNDO ** to position ${historyPosition - 1}, action: ${previousState.action}, segments: ${previousState.clipSegments.length}`);
-
+ logger.debug(
+ `** UNDO ** to position ${historyPosition - 1}, action: ${previousState.action}, segments: ${previousState.clipSegments.length}`
+ );
+
// Log segment details to help debug
- logger.debug("Segment details after undo:", previousState.clipSegments.map(seg =>
- `ID: ${seg.id}, Time: ${formatDetailedTime(seg.startTime)} - ${formatDetailedTime(seg.endTime)}`
- ));
-
+ logger.debug(
+ "Segment details after undo:",
+ previousState.clipSegments.map(
+ (seg) =>
+ `ID: ${seg.id}, Time: ${formatDetailedTime(seg.startTime)} - ${formatDetailedTime(seg.endTime)}`
+ )
+ );
+
// Apply the previous state with deep cloning to avoid reference issues
setTrimStart(previousState.trimStart);
setTrimEnd(previousState.trimEnd);
@@ -787,18 +624,24 @@ const useVideoTrimmer = () => {
logger.debug("Cannot undo: at earliest history position");
}
};
-
+
// Handle redo
const handleRedo = () => {
if (historyPosition < history.length - 1) {
const nextState = history[historyPosition + 1];
- logger.debug(`** REDO ** to position ${historyPosition + 1}, action: ${nextState.action}, segments: ${nextState.clipSegments.length}`);
-
+ logger.debug(
+ `** REDO ** to position ${historyPosition + 1}, action: ${nextState.action}, segments: ${nextState.clipSegments.length}`
+ );
+
// Log segment details to help debug
- logger.debug("Segment details after redo:", nextState.clipSegments.map(seg =>
- `ID: ${seg.id}, Time: ${formatDetailedTime(seg.startTime)} - ${formatDetailedTime(seg.endTime)}`
- ));
-
+ logger.debug(
+ "Segment details after redo:",
+ nextState.clipSegments.map(
+ (seg) =>
+ `ID: ${seg.id}, Time: ${formatDetailedTime(seg.startTime)} - ${formatDetailedTime(seg.endTime)}`
+ )
+ );
+
// Apply the next state with deep cloning to avoid reference issues
setTrimStart(nextState.trimStart);
setTrimEnd(nextState.trimEnd);
@@ -809,161 +652,152 @@ const useVideoTrimmer = () => {
logger.debug("Cannot redo: at latest history position");
}
};
-
+
// Handle zoom level change
const handleZoomChange = (level: number) => {
setZoomLevel(level);
};
-
+
// Handle play/pause of the full video
const handlePlay = () => {
const video = videoRef.current;
if (!video) return;
-
- // If in preview mode, exit it before toggling normal play
- if (isPreviewMode) {
- setIsPreviewMode(false);
- // Don't immediately start playing when exiting preview mode
- // Just update the state and return
- setIsPlaying(false);
- video.pause();
- return;
- }
-
+
if (isPlaying) {
// Pause the video
video.pause();
setIsPlaying(false);
} else {
// iOS Safari fix: Check for lastSeekedPosition
- if (typeof window !== 'undefined' && window.lastSeekedPosition > 0) {
+ if (typeof window !== "undefined" && window.lastSeekedPosition > 0) {
// Only seek if the position is significantly different
if (Math.abs(video.currentTime - window.lastSeekedPosition) > 0.1) {
console.log("handlePlay: Using lastSeekedPosition", window.lastSeekedPosition);
video.currentTime = window.lastSeekedPosition;
}
}
-
+
// Play the video from current position with proper promise handling
- video.play()
+ video
+ .play()
.then(() => {
setIsPlaying(true);
// Reset lastSeekedPosition after successful play
- if (typeof window !== 'undefined') {
+ if (typeof window !== "undefined") {
window.lastSeekedPosition = 0;
}
})
- .catch(err => {
+ .catch((err) => {
console.error("Error playing video:", err);
setIsPlaying(false); // Reset state if play failed
});
}
};
-
+
// Toggle mute state
const toggleMute = () => {
const video = videoRef.current;
if (!video) return;
-
+
video.muted = !video.muted;
setIsMuted(!isMuted);
};
-
+
// Handle save action
const handleSave = () => {
// Sort segments chronologically by start time before saving
const sortedSegments = [...clipSegments].sort((a, b) => a.startTime - b.startTime);
-
+
// Create the JSON data for saving
const saveData = {
type: "save",
- segments: sortedSegments.map(segment => ({
+ segments: sortedSegments.map((segment) => ({
startTime: formatDetailedTime(segment.startTime),
endTime: formatDetailedTime(segment.endTime)
}))
};
-
+
// Display JSON in alert (for demonstration purposes)
- if (process.env.NODE_ENV === 'development') {
+ if (process.env.NODE_ENV === "development") {
console.debug("Saving data:", saveData);
}
-
+
// Mark as saved - no unsaved changes
setHasUnsavedChanges(false);
-
+
// Debug message
- if (process.env.NODE_ENV === 'development') {
+ if (process.env.NODE_ENV === "development") {
console.debug("Changes saved - reset unsaved changes flag");
}
-
+
// Save to history with special "save" action to mark saved state
- saveState('save');
-
+ saveState("save");
+
// In a real implementation, this would make a POST request to save the data
// logger.debug("Save data:", saveData);
};
-
+
// Handle save a copy action
const handleSaveACopy = () => {
// Sort segments chronologically by start time before saving
const sortedSegments = [...clipSegments].sort((a, b) => a.startTime - b.startTime);
-
+
// Create the JSON data for saving as a copy
const saveData = {
type: "save_as_a_copy",
- segments: sortedSegments.map(segment => ({
+ segments: sortedSegments.map((segment) => ({
startTime: formatDetailedTime(segment.startTime),
endTime: formatDetailedTime(segment.endTime)
}))
};
-
+
// Display JSON in alert (for demonstration purposes)
- if (process.env.NODE_ENV === 'development') {
+ if (process.env.NODE_ENV === "development") {
console.debug("Saving data as copy:", saveData);
}
-
+
// Mark as saved - no unsaved changes
setHasUnsavedChanges(false);
-
+
// Debug message
- if (process.env.NODE_ENV === 'development') {
+ if (process.env.NODE_ENV === "development") {
console.debug("Changes saved as copy - reset unsaved changes flag");
}
-
+
// Save to history with special "save_copy" action to mark saved state
- saveState('save_copy');
+ saveState("save_copy");
};
-
+
// Handle save segments individually action
const handleSaveSegments = () => {
// Sort segments chronologically by start time before saving
const sortedSegments = [...clipSegments].sort((a, b) => a.startTime - b.startTime);
-
+
// Create the JSON data for saving individual segments
const saveData = {
type: "save_segments",
- segments: sortedSegments.map(segment => ({
+ segments: sortedSegments.map((segment) => ({
name: segment.name,
startTime: formatDetailedTime(segment.startTime),
endTime: formatDetailedTime(segment.endTime)
}))
};
-
+
// Display JSON in alert (for demonstration purposes)
- if (process.env.NODE_ENV === 'development') {
+ if (process.env.NODE_ENV === "development") {
console.debug("Saving data as segments:", saveData);
}
-
+
// Mark as saved - no unsaved changes
setHasUnsavedChanges(false);
-
+
// Debug message
logger.debug("All segments saved individually - reset unsaved changes flag");
-
+
// Save to history with special "save_segments" action to mark saved state
- saveState('save_segments');
+ saveState("save_segments");
};
-
+
// Handle seeking with mobile check
const handleMobileSafeSeek = (time: number) => {
// Only allow seeking if not on mobile or if video has been played
@@ -971,20 +805,24 @@ const useVideoTrimmer = () => {
seekVideo(time);
}
};
-
+
// Check if device is mobile
- const isMobile = typeof window !== 'undefined' && /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini|Mobile|mobile|CriOS/i.test(navigator.userAgent);
-
+ const isMobile =
+ typeof window !== "undefined" &&
+ /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini|Mobile|mobile|CriOS/i.test(
+ navigator.userAgent
+ );
+
// Add videoInitialized state
const [videoInitialized, setVideoInitialized] = useState(false);
-
+
// Effect to handle segments playback
useEffect(() => {
if (!isPlayingSegments || !videoRef.current) return;
const video = videoRef.current;
const orderedSegments = [...clipSegments].sort((a, b) => a.startTime - b.startTime);
-
+
const handleSegmentsPlayback = () => {
const currentSegment = orderedSegments[currentSegmentIndex];
if (!currentSegment) return;
@@ -1004,11 +842,11 @@ const useVideoTrimmer = () => {
const nextSegment = orderedSegments[currentSegmentIndex + 1];
video.currentTime = nextSegment.startTime;
setCurrentSegmentIndex(currentSegmentIndex + 1);
-
+
// If video is somehow paused, ensure it keeps playing
if (video.paused) {
logger.debug("Ensuring playback continues to next segment");
- video.play().catch(err => {
+ video.play().catch((err) => {
console.error("Error continuing segment playback:", err);
});
}
@@ -1017,12 +855,12 @@ const useVideoTrimmer = () => {
video.pause();
setIsPlayingSegments(false);
setCurrentSegmentIndex(0);
- video.removeEventListener('timeupdate', handleSegmentsPlayback);
+ video.removeEventListener("timeupdate", handleSegmentsPlayback);
}
}
};
- video.addEventListener('timeupdate', handleSegmentsPlayback);
+ video.addEventListener("timeupdate", handleSegmentsPlayback);
// Start playing if not already playing
if (video.paused && orderedSegments.length > 0) {
@@ -1031,7 +869,7 @@ const useVideoTrimmer = () => {
}
return () => {
- video.removeEventListener('timeupdate', handleSegmentsPlayback);
+ video.removeEventListener("timeupdate", handleSegmentsPlayback);
};
}, [isPlayingSegments, currentSegmentIndex, clipSegments]);
@@ -1040,15 +878,20 @@ const useVideoTrimmer = () => {
const handleSegmentIndexUpdate = (event: CustomEvent) => {
const { segmentIndex } = event.detail;
if (isPlayingSegments && segmentIndex !== currentSegmentIndex) {
- logger.debug(`Updating current segment index from ${currentSegmentIndex} to ${segmentIndex}`);
+ logger.debug(
+ `Updating current segment index from ${currentSegmentIndex} to ${segmentIndex}`
+ );
setCurrentSegmentIndex(segmentIndex);
}
};
- document.addEventListener('update-segment-index', handleSegmentIndexUpdate as EventListener);
+ document.addEventListener("update-segment-index", handleSegmentIndexUpdate as EventListener);
return () => {
- document.removeEventListener('update-segment-index', handleSegmentIndexUpdate as EventListener);
+ document.removeEventListener(
+ "update-segment-index",
+ handleSegmentIndexUpdate as EventListener
+ );
};
}, [isPlayingSegments, currentSegmentIndex]);
@@ -1066,28 +909,25 @@ const useVideoTrimmer = () => {
// Start segments playback
setIsPlayingSegments(true);
setCurrentSegmentIndex(0);
-
- // Exit preview mode if active
- if (isPreviewMode) {
- setIsPreviewMode(false);
- }
-
+
+ // Start segments playback
+
// Sort segments by start time
const orderedSegments = [...clipSegments].sort((a, b) => a.startTime - b.startTime);
-
+
// Start from the first segment
video.currentTime = orderedSegments[0].startTime;
-
+
// Start playback with proper error handling
- video.play().catch(err => {
+ video.play().catch((err) => {
console.error("Error starting segments playback:", err);
setIsPlayingSegments(false);
});
-
+
logger.debug("Starting playback of all segments continuously");
}
};
-
+
return {
videoRef,
currentTime,
@@ -1095,7 +935,6 @@ const useVideoTrimmer = () => {
isPlaying,
setIsPlaying,
isMuted,
- isPreviewMode,
isPlayingSegments,
thumbnails,
trimStart,
@@ -1114,7 +953,6 @@ const useVideoTrimmer = () => {
handleReset,
handleUndo,
handleRedo,
- handlePreview,
handlePlaySegments,
toggleMute,
handleSave,
@@ -1122,7 +960,7 @@ const useVideoTrimmer = () => {
handleSaveSegments,
isMobile,
videoInitialized,
- setVideoInitialized,
+ setVideoInitialized
};
};
diff --git a/frontend-tools/video-editor/client/src/index.css b/frontend-tools/video-editor/client/src/index.css
index 71eddbc0..e635a6ae 100644
--- a/frontend-tools/video-editor/client/src/index.css
+++ b/frontend-tools/video-editor/client/src/index.css
@@ -125,13 +125,13 @@
overflow-x: auto;
overflow-y: hidden;
margin-bottom: 0.75rem;
- background-color: #EEE; /* Very light gray background */
+ background-color: #eee; /* Very light gray background */
position: relative;
}
.timeline-container {
position: relative;
- background-color: #EEE; /* Very light gray background */
+ background-color: #eee; /* Very light gray background */
height: 6rem;
width: 100%;
cursor: pointer;
@@ -208,17 +208,27 @@
overflow: hidden;
cursor: grab;
user-select: none;
- transition: box-shadow 0.2s, transform 0.1s;
+ transition:
+ box-shadow 0.2s,
+ transform 0.1s;
/* Original z-index for stacking order based on segment ID */
z-index: 15;
}
/* No background colors for segments, just borders with 2-color scheme */
-.clip-segment:nth-child(odd), .segment-color-1, .segment-color-3, .segment-color-5, .segment-color-7 {
+.clip-segment:nth-child(odd),
+.segment-color-1,
+.segment-color-3,
+.segment-color-5,
+.segment-color-7 {
background-color: transparent;
border: 2px solid rgba(0, 123, 255, 0.9); /* Blue border */
}
-.clip-segment:nth-child(even), .segment-color-2, .segment-color-4, .segment-color-6, .segment-color-8 {
+.clip-segment:nth-child(even),
+.segment-color-2,
+.segment-color-4,
+.segment-color-6,
+.segment-color-8 {
background-color: transparent;
border: 2px solid rgba(108, 117, 125, 0.9); /* Gray border */
}
@@ -315,7 +325,7 @@
input[type="range"] {
-webkit-appearance: none;
height: 6px;
- background: #E0E0E0;
+ background: #e0e0e0;
border-radius: 3px;
}
@@ -350,12 +360,14 @@ input[type="range"]::-webkit-slider-thumb {
z-index: 1000;
opacity: 0;
visibility: hidden;
- transition: opacity 0.2s, visibility 0.2s;
+ transition:
+ opacity 0.2s,
+ visibility 0.2s;
pointer-events: none;
}
[data-tooltip]::after {
- content: '';
+ content: "";
position: absolute;
bottom: 100%;
left: 50%;
@@ -366,7 +378,9 @@ input[type="range"]::-webkit-slider-thumb {
margin-bottom: 0px;
opacity: 0;
visibility: hidden;
- transition: opacity 0.2s, visibility 0.2s;
+ transition:
+ opacity 0.2s,
+ visibility 0.2s;
pointer-events: none;
}
@@ -464,7 +478,7 @@ button[disabled][data-tooltip]::after {
}
.segment-tooltip::after {
- content: '';
+ content: "";
position: absolute;
bottom: -6px;
left: 50%;
@@ -539,7 +553,7 @@ button[disabled][data-tooltip]::after {
}
.empty-space-tooltip::after {
- content: '';
+ content: "";
position: absolute;
bottom: -8px;
left: 50%;
@@ -617,7 +631,9 @@ button[disabled][data-tooltip]::after {
}
/* Save buttons styling */
-.save-button, .save-copy-button, .save-segments-button {
+.save-button,
+.save-copy-button,
+.save-segments-button {
background-color: rgba(0, 123, 255, 0.8);
color: white;
border: none;
@@ -628,7 +644,8 @@ button[disabled][data-tooltip]::after {
transition: background-color 0.2s;
}
-.save-button:hover, .save-copy-button:hover {
+.save-button:hover,
+.save-copy-button:hover {
background-color: rgba(0, 123, 255, 1);
}
@@ -735,7 +752,8 @@ button[disabled][data-tooltip]::after {
font-size: 1.1rem;
}
-.current-time, .duration-time {
+.current-time,
+.duration-time {
white-space: nowrap;
}
@@ -770,7 +788,8 @@ button[disabled][data-tooltip]::after {
gap: 8px;
}
- .save-button, .save-copy-button {
+ .save-button,
+ .save-copy-button {
margin-top: 8px;
width: 100%;
}
diff --git a/frontend-tools/video-editor/client/src/lib/logger.ts b/frontend-tools/video-editor/client/src/lib/logger.ts
index 982655c1..f204c26d 100644
--- a/frontend-tools/video-editor/client/src/lib/logger.ts
+++ b/frontend-tools/video-editor/client/src/lib/logger.ts
@@ -7,25 +7,25 @@ const logger = {
* Logs debug messages only in development environment
*/
debug: (...args: any[]) => {
- if (process.env.NODE_ENV === 'development') {
+ if (process.env.NODE_ENV === "development") {
console.debug(...args);
}
},
-
+
/**
* Always logs error messages
*/
error: (...args: any[]) => console.error(...args),
-
+
/**
* Always logs warning messages
*/
warn: (...args: any[]) => console.warn(...args),
-
+
/**
* Always logs info messages
*/
info: (...args: any[]) => console.info(...args)
};
-export default logger;
\ No newline at end of file
+export default logger;
diff --git a/frontend-tools/video-editor/client/src/lib/queryClient.ts b/frontend-tools/video-editor/client/src/lib/queryClient.ts
index a8b3fc1d..892f099a 100644
--- a/frontend-tools/video-editor/client/src/lib/queryClient.ts
+++ b/frontend-tools/video-editor/client/src/lib/queryClient.ts
@@ -10,13 +10,13 @@ async function throwIfResNotOk(res: Response) {
export async function apiRequest(
method: string,
url: string,
- data?: unknown | undefined,
+ data?: unknown | undefined
): Promise {
const res = await fetch(url, {
method,
headers: data ? { "Content-Type": "application/json" } : {},
body: data ? JSON.stringify(data) : undefined,
- credentials: "include",
+ credentials: "include"
});
await throwIfResNotOk(res);
@@ -24,13 +24,11 @@ export async function apiRequest(
}
type UnauthorizedBehavior = "returnNull" | "throw";
-export const getQueryFn: (options: {
- on401: UnauthorizedBehavior;
-}) => QueryFunction =
+export const getQueryFn: (options: { on401: UnauthorizedBehavior }) => QueryFunction =
({ on401: unauthorizedBehavior }) =>
async ({ queryKey }) => {
const res = await fetch(queryKey[0] as string, {
- credentials: "include",
+ credentials: "include"
});
if (unauthorizedBehavior === "returnNull" && res.status === 401) {
@@ -48,10 +46,10 @@ export const queryClient = new QueryClient({
refetchInterval: false,
refetchOnWindowFocus: false,
staleTime: Infinity,
- retry: false,
+ retry: false
},
mutations: {
- retry: false,
- },
- },
+ retry: false
+ }
+ }
});
diff --git a/frontend-tools/video-editor/client/src/lib/timeUtils.ts b/frontend-tools/video-editor/client/src/lib/timeUtils.ts
index d33862a9..14fef1ba 100644
--- a/frontend-tools/video-editor/client/src/lib/timeUtils.ts
+++ b/frontend-tools/video-editor/client/src/lib/timeUtils.ts
@@ -3,17 +3,17 @@
*/
export const formatDetailedTime = (seconds: number): string => {
if (isNaN(seconds)) return "00:00:00.000";
-
+
const hours = Math.floor(seconds / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
const remainingSeconds = Math.floor(seconds % 60);
const milliseconds = Math.round((seconds % 1) * 1000);
-
+
const formattedHours = String(hours).padStart(2, "0");
const formattedMinutes = String(minutes).padStart(2, "0");
const formattedSeconds = String(remainingSeconds).padStart(2, "0");
const formattedMilliseconds = String(milliseconds).padStart(3, "0");
-
+
return `${formattedHours}:${formattedMinutes}:${formattedSeconds}.${formattedMilliseconds}`;
};
diff --git a/frontend-tools/video-editor/client/src/lib/utils.ts b/frontend-tools/video-editor/client/src/lib/utils.ts
index bd0c391d..a5ef1935 100644
--- a/frontend-tools/video-editor/client/src/lib/utils.ts
+++ b/frontend-tools/video-editor/client/src/lib/utils.ts
@@ -1,6 +1,6 @@
-import { clsx, type ClassValue } from "clsx"
-import { twMerge } from "tailwind-merge"
+import { clsx, type ClassValue } from "clsx";
+import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
- return twMerge(clsx(inputs))
+ return twMerge(clsx(inputs));
}
diff --git a/frontend-tools/video-editor/client/src/lib/videoUtils.ts b/frontend-tools/video-editor/client/src/lib/videoUtils.ts
index affb9d18..0586e031 100644
--- a/frontend-tools/video-editor/client/src/lib/videoUtils.ts
+++ b/frontend-tools/video-editor/client/src/lib/videoUtils.ts
@@ -2,20 +2,17 @@
* Generate a solid color background for a segment
* Returns a CSS color based on the segment position
*/
-export const generateSolidColor = (
- time: number,
- duration: number
-): string => {
+export const generateSolidColor = (time: number, duration: number): string => {
// Use the time position to create different colors
// This gives each segment a different color without needing an image
const position = Math.min(Math.max(time / (duration || 1), 0), 1);
-
+
// Calculate color based on position
// Use an extremely light blue-based color palette
const hue = 210; // Blue base
const saturation = 40 + Math.floor(position * 20); // 40-60% (less saturated)
const lightness = 85 + Math.floor(position * 8); // 85-93% (extremely light)
-
+
return `hsl(${hue}, ${saturation}%, ${lightness}%)`;
};
@@ -24,27 +21,27 @@ export const generateSolidColor = (
* Now returns a data URL for a solid color square instead of a video thumbnail
*/
export const generateThumbnail = async (
- videoElement: HTMLVideoElement,
+ videoElement: HTMLVideoElement,
time: number
): Promise => {
return new Promise((resolve) => {
// Create a small canvas for the solid color
- const canvas = document.createElement('canvas');
+ const canvas = document.createElement("canvas");
canvas.width = 10; // Much smaller - we only need a color
canvas.height = 10;
-
- const ctx = canvas.getContext('2d');
+
+ const ctx = canvas.getContext("2d");
if (ctx) {
// Get the solid color based on time
const color = generateSolidColor(time, videoElement.duration);
-
+
// Fill with solid color
ctx.fillStyle = color;
ctx.fillRect(0, 0, canvas.width, canvas.height);
}
-
+
// Convert to data URL (much smaller now)
- const dataUrl = canvas.toDataURL('image/png', 0.5);
+ const dataUrl = canvas.toDataURL("image/png", 0.5);
resolve(dataUrl);
});
};
diff --git a/frontend-tools/video-editor/client/src/main.tsx b/frontend-tools/video-editor/client/src/main.tsx
index 780c763a..044e1cd2 100644
--- a/frontend-tools/video-editor/client/src/main.tsx
+++ b/frontend-tools/video-editor/client/src/main.tsx
@@ -2,7 +2,7 @@ import { createRoot } from "react-dom/client";
import App from "./App";
import "./index.css";
-if (typeof window !== 'undefined') {
+if (typeof window !== "undefined") {
window.MEDIA_DATA = {
videoUrl: "",
mediaId: ""
@@ -30,8 +30,8 @@ const mountComponents = () => {
}
};
-if (document.readyState === 'loading') {
- document.addEventListener('DOMContentLoaded', mountComponents);
+if (document.readyState === "loading") {
+ document.addEventListener("DOMContentLoaded", mountComponents);
} else {
mountComponents();
-}
\ No newline at end of file
+}
diff --git a/frontend-tools/video-editor/client/src/services/videoApi.ts b/frontend-tools/video-editor/client/src/services/videoApi.ts
index 7e3e5175..88389907 100644
--- a/frontend-tools/video-editor/client/src/services/videoApi.ts
+++ b/frontend-tools/video-editor/client/src/services/videoApi.ts
@@ -4,36 +4,36 @@ interface TrimVideoRequest {
segments: {
startTime: string;
endTime: string;
- name?: string;
+ name?: string;
}[];
saveAsCopy?: boolean;
- saveIndividualSegments?: boolean;
+ saveIndividualSegments?: boolean;
}
interface TrimVideoResponse {
msg: string;
url_redirect: string;
- status?: number; // HTTP status code for success/error
- error?: string; // Error message if status is not 200
+ status?: number; // HTTP status code for success/error
+ error?: string; // Error message if status is not 200
}
// Helper function to simulate delay
-const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
+const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
// For now, we'll use a mock API that returns a promise
// This can be replaced with actual API calls later
export const trimVideo = async (
- mediaId: string,
+ mediaId: string,
data: TrimVideoRequest
): Promise => {
try {
// Attempt the real API call
const response = await fetch(`/api/v1/media/${mediaId}/trim_video`, {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
body: JSON.stringify(data)
});
-
+
if (!response.ok) {
// For error responses, return with error status and message
if (response.status === 400) {
@@ -86,7 +86,7 @@ export const trimVideo = async (
};
}
}
-
+
// Successful response
const jsonResponse = await response.json();
return {
@@ -104,7 +104,7 @@ export const trimVideo = async (
url_redirect: `./view?m=${mediaId}`
};
}
-
+
/* Mock implementation that simulates network latency
return new Promise((resolve) => {
setTimeout(() => {
@@ -115,4 +115,4 @@ export const trimVideo = async (
}, 1500); // Simulate 1.5 second server delay
});
*/
-};
\ No newline at end of file
+};
diff --git a/frontend-tools/video-editor/client/src/styles/ClipSegments.css b/frontend-tools/video-editor/client/src/styles/ClipSegments.css
index 49d55474..3b71925e 100644
--- a/frontend-tools/video-editor/client/src/styles/ClipSegments.css
+++ b/frontend-tools/video-editor/client/src/styles/ClipSegments.css
@@ -4,7 +4,7 @@
[data-tooltip] {
position: relative;
}
-
+
[data-tooltip]:before {
content: attr(data-tooltip);
position: absolute;
@@ -21,13 +21,15 @@
white-space: nowrap;
opacity: 0;
visibility: hidden;
- transition: opacity 0.2s, visibility 0.2s;
+ transition:
+ opacity 0.2s,
+ visibility 0.2s;
z-index: 1000;
pointer-events: none;
}
-
+
[data-tooltip]:after {
- content: '';
+ content: "";
position: absolute;
bottom: 100%;
left: 50%;
@@ -37,17 +39,19 @@
border-color: rgba(0, 0, 0, 0.8) transparent transparent transparent;
opacity: 0;
visibility: hidden;
- transition: opacity 0.2s, visibility 0.2s;
+ transition:
+ opacity 0.2s,
+ visibility 0.2s;
pointer-events: none;
}
-
+
[data-tooltip]:hover:before,
[data-tooltip]:hover:after {
opacity: 1;
visibility: visible;
}
}
-
+
/* Hide button tooltips on touch devices */
@media (pointer: coarse) {
[data-tooltip]:before,
@@ -143,7 +147,9 @@
border-radius: 9999px;
border: none;
cursor: pointer;
- transition: background-color 0.2s, color 0.2s;
+ transition:
+ background-color 0.2s,
+ color 0.2s;
min-width: auto;
&:hover {
@@ -163,12 +169,28 @@
color: rgba(51, 51, 51, 0.7);
}
- .segment-color-1 { background-color: rgba(59, 130, 246, 0.15); }
- .segment-color-2 { background-color: rgba(16, 185, 129, 0.15); }
- .segment-color-3 { background-color: rgba(245, 158, 11, 0.15); }
- .segment-color-4 { background-color: rgba(239, 68, 68, 0.15); }
- .segment-color-5 { background-color: rgba(139, 92, 246, 0.15); }
- .segment-color-6 { background-color: rgba(236, 72, 153, 0.15); }
- .segment-color-7 { background-color: rgba(6, 182, 212, 0.15); }
- .segment-color-8 { background-color: rgba(250, 204, 21, 0.15); }
-}
\ No newline at end of file
+ .segment-color-1 {
+ background-color: rgba(59, 130, 246, 0.15);
+ }
+ .segment-color-2 {
+ background-color: rgba(16, 185, 129, 0.15);
+ }
+ .segment-color-3 {
+ background-color: rgba(245, 158, 11, 0.15);
+ }
+ .segment-color-4 {
+ background-color: rgba(239, 68, 68, 0.15);
+ }
+ .segment-color-5 {
+ background-color: rgba(139, 92, 246, 0.15);
+ }
+ .segment-color-6 {
+ background-color: rgba(236, 72, 153, 0.15);
+ }
+ .segment-color-7 {
+ background-color: rgba(6, 182, 212, 0.15);
+ }
+ .segment-color-8 {
+ background-color: rgba(250, 204, 21, 0.15);
+ }
+}
diff --git a/frontend-tools/video-editor/client/src/styles/EditingTools.css b/frontend-tools/video-editor/client/src/styles/EditingTools.css
index 29cb00a7..06f611bd 100644
--- a/frontend-tools/video-editor/client/src/styles/EditingTools.css
+++ b/frontend-tools/video-editor/client/src/styles/EditingTools.css
@@ -1,11 +1,10 @@
#video-editor-trim-root {
-
/* Tooltip styles - only on desktop where hover is available */
@media (hover: hover) and (pointer: fine) {
[data-tooltip] {
position: relative;
}
-
+
[data-tooltip]:before {
content: attr(data-tooltip);
position: absolute;
@@ -22,13 +21,15 @@
white-space: nowrap;
opacity: 0;
visibility: hidden;
- transition: opacity 0.2s, visibility 0.2s;
+ transition:
+ opacity 0.2s,
+ visibility 0.2s;
z-index: 1000;
pointer-events: none;
}
-
+
[data-tooltip]:after {
- content: '';
+ content: "";
position: absolute;
bottom: 100%;
left: 50%;
@@ -38,17 +39,19 @@
border-color: rgba(0, 0, 0, 0.8) transparent transparent transparent;
opacity: 0;
visibility: hidden;
- transition: opacity 0.2s, visibility 0.2s;
+ transition:
+ opacity 0.2s,
+ visibility 0.2s;
pointer-events: none;
}
-
+
[data-tooltip]:hover:before,
[data-tooltip]:hover:after {
opacity: 1;
visibility: visible;
}
}
-
+
/* Hide button tooltips on touch devices */
@media (pointer: coarse) {
[data-tooltip]:before,
@@ -86,7 +89,7 @@
.full-text {
display: inline;
}
-
+
.short-text {
display: none;
}
@@ -99,20 +102,20 @@
.button-group {
display: flex;
align-items: center;
-
+
&.play-buttons-group {
gap: 0.75rem;
justify-content: flex-start;
flex: 0 0 auto; /* Don't expand to fill space */
}
-
+
&.secondary {
gap: 0.75rem;
align-items: center;
justify-content: flex-end;
margin-left: auto; /* Push to right edge */
}
-
+
button {
display: flex;
align-items: center;
@@ -121,17 +124,16 @@
border: none;
cursor: pointer;
min-width: auto;
-
- /* Disabled hover effect as requested */
+
&:hover:not(:disabled) {
color: inherit;
}
-
+
&:disabled {
opacity: 0.5;
cursor: not-allowed;
}
-
+
svg {
height: 1.25rem;
width: 1.25rem;
@@ -144,10 +146,11 @@
border-right: 1px solid #d1d5db;
height: 1.5rem;
margin: 0 0.5rem;
- }
+ }
/* Style for play buttons with highlight effect */
- .play-button, .preview-button {
+ .play-button,
+ .preview-button {
font-weight: 600;
display: flex;
align-items: center;
@@ -157,13 +160,13 @@
justify-content: center;
font-size: 0.875rem !important;
}
-
+
/* Greyed out play button when segments are playing */
.play-button.greyed-out {
opacity: 0.5;
cursor: not-allowed;
}
-
+
/* Highlighted stop button with blue pulse on small screens */
.segments-button.highlighted-stop {
background-color: rgba(59, 130, 246, 0.1);
@@ -171,7 +174,7 @@
border: 1px solid #3b82f6;
animation: bluePulse 2s infinite;
}
-
+
@keyframes bluePulse {
0% {
box-shadow: 0 0 0 0 rgba(59, 130, 246, 0.4);
@@ -183,9 +186,10 @@
box-shadow: 0 0 0 0 rgba(59, 130, 246, 0);
}
}
-
+
/* Completely disable ALL hover effects for play buttons */
- .play-button:hover:not(:disabled), .preview-button:hover:not(:disabled) {
+ .play-button:hover:not(:disabled),
+ .preview-button:hover:not(:disabled) {
/* Reset everything to prevent any changes */
color: inherit !important;
transform: none !important;
@@ -193,27 +197,15 @@
width: auto !important;
background: none !important;
}
-
- .play-button svg, .preview-button svg {
+
+ .play-button svg,
+ .preview-button svg {
height: 1.5rem;
width: 1.5rem;
/* Make sure SVG scales with the button but doesn't change layout */
flex-shrink: 0;
}
-
- /* Style for the preview mode message that replaces the play button */
- .preview-mode-message {
- display: flex;
- align-items: center;
- background-color: rgba(59, 130, 246, 0.1);
- color: #3b82f6;
- padding: 6px 12px;
- border-radius: 4px;
- font-weight: 600;
- font-size: 0.875rem;
- animation: pulse 2s infinite;
- }
-
+
@keyframes pulse {
0% {
opacity: 0.8;
@@ -225,19 +217,12 @@
opacity: 0.8;
}
}
-
- .preview-mode-message svg {
- height: 1.25rem;
- width: 1.25rem;
- margin-right: 0.5rem;
- color: #3b82f6;
- }
-
+
/* Add responsive button text class */
.button-text {
margin-left: 0.25rem;
}
-
+
/* Media queries for the editing tools */
@media (max-width: 992px) {
/* Hide text for undo/redo buttons on medium screens */
@@ -245,76 +230,77 @@
display: none;
}
}
-
+
@media (max-width: 768px) {
/* Keep all buttons in a single row, make them more compact */
.flex-container.single-row {
justify-content: space-between;
}
-
+
.button-group {
gap: 0.5rem;
}
-
+
/* Keep font size consistent regardless of screen size */
- .preview-button, .play-button {
+ .preview-button,
+ .play-button {
font-size: 0.875rem !important;
}
}
-
+
@media (max-width: 640px) {
/* Prevent container overflow on mobile */
.editing-tools-container {
padding: 0.75rem;
overflow-x: hidden;
}
-
+
/* At this breakpoint, make preview button text shorter */
.preview-button {
min-width: auto;
}
-
+
/* Switch to short text versions */
.full-text {
display: none;
}
-
+
.short-text {
display: inline;
margin-left: 0.15rem;
}
-
+
/* Hide reset text */
.reset-text {
display: none;
}
-
+
/* Ensure buttons stay in correct position */
.button-group.play-buttons-group {
flex: initial;
justify-content: flex-start;
flex-shrink: 0;
}
-
+
.button-group.secondary {
flex: initial;
justify-content: flex-end;
flex-shrink: 0;
}
-
+
/* Reduce button sizes on mobile */
.button-group button {
padding: 0.375rem;
min-width: auto;
}
-
+
.button-group button svg {
height: 1.125rem;
width: 1.125rem;
margin-right: 0.125rem;
}
}
-
+
@media (max-width: 576px) {
/* Keep single row, left-align play buttons, right-align controls */
.flex-container.single-row {
@@ -322,94 +308,88 @@
flex-wrap: nowrap;
gap: 10px;
}
-
+
/* Fix left-align for play buttons */
.button-group.play-buttons-group {
justify-content: flex-start;
flex: 0 0 auto;
}
-
+
/* Fix right-align for editing controls */
.button-group.secondary {
justify-content: flex-end;
margin-left: auto;
}
-
+
/* Reduce button padding to fit more easily */
.button-group button {
padding: 0.25rem;
}
-
- /* Smaller preview mode message */
- .preview-mode-message {
- font-size: 0.8rem;
- padding: 4px 8px;
- }
-
+
.divider {
margin: 0 0.25rem;
}
}
-
+
/* Very small screens - maintain layout but reduce further */
@media (max-width: 480px) {
.editing-tools-container {
padding: 0.5rem;
}
-
+
.flex-container.single-row {
gap: 8px;
}
-
+
.button-group.play-buttons-group,
.button-group.secondary {
gap: 0.25rem;
}
-
+
.divider {
display: none; /* Hide divider on very small screens */
}
-
+
/* Even smaller buttons on very small screens */
.button-group button {
padding: 0.125rem;
}
-
+
.button-group button svg {
height: 1rem;
width: 1rem;
margin-right: 0;
}
-
+
/* Hide all button text on very small screens */
.button-text,
.reset-text {
display: none;
}
}
-
+
/* Portrait orientation specific fixes */
@media (max-width: 640px) and (orientation: portrait) {
.editing-tools-container {
width: 100%;
box-sizing: border-box;
}
-
+
.flex-container.single-row {
width: 100%;
padding: 0;
margin: 0;
}
-
+
/* Ensure button groups don't overflow */
.button-group {
max-width: 50%;
}
-
+
.button-group.play-buttons-group {
max-width: 60%;
}
-
+
.button-group.secondary {
max-width: 40%;
}
diff --git a/frontend-tools/video-editor/client/src/styles/IOSNotification.css b/frontend-tools/video-editor/client/src/styles/IOSNotification.css
index 3a0c9a96..5e3af434 100644
--- a/frontend-tools/video-editor/client/src/styles/IOSNotification.css
+++ b/frontend-tools/video-editor/client/src/styles/IOSNotification.css
@@ -132,7 +132,7 @@
.ios-notification {
padding-top: env(safe-area-inset-top);
}
-
+
.ios-notification-close {
padding: 10px;
}
@@ -143,11 +143,11 @@
.ios-notification-content {
padding: 5px;
}
-
+
.ios-notification-message h3 {
font-size: 15px;
}
-
+
.ios-notification-message p,
.ios-notification-message ol {
font-size: 13px;
@@ -164,4 +164,4 @@ html.ios-device {
html.ios-device .ios-control-btn {
/* Make buttons easier to tap in desktop mode */
min-height: 44px;
-}
\ No newline at end of file
+}
diff --git a/frontend-tools/video-editor/client/src/styles/IOSPlayPrompt.css b/frontend-tools/video-editor/client/src/styles/IOSPlayPrompt.css
index 438cfd4e..9fa7d707 100644
--- a/frontend-tools/video-editor/client/src/styles/IOSPlayPrompt.css
+++ b/frontend-tools/video-editor/client/src/styles/IOSPlayPrompt.css
@@ -93,4 +93,4 @@
/* Extra spacing for mobile */
padding: 14px 25px;
}
-}
\ No newline at end of file
+}
diff --git a/frontend-tools/video-editor/client/src/styles/IOSVideoPlayer.css b/frontend-tools/video-editor/client/src/styles/IOSVideoPlayer.css
index 3b671b34..8d8dbf92 100644
--- a/frontend-tools/video-editor/client/src/styles/IOSVideoPlayer.css
+++ b/frontend-tools/video-editor/client/src/styles/IOSVideoPlayer.css
@@ -36,13 +36,13 @@
.ios-video-player-container video {
max-height: 50vh; /* Use viewport height on iOS */
}
-
+
/* Improve controls visibility on iOS */
video::-webkit-media-controls {
opacity: 1 !important;
visibility: visible !important;
}
-
+
/* Ensure controls don't disappear too quickly */
video::-webkit-media-controls-panel {
transition-duration: 3s !important;
@@ -76,19 +76,19 @@
/* Prevent text selection on buttons */
.no-select {
-webkit-touch-callout: none; /* iOS Safari */
- -webkit-user-select: none; /* Safari */
- -khtml-user-select: none; /* Konqueror HTML */
- -moz-user-select: none; /* Firefox */
- -ms-user-select: none; /* Internet Explorer/Edge */
- user-select: none; /* Non-prefixed version, supported by Chrome and Opera */
+ -webkit-user-select: none; /* Safari */
+ -khtml-user-select: none; /* Konqueror HTML */
+ -moz-user-select: none; /* Firefox */
+ -ms-user-select: none; /* Internet Explorer/Edge */
+ user-select: none; /* Non-prefixed version, supported by Chrome and Opera */
cursor: default;
}
/* Specifically prevent default behavior on fine controls */
-.ios-fine-controls button,
+.ios-fine-controls button,
.ios-external-controls .no-select {
touch-action: manipulation;
-webkit-touch-callout: none;
-webkit-user-select: none;
pointer-events: auto;
-}
\ No newline at end of file
+}
diff --git a/frontend-tools/video-editor/client/src/styles/Modal.css b/frontend-tools/video-editor/client/src/styles/Modal.css
index f5d51349..0d67c342 100644
--- a/frontend-tools/video-editor/client/src/styles/Modal.css
+++ b/frontend-tools/video-editor/client/src/styles/Modal.css
@@ -1,302 +1,306 @@
#video-editor-trim-root {
-.modal-overlay {
- position: fixed;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- background-color: rgba(0, 0, 0, 0.5);
- display: flex;
- align-items: center;
- justify-content: center;
- z-index: 1000;
-}
-
-.modal-container {
- background-color: white;
- border-radius: 8px;
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
- width: 90%;
- max-width: 500px;
- max-height: 90vh;
- overflow-y: auto;
- animation: modal-fade-in 0.3s ease-out;
-}
-
-@keyframes modal-fade-in {
- from {
- opacity: 0;
- transform: translateY(-20px);
+ .modal-overlay {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background-color: rgba(0, 0, 0, 0.5);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ z-index: 1000;
}
- to {
- opacity: 1;
- transform: translateY(0);
- }
-}
-.modal-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- padding: 16px 20px;
- border-bottom: 1px solid #eee;
-}
-
-.modal-title {
- margin: 0;
- font-size: 1.25rem;
- font-weight: 600;
- color: #333;
-}
-
-.modal-close-button {
- background: none;
- border: none;
- cursor: pointer;
- color: #666;
- padding: 4px;
- display: flex;
- align-items: center;
- justify-content: center;
- transition: color 0.2s;
-}
-
-.modal-close-button:hover {
- color: #000;
-}
-
-.modal-content {
- padding: 20px;
- color: #333;
- font-size: 1rem;
- line-height: 1.5;
- max-height: 400px;
- overflow-y: auto;
-}
-
-.modal-actions {
- display: flex;
- justify-content: flex-end;
- padding: 16px 20px;
- border-top: 1px solid #eee;
- gap: 12px;
-}
-
-.modal-button {
- padding: 8px 16px;
- border-radius: 4px;
- font-weight: 500;
- cursor: pointer;
- transition: all 0.2s;
- border: none;
-}
-
-.modal-button-primary {
- background-color: #0066cc;
- color: white;
-}
-
-.modal-button-primary:hover {
- background-color: #0055aa;
-}
-
-.modal-button-secondary {
- background-color: #f0f0f0;
- color: #333;
-}
-
-.modal-button-secondary:hover {
- background-color: #e0e0e0;
-}
-
-.modal-button-danger {
- background-color: #dc3545;
- color: white;
-}
-
-.modal-button-danger:hover {
- background-color: #bd2130;
-}
-
-/* Modal content styles */
-.modal-message {
- margin-bottom: 16px;
- font-size: 1rem;
-}
-
-.text-center {
- text-align: center;
-}
-
-.modal-spinner {
- display: flex;
- align-items: center;
- justify-content: center;
- margin: 20px 0;
-}
-
-.spinner {
- border: 4px solid rgba(0, 0, 0, 0.1);
- border-radius: 50%;
- border-top: 4px solid #0066cc;
- width: 30px;
- height: 30px;
- animation: spin 1s linear infinite;
-}
-
-@keyframes spin {
- 0% { transform: rotate(0deg); }
- 100% { transform: rotate(360deg); }
-}
-
-.modal-success-icon {
- display: flex;
- justify-content: center;
- margin-bottom: 16px;
- color: #28a745;
- font-size: 2rem;
-}
-
-.modal-success-icon svg {
- width: 60px;
- height: 60px;
- color: #4CAF50;
- animation: success-pop 0.5s ease-out;
-}
-
-@keyframes success-pop {
- 0% {
- transform: scale(0);
- opacity: 0;
- }
- 70% {
- transform: scale(1.1);
- opacity: 1;
- }
- 100% {
- transform: scale(1);
- opacity: 1;
- }
-}
-
-.modal-error-icon {
- display: flex;
- justify-content: center;
- margin-bottom: 16px;
- color: #dc3545;
- font-size: 2rem;
-}
-
-.modal-error-icon svg {
- width: 60px;
- height: 60px;
- color: #F44336;
- animation: error-pop 0.5s ease-out;
-}
-
-@keyframes error-pop {
- 0% {
- transform: scale(0);
- opacity: 0;
- }
- 70% {
- transform: scale(1.1);
- opacity: 1;
- }
- 100% {
- transform: scale(1);
- opacity: 1;
- }
-}
-
-.modal-choices {
- display: flex;
- flex-direction: column;
- gap: 10px;
- margin-top: 20px;
-}
-
-.modal-choice-button {
- padding: 12px 16px;
- border: none;
- border-radius: 4px;
- background-color: #0066cc;
- text-align: center;
- cursor: pointer;
- transition: all 0.2s;
- display: flex;
- align-items: center;
- justify-content: center;
- font-weight: 500;
- text-decoration: none;
- color: white;
-}
-
-.modal-choice-button:hover {
- background-color: #0055aa;
- transform: translateY(-1px);
- box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
-}
-
-.modal-choice-button svg {
- margin-right: 8px;
-}
-
-.success-link {
- background-color: #4CAF50;
-}
-
-.success-link:hover {
- background-color: #3d8b40;
-}
-
-.centered-choice {
- margin: 0 auto;
- width: auto;
- min-width: 220px;
- background-color: #0066cc;
- color: white;
-}
-
-.centered-choice:hover {
- background-color: #0055aa;
-}
-
-@media (max-width: 480px) {
.modal-container {
- width: 95%;
+ background-color: white;
+ border-radius: 8px;
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+ width: 90%;
+ max-width: 500px;
+ max-height: 90vh;
+ overflow-y: auto;
+ animation: modal-fade-in 0.3s ease-out;
}
-
+
+ @keyframes modal-fade-in {
+ from {
+ opacity: 0;
+ transform: translateY(-20px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+ }
+
+ .modal-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 16px 20px;
+ border-bottom: 1px solid #eee;
+ }
+
+ .modal-title {
+ margin: 0;
+ font-size: 1.25rem;
+ font-weight: 600;
+ color: #333;
+ }
+
+ .modal-close-button {
+ background: none;
+ border: none;
+ cursor: pointer;
+ color: #666;
+ padding: 4px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ transition: color 0.2s;
+ }
+
+ .modal-close-button:hover {
+ color: #000;
+ }
+
+ .modal-content {
+ padding: 20px;
+ color: #333;
+ font-size: 1rem;
+ line-height: 1.5;
+ max-height: 400px;
+ overflow-y: auto;
+ }
+
.modal-actions {
- flex-direction: column;
+ display: flex;
+ justify-content: flex-end;
+ padding: 16px 20px;
+ border-top: 1px solid #eee;
+ gap: 12px;
}
-
+
.modal-button {
- width: 100%;
+ padding: 8px 16px;
+ border-radius: 4px;
+ font-weight: 500;
+ cursor: pointer;
+ transition: all 0.2s;
+ border: none;
+ }
+
+ .modal-button-primary {
+ background-color: #0066cc;
+ color: white;
+ }
+
+ .modal-button-primary:hover {
+ background-color: #0055aa;
+ }
+
+ .modal-button-secondary {
+ background-color: #f0f0f0;
+ color: #333;
+ }
+
+ .modal-button-secondary:hover {
+ background-color: #e0e0e0;
+ }
+
+ .modal-button-danger {
+ background-color: #dc3545;
+ color: white;
+ }
+
+ .modal-button-danger:hover {
+ background-color: #bd2130;
+ }
+
+ /* Modal content styles */
+ .modal-message {
+ margin-bottom: 16px;
+ font-size: 1rem;
+ }
+
+ .text-center {
+ text-align: center;
+ }
+
+ .modal-spinner {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ margin: 20px 0;
+ }
+
+ .spinner {
+ border: 4px solid rgba(0, 0, 0, 0.1);
+ border-radius: 50%;
+ border-top: 4px solid #0066cc;
+ width: 30px;
+ height: 30px;
+ animation: spin 1s linear infinite;
+ }
+
+ @keyframes spin {
+ 0% {
+ transform: rotate(0deg);
+ }
+ 100% {
+ transform: rotate(360deg);
+ }
+ }
+
+ .modal-success-icon {
+ display: flex;
+ justify-content: center;
+ margin-bottom: 16px;
+ color: #28a745;
+ font-size: 2rem;
+ }
+
+ .modal-success-icon svg {
+ width: 60px;
+ height: 60px;
+ color: #4caf50;
+ animation: success-pop 0.5s ease-out;
+ }
+
+ @keyframes success-pop {
+ 0% {
+ transform: scale(0);
+ opacity: 0;
+ }
+ 70% {
+ transform: scale(1.1);
+ opacity: 1;
+ }
+ 100% {
+ transform: scale(1);
+ opacity: 1;
+ }
+ }
+
+ .modal-error-icon {
+ display: flex;
+ justify-content: center;
+ margin-bottom: 16px;
+ color: #dc3545;
+ font-size: 2rem;
+ }
+
+ .modal-error-icon svg {
+ width: 60px;
+ height: 60px;
+ color: #f44336;
+ animation: error-pop 0.5s ease-out;
+ }
+
+ @keyframes error-pop {
+ 0% {
+ transform: scale(0);
+ opacity: 0;
+ }
+ 70% {
+ transform: scale(1.1);
+ opacity: 1;
+ }
+ 100% {
+ transform: scale(1);
+ opacity: 1;
+ }
+ }
+
+ .modal-choices {
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+ margin-top: 20px;
+ }
+
+ .modal-choice-button {
+ padding: 12px 16px;
+ border: none;
+ border-radius: 4px;
+ background-color: #0066cc;
+ text-align: center;
+ cursor: pointer;
+ transition: all 0.2s;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-weight: 500;
+ text-decoration: none;
+ color: white;
+ }
+
+ .modal-choice-button:hover {
+ background-color: #0055aa;
+ transform: translateY(-1px);
+ box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
+ }
+
+ .modal-choice-button svg {
+ margin-right: 8px;
+ }
+
+ .success-link {
+ background-color: #4caf50;
+ }
+
+ .success-link:hover {
+ background-color: #3d8b40;
+ }
+
+ .centered-choice {
+ margin: 0 auto;
+ width: auto;
+ min-width: 220px;
+ background-color: #0066cc;
+ color: white;
+ }
+
+ .centered-choice:hover {
+ background-color: #0055aa;
+ }
+
+ @media (max-width: 480px) {
+ .modal-container {
+ width: 95%;
+ }
+
+ .modal-actions {
+ flex-direction: column;
+ }
+
+ .modal-button {
+ width: 100%;
+ }
+ }
+
+ .error-message {
+ color: #f44336;
+ font-weight: 500;
+ background-color: rgba(244, 67, 54, 0.1);
+ padding: 10px;
+ border-radius: 4px;
+ border-left: 4px solid #f44336;
+ margin-top: 10px;
+ }
+
+ .redirect-message {
+ margin-top: 20px;
+ color: #555;
+ font-size: 0.95rem;
+ padding: 0;
+ margin: 0;
+ }
+
+ .countdown {
+ font-weight: bold;
+ color: #0066cc;
+ font-size: 1.1rem;
}
}
-
-.error-message {
- color: #F44336;
- font-weight: 500;
- background-color: rgba(244, 67, 54, 0.1);
- padding: 10px;
- border-radius: 4px;
- border-left: 4px solid #F44336;
- margin-top: 10px;
-}
-
-.redirect-message {
- margin-top: 20px;
- color: #555;
- font-size: 0.95rem;
- padding: 0;
- margin: 0;
-}
-
-.countdown {
- font-weight: bold;
- color: #0066cc;
- font-size: 1.1rem;
-}
-}
\ No newline at end of file
diff --git a/frontend-tools/video-editor/client/src/styles/TimelineControls.css b/frontend-tools/video-editor/client/src/styles/TimelineControls.css
index a030b0e9..ac38e61d 100644
--- a/frontend-tools/video-editor/client/src/styles/TimelineControls.css
+++ b/frontend-tools/video-editor/client/src/styles/TimelineControls.css
@@ -56,7 +56,7 @@
.timeline-marker {
position: absolute;
- height: 82px; /* Increased height to extend below timeline */
+ height: 82px; /* Increased height to extend below timeline */
width: 2px;
background-color: #000;
transform: translateX(-50%);
@@ -83,7 +83,7 @@
.timeline-marker-drag {
position: absolute;
- bottom: -12px; /* Changed from -6px to -12px to move it further down */
+ bottom: -12px; /* Changed from -6px to -12px to move it further down */
left: 50%;
transform: translateX(-50%);
width: 16px;
@@ -248,14 +248,14 @@
right: 0;
border-radius: 0 2px 2px 0;
}
-
+
/* Enhanced handles for touch devices */
@media (pointer: coarse) {
.clip-segment-handle {
width: 14px; /* Wider target for touch devices */
background-color: rgba(0, 0, 0, 0.4); /* Darker by default for better visibility */
}
-
+
.clip-segment-handle:after {
content: "";
position: absolute;
@@ -267,15 +267,15 @@
background-color: rgba(255, 255, 255, 0.8);
border-radius: 1px;
}
-
+
.clip-segment-handle.left:after {
box-shadow: -2px 0 0 rgba(0, 0, 0, 0.5);
}
-
+
.clip-segment-handle.right:after {
box-shadow: 2px 0 0 rgba(0, 0, 0, 0.5);
}
-
+
/* Active state for touch feedback */
.clip-segment-handle:active {
background-color: rgba(0, 0, 0, 0.6);
@@ -284,19 +284,19 @@
.timeline-marker {
height: 85px;
}
-
+
.timeline-marker-head {
width: 24px;
height: 24px;
top: -13px;
}
-
+
.timeline-marker-drag {
width: 24px;
height: 24px;
bottom: -18px;
}
-
+
.timeline-marker-head.dragging {
width: 28px;
height: 28px;
@@ -321,7 +321,7 @@
.segment-tooltip:after,
.empty-space-tooltip:after {
- content: '';
+ content: "";
position: absolute;
bottom: -5px;
left: 50%;
@@ -335,7 +335,7 @@
.segment-tooltip:before,
.empty-space-tooltip:before {
- content: '';
+ content: "";
position: absolute;
bottom: -6px;
left: 50%;
@@ -438,7 +438,7 @@
font-size: 0.875rem;
border: none;
cursor: pointer;
- margin-right: 0.50rem;
+ margin-right: 0.5rem;
}
.time-button:hover {
@@ -532,8 +532,8 @@
}
/* General styles for all save buttons */
- .save-button,
- .save-copy-button,
+ .save-button,
+ .save-copy-button,
.save-segments-button {
color: #ffffff;
background: #0066cc;
@@ -548,8 +548,8 @@
}
/* Shared hover effect */
- .save-button:hover,
- .save-copy-button:hover,
+ .save-button:hover,
+ .save-copy-button:hover,
.save-segments-button:hover {
background-color: #0056b3;
}
@@ -561,30 +561,30 @@
justify-content: space-between;
gap: 0.5rem;
}
-
- .save-button,
- .save-copy-button,
+
+ .save-button,
+ .save-copy-button,
.save-segments-button {
flex: 1;
font-size: 0.7rem;
padding: 0.25rem 0.35rem;
}
}
-
+
/* Very small screens - adjust save buttons */
@media (max-width: 480px) {
- .save-button,
- .save-copy-button,
+ .save-button,
+ .save-copy-button,
.save-segments-button {
font-size: 0.675rem;
padding: 0.25rem;
}
-
+
/* Remove margins for controls-right buttons */
.controls-right {
margin: 0;
}
-
+
.controls-right button {
margin: 0;
}
@@ -595,7 +595,7 @@
[data-tooltip] {
position: relative;
}
-
+
[data-tooltip]:before {
content: attr(data-tooltip);
position: absolute;
@@ -612,13 +612,15 @@
white-space: nowrap;
opacity: 0;
visibility: hidden;
- transition: opacity 0.2s, visibility 0.2s;
+ transition:
+ opacity 0.2s,
+ visibility 0.2s;
z-index: 1000;
pointer-events: none;
}
-
+
[data-tooltip]:after {
- content: '';
+ content: "";
position: absolute;
bottom: 100%;
left: 50%;
@@ -628,17 +630,19 @@
border-color: rgba(0, 0, 0, 0.8) transparent transparent transparent;
opacity: 0;
visibility: hidden;
- transition: opacity 0.2s, visibility 0.2s;
+ transition:
+ opacity 0.2s,
+ visibility 0.2s;
pointer-events: none;
}
-
+
[data-tooltip]:hover:before,
[data-tooltip]:hover:after {
opacity: 1;
visibility: visible;
}
}
-
+
/* Hide button tooltips on touch devices */
@media (pointer: coarse) {
[data-tooltip]:before,
@@ -669,27 +673,27 @@
}
.modal-success-icon svg {
- color: #4CAF50;
+ color: #4caf50;
animation: fadeIn 0.5s ease-in-out;
}
.modal-error-icon svg {
- color: #F44336;
+ color: #f44336;
animation: fadeIn 0.5s ease-in-out;
}
.success-link {
- background-color: #4CAF50;
+ background-color: #4caf50;
color: white;
transition: background-color 0.3s;
}
.success-link:hover {
- background-color: #388E3C;
+ background-color: #388e3c;
}
.error-message {
- color: #F44336;
+ color: #f44336;
font-weight: 500;
}
@@ -809,47 +813,18 @@
}
@keyframes pulse {
- 0% { opacity: 0.7; transform: scale(1); }
- 50% { opacity: 1; transform: scale(1.05); }
- 100% { opacity: 0.7; transform: scale(1); }
-}
-
-/* Preview mode styles */
-.preview-mode .tooltip-action-btn {
- opacity: 0.5;
- pointer-events: none;
- cursor: not-allowed;
-}
-
-.preview-mode .tooltip-time-btn {
- opacity: 0.5;
- pointer-events: none;
- cursor: not-allowed;
-}
-
-/* Timeline preview mode styles */
-.timeline-container-card.preview-mode {
- pointer-events: none;
-}
-
-.timeline-container-card.preview-mode .timeline-marker-head,
-.timeline-container-card.preview-mode .timeline-marker-drag,
-.timeline-container-card.preview-mode .clip-segment,
-.timeline-container-card.preview-mode .clip-segment-handle,
-.timeline-container-card.preview-mode .time-button,
-.timeline-container-card.preview-mode .zoom-button,
-.timeline-container-card.preview-mode .save-button,
-.timeline-container-card.preview-mode .save-copy-button,
-.timeline-container-card.preview-mode .save-segments-button {
- opacity: 0.5;
- pointer-events: none;
- cursor: not-allowed;
-}
-
-.timeline-container-card.preview-mode .clip-segment:hover {
- box-shadow: none;
- border-color: rgba(0, 0, 0, 0.15);
- background-color: inherit !important;
+ 0% {
+ opacity: 0.7;
+ transform: scale(1);
+ }
+ 50% {
+ opacity: 1;
+ transform: scale(1.05);
+ }
+ 100% {
+ opacity: 0.7;
+ transform: scale(1);
+ }
}
/* Segments playback mode styles - minimal functional styling */
@@ -858,19 +833,26 @@
cursor: pointer;
}
-.segments-playback-mode .tooltip-action-btn.set-in,
-.segments-playback-mode .tooltip-action-btn.set-out,
-.segments-playback-mode .tooltip-action-btn.play-from-start {
- opacity: 0.5;
- pointer-events: none;
-}
-
.segments-playback-mode .tooltip-action-btn.play,
.segments-playback-mode .tooltip-action-btn.pause {
opacity: 1;
cursor: pointer;
}
+/* During segments playback mode, disable button interactions but keep hover working */
+.segments-playback-mode .tooltip-time-btn[disabled],
+.segments-playback-mode .tooltip-action-btn[disabled] {
+ opacity: 0.5 !important;
+ cursor: not-allowed !important;
+}
+
+/* Ensure disabled buttons still show tooltips on hover */
+.segments-playback-mode [data-tooltip][disabled]:hover:before,
+.segments-playback-mode [data-tooltip][disabled]:hover:after {
+ opacity: 1 !important;
+ visibility: visible !important;
+}
+
/* Show segments playback message */
.segments-playback-message {
display: flex;
@@ -889,4 +871,4 @@
width: 1.25rem;
margin-right: 0.5rem;
color: #3b82f6;
-}
\ No newline at end of file
+}
diff --git a/frontend-tools/video-editor/client/src/styles/TwoRowTooltip.css b/frontend-tools/video-editor/client/src/styles/TwoRowTooltip.css
index 50a58404..74bb2160 100644
--- a/frontend-tools/video-editor/client/src/styles/TwoRowTooltip.css
+++ b/frontend-tools/video-editor/client/src/styles/TwoRowTooltip.css
@@ -23,7 +23,7 @@
}
.tooltip-row:first-child {
- margin-bottom: 6px;
+ margin-bottom: 6px;
}
.tooltip-time-btn {
@@ -56,6 +56,26 @@
overflow: hidden !important;
}
+/* Disabled state for time display */
+.tooltip-time-display.disabled {
+ pointer-events: none !important;
+ cursor: not-allowed !important;
+ opacity: 0.6 !important;
+ user-select: none !important;
+ -webkit-user-select: none !important;
+ -moz-user-select: none !important;
+ -ms-user-select: none !important;
+}
+
+/* Force disabled tooltips to show on hover for better user feedback */
+.tooltip-time-btn.disabled[data-tooltip]:hover:before,
+.tooltip-time-btn.disabled[data-tooltip]:hover:after,
+.tooltip-action-btn.disabled[data-tooltip]:hover:before,
+.tooltip-action-btn.disabled[data-tooltip]:hover:after {
+ opacity: 1 !important;
+ visibility: visible !important;
+}
+
.tooltip-actions {
display: flex;
justify-content: space-between;
@@ -69,13 +89,13 @@
background-color: #f3f4f6;
border: none;
border-radius: 4px;
- padding: 5px;
+ padding: 5px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
color: #4b5563;
- width: 26px;
+ width: 26px;
height: 26px;
min-width: 20px !important;
position: relative; /* Add relative positioning for tooltips */
@@ -100,14 +120,16 @@
white-space: nowrap;
opacity: 0;
visibility: hidden;
- transition: opacity 0.2s, visibility 0.2s;
+ transition:
+ opacity 0.2s,
+ visibility 0.2s;
z-index: 2500; /* High z-index */
pointer-events: none;
}
/* Triangle arrow pointing up to the button */
.tooltip-action-btn[data-tooltip]:after {
- content: '';
+ content: "";
position: absolute;
top: 35px; /* Match the before element */
left: 50%; /* Center horizontally */
@@ -119,7 +141,9 @@
margin-left: 0; /* Reset margin */
opacity: 0;
visibility: hidden;
- transition: opacity 0.2s, visibility 0.2s;
+ transition:
+ opacity 0.2s,
+ visibility 0.2s;
z-index: 2500; /* High z-index */
pointer-events: none;
}
@@ -175,7 +199,7 @@
}
.tooltip-action-btn.play-from-start {
- color: #4f46e5;
+ color: #4f46e5;
}
.tooltip-action-btn.play-from-start:hover {
@@ -194,7 +218,7 @@
padding: 6px 10px;
display: flex;
flex-direction: row;
- color: #10b981;
+ color: #10b981;
}
.tooltip-action-btn.new-segment:hover {
@@ -227,43 +251,80 @@
color: #9ca3af;
}
+/* Ensure pause button is properly styled when disabled */
+.tooltip-action-btn.pause.disabled {
+ color: #9ca3af !important;
+ opacity: 0.5;
+ cursor: not-allowed;
+}
+
+.tooltip-action-btn.pause.disabled:hover {
+ background-color: #f3f4f6 !important;
+ color: #9ca3af !important;
+}
+
+/* Ensure play button is properly styled when disabled */
+.tooltip-action-btn.play.disabled {
+ color: #9ca3af !important;
+ opacity: 0.5;
+ cursor: not-allowed;
+}
+
+.tooltip-action-btn.play.disabled:hover {
+ background-color: #f3f4f6 !important;
+ color: #9ca3af !important;
+}
+
+/* Ensure time adjustment buttons are properly styled when disabled */
+.tooltip-time-btn.disabled {
+ opacity: 0.5 !important;
+ cursor: not-allowed !important;
+ background-color: #f3f4f6 !important;
+ color: #9ca3af !important;
+}
+
+.tooltip-time-btn.disabled:hover {
+ background-color: #f3f4f6 !important;
+ color: #9ca3af !important;
+}
+
/* Additional mobile optimizations */
@media (max-width: 768px) {
.two-row-tooltip {
- padding: 4px;
+ padding: 4px;
}
-
+
.tooltip-row:first-child {
- margin-bottom: 4px;
+ margin-bottom: 4px;
}
-
+
.tooltip-time-btn {
min-width: 20px !important;
font-size: 0.7rem !important;
padding: 3px 6px !important;
}
-
+
.tooltip-time-display {
font-size: 0.8rem !important;
padding: 3px 4px !important;
min-width: 90px !important;
}
-
+
.tooltip-action-btn {
width: 24px;
height: 24px;
padding: 4px;
}
-
+
.tooltip-action-btn.new-segment {
padding: 4px 8px;
}
-
+
.tooltip-action-btn svg {
width: 14px;
height: 14px;
}
-
+
/* Adjust tooltip position for small screens - maintain the same position but adjust size */
.tooltip-action-btn[data-tooltip]:before {
min-width: 100px;
@@ -272,7 +333,7 @@
height: 24px;
top: 33px; /* Maintain the same relative distance on mobile */
}
-
+
.tooltip-action-btn[data-tooltip]:after {
top: 33px; /* Match the tooltip position */
}
diff --git a/frontend-tools/video-editor/client/src/styles/VideoPlayer.css b/frontend-tools/video-editor/client/src/styles/VideoPlayer.css
index bbe84d67..11c7ed82 100644
--- a/frontend-tools/video-editor/client/src/styles/VideoPlayer.css
+++ b/frontend-tools/video-editor/client/src/styles/VideoPlayer.css
@@ -4,7 +4,7 @@
[data-tooltip] {
position: relative;
}
-
+
[data-tooltip]:before {
content: attr(data-tooltip);
position: absolute;
@@ -21,13 +21,15 @@
white-space: nowrap;
opacity: 0;
visibility: hidden;
- transition: opacity 0.2s, visibility 0.2s;
+ transition:
+ opacity 0.2s,
+ visibility 0.2s;
z-index: 1000;
pointer-events: none;
}
-
+
[data-tooltip]:after {
- content: '';
+ content: "";
position: absolute;
bottom: 100%;
left: 50%;
@@ -37,17 +39,19 @@
border-color: rgba(0, 0, 0, 0.8) transparent transparent transparent;
opacity: 0;
visibility: hidden;
- transition: opacity 0.2s, visibility 0.2s;
+ transition:
+ opacity 0.2s,
+ visibility 0.2s;
pointer-events: none;
}
-
+
[data-tooltip]:hover:before,
[data-tooltip]:hover:after {
opacity: 1;
visibility: visible;
}
}
-
+
/* Hide button tooltips on touch devices */
@media (pointer: coarse) {
[data-tooltip]:before,
@@ -71,7 +75,7 @@
-webkit-user-select: none;
user-select: none;
}
-
+
.video-player-container video {
width: 100%;
height: 100%;
@@ -83,7 +87,7 @@
-webkit-user-select: none;
user-select: none;
}
-
+
/* iOS-specific styles */
@supports (-webkit-touch-callout: none) {
.video-player-container video {
@@ -92,7 +96,7 @@
-webkit-touch-callout: none;
}
}
-
+
.play-pause-indicator {
position: absolute;
top: 50%;
@@ -106,19 +110,19 @@
transition: opacity 0.3s;
pointer-events: none;
}
-
+
.video-player-container:hover .play-pause-indicator {
opacity: 1;
}
-
+
.play-pause-indicator::before {
- content: '';
+ content: "";
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
-
+
.play-pause-indicator.play-icon::before {
width: 0;
height: 0;
@@ -127,14 +131,14 @@
border-left: 25px solid white;
margin-left: 3px;
}
-
+
.play-pause-indicator.pause-icon::before {
width: 20px;
height: 25px;
border-left: 6px solid white;
border-right: 6px solid white;
}
-
+
/* iOS First-play indicator */
.ios-first-play-indicator {
position: absolute;
@@ -148,7 +152,7 @@
justify-content: center;
z-index: 10;
}
-
+
.ios-play-message {
color: white;
font-size: 1.2rem;
@@ -158,13 +162,22 @@
border-radius: 0.5rem;
animation: pulse 2s infinite;
}
-
+
@keyframes pulse {
- 0% { opacity: 0.7; transform: scale(1); }
- 50% { opacity: 1; transform: scale(1.05); }
- 100% { opacity: 0.7; transform: scale(1); }
+ 0% {
+ opacity: 0.7;
+ transform: scale(1);
+ }
+ 50% {
+ opacity: 1;
+ transform: scale(1.05);
+ }
+ 100% {
+ opacity: 0.7;
+ transform: scale(1);
+ }
}
-
+
.video-controls {
position: absolute;
bottom: 0;
@@ -175,21 +188,21 @@
opacity: 0;
transition: opacity 0.3s;
}
-
+
.video-player-container:hover .video-controls {
opacity: 1;
}
-
+
.video-current-time {
color: white;
font-size: 0.875rem;
}
-
+
.video-duration {
color: white;
font-size: 0.875rem;
}
-
+
.video-time-display {
display: flex;
justify-content: space-between;
@@ -197,7 +210,7 @@
color: white;
font-size: 0.875rem;
}
-
+
.video-progress {
position: relative;
height: 6px;
@@ -208,11 +221,11 @@
touch-action: none; /* Prevent browser handling of drag gestures */
flex-grow: 1;
}
-
+
.video-progress.dragging {
height: 8px;
}
-
+
.video-progress-fill {
position: absolute;
top: 0;
@@ -222,7 +235,7 @@
border-radius: 3px;
pointer-events: none;
}
-
+
.video-scrubber {
position: absolute;
top: 50%;
@@ -232,9 +245,12 @@
background-color: #ff0000;
border-radius: 50%;
cursor: grab;
- transition: transform 0.1s ease, width 0.1s ease, height 0.1s ease;
+ transition:
+ transform 0.1s ease,
+ width 0.1s ease,
+ height 0.1s ease;
}
-
+
/* Make the scrubber larger when dragging for better control */
.video-progress.dragging .video-scrubber {
transform: translate(-50%, -50%) scale(1.2);
@@ -243,22 +259,22 @@
cursor: grabbing;
box-shadow: 0 0 8px rgba(255, 0, 0, 0.6);
}
-
+
/* Enhance for touch devices */
@media (pointer: coarse) {
.video-scrubber {
width: 20px;
height: 20px;
}
-
+
.video-progress.dragging .video-scrubber {
width: 24px;
height: 24px;
}
-
+
/* Create a larger invisible touch target */
.video-scrubber:before {
- content: '';
+ content: "";
position: absolute;
top: -10px;
left: -10px;
@@ -266,14 +282,14 @@
bottom: -10px;
}
}
-
+
.video-controls-buttons {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 0.75rem;
}
-
+
.mute-button,
.fullscreen-button {
min-width: auto;
@@ -283,17 +299,17 @@
cursor: pointer;
padding: 0.25rem;
transition: transform 0.2s;
-
+
&:hover {
transform: scale(1.1);
}
-
+
svg {
width: 1.25rem;
height: 1.25rem;
}
}
-
+
/* Time tooltip that appears when dragging */
.video-time-tooltip {
position: absolute;
@@ -309,10 +325,10 @@
white-space: nowrap;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
}
-
+
/* Add a small arrow to the tooltip */
.video-time-tooltip:after {
- content: '';
+ content: "";
position: absolute;
bottom: -4px;
left: 50%;
@@ -323,4 +339,4 @@
border-right: 4px solid transparent;
border-top: 4px solid rgba(0, 0, 0, 0.7);
}
-}
\ No newline at end of file
+}
diff --git a/frontend-tools/video-editor/package.json b/frontend-tools/video-editor/package.json
index f0ccee76..6da3b785 100644
--- a/frontend-tools/video-editor/package.json
+++ b/frontend-tools/video-editor/package.json
@@ -7,7 +7,8 @@
"dev": "vite",
"start": "NODE_ENV=production node dist/index.js",
"check": "tsc",
- "build:django": "vite build --config vite.video-editor.config.ts --outDir ../../../static/video_editor"
+ "build:django": "vite build --config vite.video-editor.config.ts --outDir ../../../static/video_editor",
+ "format": "npx prettier --write client/src/**/*.{ts,tsx,css}"
},
"dependencies": {
"@tanstack/react-query": "^5.74.4",
@@ -35,6 +36,7 @@
"autoprefixer": "^10.4.20",
"esbuild": "^0.25.0",
"postcss": "^8.4.47",
+ "prettier": "^3.6.0",
"tailwindcss": "^3.4.17",
"typescript": "^5.8.3",
"vite": "^5.4.18"
diff --git a/frontend-tools/video-editor/yarn.lock b/frontend-tools/video-editor/yarn.lock
index 1ac7c43e..b4347cd5 100644
--- a/frontend-tools/video-editor/yarn.lock
+++ b/frontend-tools/video-editor/yarn.lock
@@ -1834,6 +1834,11 @@ postcss@^8.4.43, postcss@^8.4.47:
picocolors "^1.1.1"
source-map-js "^1.2.1"
+prettier@^3.6.0:
+ version "3.6.0"
+ resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.6.0.tgz#18ec98d62cb0757a5d4eab40253ff3e6d0fc8dea"
+ integrity sha512-ujSB9uXHJKzM/2GBuE0hBOUgC77CN3Bnpqa+g80bkv3T3A93wL/xlzDATHhnhkzifz/UE2SNOvmbTz5hSkDlHw==
+
proxy-addr@~2.0.7:
version "2.0.7"
resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025"
@@ -2087,6 +2092,7 @@ statuses@2.0.1:
integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==
"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0:
+ name string-width-cjs
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@@ -2105,6 +2111,7 @@ string-width@^5.0.1, string-width@^5.1.2:
strip-ansi "^7.0.1"
"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
+ name strip-ansi-cjs
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
diff --git a/static/video_editor/video-editor.css b/static/video_editor/video-editor.css
index 0187b3b9..0b913b92 100644
--- a/static/video_editor/video-editor.css
+++ b/static/video_editor/video-editor.css
@@ -1 +1 @@
-#video-editor-trim-root{@keyframes pulse{0%{opacity:.7;transform:scale(1)}50%{opacity:1;transform:scale(1.05)}to{opacity:.7;transform:scale(1)}}}#video-editor-trim-root .video-player-container{position:relative;width:100%;background:#000;border-radius:.5rem;overflow:hidden;margin-bottom:1rem;aspect-ratio:16/9;-webkit-user-select:none;-moz-user-select:none;user-select:none}#video-editor-trim-root .video-player-container video{width:100%;height:100%;cursor:pointer;transform:translateZ(0);-webkit-transform:translateZ(0);-webkit-user-select:none;-moz-user-select:none;user-select:none}@supports (-webkit-touch-callout: none){#video-editor-trim-root .video-player-container video{-webkit-tap-highlight-color:transparent;-webkit-touch-callout:none}}#video-editor-trim-root .play-pause-indicator{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:60px;height:60px;background-color:#0009;border-radius:50%;opacity:0;transition:opacity .3s;pointer-events:none}#video-editor-trim-root .video-player-container:hover .play-pause-indicator{opacity:1}#video-editor-trim-root .play-pause-indicator:before{content:"";position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)}#video-editor-trim-root .play-pause-indicator.play-icon:before{width:0;height:0;border-top:15px solid transparent;border-bottom:15px solid transparent;border-left:25px solid white;margin-left:3px}#video-editor-trim-root .play-pause-indicator.pause-icon:before{width:20px;height:25px;border-left:6px solid white;border-right:6px solid white}#video-editor-trim-root .ios-first-play-indicator{position:absolute;top:0;left:0;width:100%;height:100%;background:#000000b3;display:flex;align-items:center;justify-content:center;z-index:10}#video-editor-trim-root .ios-play-message{color:#fff;font-size:1.2rem;text-align:center;padding:1rem;background:#000c;border-radius:.5rem;animation:pulse 2s infinite}#video-editor-trim-root .video-controls{position:absolute;bottom:0;left:0;right:0;padding:.75rem;background:linear-gradient(transparent,#000000b3);opacity:0;transition:opacity .3s}#video-editor-trim-root .video-player-container:hover .video-controls{opacity:1}#video-editor-trim-root .video-current-time,#video-editor-trim-root .video-duration{color:#fff;font-size:.875rem}#video-editor-trim-root .video-time-display{display:flex;justify-content:space-between;margin-bottom:.5rem;color:#fff;font-size:.875rem}#video-editor-trim-root .video-progress{position:relative;height:6px;background-color:#ffffff4d;border-radius:3px;cursor:pointer;margin:0 10px;touch-action:none;flex-grow:1}#video-editor-trim-root .video-progress.dragging{height:8px}#video-editor-trim-root .video-progress-fill{position:absolute;top:0;left:0;height:100%;background-color:red;border-radius:3px;pointer-events:none}#video-editor-trim-root .video-scrubber{position:absolute;top:50%;transform:translate(-50%,-50%);width:16px;height:16px;background-color:red;border-radius:50%;cursor:grab;transition:transform .1s ease,width .1s ease,height .1s ease}#video-editor-trim-root .video-progress.dragging .video-scrubber{transform:translate(-50%,-50%) scale(1.2);width:18px;height:18px;cursor:grabbing;box-shadow:0 0 8px #f009}@media (pointer: coarse){#video-editor-trim-root .video-scrubber{width:20px;height:20px}#video-editor-trim-root .video-progress.dragging .video-scrubber{width:24px;height:24px}#video-editor-trim-root .video-scrubber:before{content:"";position:absolute;top:-10px;left:-10px;right:-10px;bottom:-10px}}#video-editor-trim-root .video-controls-buttons{display:flex;align-items:center;justify-content:flex-end;gap:.75rem}#video-editor-trim-root .mute-button,#video-editor-trim-root .fullscreen-button{min-width:auto;color:#fff;background:none;border:none;cursor:pointer;padding:.25rem;transition:transform .2s}#video-editor-trim-root .mute-button:hover,#video-editor-trim-root .fullscreen-button:hover{transform:scale(1.1)}#video-editor-trim-root .mute-button svg,#video-editor-trim-root .fullscreen-button svg{width:1.25rem;height:1.25rem}#video-editor-trim-root .video-time-tooltip{position:absolute;top:-30px;background-color:#000000b3;color:#fff;padding:4px 8px;border-radius:4px;font-size:12px;font-family:monospace;pointer-events:none;z-index:1000;white-space:nowrap;box-shadow:0 2px 4px #0000004d}#video-editor-trim-root .video-time-tooltip:after{content:"";position:absolute;bottom:-4px;left:50%;transform:translate(-50%);width:0;height:0;border-left:4px solid transparent;border-right:4px solid transparent;border-top:4px solid rgba(0,0,0,.7)}#video-editor-trim-root{@keyframes modal-fade-in{0%{opacity:0;transform:translateY(-20px)}to{opacity:1;transform:translateY(0)}}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}@keyframes success-pop{0%{transform:scale(0);opacity:0}70%{transform:scale(1.1);opacity:1}to{transform:scale(1);opacity:1}}@keyframes error-pop{0%{transform:scale(0);opacity:0}70%{transform:scale(1.1);opacity:1}to{transform:scale(1);opacity:1}}}#video-editor-trim-root .modal-overlay{position:fixed;top:0;left:0;right:0;bottom:0;background-color:#00000080;display:flex;align-items:center;justify-content:center;z-index:1000}#video-editor-trim-root .modal-container{background-color:#fff;border-radius:8px;box-shadow:0 4px 12px #00000026;width:90%;max-width:500px;max-height:90vh;overflow-y:auto;animation:modal-fade-in .3s ease-out}#video-editor-trim-root .modal-header{display:flex;justify-content:space-between;align-items:center;padding:16px 20px;border-bottom:1px solid #eee}#video-editor-trim-root .modal-title{margin:0;font-size:1.25rem;font-weight:600;color:#333}#video-editor-trim-root .modal-close-button{background:none;border:none;cursor:pointer;color:#666;padding:4px;display:flex;align-items:center;justify-content:center;transition:color .2s}#video-editor-trim-root .modal-close-button:hover{color:#000}#video-editor-trim-root .modal-content{padding:20px;color:#333;font-size:1rem;line-height:1.5;max-height:400px;overflow-y:auto}#video-editor-trim-root .modal-actions{display:flex;justify-content:flex-end;padding:16px 20px;border-top:1px solid #eee;gap:12px}#video-editor-trim-root .modal-button{padding:8px 16px;border-radius:4px;font-weight:500;cursor:pointer;transition:all .2s;border:none}#video-editor-trim-root .modal-button-primary{background-color:#06c;color:#fff}#video-editor-trim-root .modal-button-primary:hover{background-color:#05a}#video-editor-trim-root .modal-button-secondary{background-color:#f0f0f0;color:#333}#video-editor-trim-root .modal-button-secondary:hover{background-color:#e0e0e0}#video-editor-trim-root .modal-button-danger{background-color:#dc3545;color:#fff}#video-editor-trim-root .modal-button-danger:hover{background-color:#bd2130}#video-editor-trim-root .modal-message{margin-bottom:16px;font-size:1rem}#video-editor-trim-root .modal-spinner{display:flex;align-items:center;justify-content:center;margin:20px 0}#video-editor-trim-root .spinner{border:4px solid rgba(0,0,0,.1);border-radius:50%;border-top:4px solid #0066cc;width:30px;height:30px;animation:spin 1s linear infinite}#video-editor-trim-root .modal-success-icon{display:flex;justify-content:center;margin-bottom:16px;color:#28a745;font-size:2rem}#video-editor-trim-root .modal-success-icon svg{width:60px;height:60px;color:#4caf50;animation:success-pop .5s ease-out}#video-editor-trim-root .modal-error-icon{display:flex;justify-content:center;margin-bottom:16px;color:#dc3545;font-size:2rem}#video-editor-trim-root .modal-error-icon svg{width:60px;height:60px;color:#f44336;animation:error-pop .5s ease-out}#video-editor-trim-root .modal-choices{display:flex;flex-direction:column;gap:10px;margin-top:20px}#video-editor-trim-root .modal-choice-button{padding:12px 16px;border:none;border-radius:4px;background-color:#06c;text-align:center;cursor:pointer;transition:all .2s;display:flex;align-items:center;justify-content:center;font-weight:500;text-decoration:none;color:#fff}#video-editor-trim-root .modal-choice-button:hover{background-color:#05a;transform:translateY(-1px);box-shadow:0 2px 5px #0000001a}#video-editor-trim-root .modal-choice-button svg{margin-right:8px}#video-editor-trim-root .success-link{background-color:#4caf50}#video-editor-trim-root .success-link:hover{background-color:#3d8b40}#video-editor-trim-root .centered-choice{margin:0 auto;width:auto;min-width:220px;background-color:#06c;color:#fff}#video-editor-trim-root .centered-choice:hover{background-color:#05a}@media (max-width: 480px){#video-editor-trim-root .modal-container{width:95%}#video-editor-trim-root .modal-actions{flex-direction:column}#video-editor-trim-root .modal-button{width:100%}}#video-editor-trim-root .error-message{color:#f44336;font-weight:500;background-color:#f443361a;padding:10px;border-radius:4px;border-left:4px solid #F44336;margin-top:10px}#video-editor-trim-root .redirect-message{color:#555;font-size:.95rem;padding:0;margin:0}#video-editor-trim-root .countdown{font-weight:700;color:#06c;font-size:1.1rem}#video-editor-trim-root{@keyframes spin{to{transform:rotate(360deg)}}@keyframes fadeIn{0%{opacity:0;transform:scale(.9)}to{opacity:1;transform:scale(1)}}}#video-editor-trim-root .timeline-container-card{background-color:#fff;border-radius:.5rem;padding:1rem;box-shadow:0 1px 2px #0000000d}#video-editor-trim-root .timeline-header{margin-bottom:.75rem;display:flex;justify-content:space-between;align-items:center}#video-editor-trim-root .timeline-title{font-size:.875rem;font-weight:500;color:var(--foreground, #333)}#video-editor-trim-root .timeline-title-text{font-weight:700}#video-editor-trim-root .current-time{font-size:.875rem;color:var(--foreground, #333)}#video-editor-trim-root .time-code{font-family:monospace;background-color:#f3f4f6;padding:0 .5rem;border-radius:.25rem}#video-editor-trim-root .duration-time{font-size:.875rem;color:var(--foreground, #333)}#video-editor-trim-root .timeline-scroll-container{position:relative;overflow:visible!important}#video-editor-trim-root .timeline-container{position:relative;min-width:100%;background-color:#fafbfc;height:70px;border-radius:.25rem;overflow:visible!important}#video-editor-trim-root .timeline-marker{position:absolute;height:82px;width:2px;background-color:#000;transform:translate(-50%);z-index:50;pointer-events:none}#video-editor-trim-root .timeline-marker-head{position:absolute;top:-6px;left:50%;transform:translate(-50%);width:16px;height:16px;background-color:#ef4444;border-radius:50%;cursor:pointer;display:flex;align-items:center;justify-content:center;pointer-events:auto;z-index:51}#video-editor-trim-root .timeline-marker-drag{position:absolute;bottom:-12px;left:50%;transform:translate(-50%);width:16px;height:16px;background-color:#4b5563;border-radius:50%;cursor:grab;display:flex;align-items:center;justify-content:center;pointer-events:auto;z-index:51}#video-editor-trim-root .timeline-marker-drag.dragging{cursor:grabbing;background-color:#374151}#video-editor-trim-root .timeline-marker-head-icon{color:#fff;font-size:14px;font-weight:700;line-height:1;-webkit-user-select:none;-moz-user-select:none;user-select:none}#video-editor-trim-root .timeline-marker-drag-icon{color:#fff;font-size:12px;line-height:1;-webkit-user-select:none;-moz-user-select:none;user-select:none;transform:rotate(90deg);display:inline-block}#video-editor-trim-root .trim-line-marker{position:absolute;top:0;bottom:0;width:1px;background-color:#00000080;z-index:20}#video-editor-trim-root .trim-handle{position:absolute;width:10px;height:20px;background-color:#000;cursor:ew-resize}#video-editor-trim-root .trim-handle.left{right:0;top:10px;border-radius:3px 0 0 3px}#video-editor-trim-root .trim-handle.right{left:0;top:10px;border-radius:0 3px 3px 0}#video-editor-trim-root .timeline-thumbnail{display:inline-block;height:70px;border-right:1px solid rgba(0,0,0,.03)}#video-editor-trim-root .split-point{position:absolute;top:0;bottom:0;width:1px;background-color:#ff000080;z-index:15}#video-editor-trim-root .clip-segment{position:absolute;height:70px;border-radius:4px;z-index:10;border:2px solid rgba(0,0,0,.15);cursor:pointer}#video-editor-trim-root .clip-segment:hover{box-shadow:0 0 0 2px #0000004d;border-color:#0006;background-color:#f0f0f0cc!important}#video-editor-trim-root .clip-segment.selected{box-shadow:0 0 0 2px #3b82f6b3;border-color:#3b82f6e6}#video-editor-trim-root .clip-segment.selected:hover{background-color:#f0f8ffd9!important}#video-editor-trim-root .clip-segment-info{position:absolute;bottom:0;left:0;right:0;padding:.4rem;background-color:#0006;color:#fff;opacity:1;transition:background-color .2s;line-height:1.3}#video-editor-trim-root .clip-segment:hover .clip-segment-info{background-color:#00000080}#video-editor-trim-root .clip-segment.selected .clip-segment-info{background-color:#3b82f680}#video-editor-trim-root .clip-segment.selected:hover .clip-segment-info{background-color:#3b82f666}#video-editor-trim-root .clip-segment-name{font-weight:700;font-size:12px}#video-editor-trim-root .clip-segment-time,#video-editor-trim-root .clip-segment-duration{font-size:10px}#video-editor-trim-root .clip-segment-handle{position:absolute;top:0;bottom:0;width:6px;background-color:#0003;cursor:ew-resize}#video-editor-trim-root .clip-segment-handle:hover{background-color:#0006}#video-editor-trim-root .clip-segment-handle.left{left:0;border-radius:2px 0 0 2px}#video-editor-trim-root .clip-segment-handle.right{right:0;border-radius:0 2px 2px 0}@media (pointer: coarse){#video-editor-trim-root .clip-segment-handle{width:14px;background-color:#0006}#video-editor-trim-root .clip-segment-handle:after{content:"";position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:2px;height:20px;background-color:#fffc;border-radius:1px}#video-editor-trim-root .clip-segment-handle.left:after{box-shadow:-2px 0 #00000080}#video-editor-trim-root .clip-segment-handle.right:after{box-shadow:2px 0 #00000080}#video-editor-trim-root .clip-segment-handle:active{background-color:#0009}#video-editor-trim-root .timeline-marker{height:85px}#video-editor-trim-root .timeline-marker-head{width:24px;height:24px;top:-13px}#video-editor-trim-root .timeline-marker-drag{width:24px;height:24px;bottom:-18px}#video-editor-trim-root .timeline-marker-head.dragging{width:28px;height:28px;top:-15px}}#video-editor-trim-root .segment-tooltip,#video-editor-trim-root .empty-space-tooltip{position:absolute;background-color:#fff;border-radius:4px;box-shadow:0 2px 8px #0000004d;padding:.5rem;z-index:1000;min-width:150px;text-align:center;pointer-events:auto;top:-100px!important;transform:translateY(-10px)}#video-editor-trim-root .segment-tooltip:after,#video-editor-trim-root .empty-space-tooltip:after{content:"";position:absolute;bottom:-5px;left:50%;transform:translate(-50%);width:0;height:0;border-left:5px solid transparent;border-right:5px solid transparent;border-top:5px solid white}#video-editor-trim-root .segment-tooltip:before,#video-editor-trim-root .empty-space-tooltip:before{content:"";position:absolute;bottom:-6px;left:50%;transform:translate(-50%);width:0;height:0;border-left:6px solid transparent;border-right:6px solid transparent;border-top:6px solid rgba(0,0,0,.1);z-index:-1}#video-editor-trim-root .tooltip-time{font-weight:600;font-size:.875rem;margin-bottom:.5rem;color:#333}#video-editor-trim-root .tooltip-actions{display:flex;justify-content:center;gap:.5rem}#video-editor-trim-root .tooltip-action-btn{background-color:#f3f4f6;border:none;border-radius:.25rem;padding:.375rem;display:flex;align-items:center;justify-content:center;cursor:pointer;color:#4b5563;min-width:20px!important}#video-editor-trim-root .tooltip-action-btn:hover{background-color:#e5e7eb;color:#111827}#video-editor-trim-root .tooltip-action-btn.delete{color:#ef4444}#video-editor-trim-root .tooltip-action-btn.delete:hover{background-color:#fee2e2}#video-editor-trim-root .tooltip-action-btn.new-segment{padding:.375rem .5rem}#video-editor-trim-root .tooltip-action-btn.new-segment .tooltip-btn-text{margin-left:.25rem;font-size:.75rem}#video-editor-trim-root .tooltip-action-btn svg{width:1rem;height:1rem}#video-editor-trim-root .timeline-controls{display:flex;align-items:center;justify-content:space-between;margin-top:.75rem}#video-editor-trim-root .time-navigation{display:none;align-items:center;gap:.5rem}#video-editor-trim-root .time-nav-label{font-size:.875rem;font-weight:500}#video-editor-trim-root .time-input{border:1px solid #d1d5db;border-radius:.25rem;padding:.25rem .5rem;width:8rem;font-size:.875rem}#video-editor-trim-root .time-button-group{display:flex}#video-editor-trim-root .time-button{background-color:#e5e7eb;color:#000;padding:.25rem .5rem;font-size:.875rem;border:none;cursor:pointer;margin-right:.5rem}#video-editor-trim-root .time-button:hover{background-color:#d1d5db}#video-editor-trim-root .time-button:first-child{border-top-left-radius:.25rem;border-bottom-left-radius:.25rem}#video-editor-trim-root .time-button:last-child{border-top-right-radius:.25rem;border-bottom-right-radius:.25rem}#video-editor-trim-root .controls-right{display:flex;align-items:center;gap:.5rem;margin-left:auto}#video-editor-trim-root .zoom-dropdown-container{position:relative;z-index:100;display:none}#video-editor-trim-root .zoom-button{background-color:#374151;color:#fff;border:none;border-radius:.25rem;padding:.25rem .75rem;font-size:.875rem;display:flex;align-items:center;cursor:pointer}#video-editor-trim-root .zoom-button:hover{background-color:#1f2937}#video-editor-trim-root .zoom-button svg{margin-left:.25rem}#video-editor-trim-root .zoom-dropdown{position:absolute;top:100%;left:0;margin-top:.25rem;width:9rem;background-color:#374151;color:#fff;border-radius:.25rem;box-shadow:0 4px 6px -1px #0000001a;z-index:50;max-height:300px;overflow-y:auto}#video-editor-trim-root .zoom-option{padding:.25rem .75rem;cursor:pointer}#video-editor-trim-root .zoom-option:hover{background-color:#4b5563}#video-editor-trim-root .zoom-option.selected{background-color:#6b7280;display:flex;align-items:center}#video-editor-trim-root .zoom-option svg{margin-right:.25rem}#video-editor-trim-root .save-buttons-row{display:flex;align-items:center;gap:.5rem;margin:0;flex-wrap:nowrap}#video-editor-trim-root .save-button,#video-editor-trim-root .save-copy-button,#video-editor-trim-root .save-segments-button{color:#fff;background:#06c;border-radius:.25rem;font-size:.75rem;padding:.25rem .5rem;cursor:pointer;border:none;white-space:nowrap;transition:background-color .2s;min-width:-moz-fit-content;min-width:fit-content}#video-editor-trim-root .save-button:hover,#video-editor-trim-root .save-copy-button:hover,#video-editor-trim-root .save-segments-button:hover{background-color:#0056b3}@media (max-width: 576px){#video-editor-trim-root .save-buttons-row{width:100%;justify-content:space-between;gap:.5rem}#video-editor-trim-root .save-button,#video-editor-trim-root .save-copy-button,#video-editor-trim-root .save-segments-button{flex:1;font-size:.7rem;padding:.25rem .35rem}}@media (max-width: 480px){#video-editor-trim-root .save-button,#video-editor-trim-root .save-copy-button,#video-editor-trim-root .save-segments-button{font-size:.675rem;padding:.25rem}#video-editor-trim-root .controls-right,#video-editor-trim-root .controls-right button{margin:0}}#video-editor-trim-root .modal-success-content,#video-editor-trim-root .modal-error-content{display:flex;flex-direction:column;align-items:center;padding:1rem;text-align:center;padding:0;margin:0}#video-editor-trim-root .modal-success-icon,#video-editor-trim-root .modal-error-icon{margin-bottom:1rem}#video-editor-trim-root .modal-success-icon svg{color:#4caf50;animation:fadeIn .5s ease-in-out}#video-editor-trim-root .modal-error-icon svg{color:#f44336;animation:fadeIn .5s ease-in-out}#video-editor-trim-root .success-link{background-color:#4caf50;color:#fff;transition:background-color .3s}#video-editor-trim-root .success-link:hover{background-color:#388e3c}#video-editor-trim-root .error-message{color:#f44336;font-weight:500}#video-editor-trim-root .modal-spinner{display:flex;justify-content:center;margin:2rem 0}#video-editor-trim-root .spinner{width:50px;height:50px;border:5px solid rgba(0,0,0,.1);border-radius:50%;border-top-color:#06c;animation:spin 1s ease-in-out infinite}#video-editor-trim-root .text-center{text-align:center}#video-editor-trim-root .modal-message{margin-bottom:1rem;line-height:1.5}#video-editor-trim-root .modal-choice-button{display:flex;align-items:center;justify-content:center;padding:.75rem 1.25rem;background-color:#06c;color:#fff;border-radius:4px;text-decoration:none;margin:0 auto;cursor:pointer;font-weight:500;gap:.5rem;border:none;transition:background-color .3s}#video-editor-trim-root .modal-choice-button:hover{background-color:#0056b3}#video-editor-trim-root .modal-choice-button svg{flex-shrink:0}#video-editor-trim-root .centered-choice{margin:0 auto;min-width:180px}.mobile-timeline-overlay{position:absolute;top:0;left:0;width:100%;height:100%;background-color:#00000080;z-index:50;display:flex;justify-content:center;align-items:center;border-radius:.5rem;pointer-events:none}.mobile-timeline-message{background-color:#000c;border-radius:8px;padding:15px 25px;text-align:center;max-width:80%;animation:pulse 2s infinite}.mobile-timeline-message p{color:#fff;font-size:16px;margin:0 0 15px;font-weight:500}.mobile-play-icon{width:0;height:0;border-top:15px solid transparent;border-bottom:15px solid transparent;border-left:25px solid white;margin:0 auto}@keyframes pulse{0%{opacity:.7;transform:scale(1)}50%{opacity:1;transform:scale(1.05)}to{opacity:.7;transform:scale(1)}}.preview-mode .tooltip-action-btn,.preview-mode .tooltip-time-btn{opacity:.5;pointer-events:none;cursor:not-allowed}.timeline-container-card.preview-mode{pointer-events:none}.timeline-container-card.preview-mode .timeline-marker-head,.timeline-container-card.preview-mode .timeline-marker-drag,.timeline-container-card.preview-mode .clip-segment,.timeline-container-card.preview-mode .clip-segment-handle,.timeline-container-card.preview-mode .time-button,.timeline-container-card.preview-mode .zoom-button,.timeline-container-card.preview-mode .save-button,.timeline-container-card.preview-mode .save-copy-button,.timeline-container-card.preview-mode .save-segments-button{opacity:.5;pointer-events:none;cursor:not-allowed}.timeline-container-card.preview-mode .clip-segment:hover{box-shadow:none;border-color:#00000026;background-color:inherit!important}.segments-playback-mode .tooltip-time-btn{opacity:1;cursor:pointer}.segments-playback-mode .tooltip-action-btn.set-in,.segments-playback-mode .tooltip-action-btn.set-out,.segments-playback-mode .tooltip-action-btn.play-from-start{opacity:.5;pointer-events:none}.segments-playback-mode .tooltip-action-btn.play,.segments-playback-mode .tooltip-action-btn.pause{opacity:1;cursor:pointer}.segments-playback-message{display:flex;align-items:center;background-color:#3b82f61a;color:#3b82f6;padding:6px 12px;border-radius:4px;font-weight:600;font-size:.875rem;animation:pulse 2s infinite}.segments-playback-message svg{height:1.25rem;width:1.25rem;margin-right:.5rem;color:#3b82f6}.two-row-tooltip{display:flex;flex-direction:column;background-color:#fff;padding:6px;border-radius:4px;box-shadow:0 2px 8px #00000026;position:relative;z-index:3000}.tooltip-time-btn[data-tooltip="Decrease by 100ms"],.tooltip-time-btn[data-tooltip="Increase by 100ms"]{display:none!important}.tooltip-row{display:flex;justify-content:space-between;align-items:center;gap:3px}.tooltip-row:first-child{margin-bottom:6px}.tooltip-time-btn{background-color:#f0f0f0!important;border:none!important;border-radius:4px!important;padding:4px 8px!important;font-size:.75rem!important;font-weight:500!important;color:#333!important;cursor:pointer!important;transition:background-color .2s!important;min-width:20px!important}.tooltip-time-btn:hover{background-color:#e0e0e0!important}.tooltip-time-display{font-family:monospace!important;font-size:.875rem!important;font-weight:600!important;color:#333!important;padding:4px 6px!important;background-color:#f7f7f7!important;border-radius:4px!important;min-width:100px!important;text-align:center!important;overflow:hidden!important}.tooltip-actions{display:flex;justify-content:space-between;align-items:center;gap:3px;position:relative;z-index:2500}.tooltip-action-btn{background-color:#f3f4f6;border:none;border-radius:4px;padding:5px;display:flex;align-items:center;justify-content:center;cursor:pointer;color:#4b5563;width:26px;height:26px;min-width:20px!important;position:relative}.tooltip-action-btn[data-tooltip]:before{content:attr(data-tooltip);position:absolute;height:30px;top:35px;left:50%;transform:translate(-50%);margin-left:0;background-color:#000000d9;color:#fff;text-align:left;padding:6px 12px;border-radius:4px;box-shadow:0 2px 8px #0003;font-size:12px;white-space:nowrap;opacity:0;visibility:hidden;transition:opacity .2s,visibility .2s;z-index:2500;pointer-events:none}.tooltip-action-btn[data-tooltip]:after{content:"";position:absolute;top:35px;left:50%;transform:translate(-50%);border-width:4px;border-style:solid;border-color:rgba(0,0,0,.85) transparent transparent transparent;margin-left:0;opacity:0;visibility:hidden;transition:opacity .2s,visibility .2s;z-index:2500;pointer-events:none}@media (hover: hover) and (pointer: fine){.tooltip-action-btn[data-tooltip]:hover:before,.tooltip-action-btn[data-tooltip]:hover:after{opacity:1;visibility:visible}}@media (pointer: coarse){.tooltip-action-btn[data-tooltip]:before,.tooltip-action-btn[data-tooltip]:after{display:none!important;opacity:0!important;visibility:hidden!important;pointer-events:none!important;content:none!important}}.tooltip-action-btn:hover{background-color:#e5e7eb;color:#111827}.tooltip-action-btn.delete{color:#ef4444}.tooltip-action-btn.delete:hover{background-color:#fee2e2}.tooltip-action-btn.play{color:#10b981}.tooltip-action-btn.play:hover{background-color:#d1fae5}.tooltip-action-btn.pause{color:#3b82f6}.tooltip-action-btn.pause:hover{background-color:#dbeafe}.tooltip-action-btn.play-from-start{color:#4f46e5}.tooltip-action-btn.play-from-start:hover{background-color:#e0e7ff}.tooltip-action-btn svg{width:16px;height:16px}.tooltip-action-btn.new-segment{width:auto;height:auto;padding:6px 10px;display:flex;flex-direction:row;color:#10b981}.tooltip-action-btn.new-segment:hover{background-color:#d1fae5}.tooltip-action-btn.new-segment .tooltip-btn-text{margin-left:6px;font-size:.75rem;white-space:nowrap}.tooltip-action-btn.disabled{opacity:.5;cursor:not-allowed;background-color:#f3f4f6}.tooltip-action-btn.disabled:hover{background-color:#f3f4f6;color:#9ca3af}.tooltip-action-btn.disabled svg{color:#9ca3af}.tooltip-action-btn.disabled .tooltip-btn-text{color:#9ca3af}@media (max-width: 768px){.two-row-tooltip{padding:4px}.tooltip-row:first-child{margin-bottom:4px}.tooltip-time-btn{min-width:20px!important;font-size:.7rem!important;padding:3px 6px!important}.tooltip-time-display{font-size:.8rem!important;padding:3px 4px!important;min-width:90px!important}.tooltip-action-btn{width:24px;height:24px;padding:4px}.tooltip-action-btn.new-segment{padding:4px 8px}.tooltip-action-btn svg{width:14px;height:14px}.tooltip-action-btn[data-tooltip]:before{min-width:100px;font-size:11px;padding:4px 8px;height:24px;top:33px}.tooltip-action-btn[data-tooltip]:after{top:33px}}#video-editor-trim-root{@keyframes bluePulse{0%{box-shadow:0 0 #3b82f666}50%{box-shadow:0 0 0 8px #3b82f600}to{box-shadow:0 0 #3b82f600}}@keyframes pulse{0%{opacity:.8}50%{opacity:1}to{opacity:.8}}}#video-editor-trim-root .editing-tools-container{background-color:#fff;border-radius:.5rem;padding:1rem;margin-bottom:2.5rem;box-shadow:0 1px 2px #0000000d}#video-editor-trim-root .flex-container{display:flex;justify-content:space-between;align-items:center;position:relative;gap:15px;width:100%}#video-editor-trim-root .flex-container.single-row{flex-wrap:nowrap}#video-editor-trim-root .full-text{display:inline}#video-editor-trim-root .short-text{display:none}#video-editor-trim-root .reset-text{display:inline}#video-editor-trim-root .button-group{display:flex;align-items:center}#video-editor-trim-root .button-group.play-buttons-group{gap:.75rem;justify-content:flex-start;flex:0 0 auto}#video-editor-trim-root .button-group.secondary{gap:.75rem;align-items:center;justify-content:flex-end;margin-left:auto}#video-editor-trim-root .button-group button{display:flex;align-items:center;color:#333;background:none;border:none;cursor:pointer;min-width:auto}#video-editor-trim-root .button-group button:hover:not(:disabled){color:inherit}#video-editor-trim-root .button-group button:disabled{opacity:.5;cursor:not-allowed}#video-editor-trim-root .button-group button svg{height:1.25rem;width:1.25rem;margin-right:.25rem}#video-editor-trim-root .divider{border-right:1px solid #d1d5db;height:1.5rem;margin:0 .5rem}#video-editor-trim-root .play-button,#video-editor-trim-root .preview-button{font-weight:600;display:flex;align-items:center;position:relative;overflow:hidden;min-width:80px;justify-content:center;font-size:.875rem!important}#video-editor-trim-root .play-button.greyed-out{opacity:.5;cursor:not-allowed}#video-editor-trim-root .segments-button.highlighted-stop{background-color:#3b82f61a;color:#3b82f6;border:1px solid #3b82f6;animation:bluePulse 2s infinite}#video-editor-trim-root .play-button:hover:not(:disabled),#video-editor-trim-root .preview-button:hover:not(:disabled){color:inherit!important;transform:none!important;font-size:.875rem!important;width:auto!important;background:none!important}#video-editor-trim-root .play-button svg,#video-editor-trim-root .preview-button svg{height:1.5rem;width:1.5rem;flex-shrink:0}#video-editor-trim-root .preview-mode-message{display:flex;align-items:center;background-color:#3b82f61a;color:#3b82f6;padding:6px 12px;border-radius:4px;font-weight:600;font-size:.875rem;animation:pulse 2s infinite}#video-editor-trim-root .preview-mode-message svg{height:1.25rem;width:1.25rem;margin-right:.5rem;color:#3b82f6}#video-editor-trim-root .button-text{margin-left:.25rem}@media (max-width: 992px){#video-editor-trim-root .button-group.secondary .button-text{display:none}}@media (max-width: 768px){#video-editor-trim-root .flex-container.single-row{justify-content:space-between}#video-editor-trim-root .button-group{gap:.5rem}#video-editor-trim-root .preview-button,#video-editor-trim-root .play-button{font-size:.875rem!important}}@media (max-width: 640px){#video-editor-trim-root .editing-tools-container{padding:.75rem;overflow-x:hidden}#video-editor-trim-root .preview-button{min-width:auto}#video-editor-trim-root .full-text{display:none}#video-editor-trim-root .short-text{display:inline;margin-left:.15rem}#video-editor-trim-root .reset-text{display:none}#video-editor-trim-root .button-group.play-buttons-group{flex:initial;justify-content:flex-start;flex-shrink:0}#video-editor-trim-root .button-group.secondary{flex:initial;justify-content:flex-end;flex-shrink:0}#video-editor-trim-root .button-group button{padding:.375rem;min-width:auto}#video-editor-trim-root .button-group button svg{height:1.125rem;width:1.125rem;margin-right:.125rem}}@media (max-width: 576px){#video-editor-trim-root .flex-container.single-row{justify-content:space-between;flex-wrap:nowrap;gap:10px}#video-editor-trim-root .button-group.play-buttons-group{justify-content:flex-start;flex:0 0 auto}#video-editor-trim-root .button-group.secondary{justify-content:flex-end;margin-left:auto}#video-editor-trim-root .button-group button{padding:.25rem}#video-editor-trim-root .preview-mode-message{font-size:.8rem;padding:4px 8px}#video-editor-trim-root .divider{margin:0 .25rem}}@media (max-width: 480px){#video-editor-trim-root .editing-tools-container{padding:.5rem}#video-editor-trim-root .flex-container.single-row{gap:8px}#video-editor-trim-root .button-group.play-buttons-group,#video-editor-trim-root .button-group.secondary{gap:.25rem}#video-editor-trim-root .divider{display:none}#video-editor-trim-root .button-group button{padding:.125rem}#video-editor-trim-root .button-group button svg{height:1rem;width:1rem;margin-right:0}#video-editor-trim-root .button-text,#video-editor-trim-root .reset-text{display:none}}@media (max-width: 640px) and (orientation: portrait){#video-editor-trim-root .editing-tools-container{width:100%;box-sizing:border-box}#video-editor-trim-root .flex-container.single-row{width:100%;padding:0;margin:0}#video-editor-trim-root .button-group{max-width:50%}#video-editor-trim-root .button-group.play-buttons-group{max-width:60%}#video-editor-trim-root .button-group.secondary{max-width:40%}}@media (hover: hover) and (pointer: fine){#video-editor-trim-root [data-tooltip]{position:relative}#video-editor-trim-root [data-tooltip]:before{content:attr(data-tooltip);position:absolute;bottom:100%;left:50%;transform:translate(-50%);margin-bottom:5px;background-color:#000c;color:#fff;text-align:center;padding:5px 10px;border-radius:3px;font-size:12px;white-space:nowrap;opacity:0;visibility:hidden;transition:opacity .2s,visibility .2s;z-index:1000;pointer-events:none}#video-editor-trim-root [data-tooltip]:after{content:"";position:absolute;bottom:100%;left:50%;transform:translate(-50%);border-width:5px;border-style:solid;border-color:rgba(0,0,0,.8) transparent transparent transparent;opacity:0;visibility:hidden;transition:opacity .2s,visibility .2s;pointer-events:none}#video-editor-trim-root [data-tooltip]:hover:before,#video-editor-trim-root [data-tooltip]:hover:after{opacity:1;visibility:visible}}@media (pointer: coarse){#video-editor-trim-root [data-tooltip]:before,#video-editor-trim-root [data-tooltip]:after{display:none!important;content:none!important;opacity:0!important;visibility:hidden!important;pointer-events:none!important}}#video-editor-trim-root .clip-segments-container{margin-top:1rem;background-color:#fff;border-radius:.5rem;padding:1rem;box-shadow:0 1px 2px #0000000d}#video-editor-trim-root .clip-segments-title{font-size:.875rem;font-weight:500;color:var(--foreground, #333);margin-bottom:.75rem}#video-editor-trim-root .segment-item{display:flex;align-items:center;justify-content:space-between;padding:.5rem;border:1px solid #e5e7eb;border-radius:.25rem;margin-bottom:.5rem;transition:box-shadow .2s ease}#video-editor-trim-root .segment-item:hover{box-shadow:0 4px 6px -1px #0000001a}#video-editor-trim-root .segment-content{display:flex;align-items:center}#video-editor-trim-root .segment-thumbnail{width:4rem;height:2.25rem;background-size:cover;background-position:center;border-radius:.25rem;margin-right:.75rem;box-shadow:0 0 0 1px #ffffff4d}#video-editor-trim-root .segment-info{display:flex;flex-direction:column}#video-editor-trim-root .segment-title{font-weight:500;font-size:.875rem;color:#000}#video-editor-trim-root .segment-time{font-size:.75rem;color:#000}#video-editor-trim-root .segment-duration{font-size:.75rem;margin-top:.25rem;display:inline-block;background-color:#f3f4f6;padding:0 .5rem;border-radius:.25rem;color:#000}#video-editor-trim-root .segment-actions{display:flex;align-items:center;gap:.5rem}#video-editor-trim-root .delete-button{padding:.375rem;color:#4b5563;background-color:#e5e7eb;border-radius:9999px;border:none;cursor:pointer;transition:background-color .2s,color .2s;min-width:auto}#video-editor-trim-root .delete-button:hover{color:#000;background-color:#d1d5db}#video-editor-trim-root .delete-button svg{height:1rem;width:1rem}#video-editor-trim-root .empty-message{padding:1rem;text-align:center;color:#333333b3}#video-editor-trim-root .segment-color-1{background-color:#3b82f626}#video-editor-trim-root .segment-color-2{background-color:#10b98126}#video-editor-trim-root .segment-color-3{background-color:#f59e0b26}#video-editor-trim-root .segment-color-4{background-color:#ef444426}#video-editor-trim-root .segment-color-5{background-color:#8b5cf626}#video-editor-trim-root .segment-color-6{background-color:#ec489926}#video-editor-trim-root .segment-color-7{background-color:#06b6d426}#video-editor-trim-root .segment-color-8{background-color:#facc1526}.mobile-play-prompt-overlay{position:fixed;top:0;left:0;width:100%;height:100%;background-color:#000000b3;display:flex;justify-content:center;align-items:center;z-index:1000;backdrop-filter:blur(5px);-webkit-backdrop-filter:blur(5px)}.mobile-play-prompt{background-color:#fff;width:90%;max-width:400px;border-radius:12px;padding:25px;box-shadow:0 4px 20px #00000040;text-align:center}.mobile-play-prompt h3{margin:0 0 15px;font-size:20px;color:#333;font-weight:600}.mobile-play-prompt p{margin:0 0 15px;font-size:16px;color:#444;line-height:1.5}.mobile-prompt-instructions{margin:20px 0;text-align:left;background-color:#f8f9fa;padding:15px;border-radius:8px}.mobile-prompt-instructions p{margin:0 0 8px;font-size:15px;font-weight:500}.mobile-prompt-instructions ol{margin:0;padding-left:22px}.mobile-prompt-instructions li{margin-bottom:8px;font-size:14px;color:#333}.mobile-play-button{background-color:#007bff;color:#fff;border:none;border-radius:8px;padding:12px 25px;font-size:16px;font-weight:500;cursor:pointer;transition:background-color .2s;margin-top:5px;min-height:44px;min-width:200px}.mobile-play-button:hover{background-color:#0069d9}.mobile-play-button:active{background-color:#0062cc;transform:scale(.98)}@supports (-webkit-touch-callout: none){.mobile-play-button{padding:14px 25px}}*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html,:host{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}*{border-color:hsl(var(--border))}.container{width:100%}@media (min-width: 640px){.container{max-width:640px}}@media (min-width: 768px){.container{max-width:768px}}@media (min-width: 1024px){.container{max-width:1024px}}@media (min-width: 1280px){.container{max-width:1280px}}@media (min-width: 1536px){.container{max-width:1536px}}.visible{visibility:visible}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.mx-auto{margin-left:auto;margin-right:auto}.mb-2{margin-bottom:.5rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.inline{display:inline}.flex{display:flex}.hidden{display:none}.min-h-screen{min-height:100vh}.w-full{width:100%}.max-w-6xl{max-width:72rem}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.resize{resize:both}.items-center{align-items:center}.justify-center{justify-content:center}.gap-4{gap:1rem}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.rounded-md{border-radius:calc(var(--radius) - 2px)}.border{border-width:1px}.bg-background{background-color:hsl(var(--background))}.bg-indigo-600{--tw-bg-opacity: 1;background-color:rgb(79 70 229 / var(--tw-bg-opacity, 1))}.bg-purple-500{--tw-bg-opacity: 1;background-color:rgb(168 85 247 / var(--tw-bg-opacity, 1))}.px-4{padding-left:1rem;padding-right:1rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-6{padding-top:1.5rem;padding-bottom:1.5rem}.text-center{text-align:center}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xs{font-size:.75rem;line-height:1rem}.text-gray-500{--tw-text-opacity: 1;color:rgb(107 114 128 / var(--tw-text-opacity, 1))}.text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.underline{text-decoration-line:underline}.filter{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)}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}@keyframes enter{0%{opacity:var(--tw-enter-opacity, 1);transform:translate3d(var(--tw-enter-translate-x, 0),var(--tw-enter-translate-y, 0),0) scale3d(var(--tw-enter-scale, 1),var(--tw-enter-scale, 1),var(--tw-enter-scale, 1)) rotate(var(--tw-enter-rotate, 0))}}@keyframes exit{to{opacity:var(--tw-exit-opacity, 1);transform:translate3d(var(--tw-exit-translate-x, 0),var(--tw-exit-translate-y, 0),0) scale3d(var(--tw-exit-scale, 1),var(--tw-exit-scale, 1),var(--tw-exit-scale, 1)) rotate(var(--tw-exit-rotate, 0))}}.paused{animation-play-state:paused}:root{--foreground: 20 14.3% 4.1%;--muted: 60 4.8% 95.9%;--muted-foreground: 25 5.3% 44.7%;--popover: 0 0% 100%;--popover-foreground: 20 14.3% 4.1%;--card: 0 0% 100%;--card-foreground: 20 14.3% 4.1%;--border: 20 5.9% 90%;--input: 20 5.9% 90%;--primary: 207 90% 54%;--primary-foreground: 211 100% 99%;--secondary: 30 84% 54%;--secondary-foreground: 60 9.1% 97.8%;--accent: 60 4.8% 95.9%;--accent-foreground: 24 9.8% 10%;--destructive: 0 84.2% 60.2%;--destructive-foreground: 60 9.1% 97.8%;--ring: 20 14.3% 4.1%;--radius: .5rem}.video-player{position:relative;width:100%;background-color:#000;overflow:hidden;border-radius:.5rem}.video-controls{position:absolute;bottom:0;left:0;right:0;background:linear-gradient(to top,rgba(0,0,0,.8),transparent);padding:1rem;display:flex;flex-direction:column}.video-current-time{color:#fff;font-weight:500}.video-progress{position:relative;height:4px;background-color:#ffffff4d;border-radius:2px;margin-bottom:1rem}.video-progress-fill{position:absolute;left:0;top:0;height:100%;background-color:hsl(var(--primary));border-radius:2px}.video-scrubber{position:absolute;width:12px;height:12px;margin-left:-6px;background-color:#fff;border-radius:50%;top:-4px}.video-player-container{position:relative;overflow:hidden}.play-pause-indicator{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:70px;height:70px;border-radius:50%;background-color:#00000080;z-index:20;opacity:0;transition:opacity .2s ease;pointer-events:none;background-position:center;background-repeat:no-repeat}.play-icon{background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' width='36' height='36' fill='white'%3E%3Cpath d='M8 5v14l11-7z'/%3E%3C/svg%3E")}.pause-icon{background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' width='36' height='36' fill='white'%3E%3Cpath d='M6 19h4V5H6v14zm8-14v14h4V5h-4z'/%3E%3C/svg%3E")}.video-player-container:hover .play-pause-indicator{opacity:1}.timeline-scroll-container{height:6rem;border-radius:.375rem;overflow-x:auto;overflow-y:hidden;margin-bottom:.75rem;background-color:#eee;position:relative}.timeline-container{position:relative;background-color:#eee;height:6rem;width:100%;cursor:pointer;transition:width .3s ease}.timeline-marker{position:absolute;top:-10px;height:calc(100% + 10px);width:2px;background-color:red;z-index:100;pointer-events:none;box-shadow:0 0 4px #ff000080}.trim-line-marker{position:absolute;top:0;bottom:0;width:2px;background-color:#007bffe6;z-index:10}.trim-handle{width:8px;background-color:#6c757de6;position:absolute;top:0;bottom:0;cursor:ew-resize;z-index:15}.trim-handle.left{left:-4px}.trim-handle.right{right:-4px}.timeline-thumbnail{height:100%;border-right:1px solid rgba(0,0,0,.1);position:relative;display:inline-block;background-size:cover;background-position:center}.split-point{position:absolute;width:2px;background-color:#6c757de6;top:0;bottom:0;z-index:5}.clip-segment{position:absolute;height:95%;top:0;border-radius:4px;background-size:cover;background-position:center;background-blend-mode:soft-light;box-shadow:0 2px 8px #0003;overflow:hidden;cursor:grab;-webkit-user-select:none;-moz-user-select:none;user-select:none;transition:box-shadow .2s,transform .1s;z-index:15}.clip-segment:nth-child(odd),.segment-color-1,.segment-color-3,.segment-color-5,.segment-color-7{background-color:transparent;border:2px solid rgba(0,123,255,.9)}.clip-segment:nth-child(2n),.segment-color-2,.segment-color-4,.segment-color-6,.segment-color-8{background-color:transparent;border:2px solid rgba(108,117,125,.9)}.clip-segment:hover{box-shadow:0 4px 12px #0000004d;transform:translateY(-1px);filter:brightness(1.1)}.clip-segment:active{cursor:grabbing;box-shadow:0 2px 6px #0000004d;transform:translateY(0)}.clip-segment.selected{border-width:3px;box-shadow:0 4px 12px #0006;z-index:25;filter:brightness(1.2)}.clip-segment-info{background-color:#e2e6eae6;color:#000;padding:6px 8px;font-size:.7rem;position:absolute;top:0;left:0;width:100%;border-radius:4px 4px 0 0;z-index:2;display:flex;flex-direction:column;gap:2px}.clip-segment-name{font-weight:700;color:#000}.clip-segment-time{font-size:.65rem;color:#000}.clip-segment-duration{font-size:.65rem;color:#000;background:#b3d9ff66;padding:1px 4px;border-radius:2px;display:inline-block;margin-top:2px}.clip-segment-handle{position:absolute;width:8px;top:0;bottom:0;background-color:#6c757de6;cursor:ew-resize;z-index:20;display:flex;align-items:center;justify-content:center}.clip-segment-handle:after{content:"↔";color:#fff;font-size:12px;text-shadow:0 0 2px rgba(0,0,0,.8)}.clip-segment-handle.left{left:0}.clip-segment-handle.right{right:0}.clip-segment-handle:hover{background-color:#007bffe6;width:10px}input[type=range]{-webkit-appearance:none;height:6px;background:#e0e0e0;border-radius:3px}input[type=range]::-webkit-slider-thumb{-webkit-appearance:none;height:16px;width:16px;border-radius:50%;background:#007bffe6;cursor:pointer}[data-tooltip]{position:relative;cursor:pointer}[data-tooltip]:before{content:attr(data-tooltip);position:absolute;bottom:100%;left:50%;transform:translate(-50%);margin-bottom:8px;background-color:#000c;color:#fff;padding:5px 10px;border-radius:4px;font-size:.8rem;white-space:nowrap;z-index:1000;opacity:0;visibility:hidden;transition:opacity .2s,visibility .2s;pointer-events:none}[data-tooltip]:after{content:"";position:absolute;bottom:100%;left:50%;transform:translate(-50%);border-width:5px;border-style:solid;border-color:rgba(0,0,0,.8) transparent transparent transparent;margin-bottom:0;opacity:0;visibility:hidden;transition:opacity .2s,visibility .2s;pointer-events:none}@media (hover: hover) and (pointer: fine){[data-tooltip]:hover:before,[data-tooltip]:hover:after{opacity:1;visibility:visible}}@media (pointer: coarse){[data-tooltip]:before,[data-tooltip]:after{display:none!important;content:none!important;opacity:0!important;visibility:hidden!important;pointer-events:none!important}}button[disabled][data-tooltip]:before,button[disabled][data-tooltip]:after{opacity:.5}.tooltip-action-btn{position:relative}.tooltip-action-btn[data-tooltip]:before,.tooltip-action-btn[data-tooltip]:after{opacity:0;visibility:hidden;position:absolute;pointer-events:none;transition:all .3s ease}.tooltip-action-btn[data-tooltip]:before{content:attr(data-tooltip);background-color:#000c;color:#fff;font-size:12px;padding:4px 8px;border-radius:3px;white-space:nowrap;bottom:-35px;left:50%;transform:translate(-50%);z-index:9999}.tooltip-action-btn[data-tooltip]:after{content:"";border-width:5px;border-style:solid;border-color:transparent transparent rgba(0,0,0,.8) transparent;bottom:-15px;left:50%;transform:translate(-50%);z-index:9999}@media (hover: hover) and (pointer: fine){.tooltip-action-btn:hover[data-tooltip]:before,.tooltip-action-btn:hover[data-tooltip]:after{opacity:1;visibility:visible}}.segment-tooltip{background-color:#b3d9fff2;color:#000;border-radius:4px;padding:6px;min-width:140px;z-index:1000;box-shadow:0 3px 10px #0003}.segment-tooltip:after{content:"";position:absolute;bottom:-6px;left:50%;transform:translate(-50%);width:0;height:0;border-left:6px solid transparent;border-right:6px solid transparent;border-top:6px solid rgba(179,217,255,.95)}.tooltip-time{font-size:.85rem;font-weight:700;text-align:center;margin-bottom:6px;color:#000}.tooltip-actions{display:flex;justify-content:space-between;gap:5px;position:relative}.tooltip-action-btn{background-color:#007bff33;border:none;border-radius:3px;width:30px;height:30px;display:flex;align-items:center;justify-content:center;cursor:pointer;padding:6px;transition:background-color .2s;min-width:20px!important}.tooltip-action-btn:hover{background-color:#007bff66}.tooltip-action-btn svg{width:100%;height:100%;stroke:currentColor}.tooltip-action-btn.set-in svg,.tooltip-action-btn.set-out svg{width:100%;height:100%;margin:0 auto;fill:currentColor;stroke:none}.empty-space-tooltip{background-color:#fff;border-radius:6px;box-shadow:0 2px 8px #00000026;padding:8px;z-index:50;min-width:120px;text-align:center;position:relative}.empty-space-tooltip:after{content:"";position:absolute;bottom:-8px;left:50%;transform:translate(-50%);border-width:8px 8px 0;border-style:solid;border-color:white transparent transparent}.tooltip-action-btn.new-segment{width:auto;padding:6px 10px;display:flex;align-items:center;gap:5px}.tooltip-btn-text{font-size:.8rem;white-space:nowrap;color:#000}.icon-new-segment{width:20px;height:20px}.zoom-dropdown-container{position:relative}.zoom-button{display:flex;align-items:center;gap:6px;background-color:#6c757dcc;color:#fff;border:none;border-radius:4px;padding:8px 12px;font-weight:500;cursor:pointer;transition:background-color .2s}.zoom-button:hover{background-color:#6c757d}.zoom-dropdown{background-color:#fff;border-radius:4px;box-shadow:0 2px 10px #00000026;max-height:300px;overflow-y:auto}.zoom-option{padding:8px 12px;cursor:pointer;display:flex;align-items:center;gap:5px}.zoom-option:hover{background-color:#007bff1a}.zoom-option.selected{background-color:#007bff33;font-weight:500}.save-button,.save-copy-button,.save-segments-button{background-color:#007bffcc;color:#fff;border:none;border-radius:4px;padding:8px 12px;font-weight:500;cursor:pointer;transition:background-color .2s}.save-button:hover,.save-copy-button:hover{background-color:#007bff}.save-copy-button{background-color:#6c757dcc}.save-copy-button:hover{background-color:#6c757d}.time-nav-label{font-weight:500;font-size:.9rem}.time-input{padding:6px 10px;border-radius:4px;border:1px solid #ccc;width:150px;font-family:monospace}.time-button-group{display:flex;gap:5px}.time-button{background-color:#6c757dcc;color:#fff;border:none;border-radius:4px;padding:6px 8px;font-size:.8rem;cursor:pointer;transition:background-color .2s}.time-button:hover{background-color:#6c757d}.timeline-controls{display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;padding:12px;background-color:#f5f5f5;border-radius:6px;margin-top:15px}.time-navigation{display:flex;align-items:center;gap:10px;flex-wrap:wrap}.controls-right{display:flex;align-items:center;gap:10px}@media (max-width: 768px){.timeline-controls{flex-direction:column;align-items:flex-start;gap:15px}.controls-right{margin-top:10px;width:100%;justify-content:flex-start;text-align:center;align-items:center;justify-content:center}}.timeline-header{display:flex;align-items:center;gap:20px;margin-bottom:10px;flex-wrap:wrap}.timeline-title{font-weight:700;margin-right:20px}.timeline-title-text{font-size:1.1rem}.current-time,.duration-time{white-space:nowrap}.time-code{font-family:monospace;font-weight:500}@media (max-width: 480px){.timeline-header{flex-direction:column;align-items:flex-start;gap:8px}.time-navigation{width:100%;flex-direction:column;align-items:flex-start;gap:10px}.time-button-group{width:100%;display:flex;justify-content:space-between;margin-top:10px}.controls-right{flex-wrap:wrap;gap:8px}.save-button,.save-copy-button{margin-top:8px;width:100%}.zoom-dropdown-container{width:100%}.zoom-button{width:100%;justify-content:center}}
+#video-editor-trim-root{@keyframes pulse{0%{opacity:.7;transform:scale(1)}50%{opacity:1;transform:scale(1.05)}to{opacity:.7;transform:scale(1)}}}#video-editor-trim-root .video-player-container{position:relative;width:100%;background:#000;border-radius:.5rem;overflow:hidden;margin-bottom:1rem;aspect-ratio:16/9;-webkit-user-select:none;-moz-user-select:none;user-select:none}#video-editor-trim-root .video-player-container video{width:100%;height:100%;cursor:pointer;transform:translateZ(0);-webkit-transform:translateZ(0);-webkit-user-select:none;-moz-user-select:none;user-select:none}@supports (-webkit-touch-callout: none){#video-editor-trim-root .video-player-container video{-webkit-tap-highlight-color:transparent;-webkit-touch-callout:none}}#video-editor-trim-root .play-pause-indicator{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:60px;height:60px;background-color:#0009;border-radius:50%;opacity:0;transition:opacity .3s;pointer-events:none}#video-editor-trim-root .video-player-container:hover .play-pause-indicator{opacity:1}#video-editor-trim-root .play-pause-indicator:before{content:"";position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)}#video-editor-trim-root .play-pause-indicator.play-icon:before{width:0;height:0;border-top:15px solid transparent;border-bottom:15px solid transparent;border-left:25px solid white;margin-left:3px}#video-editor-trim-root .play-pause-indicator.pause-icon:before{width:20px;height:25px;border-left:6px solid white;border-right:6px solid white}#video-editor-trim-root .ios-first-play-indicator{position:absolute;top:0;left:0;width:100%;height:100%;background:#000000b3;display:flex;align-items:center;justify-content:center;z-index:10}#video-editor-trim-root .ios-play-message{color:#fff;font-size:1.2rem;text-align:center;padding:1rem;background:#000c;border-radius:.5rem;animation:pulse 2s infinite}#video-editor-trim-root .video-controls{position:absolute;bottom:0;left:0;right:0;padding:.75rem;background:linear-gradient(transparent,#000000b3);opacity:0;transition:opacity .3s}#video-editor-trim-root .video-player-container:hover .video-controls{opacity:1}#video-editor-trim-root .video-current-time,#video-editor-trim-root .video-duration{color:#fff;font-size:.875rem}#video-editor-trim-root .video-time-display{display:flex;justify-content:space-between;margin-bottom:.5rem;color:#fff;font-size:.875rem}#video-editor-trim-root .video-progress{position:relative;height:6px;background-color:#ffffff4d;border-radius:3px;cursor:pointer;margin:0 10px;touch-action:none;flex-grow:1}#video-editor-trim-root .video-progress.dragging{height:8px}#video-editor-trim-root .video-progress-fill{position:absolute;top:0;left:0;height:100%;background-color:red;border-radius:3px;pointer-events:none}#video-editor-trim-root .video-scrubber{position:absolute;top:50%;transform:translate(-50%,-50%);width:16px;height:16px;background-color:red;border-radius:50%;cursor:grab;transition:transform .1s ease,width .1s ease,height .1s ease}#video-editor-trim-root .video-progress.dragging .video-scrubber{transform:translate(-50%,-50%) scale(1.2);width:18px;height:18px;cursor:grabbing;box-shadow:0 0 8px #f009}@media (pointer: coarse){#video-editor-trim-root .video-scrubber{width:20px;height:20px}#video-editor-trim-root .video-progress.dragging .video-scrubber{width:24px;height:24px}#video-editor-trim-root .video-scrubber:before{content:"";position:absolute;top:-10px;left:-10px;right:-10px;bottom:-10px}}#video-editor-trim-root .video-controls-buttons{display:flex;align-items:center;justify-content:flex-end;gap:.75rem}#video-editor-trim-root .mute-button,#video-editor-trim-root .fullscreen-button{min-width:auto;color:#fff;background:none;border:none;cursor:pointer;padding:.25rem;transition:transform .2s}#video-editor-trim-root .mute-button:hover,#video-editor-trim-root .fullscreen-button:hover{transform:scale(1.1)}#video-editor-trim-root .mute-button svg,#video-editor-trim-root .fullscreen-button svg{width:1.25rem;height:1.25rem}#video-editor-trim-root .video-time-tooltip{position:absolute;top:-30px;background-color:#000000b3;color:#fff;padding:4px 8px;border-radius:4px;font-size:12px;font-family:monospace;pointer-events:none;z-index:1000;white-space:nowrap;box-shadow:0 2px 4px #0000004d}#video-editor-trim-root .video-time-tooltip:after{content:"";position:absolute;bottom:-4px;left:50%;transform:translate(-50%);width:0;height:0;border-left:4px solid transparent;border-right:4px solid transparent;border-top:4px solid rgba(0,0,0,.7)}#video-editor-trim-root{@keyframes modal-fade-in{0%{opacity:0;transform:translateY(-20px)}to{opacity:1;transform:translateY(0)}}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}@keyframes success-pop{0%{transform:scale(0);opacity:0}70%{transform:scale(1.1);opacity:1}to{transform:scale(1);opacity:1}}@keyframes error-pop{0%{transform:scale(0);opacity:0}70%{transform:scale(1.1);opacity:1}to{transform:scale(1);opacity:1}}}#video-editor-trim-root .modal-overlay{position:fixed;top:0;left:0;right:0;bottom:0;background-color:#00000080;display:flex;align-items:center;justify-content:center;z-index:1000}#video-editor-trim-root .modal-container{background-color:#fff;border-radius:8px;box-shadow:0 4px 12px #00000026;width:90%;max-width:500px;max-height:90vh;overflow-y:auto;animation:modal-fade-in .3s ease-out}#video-editor-trim-root .modal-header{display:flex;justify-content:space-between;align-items:center;padding:16px 20px;border-bottom:1px solid #eee}#video-editor-trim-root .modal-title{margin:0;font-size:1.25rem;font-weight:600;color:#333}#video-editor-trim-root .modal-close-button{background:none;border:none;cursor:pointer;color:#666;padding:4px;display:flex;align-items:center;justify-content:center;transition:color .2s}#video-editor-trim-root .modal-close-button:hover{color:#000}#video-editor-trim-root .modal-content{padding:20px;color:#333;font-size:1rem;line-height:1.5;max-height:400px;overflow-y:auto}#video-editor-trim-root .modal-actions{display:flex;justify-content:flex-end;padding:16px 20px;border-top:1px solid #eee;gap:12px}#video-editor-trim-root .modal-button{padding:8px 16px;border-radius:4px;font-weight:500;cursor:pointer;transition:all .2s;border:none}#video-editor-trim-root .modal-button-primary{background-color:#06c;color:#fff}#video-editor-trim-root .modal-button-primary:hover{background-color:#05a}#video-editor-trim-root .modal-button-secondary{background-color:#f0f0f0;color:#333}#video-editor-trim-root .modal-button-secondary:hover{background-color:#e0e0e0}#video-editor-trim-root .modal-button-danger{background-color:#dc3545;color:#fff}#video-editor-trim-root .modal-button-danger:hover{background-color:#bd2130}#video-editor-trim-root .modal-message{margin-bottom:16px;font-size:1rem}#video-editor-trim-root .modal-spinner{display:flex;align-items:center;justify-content:center;margin:20px 0}#video-editor-trim-root .spinner{border:4px solid rgba(0,0,0,.1);border-radius:50%;border-top:4px solid #0066cc;width:30px;height:30px;animation:spin 1s linear infinite}#video-editor-trim-root .modal-success-icon{display:flex;justify-content:center;margin-bottom:16px;color:#28a745;font-size:2rem}#video-editor-trim-root .modal-success-icon svg{width:60px;height:60px;color:#4caf50;animation:success-pop .5s ease-out}#video-editor-trim-root .modal-error-icon{display:flex;justify-content:center;margin-bottom:16px;color:#dc3545;font-size:2rem}#video-editor-trim-root .modal-error-icon svg{width:60px;height:60px;color:#f44336;animation:error-pop .5s ease-out}#video-editor-trim-root .modal-choices{display:flex;flex-direction:column;gap:10px;margin-top:20px}#video-editor-trim-root .modal-choice-button{padding:12px 16px;border:none;border-radius:4px;background-color:#06c;text-align:center;cursor:pointer;transition:all .2s;display:flex;align-items:center;justify-content:center;font-weight:500;text-decoration:none;color:#fff}#video-editor-trim-root .modal-choice-button:hover{background-color:#05a;transform:translateY(-1px);box-shadow:0 2px 5px #0000001a}#video-editor-trim-root .modal-choice-button svg{margin-right:8px}#video-editor-trim-root .success-link{background-color:#4caf50}#video-editor-trim-root .success-link:hover{background-color:#3d8b40}#video-editor-trim-root .centered-choice{margin:0 auto;width:auto;min-width:220px;background-color:#06c;color:#fff}#video-editor-trim-root .centered-choice:hover{background-color:#05a}@media (max-width: 480px){#video-editor-trim-root .modal-container{width:95%}#video-editor-trim-root .modal-actions{flex-direction:column}#video-editor-trim-root .modal-button{width:100%}}#video-editor-trim-root .error-message{color:#f44336;font-weight:500;background-color:#f443361a;padding:10px;border-radius:4px;border-left:4px solid #f44336;margin-top:10px}#video-editor-trim-root .redirect-message{color:#555;font-size:.95rem;padding:0;margin:0}#video-editor-trim-root .countdown{font-weight:700;color:#06c;font-size:1.1rem}#video-editor-trim-root{@keyframes spin{to{transform:rotate(360deg)}}@keyframes fadeIn{0%{opacity:0;transform:scale(.9)}to{opacity:1;transform:scale(1)}}}#video-editor-trim-root .timeline-container-card{background-color:#fff;border-radius:.5rem;padding:1rem;box-shadow:0 1px 2px #0000000d}#video-editor-trim-root .timeline-header{margin-bottom:.75rem;display:flex;justify-content:space-between;align-items:center}#video-editor-trim-root .timeline-title{font-size:.875rem;font-weight:500;color:var(--foreground, #333)}#video-editor-trim-root .timeline-title-text{font-weight:700}#video-editor-trim-root .current-time{font-size:.875rem;color:var(--foreground, #333)}#video-editor-trim-root .time-code{font-family:monospace;background-color:#f3f4f6;padding:0 .5rem;border-radius:.25rem}#video-editor-trim-root .duration-time{font-size:.875rem;color:var(--foreground, #333)}#video-editor-trim-root .timeline-scroll-container{position:relative;overflow:visible!important}#video-editor-trim-root .timeline-container{position:relative;min-width:100%;background-color:#fafbfc;height:70px;border-radius:.25rem;overflow:visible!important}#video-editor-trim-root .timeline-marker{position:absolute;height:82px;width:2px;background-color:#000;transform:translate(-50%);z-index:50;pointer-events:none}#video-editor-trim-root .timeline-marker-head{position:absolute;top:-6px;left:50%;transform:translate(-50%);width:16px;height:16px;background-color:#ef4444;border-radius:50%;cursor:pointer;display:flex;align-items:center;justify-content:center;pointer-events:auto;z-index:51}#video-editor-trim-root .timeline-marker-drag{position:absolute;bottom:-12px;left:50%;transform:translate(-50%);width:16px;height:16px;background-color:#4b5563;border-radius:50%;cursor:grab;display:flex;align-items:center;justify-content:center;pointer-events:auto;z-index:51}#video-editor-trim-root .timeline-marker-drag.dragging{cursor:grabbing;background-color:#374151}#video-editor-trim-root .timeline-marker-head-icon{color:#fff;font-size:14px;font-weight:700;line-height:1;-webkit-user-select:none;-moz-user-select:none;user-select:none}#video-editor-trim-root .timeline-marker-drag-icon{color:#fff;font-size:12px;line-height:1;-webkit-user-select:none;-moz-user-select:none;user-select:none;transform:rotate(90deg);display:inline-block}#video-editor-trim-root .trim-line-marker{position:absolute;top:0;bottom:0;width:1px;background-color:#00000080;z-index:20}#video-editor-trim-root .trim-handle{position:absolute;width:10px;height:20px;background-color:#000;cursor:ew-resize}#video-editor-trim-root .trim-handle.left{right:0;top:10px;border-radius:3px 0 0 3px}#video-editor-trim-root .trim-handle.right{left:0;top:10px;border-radius:0 3px 3px 0}#video-editor-trim-root .timeline-thumbnail{display:inline-block;height:70px;border-right:1px solid rgba(0,0,0,.03)}#video-editor-trim-root .split-point{position:absolute;top:0;bottom:0;width:1px;background-color:#ff000080;z-index:15}#video-editor-trim-root .clip-segment{position:absolute;height:70px;border-radius:4px;z-index:10;border:2px solid rgba(0,0,0,.15);cursor:pointer}#video-editor-trim-root .clip-segment:hover{box-shadow:0 0 0 2px #0000004d;border-color:#0006;background-color:#f0f0f0cc!important}#video-editor-trim-root .clip-segment.selected{box-shadow:0 0 0 2px #3b82f6b3;border-color:#3b82f6e6}#video-editor-trim-root .clip-segment.selected:hover{background-color:#f0f8ffd9!important}#video-editor-trim-root .clip-segment-info{position:absolute;bottom:0;left:0;right:0;padding:.4rem;background-color:#0006;color:#fff;opacity:1;transition:background-color .2s;line-height:1.3}#video-editor-trim-root .clip-segment:hover .clip-segment-info{background-color:#00000080}#video-editor-trim-root .clip-segment.selected .clip-segment-info{background-color:#3b82f680}#video-editor-trim-root .clip-segment.selected:hover .clip-segment-info{background-color:#3b82f666}#video-editor-trim-root .clip-segment-name{font-weight:700;font-size:12px}#video-editor-trim-root .clip-segment-time,#video-editor-trim-root .clip-segment-duration{font-size:10px}#video-editor-trim-root .clip-segment-handle{position:absolute;top:0;bottom:0;width:6px;background-color:#0003;cursor:ew-resize}#video-editor-trim-root .clip-segment-handle:hover{background-color:#0006}#video-editor-trim-root .clip-segment-handle.left{left:0;border-radius:2px 0 0 2px}#video-editor-trim-root .clip-segment-handle.right{right:0;border-radius:0 2px 2px 0}@media (pointer: coarse){#video-editor-trim-root .clip-segment-handle{width:14px;background-color:#0006}#video-editor-trim-root .clip-segment-handle:after{content:"";position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:2px;height:20px;background-color:#fffc;border-radius:1px}#video-editor-trim-root .clip-segment-handle.left:after{box-shadow:-2px 0 #00000080}#video-editor-trim-root .clip-segment-handle.right:after{box-shadow:2px 0 #00000080}#video-editor-trim-root .clip-segment-handle:active{background-color:#0009}#video-editor-trim-root .timeline-marker{height:85px}#video-editor-trim-root .timeline-marker-head{width:24px;height:24px;top:-13px}#video-editor-trim-root .timeline-marker-drag{width:24px;height:24px;bottom:-18px}#video-editor-trim-root .timeline-marker-head.dragging{width:28px;height:28px;top:-15px}}#video-editor-trim-root .segment-tooltip,#video-editor-trim-root .empty-space-tooltip{position:absolute;background-color:#fff;border-radius:4px;box-shadow:0 2px 8px #0000004d;padding:.5rem;z-index:1000;min-width:150px;text-align:center;pointer-events:auto;top:-100px!important;transform:translateY(-10px)}#video-editor-trim-root .segment-tooltip:after,#video-editor-trim-root .empty-space-tooltip:after{content:"";position:absolute;bottom:-5px;left:50%;transform:translate(-50%);width:0;height:0;border-left:5px solid transparent;border-right:5px solid transparent;border-top:5px solid white}#video-editor-trim-root .segment-tooltip:before,#video-editor-trim-root .empty-space-tooltip:before{content:"";position:absolute;bottom:-6px;left:50%;transform:translate(-50%);width:0;height:0;border-left:6px solid transparent;border-right:6px solid transparent;border-top:6px solid rgba(0,0,0,.1);z-index:-1}#video-editor-trim-root .tooltip-time{font-weight:600;font-size:.875rem;margin-bottom:.5rem;color:#333}#video-editor-trim-root .tooltip-actions{display:flex;justify-content:center;gap:.5rem}#video-editor-trim-root .tooltip-action-btn{background-color:#f3f4f6;border:none;border-radius:.25rem;padding:.375rem;display:flex;align-items:center;justify-content:center;cursor:pointer;color:#4b5563;min-width:20px!important}#video-editor-trim-root .tooltip-action-btn:hover{background-color:#e5e7eb;color:#111827}#video-editor-trim-root .tooltip-action-btn.delete{color:#ef4444}#video-editor-trim-root .tooltip-action-btn.delete:hover{background-color:#fee2e2}#video-editor-trim-root .tooltip-action-btn.new-segment{padding:.375rem .5rem}#video-editor-trim-root .tooltip-action-btn.new-segment .tooltip-btn-text{margin-left:.25rem;font-size:.75rem}#video-editor-trim-root .tooltip-action-btn svg{width:1rem;height:1rem}#video-editor-trim-root .timeline-controls{display:flex;align-items:center;justify-content:space-between;margin-top:.75rem}#video-editor-trim-root .time-navigation{display:none;align-items:center;gap:.5rem}#video-editor-trim-root .time-nav-label{font-size:.875rem;font-weight:500}#video-editor-trim-root .time-input{border:1px solid #d1d5db;border-radius:.25rem;padding:.25rem .5rem;width:8rem;font-size:.875rem}#video-editor-trim-root .time-button-group{display:flex}#video-editor-trim-root .time-button{background-color:#e5e7eb;color:#000;padding:.25rem .5rem;font-size:.875rem;border:none;cursor:pointer;margin-right:.5rem}#video-editor-trim-root .time-button:hover{background-color:#d1d5db}#video-editor-trim-root .time-button:first-child{border-top-left-radius:.25rem;border-bottom-left-radius:.25rem}#video-editor-trim-root .time-button:last-child{border-top-right-radius:.25rem;border-bottom-right-radius:.25rem}#video-editor-trim-root .controls-right{display:flex;align-items:center;gap:.5rem;margin-left:auto}#video-editor-trim-root .zoom-dropdown-container{position:relative;z-index:100;display:none}#video-editor-trim-root .zoom-button{background-color:#374151;color:#fff;border:none;border-radius:.25rem;padding:.25rem .75rem;font-size:.875rem;display:flex;align-items:center;cursor:pointer}#video-editor-trim-root .zoom-button:hover{background-color:#1f2937}#video-editor-trim-root .zoom-button svg{margin-left:.25rem}#video-editor-trim-root .zoom-dropdown{position:absolute;top:100%;left:0;margin-top:.25rem;width:9rem;background-color:#374151;color:#fff;border-radius:.25rem;box-shadow:0 4px 6px -1px #0000001a;z-index:50;max-height:300px;overflow-y:auto}#video-editor-trim-root .zoom-option{padding:.25rem .75rem;cursor:pointer}#video-editor-trim-root .zoom-option:hover{background-color:#4b5563}#video-editor-trim-root .zoom-option.selected{background-color:#6b7280;display:flex;align-items:center}#video-editor-trim-root .zoom-option svg{margin-right:.25rem}#video-editor-trim-root .save-buttons-row{display:flex;align-items:center;gap:.5rem;margin:0;flex-wrap:nowrap}#video-editor-trim-root .save-button,#video-editor-trim-root .save-copy-button,#video-editor-trim-root .save-segments-button{color:#fff;background:#06c;border-radius:.25rem;font-size:.75rem;padding:.25rem .5rem;cursor:pointer;border:none;white-space:nowrap;transition:background-color .2s;min-width:-moz-fit-content;min-width:fit-content}#video-editor-trim-root .save-button:hover,#video-editor-trim-root .save-copy-button:hover,#video-editor-trim-root .save-segments-button:hover{background-color:#0056b3}@media (max-width: 576px){#video-editor-trim-root .save-buttons-row{width:100%;justify-content:space-between;gap:.5rem}#video-editor-trim-root .save-button,#video-editor-trim-root .save-copy-button,#video-editor-trim-root .save-segments-button{flex:1;font-size:.7rem;padding:.25rem .35rem}}@media (max-width: 480px){#video-editor-trim-root .save-button,#video-editor-trim-root .save-copy-button,#video-editor-trim-root .save-segments-button{font-size:.675rem;padding:.25rem}#video-editor-trim-root .controls-right,#video-editor-trim-root .controls-right button{margin:0}}#video-editor-trim-root .modal-success-content,#video-editor-trim-root .modal-error-content{display:flex;flex-direction:column;align-items:center;padding:1rem;text-align:center;padding:0;margin:0}#video-editor-trim-root .modal-success-icon,#video-editor-trim-root .modal-error-icon{margin-bottom:1rem}#video-editor-trim-root .modal-success-icon svg{color:#4caf50;animation:fadeIn .5s ease-in-out}#video-editor-trim-root .modal-error-icon svg{color:#f44336;animation:fadeIn .5s ease-in-out}#video-editor-trim-root .success-link{background-color:#4caf50;color:#fff;transition:background-color .3s}#video-editor-trim-root .success-link:hover{background-color:#388e3c}#video-editor-trim-root .error-message{color:#f44336;font-weight:500}#video-editor-trim-root .modal-spinner{display:flex;justify-content:center;margin:2rem 0}#video-editor-trim-root .spinner{width:50px;height:50px;border:5px solid rgba(0,0,0,.1);border-radius:50%;border-top-color:#06c;animation:spin 1s ease-in-out infinite}#video-editor-trim-root .text-center{text-align:center}#video-editor-trim-root .modal-message{margin-bottom:1rem;line-height:1.5}#video-editor-trim-root .modal-choice-button{display:flex;align-items:center;justify-content:center;padding:.75rem 1.25rem;background-color:#06c;color:#fff;border-radius:4px;text-decoration:none;margin:0 auto;cursor:pointer;font-weight:500;gap:.5rem;border:none;transition:background-color .3s}#video-editor-trim-root .modal-choice-button:hover{background-color:#0056b3}#video-editor-trim-root .modal-choice-button svg{flex-shrink:0}#video-editor-trim-root .centered-choice{margin:0 auto;min-width:180px}.mobile-timeline-overlay{position:absolute;top:0;left:0;width:100%;height:100%;background-color:#00000080;z-index:50;display:flex;justify-content:center;align-items:center;border-radius:.5rem;pointer-events:none}.mobile-timeline-message{background-color:#000c;border-radius:8px;padding:15px 25px;text-align:center;max-width:80%;animation:pulse 2s infinite}.mobile-timeline-message p{color:#fff;font-size:16px;margin:0 0 15px;font-weight:500}.mobile-play-icon{width:0;height:0;border-top:15px solid transparent;border-bottom:15px solid transparent;border-left:25px solid white;margin:0 auto}@keyframes pulse{0%{opacity:.7;transform:scale(1)}50%{opacity:1;transform:scale(1.05)}to{opacity:.7;transform:scale(1)}}.segments-playback-mode .tooltip-time-btn,.segments-playback-mode .tooltip-action-btn.play,.segments-playback-mode .tooltip-action-btn.pause{opacity:1;cursor:pointer}.segments-playback-mode .tooltip-time-btn[disabled],.segments-playback-mode .tooltip-action-btn[disabled]{opacity:.5!important;cursor:not-allowed!important}.segments-playback-mode [data-tooltip][disabled]:hover:before,.segments-playback-mode [data-tooltip][disabled]:hover:after{opacity:1!important;visibility:visible!important}.segments-playback-message{display:flex;align-items:center;background-color:#3b82f61a;color:#3b82f6;padding:6px 12px;border-radius:4px;font-weight:600;font-size:.875rem;animation:pulse 2s infinite}.segments-playback-message svg{height:1.25rem;width:1.25rem;margin-right:.5rem;color:#3b82f6}.two-row-tooltip{display:flex;flex-direction:column;background-color:#fff;padding:6px;border-radius:4px;box-shadow:0 2px 8px #00000026;position:relative;z-index:3000}.tooltip-time-btn[data-tooltip="Decrease by 100ms"],.tooltip-time-btn[data-tooltip="Increase by 100ms"]{display:none!important}.tooltip-row{display:flex;justify-content:space-between;align-items:center;gap:3px}.tooltip-row:first-child{margin-bottom:6px}.tooltip-time-btn{background-color:#f0f0f0!important;border:none!important;border-radius:4px!important;padding:4px 8px!important;font-size:.75rem!important;font-weight:500!important;color:#333!important;cursor:pointer!important;transition:background-color .2s!important;min-width:20px!important}.tooltip-time-btn:hover{background-color:#e0e0e0!important}.tooltip-time-display{font-family:monospace!important;font-size:.875rem!important;font-weight:600!important;color:#333!important;padding:4px 6px!important;background-color:#f7f7f7!important;border-radius:4px!important;min-width:100px!important;text-align:center!important;overflow:hidden!important}.tooltip-time-display.disabled{pointer-events:none!important;cursor:not-allowed!important;opacity:.6!important;user-select:none!important;-webkit-user-select:none!important;-moz-user-select:none!important;-ms-user-select:none!important}.tooltip-time-btn.disabled[data-tooltip]:hover:before,.tooltip-time-btn.disabled[data-tooltip]:hover:after,.tooltip-action-btn.disabled[data-tooltip]:hover:before,.tooltip-action-btn.disabled[data-tooltip]:hover:after{opacity:1!important;visibility:visible!important}.tooltip-actions{display:flex;justify-content:space-between;align-items:center;gap:3px;position:relative;z-index:2500}.tooltip-action-btn{background-color:#f3f4f6;border:none;border-radius:4px;padding:5px;display:flex;align-items:center;justify-content:center;cursor:pointer;color:#4b5563;width:26px;height:26px;min-width:20px!important;position:relative}.tooltip-action-btn[data-tooltip]:before{content:attr(data-tooltip);position:absolute;height:30px;top:35px;left:50%;transform:translate(-50%);margin-left:0;background-color:#000000d9;color:#fff;text-align:left;padding:6px 12px;border-radius:4px;box-shadow:0 2px 8px #0003;font-size:12px;white-space:nowrap;opacity:0;visibility:hidden;transition:opacity .2s,visibility .2s;z-index:2500;pointer-events:none}.tooltip-action-btn[data-tooltip]:after{content:"";position:absolute;top:35px;left:50%;transform:translate(-50%);border-width:4px;border-style:solid;border-color:rgba(0,0,0,.85) transparent transparent transparent;margin-left:0;opacity:0;visibility:hidden;transition:opacity .2s,visibility .2s;z-index:2500;pointer-events:none}@media (hover: hover) and (pointer: fine){.tooltip-action-btn[data-tooltip]:hover:before,.tooltip-action-btn[data-tooltip]:hover:after{opacity:1;visibility:visible}}@media (pointer: coarse){.tooltip-action-btn[data-tooltip]:before,.tooltip-action-btn[data-tooltip]:after{display:none!important;opacity:0!important;visibility:hidden!important;pointer-events:none!important;content:none!important}}.tooltip-action-btn:hover{background-color:#e5e7eb;color:#111827}.tooltip-action-btn.delete{color:#ef4444}.tooltip-action-btn.delete:hover{background-color:#fee2e2}.tooltip-action-btn.play{color:#10b981}.tooltip-action-btn.play:hover{background-color:#d1fae5}.tooltip-action-btn.pause{color:#3b82f6}.tooltip-action-btn.pause:hover{background-color:#dbeafe}.tooltip-action-btn.play-from-start{color:#4f46e5}.tooltip-action-btn.play-from-start:hover{background-color:#e0e7ff}.tooltip-action-btn svg{width:16px;height:16px}.tooltip-action-btn.new-segment{width:auto;height:auto;padding:6px 10px;display:flex;flex-direction:row;color:#10b981}.tooltip-action-btn.new-segment:hover{background-color:#d1fae5}.tooltip-action-btn.new-segment .tooltip-btn-text{margin-left:6px;font-size:.75rem;white-space:nowrap}.tooltip-action-btn.disabled{opacity:.5;cursor:not-allowed;background-color:#f3f4f6}.tooltip-action-btn.disabled:hover{background-color:#f3f4f6;color:#9ca3af}.tooltip-action-btn.disabled svg{color:#9ca3af}.tooltip-action-btn.disabled .tooltip-btn-text{color:#9ca3af}.tooltip-action-btn.pause.disabled{color:#9ca3af!important;opacity:.5;cursor:not-allowed}.tooltip-action-btn.pause.disabled:hover{background-color:#f3f4f6!important;color:#9ca3af!important}.tooltip-action-btn.play.disabled{color:#9ca3af!important;opacity:.5;cursor:not-allowed}.tooltip-action-btn.play.disabled:hover{background-color:#f3f4f6!important;color:#9ca3af!important}.tooltip-time-btn.disabled{opacity:.5!important;cursor:not-allowed!important;background-color:#f3f4f6!important;color:#9ca3af!important}.tooltip-time-btn.disabled:hover{background-color:#f3f4f6!important;color:#9ca3af!important}@media (max-width: 768px){.two-row-tooltip{padding:4px}.tooltip-row:first-child{margin-bottom:4px}.tooltip-time-btn{min-width:20px!important;font-size:.7rem!important;padding:3px 6px!important}.tooltip-time-display{font-size:.8rem!important;padding:3px 4px!important;min-width:90px!important}.tooltip-action-btn{width:24px;height:24px;padding:4px}.tooltip-action-btn.new-segment{padding:4px 8px}.tooltip-action-btn svg{width:14px;height:14px}.tooltip-action-btn[data-tooltip]:before{min-width:100px;font-size:11px;padding:4px 8px;height:24px;top:33px}.tooltip-action-btn[data-tooltip]:after{top:33px}}#video-editor-trim-root{@keyframes bluePulse{0%{box-shadow:0 0 #3b82f666}50%{box-shadow:0 0 0 8px #3b82f600}to{box-shadow:0 0 #3b82f600}}@keyframes pulse{0%{opacity:.8}50%{opacity:1}to{opacity:.8}}}#video-editor-trim-root .editing-tools-container{background-color:#fff;border-radius:.5rem;padding:1rem;margin-bottom:2.5rem;box-shadow:0 1px 2px #0000000d}#video-editor-trim-root .flex-container{display:flex;justify-content:space-between;align-items:center;position:relative;gap:15px;width:100%}#video-editor-trim-root .flex-container.single-row{flex-wrap:nowrap}#video-editor-trim-root .full-text{display:inline}#video-editor-trim-root .short-text{display:none}#video-editor-trim-root .reset-text{display:inline}#video-editor-trim-root .button-group{display:flex;align-items:center}#video-editor-trim-root .button-group.play-buttons-group{gap:.75rem;justify-content:flex-start;flex:0 0 auto}#video-editor-trim-root .button-group.secondary{gap:.75rem;align-items:center;justify-content:flex-end;margin-left:auto}#video-editor-trim-root .button-group button{display:flex;align-items:center;color:#333;background:none;border:none;cursor:pointer;min-width:auto}#video-editor-trim-root .button-group button:hover:not(:disabled){color:inherit}#video-editor-trim-root .button-group button:disabled{opacity:.5;cursor:not-allowed}#video-editor-trim-root .button-group button svg{height:1.25rem;width:1.25rem;margin-right:.25rem}#video-editor-trim-root .divider{border-right:1px solid #d1d5db;height:1.5rem;margin:0 .5rem}#video-editor-trim-root .play-button,#video-editor-trim-root .preview-button{font-weight:600;display:flex;align-items:center;position:relative;overflow:hidden;min-width:80px;justify-content:center;font-size:.875rem!important}#video-editor-trim-root .play-button.greyed-out{opacity:.5;cursor:not-allowed}#video-editor-trim-root .segments-button.highlighted-stop{background-color:#3b82f61a;color:#3b82f6;border:1px solid #3b82f6;animation:bluePulse 2s infinite}#video-editor-trim-root .play-button:hover:not(:disabled),#video-editor-trim-root .preview-button:hover:not(:disabled){color:inherit!important;transform:none!important;font-size:.875rem!important;width:auto!important;background:none!important}#video-editor-trim-root .play-button svg,#video-editor-trim-root .preview-button svg{height:1.5rem;width:1.5rem;flex-shrink:0}#video-editor-trim-root .button-text{margin-left:.25rem}@media (max-width: 992px){#video-editor-trim-root .button-group.secondary .button-text{display:none}}@media (max-width: 768px){#video-editor-trim-root .flex-container.single-row{justify-content:space-between}#video-editor-trim-root .button-group{gap:.5rem}#video-editor-trim-root .preview-button,#video-editor-trim-root .play-button{font-size:.875rem!important}}@media (max-width: 640px){#video-editor-trim-root .editing-tools-container{padding:.75rem;overflow-x:hidden}#video-editor-trim-root .preview-button{min-width:auto}#video-editor-trim-root .full-text{display:none}#video-editor-trim-root .short-text{display:inline;margin-left:.15rem}#video-editor-trim-root .reset-text{display:none}#video-editor-trim-root .button-group.play-buttons-group{flex:initial;justify-content:flex-start;flex-shrink:0}#video-editor-trim-root .button-group.secondary{flex:initial;justify-content:flex-end;flex-shrink:0}#video-editor-trim-root .button-group button{padding:.375rem;min-width:auto}#video-editor-trim-root .button-group button svg{height:1.125rem;width:1.125rem;margin-right:.125rem}}@media (max-width: 576px){#video-editor-trim-root .flex-container.single-row{justify-content:space-between;flex-wrap:nowrap;gap:10px}#video-editor-trim-root .button-group.play-buttons-group{justify-content:flex-start;flex:0 0 auto}#video-editor-trim-root .button-group.secondary{justify-content:flex-end;margin-left:auto}#video-editor-trim-root .button-group button{padding:.25rem}#video-editor-trim-root .divider{margin:0 .25rem}}@media (max-width: 480px){#video-editor-trim-root .editing-tools-container{padding:.5rem}#video-editor-trim-root .flex-container.single-row{gap:8px}#video-editor-trim-root .button-group.play-buttons-group,#video-editor-trim-root .button-group.secondary{gap:.25rem}#video-editor-trim-root .divider{display:none}#video-editor-trim-root .button-group button{padding:.125rem}#video-editor-trim-root .button-group button svg{height:1rem;width:1rem;margin-right:0}#video-editor-trim-root .button-text,#video-editor-trim-root .reset-text{display:none}}@media (max-width: 640px) and (orientation: portrait){#video-editor-trim-root .editing-tools-container{width:100%;box-sizing:border-box}#video-editor-trim-root .flex-container.single-row{width:100%;padding:0;margin:0}#video-editor-trim-root .button-group{max-width:50%}#video-editor-trim-root .button-group.play-buttons-group{max-width:60%}#video-editor-trim-root .button-group.secondary{max-width:40%}}@media (hover: hover) and (pointer: fine){#video-editor-trim-root [data-tooltip]{position:relative}#video-editor-trim-root [data-tooltip]:before{content:attr(data-tooltip);position:absolute;bottom:100%;left:50%;transform:translate(-50%);margin-bottom:5px;background-color:#000c;color:#fff;text-align:center;padding:5px 10px;border-radius:3px;font-size:12px;white-space:nowrap;opacity:0;visibility:hidden;transition:opacity .2s,visibility .2s;z-index:1000;pointer-events:none}#video-editor-trim-root [data-tooltip]:after{content:"";position:absolute;bottom:100%;left:50%;transform:translate(-50%);border-width:5px;border-style:solid;border-color:rgba(0,0,0,.8) transparent transparent transparent;opacity:0;visibility:hidden;transition:opacity .2s,visibility .2s;pointer-events:none}#video-editor-trim-root [data-tooltip]:hover:before,#video-editor-trim-root [data-tooltip]:hover:after{opacity:1;visibility:visible}}@media (pointer: coarse){#video-editor-trim-root [data-tooltip]:before,#video-editor-trim-root [data-tooltip]:after{display:none!important;content:none!important;opacity:0!important;visibility:hidden!important;pointer-events:none!important}}#video-editor-trim-root .clip-segments-container{margin-top:1rem;background-color:#fff;border-radius:.5rem;padding:1rem;box-shadow:0 1px 2px #0000000d}#video-editor-trim-root .clip-segments-title{font-size:.875rem;font-weight:500;color:var(--foreground, #333);margin-bottom:.75rem}#video-editor-trim-root .segment-item{display:flex;align-items:center;justify-content:space-between;padding:.5rem;border:1px solid #e5e7eb;border-radius:.25rem;margin-bottom:.5rem;transition:box-shadow .2s ease}#video-editor-trim-root .segment-item:hover{box-shadow:0 4px 6px -1px #0000001a}#video-editor-trim-root .segment-content{display:flex;align-items:center}#video-editor-trim-root .segment-thumbnail{width:4rem;height:2.25rem;background-size:cover;background-position:center;border-radius:.25rem;margin-right:.75rem;box-shadow:0 0 0 1px #ffffff4d}#video-editor-trim-root .segment-info{display:flex;flex-direction:column}#video-editor-trim-root .segment-title{font-weight:500;font-size:.875rem;color:#000}#video-editor-trim-root .segment-time{font-size:.75rem;color:#000}#video-editor-trim-root .segment-duration{font-size:.75rem;margin-top:.25rem;display:inline-block;background-color:#f3f4f6;padding:0 .5rem;border-radius:.25rem;color:#000}#video-editor-trim-root .segment-actions{display:flex;align-items:center;gap:.5rem}#video-editor-trim-root .delete-button{padding:.375rem;color:#4b5563;background-color:#e5e7eb;border-radius:9999px;border:none;cursor:pointer;transition:background-color .2s,color .2s;min-width:auto}#video-editor-trim-root .delete-button:hover{color:#000;background-color:#d1d5db}#video-editor-trim-root .delete-button svg{height:1rem;width:1rem}#video-editor-trim-root .empty-message{padding:1rem;text-align:center;color:#333333b3}#video-editor-trim-root .segment-color-1{background-color:#3b82f626}#video-editor-trim-root .segment-color-2{background-color:#10b98126}#video-editor-trim-root .segment-color-3{background-color:#f59e0b26}#video-editor-trim-root .segment-color-4{background-color:#ef444426}#video-editor-trim-root .segment-color-5{background-color:#8b5cf626}#video-editor-trim-root .segment-color-6{background-color:#ec489926}#video-editor-trim-root .segment-color-7{background-color:#06b6d426}#video-editor-trim-root .segment-color-8{background-color:#facc1526}.mobile-play-prompt-overlay{position:fixed;top:0;left:0;width:100%;height:100%;background-color:#000000b3;display:flex;justify-content:center;align-items:center;z-index:1000;backdrop-filter:blur(5px);-webkit-backdrop-filter:blur(5px)}.mobile-play-prompt{background-color:#fff;width:90%;max-width:400px;border-radius:12px;padding:25px;box-shadow:0 4px 20px #00000040;text-align:center}.mobile-play-prompt h3{margin:0 0 15px;font-size:20px;color:#333;font-weight:600}.mobile-play-prompt p{margin:0 0 15px;font-size:16px;color:#444;line-height:1.5}.mobile-prompt-instructions{margin:20px 0;text-align:left;background-color:#f8f9fa;padding:15px;border-radius:8px}.mobile-prompt-instructions p{margin:0 0 8px;font-size:15px;font-weight:500}.mobile-prompt-instructions ol{margin:0;padding-left:22px}.mobile-prompt-instructions li{margin-bottom:8px;font-size:14px;color:#333}.mobile-play-button{background-color:#007bff;color:#fff;border:none;border-radius:8px;padding:12px 25px;font-size:16px;font-weight:500;cursor:pointer;transition:background-color .2s;margin-top:5px;min-height:44px;min-width:200px}.mobile-play-button:hover{background-color:#0069d9}.mobile-play-button:active{background-color:#0062cc;transform:scale(.98)}@supports (-webkit-touch-callout: none){.mobile-play-button{padding:14px 25px}}*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html,:host{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}*{border-color:hsl(var(--border))}.container{width:100%}@media (min-width: 640px){.container{max-width:640px}}@media (min-width: 768px){.container{max-width:768px}}@media (min-width: 1024px){.container{max-width:1024px}}@media (min-width: 1280px){.container{max-width:1280px}}@media (min-width: 1536px){.container{max-width:1536px}}.visible{visibility:visible}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.mx-auto{margin-left:auto;margin-right:auto}.mb-2{margin-bottom:.5rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.inline{display:inline}.flex{display:flex}.hidden{display:none}.min-h-screen{min-height:100vh}.w-full{width:100%}.max-w-6xl{max-width:72rem}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.resize{resize:both}.items-center{align-items:center}.justify-center{justify-content:center}.gap-4{gap:1rem}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.rounded-md{border-radius:calc(var(--radius) - 2px)}.border{border-width:1px}.bg-background{background-color:hsl(var(--background))}.bg-indigo-600{--tw-bg-opacity: 1;background-color:rgb(79 70 229 / var(--tw-bg-opacity, 1))}.bg-purple-500{--tw-bg-opacity: 1;background-color:rgb(168 85 247 / var(--tw-bg-opacity, 1))}.px-4{padding-left:1rem;padding-right:1rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-6{padding-top:1.5rem;padding-bottom:1.5rem}.text-center{text-align:center}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xs{font-size:.75rem;line-height:1rem}.text-gray-500{--tw-text-opacity: 1;color:rgb(107 114 128 / var(--tw-text-opacity, 1))}.text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.underline{text-decoration-line:underline}.filter{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)}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}@keyframes enter{0%{opacity:var(--tw-enter-opacity, 1);transform:translate3d(var(--tw-enter-translate-x, 0),var(--tw-enter-translate-y, 0),0) scale3d(var(--tw-enter-scale, 1),var(--tw-enter-scale, 1),var(--tw-enter-scale, 1)) rotate(var(--tw-enter-rotate, 0))}}@keyframes exit{to{opacity:var(--tw-exit-opacity, 1);transform:translate3d(var(--tw-exit-translate-x, 0),var(--tw-exit-translate-y, 0),0) scale3d(var(--tw-exit-scale, 1),var(--tw-exit-scale, 1),var(--tw-exit-scale, 1)) rotate(var(--tw-exit-rotate, 0))}}.paused{animation-play-state:paused}:root{--foreground: 20 14.3% 4.1%;--muted: 60 4.8% 95.9%;--muted-foreground: 25 5.3% 44.7%;--popover: 0 0% 100%;--popover-foreground: 20 14.3% 4.1%;--card: 0 0% 100%;--card-foreground: 20 14.3% 4.1%;--border: 20 5.9% 90%;--input: 20 5.9% 90%;--primary: 207 90% 54%;--primary-foreground: 211 100% 99%;--secondary: 30 84% 54%;--secondary-foreground: 60 9.1% 97.8%;--accent: 60 4.8% 95.9%;--accent-foreground: 24 9.8% 10%;--destructive: 0 84.2% 60.2%;--destructive-foreground: 60 9.1% 97.8%;--ring: 20 14.3% 4.1%;--radius: .5rem}.video-player{position:relative;width:100%;background-color:#000;overflow:hidden;border-radius:.5rem}.video-controls{position:absolute;bottom:0;left:0;right:0;background:linear-gradient(to top,rgba(0,0,0,.8),transparent);padding:1rem;display:flex;flex-direction:column}.video-current-time{color:#fff;font-weight:500}.video-progress{position:relative;height:4px;background-color:#ffffff4d;border-radius:2px;margin-bottom:1rem}.video-progress-fill{position:absolute;left:0;top:0;height:100%;background-color:hsl(var(--primary));border-radius:2px}.video-scrubber{position:absolute;width:12px;height:12px;margin-left:-6px;background-color:#fff;border-radius:50%;top:-4px}.video-player-container{position:relative;overflow:hidden}.play-pause-indicator{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:70px;height:70px;border-radius:50%;background-color:#00000080;z-index:20;opacity:0;transition:opacity .2s ease;pointer-events:none;background-position:center;background-repeat:no-repeat}.play-icon{background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' width='36' height='36' fill='white'%3E%3Cpath d='M8 5v14l11-7z'/%3E%3C/svg%3E")}.pause-icon{background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' width='36' height='36' fill='white'%3E%3Cpath d='M6 19h4V5H6v14zm8-14v14h4V5h-4z'/%3E%3C/svg%3E")}.video-player-container:hover .play-pause-indicator{opacity:1}.timeline-scroll-container{height:6rem;border-radius:.375rem;overflow-x:auto;overflow-y:hidden;margin-bottom:.75rem;background-color:#eee;position:relative}.timeline-container{position:relative;background-color:#eee;height:6rem;width:100%;cursor:pointer;transition:width .3s ease}.timeline-marker{position:absolute;top:-10px;height:calc(100% + 10px);width:2px;background-color:red;z-index:100;pointer-events:none;box-shadow:0 0 4px #ff000080}.trim-line-marker{position:absolute;top:0;bottom:0;width:2px;background-color:#007bffe6;z-index:10}.trim-handle{width:8px;background-color:#6c757de6;position:absolute;top:0;bottom:0;cursor:ew-resize;z-index:15}.trim-handle.left{left:-4px}.trim-handle.right{right:-4px}.timeline-thumbnail{height:100%;border-right:1px solid rgba(0,0,0,.1);position:relative;display:inline-block;background-size:cover;background-position:center}.split-point{position:absolute;width:2px;background-color:#6c757de6;top:0;bottom:0;z-index:5}.clip-segment{position:absolute;height:95%;top:0;border-radius:4px;background-size:cover;background-position:center;background-blend-mode:soft-light;box-shadow:0 2px 8px #0003;overflow:hidden;cursor:grab;-webkit-user-select:none;-moz-user-select:none;user-select:none;transition:box-shadow .2s,transform .1s;z-index:15}.clip-segment:nth-child(odd),.segment-color-1,.segment-color-3,.segment-color-5,.segment-color-7{background-color:transparent;border:2px solid rgba(0,123,255,.9)}.clip-segment:nth-child(2n),.segment-color-2,.segment-color-4,.segment-color-6,.segment-color-8{background-color:transparent;border:2px solid rgba(108,117,125,.9)}.clip-segment:hover{box-shadow:0 4px 12px #0000004d;transform:translateY(-1px);filter:brightness(1.1)}.clip-segment:active{cursor:grabbing;box-shadow:0 2px 6px #0000004d;transform:translateY(0)}.clip-segment.selected{border-width:3px;box-shadow:0 4px 12px #0006;z-index:25;filter:brightness(1.2)}.clip-segment-info{background-color:#e2e6eae6;color:#000;padding:6px 8px;font-size:.7rem;position:absolute;top:0;left:0;width:100%;border-radius:4px 4px 0 0;z-index:2;display:flex;flex-direction:column;gap:2px}.clip-segment-name{font-weight:700;color:#000}.clip-segment-time{font-size:.65rem;color:#000}.clip-segment-duration{font-size:.65rem;color:#000;background:#b3d9ff66;padding:1px 4px;border-radius:2px;display:inline-block;margin-top:2px}.clip-segment-handle{position:absolute;width:8px;top:0;bottom:0;background-color:#6c757de6;cursor:ew-resize;z-index:20;display:flex;align-items:center;justify-content:center}.clip-segment-handle:after{content:"↔";color:#fff;font-size:12px;text-shadow:0 0 2px rgba(0,0,0,.8)}.clip-segment-handle.left{left:0}.clip-segment-handle.right{right:0}.clip-segment-handle:hover{background-color:#007bffe6;width:10px}input[type=range]{-webkit-appearance:none;height:6px;background:#e0e0e0;border-radius:3px}input[type=range]::-webkit-slider-thumb{-webkit-appearance:none;height:16px;width:16px;border-radius:50%;background:#007bffe6;cursor:pointer}[data-tooltip]{position:relative;cursor:pointer}[data-tooltip]:before{content:attr(data-tooltip);position:absolute;bottom:100%;left:50%;transform:translate(-50%);margin-bottom:8px;background-color:#000c;color:#fff;padding:5px 10px;border-radius:4px;font-size:.8rem;white-space:nowrap;z-index:1000;opacity:0;visibility:hidden;transition:opacity .2s,visibility .2s;pointer-events:none}[data-tooltip]:after{content:"";position:absolute;bottom:100%;left:50%;transform:translate(-50%);border-width:5px;border-style:solid;border-color:rgba(0,0,0,.8) transparent transparent transparent;margin-bottom:0;opacity:0;visibility:hidden;transition:opacity .2s,visibility .2s;pointer-events:none}@media (hover: hover) and (pointer: fine){[data-tooltip]:hover:before,[data-tooltip]:hover:after{opacity:1;visibility:visible}}@media (pointer: coarse){[data-tooltip]:before,[data-tooltip]:after{display:none!important;content:none!important;opacity:0!important;visibility:hidden!important;pointer-events:none!important}}button[disabled][data-tooltip]:before,button[disabled][data-tooltip]:after{opacity:.5}.tooltip-action-btn{position:relative}.tooltip-action-btn[data-tooltip]:before,.tooltip-action-btn[data-tooltip]:after{opacity:0;visibility:hidden;position:absolute;pointer-events:none;transition:all .3s ease}.tooltip-action-btn[data-tooltip]:before{content:attr(data-tooltip);background-color:#000c;color:#fff;font-size:12px;padding:4px 8px;border-radius:3px;white-space:nowrap;bottom:-35px;left:50%;transform:translate(-50%);z-index:9999}.tooltip-action-btn[data-tooltip]:after{content:"";border-width:5px;border-style:solid;border-color:transparent transparent rgba(0,0,0,.8) transparent;bottom:-15px;left:50%;transform:translate(-50%);z-index:9999}@media (hover: hover) and (pointer: fine){.tooltip-action-btn:hover[data-tooltip]:before,.tooltip-action-btn:hover[data-tooltip]:after{opacity:1;visibility:visible}}.segment-tooltip{background-color:#b3d9fff2;color:#000;border-radius:4px;padding:6px;min-width:140px;z-index:1000;box-shadow:0 3px 10px #0003}.segment-tooltip:after{content:"";position:absolute;bottom:-6px;left:50%;transform:translate(-50%);width:0;height:0;border-left:6px solid transparent;border-right:6px solid transparent;border-top:6px solid rgba(179,217,255,.95)}.tooltip-time{font-size:.85rem;font-weight:700;text-align:center;margin-bottom:6px;color:#000}.tooltip-actions{display:flex;justify-content:space-between;gap:5px;position:relative}.tooltip-action-btn{background-color:#007bff33;border:none;border-radius:3px;width:30px;height:30px;display:flex;align-items:center;justify-content:center;cursor:pointer;padding:6px;transition:background-color .2s;min-width:20px!important}.tooltip-action-btn:hover{background-color:#007bff66}.tooltip-action-btn svg{width:100%;height:100%;stroke:currentColor}.tooltip-action-btn.set-in svg,.tooltip-action-btn.set-out svg{width:100%;height:100%;margin:0 auto;fill:currentColor;stroke:none}.empty-space-tooltip{background-color:#fff;border-radius:6px;box-shadow:0 2px 8px #00000026;padding:8px;z-index:50;min-width:120px;text-align:center;position:relative}.empty-space-tooltip:after{content:"";position:absolute;bottom:-8px;left:50%;transform:translate(-50%);border-width:8px 8px 0;border-style:solid;border-color:white transparent transparent}.tooltip-action-btn.new-segment{width:auto;padding:6px 10px;display:flex;align-items:center;gap:5px}.tooltip-btn-text{font-size:.8rem;white-space:nowrap;color:#000}.icon-new-segment{width:20px;height:20px}.zoom-dropdown-container{position:relative}.zoom-button{display:flex;align-items:center;gap:6px;background-color:#6c757dcc;color:#fff;border:none;border-radius:4px;padding:8px 12px;font-weight:500;cursor:pointer;transition:background-color .2s}.zoom-button:hover{background-color:#6c757d}.zoom-dropdown{background-color:#fff;border-radius:4px;box-shadow:0 2px 10px #00000026;max-height:300px;overflow-y:auto}.zoom-option{padding:8px 12px;cursor:pointer;display:flex;align-items:center;gap:5px}.zoom-option:hover{background-color:#007bff1a}.zoom-option.selected{background-color:#007bff33;font-weight:500}.save-button,.save-copy-button,.save-segments-button{background-color:#007bffcc;color:#fff;border:none;border-radius:4px;padding:8px 12px;font-weight:500;cursor:pointer;transition:background-color .2s}.save-button:hover,.save-copy-button:hover{background-color:#007bff}.save-copy-button{background-color:#6c757dcc}.save-copy-button:hover{background-color:#6c757d}.time-nav-label{font-weight:500;font-size:.9rem}.time-input{padding:6px 10px;border-radius:4px;border:1px solid #ccc;width:150px;font-family:monospace}.time-button-group{display:flex;gap:5px}.time-button{background-color:#6c757dcc;color:#fff;border:none;border-radius:4px;padding:6px 8px;font-size:.8rem;cursor:pointer;transition:background-color .2s}.time-button:hover{background-color:#6c757d}.timeline-controls{display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;padding:12px;background-color:#f5f5f5;border-radius:6px;margin-top:15px}.time-navigation{display:flex;align-items:center;gap:10px;flex-wrap:wrap}.controls-right{display:flex;align-items:center;gap:10px}@media (max-width: 768px){.timeline-controls{flex-direction:column;align-items:flex-start;gap:15px}.controls-right{margin-top:10px;width:100%;justify-content:flex-start;text-align:center;align-items:center;justify-content:center}}.timeline-header{display:flex;align-items:center;gap:20px;margin-bottom:10px;flex-wrap:wrap}.timeline-title{font-weight:700;margin-right:20px}.timeline-title-text{font-size:1.1rem}.current-time,.duration-time{white-space:nowrap}.time-code{font-family:monospace;font-weight:500}@media (max-width: 480px){.timeline-header{flex-direction:column;align-items:flex-start;gap:8px}.time-navigation{width:100%;flex-direction:column;align-items:flex-start;gap:10px}.time-button-group{width:100%;display:flex;justify-content:space-between;margin-top:10px}.controls-right{flex-wrap:wrap;gap:8px}.save-button,.save-copy-button{margin-top:8px;width:100%}.zoom-dropdown-container{width:100%}.zoom-button{width:100%;justify-content:center}}
diff --git a/static/video_editor/video-editor.js b/static/video_editor/video-editor.js
index a5003ec9..40d8020f 100644
--- a/static/video_editor/video-editor.js
+++ b/static/video_editor/video-editor.js
@@ -1,4 +1,4 @@
-(function(){"use strict";var dm={exports:{}},Kc={exports:{}},rl={exports:{}};rl.exports;var vm;function HS(){return vm||(vm=1,function(C,k){/**
+(function(){"use strict";var dm={exports:{}},Kc={exports:{}},al={exports:{}};al.exports;var vm;function z0(){return vm||(vm=1,function(w,L){/**
* @license React
* react.development.js
*
@@ -6,29 +6,29 @@
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
- */(function(){typeof __REACT_DEVTOOLS_GLOBAL_HOOK__<"u"&&typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart=="function"&&__REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart(new Error);var me="18.3.1",Se=Symbol.for("react.element"),ve=Symbol.for("react.portal"),Ee=Symbol.for("react.fragment"),d=Symbol.for("react.strict_mode"),Z=Symbol.for("react.profiler"),ye=Symbol.for("react.provider"),oe=Symbol.for("react.context"),rt=Symbol.for("react.forward_ref"),G=Symbol.for("react.suspense"),j=Symbol.for("react.suspense_list"),ne=Symbol.for("react.memo"),Ue=Symbol.for("react.lazy"),kt=Symbol.for("react.offscreen"),xt=Symbol.iterator,Rt="@@iterator";function Xe(s){if(s===null||typeof s!="object")return null;var h=xt&&s[xt]||s[Rt];return typeof h=="function"?h:null}var xe={current:null},mt={transition:null},Re={current:null,isBatchingLegacy:!1,didScheduleLegacyUpdate:!1},et={current:null},B={},Ot=null;function at(s){Ot=s}B.setExtraStackFrame=function(s){Ot=s},B.getCurrentStack=null,B.getStackAddendum=function(){var s="";Ot&&(s+=Ot);var h=B.getCurrentStack;return h&&(s+=h()||""),s};var Fe=!1,ee=!1,Je=!1,X=!1,At=!1,Be={ReactCurrentDispatcher:xe,ReactCurrentBatchConfig:mt,ReactCurrentOwner:et};Be.ReactDebugCurrentFrame=B,Be.ReactCurrentActQueue=Re;function I(s){{for(var h=arguments.length,M=new Array(h>1?h-1:0),O=1;O1?h-1:0),O=1;O1){for(var Wt=Array(jt),qt=0;qt1){for(var lt=Array(qt),nn=0;nn is not supported and will be removed in a future major release. Did you mean to render instead?")),h.Provider},set:function(be){h.Provider=be}},_currentValue:{get:function(){return h._currentValue},set:function(be){h._currentValue=be}},_currentValue2:{get:function(){return h._currentValue2},set:function(be){h._currentValue2=be}},_threadCount:{get:function(){return h._threadCount},set:function(be){h._threadCount=be}},Consumer:{get:function(){return M||(M=!0,$("Rendering is not supported and will be removed in a future major release. Did you mean to render instead?")),h.Consumer}},displayName:{get:function(){return h.displayName},set:function(be){K||(I("Setting `displayName` on Context.Consumer has no effect. You should set it directly on the context with Context.displayName = '%s'.",be),K=!0)}}}),h.Consumer=Oe}return h._currentRenderer=null,h._currentRenderer2=null,h}var ya=-1,Kn=0,y=1,F=2;function _(s){if(s._status===ya){var h=s._result,M=h();if(M.then(function(Oe){if(s._status===Kn||s._status===ya){var be=s;be._status=y,be._result=Oe}},function(Oe){if(s._status===Kn||s._status===ya){var be=s;be._status=F,be._result=Oe}}),s._status===ya){var O=s;O._status=Kn,O._result=M}}if(s._status===y){var K=s._result;return K===void 0&&$(`lazy: Expected the result of a dynamic import() call. Instead received: %s
+ */(function(){typeof __REACT_DEVTOOLS_GLOBAL_HOOK__<"u"&&typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart=="function"&&__REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart(new Error);var me="18.3.1",Ee=Symbol.for("react.element"),fe=Symbol.for("react.portal"),Re=Symbol.for("react.fragment"),d=Symbol.for("react.strict_mode"),Q=Symbol.for("react.profiler"),he=Symbol.for("react.provider"),te=Symbol.for("react.context"),bt=Symbol.for("react.forward_ref"),I=Symbol.for("react.suspense"),H=Symbol.for("react.suspense_list"),K=Symbol.for("react.memo"),Oe=Symbol.for("react.lazy"),zt=Symbol.for("react.offscreen"),qt=Symbol.iterator,St="@@iterator";function Pe(s){if(s===null||typeof s!="object")return null;var m=qt&&s[qt]||s[St];return typeof m=="function"?m:null}var _e={current:null},ot={transition:null},_={current:null,isBatchingLegacy:!1,didScheduleLegacyUpdate:!1},ne={current:null},De={},Vt=null;function Me(s){Vt=s}De.setExtraStackFrame=function(s){Vt=s},De.getCurrentStack=null,De.getStackAddendum=function(){var s="";Vt&&(s+=Vt);var m=De.getCurrentStack;return m&&(s+=m()||""),s};var pe=!1,nt=!1,oe=!1,Le=!1,Ke=!1,le={ReactCurrentDispatcher:_e,ReactCurrentBatchConfig:ot,ReactCurrentOwner:ne};le.ReactDebugCurrentFrame=De,le.ReactCurrentActQueue=_;function W(s){{for(var m=arguments.length,k=new Array(m>1?m-1:0),N=1;N1?m-1:0),N=1;N1){for(var Yt=Array(At),Wt=0;Wt1){for(var lt=Array(Wt),en=0;en is not supported and will be removed in a future major release. Did you mean to render instead?")),m.Provider},set:function(Se){m.Provider=Se}},_currentValue:{get:function(){return m._currentValue},set:function(Se){m._currentValue=Se}},_currentValue2:{get:function(){return m._currentValue2},set:function(Se){m._currentValue2=Se}},_threadCount:{get:function(){return m._threadCount},set:function(Se){m._threadCount=Se}},Consumer:{get:function(){return k||(k=!0,q("Rendering is not supported and will be removed in a future major release. Did you mean to render instead?")),m.Consumer}},displayName:{get:function(){return m.displayName},set:function(Se){X||(W("Setting `displayName` on Context.Consumer has no effect. You should set it directly on the context with Context.displayName = '%s'.",Se),X=!0)}}}),m.Consumer=Ne}return m._currentRenderer=null,m._currentRenderer2=null,m}var ma=-1,h=0,B=1,D=2;function T(s){if(s._status===ma){var m=s._result,k=m();if(k.then(function(Ne){if(s._status===h||s._status===ma){var Se=s;Se._status=B,Se._result=Ne}},function(Ne){if(s._status===h||s._status===ma){var Se=s;Se._status=D,Se._result=Ne}}),s._status===ma){var N=s;N._status=h,N._result=k}}if(s._status===B){var X=s._result;return X===void 0&&q(`lazy: Expected the result of a dynamic import() call. Instead received: %s
Your code should look like:
const MyComponent = lazy(() => import('./MyComponent'))
-Did you accidentally put curly braces around the import?`,K),"default"in K||$(`lazy: Expected the result of a dynamic import() call. Instead received: %s
+Did you accidentally put curly braces around the import?`,X),"default"in X||q(`lazy: Expected the result of a dynamic import() call. Instead received: %s
Your code should look like:
- const MyComponent = lazy(() => import('./MyComponent'))`,K),K.default}else throw s._result}function S(s){var h={_status:ya,_result:s},M={$$typeof:Ue,_payload:h,_init:_};{var O,K;Object.defineProperties(M,{defaultProps:{configurable:!0,get:function(){return O},set:function(Oe){$("React.lazy(...): It is not supported to assign `defaultProps` to a lazy component import. Either specify them where the component is defined, or create a wrapping component around it."),O=Oe,Object.defineProperty(M,"defaultProps",{enumerable:!0})}},propTypes:{configurable:!0,get:function(){return K},set:function(Oe){$("React.lazy(...): It is not supported to assign `propTypes` to a lazy component import. Either specify them where the component is defined, or create a wrapping component around it."),K=Oe,Object.defineProperty(M,"propTypes",{enumerable:!0})}}})}return M}function w(s){s!=null&&s.$$typeof===ne?$("forwardRef requires a render function but received a `memo` component. Instead of forwardRef(memo(...)), use memo(forwardRef(...))."):typeof s!="function"?$("forwardRef requires a render function but was given %s.",s===null?"null":typeof s):s.length!==0&&s.length!==2&&$("forwardRef render functions accept exactly two parameters: props and ref. %s",s.length===1?"Did you forget to use the ref parameter?":"Any additional parameter will be undefined."),s!=null&&(s.defaultProps!=null||s.propTypes!=null)&&$("forwardRef render functions do not support propTypes or defaultProps. Did you accidentally pass a React component?");var h={$$typeof:rt,render:s};{var M;Object.defineProperty(h,"displayName",{enumerable:!1,configurable:!0,get:function(){return M},set:function(O){M=O,!s.name&&!s.displayName&&(s.displayName=O)}})}return h}var c;c=Symbol.for("react.module.reference");function p(s){return!!(typeof s=="string"||typeof s=="function"||s===Ee||s===Z||At||s===d||s===G||s===j||X||s===kt||Fe||ee||Je||typeof s=="object"&&s!==null&&(s.$$typeof===Ue||s.$$typeof===ne||s.$$typeof===ye||s.$$typeof===oe||s.$$typeof===rt||s.$$typeof===c||s.getModuleId!==void 0))}function T(s,h){p(s)||$("memo: The first argument must be a component. Instead received: %s",s===null?"null":typeof s);var M={$$typeof:ne,type:s,compare:h===void 0?null:h};{var O;Object.defineProperty(M,"displayName",{enumerable:!1,configurable:!0,get:function(){return O},set:function(K){O=K,!s.name&&!s.displayName&&(s.displayName=K)}})}return M}function E(){var s=xe.current;return s===null&&$(`Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
+ const MyComponent = lazy(() => import('./MyComponent'))`,X),X.default}else throw s._result}function x(s){var m={_status:ma,_result:s},k={$$typeof:Oe,_payload:m,_init:T};{var N,X;Object.defineProperties(k,{defaultProps:{configurable:!0,get:function(){return N},set:function(Ne){q("React.lazy(...): It is not supported to assign `defaultProps` to a lazy component import. Either specify them where the component is defined, or create a wrapping component around it."),N=Ne,Object.defineProperty(k,"defaultProps",{enumerable:!0})}},propTypes:{configurable:!0,get:function(){return X},set:function(Ne){q("React.lazy(...): It is not supported to assign `propTypes` to a lazy component import. Either specify them where the component is defined, or create a wrapping component around it."),X=Ne,Object.defineProperty(k,"propTypes",{enumerable:!0})}}})}return k}function O(s){s!=null&&s.$$typeof===K?q("forwardRef requires a render function but received a `memo` component. Instead of forwardRef(memo(...)), use memo(forwardRef(...))."):typeof s!="function"?q("forwardRef requires a render function but was given %s.",s===null?"null":typeof s):s.length!==0&&s.length!==2&&q("forwardRef render functions accept exactly two parameters: props and ref. %s",s.length===1?"Did you forget to use the ref parameter?":"Any additional parameter will be undefined."),s!=null&&(s.defaultProps!=null||s.propTypes!=null)&&q("forwardRef render functions do not support propTypes or defaultProps. Did you accidentally pass a React component?");var m={$$typeof:bt,render:s};{var k;Object.defineProperty(m,"displayName",{enumerable:!1,configurable:!0,get:function(){return k},set:function(N){k=N,!s.name&&!s.displayName&&(s.displayName=N)}})}return m}var c;c=Symbol.for("react.module.reference");function y(s){return!!(typeof s=="string"||typeof s=="function"||s===Re||s===Q||Ke||s===d||s===I||s===H||Le||s===zt||pe||nt||oe||typeof s=="object"&&s!==null&&(s.$$typeof===Oe||s.$$typeof===K||s.$$typeof===he||s.$$typeof===te||s.$$typeof===bt||s.$$typeof===c||s.getModuleId!==void 0))}function S(s,m){y(s)||q("memo: The first argument must be a component. Instead received: %s",s===null?"null":typeof s);var k={$$typeof:K,type:s,compare:m===void 0?null:m};{var N;Object.defineProperty(k,"displayName",{enumerable:!1,configurable:!0,get:function(){return N},set:function(X){N=X,!s.name&&!s.displayName&&(s.displayName=X)}})}return k}function A(){var s=_e.current;return s===null&&q(`Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
-See https://reactjs.org/link/invalid-hook-call for tips about how to debug and fix this problem.`),s}function P(s){var h=E();if(s._context!==void 0){var M=s._context;M.Consumer===s?$("Calling useContext(Context.Consumer) is not supported, may cause bugs, and will be removed in a future major release. Did you mean to call useContext(Context) instead?"):M.Provider===s&&$("Calling useContext(Context.Provider) is not supported. Did you mean to call useContext(Context) instead?")}return h.useContext(s)}function ae(s){var h=E();return h.useState(s)}function H(s,h,M){var O=E();return O.useReducer(s,h,M)}function le(s){var h=E();return h.useRef(s)}function Qe(s,h){var M=E();return M.useEffect(s,h)}function _e(s,h){var M=E();return M.useInsertionEffect(s,h)}function ot(s,h){var M=E();return M.useLayoutEffect(s,h)}function fn(s,h){var M=E();return M.useCallback(s,h)}function _n(s,h){var M=E();return M.useMemo(s,h)}function en(s,h,M){var O=E();return O.useImperativeHandle(s,h,M)}function Ft(s,h){{var M=E();return M.useDebugValue(s,h)}}function Me(){var s=E();return s.useTransition()}function Dt(s){var h=E();return h.useDeferredValue(s)}function Zn(){var s=E();return s.useId()}function yr(s,h,M){var O=E();return O.useSyncExternalStore(s,h,M)}var Va=0,Br,ga,ba,$r,gi,dn,_t;function Sa(){}Sa.__reactDisabledLog=!0;function Ta(){{if(Va===0){Br=console.log,ga=console.info,ba=console.warn,$r=console.error,gi=console.group,dn=console.groupCollapsed,_t=console.groupEnd;var s={configurable:!0,enumerable:!0,value:Sa,writable:!0};Object.defineProperties(console,{info:s,log:s,warn:s,error:s,group:s,groupCollapsed:s,groupEnd:s})}Va++}}function sa(){{if(Va--,Va===0){var s={configurable:!0,enumerable:!0,writable:!0};Object.defineProperties(console,{log:Ze({},s,{value:Br}),info:Ze({},s,{value:ga}),warn:Ze({},s,{value:ba}),error:Ze({},s,{value:$r}),group:Ze({},s,{value:gi}),groupCollapsed:Ze({},s,{value:dn}),groupEnd:Ze({},s,{value:_t})})}Va<0&&$("disabledDepth fell below zero. This is a bug in React. Please file an issue.")}}var bi=Be.ReactCurrentDispatcher,Yr;function ao(s,h,M){{if(Yr===void 0)try{throw Error()}catch(K){var O=K.stack.trim().match(/\n( *(at )?)/);Yr=O&&O[1]||""}return`
-`+Yr+s}}var Si=!1,ro;{var ol=typeof WeakMap=="function"?WeakMap:Map;ro=new ol}function Yu(s,h){if(!s||Si)return"";{var M=ro.get(s);if(M!==void 0)return M}var O;Si=!0;var K=Error.prepareStackTrace;Error.prepareStackTrace=void 0;var Oe;Oe=bi.current,bi.current=null,Ta();try{if(h){var be=function(){throw Error()};if(Object.defineProperty(be.prototype,"props",{set:function(){throw Error()}}),typeof Reflect=="object"&&Reflect.construct){try{Reflect.construct(be,[])}catch(vn){O=vn}Reflect.construct(s,[],be)}else{try{be.call()}catch(vn){O=vn}s.call(be.prototype)}}else{try{throw Error()}catch(vn){O=vn}s()}}catch(vn){if(vn&&O&&typeof vn.stack=="string"){for(var qe=vn.stack.split(`
-`),ft=O.stack.split(`
-`),jt=qe.length-1,Wt=ft.length-1;jt>=1&&Wt>=0&&qe[jt]!==ft[Wt];)Wt--;for(;jt>=1&&Wt>=0;jt--,Wt--)if(qe[jt]!==ft[Wt]){if(jt!==1||Wt!==1)do if(jt--,Wt--,Wt<0||qe[jt]!==ft[Wt]){var qt=`
-`+qe[jt].replace(" at new "," at ");return s.displayName&&qt.includes("")&&(qt=qt.replace("",s.displayName)),typeof s=="function"&&ro.set(s,qt),qt}while(jt>=1&&Wt>=0);break}}}finally{Si=!1,bi.current=Oe,sa(),Error.prepareStackTrace=K}var lt=s?s.displayName||s.name:"",nn=lt?ao(lt):"";return typeof s=="function"&&ro.set(s,nn),nn}function ll(s,h,M){return Yu(s,!1)}function af(s){var h=s.prototype;return!!(h&&h.isReactComponent)}function Ti(s,h,M){if(s==null)return"";if(typeof s=="function")return Yu(s,af(s));if(typeof s=="string")return ao(s);switch(s){case G:return ao("Suspense");case j:return ao("SuspenseList")}if(typeof s=="object")switch(s.$$typeof){case rt:return ll(s.render);case ne:return Ti(s.type,h,M);case Ue:{var O=s,K=O._payload,Oe=O._init;try{return Ti(Oe(K),h,M)}catch{}}}return""}var Iu={},ul=Be.ReactDebugCurrentFrame;function Et(s){if(s){var h=s._owner,M=Ti(s.type,s._source,h?h.type:null);ul.setExtraStackFrame(M)}else ul.setExtraStackFrame(null)}function rf(s,h,M,O,K){{var Oe=Function.call.bind(ue);for(var be in s)if(Oe(s,be)){var qe=void 0;try{if(typeof s[be]!="function"){var ft=Error((O||"React class")+": "+M+" type `"+be+"` is invalid; it must be a function, usually from the `prop-types` package, but received `"+typeof s[be]+"`.This often happens because of typos such as `PropTypes.function` instead of `PropTypes.func`.");throw ft.name="Invariant Violation",ft}qe=s[be](h,be,O,M,null,"SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED")}catch(jt){qe=jt}qe&&!(qe instanceof Error)&&(Et(K),$("%s: type specification of %s `%s` is invalid; the type checker function must return `null` or an `Error` but returned a %s. You may have forgotten to pass an argument to the type checker creator (arrayOf, instanceOf, objectOf, oneOf, oneOfType, and shape all require an argument).",O||"React class",M,be,typeof qe),Et(null)),qe instanceof Error&&!(qe.message in Iu)&&(Iu[qe.message]=!0,Et(K),$("Failed %s type: %s",M,qe.message),Et(null))}}}function gr(s){if(s){var h=s._owner,M=Ti(s.type,s._source,h?h.type:null);at(M)}else at(null)}var Ke;Ke=!1;function sl(){if(et.current){var s=pe(et.current.type);if(s)return`
+See https://reactjs.org/link/invalid-hook-call for tips about how to debug and fix this problem.`),s}function ve(s){var m=A();if(s._context!==void 0){var k=s._context;k.Consumer===s?q("Calling useContext(Context.Consumer) is not supported, may cause bugs, and will be removed in a future major release. Did you mean to call useContext(Context) instead?"):k.Provider===s&&q("Calling useContext(Context.Provider) is not supported. Did you mean to call useContext(Context) instead?")}return m.useContext(s)}function z(s){var m=A();return m.useState(s)}function ue(s,m,k){var N=A();return N.useReducer(s,m,k)}function ye(s){var m=A();return m.useRef(s)}function ze(s,m){var k=A();return k.useEffect(s,m)}function it(s,m){var k=A();return k.useInsertionEffect(s,m)}function Ft(s,m){var k=A();return k.useLayoutEffect(s,m)}function nn(s,m){var k=A();return k.useCallback(s,m)}function Zt(s,m){var k=A();return k.useMemo(s,m)}function Pn(s,m,k){var N=A();return N.useImperativeHandle(s,m,k)}function ft(s,m){{var k=A();return k.useDebugValue(s,m)}}function be(){var s=A();return s.useTransition()}function Rn(s){var m=A();return m.useDeferredValue(s)}function Ua(){var s=A();return s.useId()}function il(s,m,k){var N=A();return N.useSyncExternalStore(s,m,k)}var Ha=0,ha,ya,$r,yi,un,_t,za;function oa(){}oa.__reactDisabledLog=!0;function hr(){{if(Ha===0){ha=console.log,ya=console.info,$r=console.warn,yi=console.error,un=console.group,_t=console.groupCollapsed,za=console.groupEnd;var s={configurable:!0,enumerable:!0,value:oa,writable:!0};Object.defineProperties(console,{info:s,log:s,warn:s,error:s,group:s,groupCollapsed:s,groupEnd:s})}Ha++}}function nr(){{if(Ha--,Ha===0){var s={configurable:!0,enumerable:!0,writable:!0};Object.defineProperties(console,{log:We({},s,{value:ha}),info:We({},s,{value:ya}),warn:We({},s,{value:$r}),error:We({},s,{value:yi}),group:We({},s,{value:un}),groupCollapsed:We({},s,{value:_t}),groupEnd:We({},s,{value:za})})}Ha<0&&q("disabledDepth fell below zero. This is a bug in React. Please file an issue.")}}var gi=le.ReactCurrentDispatcher,Pr;function no(s,m,k){{if(Pr===void 0)try{throw Error()}catch(X){var N=X.stack.trim().match(/\n( *(at )?)/);Pr=N&&N[1]||""}return`
+`+Pr+s}}var bi=!1,ao;{var ol=typeof WeakMap=="function"?WeakMap:Map;ao=new ol}function Yu(s,m){if(!s||bi)return"";{var k=ao.get(s);if(k!==void 0)return k}var N;bi=!0;var X=Error.prepareStackTrace;Error.prepareStackTrace=void 0;var Ne;Ne=gi.current,gi.current=null,hr();try{if(m){var Se=function(){throw Error()};if(Object.defineProperty(Se.prototype,"props",{set:function(){throw Error()}}),typeof Reflect=="object"&&Reflect.construct){try{Reflect.construct(Se,[])}catch(dn){N=dn}Reflect.construct(s,[],Se)}else{try{Se.call()}catch(dn){N=dn}s.call(Se.prototype)}}else{try{throw Error()}catch(dn){N=dn}s()}}catch(dn){if(dn&&N&&typeof dn.stack=="string"){for(var Ie=dn.stack.split(`
+`),dt=N.stack.split(`
+`),At=Ie.length-1,Yt=dt.length-1;At>=1&&Yt>=0&&Ie[At]!==dt[Yt];)Yt--;for(;At>=1&&Yt>=0;At--,Yt--)if(Ie[At]!==dt[Yt]){if(At!==1||Yt!==1)do if(At--,Yt--,Yt<0||Ie[At]!==dt[Yt]){var Wt=`
+`+Ie[At].replace(" at new "," at ");return s.displayName&&Wt.includes("")&&(Wt=Wt.replace("",s.displayName)),typeof s=="function"&&ao.set(s,Wt),Wt}while(At>=1&&Yt>=0);break}}}finally{bi=!1,gi.current=Ne,nr(),Error.prepareStackTrace=X}var lt=s?s.displayName||s.name:"",en=lt?no(lt):"";return typeof s=="function"&&ao.set(s,en),en}function ll(s,m,k){return Yu(s,!1)}function af(s){var m=s.prototype;return!!(m&&m.isReactComponent)}function Si(s,m,k){if(s==null)return"";if(typeof s=="function")return Yu(s,af(s));if(typeof s=="string")return no(s);switch(s){case I:return no("Suspense");case H:return no("SuspenseList")}if(typeof s=="object")switch(s.$$typeof){case bt:return ll(s.render);case K:return Si(s.type,m,k);case Oe:{var N=s,X=N._payload,Ne=N._init;try{return Si(Ne(X),m,k)}catch{}}}return""}var Wu={},ul=le.ReactDebugCurrentFrame;function xt(s){if(s){var m=s._owner,k=Si(s.type,s._source,m?m.type:null);ul.setExtraStackFrame(k)}else ul.setExtraStackFrame(null)}function rf(s,m,k,N,X){{var Ne=Function.call.bind(yt);for(var Se in s)if(Ne(s,Se)){var Ie=void 0;try{if(typeof s[Se]!="function"){var dt=Error((N||"React class")+": "+k+" type `"+Se+"` is invalid; it must be a function, usually from the `prop-types` package, but received `"+typeof s[Se]+"`.This often happens because of typos such as `PropTypes.function` instead of `PropTypes.func`.");throw dt.name="Invariant Violation",dt}Ie=s[Se](m,Se,N,k,null,"SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED")}catch(At){Ie=At}Ie&&!(Ie instanceof Error)&&(xt(X),q("%s: type specification of %s `%s` is invalid; the type checker function must return `null` or an `Error` but returned a %s. You may have forgotten to pass an argument to the type checker creator (arrayOf, instanceOf, objectOf, oneOf, oneOfType, and shape all require an argument).",N||"React class",k,Se,typeof Ie),xt(null)),Ie instanceof Error&&!(Ie.message in Wu)&&(Wu[Ie.message]=!0,xt(X),q("Failed %s type: %s",k,Ie.message),xt(null))}}}function yr(s){if(s){var m=s._owner,k=Si(s.type,s._source,m?m.type:null);Me(k)}else Me(null)}var Xe;Xe=!1;function sl(){if(ne.current){var s=Ge(ne.current.type);if(s)return`
-Check the render method of \``+s+"`."}return""}function Jn(s){if(s!==void 0){var h=s.fileName.replace(/^.*[\\\/]/,""),M=s.lineNumber;return`
+Check the render method of \``+s+"`."}return""}function Qn(s){if(s!==void 0){var m=s.fileName.replace(/^.*[\\\/]/,""),k=s.lineNumber;return`
-Check your code at `+h+":"+M+"."}return""}function Ei(s){return s!=null?Jn(s.__source):""}var Ir={};function of(s){var h=sl();if(!h){var M=typeof s=="string"?s:s.displayName||s.name;M&&(h=`
+Check your code at `+m+":"+k+"."}return""}function Ti(s){return s!=null?Qn(s.__source):""}var Yr={};function of(s){var m=sl();if(!m){var k=typeof s=="string"?s:s.displayName||s.name;k&&(m=`
-Check the top-level render call using <`+M+">.")}return h}function Mn(s,h){if(!(!s._store||s._store.validated||s.key!=null)){s._store.validated=!0;var M=of(h);if(!Ir[M]){Ir[M]=!0;var O="";s&&s._owner&&s._owner!==et.current&&(O=" It was passed a child from "+pe(s._owner.type)+"."),gr(s),$('Each child in a list should have a unique "key" prop.%s%s See https://reactjs.org/link/warning-keys for more information.',M,O),gr(null)}}}function tn(s,h){if(typeof s=="object"){if(Xt(s))for(var M=0;M",K=" Did you accidentally export a JSX literal instead of a component?"):be=typeof s,$("React.createElement: type is invalid -- expected a string (for built-in components) or a class/function (for composite components) but got: %s.%s",be,K)}var qe=Pe.apply(this,arguments);if(qe==null)return qe;if(O)for(var ft=2;ft10&&I("Detected a large number of updates inside startTransition. If this is due to a subscription please re-write it to use React provided hooks. Otherwise concurrent mode guarantees are off the table."),O._updatedFibers.clear()}}}var fl=!1,io=null;function uf(s){if(io===null)try{var h=("require"+Math.random()).slice(0,7),M=C&&C[h];io=M.call(C,"timers").setImmediate}catch{io=function(K){fl===!1&&(fl=!0,typeof MessageChannel>"u"&&$("This browser does not have a MessageChannel implementation, so enqueuing tasks via await act(async () => ...) will fail. Please file an issue at https://github.com/facebook/react/issues if you encounter this warning."));var Oe=new MessageChannel;Oe.port1.onmessage=K,Oe.port2.postMessage(void 0)}}return io(s)}var Wr=0,Ci=!1;function dl(s){{var h=Wr;Wr++,Re.current===null&&(Re.current=[]);var M=Re.isBatchingLegacy,O;try{if(Re.isBatchingLegacy=!0,O=s(),!M&&Re.didScheduleLegacyUpdate){var K=Re.current;K!==null&&(Re.didScheduleLegacyUpdate=!1,uo(K))}}catch(lt){throw br(h),lt}finally{Re.isBatchingLegacy=M}if(O!==null&&typeof O=="object"&&typeof O.then=="function"){var Oe=O,be=!1,qe={then:function(lt,nn){be=!0,Oe.then(function(vn){br(h),Wr===0?oo(vn,lt,nn):lt(vn)},function(vn){br(h),nn(vn)})}};return!Ci&&typeof Promise<"u"&&Promise.resolve().then(function(){}).then(function(){be||(Ci=!0,$("You called act(async () => ...) without await. This could lead to unexpected testing behaviour, interleaving multiple act calls and mixing their scopes. You should - await act(async () => ...);"))}),qe}else{var ft=O;if(br(h),Wr===0){var jt=Re.current;jt!==null&&(uo(jt),Re.current=null);var Wt={then:function(lt,nn){Re.current===null?(Re.current=[],oo(ft,lt,nn)):lt(ft)}};return Wt}else{var qt={then:function(lt,nn){lt(ft)}};return qt}}}}function br(s){s!==Wr-1&&$("You seem to have overlapping act() calls, this is not supported. Be sure to await previous act() calls before making a new one. "),Wr=s}function oo(s,h,M){{var O=Re.current;if(O!==null)try{uo(O),uf(function(){O.length===0?(Re.current=null,h(s)):oo(s,h,M)})}catch(K){M(K)}else h(s)}}var lo=!1;function uo(s){if(!lo){lo=!0;var h=0;try{for(;h.")}return m}function Dn(s,m){if(!(!s._store||s._store.validated||s.key!=null)){s._store.validated=!0;var k=of(m);if(!Yr[k]){Yr[k]=!0;var N="";s&&s._owner&&s._owner!==ne.current&&(N=" It was passed a child from "+Ge(s._owner.type)+"."),yr(s),q('Each child in a list should have a unique "key" prop.%s%s See https://reactjs.org/link/warning-keys for more information.',k,N),yr(null)}}}function Jt(s,m){if(typeof s=="object"){if(F(s))for(var k=0;k",X=" Did you accidentally export a JSX literal instead of a component?"):Se=typeof s,q("React.createElement: type is invalid -- expected a string (for built-in components) or a class/function (for composite components) but got: %s.%s",Se,X)}var Ie=Je.apply(this,arguments);if(Ie==null)return Ie;if(N)for(var dt=2;dt10&&W("Detected a large number of updates inside startTransition. If this is due to a subscription please re-write it to use React provided hooks. Otherwise concurrent mode guarantees are off the table."),N._updatedFibers.clear()}}}var fl=!1,ro=null;function uf(s){if(ro===null)try{var m=("require"+Math.random()).slice(0,7),k=w&&w[m];ro=k.call(w,"timers").setImmediate}catch{ro=function(X){fl===!1&&(fl=!0,typeof MessageChannel>"u"&&q("This browser does not have a MessageChannel implementation, so enqueuing tasks via await act(async () => ...) will fail. Please file an issue at https://github.com/facebook/react/issues if you encounter this warning."));var Ne=new MessageChannel;Ne.port1.onmessage=X,Ne.port2.postMessage(void 0)}}return ro(s)}var Wr=0,Ei=!1;function dl(s){{var m=Wr;Wr++,_.current===null&&(_.current=[]);var k=_.isBatchingLegacy,N;try{if(_.isBatchingLegacy=!0,N=s(),!k&&_.didScheduleLegacyUpdate){var X=_.current;X!==null&&(_.didScheduleLegacyUpdate=!1,lo(X))}}catch(lt){throw gr(m),lt}finally{_.isBatchingLegacy=k}if(N!==null&&typeof N=="object"&&typeof N.then=="function"){var Ne=N,Se=!1,Ie={then:function(lt,en){Se=!0,Ne.then(function(dn){gr(m),Wr===0?io(dn,lt,en):lt(dn)},function(dn){gr(m),en(dn)})}};return!Ei&&typeof Promise<"u"&&Promise.resolve().then(function(){}).then(function(){Se||(Ei=!0,q("You called act(async () => ...) without await. This could lead to unexpected testing behaviour, interleaving multiple act calls and mixing their scopes. You should - await act(async () => ...);"))}),Ie}else{var dt=N;if(gr(m),Wr===0){var At=_.current;At!==null&&(lo(At),_.current=null);var Yt={then:function(lt,en){_.current===null?(_.current=[],io(dt,lt,en)):lt(dt)}};return Yt}else{var Wt={then:function(lt,en){lt(dt)}};return Wt}}}}function gr(s){s!==Wr-1&&q("You seem to have overlapping act() calls, this is not supported. Be sure to await previous act() calls before making a new one. "),Wr=s}function io(s,m,k){{var N=_.current;if(N!==null)try{lo(N),uf(function(){N.length===0?(_.current=null,m(s)):io(s,m,k)})}catch(X){k(X)}else m(s)}}var oo=!1;function lo(s){if(!oo){oo=!0;var m=0;try{for(;m.")}return h}function Mn(s,h){if(!(
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
- */return function(){var C=Zc(),k=Symbol.for("react.element"),me=Symbol.for("react.portal"),Se=Symbol.for("react.fragment"),ve=Symbol.for("react.strict_mode"),Ee=Symbol.for("react.profiler"),d=Symbol.for("react.provider"),Z=Symbol.for("react.context"),ye=Symbol.for("react.forward_ref"),oe=Symbol.for("react.suspense"),rt=Symbol.for("react.suspense_list"),G=Symbol.for("react.memo"),j=Symbol.for("react.lazy"),ne=Symbol.for("react.offscreen"),Ue=Symbol.iterator,kt="@@iterator";function xt(c){if(c===null||typeof c!="object")return null;var p=Ue&&c[Ue]||c[kt];return typeof p=="function"?p:null}var Rt=C.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;function Xe(c){{for(var p=arguments.length,T=new Array(p>1?p-1:0),E=1;E
=1&&ot>=0&&le[_e]!==Qe[ot];)ot--;for(;_e>=1&&ot>=0;_e--,ot--)if(le[_e]!==Qe[ot]){if(_e!==1||ot!==1)do if(_e--,ot--,ot<0||le[_e]!==Qe[ot]){var fn=`
-`+le[_e].replace(" at new "," at ");return c.displayName&&fn.includes("")&&(fn=fn.replace("",c.displayName)),typeof c=="function"&&We.set(c,fn),fn}while(_e>=1&&ot>=0);break}}}finally{it=!1,$t.current=ae,st(),Error.prepareStackTrace=P}var _n=c?c.displayName||c.name:"",en=_n?Nt(_n):"";return typeof c=="function"&&We.set(c,en),en}function Xt(c,p,T){return xn(c,!1)}function cn(c){var p=c.prototype;return!!(p&&p.isReactComponent)}function Yt(c,p,T){if(c==null)return"";if(typeof c=="function")return xn(c,cn(c));if(typeof c=="string")return Nt(c);switch(c){case oe:return Nt("Suspense");case rt:return Nt("SuspenseList")}if(typeof c=="object")switch(c.$$typeof){case ye:return Xt(c.render);case G:return Yt(c.type,p,T);case j:{var E=c,P=E._payload,ae=E._init;try{return Yt(ae(P),p,T)}catch{}}}return""}var an=Object.prototype.hasOwnProperty,Rn={},U=Rt.ReactDebugCurrentFrame;function Y(c){if(c){var p=c._owner,T=Yt(c.type,c._source,p?p.type:null);U.setExtraStackFrame(T)}else U.setExtraStackFrame(null)}function pe(c,p,T,E,P){{var ae=Function.call.bind(an);for(var H in c)if(ae(c,H)){var le=void 0;try{if(typeof c[H]!="function"){var Qe=Error((E||"React class")+": "+T+" type `"+H+"` is invalid; it must be a function, usually from the `prop-types` package, but received `"+typeof c[H]+"`.This often happens because of typos such as `PropTypes.function` instead of `PropTypes.func`.");throw Qe.name="Invariant Violation",Qe}le=c[H](p,H,E,T,null,"SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED")}catch(_e){le=_e}le&&!(le instanceof Error)&&(Y(P),Xe("%s: type specification of %s `%s` is invalid; the type checker function must return `null` or an `Error` but returned a %s. You may have forgotten to pass an argument to the type checker creator (arrayOf, instanceOf, objectOf, oneOf, oneOfType, and shape all require an argument).",E||"React class",T,H,typeof le),Y(null)),le instanceof Error&&!(le.message in Rn)&&(Rn[le.message]=!0,Y(P),Xe("Failed %s type: %s",T,le.message),Y(null))}}}var ue=Array.isArray;function Ce(c){return ue(c)}function De(c){{var p=typeof Symbol=="function"&&Symbol.toStringTag,T=p&&c[Symbol.toStringTag]||c.constructor.name||"Object";return T}}function yt(c){try{return Le(c),!1}catch{return!0}}function Le(c){return""+c}function ct(c){if(yt(c))return Xe("The provided key is an unsupported type %s. This value must be coerced to a string before before using it here.",De(c)),Le(c)}var wt=Rt.ReactCurrentOwner,un={key:!0,ref:!0,__self:!0,__source:!0},wn,Q;function ge(c){if(an.call(c,"ref")){var p=Object.getOwnPropertyDescriptor(c,"ref").get;if(p&&p.isReactWarning)return!1}return c.ref!==void 0}function Pe(c){if(an.call(c,"key")){var p=Object.getOwnPropertyDescriptor(c,"key").get;if(p&&p.isReactWarning)return!1}return c.key!==void 0}function gt(c,p){typeof c.ref=="string"&&wt.current}function Pt(c,p){{var T=function(){wn||(wn=!0,Xe("%s: `key` is not a prop. Trying to access it will result in `undefined` being returned. If you need to access the same value within the child component, you should pass it as a different prop. (https://reactjs.org/link/special-props)",p))};T.isReactWarning=!0,Object.defineProperty(c,"key",{get:T,configurable:!0})}}function Qt(c,p){{var T=function(){Q||(Q=!0,Xe("%s: `ref` is not a prop. Trying to access it will result in `undefined` being returned. If you need to access the same value within the child component, you should pass it as a different prop. (https://reactjs.org/link/special-props)",p))};T.isReactWarning=!0,Object.defineProperty(c,"ref",{get:T,configurable:!0})}}var Kt=function(c,p,T,E,P,ae,H){var le={$$typeof:k,type:c,key:p,ref:T,props:H,_owner:ae};return le._store={},Object.defineProperty(le._store,"validated",{configurable:!1,enumerable:!1,writable:!0,value:!1}),Object.defineProperty(le,"_self",{configurable:!1,enumerable:!1,writable:!1,value:E}),Object.defineProperty(le,"_source",{configurable:!1,enumerable:!1,writable:!1,value:P}),Object.freeze&&(Object.freeze(le.props),Object.freeze(le)),le};function Dn(c,p,T,E,P){{var ae,H={},le=null,Qe=null;T!==void 0&&(ct(T),le=""+T),Pe(p)&&(ct(p.key),le=""+p.key),ge(p)&&(Qe=p.ref,gt(p,P));for(ae in p)an.call(p,ae)&&!un.hasOwnProperty(ae)&&(H[ae]=p[ae]);if(c&&c.defaultProps){var _e=c.defaultProps;for(ae in _e)H[ae]===void 0&&(H[ae]=_e[ae])}if(le||Qe){var ot=typeof c=="function"?c.displayName||c.name||"Unknown":c;le&&Pt(H,ot),Qe&&Qt(H,ot)}return Kt(c,le,Qe,P,E,wt.current,H)}}var It=Rt.ReactCurrentOwner,Vt=Rt.ReactDebugCurrentFrame;function bt(c){if(c){var p=c._owner,T=Yt(c.type,c._source,p?p.type:null);Vt.setExtraStackFrame(T)}else Vt.setExtraStackFrame(null)}var ua;ua=!1;function ha(c){return typeof c=="object"&&c!==null&&c.$$typeof===k}function Yn(){{if(It.current){var c=X(It.current.type);if(c)return`
+ */return function(){var w=Zc(),L=Symbol.for("react.element"),me=Symbol.for("react.portal"),Ee=Symbol.for("react.fragment"),fe=Symbol.for("react.strict_mode"),Re=Symbol.for("react.profiler"),d=Symbol.for("react.provider"),Q=Symbol.for("react.context"),he=Symbol.for("react.forward_ref"),te=Symbol.for("react.suspense"),bt=Symbol.for("react.suspense_list"),I=Symbol.for("react.memo"),H=Symbol.for("react.lazy"),K=Symbol.for("react.offscreen"),Oe=Symbol.iterator,zt="@@iterator";function qt(c){if(c===null||typeof c!="object")return null;var y=Oe&&c[Oe]||c[zt];return typeof y=="function"?y:null}var St=w.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;function Pe(c){{for(var y=arguments.length,S=new Array(y>1?y-1:0),A=1;A=1&&Ft>=0&&ye[it]!==ze[Ft];)Ft--;for(;it>=1&&Ft>=0;it--,Ft--)if(ye[it]!==ze[Ft]){if(it!==1||Ft!==1)do if(it--,Ft--,Ft<0||ye[it]!==ze[Ft]){var nn=`
+`+ye[it].replace(" at new "," at ");return c.displayName&&nn.includes("")&&(nn=nn.replace("",c.displayName)),typeof c=="function"&&Gt.set(c,nn),nn}while(it>=1&&Ft>=0);break}}}finally{Qe=!1,et.current=z,cn(),Error.prepareStackTrace=ve}var Zt=c?c.displayName||c.name:"",Pn=Zt?gt(Zt):"";return typeof c=="function"&&Gt.set(c,Pn),Pn}function F(c,y,S){return fn(c,!1)}function G(c){var y=c.prototype;return!!(y&&y.isReactComponent)}function Te(c,y,S){if(c==null)return"";if(typeof c=="function")return fn(c,G(c));if(typeof c=="string")return gt(c);switch(c){case te:return gt("Suspense");case bt:return gt("SuspenseList")}if(typeof c=="object")switch(c.$$typeof){case he:return F(c.render);case I:return Te(c.type,y,S);case H:{var A=c,ve=A._payload,z=A._init;try{return Te(z(ve),y,S)}catch{}}}return""}var Ce=Object.prototype.hasOwnProperty,Ze={},st=St.ReactDebugCurrentFrame;function ht(c){if(c){var y=c._owner,S=Te(c.type,c._source,y?y.type:null);st.setExtraStackFrame(S)}else st.setExtraStackFrame(null)}function Ge(c,y,S,A,ve){{var z=Function.call.bind(Ce);for(var ue in c)if(z(c,ue)){var ye=void 0;try{if(typeof c[ue]!="function"){var ze=Error((A||"React class")+": "+S+" type `"+ue+"` is invalid; it must be a function, usually from the `prop-types` package, but received `"+typeof c[ue]+"`.This often happens because of typos such as `PropTypes.function` instead of `PropTypes.func`.");throw ze.name="Invariant Violation",ze}ye=c[ue](y,ue,A,S,null,"SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED")}catch(it){ye=it}ye&&!(ye instanceof Error)&&(ht(ve),Pe("%s: type specification of %s `%s` is invalid; the type checker function must return `null` or an `Error` but returned a %s. You may have forgotten to pass an argument to the type checker creator (arrayOf, instanceOf, objectOf, oneOf, oneOfType, and shape all require an argument).",A||"React class",S,ue,typeof ye),ht(null)),ye instanceof Error&&!(ye.message in Ze)&&(Ze[ye.message]=!0,ht(ve),Pe("Failed %s type: %s",S,ye.message),ht(null))}}}var yt=Array.isArray;function rt(c){return yt(c)}function tn(c){{var y=typeof Symbol=="function"&&Symbol.toStringTag,S=y&&c[Symbol.toStringTag]||c.constructor.name||"Object";return S}}function mn(c){try{return Ot(c),!1}catch{return!0}}function Ot(c){return""+c}function Lt(c){if(mn(c))return Pe("The provided key is an unsupported type %s. This value must be coerced to a string before before using it here.",tn(c)),Ot(c)}var hn=St.ReactCurrentOwner,ia={key:!0,ref:!0,__self:!0,__source:!0},va,Z;function xe(c){if(Ce.call(c,"ref")){var y=Object.getOwnPropertyDescriptor(c,"ref").get;if(y&&y.isReactWarning)return!1}return c.ref!==void 0}function Je(c){if(Ce.call(c,"key")){var y=Object.getOwnPropertyDescriptor(c,"key").get;if(y&&y.isReactWarning)return!1}return c.key!==void 0}function Rt(c,y){typeof c.ref=="string"&&hn.current}function Bt(c,y){{var S=function(){va||(va=!0,Pe("%s: `key` is not a prop. Trying to access it will result in `undefined` being returned. If you need to access the same value within the child component, you should pass it as a different prop. (https://reactjs.org/link/special-props)",y))};S.isReactWarning=!0,Object.defineProperty(c,"key",{get:S,configurable:!0})}}function Xt(c,y){{var S=function(){Z||(Z=!0,Pe("%s: `ref` is not a prop. Trying to access it will result in `undefined` being returned. If you need to access the same value within the child component, you should pass it as a different prop. (https://reactjs.org/link/special-props)",y))};S.isReactWarning=!0,Object.defineProperty(c,"ref",{get:S,configurable:!0})}}var Qt=function(c,y,S,A,ve,z,ue){var ye={$$typeof:L,type:c,key:y,ref:S,props:ue,_owner:z};return ye._store={},Object.defineProperty(ye._store,"validated",{configurable:!1,enumerable:!1,writable:!0,value:!1}),Object.defineProperty(ye,"_self",{configurable:!1,enumerable:!1,writable:!1,value:A}),Object.defineProperty(ye,"_source",{configurable:!1,enumerable:!1,writable:!1,value:ve}),Object.freeze&&(Object.freeze(ye.props),Object.freeze(ye)),ye};function wn(c,y,S,A,ve){{var z,ue={},ye=null,ze=null;S!==void 0&&(Lt(S),ye=""+S),Je(y)&&(Lt(y.key),ye=""+y.key),xe(y)&&(ze=y.ref,Rt(y,ve));for(z in y)Ce.call(y,z)&&!ia.hasOwnProperty(z)&&(ue[z]=y[z]);if(c&&c.defaultProps){var it=c.defaultProps;for(z in it)ue[z]===void 0&&(ue[z]=it[z])}if(ye||ze){var Ft=typeof c=="function"?c.displayName||c.name||"Unknown":c;ye&&Bt(ue,Ft),ze&&Xt(ue,Ft)}return Qt(c,ye,ze,ve,A,hn.current,ue)}}var Pt=St.ReactCurrentOwner,Tt=St.ReactDebugCurrentFrame;function Dt(c){if(c){var y=c._owner,S=Te(c.type,c._source,y?y.type:null);Tt.setExtraStackFrame(S)}else Tt.setExtraStackFrame(null)}var er;er=!1;function $n(c){return typeof c=="object"&&c!==null&&c.$$typeof===L}function pa(){{if(Pt.current){var c=Le(Pt.current.type);if(c)return`
-Check the render method of \``+c+"`."}return""}}function nr(c){return""}var Vr={};function Pr(c){{var p=Yn();if(!p){var T=typeof c=="string"?c:c.displayName||c.name;T&&(p=`
+Check the render method of \``+c+"`."}return""}}function tr(c){return""}var Fr={};function Vr(c){{var y=pa();if(!y){var S=typeof c=="string"?c:c.displayName||c.name;S&&(y=`
-Check the top-level render call using <`+T+">.")}return p}}function hr(c,p){{if(!c._store||c._store.validated||c.key!=null)return;c._store.validated=!0;var T=Pr(p);if(Vr[T])return;Vr[T]=!0;var E="";c&&c._owner&&c._owner!==It.current&&(E=" It was passed a child from "+X(c._owner.type)+"."),bt(c),Xe('Each child in a list should have a unique "key" prop.%s%s See https://reactjs.org/link/warning-keys for more information.',T,E),bt(null)}}function Ha(c,p){{if(typeof c!="object")return;if(Ce(c))for(var T=0;T",le=" Did you accidentally export a JSX literal instead of a component?"):_e=typeof c,Xe("React.jsx: type is invalid -- expected a string (for built-in components) or a class/function (for composite components) but got: %s.%s",_e,le)}var ot=Dn(c,p,T,P,ae);if(ot==null)return ot;if(H){var fn=p.children;if(fn!==void 0)if(E)if(Ce(fn)){for(var _n=0;_n0?"{key: someKey, "+Ft.join(": ..., ")+": ...}":"{key: someKey}";if(!Kn[en+Me]){var Dt=Ft.length>0?"{"+Ft.join(": ..., ")+": ...}":"{}";Xe(`A props object containing a "key" prop is being spread into JSX:
+Check the top-level render call using <`+S+">.")}return y}}function wa(c,y){{if(!c._store||c._store.validated||c.key!=null)return;c._store.validated=!0;var S=Vr(y);if(Fr[S])return;Fr[S]=!0;var A="";c&&c._owner&&c._owner!==Pt.current&&(A=" It was passed a child from "+Le(c._owner.type)+"."),Dt(c),Pe('Each child in a list should have a unique "key" prop.%s%s See https://reactjs.org/link/warning-keys for more information.',S,A),Dt(null)}}function ja(c,y){{if(typeof c!="object")return;if(rt(c))for(var S=0;S",ye=" Did you accidentally export a JSX literal instead of a component?"):it=typeof c,Pe("React.jsx: type is invalid -- expected a string (for built-in components) or a class/function (for composite components) but got: %s.%s",it,ye)}var Ft=wn(c,y,S,ve,z);if(Ft==null)return Ft;if(ue){var nn=y.children;if(nn!==void 0)if(A)if(rt(nn)){for(var Zt=0;Zt0?"{key: someKey, "+ft.join(": ..., ")+": ...}":"{key: someKey}";if(!h[Pn+be]){var Rn=ft.length>0?"{"+ft.join(": ..., ")+": ...}":"{}";Pe(`A props object containing a "key" prop is being spread into JSX:
let props = %s;
<%s {...props} />
React keys must be passed directly to JSX without using spread:
let props = %s;
- <%s key={someKey} {...props} />`,Me,en,Dt,en),Kn[en+Me]=!0}}return c===Se?ya(ot):Fa(ot),ot}}function F(c,p,T){return y(c,p,T,!0)}function _(c,p,T){return y(c,p,T,!1)}var S=_,w=F;il.Fragment=Se,il.jsx=S,il.jsxs=w}(),il}dm.exports=FS();var m=dm.exports,hm={exports:{}},Jc={exports:{}},ef={},ym;function VS(){return ym||(ym=1,function(C){/**
+ <%s key={someKey} {...props} />`,be,Pn,Rn,Pn),h[Pn+be]=!0}}return c===Ee?ma(Ft):Br(Ft),Ft}}function D(c,y,S){return B(c,y,S,!0)}function T(c,y,S){return B(c,y,S,!1)}var x=T,O=D;rl.Fragment=Ee,rl.jsx=x,rl.jsxs=O}(),rl}dm.exports=F0();var p=dm.exports,hm={exports:{}},Jc={exports:{}},ef={},ym;function V0(){return ym||(ym=1,function(w){/**
* @license React
* scheduler.development.js
*
@@ -57,7 +57,7 @@ React keys must be passed directly to JSX without using spread:
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
- */(function(){typeof __REACT_DEVTOOLS_GLOBAL_HOOK__<"u"&&typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart=="function"&&__REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart(new Error);var k=!1,me=5;function Se(Q,ge){var Pe=Q.length;Q.push(ge),d(Q,ge,Pe)}function ve(Q){return Q.length===0?null:Q[0]}function Ee(Q){if(Q.length===0)return null;var ge=Q[0],Pe=Q.pop();return Pe!==ge&&(Q[0]=Pe,Z(Q,Pe,0)),ge}function d(Q,ge,Pe){for(var gt=Pe;gt>0;){var Pt=gt-1>>>1,Qt=Q[Pt];if(ye(Qt,ge)>0)Q[Pt]=ge,Q[gt]=Qt,gt=Pt;else return}}function Z(Q,ge,Pe){for(var gt=Pe,Pt=Q.length,Qt=Pt>>>1;gtPe&&(!Q||U()));){var gt=Je.callback;if(typeof gt=="function"){Je.callback=null,X=Je.priorityLevel;var Pt=Je.expirationTime<=Pe,Qt=gt(Pt);Pe=C.unstable_now(),typeof Qt=="function"?Je.callback=Qt:Je===ve(at)&&Ee(at),nt(Pe)}else Ee(at);Je=ve(at)}if(Je!==null)return!0;var Kt=ve(Fe);return Kt!==null&&ct(ht,Kt.startTime-Pe),!1}function Ne(Q,ge){switch(Q){case oe:case rt:case G:case j:case ne:break;default:Q=G}var Pe=X;X=Q;try{return ge()}finally{X=Pe}}function st(Q){var ge;switch(X){case oe:case rt:case G:ge=G;break;default:ge=X;break}var Pe=X;X=ge;try{return Q()}finally{X=Pe}}function $t(Q){var ge=X;return function(){var Pe=X;X=ge;try{return Q.apply(this,arguments)}finally{X=Pe}}}function Ie(Q,ge,Pe){var gt=C.unstable_now(),Pt;if(typeof Pe=="object"&&Pe!==null){var Qt=Pe.delay;typeof Qt=="number"&&Qt>0?Pt=gt+Qt:Pt=gt}else Pt=gt;var Kt;switch(Q){case oe:Kt=mt;break;case rt:Kt=Re;break;case ne:Kt=Ot;break;case j:Kt=B;break;case G:default:Kt=et;break}var Dn=Pt+Kt,It={id:ee++,callback:ge,priorityLevel:Q,startTime:Pt,expirationTime:Dn,sortIndex:-1};return Pt>gt?(It.sortIndex=Pt,Se(Fe,It),ve(at)===null&&It===ve(Fe)&&(I?wt():I=!0,ct(ht,Pt-gt))):(It.sortIndex=Dn,Se(at,It),!Be&&!At&&(Be=!0,Le(Ze))),It}function Nt(){}function it(){!Be&&!At&&(Be=!0,Le(Ze))}function We(){return ve(at)}function Jt(Q){Q.callback=null}function xn(){return X}var Xt=!1,cn=null,Yt=-1,an=me,Rn=-1;function U(){var Q=C.unstable_now()-Rn;return!(Q125){console.error("forceFrameRate takes a positive int between 0 and 125, forcing frame rates higher than 125 fps is not supported");return}Q>0?an=Math.floor(1e3/Q):an=me}var ue=function(){if(cn!==null){var Q=C.unstable_now();Rn=Q;var ge=!0,Pe=!0;try{Pe=cn(ge,Q)}finally{Pe?Ce():(Xt=!1,cn=null)}}else Xt=!1},Ce;if(typeof Ve=="function")Ce=function(){Ve(ue)};else if(typeof MessageChannel<"u"){var De=new MessageChannel,yt=De.port2;De.port1.onmessage=ue,Ce=function(){yt.postMessage(null)}}else Ce=function(){$(ue,0)};function Le(Q){cn=Q,Xt||(Xt=!0,Ce())}function ct(Q,ge){Yt=$(function(){Q(C.unstable_now())},ge)}function wt(){He(Yt),Yt=-1}var un=Y,wn=null;C.unstable_IdlePriority=ne,C.unstable_ImmediatePriority=oe,C.unstable_LowPriority=j,C.unstable_NormalPriority=G,C.unstable_Profiling=wn,C.unstable_UserBlockingPriority=rt,C.unstable_cancelCallback=Jt,C.unstable_continueExecution=it,C.unstable_forceFrameRate=pe,C.unstable_getCurrentPriorityLevel=xn,C.unstable_getFirstCallbackNode=We,C.unstable_next=st,C.unstable_pauseExecution=Nt,C.unstable_requestPaint=un,C.unstable_runWithPriority=Ne,C.unstable_scheduleCallback=Ie,C.unstable_shouldYield=U,C.unstable_wrapCallback=$t,typeof __REACT_DEVTOOLS_GLOBAL_HOOK__<"u"&&typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop=="function"&&__REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop(new Error)})()}(ef)),ef}var gm;function PS(){return gm||(gm=1,Jc.exports=VS()),Jc.exports}var la={},bm;function BS(){if(bm)return la;bm=1;/**
+ */(function(){typeof __REACT_DEVTOOLS_GLOBAL_HOOK__<"u"&&typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart=="function"&&__REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart(new Error);var L=!1,me=5;function Ee(Z,xe){var Je=Z.length;Z.push(xe),d(Z,xe,Je)}function fe(Z){return Z.length===0?null:Z[0]}function Re(Z){if(Z.length===0)return null;var xe=Z[0],Je=Z.pop();return Je!==xe&&(Z[0]=Je,Q(Z,Je,0)),xe}function d(Z,xe,Je){for(var Rt=Je;Rt>0;){var Bt=Rt-1>>>1,Xt=Z[Bt];if(he(Xt,xe)>0)Z[Bt]=xe,Z[Rt]=Xt,Rt=Bt;else return}}function Q(Z,xe,Je){for(var Rt=Je,Bt=Z.length,Xt=Bt>>>1;RtJe&&(!Z||st()));){var Rt=oe.callback;if(typeof Rt=="function"){oe.callback=null,Le=oe.priorityLevel;var Bt=oe.expirationTime<=Je,Xt=Rt(Bt);Je=w.unstable_now(),typeof Xt=="function"?oe.callback=Xt:oe===fe(Me)&&Re(Me),at(Je)}else Re(Me);oe=fe(Me)}if(oe!==null)return!0;var Qt=fe(pe);return Qt!==null&&Lt(ct,Qt.startTime-Je),!1}function Ue(Z,xe){switch(Z){case te:case bt:case I:case H:case K:break;default:Z=I}var Je=Le;Le=Z;try{return xe()}finally{Le=Je}}function cn(Z){var xe;switch(Le){case te:case bt:case I:xe=I;break;default:xe=Le;break}var Je=Le;Le=xe;try{return Z()}finally{Le=Je}}function et(Z){var xe=Le;return function(){var Je=Le;Le=xe;try{return Z.apply(this,arguments)}finally{Le=Je}}}function Ht(Z,xe,Je){var Rt=w.unstable_now(),Bt;if(typeof Je=="object"&&Je!==null){var Xt=Je.delay;typeof Xt=="number"&&Xt>0?Bt=Rt+Xt:Bt=Rt}else Bt=Rt;var Qt;switch(Z){case te:Qt=ot;break;case bt:Qt=_;break;case K:Qt=Vt;break;case H:Qt=De;break;case I:default:Qt=ne;break}var wn=Bt+Qt,Pt={id:nt++,callback:xe,priorityLevel:Z,startTime:Bt,expirationTime:wn,sortIndex:-1};return Bt>Rt?(Pt.sortIndex=Bt,Ee(pe,Pt),fe(Me)===null&&Pt===fe(pe)&&(W?hn():W=!0,Lt(ct,Bt-Rt))):(Pt.sortIndex=wn,Ee(Me,Pt),!le&&!Ke&&(le=!0,Ot(We))),Pt}function gt(){}function Qe(){!le&&!Ke&&(le=!0,Ot(We))}function Gt(){return fe(Me)}function rn(Z){Z.callback=null}function fn(){return Le}var F=!1,G=null,Te=-1,Ce=me,Ze=-1;function st(){var Z=w.unstable_now()-Ze;return!(Z125){console.error("forceFrameRate takes a positive int between 0 and 125, forcing frame rates higher than 125 fps is not supported");return}Z>0?Ce=Math.floor(1e3/Z):Ce=me}var yt=function(){if(G!==null){var Z=w.unstable_now();Ze=Z;var xe=!0,Je=!0;try{Je=G(xe,Z)}finally{Je?rt():(F=!1,G=null)}}else F=!1},rt;if(typeof Fe=="function")rt=function(){Fe(yt)};else if(typeof MessageChannel<"u"){var tn=new MessageChannel,mn=tn.port2;tn.port1.onmessage=yt,rt=function(){mn.postMessage(null)}}else rt=function(){q(yt,0)};function Ot(Z){G=Z,F||(F=!0,rt())}function Lt(Z,xe){Te=q(function(){Z(w.unstable_now())},xe)}function hn(){Ye(Te),Te=-1}var ia=ht,va=null;w.unstable_IdlePriority=K,w.unstable_ImmediatePriority=te,w.unstable_LowPriority=H,w.unstable_NormalPriority=I,w.unstable_Profiling=va,w.unstable_UserBlockingPriority=bt,w.unstable_cancelCallback=rn,w.unstable_continueExecution=Qe,w.unstable_forceFrameRate=Ge,w.unstable_getCurrentPriorityLevel=fn,w.unstable_getFirstCallbackNode=Gt,w.unstable_next=cn,w.unstable_pauseExecution=gt,w.unstable_requestPaint=ia,w.unstable_runWithPriority=Ue,w.unstable_scheduleCallback=Ht,w.unstable_shouldYield=st,w.unstable_wrapCallback=et,typeof __REACT_DEVTOOLS_GLOBAL_HOOK__<"u"&&typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop=="function"&&__REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop(new Error)})()}(ef)),ef}var gm;function B0(){return gm||(gm=1,Jc.exports=V0()),Jc.exports}var ra={},bm;function $0(){if(bm)return ra;bm=1;/**
* @license React
* react-dom.development.js
*
@@ -65,15 +65,15 @@ React keys must be passed directly to JSX without using spread:
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
- */return function(){typeof __REACT_DEVTOOLS_GLOBAL_HOOK__<"u"&&typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart=="function"&&__REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart(new Error);var C=Zc(),k=PS(),me=C.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED,Se=!1;function ve(e){Se=e}function Ee(e){if(!Se){for(var t=arguments.length,n=new Array(t>1?t-1:0),a=1;a1?t-1:0),a=1;a2&&(e[0]==="o"||e[0]==="O")&&(e[1]==="n"||e[1]==="N")}function Kt(e,t,n,a){if(n!==null&&n.type===Ce)return!1;switch(typeof t){case"function":case"symbol":return!0;case"boolean":{if(a)return!1;if(n!==null)return!n.acceptsBooleans;var r=e.toLowerCase().slice(0,5);return r!=="data-"&&r!=="aria-"}default:return!1}}function Dn(e,t,n,a){if(t===null||typeof t>"u"||Kt(e,t,n,a))return!0;if(a)return!1;if(n!==null)switch(n.type){case Le:return!t;case ct:return t===!1;case wt:return isNaN(t);case un:return isNaN(t)||t<1}return!1}function It(e){return bt.hasOwnProperty(e)?bt[e]:null}function Vt(e,t,n,a,r,i,o){this.acceptsBooleans=t===yt||t===Le||t===ct,this.attributeName=a,this.attributeNamespace=r,this.mustUseProperty=n,this.propertyName=e,this.type=t,this.sanitizeURL=i,this.removeEmptyString=o}var bt={},ua=["children","dangerouslySetInnerHTML","defaultValue","defaultChecked","innerHTML","suppressContentEditableWarning","suppressHydrationWarning","style"];ua.forEach(function(e){bt[e]=new Vt(e,Ce,!1,e,null,!1,!1)}),[["acceptCharset","accept-charset"],["className","class"],["htmlFor","for"],["httpEquiv","http-equiv"]].forEach(function(e){var t=e[0],n=e[1];bt[t]=new Vt(t,De,!1,n,null,!1,!1)}),["contentEditable","draggable","spellCheck","value"].forEach(function(e){bt[e]=new Vt(e,yt,!1,e.toLowerCase(),null,!1,!1)}),["autoReverse","externalResourcesRequired","focusable","preserveAlpha"].forEach(function(e){bt[e]=new Vt(e,yt,!1,e,null,!1,!1)}),["allowFullScreen","async","autoFocus","autoPlay","controls","default","defer","disabled","disablePictureInPicture","disableRemotePlayback","formNoValidate","hidden","loop","noModule","noValidate","open","playsInline","readOnly","required","reversed","scoped","seamless","itemScope"].forEach(function(e){bt[e]=new Vt(e,Le,!1,e.toLowerCase(),null,!1,!1)}),["checked","multiple","muted","selected"].forEach(function(e){bt[e]=new Vt(e,Le,!0,e,null,!1,!1)}),["capture","download"].forEach(function(e){bt[e]=new Vt(e,ct,!1,e,null,!1,!1)}),["cols","rows","size","span"].forEach(function(e){bt[e]=new Vt(e,un,!1,e,null,!1,!1)}),["rowSpan","start"].forEach(function(e){bt[e]=new Vt(e,wt,!1,e.toLowerCase(),null,!1,!1)});var ha=/[\-\:]([a-z])/g,Yn=function(e){return e[1].toUpperCase()};["accent-height","alignment-baseline","arabic-form","baseline-shift","cap-height","clip-path","clip-rule","color-interpolation","color-interpolation-filters","color-profile","color-rendering","dominant-baseline","enable-background","fill-opacity","fill-rule","flood-color","flood-opacity","font-family","font-size","font-size-adjust","font-stretch","font-style","font-variant","font-weight","glyph-name","glyph-orientation-horizontal","glyph-orientation-vertical","horiz-adv-x","horiz-origin-x","image-rendering","letter-spacing","lighting-color","marker-end","marker-mid","marker-start","overline-position","overline-thickness","paint-order","panose-1","pointer-events","rendering-intent","shape-rendering","stop-color","stop-opacity","strikethrough-position","strikethrough-thickness","stroke-dasharray","stroke-dashoffset","stroke-linecap","stroke-linejoin","stroke-miterlimit","stroke-opacity","stroke-width","text-anchor","text-decoration","text-rendering","underline-position","underline-thickness","unicode-bidi","unicode-range","units-per-em","v-alphabetic","v-hanging","v-ideographic","v-mathematical","vector-effect","vert-adv-y","vert-origin-x","vert-origin-y","word-spacing","writing-mode","xmlns:xlink","x-height"].forEach(function(e){var t=e.replace(ha,Yn);bt[t]=new Vt(t,De,!1,e,null,!1,!1)}),["xlink:actuate","xlink:arcrole","xlink:role","xlink:show","xlink:title","xlink:type"].forEach(function(e){var t=e.replace(ha,Yn);bt[t]=new Vt(t,De,!1,e,"http://www.w3.org/1999/xlink",!1,!1)}),["xml:base","xml:lang","xml:space"].forEach(function(e){var t=e.replace(ha,Yn);bt[t]=new Vt(t,De,!1,e,"http://www.w3.org/XML/1998/namespace",!1,!1)}),["tabIndex","crossOrigin"].forEach(function(e){bt[e]=new Vt(e,De,!1,e.toLowerCase(),null,!1,!1)});var nr="xlinkHref";bt[nr]=new Vt("xlinkHref",De,!1,"xlink:href","http://www.w3.org/1999/xlink",!0,!1),["src","href","action","formAction"].forEach(function(e){bt[e]=new Vt(e,De,!1,e.toLowerCase(),null,!0,!0)});var Vr=/^[\u0000-\u001F ]*j[\r\n\t]*a[\r\n\t]*v[\r\n\t]*a[\r\n\t]*s[\r\n\t]*c[\r\n\t]*r[\r\n\t]*i[\r\n\t]*p[\r\n\t]*t[\r\n\t]*\:/i,Pr=!1;function hr(e){!Pr&&Vr.test(e)&&(Pr=!0,d("A future version of React will block javascript: URLs as a security precaution. Use event handlers instead if you can. If you need to generate unsafe HTML try using dangerouslySetInnerHTML instead. React was passed %s.",JSON.stringify(e)))}function Ha(e,t,n,a){if(a.mustUseProperty){var r=a.propertyName;return e[r]}else{an(n,t),a.sanitizeURL&&hr(""+n);var i=a.attributeName,o=null;if(a.type===ct){if(e.hasAttribute(i)){var l=e.getAttribute(i);return l===""?!0:Dn(t,n,a,!1)?l:l===""+n?n:l}}else if(e.hasAttribute(i)){if(Dn(t,n,a,!1))return e.getAttribute(i);if(a.type===Le)return n;o=e.getAttribute(i)}return Dn(t,n,a,!1)?o===null?n:o:o===""+n?n:o}}function Fa(e,t,n,a){{if(!Pt(t))return;if(!e.hasAttribute(t))return n===void 0?void 0:null;var r=e.getAttribute(t);return an(n,t),r===""+n?n:r}}function ya(e,t,n,a){var r=It(t);if(!Qt(t,r,a)){if(Dn(t,n,r,a)&&(n=null),a||r===null){if(Pt(t)){var i=t;n===null?e.removeAttribute(i):(an(n,t),e.setAttribute(i,""+n))}return}var o=r.mustUseProperty;if(o){var l=r.propertyName;if(n===null){var u=r.type;e[l]=u===Le?!1:""}else e[l]=n;return}var f=r.attributeName,v=r.attributeNamespace;if(n===null)e.removeAttribute(f);else{var b=r.type,g;b===Le||b===ct&&n===!0?g="":(an(n,f),g=""+n,r.sanitizeURL&&hr(g.toString())),v?e.setAttributeNS(v,f,g):e.setAttribute(f,g)}}}var Kn=Symbol.for("react.element"),y=Symbol.for("react.portal"),F=Symbol.for("react.fragment"),_=Symbol.for("react.strict_mode"),S=Symbol.for("react.profiler"),w=Symbol.for("react.provider"),c=Symbol.for("react.context"),p=Symbol.for("react.forward_ref"),T=Symbol.for("react.suspense"),E=Symbol.for("react.suspense_list"),P=Symbol.for("react.memo"),ae=Symbol.for("react.lazy"),H=Symbol.for("react.scope"),le=Symbol.for("react.debug_trace_mode"),Qe=Symbol.for("react.offscreen"),_e=Symbol.for("react.legacy_hidden"),ot=Symbol.for("react.cache"),fn=Symbol.for("react.tracing_marker"),_n=Symbol.iterator,en="@@iterator";function Ft(e){if(e===null||typeof e!="object")return null;var t=_n&&e[_n]||e[en];return typeof t=="function"?t:null}var Me=Object.assign,Dt=0,Zn,yr,Va,Br,ga,ba,$r;function gi(){}gi.__reactDisabledLog=!0;function dn(){{if(Dt===0){Zn=console.log,yr=console.info,Va=console.warn,Br=console.error,ga=console.group,ba=console.groupCollapsed,$r=console.groupEnd;var e={configurable:!0,enumerable:!0,value:gi,writable:!0};Object.defineProperties(console,{info:e,log:e,warn:e,error:e,group:e,groupCollapsed:e,groupEnd:e})}Dt++}}function _t(){{if(Dt--,Dt===0){var e={configurable:!0,enumerable:!0,writable:!0};Object.defineProperties(console,{log:Me({},e,{value:Zn}),info:Me({},e,{value:yr}),warn:Me({},e,{value:Va}),error:Me({},e,{value:Br}),group:Me({},e,{value:ga}),groupCollapsed:Me({},e,{value:ba}),groupEnd:Me({},e,{value:$r})})}Dt<0&&d("disabledDepth fell below zero. This is a bug in React. Please file an issue.")}}var Sa=me.ReactCurrentDispatcher,Ta;function sa(e,t,n){{if(Ta===void 0)try{throw Error()}catch(r){var a=r.stack.trim().match(/\n( *(at )?)/);Ta=a&&a[1]||""}return`
-`+Ta+e}}var bi=!1,Yr;{var ao=typeof WeakMap=="function"?WeakMap:Map;Yr=new ao}function Si(e,t){if(!e||bi)return"";{var n=Yr.get(e);if(n!==void 0)return n}var a;bi=!0;var r=Error.prepareStackTrace;Error.prepareStackTrace=void 0;var i;i=Sa.current,Sa.current=null,dn();try{if(t){var o=function(){throw Error()};if(Object.defineProperty(o.prototype,"props",{set:function(){throw Error()}}),typeof Reflect=="object"&&Reflect.construct){try{Reflect.construct(o,[])}catch(L){a=L}Reflect.construct(e,[],o)}else{try{o.call()}catch(L){a=L}e.call(o.prototype)}}else{try{throw Error()}catch(L){a=L}e()}}catch(L){if(L&&a&&typeof L.stack=="string"){for(var l=L.stack.split(`
+ */return function(){typeof __REACT_DEVTOOLS_GLOBAL_HOOK__<"u"&&typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart=="function"&&__REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart(new Error);var w=Zc(),L=B0(),me=w.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED,Ee=!1;function fe(e){Ee=e}function Re(e){if(!Ee){for(var t=arguments.length,n=new Array(t>1?t-1:0),a=1;a1?t-1:0),a=1;a2&&(e[0]==="o"||e[0]==="O")&&(e[1]==="n"||e[1]==="N")}function Qt(e,t,n,a){if(n!==null&&n.type===rt)return!1;switch(typeof t){case"function":case"symbol":return!0;case"boolean":{if(a)return!1;if(n!==null)return!n.acceptsBooleans;var r=e.toLowerCase().slice(0,5);return r!=="data-"&&r!=="aria-"}default:return!1}}function wn(e,t,n,a){if(t===null||typeof t>"u"||Qt(e,t,n,a))return!0;if(a)return!1;if(n!==null)switch(n.type){case Ot:return!t;case Lt:return t===!1;case hn:return isNaN(t);case ia:return isNaN(t)||t<1}return!1}function Pt(e){return Dt.hasOwnProperty(e)?Dt[e]:null}function Tt(e,t,n,a,r,i,o){this.acceptsBooleans=t===mn||t===Ot||t===Lt,this.attributeName=a,this.attributeNamespace=r,this.mustUseProperty=n,this.propertyName=e,this.type=t,this.sanitizeURL=i,this.removeEmptyString=o}var Dt={},er=["children","dangerouslySetInnerHTML","defaultValue","defaultChecked","innerHTML","suppressContentEditableWarning","suppressHydrationWarning","style"];er.forEach(function(e){Dt[e]=new Tt(e,rt,!1,e,null,!1,!1)}),[["acceptCharset","accept-charset"],["className","class"],["htmlFor","for"],["httpEquiv","http-equiv"]].forEach(function(e){var t=e[0],n=e[1];Dt[t]=new Tt(t,tn,!1,n,null,!1,!1)}),["contentEditable","draggable","spellCheck","value"].forEach(function(e){Dt[e]=new Tt(e,mn,!1,e.toLowerCase(),null,!1,!1)}),["autoReverse","externalResourcesRequired","focusable","preserveAlpha"].forEach(function(e){Dt[e]=new Tt(e,mn,!1,e,null,!1,!1)}),["allowFullScreen","async","autoFocus","autoPlay","controls","default","defer","disabled","disablePictureInPicture","disableRemotePlayback","formNoValidate","hidden","loop","noModule","noValidate","open","playsInline","readOnly","required","reversed","scoped","seamless","itemScope"].forEach(function(e){Dt[e]=new Tt(e,Ot,!1,e.toLowerCase(),null,!1,!1)}),["checked","multiple","muted","selected"].forEach(function(e){Dt[e]=new Tt(e,Ot,!0,e,null,!1,!1)}),["capture","download"].forEach(function(e){Dt[e]=new Tt(e,Lt,!1,e,null,!1,!1)}),["cols","rows","size","span"].forEach(function(e){Dt[e]=new Tt(e,ia,!1,e,null,!1,!1)}),["rowSpan","start"].forEach(function(e){Dt[e]=new Tt(e,hn,!1,e.toLowerCase(),null,!1,!1)});var $n=/[\-\:]([a-z])/g,pa=function(e){return e[1].toUpperCase()};["accent-height","alignment-baseline","arabic-form","baseline-shift","cap-height","clip-path","clip-rule","color-interpolation","color-interpolation-filters","color-profile","color-rendering","dominant-baseline","enable-background","fill-opacity","fill-rule","flood-color","flood-opacity","font-family","font-size","font-size-adjust","font-stretch","font-style","font-variant","font-weight","glyph-name","glyph-orientation-horizontal","glyph-orientation-vertical","horiz-adv-x","horiz-origin-x","image-rendering","letter-spacing","lighting-color","marker-end","marker-mid","marker-start","overline-position","overline-thickness","paint-order","panose-1","pointer-events","rendering-intent","shape-rendering","stop-color","stop-opacity","strikethrough-position","strikethrough-thickness","stroke-dasharray","stroke-dashoffset","stroke-linecap","stroke-linejoin","stroke-miterlimit","stroke-opacity","stroke-width","text-anchor","text-decoration","text-rendering","underline-position","underline-thickness","unicode-bidi","unicode-range","units-per-em","v-alphabetic","v-hanging","v-ideographic","v-mathematical","vector-effect","vert-adv-y","vert-origin-x","vert-origin-y","word-spacing","writing-mode","xmlns:xlink","x-height"].forEach(function(e){var t=e.replace($n,pa);Dt[t]=new Tt(t,tn,!1,e,null,!1,!1)}),["xlink:actuate","xlink:arcrole","xlink:role","xlink:show","xlink:title","xlink:type"].forEach(function(e){var t=e.replace($n,pa);Dt[t]=new Tt(t,tn,!1,e,"http://www.w3.org/1999/xlink",!1,!1)}),["xml:base","xml:lang","xml:space"].forEach(function(e){var t=e.replace($n,pa);Dt[t]=new Tt(t,tn,!1,e,"http://www.w3.org/XML/1998/namespace",!1,!1)}),["tabIndex","crossOrigin"].forEach(function(e){Dt[e]=new Tt(e,tn,!1,e.toLowerCase(),null,!1,!1)});var tr="xlinkHref";Dt[tr]=new Tt("xlinkHref",tn,!1,"xlink:href","http://www.w3.org/1999/xlink",!0,!1),["src","href","action","formAction"].forEach(function(e){Dt[e]=new Tt(e,tn,!1,e.toLowerCase(),null,!0,!0)});var Fr=/^[\u0000-\u001F ]*j[\r\n\t]*a[\r\n\t]*v[\r\n\t]*a[\r\n\t]*s[\r\n\t]*c[\r\n\t]*r[\r\n\t]*i[\r\n\t]*p[\r\n\t]*t[\r\n\t]*\:/i,Vr=!1;function wa(e){!Vr&&Fr.test(e)&&(Vr=!0,d("A future version of React will block javascript: URLs as a security precaution. Use event handlers instead if you can. If you need to generate unsafe HTML try using dangerouslySetInnerHTML instead. React was passed %s.",JSON.stringify(e)))}function ja(e,t,n,a){if(a.mustUseProperty){var r=a.propertyName;return e[r]}else{Ce(n,t),a.sanitizeURL&&wa(""+n);var i=a.attributeName,o=null;if(a.type===Lt){if(e.hasAttribute(i)){var l=e.getAttribute(i);return l===""?!0:wn(t,n,a,!1)?l:l===""+n?n:l}}else if(e.hasAttribute(i)){if(wn(t,n,a,!1))return e.getAttribute(i);if(a.type===Ot)return n;o=e.getAttribute(i)}return wn(t,n,a,!1)?o===null?n:o:o===""+n?n:o}}function Br(e,t,n,a){{if(!Bt(t))return;if(!e.hasAttribute(t))return n===void 0?void 0:null;var r=e.getAttribute(t);return Ce(n,t),r===""+n?n:r}}function ma(e,t,n,a){var r=Pt(t);if(!Xt(t,r,a)){if(wn(t,n,r,a)&&(n=null),a||r===null){if(Bt(t)){var i=t;n===null?e.removeAttribute(i):(Ce(n,t),e.setAttribute(i,""+n))}return}var o=r.mustUseProperty;if(o){var l=r.propertyName;if(n===null){var u=r.type;e[l]=u===Ot?!1:""}else e[l]=n;return}var f=r.attributeName,v=r.attributeNamespace;if(n===null)e.removeAttribute(f);else{var b=r.type,g;b===Ot||b===Lt&&n===!0?g="":(Ce(n,f),g=""+n,r.sanitizeURL&&wa(g.toString())),v?e.setAttributeNS(v,f,g):e.setAttribute(f,g)}}}var h=Symbol.for("react.element"),B=Symbol.for("react.portal"),D=Symbol.for("react.fragment"),T=Symbol.for("react.strict_mode"),x=Symbol.for("react.profiler"),O=Symbol.for("react.provider"),c=Symbol.for("react.context"),y=Symbol.for("react.forward_ref"),S=Symbol.for("react.suspense"),A=Symbol.for("react.suspense_list"),ve=Symbol.for("react.memo"),z=Symbol.for("react.lazy"),ue=Symbol.for("react.scope"),ye=Symbol.for("react.debug_trace_mode"),ze=Symbol.for("react.offscreen"),it=Symbol.for("react.legacy_hidden"),Ft=Symbol.for("react.cache"),nn=Symbol.for("react.tracing_marker"),Zt=Symbol.iterator,Pn="@@iterator";function ft(e){if(e===null||typeof e!="object")return null;var t=Zt&&e[Zt]||e[Pn];return typeof t=="function"?t:null}var be=Object.assign,Rn=0,Ua,il,Ha,ha,ya,$r,yi;function un(){}un.__reactDisabledLog=!0;function _t(){{if(Rn===0){Ua=console.log,il=console.info,Ha=console.warn,ha=console.error,ya=console.group,$r=console.groupCollapsed,yi=console.groupEnd;var e={configurable:!0,enumerable:!0,value:un,writable:!0};Object.defineProperties(console,{info:e,log:e,warn:e,error:e,group:e,groupCollapsed:e,groupEnd:e})}Rn++}}function za(){{if(Rn--,Rn===0){var e={configurable:!0,enumerable:!0,writable:!0};Object.defineProperties(console,{log:be({},e,{value:Ua}),info:be({},e,{value:il}),warn:be({},e,{value:Ha}),error:be({},e,{value:ha}),group:be({},e,{value:ya}),groupCollapsed:be({},e,{value:$r}),groupEnd:be({},e,{value:yi})})}Rn<0&&d("disabledDepth fell below zero. This is a bug in React. Please file an issue.")}}var oa=me.ReactCurrentDispatcher,hr;function nr(e,t,n){{if(hr===void 0)try{throw Error()}catch(r){var a=r.stack.trim().match(/\n( *(at )?)/);hr=a&&a[1]||""}return`
+`+hr+e}}var gi=!1,Pr;{var no=typeof WeakMap=="function"?WeakMap:Map;Pr=new no}function bi(e,t){if(!e||gi)return"";{var n=Pr.get(e);if(n!==void 0)return n}var a;gi=!0;var r=Error.prepareStackTrace;Error.prepareStackTrace=void 0;var i;i=oa.current,oa.current=null,_t();try{if(t){var o=function(){throw Error()};if(Object.defineProperty(o.prototype,"props",{set:function(){throw Error()}}),typeof Reflect=="object"&&Reflect.construct){try{Reflect.construct(o,[])}catch(M){a=M}Reflect.construct(e,[],o)}else{try{o.call()}catch(M){a=M}e.call(o.prototype)}}else{try{throw Error()}catch(M){a=M}e()}}catch(M){if(M&&a&&typeof M.stack=="string"){for(var l=M.stack.split(`
`),u=a.stack.split(`
`),f=l.length-1,v=u.length-1;f>=1&&v>=0&&l[f]!==u[v];)v--;for(;f>=1&&v>=0;f--,v--)if(l[f]!==u[v]){if(f!==1||v!==1)do if(f--,v--,v<0||l[f]!==u[v]){var b=`
-`+l[f].replace(" at new "," at ");return e.displayName&&b.includes("")&&(b=b.replace("",e.displayName)),typeof e=="function"&&Yr.set(e,b),b}while(f>=1&&v>=0);break}}}finally{bi=!1,Sa.current=i,_t(),Error.prepareStackTrace=r}var g=e?e.displayName||e.name:"",D=g?sa(g):"";return typeof e=="function"&&Yr.set(e,D),D}function ro(e,t,n){return Si(e,!0)}function ol(e,t,n){return Si(e,!1)}function Yu(e){var t=e.prototype;return!!(t&&t.isReactComponent)}function ll(e,t,n){if(e==null)return"";if(typeof e=="function")return Si(e,Yu(e));if(typeof e=="string")return sa(e);switch(e){case T:return sa("Suspense");case E:return sa("SuspenseList")}if(typeof e=="object")switch(e.$$typeof){case p:return ol(e.render);case P:return ll(e.type,t,n);case ae:{var a=e,r=a._payload,i=a._init;try{return ll(i(r),t,n)}catch{}}}return""}function af(e){switch(e._debugOwner&&e._debugOwner.type,e._debugSource,e.tag){case ne:return sa(e.type);case Ot:return sa("Lazy");case Re:return sa("Suspense");case ee:return sa("SuspenseList");case ye:case rt:case B:return ol(e.type);case xe:return ol(e.type.render);case oe:return ro(e.type);default:return""}}function Ti(e){try{var t="",n=e;do t+=af(n),n=n.return;while(n);return t}catch(a){return`
+`+l[f].replace(" at new "," at ");return e.displayName&&b.includes("")&&(b=b.replace("",e.displayName)),typeof e=="function"&&Pr.set(e,b),b}while(f>=1&&v>=0);break}}}finally{gi=!1,oa.current=i,za(),Error.prepareStackTrace=r}var g=e?e.displayName||e.name:"",R=g?nr(g):"";return typeof e=="function"&&Pr.set(e,R),R}function ao(e,t,n){return bi(e,!0)}function ol(e,t,n){return bi(e,!1)}function Yu(e){var t=e.prototype;return!!(t&&t.isReactComponent)}function ll(e,t,n){if(e==null)return"";if(typeof e=="function")return bi(e,Yu(e));if(typeof e=="string")return nr(e);switch(e){case S:return nr("Suspense");case A:return nr("SuspenseList")}if(typeof e=="object")switch(e.$$typeof){case y:return ol(e.render);case ve:return ll(e.type,t,n);case z:{var a=e,r=a._payload,i=a._init;try{return ll(i(r),t,n)}catch{}}}return""}function af(e){switch(e._debugOwner&&e._debugOwner.type,e._debugSource,e.tag){case K:return nr(e.type);case Vt:return nr("Lazy");case _:return nr("Suspense");case nt:return nr("SuspenseList");case he:case bt:case De:return ol(e.type);case _e:return ol(e.type.render);case te:return ao(e.type);default:return""}}function Si(e){try{var t="",n=e;do t+=af(n),n=n.return;while(n);return t}catch(a){return`
Error generating stack: `+a.message+`
-`+a.stack}}function Iu(e,t,n){var a=e.displayName;if(a)return a;var r=t.displayName||t.name||"";return r!==""?n+"("+r+")":n}function ul(e){return e.displayName||"Context"}function Et(e){if(e==null)return null;if(typeof e.tag=="number"&&d("Received an unexpected object in getComponentNameFromType(). This is likely a bug in React. Please file an issue."),typeof e=="function")return e.displayName||e.name||null;if(typeof e=="string")return e;switch(e){case F:return"Fragment";case y:return"Portal";case S:return"Profiler";case _:return"StrictMode";case T:return"Suspense";case E:return"SuspenseList"}if(typeof e=="object")switch(e.$$typeof){case c:var t=e;return ul(t)+".Consumer";case w:var n=e;return ul(n._context)+".Provider";case p:return Iu(e,e.render,"ForwardRef");case P:var a=e.displayName||null;return a!==null?a:Et(e.type)||"Memo";case ae:{var r=e,i=r._payload,o=r._init;try{return Et(o(i))}catch{return null}}}return null}function rf(e,t,n){var a=t.displayName||t.name||"";return e.displayName||(a!==""?n+"("+a+")":n)}function gr(e){return e.displayName||"Context"}function Ke(e){var t=e.tag,n=e.type;switch(t){case Be:return"Cache";case Rt:var a=n;return gr(a)+".Consumer";case Xe:var r=n;return gr(r._context)+".Provider";case Fe:return"DehydratedFragment";case xe:return rf(n,n.render,"ForwardRef");case kt:return"Fragment";case ne:return n;case j:return"Portal";case G:return"Root";case Ue:return"Text";case Ot:return Et(n);case xt:return n===_?"StrictMode":"Mode";case X:return"Offscreen";case mt:return"Profiler";case Je:return"Scope";case Re:return"Suspense";case ee:return"SuspenseList";case I:return"TracingMarker";case oe:case ye:case at:case rt:case et:case B:if(typeof n=="function")return n.displayName||n.name||null;if(typeof n=="string")return n;break}return null}var sl=me.ReactDebugCurrentFrame,Jn=null,Ei=!1;function Ir(){{if(Jn===null)return null;var e=Jn._debugOwner;if(e!==null&&typeof e<"u")return Ke(e)}return null}function of(){return Jn===null?"":Ti(Jn)}function Mn(){sl.getCurrentStack=null,Jn=null,Ei=!1}function tn(e){sl.getCurrentStack=e===null?null:of,Jn=e,Ei=!1}function Wu(){return Jn}function Ma(e){Ei=e}function ea(e){return""+e}function Pa(e){switch(typeof e){case"boolean":case"number":case"string":case"undefined":return e;case"object":return ue(e),e;default:return""}}var lf={button:!0,checkbox:!0,image:!0,hidden:!0,radio:!0,reset:!0,submit:!0};function cl(e,t){lf[t.type]||t.onChange||t.onInput||t.readOnly||t.disabled||t.value==null||d("You provided a `value` prop to a form field without an `onChange` handler. This will render a read-only field. If the field should be mutable use `defaultValue`. Otherwise, set either `onChange` or `readOnly`."),t.onChange||t.readOnly||t.disabled||t.checked==null||d("You provided a `checked` prop to a form field without an `onChange` handler. This will render a read-only field. If the field should be mutable use `defaultChecked`. Otherwise, set either `onChange` or `readOnly`.")}function qu(e){var t=e.type,n=e.nodeName;return n&&n.toLowerCase()==="input"&&(t==="checkbox"||t==="radio")}function fl(e){return e._valueTracker}function io(e){e._valueTracker=null}function uf(e){var t="";return e&&(qu(e)?t=e.checked?"true":"false":t=e.value),t}function Wr(e){var t=qu(e)?"checked":"value",n=Object.getOwnPropertyDescriptor(e.constructor.prototype,t);ue(e[t]);var a=""+e[t];if(!(e.hasOwnProperty(t)||typeof n>"u"||typeof n.get!="function"||typeof n.set!="function")){var r=n.get,i=n.set;Object.defineProperty(e,t,{configurable:!0,get:function(){return r.call(this)},set:function(l){ue(l),a=""+l,i.call(this,l)}}),Object.defineProperty(e,t,{enumerable:n.enumerable});var o={getValue:function(){return a},setValue:function(l){ue(l),a=""+l},stopTracking:function(){io(e),delete e[t]}};return o}}function Ci(e){fl(e)||(e._valueTracker=Wr(e))}function dl(e){if(!e)return!1;var t=fl(e);if(!t)return!0;var n=t.getValue(),a=uf(e);return a!==n?(t.setValue(a),!0):!1}function br(e){if(e=e||(typeof document<"u"?document:void 0),typeof e>"u")return null;try{return e.activeElement||e.body}catch{return e.body}}var oo=!1,lo=!1,uo=!1,Gu=!1;function Xu(e){var t=e.type==="checkbox"||e.type==="radio";return t?e.checked!=null:e.value!=null}function vl(e,t){var n=e,a=t.checked,r=Me({},t,{defaultChecked:void 0,defaultValue:void 0,value:void 0,checked:a??n._wrapperState.initialChecked});return r}function Qu(e,t){cl("input",t),t.checked!==void 0&&t.defaultChecked!==void 0&&!lo&&(d("%s contains an input of type %s with both checked and defaultChecked props. Input elements must be either controlled or uncontrolled (specify either the checked prop, or the defaultChecked prop, but not both). Decide between using a controlled or uncontrolled input element and remove one of these props. More info: https://reactjs.org/link/controlled-components",Ir()||"A component",t.type),lo=!0),t.value!==void 0&&t.defaultValue!==void 0&&!oo&&(d("%s contains an input of type %s with both value and defaultValue props. Input elements must be either controlled or uncontrolled (specify either the value prop, or the defaultValue prop, but not both). Decide between using a controlled or uncontrolled input element and remove one of these props. More info: https://reactjs.org/link/controlled-components",Ir()||"A component",t.type),oo=!0);var n=e,a=t.defaultValue==null?"":t.defaultValue;n._wrapperState={initialChecked:t.checked!=null?t.checked:t.defaultChecked,initialValue:Pa(t.value!=null?t.value:a),controlled:Xu(t)}}function s(e,t){var n=e,a=t.checked;a!=null&&ya(n,"checked",a,!1)}function h(e,t){var n=e;{var a=Xu(t);!n._wrapperState.controlled&&a&&!Gu&&(d("A component is changing an uncontrolled input to be controlled. This is likely caused by the value changing from undefined to a defined value, which should not happen. Decide between using a controlled or uncontrolled input element for the lifetime of the component. More info: https://reactjs.org/link/controlled-components"),Gu=!0),n._wrapperState.controlled&&!a&&!uo&&(d("A component is changing a controlled input to be uncontrolled. This is likely caused by the value changing from a defined to undefined, which should not happen. Decide between using a controlled or uncontrolled input element for the lifetime of the component. More info: https://reactjs.org/link/controlled-components"),uo=!0)}s(e,t);var r=Pa(t.value),i=t.type;if(r!=null)i==="number"?(r===0&&n.value===""||n.value!=r)&&(n.value=ea(r)):n.value!==ea(r)&&(n.value=ea(r));else if(i==="submit"||i==="reset"){n.removeAttribute("value");return}t.hasOwnProperty("value")?Oe(n,t.type,r):t.hasOwnProperty("defaultValue")&&Oe(n,t.type,Pa(t.defaultValue)),t.checked==null&&t.defaultChecked!=null&&(n.defaultChecked=!!t.defaultChecked)}function M(e,t,n){var a=e;if(t.hasOwnProperty("value")||t.hasOwnProperty("defaultValue")){var r=t.type,i=r==="submit"||r==="reset";if(i&&(t.value===void 0||t.value===null))return;var o=ea(a._wrapperState.initialValue);n||o!==a.value&&(a.value=o),a.defaultValue=o}var l=a.name;l!==""&&(a.name=""),a.defaultChecked=!a.defaultChecked,a.defaultChecked=!!a._wrapperState.initialChecked,l!==""&&(a.name=l)}function O(e,t){var n=e;h(n,t),K(n,t)}function K(e,t){var n=t.name;if(t.type==="radio"&&n!=null){for(var a=e;a.parentNode;)a=a.parentNode;an(n,"name");for(var r=a.querySelectorAll("input[name="+JSON.stringify(""+n)+'][type="radio"]'),i=0;i.")))}):t.dangerouslySetInnerHTML!=null&&(ft||(ft=!0,d("Pass a `value` prop if you set dangerouslyInnerHTML so React knows which value should be selected.")))),t.selected!=null&&!be&&(d("Use the `defaultValue` or `value` props on