/** * TransitionPreviewOverlay Component * * Full-screen overlay for transition video preview. * Designed to work with useTransitionPlayback hook which manages * video src and playback externally via the videoRef. * * Supports letterbox mode to constrain transitions within canvas bounds, * matching the behavior of background images and UI elements. */ import React from 'react'; import CanvasLoadingSpinner from '../CanvasLoadingSpinner'; interface TransitionPreviewOverlayProps { /** Reference to the video element - useTransitionPlayback manages src and playback */ videoRef: React.RefObject; /** Whether the overlay is visible */ isActive: boolean; /** Whether the video is currently buffering (used to hide video during load) */ isBuffering?: boolean; /** Show loading spinner during buffering (default: false for backward compat) */ showSpinner?: boolean; /** Loading message for spinner */ spinnerMessage?: string; /** Letterbox styles from useCanvasScale - positions overlay within canvas bounds */ letterboxStyles?: React.CSSProperties; /** Video object-fit mode (default: 'contain' to match backgrounds) */ videoFit?: 'contain' | 'cover'; /** Additional opacity value for fade-out effects (0-1) */ opacity?: number; /** Forces video element remount when changed - prevents decoder state issues with pre-created blob URLs */ videoKey?: string; } const TransitionPreviewOverlay: React.FC = ({ videoRef, isActive, isBuffering = false, showSpinner = false, spinnerMessage = 'Preparing transition...', letterboxStyles, videoFit = 'contain', opacity, videoKey, }) => { if (!isActive) return null; // Container opacity: 0 while buffering to prevent black flash // Video first frame = old page background, so we hide everything until ready const containerOpacity = isBuffering ? 0 : (opacity ?? 1); return ( // Outer: full viewport, transparent background // Transparent ensures if Safari clears video frame when paused, // the new page background shows through instead of black flash
{/* Loading spinner during buffering - provides user feedback */} {isBuffering && showSpinner && ( )} {/* Video container - hidden while buffering */}
{/* Inner: respects letterbox dimensions when provided */}
{/* Video element - container handles visibility, video is always opaque */} {/* key forces React to remount the video element when URL changes, clearing decoder state */}
); }; export default TransitionPreviewOverlay;