Repository: alyssaxuu/screenity Branch: master Commit: aaba4088b293 Files: 376 Total size: 53.8 MB Directory structure: gitextract_mujineax/ ├── .babelrc ├── .gitattributes ├── .github/ │ └── FUNDING.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── custom.config.js ├── package.json ├── patches/ │ ├── fabric+5.3.0.patch │ ├── fabric+5.5.2.patch │ └── plyr+3.7.8.patch ├── scripts/ │ └── patch-radix.js ├── src/ │ ├── _locales/ │ │ ├── ca/ │ │ │ └── messages.json │ │ ├── de/ │ │ │ └── messages.json │ │ ├── en/ │ │ │ └── messages.json │ │ ├── es/ │ │ │ └── messages.json │ │ ├── fr/ │ │ │ └── messages.json │ │ ├── hi/ │ │ │ └── messages.json │ │ ├── id/ │ │ │ └── messages.json │ │ ├── it/ │ │ │ └── messages.json │ │ ├── ko/ │ │ │ └── messages.json │ │ ├── pl/ │ │ │ └── messages.json │ │ ├── pt_BR/ │ │ │ └── messages.json │ │ ├── pt_PT/ │ │ │ └── messages.json │ │ ├── ru/ │ │ │ └── messages.json │ │ ├── ta/ │ │ │ └── messages.json │ │ ├── tr/ │ │ │ └── messages.json │ │ ├── uk/ │ │ │ └── messages.json │ │ ├── zh_CN/ │ │ │ └── messages.json │ │ └── zh_TW/ │ │ └── messages.json │ ├── assets/ │ │ ├── editor/ │ │ │ └── index.html │ │ ├── fonts/ │ │ │ └── fonts.css │ │ ├── mediapipeVision/ │ │ │ ├── selfie_segmenter.tflite │ │ │ ├── vision_wasm_internal.js │ │ │ ├── vision_wasm_internal.wasm │ │ │ ├── vision_wasm_nosimd_internal.js │ │ │ └── vision_wasm_nosimd_internal.wasm │ │ ├── satoshi.css │ │ └── vendor/ │ │ ├── ffmpeg-core.js │ │ ├── ffmpeg-core.wasm │ │ ├── ffmpeg-core.worker.js │ │ └── gif.js/ │ │ └── gif.worker.js │ ├── manifest.json │ ├── media/ │ │ └── fastRecorderGate.ts │ ├── messaging/ │ │ └── messageRouter.js │ ├── pages/ │ │ ├── AudioOffscreen/ │ │ │ ├── index.html │ │ │ └── index.js │ │ ├── Background/ │ │ │ ├── alarms/ │ │ │ │ ├── addAlarmListener.js │ │ │ │ └── handleAlarm.js │ │ │ ├── auth/ │ │ │ │ └── loginWithWebsite.js │ │ │ ├── backup/ │ │ │ │ └── initBackup.js │ │ │ ├── drive/ │ │ │ │ ├── handleSaveToDrive.js │ │ │ │ └── handleSignOutDrive.js │ │ │ ├── index.js │ │ │ ├── listeners/ │ │ │ │ ├── index.js │ │ │ │ ├── onActionButtonClickedListener.js │ │ │ │ ├── onCommandListener.js │ │ │ │ ├── onInstalledListener.js │ │ │ │ ├── onMessageExternalListener.js │ │ │ │ ├── onStartupListener.js │ │ │ │ ├── onTabActivatedListener.js │ │ │ │ ├── onTabRemovedListener.js │ │ │ │ ├── onTabUpdatedListener.js │ │ │ │ └── onWindowFocusChangedListener.js │ │ │ ├── messaging/ │ │ │ │ └── handlers.js │ │ │ ├── modules/ │ │ │ │ └── signIn.js │ │ │ ├── offscreen/ │ │ │ │ ├── closeOffscreenDocument.js │ │ │ │ ├── discardOffscreenDocuments.js │ │ │ │ └── offscreenDocument.js │ │ │ ├── recording/ │ │ │ │ ├── cancelRecording.js │ │ │ │ ├── checkRecording.js │ │ │ │ ├── chunkHandler.js │ │ │ │ ├── cloudLocalPlaybackConstants.js │ │ │ │ ├── desktopCapture.js │ │ │ │ ├── discardRecording.js │ │ │ │ ├── forceProcessing.js │ │ │ │ ├── getStreamingData.js │ │ │ │ ├── recordingHelpers.js │ │ │ │ ├── restartRecording.js │ │ │ │ ├── restoreCloudRecording.js │ │ │ │ ├── restoreRecording.js │ │ │ │ ├── sendChunks.js │ │ │ │ ├── sendMessageRecord.js │ │ │ │ ├── startRecording.js │ │ │ │ └── stopRecording.js │ │ │ ├── tabManagement/ │ │ │ │ ├── createTab.js │ │ │ │ ├── editorTab.js │ │ │ │ ├── focusTab.js │ │ │ │ ├── getCurrentTab.js │ │ │ │ ├── index.js │ │ │ │ ├── removeTab.js │ │ │ │ ├── resetActiveTab.js │ │ │ │ ├── sendMessageTab.js │ │ │ │ ├── setSurface.js │ │ │ │ └── tabHelpers.js │ │ │ └── utils/ │ │ │ ├── base64ToUint8Array.js │ │ │ ├── blobToBase64.js │ │ │ ├── browserHelpers.js │ │ │ ├── downloadHelpers.js │ │ │ ├── executeScripts.js │ │ │ ├── featureDetection.js │ │ │ └── waitForContentScript.js │ │ ├── Backup/ │ │ │ ├── Backup.jsx │ │ │ ├── index.html │ │ │ ├── index.jsx │ │ │ └── messaging/ │ │ │ └── handlers.js │ │ ├── Camera/ │ │ │ ├── Camera.jsx │ │ │ ├── components/ │ │ │ │ └── Background.js │ │ │ ├── context/ │ │ │ │ └── CameraContext.js │ │ │ ├── index.html │ │ │ ├── index.jsx │ │ │ ├── messaging/ │ │ │ │ └── handlers.js │ │ │ └── utils/ │ │ │ ├── backgroundUtils.js │ │ │ ├── cameraUtils.js │ │ │ ├── canvasUtils.js │ │ │ ├── effects.js │ │ │ └── uiState.js │ │ ├── CloudRecorder/ │ │ │ ├── CloudRecorder.jsx │ │ │ ├── RecorderUI.jsx │ │ │ ├── bunnyTusUploader.js │ │ │ ├── createVideoProject.js │ │ │ ├── index.html │ │ │ ├── index.jsx │ │ │ ├── messaging.js │ │ │ ├── recorderConfig.js │ │ │ └── warning/ │ │ │ └── Warning.jsx │ │ ├── Content/ │ │ │ ├── Content.jsx │ │ │ ├── DevHUD.jsx │ │ │ ├── Wrapper.jsx │ │ │ ├── camera/ │ │ │ │ ├── Camera.jsx │ │ │ │ ├── components/ │ │ │ │ │ └── ResizeHandle.jsx │ │ │ │ ├── layout/ │ │ │ │ │ ├── CameraToolbar.jsx │ │ │ │ │ └── CameraWrap.jsx │ │ │ │ └── styles/ │ │ │ │ ├── _Camera.scss │ │ │ │ ├── components/ │ │ │ │ │ └── _ResizeHandle.scss │ │ │ │ └── layout/ │ │ │ │ ├── _CameraToolbar.scss │ │ │ │ └── _CameraWrap.scss │ │ │ ├── camera-only/ │ │ │ │ ├── CameraOnly.jsx │ │ │ │ ├── layout/ │ │ │ │ │ └── CameraWrap.jsx │ │ │ │ └── styles/ │ │ │ │ └── _CameraOnly.scss │ │ │ ├── canvas/ │ │ │ │ ├── Canvas.jsx │ │ │ │ ├── layout/ │ │ │ │ │ ├── CanvasWrap.jsx │ │ │ │ │ └── TextToolbar.jsx │ │ │ │ ├── modules/ │ │ │ │ │ ├── ArrowTool.jsx │ │ │ │ │ ├── CustomControls.jsx │ │ │ │ │ ├── EraserTool.jsx │ │ │ │ │ ├── History.jsx │ │ │ │ │ ├── ImageTool.jsx │ │ │ │ │ ├── PenTool.jsx │ │ │ │ │ ├── SelectTool.jsx │ │ │ │ │ ├── ShapeTool.jsx │ │ │ │ │ └── TextTool.jsx │ │ │ │ └── styles/ │ │ │ │ ├── _Canvas.scss │ │ │ │ └── layout/ │ │ │ │ └── _Canvas.scss │ │ │ ├── context/ │ │ │ │ ├── ContentState.jsx │ │ │ │ ├── messaging/ │ │ │ │ │ └── handlers.js │ │ │ │ └── utils/ │ │ │ │ ├── checkAuthStatus.js │ │ │ │ ├── checkRecording.js │ │ │ │ └── updateFromStorage.js │ │ │ ├── countdown/ │ │ │ │ ├── Countdown.jsx │ │ │ │ └── styles/ │ │ │ │ └── _Countdown.scss │ │ │ ├── cursor/ │ │ │ │ └── trackClicks.js │ │ │ ├── images/ │ │ │ │ └── popup/ │ │ │ │ └── images.js │ │ │ ├── index.css │ │ │ ├── index.js │ │ │ ├── index.jsx │ │ │ ├── modal/ │ │ │ │ ├── Modal.jsx │ │ │ │ └── styles/ │ │ │ │ └── _Modal.scss │ │ │ ├── popup/ │ │ │ │ ├── PopupContainer.jsx │ │ │ │ ├── components/ │ │ │ │ │ ├── BackgroundEffects.jsx │ │ │ │ │ ├── Dropdown.jsx │ │ │ │ │ ├── RegionDimensions.jsx │ │ │ │ │ ├── Switch.jsx │ │ │ │ │ ├── TimeSetter.jsx │ │ │ │ │ ├── TooltipWrap.jsx │ │ │ │ │ └── VideoItem.jsx │ │ │ │ ├── layout/ │ │ │ │ │ ├── Announcement.jsx │ │ │ │ │ ├── InactiveSubscription.jsx │ │ │ │ │ ├── LoggedOut.jsx │ │ │ │ │ ├── RecordingTab.jsx │ │ │ │ │ ├── RecordingType.jsx │ │ │ │ │ ├── Settings.jsx │ │ │ │ │ ├── SettingsMenu.jsx │ │ │ │ │ ├── VideosTab.jsx │ │ │ │ │ ├── Welcome.jsx │ │ │ │ │ └── WelcomeAlternate.jsx │ │ │ │ ├── onboarding/ │ │ │ │ │ ├── proOnboarding.js │ │ │ │ │ └── storage.js │ │ │ │ └── styles/ │ │ │ │ ├── _Popup.scss │ │ │ │ ├── components/ │ │ │ │ │ ├── _BackgroundEffects.scss │ │ │ │ │ ├── _Dropdown.scss │ │ │ │ │ ├── _MainButton.scss │ │ │ │ │ ├── _RegionDimensions.scss │ │ │ │ │ ├── _Switch.scss │ │ │ │ │ ├── _Tabs.scss │ │ │ │ │ ├── _TimeSetter.scss │ │ │ │ │ ├── _Tooltip.scss │ │ │ │ │ └── _VideoItem.scss │ │ │ │ └── layout/ │ │ │ │ ├── _Announcement.scss │ │ │ │ ├── _PopupContainer.scss │ │ │ │ ├── _Settings.scss │ │ │ │ ├── _SettingsMenu.scss │ │ │ │ ├── _VideosTab.scss │ │ │ │ └── _Welcome.scss │ │ │ ├── region/ │ │ │ │ ├── Region.jsx │ │ │ │ ├── components/ │ │ │ │ │ └── RegionHandles.jsx │ │ │ │ ├── layout/ │ │ │ │ │ ├── CameraToolbar.jsx │ │ │ │ │ └── CameraWrap.jsx │ │ │ │ └── styles/ │ │ │ │ ├── _Region.scss │ │ │ │ └── layout/ │ │ │ │ ├── _CameraToolbar.scss │ │ │ │ └── _CameraWrap.scss │ │ │ ├── shortcuts/ │ │ │ │ └── Shortcuts.jsx │ │ │ ├── styles/ │ │ │ │ ├── _variables.scss │ │ │ │ ├── app.css │ │ │ │ ├── app.scss │ │ │ │ └── dist/ │ │ │ │ └── app.css │ │ │ ├── toolbar/ │ │ │ │ ├── Toolbar.jsx │ │ │ │ ├── components/ │ │ │ │ │ ├── ColorWheel.jsx │ │ │ │ │ ├── MicToggle.jsx │ │ │ │ │ ├── RadialMenu.jsx │ │ │ │ │ ├── SVG.jsx │ │ │ │ │ ├── StrokeWeight.jsx │ │ │ │ │ ├── Toast.jsx │ │ │ │ │ ├── ToolTrigger.jsx │ │ │ │ │ └── TooltipWrap.jsx │ │ │ │ ├── layout/ │ │ │ │ │ ├── BlurToolbar.jsx │ │ │ │ │ ├── CursorToolbar.jsx │ │ │ │ │ ├── DrawingToolbar.jsx │ │ │ │ │ ├── ShapeToolbar.jsx │ │ │ │ │ └── ToolbarWrap.jsx │ │ │ │ └── styles/ │ │ │ │ ├── _Page.scss │ │ │ │ ├── components/ │ │ │ │ │ ├── _ColorWheel.scss │ │ │ │ │ ├── _RadialMenu.scss │ │ │ │ │ ├── _StrokeWidth.scss │ │ │ │ │ ├── _Toast.scss │ │ │ │ │ └── _Tooltip.scss │ │ │ │ └── layout/ │ │ │ │ ├── _DrawingToolbar.scss │ │ │ │ ├── _ShapeToolbar.scss │ │ │ │ └── _Toolbar.scss │ │ │ ├── utils/ │ │ │ │ ├── BlurTool.jsx │ │ │ │ ├── CursorModes.jsx │ │ │ │ └── ZoomContainer.jsx │ │ │ └── warning/ │ │ │ ├── Warning.jsx │ │ │ └── styles/ │ │ │ └── _Warning.scss │ │ ├── Download/ │ │ │ ├── Download.jsx │ │ │ ├── index.html │ │ │ └── index.jsx │ │ ├── Editor/ │ │ │ ├── Sandbox.jsx │ │ │ ├── index.html │ │ │ ├── index.jsx │ │ │ └── utils/ │ │ │ ├── addAudioToVideo.js │ │ │ ├── base64toBlob.js │ │ │ ├── blobToArrayBuffer.js │ │ │ ├── cropVideo.js │ │ │ ├── cutVideo.js │ │ │ ├── getFrame.js │ │ │ ├── hasAudio.js │ │ │ ├── muteVideo.js │ │ │ ├── reencodeVideo.js │ │ │ ├── toGIF.js │ │ │ └── toWebM.js │ │ ├── EditorViewer/ │ │ │ ├── Sandbox.jsx │ │ │ ├── index.css │ │ │ ├── index.html │ │ │ └── index.jsx │ │ ├── EditorWebCodecs/ │ │ │ ├── Sandbox.jsx │ │ │ ├── index.html │ │ │ ├── index.jsx │ │ │ ├── mediabunny/ │ │ │ │ └── lib/ │ │ │ │ ├── videoAudioMixer.ts │ │ │ │ ├── videoConverter.ts │ │ │ │ ├── videoCropper.ts │ │ │ │ ├── videoCutter.ts │ │ │ │ ├── videoMuter.ts │ │ │ │ └── videoTrimmer.ts │ │ │ └── utils/ │ │ │ ├── addAudioToVideo.js │ │ │ ├── blobToArrayBuffer.js │ │ │ ├── convertMp4ToWebm.js │ │ │ ├── convertWebmToMp4.js │ │ │ ├── cropVideo.js │ │ │ ├── cutVideo.js │ │ │ ├── getFrame.js │ │ │ ├── hasAudio.js │ │ │ ├── muteVideo.js │ │ │ ├── reencodeVideo.js │ │ │ └── toGIF.js │ │ ├── Permissions/ │ │ │ ├── Permissions.jsx │ │ │ ├── index.html │ │ │ └── index.jsx │ │ ├── Playground/ │ │ │ ├── Setup.jsx │ │ │ ├── index.html │ │ │ └── index.jsx │ │ ├── Recorder/ │ │ │ ├── Recorder.jsx │ │ │ ├── RecorderUI.jsx │ │ │ ├── index.html │ │ │ ├── index.jsx │ │ │ ├── mediaRecorderUtils.js │ │ │ ├── messaging.js │ │ │ ├── recorderConfig.js │ │ │ ├── warning/ │ │ │ │ └── Warning.jsx │ │ │ └── webcodecs/ │ │ │ ├── Mp4MuxerWrapper.ts │ │ │ └── WebCodecsRecorder.js │ │ ├── RecorderOffscreen/ │ │ │ ├── RecorderOffscreen.jsx │ │ │ ├── index.html │ │ │ └── index.jsx │ │ ├── Region/ │ │ │ ├── Recorder.jsx │ │ │ ├── index.html │ │ │ └── index.jsx │ │ ├── Sandbox/ │ │ │ ├── DevHUD.jsx │ │ │ ├── Sandbox.jsx │ │ │ ├── components/ │ │ │ │ ├── editor/ │ │ │ │ │ ├── CropperWrap.jsx │ │ │ │ │ ├── Dropdown.jsx │ │ │ │ │ ├── Switch.jsx │ │ │ │ │ ├── Trimmer.jsx │ │ │ │ │ ├── VideoPlayer.jsx │ │ │ │ │ └── Waveform.jsx │ │ │ │ ├── global/ │ │ │ │ │ ├── Modal.jsx │ │ │ │ │ └── ProBanner.jsx │ │ │ │ └── player/ │ │ │ │ ├── HelpButton.jsx │ │ │ │ ├── ShareModal.jsx │ │ │ │ ├── Title.jsx │ │ │ │ └── VideoPlayer.jsx │ │ │ ├── context/ │ │ │ │ └── ContentState.jsx │ │ │ ├── index.html │ │ │ ├── index.jsx │ │ │ ├── layout/ │ │ │ │ ├── editor/ │ │ │ │ │ ├── AudioNav.js │ │ │ │ │ ├── AudioUI.js │ │ │ │ │ ├── CropNav.js │ │ │ │ │ ├── CropUI.js │ │ │ │ │ ├── Editor.js │ │ │ │ │ ├── EditorNav.js │ │ │ │ │ └── TrimUI.js │ │ │ │ └── player/ │ │ │ │ ├── Content.js │ │ │ │ ├── Player.js │ │ │ │ ├── PlayerNav.js │ │ │ │ └── RightPanel.js │ │ │ └── styles/ │ │ │ ├── edit/ │ │ │ │ ├── _Dropdown.module.scss │ │ │ │ ├── _EditorNav.module.scss │ │ │ │ ├── _Switch.module.scss │ │ │ │ ├── _TrimUI.module.scss │ │ │ │ ├── _Trimmer.module.scss │ │ │ │ ├── _VideoPlayer.scss │ │ │ │ └── _Waveform.module.scss │ │ │ ├── global/ │ │ │ │ ├── _app.scss │ │ │ │ └── _variables.scss │ │ │ ├── player/ │ │ │ │ ├── _Content.module.scss │ │ │ │ ├── _HelpButton.module.scss │ │ │ │ ├── _Nav.module.scss │ │ │ │ ├── _Player.module.scss │ │ │ │ ├── _RightPanel.module.scss │ │ │ │ ├── _ShareModal.module.scss │ │ │ │ └── _Title.module.scss │ │ │ └── plyr.css │ │ ├── Setup/ │ │ │ ├── Setup.jsx │ │ │ ├── index.html │ │ │ └── index.jsx │ │ ├── Waveform/ │ │ │ ├── Waveform.jsx │ │ │ ├── index.html │ │ │ └── index.jsx │ │ └── utils/ │ │ ├── buildDiagnosticZip.js │ │ ├── buildSupportContext.js │ │ ├── buildSupportDebugInfo.js │ │ ├── diagnosticLog.js │ │ ├── errorCodes.js │ │ ├── filenameHelpers.js │ │ ├── mediaDeviceFallback.js │ │ ├── recordingDebug.js │ │ └── startFlowTrace.js │ └── schema.json ├── tsconfig.json ├── utils/ │ ├── autoReloadClients/ │ │ ├── backgroundClient.js │ │ └── contentScriptClient.js │ ├── build.js │ ├── env.js │ └── server.js └── webpack.config.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: .babelrc ================================================ { "presets": [ ["@babel/preset-env", { "targets": { "chrome": "110" } }], ["@babel/preset-react", { "runtime": "automatic" }] ] } ================================================ FILE: .gitattributes ================================================ # Auto detect text files and perform LF normalization * text=auto ================================================ FILE: .github/FUNDING.yml ================================================ # These are supported funding model platforms github: alyssaxuu ================================================ FILE: .gitignore ================================================ logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* node_modules/ .DS_Store .vscode/ .env .env.local .env.development .env.production .env.*.local build/ docs/ tools/ ================================================ FILE: CODE_OF_CONDUCT.md ================================================ # Contributor Covenant Code of Conduct ## Our Pledge We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. ## Our Standards Examples of behavior that contributes to a positive environment for our community include: * Demonstrating empathy and kindness toward other people * Being respectful of differing opinions, viewpoints, and experiences * Giving and gracefully accepting constructive feedback * Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience * Focusing on what is best not just for us as individuals, but for the overall community Examples of unacceptable behavior include: * The use of sexualized language or imagery, and sexual attention or advances of any kind * Trolling, insulting or derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or email address, without their explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Enforcement Responsibilities Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful. Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate. ## Scope This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at hi@alyssax.com. All complaints will be reviewed and investigated promptly and fairly. All community leaders are obligated to respect the privacy and security of the reporter of any incident. ## Enforcement Guidelines Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct: ### 1. Correction **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community. **Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested. ### 2. Warning **Community Impact**: A violation through a single incident or series of actions. **Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban. ### 3. Temporary Ban **Community Impact**: A serious violation of community standards, including sustained inappropriate behavior. **Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban. ### 4. Permanent Ban **Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. **Consequence**: A permanent ban from any sort of public interaction within the community. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0, available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity). [homepage]: https://www.contributor-covenant.org For answers to common questions about this code of conduct, see the FAQ at https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations. ================================================ FILE: LICENSE ================================================ GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . ================================================ FILE: README.md ================================================ # Screenity > _✨ Screenity's open source work is sponsored by_ > ### Recall.ai - API for desktop recording [](https://www.recall.ai/product/desktop-recording-sdk?utm_source=github&utm_medium=sponsorship&utm_campaign=alyssaxuu-screenity) > > If you’re looking for a hosted desktop recording API, consider checking out [Recall.ai](https://www.recall.ai/product/desktop-recording-sdk?utm_source=github&utm_medium=sponsorship&utm_campaign=alyssaxuu-screenity), an API that records Zoom, Google Meet, Microsoft Teams, in-person meetings, and more. [![jiewjjc232](https://github.com/alyssaxuu/screenity/assets/7581348/ed55e52e-4adf-442b-b774-6856abacdffb)](https://screenity.io) The free and privacy-friendly screen recorder with no limits 🎥 [Get it now - it's free!](https://chrome.google.com/webstore/detail/screenity-screen-recorder/kbbdabhdfibnancpjfhlkhafgdilcnji) Screenity is a powerful privacy-friendly screen recorder and annotation tool to make better videos for work, education, and more. You can create stunning product demos, tutorials, presentations, or share feedback with your team - all for free. Screenity - The most powerful screen recorder for Chrome | Product Hunt Featured on HackerNews > Want to support the project (and the solo developer behind it)? > > Check out [**Screenity Pro**](https://screenity.io/pro): a privacy-friendly, EU-hosted platform with link sharing, multi-scene editing, zoom keyframes, captions, and more ❤️ Made by [Alyssa X](https://alyssax.com) ## Table of contents - [Screenity](#screenity) - [Table of contents](#table-of-contents) - [Features](#features) - [Self-hosting Screenity](#self-hosting-screenity) - [Creating a development version](#creating-a-development-version) - [Enabling Save to Google Drive](#enabling-save-to-google-drive) - [Acknowledgements](#acknowledgements) ## Features 🎥 Make unlimited recordings of your tab, a specific area, desktop, any application, or camera
🎙️ Record your microphone or internal audio, and use features like push to talk
✏️ Annotate by drawing anywhere on the screen, adding text, arrows, shapes, and more
✨ Use AI-powered camera backgrounds or blur to enhance your recordings
🔎 Zoom in smoothly in your recordings to focus on specific areas
🪄 Blur out any sensitive content of any page to keep it private
✂️ Remove or add audio, cut, trim, or crop your recordings with a comprehensive editor
👀 Highlight your clicks and cursor, and go in spotlight mode
⏱️ Set up alarms to automatically stop your recording
💾 Export as mp4, gif, and webm, or save the video directly to Google Drive to share a link
⚙️ Set a countdown, hide parts of the UI, or move it anywhere
🔒 Only you can see your videos, we don’t collect any of your data. You can even go offline!
💙 No limits, make as many videos as you want, for as long as you want
…and much more - all for free & no sign in needed! ## Self-hosting Screenity > 🛠️ Note: When self-hosted, the extension runs entirely in local-only mode. > No API calls, sign-in flows, or platform features are enabled, nothing is sent anywhere. > Some internal code paths connect to [Screenity Pro](https://screenity.io/pro), but these are only active in the official Chrome Store version. You can run Screenity locally without having to install it from the Chrome Store. Here's how: 1. Download the latest Build.zip from the [releases page](https://github.com/alyssaxuu/screenity/releases) 2. Load the extension by pasting `chrome://extensions/` in the address bar, and [enabling developer mode](https://developer.chrome.com/docs/extensions/mv2/faq/#:~:text=You%20can%20start%20by%20turning,a%20packaged%20extension%2C%20and%20more.). 3. Drag the folder that contains the code (make sure it's a folder and not a ZIP file, so unzip first), or click on the "Load unpacked" button and locate the folder. 4. That's it, you should now be able to use Screenity locally. [Follow these instructions](#enabling-save-to-google-drive) to set up the Google Drive integration. Self-hosting is totally fine for personal, educational, or internal use. If you’re thinking of building a commercial product from this, feel free to [reach out](mailto:alyssa@screenity.io), I’m open to chatting 💜 ## Creating a development version > ❗️ Note that the license has changed to [GPLv3](https://github.com/alyssaxuu/screenity/blob/master/LICENSE) for the current MV3 version (Screenity version 3.0.0 and higher). Make sure to read the license and the [Terms of Service](https://screenity.io/en/terms/) regarding intellectual property. 1. Check if your [Node.js](https://nodejs.org/) version is >= **14**. 2. Clone this repository. 3. Run `npm install` to install dependencies. 4. Run `npm start` to start the local development server. 5. Open `chrome://extensions/` in your browser and [enable developer mode](https://developer.chrome.com/docs/extensions/mv2/faq/#:~:text=You%20can%20start%20by%20turning,a%20packaged%20extension%2C%20and%20more.). 6. Click **Load unpacked** and select the `build` folder. 7. The extension should now be available locally. To rebuild after code changes, run `npm run build`. ### Enabling Save to Google Drive To enable the Google Drive Upload (authorization consent screen) you must change the client_id in the manifest.json file with your linked extension key. You can create it accessing [Google Cloud Console](https://console.cloud.google.com/apis/credentials) and selecting Create Credential > OAuth Client ID > Chrome App. To create a persistent extension key, you can follow the steps detailed [here](https://developer.chrome.com/docs/extensions/reference/manifest/key). ## Acknowledgements - Thanks to [HelpKit](https://www.helpkit.so/) for sponsoring this project by hosting the [Screenity Help Center](https://help.screenity.io/). - Thanks to [Mei Xuan](https://www.behance.net/meixuanloo) for helping with the Chinese translation of the extension. If you need any help, or want to become a Screenity expert, you can browse articles and guides in the [help center](https://help.screenity.io). You can also submit any feedback or ideas in this [form](https://tally.so/r/3ElpXq), or contact through [this page](https://help.screenity.io/contact) ================================================ FILE: custom.config.js ================================================ module.exports = { notHMR: ["background", "contentScript", "devtools", "sandbox"], enableBackgroundAutoReload: true, enableContentScriptsAutoReload: true, }; ================================================ FILE: package.json ================================================ { "name": "screenity", "version": "4.3.0", "description": "The free and privacy-friendly screen recorder with no sign in needed.", "scripts": { "build": "cross-env NODE_ENV=production node utils/build.js", "start": "cross-env NODE_ENV=development node utils/server.js", "dev": "npm run hot-reload", "watch": "cross-env NODE_ENV=development webpack --watch", "hot-reload": "cross-env NODE_ENV=development webpack serve --hot", "prettier": "prettier --write '**/*.{js,jsx,ts,tsx,json,css,scss,md}'", "postinstall": "patch-package && node scripts/patch-radix.js", "package": "npm run build && cd build && zip -r ../extension.zip . && cd ..", "clean": "rm -rf build && mkdir build", "lint": "eslint src/**/*.js --fix" }, "dependencies": { "@babel/plugin-transform-react-jsx": "^7.27.1", "@mediapipe/tasks-vision": "0.10.32", "@radix-ui/react-alert-dialog": "^1.0.4", "@radix-ui/react-collapsible": "^1.0.2", "@radix-ui/react-dropdown-menu": "^2.0.6", "@radix-ui/react-popover": "^1.0.5", "@radix-ui/react-select": "^1.2.1", "@radix-ui/react-slider": "^1.1.2", "@radix-ui/react-switch": "^1.0.2", "@radix-ui/react-tabs": "^1.0.3", "@radix-ui/react-toast": "^1.2.13", "@radix-ui/react-toggle": "^1.1.8", "@radix-ui/react-toggle-group": "^1.1.9", "@radix-ui/react-toolbar": "^1.1.9", "@radix-ui/react-tooltip": "^1.2.6", "@sentry/browser": "^7.94.1", "@uiw/color-convert": "^1.2.2", "@uiw/react-color-wheel": "^1.2.2", "axios": "^1.6.2", "concurrently": "^8.2.0", "dotenv": "^17.0.1", "driver.js": "^1.3.6", "fabric": "^5.3.0", "fix-webm-duration": "^1.0.5", "gif.js": "^0.2.0", "jszip": "^3.10.1", "localforage": "^1.10.0", "mediabunny": "^1.24.4", "patch-package": "^8.0.0", "plyr": "3.7.8", "plyr-react": "^5.3.0", "querystring": "^0.2.1", "raw-loader": "^4.0.2", "react": "^18.2.0", "react-advanced-cropper": "^0.19.4", "react-dom": "^18.2.0", "react-hotkeys-hook": "^4.4.1", "react-rnd": "^10.4.1", "react-shadow": "^20.0.0", "react-svg": "^16.1.18", "ssestream": "^1.1.0", "tar": "7.5.9", "wavesurfer.js": "^7.4.2", "webm-duration-fix": "^1.0.4" }, "devDependencies": { "@babel/core": "^7.20.2", "@babel/plugin-transform-runtime": "^7.27.1", "@babel/preset-env": "^7.20.2", "@babel/preset-react": "^7.27.1", "@types/react": "^18.3.21", "@types/react-dom": "^18.3.7", "babel-loader": "^9.1.3", "copy-webpack-plugin": "^7.0.0", "cross-env": "^7.0.3", "css-loader": "^6.7.2", "fs-extra": "^10.1.0", "html-loader": "^3.1.0", "html-webpack-plugin": "^5.5.0", "prettier": "^2.7.1", "sass": "^1.97.3", "sass-loader": "^16.0.7", "source-map-loader": "^3.0.1", "style-loader": "^3.3.1", "terser-webpack-plugin": "^5.3.6", "ts-loader": "^9.4.1", "typescript": "^4.9.3", "webpack-cli": "^5.1.4", "webpack-dev-server": "^4.11.1" }, "overrides": { "tar": "7.5.9", "minimatch": "10.2.2" } } ================================================ FILE: patches/fabric+5.3.0.patch ================================================ diff --git a/node_modules/fabric/dist/fabric.js b/node_modules/fabric/dist/fabric.js index faee7fc..02e5ae3 100644 --- a/node_modules/fabric/dist/fabric.js +++ b/node_modules/fabric/dist/fabric.js @@ -12591,7 +12591,7 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab this.cacheCanvasEl = this._createCanvasElement(); this.cacheCanvasEl.setAttribute('width', this.width); this.cacheCanvasEl.setAttribute('height', this.height); - this.contextCache = this.cacheCanvasEl.getContext('2d'); + this.contextCache = this.cacheCanvasEl.getContext('2d', {willReadFrequently: true}); }, /** @@ -15103,7 +15103,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati _createCacheCanvas: function() { this._cacheProperties = {}; this._cacheCanvas = fabric.util.createCanvasElement(); - this._cacheContext = this._cacheCanvas.getContext('2d'); + this._cacheContext = this._cacheCanvas.getContext('2d', {willReadFrequently: true}); this._updateCacheCanvas(); // if canvas gets created, is empty, so dirty. this.dirty = true; ================================================ FILE: patches/fabric+5.5.2.patch ================================================ diff --git a/node_modules/fabric/dist/fabric.js b/node_modules/fabric/dist/fabric.js index a7ad0f5..d8e7670 100644 --- a/node_modules/fabric/dist/fabric.js +++ b/node_modules/fabric/dist/fabric.js @@ -12596,7 +12596,7 @@ fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fab this.cacheCanvasEl = this._createCanvasElement(); this.cacheCanvasEl.setAttribute('width', this.width); this.cacheCanvasEl.setAttribute('height', this.height); - this.contextCache = this.cacheCanvasEl.getContext('2d'); + this.contextCache = this.cacheCanvasEl.getContext('2d', {willReadFrequently: true}); }, /** @@ -15108,7 +15108,7 @@ fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.Stati _createCacheCanvas: function() { this._cacheProperties = {}; this._cacheCanvas = fabric.util.createCanvasElement(); - this._cacheContext = this._cacheCanvas.getContext('2d'); + this._cacheContext = this._cacheCanvas.getContext('2d', {willReadFrequently: true}); this._updateCacheCanvas(); // if canvas gets created, is empty, so dirty. this.dirty = true; ================================================ FILE: patches/plyr+3.7.8.patch ================================================ diff --git a/node_modules/plyr/dist/plyr.js b/node_modules/plyr/dist/plyr.js index 0a79517..e69634f 100644 --- a/node_modules/plyr/dist/plyr.js +++ b/node_modules/plyr/dist/plyr.js @@ -3539,21 +3539,7 @@ typeof navigator === "object" && (function (global, factory) { } }, // URLs - urls: { - download: null, - vimeo: { - sdk: 'https://player.vimeo.com/api/player.js', - iframe: 'https://player.vimeo.com/video/{0}?{1}', - api: 'https://vimeo.com/api/oembed.json?url={0}' - }, - youtube: { - sdk: 'https://www.youtube.com/iframe_api', - api: 'https://noembed.com/embed?url=https://www.youtube.com/watch?v={0}' - }, - googleIMA: { - sdk: 'https://imasdk.googleapis.com/js/sdkloader/ima3.js' - } - }, + urls: null, // Custom control listeners listeners: { seek: null, diff --git a/node_modules/plyr/dist/plyr.min.js b/node_modules/plyr/dist/plyr.min.js index 707308e..25fc326 100644 --- a/node_modules/plyr/dist/plyr.min.js +++ b/node_modules/plyr/dist/plyr.min.js @@ -1,2 +1,2 @@ -"object"==typeof navigator&&function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define("Plyr",t):(e="undefined"!=typeof globalThis?globalThis:e||self).Plyr=t()}(this,(function(){"use strict";function e(e,t,i){return(t=function(e){var t=function(e,t){if("object"!=typeof e||null===e)return e;var i=e[Symbol.toPrimitive];if(void 0!==i){var s=i.call(e,t||"default");if("object"!=typeof s)return s;throw new TypeError("@@toPrimitive must return a primitive value.")}return("string"===t?String:Number)(e)}(e,"string");return"symbol"==typeof t?t:String(t)}(t))in e?Object.defineProperty(e,t,{value:i,enumerable:!0,configurable:!0,writable:!0}):e[t]=i,e}function t(e,t){for(var i=0;it){var i=function(e){var t="".concat(e).match(/(?:\.(\d+))?(?:[eE]([+-]?\d+))?$/);return t?Math.max(0,(t[1]?t[1].length:0)-(t[2]?+t[2]:0)):0}(t);return parseFloat(e.toFixed(i))}return Math.round(e/t)*t}var g=function(){function e(t,i){(function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")})(this,e),m.element(t)?this.element=t:m.string(t)&&(this.element=document.querySelector(t)),m.element(this.element)&&m.empty(this.element.rangeTouch)&&(this.config=n({},a,{},i),this.init())}return function(e,i,s){i&&t(e.prototype,i),s&&t(e,s)}(e,[{key:"init",value:function(){e.enabled&&(this.config.addCSS&&(this.element.style.userSelect="none",this.element.style.webKitUserSelect="none",this.element.style.touchAction="manipulation"),this.listeners(!0),this.element.rangeTouch=this)}},{key:"destroy",value:function(){e.enabled&&(this.config.addCSS&&(this.element.style.userSelect="",this.element.style.webKitUserSelect="",this.element.style.touchAction=""),this.listeners(!1),this.element.rangeTouch=null)}},{key:"listeners",value:function(e){var t=this,i=e?"addEventListener":"removeEventListener";["touchstart","touchmove","touchend"].forEach((function(e){t.element[i](e,(function(e){return t.set(e)}),!1)}))}},{key:"get",value:function(t){if(!e.enabled||!m.event(t))return null;var i,s=t.target,n=t.changedTouches[0],a=parseFloat(s.getAttribute("min"))||0,l=parseFloat(s.getAttribute("max"))||100,r=parseFloat(s.getAttribute("step"))||1,o=s.getBoundingClientRect(),c=100/o.width*(this.config.thumbWidth/2)/100;return 0>(i=100/o.width*(n.clientX-o.left))?i=0:100i?i-=(100-2*i)*c:50null!=e?e.constructor:null,y=(e,t)=>Boolean(e&&t&&e instanceof t),b=e=>null==e,v=e=>f(e)===Object,w=e=>f(e)===String,T=e=>"function"==typeof e,k=e=>Array.isArray(e),C=e=>y(e,NodeList),A=e=>b(e)||(w(e)||k(e)||C(e))&&!e.length||v(e)&&!Object.keys(e).length;var S={nullOrUndefined:b,object:v,number:e=>f(e)===Number&&!Number.isNaN(e),string:w,boolean:e=>f(e)===Boolean,function:T,array:k,weakMap:e=>y(e,WeakMap),nodeList:C,element:e=>null!==e&&"object"==typeof e&&1===e.nodeType&&"object"==typeof e.style&&"object"==typeof e.ownerDocument,textNode:e=>f(e)===Text,event:e=>y(e,Event),keyboardEvent:e=>y(e,KeyboardEvent),cue:e=>y(e,window.TextTrackCue)||y(e,window.VTTCue),track:e=>y(e,TextTrack)||!b(e)&&w(e.kind),promise:e=>y(e,Promise)&&T(e.then),url:e=>{if(y(e,window.URL))return!0;if(!w(e))return!1;let t=e;e.startsWith("http://")&&e.startsWith("https://")||(t=`http://${e}`);try{return!A(new URL(t).hostname)}catch(e){return!1}},empty:A};const E=(()=>{const e=document.createElement("span"),t={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"},i=Object.keys(t).find((t=>void 0!==e.style[t]));return!!S.string(i)&&t[i]})();function P(e,t){setTimeout((()=>{try{e.hidden=!0,e.offsetHeight,e.hidden=!1}catch(e){}}),t)}var M={isIE:Boolean(window.document.documentMode),isEdge:/Edge/g.test(navigator.userAgent),isWebKit:"WebkitAppearance"in document.documentElement.style&&!/Edge/g.test(navigator.userAgent),isIPhone:/iPhone|iPod/gi.test(navigator.userAgent)&&navigator.maxTouchPoints>1,isIPadOS:"MacIntel"===navigator.platform&&navigator.maxTouchPoints>1,isIos:/iPad|iPhone|iPod/gi.test(navigator.userAgent)&&navigator.maxTouchPoints>1};function N(e,t){return t.split(".").reduce(((e,t)=>e&&e[t]),e)}function x(e={},...t){if(!t.length)return e;const i=t.shift();return S.object(i)?(Object.keys(i).forEach((t=>{S.object(i[t])?(Object.keys(e).includes(t)||Object.assign(e,{[t]:{}}),x(e[t],i[t])):Object.assign(e,{[t]:i[t]})})),x(e,...t)):e}function L(e,t){const i=e.length?e:[e];Array.from(i).reverse().forEach(((e,i)=>{const s=i>0?t.cloneNode(!0):t,n=e.parentNode,a=e.nextSibling;s.appendChild(e),a?n.insertBefore(s,a):n.appendChild(s)}))}function I(e,t){S.element(e)&&!S.empty(t)&&Object.entries(t).filter((([,e])=>!S.nullOrUndefined(e))).forEach((([t,i])=>e.setAttribute(t,i)))}function $(e,t,i){const s=document.createElement(e);return S.object(t)&&I(s,t),S.string(i)&&(s.innerText=i),s}function _(e,t,i,s){S.element(t)&&t.appendChild($(e,i,s))}function O(e){S.nodeList(e)||S.array(e)?Array.from(e).forEach(O):S.element(e)&&S.element(e.parentNode)&&e.parentNode.removeChild(e)}function j(e){if(!S.element(e))return;let{length:t}=e.childNodes;for(;t>0;)e.removeChild(e.lastChild),t-=1}function q(e,t){return S.element(t)&&S.element(t.parentNode)&&S.element(e)?(t.parentNode.replaceChild(e,t),e):null}function D(e,t){if(!S.string(e)||S.empty(e))return{};const i={},s=x({},t);return e.split(",").forEach((e=>{const t=e.trim(),n=t.replace(".",""),a=t.replace(/[[\]]/g,"").split("="),[l]=a,r=a.length>1?a[1].replace(/["']/g,""):"";switch(t.charAt(0)){case".":S.string(s.class)?i.class=`${s.class} ${n}`:i.class=n;break;case"#":i.id=t.replace("#","");break;case"[":i[l]=r}})),x(s,i)}function H(e,t){if(!S.element(e))return;let i=t;S.boolean(i)||(i=!e.hidden),e.hidden=i}function R(e,t,i){if(S.nodeList(e))return Array.from(e).map((e=>R(e,t,i)));if(S.element(e)){let s="toggle";return void 0!==i&&(s=i?"add":"remove"),e.classList[s](t),e.classList.contains(t)}return!1}function F(e,t){return S.element(e)&&e.classList.contains(t)}function V(e,t){const{prototype:i}=Element;return(i.matches||i.webkitMatchesSelector||i.mozMatchesSelector||i.msMatchesSelector||function(){return Array.from(document.querySelectorAll(t)).includes(this)}).call(e,t)}function U(e){return this.elements.container.querySelectorAll(e)}function B(e){return this.elements.container.querySelector(e)}function W(e=null,t=!1){S.element(e)&&e.focus({preventScroll:!0,focusVisible:t})}const z={"audio/ogg":"vorbis","audio/wav":"1","video/webm":"vp8, vorbis","video/mp4":"avc1.42E01E, mp4a.40.2","video/ogg":"theora"},K={audio:"canPlayType"in document.createElement("audio"),video:"canPlayType"in document.createElement("video"),check(e,t){const i=K[e]||"html5"!==t;return{api:i,ui:i&&K.rangeInput}},pip:!(M.isIPhone||!S.function($("video").webkitSetPresentationMode)&&(!document.pictureInPictureEnabled||$("video").disablePictureInPicture)),airplay:S.function(window.WebKitPlaybackTargetAvailabilityEvent),playsinline:"playsInline"in document.createElement("video"),mime(e){if(S.empty(e))return!1;const[t]=e.split("/");let i=e;if(!this.isHTML5||t!==this.type)return!1;Object.keys(z).includes(i)&&(i+=`; codecs="${z[e]}"`);try{return Boolean(i&&this.media.canPlayType(i).replace(/no/,""))}catch(e){return!1}},textTracks:"textTracks"in document.createElement("video"),rangeInput:(()=>{const e=document.createElement("input");return e.type="range","range"===e.type})(),touch:"ontouchstart"in document.documentElement,transitions:!1!==E,reducedMotion:"matchMedia"in window&&window.matchMedia("(prefers-reduced-motion)").matches},Y=(()=>{let e=!1;try{const t=Object.defineProperty({},"passive",{get:()=>(e=!0,null)});window.addEventListener("test",null,t),window.removeEventListener("test",null,t)}catch(e){}return e})();function Q(e,t,i,s=!1,n=!0,a=!1){if(!e||!("addEventListener"in e)||S.empty(t)||!S.function(i))return;const l=t.split(" ");let r=a;Y&&(r={passive:n,capture:a}),l.forEach((t=>{this&&this.eventListeners&&s&&this.eventListeners.push({element:e,type:t,callback:i,options:r}),e[s?"addEventListener":"removeEventListener"](t,i,r)}))}function X(e,t="",i,s=!0,n=!1){Q.call(this,e,t,i,!0,s,n)}function J(e,t="",i,s=!0,n=!1){Q.call(this,e,t,i,!1,s,n)}function G(e,t="",i,s=!0,n=!1){const a=(...l)=>{J(e,t,a,s,n),i.apply(this,l)};Q.call(this,e,t,a,!0,s,n)}function Z(e,t="",i=!1,s={}){if(!S.element(e)||S.empty(t))return;const n=new CustomEvent(t,{bubbles:i,detail:{...s,plyr:this}});e.dispatchEvent(n)}function ee(){this&&this.eventListeners&&(this.eventListeners.forEach((e=>{const{element:t,type:i,callback:s,options:n}=e;t.removeEventListener(i,s,n)})),this.eventListeners=[])}function te(){return new Promise((e=>this.ready?setTimeout(e,0):X.call(this,this.elements.container,"ready",e))).then((()=>{}))}function ie(e){S.promise(e)&&e.then(null,(()=>{}))}function se(e){return S.array(e)?e.filter(((t,i)=>e.indexOf(t)===i)):e}function ne(e,t){return S.array(e)&&e.length?e.reduce(((e,i)=>Math.abs(i-t)({...e,[t/i]:[t,i]})),{});function re(e){if(!(S.array(e)||S.string(e)&&e.includes(":")))return!1;return(S.array(e)?e:e.split(":")).map(Number).every(S.number)}function oe(e){if(!S.array(e)||!e.every(S.number))return null;const[t,i]=e,s=(e,t)=>0===t?e:s(t,e%t),n=s(t,i);return[t/n,i/n]}function ce(e){const t=e=>re(e)?e.split(":").map(Number):null;let i=t(e);if(null===i&&(i=t(this.config.ratio)),null===i&&!S.empty(this.embed)&&S.array(this.embed.ratio)&&({ratio:i}=this.embed),null===i&&this.isHTML5){const{videoWidth:e,videoHeight:t}=this.media;i=[e,t]}return oe(i)}function ue(e){if(!this.isVideo)return{};const{wrapper:t}=this.elements,i=ce.call(this,e);if(!S.array(i))return{};const[s,n]=oe(i),a=100/s*n;if(ae(`aspect-ratio: ${s}/${n}`)?t.style.aspectRatio=`${s}/${n}`:t.style.paddingBottom=`${a}%`,this.isVimeo&&!this.config.vimeo.premium&&this.supported.ui){const e=100/this.media.offsetWidth*parseInt(window.getComputedStyle(this.media).paddingBottom,10),i=(e-a)/(e/50);this.fullscreen.active?t.style.paddingBottom=null:this.media.style.transform=`translateY(-${i}%)`}else this.isHTML5&&t.classList.add(this.config.classNames.videoFixedRatio);return{padding:a,ratio:i}}function he(e,t,i=.05){const s=e/t,n=ne(Object.keys(le),s);return Math.abs(n-s)<=i?le[n]:[e,t]}const de={getSources(){if(!this.isHTML5)return[];return Array.from(this.media.querySelectorAll("source")).filter((e=>{const t=e.getAttribute("type");return!!S.empty(t)||K.mime.call(this,t)}))},getQualityOptions(){return this.config.quality.forced?this.config.quality.options:de.getSources.call(this).map((e=>Number(e.getAttribute("size")))).filter(Boolean)},setup(){if(!this.isHTML5)return;const e=this;e.options.speed=e.config.speed.options,S.empty(this.config.ratio)||ue.call(e),Object.defineProperty(e.media,"quality",{get(){const t=de.getSources.call(e).find((t=>t.getAttribute("src")===e.source));return t&&Number(t.getAttribute("size"))},set(t){if(e.quality!==t){if(e.config.quality.forced&&S.function(e.config.quality.onChange))e.config.quality.onChange(t);else{const i=de.getSources.call(e).find((e=>Number(e.getAttribute("size"))===t));if(!i)return;const{currentTime:s,paused:n,preload:a,readyState:l,playbackRate:r}=e.media;e.media.src=i.getAttribute("src"),("none"!==a||l)&&(e.once("loadedmetadata",(()=>{e.speed=r,e.currentTime=s,n||ie(e.play())})),e.media.load())}Z.call(e,e.media,"qualitychange",!1,{quality:t})}}})},cancelRequests(){this.isHTML5&&(O(de.getSources.call(this)),this.media.setAttribute("src",this.config.blankVideo),this.media.load(),this.debug.log("Cancelled network requests"))}};function me(e,...t){return S.empty(e)?e:e.toString().replace(/{(\d+)}/g,((e,i)=>t[i].toString()))}const pe=(e="",t="",i="")=>e.replace(new RegExp(t.toString().replace(/([.*+?^=!:${}()|[\]/\\])/g,"\\$1"),"g"),i.toString()),ge=(e="")=>e.toString().replace(/\w\S*/g,(e=>e.charAt(0).toUpperCase()+e.slice(1).toLowerCase()));function fe(e=""){let t=e.toString();return t=function(e=""){let t=e.toString();return t=pe(t,"-"," "),t=pe(t,"_"," "),t=ge(t),pe(t," ","")}(t),t.charAt(0).toLowerCase()+t.slice(1)}function ye(e){const t=document.createElement("div");return t.appendChild(e),t.innerHTML}const be={pip:"PIP",airplay:"AirPlay",html5:"HTML5",vimeo:"Vimeo",youtube:"YouTube"},ve={get(e="",t={}){if(S.empty(e)||S.empty(t))return"";let i=N(t.i18n,e);if(S.empty(i))return Object.keys(be).includes(e)?be[e]:"";const s={"{seektime}":t.seekTime,"{title}":t.title};return Object.entries(s).forEach((([e,t])=>{i=pe(i,e,t)})),i}};class we{constructor(t){e(this,"get",(e=>{if(!we.supported||!this.enabled)return null;const t=window.localStorage.getItem(this.key);if(S.empty(t))return null;const i=JSON.parse(t);return S.string(e)&&e.length?i[e]:i})),e(this,"set",(e=>{if(!we.supported||!this.enabled)return;if(!S.object(e))return;let t=this.get();S.empty(t)&&(t={}),x(t,e);try{window.localStorage.setItem(this.key,JSON.stringify(t))}catch(e){}})),this.enabled=t.config.storage.enabled,this.key=t.config.storage.key}static get supported(){try{if(!("localStorage"in window))return!1;const e="___test";return window.localStorage.setItem(e,e),window.localStorage.removeItem(e),!0}catch(e){return!1}}}function Te(e,t="text"){return new Promise(((i,s)=>{try{const s=new XMLHttpRequest;if(!("withCredentials"in s))return;s.addEventListener("load",(()=>{if("text"===t)try{i(JSON.parse(s.responseText))}catch(e){i(s.responseText)}else i(s.response)})),s.addEventListener("error",(()=>{throw new Error(s.status)})),s.open("GET",e,!0),s.responseType=t,s.send()}catch(e){s(e)}}))}function ke(e,t){if(!S.string(e))return;const i="cache",s=S.string(t);let n=!1;const a=()=>null!==document.getElementById(t),l=(e,t)=>{e.innerHTML=t,s&&a()||document.body.insertAdjacentElement("afterbegin",e)};if(!s||!a()){const a=we.supported,r=document.createElement("div");if(r.setAttribute("hidden",""),s&&r.setAttribute("id",t),a){const e=window.localStorage.getItem(`${i}-${t}`);if(n=null!==e,n){const t=JSON.parse(e);l(r,t.content)}}Te(e).then((e=>{if(!S.empty(e)){if(a)try{window.localStorage.setItem(`${i}-${t}`,JSON.stringify({content:e}))}catch(e){}l(r,e)}})).catch((()=>{}))}}const Ce=e=>Math.trunc(e/60/60%60,10),Ae=e=>Math.trunc(e/60%60,10),Se=e=>Math.trunc(e%60,10);function Ee(e=0,t=!1,i=!1){if(!S.number(e))return Ee(void 0,t,i);const s=e=>`0${e}`.slice(-2);let n=Ce(e);const a=Ae(e),l=Se(e);return n=t||n>0?`${n}:`:"",`${i&&e>0?"-":""}${n}${s(a)}:${s(l)}`}const Pe={getIconUrl(){const e=new URL(this.config.iconUrl,window.location),t=window.location.host?window.location.host:window.top.location.host,i=e.host!==t||M.isIE&&!window.svg4everybody;return{url:this.config.iconUrl,cors:i}},findElements(){try{return this.elements.controls=B.call(this,this.config.selectors.controls.wrapper),this.elements.buttons={play:U.call(this,this.config.selectors.buttons.play),pause:B.call(this,this.config.selectors.buttons.pause),restart:B.call(this,this.config.selectors.buttons.restart),rewind:B.call(this,this.config.selectors.buttons.rewind),fastForward:B.call(this,this.config.selectors.buttons.fastForward),mute:B.call(this,this.config.selectors.buttons.mute),pip:B.call(this,this.config.selectors.buttons.pip),airplay:B.call(this,this.config.selectors.buttons.airplay),settings:B.call(this,this.config.selectors.buttons.settings),captions:B.call(this,this.config.selectors.buttons.captions),fullscreen:B.call(this,this.config.selectors.buttons.fullscreen)},this.elements.progress=B.call(this,this.config.selectors.progress),this.elements.inputs={seek:B.call(this,this.config.selectors.inputs.seek),volume:B.call(this,this.config.selectors.inputs.volume)},this.elements.display={buffer:B.call(this,this.config.selectors.display.buffer),currentTime:B.call(this,this.config.selectors.display.currentTime),duration:B.call(this,this.config.selectors.display.duration)},S.element(this.elements.progress)&&(this.elements.display.seekTooltip=this.elements.progress.querySelector(`.${this.config.classNames.tooltip}`)),!0}catch(e){return this.debug.warn("It looks like there is a problem with your custom controls HTML",e),this.toggleNativeControls(!0),!1}},createIcon(e,t){const i="http://www.w3.org/2000/svg",s=Pe.getIconUrl.call(this),n=`${s.cors?"":s.url}#${this.config.iconPrefix}`,a=document.createElementNS(i,"svg");I(a,x(t,{"aria-hidden":"true",focusable:"false"}));const l=document.createElementNS(i,"use"),r=`${n}-${e}`;return"href"in l&&l.setAttributeNS("http://www.w3.org/1999/xlink","href",r),l.setAttributeNS("http://www.w3.org/1999/xlink","xlink:href",r),a.appendChild(l),a},createLabel(e,t={}){const i=ve.get(e,this.config);return $("span",{...t,class:[t.class,this.config.classNames.hidden].filter(Boolean).join(" ")},i)},createBadge(e){if(S.empty(e))return null;const t=$("span",{class:this.config.classNames.menu.value});return t.appendChild($("span",{class:this.config.classNames.menu.badge},e)),t},createButton(e,t){const i=x({},t);let s=fe(e);const n={element:"button",toggle:!1,label:null,icon:null,labelPressed:null,iconPressed:null};switch(["element","icon","label"].forEach((e=>{Object.keys(i).includes(e)&&(n[e]=i[e],delete i[e])})),"button"!==n.element||Object.keys(i).includes("type")||(i.type="button"),Object.keys(i).includes("class")?i.class.split(" ").some((e=>e===this.config.classNames.control))||x(i,{class:`${i.class} ${this.config.classNames.control}`}):i.class=this.config.classNames.control,e){case"play":n.toggle=!0,n.label="play",n.labelPressed="pause",n.icon="play",n.iconPressed="pause";break;case"mute":n.toggle=!0,n.label="mute",n.labelPressed="unmute",n.icon="volume",n.iconPressed="muted";break;case"captions":n.toggle=!0,n.label="enableCaptions",n.labelPressed="disableCaptions",n.icon="captions-off",n.iconPressed="captions-on";break;case"fullscreen":n.toggle=!0,n.label="enterFullscreen",n.labelPressed="exitFullscreen",n.icon="enter-fullscreen",n.iconPressed="exit-fullscreen";break;case"play-large":i.class+=` ${this.config.classNames.control}--overlaid`,s="play",n.label="play",n.icon="play";break;default:S.empty(n.label)&&(n.label=s),S.empty(n.icon)&&(n.icon=e)}const a=$(n.element);return n.toggle?(a.appendChild(Pe.createIcon.call(this,n.iconPressed,{class:"icon--pressed"})),a.appendChild(Pe.createIcon.call(this,n.icon,{class:"icon--not-pressed"})),a.appendChild(Pe.createLabel.call(this,n.labelPressed,{class:"label--pressed"})),a.appendChild(Pe.createLabel.call(this,n.label,{class:"label--not-pressed"}))):(a.appendChild(Pe.createIcon.call(this,n.icon)),a.appendChild(Pe.createLabel.call(this,n.label))),x(i,D(this.config.selectors.buttons[s],i)),I(a,i),"play"===s?(S.array(this.elements.buttons[s])||(this.elements.buttons[s]=[]),this.elements.buttons[s].push(a)):this.elements.buttons[s]=a,a},createRange(e,t){const i=$("input",x(D(this.config.selectors.inputs[e]),{type:"range",min:0,max:100,step:.01,value:0,autocomplete:"off",role:"slider","aria-label":ve.get(e,this.config),"aria-valuemin":0,"aria-valuemax":100,"aria-valuenow":0},t));return this.elements.inputs[e]=i,Pe.updateRangeFill.call(this,i),g.setup(i),i},createProgress(e,t){const i=$("progress",x(D(this.config.selectors.display[e]),{min:0,max:100,value:0,role:"progressbar","aria-hidden":!0},t));if("volume"!==e){i.appendChild($("span",null,"0"));const t={played:"played",buffer:"buffered"}[e],s=t?ve.get(t,this.config):"";i.innerText=`% ${s.toLowerCase()}`}return this.elements.display[e]=i,i},createTime(e,t){const i=D(this.config.selectors.display[e],t),s=$("div",x(i,{class:`${i.class?i.class:""} ${this.config.classNames.display.time} `.trim(),"aria-label":ve.get(e,this.config),role:"timer"}),"00:00");return this.elements.display[e]=s,s},bindMenuItemShortcuts(e,t){X.call(this,e,"keydown keyup",(i=>{if(![" ","ArrowUp","ArrowDown","ArrowRight"].includes(i.key))return;if(i.preventDefault(),i.stopPropagation(),"keydown"===i.type)return;const s=V(e,'[role="menuitemradio"]');if(!s&&[" ","ArrowRight"].includes(i.key))Pe.showMenuPanel.call(this,t,!0);else{let t;" "!==i.key&&("ArrowDown"===i.key||s&&"ArrowRight"===i.key?(t=e.nextElementSibling,S.element(t)||(t=e.parentNode.firstElementChild)):(t=e.previousElementSibling,S.element(t)||(t=e.parentNode.lastElementChild)),W.call(this,t,!0))}}),!1),X.call(this,e,"keyup",(e=>{"Return"===e.key&&Pe.focusFirstMenuItem.call(this,null,!0)}))},createMenuItem({value:e,list:t,type:i,title:s,badge:n=null,checked:a=!1}){const l=D(this.config.selectors.inputs[i]),r=$("button",x(l,{type:"button",role:"menuitemradio",class:`${this.config.classNames.control} ${l.class?l.class:""}`.trim(),"aria-checked":a,value:e})),o=$("span");o.innerHTML=s,S.element(n)&&o.appendChild(n),r.appendChild(o),Object.defineProperty(r,"checked",{enumerable:!0,get:()=>"true"===r.getAttribute("aria-checked"),set(e){e&&Array.from(r.parentNode.children).filter((e=>V(e,'[role="menuitemradio"]'))).forEach((e=>e.setAttribute("aria-checked","false"))),r.setAttribute("aria-checked",e?"true":"false")}}),this.listeners.bind(r,"click keyup",(t=>{if(!S.keyboardEvent(t)||" "===t.key){switch(t.preventDefault(),t.stopPropagation(),r.checked=!0,i){case"language":this.currentTrack=Number(e);break;case"quality":this.quality=e;break;case"speed":this.speed=parseFloat(e)}Pe.showMenuPanel.call(this,"home",S.keyboardEvent(t))}}),i,!1),Pe.bindMenuItemShortcuts.call(this,r,i),t.appendChild(r)},formatTime(e=0,t=!1){if(!S.number(e))return e;return Ee(e,Ce(this.duration)>0,t)},updateTimeDisplay(e=null,t=0,i=!1){S.element(e)&&S.number(t)&&(e.innerText=Pe.formatTime(t,i))},updateVolume(){this.supported.ui&&(S.element(this.elements.inputs.volume)&&Pe.setRange.call(this,this.elements.inputs.volume,this.muted?0:this.volume),S.element(this.elements.buttons.mute)&&(this.elements.buttons.mute.pressed=this.muted||0===this.volume))},setRange(e,t=0){S.element(e)&&(e.value=t,Pe.updateRangeFill.call(this,e))},updateProgress(e){if(!this.supported.ui||!S.event(e))return;let t=0;const i=(e,t)=>{const i=S.number(t)?t:0,s=S.element(e)?e:this.elements.display.buffer;if(S.element(s)){s.value=i;const e=s.getElementsByTagName("span")[0];S.element(e)&&(e.childNodes[0].nodeValue=i)}};if(e)switch(e.type){case"timeupdate":case"seeking":case"seeked":s=this.currentTime,n=this.duration,t=0===s||0===n||Number.isNaN(s)||Number.isNaN(n)?0:(s/n*100).toFixed(2),"timeupdate"===e.type&&Pe.setRange.call(this,this.elements.inputs.seek,t);break;case"playing":case"progress":i(this.elements.display.buffer,100*this.buffered)}var s,n},updateRangeFill(e){const t=S.event(e)?e.target:e;if(S.element(t)&&"range"===t.getAttribute("type")){if(V(t,this.config.selectors.inputs.seek)){t.setAttribute("aria-valuenow",this.currentTime);const e=Pe.formatTime(this.currentTime),i=Pe.formatTime(this.duration),s=ve.get("seekLabel",this.config);t.setAttribute("aria-valuetext",s.replace("{currentTime}",e).replace("{duration}",i))}else if(V(t,this.config.selectors.inputs.volume)){const e=100*t.value;t.setAttribute("aria-valuenow",e),t.setAttribute("aria-valuetext",`${e.toFixed(1)}%`)}else t.setAttribute("aria-valuenow",t.value);(M.isWebKit||M.isIPadOS)&&t.style.setProperty("--value",t.value/t.max*100+"%")}},updateSeekTooltip(e){var t,i;if(!this.config.tooltips.seek||!S.element(this.elements.inputs.seek)||!S.element(this.elements.display.seekTooltip)||0===this.duration)return;const s=this.elements.display.seekTooltip,n=`${this.config.classNames.tooltip}--visible`,a=e=>R(s,n,e);if(this.touch)return void a(!1);let l=0;const r=this.elements.progress.getBoundingClientRect();if(S.event(e))l=100/r.width*(e.pageX-r.left);else{if(!F(s,n))return;l=parseFloat(s.style.left,10)}l<0?l=0:l>100&&(l=100);const o=this.duration/100*l;s.innerText=Pe.formatTime(o);const c=null===(t=this.config.markers)||void 0===t||null===(i=t.points)||void 0===i?void 0:i.find((({time:e})=>e===Math.round(o)));c&&s.insertAdjacentHTML("afterbegin",`${c.label}
`),s.style.left=`${l}%`,S.event(e)&&["mouseenter","mouseleave"].includes(e.type)&&a("mouseenter"===e.type)},timeUpdate(e){const t=!S.element(this.elements.display.duration)&&this.config.invertTime;Pe.updateTimeDisplay.call(this,this.elements.display.currentTime,t?this.duration-this.currentTime:this.currentTime,t),e&&"timeupdate"===e.type&&this.media.seeking||Pe.updateProgress.call(this,e)},durationUpdate(){if(!this.supported.ui||!this.config.invertTime&&this.currentTime)return;if(this.duration>=2**32)return H(this.elements.display.currentTime,!0),void H(this.elements.progress,!0);S.element(this.elements.inputs.seek)&&this.elements.inputs.seek.setAttribute("aria-valuemax",this.duration);const e=S.element(this.elements.display.duration);!e&&this.config.displayDuration&&this.paused&&Pe.updateTimeDisplay.call(this,this.elements.display.currentTime,this.duration),e&&Pe.updateTimeDisplay.call(this,this.elements.display.duration,this.duration),this.config.markers.enabled&&Pe.setMarkers.call(this),Pe.updateSeekTooltip.call(this)},toggleMenuButton(e,t){H(this.elements.settings.buttons[e],!t)},updateSetting(e,t,i){const s=this.elements.settings.panels[e];let n=null,a=t;if("captions"===e)n=this.currentTrack;else{if(n=S.empty(i)?this[e]:i,S.empty(n)&&(n=this.config[e].default),!S.empty(this.options[e])&&!this.options[e].includes(n))return void this.debug.warn(`Unsupported value of '${n}' for ${e}`);if(!this.config[e].options.includes(n))return void this.debug.warn(`Disabled value of '${n}' for ${e}`)}if(S.element(a)||(a=s&&s.querySelector('[role="menu"]')),!S.element(a))return;this.elements.settings.buttons[e].querySelector(`.${this.config.classNames.menu.value}`).innerHTML=Pe.getLabel.call(this,e,n);const l=a&&a.querySelector(`[value="${n}"]`);S.element(l)&&(l.checked=!0)},getLabel(e,t){switch(e){case"speed":return 1===t?ve.get("normal",this.config):`${t}×`;case"quality":if(S.number(t)){const e=ve.get(`qualityLabel.${t}`,this.config);return e.length?e:`${t}p`}return ge(t);case"captions":return xe.getLabel.call(this);default:return null}},setQualityMenu(e){if(!S.element(this.elements.settings.panels.quality))return;const t="quality",i=this.elements.settings.panels.quality.querySelector('[role="menu"]');S.array(e)&&(this.options.quality=se(e).filter((e=>this.config.quality.options.includes(e))));const s=!S.empty(this.options.quality)&&this.options.quality.length>1;if(Pe.toggleMenuButton.call(this,t,s),j(i),Pe.checkMenu.call(this),!s)return;const n=e=>{const t=ve.get(`qualityBadge.${e}`,this.config);return t.length?Pe.createBadge.call(this,t):null};this.options.quality.sort(((e,t)=>{const i=this.config.quality.options;return i.indexOf(e)>i.indexOf(t)?1:-1})).forEach((e=>{Pe.createMenuItem.call(this,{value:e,list:i,type:t,title:Pe.getLabel.call(this,"quality",e),badge:n(e)})})),Pe.updateSetting.call(this,t,i)},setCaptionsMenu(){if(!S.element(this.elements.settings.panels.captions))return;const e="captions",t=this.elements.settings.panels.captions.querySelector('[role="menu"]'),i=xe.getTracks.call(this),s=Boolean(i.length);if(Pe.toggleMenuButton.call(this,e,s),j(t),Pe.checkMenu.call(this),!s)return;const n=i.map(((e,i)=>({value:i,checked:this.captions.toggled&&this.currentTrack===i,title:xe.getLabel.call(this,e),badge:e.language&&Pe.createBadge.call(this,e.language.toUpperCase()),list:t,type:"language"})));n.unshift({value:-1,checked:!this.captions.toggled,title:ve.get("disabled",this.config),list:t,type:"language"}),n.forEach(Pe.createMenuItem.bind(this)),Pe.updateSetting.call(this,e,t)},setSpeedMenu(){if(!S.element(this.elements.settings.panels.speed))return;const e="speed",t=this.elements.settings.panels.speed.querySelector('[role="menu"]');this.options.speed=this.options.speed.filter((e=>e>=this.minimumSpeed&&e<=this.maximumSpeed));const i=!S.empty(this.options.speed)&&this.options.speed.length>1;Pe.toggleMenuButton.call(this,e,i),j(t),Pe.checkMenu.call(this),i&&(this.options.speed.forEach((i=>{Pe.createMenuItem.call(this,{value:i,list:t,type:e,title:Pe.getLabel.call(this,"speed",i)})})),Pe.updateSetting.call(this,e,t))},checkMenu(){const{buttons:e}=this.elements.settings,t=!S.empty(e)&&Object.values(e).some((e=>!e.hidden));H(this.elements.settings.menu,!t)},focusFirstMenuItem(e,t=!1){if(this.elements.settings.popup.hidden)return;let i=e;S.element(i)||(i=Object.values(this.elements.settings.panels).find((e=>!e.hidden)));const s=i.querySelector('[role^="menuitem"]');W.call(this,s,t)},toggleMenu(e){const{popup:t}=this.elements.settings,i=this.elements.buttons.settings;if(!S.element(t)||!S.element(i))return;const{hidden:s}=t;let n=s;if(S.boolean(e))n=e;else if(S.keyboardEvent(e)&&"Escape"===e.key)n=!1;else if(S.event(e)){const s=S.function(e.composedPath)?e.composedPath()[0]:e.target,a=t.contains(s);if(a||!a&&e.target!==i&&n)return}i.setAttribute("aria-expanded",n),H(t,!n),R(this.elements.container,this.config.classNames.menu.open,n),n&&S.keyboardEvent(e)?Pe.focusFirstMenuItem.call(this,null,!0):n||s||W.call(this,i,S.keyboardEvent(e))},getMenuSize(e){const t=e.cloneNode(!0);t.style.position="absolute",t.style.opacity=0,t.removeAttribute("hidden"),e.parentNode.appendChild(t);const i=t.scrollWidth,s=t.scrollHeight;return O(t),{width:i,height:s}},showMenuPanel(e="",t=!1){const i=this.elements.container.querySelector(`#plyr-settings-${this.id}-${e}`);if(!S.element(i))return;const s=i.parentNode,n=Array.from(s.children).find((e=>!e.hidden));if(K.transitions&&!K.reducedMotion){s.style.width=`${n.scrollWidth}px`,s.style.height=`${n.scrollHeight}px`;const e=Pe.getMenuSize.call(this,i),t=e=>{e.target===s&&["width","height"].includes(e.propertyName)&&(s.style.width="",s.style.height="",J.call(this,s,E,t))};X.call(this,s,E,t),s.style.width=`${e.width}px`,s.style.height=`${e.height}px`}H(n,!0),H(i,!1),Pe.focusFirstMenuItem.call(this,i,t)},setDownloadUrl(){const e=this.elements.buttons.download;S.element(e)&&e.setAttribute("href",this.download)},create(e){const{bindMenuItemShortcuts:t,createButton:i,createProgress:s,createRange:n,createTime:a,setQualityMenu:l,setSpeedMenu:r,showMenuPanel:o}=Pe;this.elements.controls=null,S.array(this.config.controls)&&this.config.controls.includes("play-large")&&this.elements.container.appendChild(i.call(this,"play-large"));const c=$("div",D(this.config.selectors.controls.wrapper));this.elements.controls=c;const u={class:"plyr__controls__item"};return se(S.array(this.config.controls)?this.config.controls:[]).forEach((l=>{if("restart"===l&&c.appendChild(i.call(this,"restart",u)),"rewind"===l&&c.appendChild(i.call(this,"rewind",u)),"play"===l&&c.appendChild(i.call(this,"play",u)),"fast-forward"===l&&c.appendChild(i.call(this,"fast-forward",u)),"progress"===l){const t=$("div",{class:`${u.class} plyr__progress__container`}),i=$("div",D(this.config.selectors.progress));if(i.appendChild(n.call(this,"seek",{id:`plyr-seek-${e.id}`})),i.appendChild(s.call(this,"buffer")),this.config.tooltips.seek){const e=$("span",{class:this.config.classNames.tooltip},"00:00");i.appendChild(e),this.elements.display.seekTooltip=e}this.elements.progress=i,t.appendChild(this.elements.progress),c.appendChild(t)}if("current-time"===l&&c.appendChild(a.call(this,"currentTime",u)),"duration"===l&&c.appendChild(a.call(this,"duration",u)),"mute"===l||"volume"===l){let{volume:t}=this.elements;if(S.element(t)&&c.contains(t)||(t=$("div",x({},u,{class:`${u.class} plyr__volume`.trim()})),this.elements.volume=t,c.appendChild(t)),"mute"===l&&t.appendChild(i.call(this,"mute")),"volume"===l&&!M.isIos&&!M.isIPadOS){const i={max:1,step:.05,value:this.config.volume};t.appendChild(n.call(this,"volume",x(i,{id:`plyr-volume-${e.id}`})))}}if("captions"===l&&c.appendChild(i.call(this,"captions",u)),"settings"===l&&!S.empty(this.config.settings)){const s=$("div",x({},u,{class:`${u.class} plyr__menu`.trim(),hidden:""}));s.appendChild(i.call(this,"settings",{"aria-haspopup":!0,"aria-controls":`plyr-settings-${e.id}`,"aria-expanded":!1}));const n=$("div",{class:"plyr__menu__container",id:`plyr-settings-${e.id}`,hidden:""}),a=$("div"),l=$("div",{id:`plyr-settings-${e.id}-home`}),r=$("div",{role:"menu"});l.appendChild(r),a.appendChild(l),this.elements.settings.panels.home=l,this.config.settings.forEach((i=>{const s=$("button",x(D(this.config.selectors.buttons.settings),{type:"button",class:`${this.config.classNames.control} ${this.config.classNames.control}--forward`,role:"menuitem","aria-haspopup":!0,hidden:""}));t.call(this,s,i),X.call(this,s,"click",(()=>{o.call(this,i,!1)}));const n=$("span",null,ve.get(i,this.config)),l=$("span",{class:this.config.classNames.menu.value});l.innerHTML=e[i],n.appendChild(l),s.appendChild(n),r.appendChild(s);const c=$("div",{id:`plyr-settings-${e.id}-${i}`,hidden:""}),u=$("button",{type:"button",class:`${this.config.classNames.control} ${this.config.classNames.control}--back`});u.appendChild($("span",{"aria-hidden":!0},ve.get(i,this.config))),u.appendChild($("span",{class:this.config.classNames.hidden},ve.get("menuBack",this.config))),X.call(this,c,"keydown",(e=>{"ArrowLeft"===e.key&&(e.preventDefault(),e.stopPropagation(),o.call(this,"home",!0))}),!1),X.call(this,u,"click",(()=>{o.call(this,"home",!1)})),c.appendChild(u),c.appendChild($("div",{role:"menu"})),a.appendChild(c),this.elements.settings.buttons[i]=s,this.elements.settings.panels[i]=c})),n.appendChild(a),s.appendChild(n),c.appendChild(s),this.elements.settings.popup=n,this.elements.settings.menu=s}if("pip"===l&&K.pip&&c.appendChild(i.call(this,"pip",u)),"airplay"===l&&K.airplay&&c.appendChild(i.call(this,"airplay",u)),"download"===l){const e=x({},u,{element:"a",href:this.download,target:"_blank"});this.isHTML5&&(e.download="");const{download:t}=this.config.urls;!S.url(t)&&this.isEmbed&&x(e,{icon:`logo-${this.provider}`,label:this.provider}),c.appendChild(i.call(this,"download",e))}"fullscreen"===l&&c.appendChild(i.call(this,"fullscreen",u))})),this.isHTML5&&l.call(this,de.getQualityOptions.call(this)),r.call(this),c},inject(){if(this.config.loadSprite){const e=Pe.getIconUrl.call(this);e.cors&&ke(e.url,"sprite-plyr")}this.id=Math.floor(1e4*Math.random());let e=null;this.elements.controls=null;const t={id:this.id,seektime:this.config.seekTime,title:this.config.title};let i=!0;S.function(this.config.controls)&&(this.config.controls=this.config.controls.call(this,t)),this.config.controls||(this.config.controls=[]),S.element(this.config.controls)||S.string(this.config.controls)?e=this.config.controls:(e=Pe.create.call(this,{id:this.id,seektime:this.config.seekTime,speed:this.speed,quality:this.quality,captions:xe.getLabel.call(this)}),i=!1);let s;i&&S.string(this.config.controls)&&(e=(e=>{let i=e;return Object.entries(t).forEach((([e,t])=>{i=pe(i,`{${e}}`,t)})),i})(e)),S.string(this.config.selectors.controls.container)&&(s=document.querySelector(this.config.selectors.controls.container)),S.element(s)||(s=this.elements.container);if(s[S.element(e)?"insertAdjacentElement":"insertAdjacentHTML"]("afterbegin",e),S.element(this.elements.controls)||Pe.findElements.call(this),!S.empty(this.elements.buttons)){const e=e=>{const t=this.config.classNames.controlPressed;e.setAttribute("aria-pressed","false"),Object.defineProperty(e,"pressed",{configurable:!0,enumerable:!0,get:()=>F(e,t),set(i=!1){R(e,t,i),e.setAttribute("aria-pressed",i?"true":"false")}})};Object.values(this.elements.buttons).filter(Boolean).forEach((t=>{S.array(t)||S.nodeList(t)?Array.from(t).filter(Boolean).forEach(e):e(t)}))}if(M.isEdge&&P(s),this.config.tooltips.controls){const{classNames:e,selectors:t}=this.config,i=`${t.controls.wrapper} ${t.labels} .${e.hidden}`,s=U.call(this,i);Array.from(s).forEach((e=>{R(e,this.config.classNames.hidden,!1),R(e,this.config.classNames.tooltip,!0)}))}},setMediaMetadata(){try{"mediaSession"in navigator&&(navigator.mediaSession.metadata=new window.MediaMetadata({title:this.config.mediaMetadata.title,artist:this.config.mediaMetadata.artist,album:this.config.mediaMetadata.album,artwork:this.config.mediaMetadata.artwork}))}catch(e){}},setMarkers(){var e,t;if(!this.duration||this.elements.markers)return;const i=null===(e=this.config.markers)||void 0===e||null===(t=e.points)||void 0===t?void 0:t.filter((({time:e})=>e>0&&eR(a,l,e);i.forEach((e=>{const t=$("span",{class:this.config.classNames.marker},""),i=e.time/this.duration*100+"%";a&&(t.addEventListener("mouseenter",(()=>{e.label||(a.style.left=i,a.innerHTML=e.label,r(!0))})),t.addEventListener("mouseleave",(()=>{r(!1)}))),t.addEventListener("click",(()=>{this.currentTime=e.time})),t.style.left=i,n.appendChild(t)})),s.appendChild(n),this.config.tooltips.seek||(a=$("span",{class:this.config.classNames.tooltip},""),s.appendChild(a)),this.elements.markers={points:n,tip:a},this.elements.progress.appendChild(s)}};function Me(e,t=!0){let i=e;if(t){const e=document.createElement("a");e.href=i,i=e.href}try{return new URL(i)}catch(e){return null}}function Ne(e){const t=new URLSearchParams;return S.object(e)&&Object.entries(e).forEach((([e,i])=>{t.set(e,i)})),t}const xe={setup(){if(!this.supported.ui)return;if(!this.isVideo||this.isYouTube||this.isHTML5&&!K.textTracks)return void(S.array(this.config.controls)&&this.config.controls.includes("settings")&&this.config.settings.includes("captions")&&Pe.setCaptionsMenu.call(this));var e,t;if(S.element(this.elements.captions)||(this.elements.captions=$("div",D(this.config.selectors.captions)),this.elements.captions.setAttribute("dir","auto"),e=this.elements.captions,t=this.elements.wrapper,S.element(e)&&S.element(t)&&t.parentNode.insertBefore(e,t.nextSibling)),M.isIE&&window.URL){const e=this.media.querySelectorAll("track");Array.from(e).forEach((e=>{const t=e.getAttribute("src"),i=Me(t);null!==i&&i.hostname!==window.location.href.hostname&&["http:","https:"].includes(i.protocol)&&Te(t,"blob").then((t=>{e.setAttribute("src",window.URL.createObjectURL(t))})).catch((()=>{O(e)}))}))}const i=se((navigator.languages||[navigator.language||navigator.userLanguage||"en"]).map((e=>e.split("-")[0])));let s=(this.storage.get("language")||this.config.captions.language||"auto").toLowerCase();"auto"===s&&([s]=i);let n=this.storage.get("captions");if(S.boolean(n)||({active:n}=this.config.captions),Object.assign(this.captions,{toggled:!1,active:n,language:s,languages:i}),this.isHTML5){const e=this.config.captions.update?"addtrack removetrack":"removetrack";X.call(this,this.media.textTracks,e,xe.update.bind(this))}setTimeout(xe.update.bind(this),0)},update(){const e=xe.getTracks.call(this,!0),{active:t,language:i,meta:s,currentTrackNode:n}=this.captions,a=Boolean(e.find((e=>e.language===i)));this.isHTML5&&this.isVideo&&e.filter((e=>!s.get(e))).forEach((e=>{this.debug.log("Track added",e),s.set(e,{default:"showing"===e.mode}),"showing"===e.mode&&(e.mode="hidden"),X.call(this,e,"cuechange",(()=>xe.updateCues.call(this)))})),(a&&this.language!==i||!e.includes(n))&&(xe.setLanguage.call(this,i),xe.toggle.call(this,t&&a)),this.elements&&R(this.elements.container,this.config.classNames.captions.enabled,!S.empty(e)),S.array(this.config.controls)&&this.config.controls.includes("settings")&&this.config.settings.includes("captions")&&Pe.setCaptionsMenu.call(this)},toggle(e,t=!0){if(!this.supported.ui)return;const{toggled:i}=this.captions,s=this.config.classNames.captions.active,n=S.nullOrUndefined(e)?!i:e;if(n!==i){if(t||(this.captions.active=n,this.storage.set({captions:n})),!this.language&&n&&!t){const e=xe.getTracks.call(this),t=xe.findTrack.call(this,[this.captions.language,...this.captions.languages],!0);return this.captions.language=t.language,void xe.set.call(this,e.indexOf(t))}this.elements.buttons.captions&&(this.elements.buttons.captions.pressed=n),R(this.elements.container,s,n),this.captions.toggled=n,Pe.updateSetting.call(this,"captions"),Z.call(this,this.media,n?"captionsenabled":"captionsdisabled")}setTimeout((()=>{n&&this.captions.toggled&&(this.captions.currentTrackNode.mode="hidden")}))},set(e,t=!0){const i=xe.getTracks.call(this);if(-1!==e)if(S.number(e))if(e in i){if(this.captions.currentTrack!==e){this.captions.currentTrack=e;const s=i[e],{language:n}=s||{};this.captions.currentTrackNode=s,Pe.updateSetting.call(this,"captions"),t||(this.captions.language=n,this.storage.set({language:n})),this.isVimeo&&this.embed.enableTextTrack(n),Z.call(this,this.media,"languagechange")}xe.toggle.call(this,!0,t),this.isHTML5&&this.isVideo&&xe.updateCues.call(this)}else this.debug.warn("Track not found",e);else this.debug.warn("Invalid caption argument",e);else xe.toggle.call(this,!1,t)},setLanguage(e,t=!0){if(!S.string(e))return void this.debug.warn("Invalid language argument",e);const i=e.toLowerCase();this.captions.language=i;const s=xe.getTracks.call(this),n=xe.findTrack.call(this,[i]);xe.set.call(this,s.indexOf(n),t)},getTracks(e=!1){return Array.from((this.media||{}).textTracks||[]).filter((t=>!this.isHTML5||e||this.captions.meta.has(t))).filter((e=>["captions","subtitles"].includes(e.kind)))},findTrack(e,t=!1){const i=xe.getTracks.call(this),s=e=>Number((this.captions.meta.get(e)||{}).default),n=Array.from(i).sort(((e,t)=>s(t)-s(e)));let a;return e.every((e=>(a=n.find((t=>t.language===e)),!a))),a||(t?n[0]:void 0)},getCurrentTrack(){return xe.getTracks.call(this)[this.currentTrack]},getLabel(e){let t=e;return!S.track(t)&&K.textTracks&&this.captions.toggled&&(t=xe.getCurrentTrack.call(this)),S.track(t)?S.empty(t.label)?S.empty(t.language)?ve.get("enabled",this.config):e.language.toUpperCase():t.label:ve.get("disabled",this.config)},updateCues(e){if(!this.supported.ui)return;if(!S.element(this.elements.captions))return void this.debug.warn("No captions element to render to");if(!S.nullOrUndefined(e)&&!Array.isArray(e))return void this.debug.warn("updateCues: Invalid input",e);let t=e;if(!t){const e=xe.getCurrentTrack.call(this);t=Array.from((e||{}).activeCues||[]).map((e=>e.getCueAsHTML())).map(ye)}const i=t.map((e=>e.trim())).join("\n");if(i!==this.elements.captions.innerHTML){j(this.elements.captions);const e=$("span",D(this.config.selectors.caption));e.innerHTML=i,this.elements.captions.appendChild(e),Z.call(this,this.media,"cuechange")}}},Le={enabled:!0,title:"",debug:!1,autoplay:!1,autopause:!0,playsinline:!0,seekTime:10,volume:1,muted:!1,duration:null,displayDuration:!0,invertTime:!0,toggleInvert:!0,ratio:null,clickToPlay:!0,hideControls:!0,resetOnEnd:!1,disableContextMenu:!0,loadSprite:!0,iconPrefix:"plyr",iconUrl:"https://cdn.plyr.io/3.7.8/plyr.svg",blankVideo:"https://cdn.plyr.io/static/blank.mp4",quality:{default:576,options:[4320,2880,2160,1440,1080,720,576,480,360,240],forced:!1,onChange:null},loop:{active:!1},speed:{selected:1,options:[.5,.75,1,1.25,1.5,1.75,2,4]},keyboard:{focused:!0,global:!1},tooltips:{controls:!1,seek:!0},captions:{active:!1,language:"auto",update:!1},fullscreen:{enabled:!0,fallback:!0,iosNative:!1},storage:{enabled:!0,key:"plyr"},controls:["play-large","play","progress","current-time","mute","volume","captions","settings","pip","airplay","fullscreen"],settings:["captions","quality","speed"],i18n:{restart:"Restart",rewind:"Rewind {seektime}s",play:"Play",pause:"Pause",fastForward:"Forward {seektime}s",seek:"Seek",seekLabel:"{currentTime} of {duration}",played:"Played",buffered:"Buffered",currentTime:"Current time",duration:"Duration",volume:"Volume",mute:"Mute",unmute:"Unmute",enableCaptions:"Enable captions",disableCaptions:"Disable captions",download:"Download",enterFullscreen:"Enter fullscreen",exitFullscreen:"Exit fullscreen",frameTitle:"Player for {title}",captions:"Captions",settings:"Settings",pip:"PIP",menuBack:"Go back to previous menu",speed:"Speed",normal:"Normal",quality:"Quality",loop:"Loop",start:"Start",end:"End",all:"All",reset:"Reset",disabled:"Disabled",enabled:"Enabled",advertisement:"Ad",qualityBadge:{2160:"4K",1440:"HD",1080:"HD",720:"HD",576:"SD",480:"SD"}},urls:{download:null,vimeo:{sdk:"https://player.vimeo.com/api/player.js",iframe:"https://player.vimeo.com/video/{0}?{1}",api:"https://vimeo.com/api/oembed.json?url={0}"},youtube:{sdk:"https://www.youtube.com/iframe_api",api:"https://noembed.com/embed?url=https://www.youtube.com/watch?v={0}"},googleIMA:{sdk:"https://imasdk.googleapis.com/js/sdkloader/ima3.js"}},listeners:{seek:null,play:null,pause:null,restart:null,rewind:null,fastForward:null,mute:null,volume:null,captions:null,download:null,fullscreen:null,pip:null,airplay:null,speed:null,quality:null,loop:null,language:null},events:["ended","progress","stalled","playing","waiting","canplay","canplaythrough","loadstart","loadeddata","loadedmetadata","timeupdate","volumechange","play","pause","error","seeking","seeked","emptied","ratechange","cuechange","download","enterfullscreen","exitfullscreen","captionsenabled","captionsdisabled","languagechange","controlshidden","controlsshown","ready","statechange","qualitychange","adsloaded","adscontentpause","adscontentresume","adstarted","adsmidpoint","adscomplete","adsallcomplete","adsimpression","adsclick"],selectors:{editable:"input, textarea, select, [contenteditable]",container:".plyr",controls:{container:null,wrapper:".plyr__controls"},labels:"[data-plyr]",buttons:{play:'[data-plyr="play"]',pause:'[data-plyr="pause"]',restart:'[data-plyr="restart"]',rewind:'[data-plyr="rewind"]',fastForward:'[data-plyr="fast-forward"]',mute:'[data-plyr="mute"]',captions:'[data-plyr="captions"]',download:'[data-plyr="download"]',fullscreen:'[data-plyr="fullscreen"]',pip:'[data-plyr="pip"]',airplay:'[data-plyr="airplay"]',settings:'[data-plyr="settings"]',loop:'[data-plyr="loop"]'},inputs:{seek:'[data-plyr="seek"]',volume:'[data-plyr="volume"]',speed:'[data-plyr="speed"]',language:'[data-plyr="language"]',quality:'[data-plyr="quality"]'},display:{currentTime:".plyr__time--current",duration:".plyr__time--duration",buffer:".plyr__progress__buffer",loop:".plyr__progress__loop",volume:".plyr__volume--display"},progress:".plyr__progress",captions:".plyr__captions",caption:".plyr__caption"},classNames:{type:"plyr--{0}",provider:"plyr--{0}",video:"plyr__video-wrapper",embed:"plyr__video-embed",videoFixedRatio:"plyr__video-wrapper--fixed-ratio",embedContainer:"plyr__video-embed__container",poster:"plyr__poster",posterEnabled:"plyr__poster-enabled",ads:"plyr__ads",control:"plyr__control",controlPressed:"plyr__control--pressed",playing:"plyr--playing",paused:"plyr--paused",stopped:"plyr--stopped",loading:"plyr--loading",hover:"plyr--hover",tooltip:"plyr__tooltip",cues:"plyr__cues",marker:"plyr__progress__marker",hidden:"plyr__sr-only",hideControls:"plyr--hide-controls",isTouch:"plyr--is-touch",uiSupported:"plyr--full-ui",noTransition:"plyr--no-transition",display:{time:"plyr__time"},menu:{value:"plyr__menu__value",badge:"plyr__badge",open:"plyr--menu-open"},captions:{enabled:"plyr--captions-enabled",active:"plyr--captions-active"},fullscreen:{enabled:"plyr--fullscreen-enabled",fallback:"plyr--fullscreen-fallback"},pip:{supported:"plyr--pip-supported",active:"plyr--pip-active"},airplay:{supported:"plyr--airplay-supported",active:"plyr--airplay-active"},previewThumbnails:{thumbContainer:"plyr__preview-thumb",thumbContainerShown:"plyr__preview-thumb--is-shown",imageContainer:"plyr__preview-thumb__image-container",timeContainer:"plyr__preview-thumb__time-container",scrubbingContainer:"plyr__preview-scrubbing",scrubbingContainerShown:"plyr__preview-scrubbing--is-shown"}},attributes:{embed:{provider:"data-plyr-provider",id:"data-plyr-embed-id",hash:"data-plyr-embed-hash"}},ads:{enabled:!1,publisherId:"",tagUrl:""},previewThumbnails:{enabled:!1,src:""},vimeo:{byline:!1,portrait:!1,title:!1,speed:!0,transparent:!1,customControls:!0,referrerPolicy:null,premium:!1},youtube:{rel:0,showinfo:0,iv_load_policy:3,modestbranding:1,customControls:!0,noCookie:!1},mediaMetadata:{title:"",artist:"",album:"",artwork:[]},markers:{enabled:!1,points:[]}},Ie="picture-in-picture",$e="inline",_e={html5:"html5",youtube:"youtube",vimeo:"vimeo"},Oe="audio",je="video";const qe=()=>{};class De{constructor(e=!1){this.enabled=window.console&&e,this.enabled&&this.log("Debugging enabled")}get log(){return this.enabled?Function.prototype.bind.call(console.log,console):qe}get warn(){return this.enabled?Function.prototype.bind.call(console.warn,console):qe}get error(){return this.enabled?Function.prototype.bind.call(console.error,console):qe}}class He{constructor(t){e(this,"onChange",(()=>{if(!this.supported)return;const e=this.player.elements.buttons.fullscreen;S.element(e)&&(e.pressed=this.active);const t=this.target===this.player.media?this.target:this.player.elements.container;Z.call(this.player,t,this.active?"enterfullscreen":"exitfullscreen",!0)})),e(this,"toggleFallback",((e=!1)=>{if(e?this.scrollPosition={x:window.scrollX??0,y:window.scrollY??0}:window.scrollTo(this.scrollPosition.x,this.scrollPosition.y),document.body.style.overflow=e?"hidden":"",R(this.target,this.player.config.classNames.fullscreen.fallback,e),M.isIos){let t=document.head.querySelector('meta[name="viewport"]');const i="viewport-fit=cover";t||(t=document.createElement("meta"),t.setAttribute("name","viewport"));const s=S.string(t.content)&&t.content.includes(i);e?(this.cleanupViewport=!s,s||(t.content+=`,${i}`)):this.cleanupViewport&&(t.content=t.content.split(",").filter((e=>e.trim()!==i)).join(","))}this.onChange()})),e(this,"trapFocus",(e=>{if(M.isIos||M.isIPadOS||!this.active||"Tab"!==e.key)return;const t=document.activeElement,i=U.call(this.player,"a[href], button:not(:disabled), input:not(:disabled), [tabindex]"),[s]=i,n=i[i.length-1];t!==n||e.shiftKey?t===s&&e.shiftKey&&(n.focus(),e.preventDefault()):(s.focus(),e.preventDefault())})),e(this,"update",(()=>{if(this.supported){let e;e=this.forceFallback?"Fallback (forced)":He.nativeSupported?"Native":"Fallback",this.player.debug.log(`${e} fullscreen enabled`)}else this.player.debug.log("Fullscreen not supported and fallback disabled");R(this.player.elements.container,this.player.config.classNames.fullscreen.enabled,this.supported)})),e(this,"enter",(()=>{this.supported&&(M.isIos&&this.player.config.fullscreen.iosNative?this.player.isVimeo?this.player.embed.requestFullscreen():this.target.webkitEnterFullscreen():!He.nativeSupported||this.forceFallback?this.toggleFallback(!0):this.prefix?S.empty(this.prefix)||this.target[`${this.prefix}Request${this.property}`]():this.target.requestFullscreen({navigationUI:"hide"}))})),e(this,"exit",(()=>{if(this.supported)if(M.isIos&&this.player.config.fullscreen.iosNative)this.player.isVimeo?this.player.embed.exitFullscreen():this.target.webkitEnterFullscreen(),ie(this.player.play());else if(!He.nativeSupported||this.forceFallback)this.toggleFallback(!1);else if(this.prefix){if(!S.empty(this.prefix)){const e="moz"===this.prefix?"Cancel":"Exit";document[`${this.prefix}${e}${this.property}`]()}}else(document.cancelFullScreen||document.exitFullscreen).call(document)})),e(this,"toggle",(()=>{this.active?this.exit():this.enter()})),this.player=t,this.prefix=He.prefix,this.property=He.property,this.scrollPosition={x:0,y:0},this.forceFallback="force"===t.config.fullscreen.fallback,this.player.elements.fullscreen=t.config.fullscreen.container&&function(e,t){const{prototype:i}=Element;return(i.closest||function(){let e=this;do{if(V.matches(e,t))return e;e=e.parentElement||e.parentNode}while(null!==e&&1===e.nodeType);return null}).call(e,t)}(this.player.elements.container,t.config.fullscreen.container),X.call(this.player,document,"ms"===this.prefix?"MSFullscreenChange":`${this.prefix}fullscreenchange`,(()=>{this.onChange()})),X.call(this.player,this.player.elements.container,"dblclick",(e=>{S.element(this.player.elements.controls)&&this.player.elements.controls.contains(e.target)||this.player.listeners.proxy(e,this.toggle,"fullscreen")})),X.call(this,this.player.elements.container,"keydown",(e=>this.trapFocus(e))),this.update()}static get nativeSupported(){return!!(document.fullscreenEnabled||document.webkitFullscreenEnabled||document.mozFullScreenEnabled||document.msFullscreenEnabled)}get useNative(){return He.nativeSupported&&!this.forceFallback}static get prefix(){if(S.function(document.exitFullscreen))return"";let e="";return["webkit","moz","ms"].some((t=>!(!S.function(document[`${t}ExitFullscreen`])&&!S.function(document[`${t}CancelFullScreen`]))&&(e=t,!0))),e}static get property(){return"moz"===this.prefix?"FullScreen":"Fullscreen"}get supported(){return[this.player.config.fullscreen.enabled,this.player.isVideo,He.nativeSupported||this.player.config.fullscreen.fallback,!this.player.isYouTube||He.nativeSupported||!M.isIos||this.player.config.playsinline&&!this.player.config.fullscreen.iosNative].every(Boolean)}get active(){if(!this.supported)return!1;if(!He.nativeSupported||this.forceFallback)return F(this.target,this.player.config.classNames.fullscreen.fallback);const e=this.prefix?this.target.getRootNode()[`${this.prefix}${this.property}Element`]:this.target.getRootNode().fullscreenElement;return e&&e.shadowRoot?e===this.target.getRootNode().host:e===this.target}get target(){return M.isIos&&this.player.config.fullscreen.iosNative?this.player.media:this.player.elements.fullscreen??this.player.elements.container}}function Re(e,t=1){return new Promise(((i,s)=>{const n=new Image,a=()=>{delete n.onload,delete n.onerror,(n.naturalWidth>=t?i:s)(n)};Object.assign(n,{onload:a,onerror:a,src:e})}))}const Fe={addStyleHook(){R(this.elements.container,this.config.selectors.container.replace(".",""),!0),R(this.elements.container,this.config.classNames.uiSupported,this.supported.ui)},toggleNativeControls(e=!1){e&&this.isHTML5?this.media.setAttribute("controls",""):this.media.removeAttribute("controls")},build(){if(this.listeners.media(),!this.supported.ui)return this.debug.warn(`Basic support only for ${this.provider} ${this.type}`),void Fe.toggleNativeControls.call(this,!0);S.element(this.elements.controls)||(Pe.inject.call(this),this.listeners.controls()),Fe.toggleNativeControls.call(this),this.isHTML5&&xe.setup.call(this),this.volume=null,this.muted=null,this.loop=null,this.quality=null,this.speed=null,Pe.updateVolume.call(this),Pe.timeUpdate.call(this),Pe.durationUpdate.call(this),Fe.checkPlaying.call(this),R(this.elements.container,this.config.classNames.pip.supported,K.pip&&this.isHTML5&&this.isVideo),R(this.elements.container,this.config.classNames.airplay.supported,K.airplay&&this.isHTML5),R(this.elements.container,this.config.classNames.isTouch,this.touch),this.ready=!0,setTimeout((()=>{Z.call(this,this.media,"ready")}),0),Fe.setTitle.call(this),this.poster&&Fe.setPoster.call(this,this.poster,!1).catch((()=>{})),this.config.duration&&Pe.durationUpdate.call(this),this.config.mediaMetadata&&Pe.setMediaMetadata.call(this)},setTitle(){let e=ve.get("play",this.config);if(S.string(this.config.title)&&!S.empty(this.config.title)&&(e+=`, ${this.config.title}`),Array.from(this.elements.buttons.play||[]).forEach((t=>{t.setAttribute("aria-label",e)})),this.isEmbed){const e=B.call(this,"iframe");if(!S.element(e))return;const t=S.empty(this.config.title)?"video":this.config.title,i=ve.get("frameTitle",this.config);e.setAttribute("title",i.replace("{title}",t))}},togglePoster(e){R(this.elements.container,this.config.classNames.posterEnabled,e)},setPoster(e,t=!0){return t&&this.poster?Promise.reject(new Error("Poster already set")):(this.media.setAttribute("data-poster",e),this.elements.poster.removeAttribute("hidden"),te.call(this).then((()=>Re(e))).catch((t=>{throw e===this.poster&&Fe.togglePoster.call(this,!1),t})).then((()=>{if(e!==this.poster)throw new Error("setPoster cancelled by later call to setPoster")})).then((()=>(Object.assign(this.elements.poster.style,{backgroundImage:`url('${e}')`,backgroundSize:""}),Fe.togglePoster.call(this,!0),e))))},checkPlaying(e){R(this.elements.container,this.config.classNames.playing,this.playing),R(this.elements.container,this.config.classNames.paused,this.paused),R(this.elements.container,this.config.classNames.stopped,this.stopped),Array.from(this.elements.buttons.play||[]).forEach((e=>{Object.assign(e,{pressed:this.playing}),e.setAttribute("aria-label",ve.get(this.playing?"pause":"play",this.config))})),S.event(e)&&"timeupdate"===e.type||Fe.toggleControls.call(this)},checkLoading(e){this.loading=["stalled","waiting"].includes(e.type),clearTimeout(this.timers.loading),this.timers.loading=setTimeout((()=>{R(this.elements.container,this.config.classNames.loading,this.loading),Fe.toggleControls.call(this)}),this.loading?250:0)},toggleControls(e){const{controls:t}=this.elements;if(t&&this.config.hideControls){const i=this.touch&&this.lastSeekTime+2e3>Date.now();this.toggleControls(Boolean(e||this.loading||this.paused||t.pressed||t.hover||i))}},migrateStyles(){Object.values({...this.media.style}).filter((e=>!S.empty(e)&&S.string(e)&&e.startsWith("--plyr"))).forEach((e=>{this.elements.container.style.setProperty(e,this.media.style.getPropertyValue(e)),this.media.style.removeProperty(e)})),S.empty(this.media.style)&&this.media.removeAttribute("style")}};class Ve{constructor(t){e(this,"firstTouch",(()=>{const{player:e}=this,{elements:t}=e;e.touch=!0,R(t.container,e.config.classNames.isTouch,!0)})),e(this,"global",((e=!0)=>{const{player:t}=this;t.config.keyboard.global&&Q.call(t,window,"keydown keyup",this.handleKey,e,!1),Q.call(t,document.body,"click",this.toggleMenu,e),G.call(t,document.body,"touchstart",this.firstTouch)})),e(this,"container",(()=>{const{player:e}=this,{config:t,elements:i,timers:s}=e;!t.keyboard.global&&t.keyboard.focused&&X.call(e,i.container,"keydown keyup",this.handleKey,!1),X.call(e,i.container,"mousemove mouseleave touchstart touchmove enterfullscreen exitfullscreen",(t=>{const{controls:n}=i;n&&"enterfullscreen"===t.type&&(n.pressed=!1,n.hover=!1);let a=0;["touchstart","touchmove","mousemove"].includes(t.type)&&(Fe.toggleControls.call(e,!0),a=e.touch?3e3:2e3),clearTimeout(s.controls),s.controls=setTimeout((()=>Fe.toggleControls.call(e,!1)),a)}));const n=()=>{if(!e.isVimeo||e.config.vimeo.premium)return;const t=i.wrapper,{active:s}=e.fullscreen,[n,a]=ce.call(e),l=ae(`aspect-ratio: ${n} / ${a}`);if(!s)return void(l?(t.style.width=null,t.style.height=null):(t.style.maxWidth=null,t.style.margin=null));const[r,o]=[Math.max(document.documentElement.clientWidth||0,window.innerWidth||0),Math.max(document.documentElement.clientHeight||0,window.innerHeight||0)],c=r/o>n/a;l?(t.style.width=c?"auto":"100%",t.style.height=c?"100%":"auto"):(t.style.maxWidth=c?o/a*n+"px":null,t.style.margin=c?"0 auto":null)},a=()=>{clearTimeout(s.resized),s.resized=setTimeout(n,50)};X.call(e,i.container,"enterfullscreen exitfullscreen",(t=>{const{target:s}=e.fullscreen;if(s!==i.container)return;if(!e.isEmbed&&S.empty(e.config.ratio))return;n();("enterfullscreen"===t.type?X:J).call(e,window,"resize",a)}))})),e(this,"media",(()=>{const{player:e}=this,{elements:t}=e;if(X.call(e,e.media,"timeupdate seeking seeked",(t=>Pe.timeUpdate.call(e,t))),X.call(e,e.media,"durationchange loadeddata loadedmetadata",(t=>Pe.durationUpdate.call(e,t))),X.call(e,e.media,"ended",(()=>{e.isHTML5&&e.isVideo&&e.config.resetOnEnd&&(e.restart(),e.pause())})),X.call(e,e.media,"progress playing seeking seeked",(t=>Pe.updateProgress.call(e,t))),X.call(e,e.media,"volumechange",(t=>Pe.updateVolume.call(e,t))),X.call(e,e.media,"playing play pause ended emptied timeupdate",(t=>Fe.checkPlaying.call(e,t))),X.call(e,e.media,"waiting canplay seeked playing",(t=>Fe.checkLoading.call(e,t))),e.supported.ui&&e.config.clickToPlay&&!e.isAudio){const i=B.call(e,`.${e.config.classNames.video}`);if(!S.element(i))return;X.call(e,t.container,"click",(s=>{([t.container,i].includes(s.target)||i.contains(s.target))&&(e.touch&&e.config.hideControls||(e.ended?(this.proxy(s,e.restart,"restart"),this.proxy(s,(()=>{ie(e.play())}),"play")):this.proxy(s,(()=>{ie(e.togglePlay())}),"play")))}))}e.supported.ui&&e.config.disableContextMenu&&X.call(e,t.wrapper,"contextmenu",(e=>{e.preventDefault()}),!1),X.call(e,e.media,"volumechange",(()=>{e.storage.set({volume:e.volume,muted:e.muted})})),X.call(e,e.media,"ratechange",(()=>{Pe.updateSetting.call(e,"speed"),e.storage.set({speed:e.speed})})),X.call(e,e.media,"qualitychange",(t=>{Pe.updateSetting.call(e,"quality",null,t.detail.quality)})),X.call(e,e.media,"ready qualitychange",(()=>{Pe.setDownloadUrl.call(e)}));const i=e.config.events.concat(["keyup","keydown"]).join(" ");X.call(e,e.media,i,(i=>{let{detail:s={}}=i;"error"===i.type&&(s=e.media.error),Z.call(e,t.container,i.type,!0,s)}))})),e(this,"proxy",((e,t,i)=>{const{player:s}=this,n=s.config.listeners[i];let a=!0;S.function(n)&&(a=n.call(s,e)),!1!==a&&S.function(t)&&t.call(s,e)})),e(this,"bind",((e,t,i,s,n=!0)=>{const{player:a}=this,l=a.config.listeners[s],r=S.function(l);X.call(a,e,t,(e=>this.proxy(e,i,s)),n&&!r)})),e(this,"controls",(()=>{const{player:e}=this,{elements:t}=e,i=M.isIE?"change":"input";if(t.buttons.play&&Array.from(t.buttons.play).forEach((t=>{this.bind(t,"click",(()=>{ie(e.togglePlay())}),"play")})),this.bind(t.buttons.restart,"click",e.restart,"restart"),this.bind(t.buttons.rewind,"click",(()=>{e.lastSeekTime=Date.now(),e.rewind()}),"rewind"),this.bind(t.buttons.fastForward,"click",(()=>{e.lastSeekTime=Date.now(),e.forward()}),"fastForward"),this.bind(t.buttons.mute,"click",(()=>{e.muted=!e.muted}),"mute"),this.bind(t.buttons.captions,"click",(()=>e.toggleCaptions())),this.bind(t.buttons.download,"click",(()=>{Z.call(e,e.media,"download")}),"download"),this.bind(t.buttons.fullscreen,"click",(()=>{e.fullscreen.toggle()}),"fullscreen"),this.bind(t.buttons.pip,"click",(()=>{e.pip="toggle"}),"pip"),this.bind(t.buttons.airplay,"click",e.airplay,"airplay"),this.bind(t.buttons.settings,"click",(t=>{t.stopPropagation(),t.preventDefault(),Pe.toggleMenu.call(e,t)}),null,!1),this.bind(t.buttons.settings,"keyup",(t=>{[" ","Enter"].includes(t.key)&&("Enter"!==t.key?(t.preventDefault(),t.stopPropagation(),Pe.toggleMenu.call(e,t)):Pe.focusFirstMenuItem.call(e,null,!0))}),null,!1),this.bind(t.settings.menu,"keydown",(t=>{"Escape"===t.key&&Pe.toggleMenu.call(e,t)})),this.bind(t.inputs.seek,"mousedown mousemove",(e=>{const i=t.progress.getBoundingClientRect(),s=100/i.width*(e.pageX-i.left);e.currentTarget.setAttribute("seek-value",s)})),this.bind(t.inputs.seek,"mousedown mouseup keydown keyup touchstart touchend",(t=>{const i=t.currentTarget,s="play-on-seeked";if(S.keyboardEvent(t)&&!["ArrowLeft","ArrowRight"].includes(t.key))return;e.lastSeekTime=Date.now();const n=i.hasAttribute(s),a=["mouseup","touchend","keyup"].includes(t.type);n&&a?(i.removeAttribute(s),ie(e.play())):!a&&e.playing&&(i.setAttribute(s,""),e.pause())})),M.isIos){const t=U.call(e,'input[type="range"]');Array.from(t).forEach((e=>this.bind(e,i,(e=>P(e.target)))))}this.bind(t.inputs.seek,i,(t=>{const i=t.currentTarget;let s=i.getAttribute("seek-value");S.empty(s)&&(s=i.value),i.removeAttribute("seek-value"),e.currentTime=s/i.max*e.duration}),"seek"),this.bind(t.progress,"mouseenter mouseleave mousemove",(t=>Pe.updateSeekTooltip.call(e,t))),this.bind(t.progress,"mousemove touchmove",(t=>{const{previewThumbnails:i}=e;i&&i.loaded&&i.startMove(t)})),this.bind(t.progress,"mouseleave touchend click",(()=>{const{previewThumbnails:t}=e;t&&t.loaded&&t.endMove(!1,!0)})),this.bind(t.progress,"mousedown touchstart",(t=>{const{previewThumbnails:i}=e;i&&i.loaded&&i.startScrubbing(t)})),this.bind(t.progress,"mouseup touchend",(t=>{const{previewThumbnails:i}=e;i&&i.loaded&&i.endScrubbing(t)})),M.isWebKit&&Array.from(U.call(e,'input[type="range"]')).forEach((t=>{this.bind(t,"input",(t=>Pe.updateRangeFill.call(e,t.target)))})),e.config.toggleInvert&&!S.element(t.display.duration)&&this.bind(t.display.currentTime,"click",(()=>{0!==e.currentTime&&(e.config.invertTime=!e.config.invertTime,Pe.timeUpdate.call(e))})),this.bind(t.inputs.volume,i,(t=>{e.volume=t.target.value}),"volume"),this.bind(t.controls,"mouseenter mouseleave",(i=>{t.controls.hover=!e.touch&&"mouseenter"===i.type})),t.fullscreen&&Array.from(t.fullscreen.children).filter((e=>!e.contains(t.container))).forEach((i=>{this.bind(i,"mouseenter mouseleave",(i=>{t.controls&&(t.controls.hover=!e.touch&&"mouseenter"===i.type)}))})),this.bind(t.controls,"mousedown mouseup touchstart touchend touchcancel",(e=>{t.controls.pressed=["mousedown","touchstart"].includes(e.type)})),this.bind(t.controls,"focusin",(()=>{const{config:i,timers:s}=e;R(t.controls,i.classNames.noTransition,!0),Fe.toggleControls.call(e,!0),setTimeout((()=>{R(t.controls,i.classNames.noTransition,!1)}),0);const n=this.touch?3e3:4e3;clearTimeout(s.controls),s.controls=setTimeout((()=>Fe.toggleControls.call(e,!1)),n)})),this.bind(t.inputs.volume,"wheel",(t=>{const i=t.webkitDirectionInvertedFromDevice,[s,n]=[t.deltaX,-t.deltaY].map((e=>i?-e:e)),a=Math.sign(Math.abs(s)>Math.abs(n)?s:n);e.increaseVolume(a/50);const{volume:l}=e.media;(1===a&&l<1||-1===a&&l>0)&&t.preventDefault()}),"volume",!1)})),this.player=t,this.lastKey=null,this.focusTimer=null,this.lastKeyDown=null,this.handleKey=this.handleKey.bind(this),this.toggleMenu=this.toggleMenu.bind(this),this.firstTouch=this.firstTouch.bind(this)}handleKey(e){const{player:t}=this,{elements:i}=t,{key:s,type:n,altKey:a,ctrlKey:l,metaKey:r,shiftKey:o}=e,c="keydown"===n,u=c&&s===this.lastKey;if(a||l||r||o)return;if(!s)return;if(c){const n=document.activeElement;if(S.element(n)){const{editable:s}=t.config.selectors,{seek:a}=i.inputs;if(n!==a&&V(n,s))return;if(" "===e.key&&V(n,'button, [role^="menuitem"]'))return}switch([" ","ArrowLeft","ArrowUp","ArrowRight","ArrowDown","0","1","2","3","4","5","6","7","8","9","c","f","k","l","m"].includes(s)&&(e.preventDefault(),e.stopPropagation()),s){case"0":case"1":case"2":case"3":case"4":case"5":case"6":case"7":case"8":case"9":u||(h=parseInt(s,10),t.currentTime=t.duration/10*h);break;case" ":case"k":u||ie(t.togglePlay());break;case"ArrowUp":t.increaseVolume(.1);break;case"ArrowDown":t.decreaseVolume(.1);break;case"m":u||(t.muted=!t.muted);break;case"ArrowRight":t.forward();break;case"ArrowLeft":t.rewind();break;case"f":t.fullscreen.toggle();break;case"c":u||t.toggleCaptions();break;case"l":t.loop=!t.loop}"Escape"===s&&!t.fullscreen.usingNative&&t.fullscreen.active&&t.fullscreen.toggle(),this.lastKey=s}else this.lastKey=null;var h}toggleMenu(e){Pe.toggleMenu.call(this.player,e)}}"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self&&self;var Ue=function(e,t){return e(t={exports:{}},t.exports),t.exports}((function(e,t){e.exports=function(){var e=function(){},t={},i={},s={};function n(e,t){e=e.push?e:[e];var n,a,l,r=[],o=e.length,c=o;for(n=function(e,i){i.length&&r.push(e),--c||t(r)};o--;)a=e[o],(l=i[a])?n(a,l):(s[a]=s[a]||[]).push(n)}function a(e,t){if(e){var n=s[e];if(i[e]=t,n)for(;n.length;)n[0](e,t),n.splice(0,1)}}function l(t,i){t.call&&(t={success:t}),i.length?(t.error||e)(i):(t.success||e)(t)}function r(t,i,s,n){var a,l,o=document,c=s.async,u=(s.numRetries||0)+1,h=s.before||e,d=t.replace(/[\?|#].*$/,""),m=t.replace(/^(css|img)!/,"");n=n||0,/(^css!|\.css$)/.test(d)?((l=o.createElement("link")).rel="stylesheet",l.href=m,(a="hideFocus"in l)&&l.relList&&(a=0,l.rel="preload",l.as="style")):/(^img!|\.(png|gif|jpg|svg|webp)$)/.test(d)?(l=o.createElement("img")).src=m:((l=o.createElement("script")).src=t,l.async=void 0===c||c),l.onload=l.onerror=l.onbeforeload=function(e){var o=e.type[0];if(a)try{l.sheet.cssText.length||(o="e")}catch(e){18!=e.code&&(o="e")}if("e"==o){if((n+=1){Ue(e,{success:t,error:i})}))}function We(e){e&&!this.embed.hasPlayed&&(this.embed.hasPlayed=!0),this.media.paused===e&&(this.media.paused=!e,Z.call(this,this.media,e?"play":"pause"))}const ze={setup(){const e=this;R(e.elements.wrapper,e.config.classNames.embed,!0),e.options.speed=e.config.speed.options,ue.call(e),S.object(window.Vimeo)?ze.ready.call(e):Be(e.config.urls.vimeo.sdk).then((()=>{ze.ready.call(e)})).catch((t=>{e.debug.warn("Vimeo SDK (player.js) failed to load",t)}))},ready(){const e=this,t=e.config.vimeo,{premium:i,referrerPolicy:s,...n}=t;let a=e.media.getAttribute("src"),l="";S.empty(a)?(a=e.media.getAttribute(e.config.attributes.embed.id),l=e.media.getAttribute(e.config.attributes.embed.hash)):l=function(e){const t=e.match(/^.*(vimeo.com\/|video\/)(\d+)(\?.*&*h=|\/)+([\d,a-f]+)/);return t&&5===t.length?t[4]:null}(a);const r=l?{h:l}:{};i&&Object.assign(n,{controls:!1,sidedock:!1});const o=Ne({loop:e.config.loop.active,autoplay:e.autoplay,muted:e.muted,gesture:"media",playsinline:e.config.playsinline,...r,...n}),c=(u=a,S.empty(u)?null:S.number(Number(u))?u:u.match(/^.*(vimeo.com\/|video\/)(\d+).*/)?RegExp.$2:u);var u;const h=$("iframe"),d=me(e.config.urls.vimeo.iframe,c,o);if(h.setAttribute("src",d),h.setAttribute("allowfullscreen",""),h.setAttribute("allow",["autoplay","fullscreen","picture-in-picture","encrypted-media","accelerometer","gyroscope"].join("; ")),S.empty(s)||h.setAttribute("referrerPolicy",s),i||!t.customControls)h.setAttribute("data-poster",e.poster),e.media=q(h,e.media);else{const t=$("div",{class:e.config.classNames.embedContainer,"data-poster":e.poster});t.appendChild(h),e.media=q(t,e.media)}t.customControls||Te(me(e.config.urls.vimeo.api,d)).then((t=>{!S.empty(t)&&t.thumbnail_url&&Fe.setPoster.call(e,t.thumbnail_url).catch((()=>{}))})),e.embed=new window.Vimeo.Player(h,{autopause:e.config.autopause,muted:e.muted}),e.media.paused=!0,e.media.currentTime=0,e.supported.ui&&e.embed.disableTextTrack(),e.media.play=()=>(We.call(e,!0),e.embed.play()),e.media.pause=()=>(We.call(e,!1),e.embed.pause()),e.media.stop=()=>{e.pause(),e.currentTime=0};let{currentTime:m}=e.media;Object.defineProperty(e.media,"currentTime",{get:()=>m,set(t){const{embed:i,media:s,paused:n,volume:a}=e,l=n&&!i.hasPlayed;s.seeking=!0,Z.call(e,s,"seeking"),Promise.resolve(l&&i.setVolume(0)).then((()=>i.setCurrentTime(t))).then((()=>l&&i.pause())).then((()=>l&&i.setVolume(a))).catch((()=>{}))}});let p=e.config.speed.selected;Object.defineProperty(e.media,"playbackRate",{get:()=>p,set(t){e.embed.setPlaybackRate(t).then((()=>{p=t,Z.call(e,e.media,"ratechange")})).catch((()=>{e.options.speed=[1]}))}});let{volume:g}=e.config;Object.defineProperty(e.media,"volume",{get:()=>g,set(t){e.embed.setVolume(t).then((()=>{g=t,Z.call(e,e.media,"volumechange")}))}});let{muted:f}=e.config;Object.defineProperty(e.media,"muted",{get:()=>f,set(t){const i=!!S.boolean(t)&&t;e.embed.setMuted(!!i||e.config.muted).then((()=>{f=i,Z.call(e,e.media,"volumechange")}))}});let y,{loop:b}=e.config;Object.defineProperty(e.media,"loop",{get:()=>b,set(t){const i=S.boolean(t)?t:e.config.loop.active;e.embed.setLoop(i).then((()=>{b=i}))}}),e.embed.getVideoUrl().then((t=>{y=t,Pe.setDownloadUrl.call(e)})).catch((e=>{this.debug.warn(e)})),Object.defineProperty(e.media,"currentSrc",{get:()=>y}),Object.defineProperty(e.media,"ended",{get:()=>e.currentTime===e.duration}),Promise.all([e.embed.getVideoWidth(),e.embed.getVideoHeight()]).then((t=>{const[i,s]=t;e.embed.ratio=he(i,s),ue.call(this)})),e.embed.setAutopause(e.config.autopause).then((t=>{e.config.autopause=t})),e.embed.getVideoTitle().then((t=>{e.config.title=t,Fe.setTitle.call(this)})),e.embed.getCurrentTime().then((t=>{m=t,Z.call(e,e.media,"timeupdate")})),e.embed.getDuration().then((t=>{e.media.duration=t,Z.call(e,e.media,"durationchange")})),e.embed.getTextTracks().then((t=>{e.media.textTracks=t,xe.setup.call(e)})),e.embed.on("cuechange",(({cues:t=[]})=>{const i=t.map((e=>function(e){const t=document.createDocumentFragment(),i=document.createElement("div");return t.appendChild(i),i.innerHTML=e,t.firstChild.innerText}(e.text)));xe.updateCues.call(e,i)})),e.embed.on("loaded",(()=>{if(e.embed.getPaused().then((t=>{We.call(e,!t),t||Z.call(e,e.media,"playing")})),S.element(e.embed.element)&&e.supported.ui){e.embed.element.setAttribute("tabindex",-1)}})),e.embed.on("bufferstart",(()=>{Z.call(e,e.media,"waiting")})),e.embed.on("bufferend",(()=>{Z.call(e,e.media,"playing")})),e.embed.on("play",(()=>{We.call(e,!0),Z.call(e,e.media,"playing")})),e.embed.on("pause",(()=>{We.call(e,!1)})),e.embed.on("timeupdate",(t=>{e.media.seeking=!1,m=t.seconds,Z.call(e,e.media,"timeupdate")})),e.embed.on("progress",(t=>{e.media.buffered=t.percent,Z.call(e,e.media,"progress"),1===parseInt(t.percent,10)&&Z.call(e,e.media,"canplaythrough"),e.embed.getDuration().then((t=>{t!==e.media.duration&&(e.media.duration=t,Z.call(e,e.media,"durationchange"))}))})),e.embed.on("seeked",(()=>{e.media.seeking=!1,Z.call(e,e.media,"seeked")})),e.embed.on("ended",(()=>{e.media.paused=!0,Z.call(e,e.media,"ended")})),e.embed.on("error",(t=>{e.media.error=t,Z.call(e,e.media,"error")})),t.customControls&&setTimeout((()=>Fe.build.call(e)),0)}};function Ke(e){e&&!this.embed.hasPlayed&&(this.embed.hasPlayed=!0),this.media.paused===e&&(this.media.paused=!e,Z.call(this,this.media,e?"play":"pause"))}function Ye(e){return e.noCookie?"https://www.youtube-nocookie.com":"http:"===window.location.protocol?"http://www.youtube.com":void 0}const Qe={setup(){if(R(this.elements.wrapper,this.config.classNames.embed,!0),S.object(window.YT)&&S.function(window.YT.Player))Qe.ready.call(this);else{const e=window.onYouTubeIframeAPIReady;window.onYouTubeIframeAPIReady=()=>{S.function(e)&&e(),Qe.ready.call(this)},Be(this.config.urls.youtube.sdk).catch((e=>{this.debug.warn("YouTube API failed to load",e)}))}},getTitle(e){Te(me(this.config.urls.youtube.api,e)).then((e=>{if(S.object(e)){const{title:t,height:i,width:s}=e;this.config.title=t,Fe.setTitle.call(this),this.embed.ratio=he(s,i)}ue.call(this)})).catch((()=>{ue.call(this)}))},ready(){const e=this,t=e.config.youtube,i=e.media&&e.media.getAttribute("id");if(!S.empty(i)&&i.startsWith("youtube-"))return;let s=e.media.getAttribute("src");S.empty(s)&&(s=e.media.getAttribute(this.config.attributes.embed.id));const n=(a=s,S.empty(a)?null:a.match(/^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|&v=)([^#&?]*).*/)?RegExp.$2:a);var a;const l=$("div",{id:`${e.provider}-${Math.floor(1e4*Math.random())}`,"data-poster":t.customControls?e.poster:void 0});if(e.media=q(l,e.media),t.customControls){const t=e=>`https://i.ytimg.com/vi/${n}/${e}default.jpg`;Re(t("maxres"),121).catch((()=>Re(t("sd"),121))).catch((()=>Re(t("hq")))).then((t=>Fe.setPoster.call(e,t.src))).then((t=>{t.includes("maxres")||(e.elements.poster.style.backgroundSize="cover")})).catch((()=>{}))}e.embed=new window.YT.Player(e.media,{videoId:n,host:Ye(t),playerVars:x({},{autoplay:e.config.autoplay?1:0,hl:e.config.hl,controls:e.supported.ui&&t.customControls?0:1,disablekb:1,playsinline:e.config.playsinline&&!e.config.fullscreen.iosNative?1:0,cc_load_policy:e.captions.active?1:0,cc_lang_pref:e.config.captions.language,widget_referrer:window?window.location.href:null},t),events:{onError(t){if(!e.media.error){const i=t.data,s={2:"The request contains an invalid parameter value. For example, this error occurs if you specify a video ID that does not have 11 characters, or if the video ID contains invalid characters, such as exclamation points or asterisks.",5:"The requested content cannot be played in an HTML5 player or another error related to the HTML5 player has occurred.",100:"The video requested was not found. This error occurs when a video has been removed (for any reason) or has been marked as private.",101:"The owner of the requested video does not allow it to be played in embedded players.",150:"The owner of the requested video does not allow it to be played in embedded players."}[i]||"An unknown error occurred";e.media.error={code:i,message:s},Z.call(e,e.media,"error")}},onPlaybackRateChange(t){const i=t.target;e.media.playbackRate=i.getPlaybackRate(),Z.call(e,e.media,"ratechange")},onReady(i){if(S.function(e.media.play))return;const s=i.target;Qe.getTitle.call(e,n),e.media.play=()=>{Ke.call(e,!0),s.playVideo()},e.media.pause=()=>{Ke.call(e,!1),s.pauseVideo()},e.media.stop=()=>{s.stopVideo()},e.media.duration=s.getDuration(),e.media.paused=!0,e.media.currentTime=0,Object.defineProperty(e.media,"currentTime",{get:()=>Number(s.getCurrentTime()),set(t){e.paused&&!e.embed.hasPlayed&&e.embed.mute(),e.media.seeking=!0,Z.call(e,e.media,"seeking"),s.seekTo(t)}}),Object.defineProperty(e.media,"playbackRate",{get:()=>s.getPlaybackRate(),set(e){s.setPlaybackRate(e)}});let{volume:a}=e.config;Object.defineProperty(e.media,"volume",{get:()=>a,set(t){a=t,s.setVolume(100*a),Z.call(e,e.media,"volumechange")}});let{muted:l}=e.config;Object.defineProperty(e.media,"muted",{get:()=>l,set(t){const i=S.boolean(t)?t:l;l=i,s[i?"mute":"unMute"](),s.setVolume(100*a),Z.call(e,e.media,"volumechange")}}),Object.defineProperty(e.media,"currentSrc",{get:()=>s.getVideoUrl()}),Object.defineProperty(e.media,"ended",{get:()=>e.currentTime===e.duration});const r=s.getAvailablePlaybackRates();e.options.speed=r.filter((t=>e.config.speed.options.includes(t))),e.supported.ui&&t.customControls&&e.media.setAttribute("tabindex",-1),Z.call(e,e.media,"timeupdate"),Z.call(e,e.media,"durationchange"),clearInterval(e.timers.buffering),e.timers.buffering=setInterval((()=>{e.media.buffered=s.getVideoLoadedFraction(),(null===e.media.lastBuffered||e.media.lastBufferedFe.build.call(e)),50)},onStateChange(i){const s=i.target;clearInterval(e.timers.playing);switch(e.media.seeking&&[1,2].includes(i.data)&&(e.media.seeking=!1,Z.call(e,e.media,"seeked")),i.data){case-1:Z.call(e,e.media,"timeupdate"),e.media.buffered=s.getVideoLoadedFraction(),Z.call(e,e.media,"progress");break;case 0:Ke.call(e,!1),e.media.loop?(s.stopVideo(),s.playVideo()):Z.call(e,e.media,"ended");break;case 1:t.customControls&&!e.config.autoplay&&e.media.paused&&!e.embed.hasPlayed?e.media.pause():(Ke.call(e,!0),Z.call(e,e.media,"playing"),e.timers.playing=setInterval((()=>{Z.call(e,e.media,"timeupdate")}),50),e.media.duration!==s.getDuration()&&(e.media.duration=s.getDuration(),Z.call(e,e.media,"durationchange")));break;case 2:e.muted||e.embed.unMute(),Ke.call(e,!1);break;case 3:Z.call(e,e.media,"waiting")}Z.call(e,e.elements.container,"statechange",!1,{code:i.data})}}})}},Xe={setup(){this.media?(R(this.elements.container,this.config.classNames.type.replace("{0}",this.type),!0),R(this.elements.container,this.config.classNames.provider.replace("{0}",this.provider),!0),this.isEmbed&&R(this.elements.container,this.config.classNames.type.replace("{0}","video"),!0),this.isVideo&&(this.elements.wrapper=$("div",{class:this.config.classNames.video}),L(this.media,this.elements.wrapper),this.elements.poster=$("div",{class:this.config.classNames.poster}),this.elements.wrapper.appendChild(this.elements.poster)),this.isHTML5?de.setup.call(this):this.isYouTube?Qe.setup.call(this):this.isVimeo&&ze.setup.call(this)):this.debug.warn("No media element found!")}};class Je{constructor(t){e(this,"load",(()=>{this.enabled&&(S.object(window.google)&&S.object(window.google.ima)?this.ready():Be(this.player.config.urls.googleIMA.sdk).then((()=>{this.ready()})).catch((()=>{this.trigger("error",new Error("Google IMA SDK failed to load"))})))})),e(this,"ready",(()=>{var e;this.enabled||((e=this).manager&&e.manager.destroy(),e.elements.displayContainer&&e.elements.displayContainer.destroy(),e.elements.container.remove()),this.startSafetyTimer(12e3,"ready()"),this.managerPromise.then((()=>{this.clearSafetyTimer("onAdsManagerLoaded()")})),this.listeners(),this.setupIMA()})),e(this,"setupIMA",(()=>{this.elements.container=$("div",{class:this.player.config.classNames.ads}),this.player.elements.container.appendChild(this.elements.container),google.ima.settings.setVpaidMode(google.ima.ImaSdkSettings.VpaidMode.ENABLED),google.ima.settings.setLocale(this.player.config.ads.language),google.ima.settings.setDisableCustomPlaybackForIOS10Plus(this.player.config.playsinline),this.elements.displayContainer=new google.ima.AdDisplayContainer(this.elements.container,this.player.media),this.loader=new google.ima.AdsLoader(this.elements.displayContainer),this.loader.addEventListener(google.ima.AdsManagerLoadedEvent.Type.ADS_MANAGER_LOADED,(e=>this.onAdsManagerLoaded(e)),!1),this.loader.addEventListener(google.ima.AdErrorEvent.Type.AD_ERROR,(e=>this.onAdError(e)),!1),this.requestAds()})),e(this,"requestAds",(()=>{const{container:e}=this.player.elements;try{const t=new google.ima.AdsRequest;t.adTagUrl=this.tagUrl,t.linearAdSlotWidth=e.offsetWidth,t.linearAdSlotHeight=e.offsetHeight,t.nonLinearAdSlotWidth=e.offsetWidth,t.nonLinearAdSlotHeight=e.offsetHeight,t.forceNonLinearFullSlot=!1,t.setAdWillPlayMuted(!this.player.muted),this.loader.requestAds(t)}catch(e){this.onAdError(e)}})),e(this,"pollCountdown",((e=!1)=>{if(!e)return clearInterval(this.countdownTimer),void this.elements.container.removeAttribute("data-badge-text");this.countdownTimer=setInterval((()=>{const e=Ee(Math.max(this.manager.getRemainingTime(),0)),t=`${ve.get("advertisement",this.player.config)} - ${e}`;this.elements.container.setAttribute("data-badge-text",t)}),100)})),e(this,"onAdsManagerLoaded",(e=>{if(!this.enabled)return;const t=new google.ima.AdsRenderingSettings;t.restoreCustomPlaybackStateOnAdBreakComplete=!0,t.enablePreloading=!0,this.manager=e.getAdsManager(this.player,t),this.cuePoints=this.manager.getCuePoints(),this.manager.addEventListener(google.ima.AdErrorEvent.Type.AD_ERROR,(e=>this.onAdError(e))),Object.keys(google.ima.AdEvent.Type).forEach((e=>{this.manager.addEventListener(google.ima.AdEvent.Type[e],(e=>this.onAdEvent(e)))})),this.trigger("loaded")})),e(this,"addCuePoints",(()=>{S.empty(this.cuePoints)||this.cuePoints.forEach((e=>{if(0!==e&&-1!==e&&e{const{container:t}=this.player.elements,i=e.getAd(),s=e.getAdData();switch((e=>{Z.call(this.player,this.player.media,`ads${e.replace(/_/g,"").toLowerCase()}`)})(e.type),e.type){case google.ima.AdEvent.Type.LOADED:this.trigger("loaded"),this.pollCountdown(!0),i.isLinear()||(i.width=t.offsetWidth,i.height=t.offsetHeight);break;case google.ima.AdEvent.Type.STARTED:this.manager.setVolume(this.player.volume);break;case google.ima.AdEvent.Type.ALL_ADS_COMPLETED:this.player.ended?this.loadAds():this.loader.contentComplete();break;case google.ima.AdEvent.Type.CONTENT_PAUSE_REQUESTED:this.pauseContent();break;case google.ima.AdEvent.Type.CONTENT_RESUME_REQUESTED:this.pollCountdown(),this.resumeContent();break;case google.ima.AdEvent.Type.LOG:s.adError&&this.player.debug.warn(`Non-fatal ad error: ${s.adError.getMessage()}`)}})),e(this,"onAdError",(e=>{this.cancel(),this.player.debug.warn("Ads error",e)})),e(this,"listeners",(()=>{const{container:e}=this.player.elements;let t;this.player.on("canplay",(()=>{this.addCuePoints()})),this.player.on("ended",(()=>{this.loader.contentComplete()})),this.player.on("timeupdate",(()=>{t=this.player.currentTime})),this.player.on("seeked",(()=>{const e=this.player.currentTime;S.empty(this.cuePoints)||this.cuePoints.forEach(((i,s)=>{t{this.manager&&this.manager.resize(e.offsetWidth,e.offsetHeight,google.ima.ViewMode.NORMAL)}))})),e(this,"play",(()=>{const{container:e}=this.player.elements;this.managerPromise||this.resumeContent(),this.managerPromise.then((()=>{this.manager.setVolume(this.player.volume),this.elements.displayContainer.initialize();try{this.initialized||(this.manager.init(e.offsetWidth,e.offsetHeight,google.ima.ViewMode.NORMAL),this.manager.start()),this.initialized=!0}catch(e){this.onAdError(e)}})).catch((()=>{}))})),e(this,"resumeContent",(()=>{this.elements.container.style.zIndex="",this.playing=!1,ie(this.player.media.play())})),e(this,"pauseContent",(()=>{this.elements.container.style.zIndex=3,this.playing=!0,this.player.media.pause()})),e(this,"cancel",(()=>{this.initialized&&this.resumeContent(),this.trigger("error"),this.loadAds()})),e(this,"loadAds",(()=>{this.managerPromise.then((()=>{this.manager&&this.manager.destroy(),this.managerPromise=new Promise((e=>{this.on("loaded",e),this.player.debug.log(this.manager)})),this.initialized=!1,this.requestAds()})).catch((()=>{}))})),e(this,"trigger",((e,...t)=>{const i=this.events[e];S.array(i)&&i.forEach((e=>{S.function(e)&&e.apply(this,t)}))})),e(this,"on",((e,t)=>(S.array(this.events[e])||(this.events[e]=[]),this.events[e].push(t),this))),e(this,"startSafetyTimer",((e,t)=>{this.player.debug.log(`Safety timer invoked from: ${t}`),this.safetyTimer=setTimeout((()=>{this.cancel(),this.clearSafetyTimer("startSafetyTimer()")}),e)})),e(this,"clearSafetyTimer",(e=>{S.nullOrUndefined(this.safetyTimer)||(this.player.debug.log(`Safety timer cleared from: ${e}`),clearTimeout(this.safetyTimer),this.safetyTimer=null)})),this.player=t,this.config=t.config.ads,this.playing=!1,this.initialized=!1,this.elements={container:null,displayContainer:null},this.manager=null,this.loader=null,this.cuePoints=null,this.events={},this.safetyTimer=null,this.countdownTimer=null,this.managerPromise=new Promise(((e,t)=>{this.on("loaded",e),this.on("error",t)})),this.load()}get enabled(){const{config:e}=this;return this.player.isHTML5&&this.player.isVideo&&e.enabled&&(!S.empty(e.publisherId)||S.url(e.tagUrl))}get tagUrl(){const{config:e}=this;if(S.url(e.tagUrl))return e.tagUrl;return`https://go.aniview.com/api/adserver6/vast/?${Ne({AV_PUBLISHERID:"58c25bb0073ef448b1087ad6",AV_CHANNELID:"5a0458dc28a06145e4519d21",AV_URL:window.location.hostname,cb:Date.now(),AV_WIDTH:640,AV_HEIGHT:480,AV_CDIM2:e.publisherId})}`}}function Ge(e=0,t=0,i=255){return Math.min(Math.max(e,t),i)}const Ze=e=>{const t=[];return e.split(/\r\n\r\n|\n\n|\r\r/).forEach((e=>{const i={};e.split(/\r\n|\n|\r/).forEach((e=>{if(S.number(i.startTime)){if(!S.empty(e.trim())&&S.empty(i.text)){const t=e.trim().split("#xywh=");[i.text]=t,t[1]&&([i.x,i.y,i.w,i.h]=t[1].split(","))}}else{const t=e.match(/([0-9]{2})?:?([0-9]{2}):([0-9]{2}).([0-9]{2,3})( ?--> ?)([0-9]{2})?:?([0-9]{2}):([0-9]{2}).([0-9]{2,3})/);t&&(i.startTime=60*Number(t[1]||0)*60+60*Number(t[2])+Number(t[3])+Number(`0.${t[4]}`),i.endTime=60*Number(t[6]||0)*60+60*Number(t[7])+Number(t[8])+Number(`0.${t[9]}`))}})),i.text&&t.push(i)})),t},et=(e,t)=>{const i={};return e>t.width/t.height?(i.width=t.width,i.height=1/e*t.width):(i.height=t.height,i.width=e*t.height),i};class tt{constructor(t){e(this,"load",(()=>{this.player.elements.display.seekTooltip&&(this.player.elements.display.seekTooltip.hidden=this.enabled),this.enabled&&this.getThumbnails().then((()=>{this.enabled&&(this.render(),this.determineContainerAutoSizing(),this.listeners(),this.loaded=!0)}))})),e(this,"getThumbnails",(()=>new Promise((e=>{const{src:t}=this.player.config.previewThumbnails;if(S.empty(t))throw new Error("Missing previewThumbnails.src config attribute");const i=()=>{this.thumbnails.sort(((e,t)=>e.height-t.height)),this.player.debug.log("Preview thumbnails",this.thumbnails),e()};if(S.function(t))t((e=>{this.thumbnails=e,i()}));else{const e=(S.string(t)?[t]:t).map((e=>this.getThumbnail(e)));Promise.all(e).then(i)}})))),e(this,"getThumbnail",(e=>new Promise((t=>{Te(e).then((i=>{const s={frames:Ze(i),height:null,urlPrefix:""};s.frames[0].text.startsWith("/")||s.frames[0].text.startsWith("http://")||s.frames[0].text.startsWith("https://")||(s.urlPrefix=e.substring(0,e.lastIndexOf("/")+1));const n=new Image;n.onload=()=>{s.height=n.naturalHeight,s.width=n.naturalWidth,this.thumbnails.push(s),t()},n.src=s.urlPrefix+s.frames[0].text}))})))),e(this,"startMove",(e=>{if(this.loaded&&S.event(e)&&["touchmove","mousemove"].includes(e.type)&&this.player.media.duration){if("touchmove"===e.type)this.seekTime=this.player.media.duration*(this.player.elements.inputs.seek.value/100);else{var t,i;const s=this.player.elements.progress.getBoundingClientRect(),n=100/s.width*(e.pageX-s.left);this.seekTime=this.player.media.duration*(n/100),this.seekTime<0&&(this.seekTime=0),this.seekTime>this.player.media.duration-1&&(this.seekTime=this.player.media.duration-1),this.mousePosX=e.pageX,this.elements.thumb.time.innerText=Ee(this.seekTime);const a=null===(t=this.player.config.markers)||void 0===t||null===(i=t.points)||void 0===i?void 0:i.find((({time:e})=>e===Math.round(this.seekTime)));a&&this.elements.thumb.time.insertAdjacentHTML("afterbegin",`${a.label}
`)}this.showImageAtCurrentTime()}})),e(this,"endMove",(()=>{this.toggleThumbContainer(!1,!0)})),e(this,"startScrubbing",(e=>{(S.nullOrUndefined(e.button)||!1===e.button||0===e.button)&&(this.mouseDown=!0,this.player.media.duration&&(this.toggleScrubbingContainer(!0),this.toggleThumbContainer(!1,!0),this.showImageAtCurrentTime()))})),e(this,"endScrubbing",(()=>{this.mouseDown=!1,Math.ceil(this.lastTime)===Math.ceil(this.player.media.currentTime)?this.toggleScrubbingContainer(!1):G.call(this.player,this.player.media,"timeupdate",(()=>{this.mouseDown||this.toggleScrubbingContainer(!1)}))})),e(this,"listeners",(()=>{this.player.on("play",(()=>{this.toggleThumbContainer(!1,!0)})),this.player.on("seeked",(()=>{this.toggleThumbContainer(!1)})),this.player.on("timeupdate",(()=>{this.lastTime=this.player.media.currentTime}))})),e(this,"render",(()=>{this.elements.thumb.container=$("div",{class:this.player.config.classNames.previewThumbnails.thumbContainer}),this.elements.thumb.imageContainer=$("div",{class:this.player.config.classNames.previewThumbnails.imageContainer}),this.elements.thumb.container.appendChild(this.elements.thumb.imageContainer);const e=$("div",{class:this.player.config.classNames.previewThumbnails.timeContainer});this.elements.thumb.time=$("span",{},"00:00"),e.appendChild(this.elements.thumb.time),this.elements.thumb.imageContainer.appendChild(e),S.element(this.player.elements.progress)&&this.player.elements.progress.appendChild(this.elements.thumb.container),this.elements.scrubbing.container=$("div",{class:this.player.config.classNames.previewThumbnails.scrubbingContainer}),this.player.elements.wrapper.appendChild(this.elements.scrubbing.container)})),e(this,"destroy",(()=>{this.elements.thumb.container&&this.elements.thumb.container.remove(),this.elements.scrubbing.container&&this.elements.scrubbing.container.remove()})),e(this,"showImageAtCurrentTime",(()=>{this.mouseDown?this.setScrubbingContainerSize():this.setThumbContainerSizeAndPos();const e=this.thumbnails[0].frames.findIndex((e=>this.seekTime>=e.startTime&&this.seekTime<=e.endTime)),t=e>=0;let i=0;this.mouseDown||this.toggleThumbContainer(t),t&&(this.thumbnails.forEach(((t,s)=>{this.loadedImages.includes(t.frames[e].text)&&(i=s)})),e!==this.showingThumb&&(this.showingThumb=e,this.loadImage(i)))})),e(this,"loadImage",((e=0)=>{const t=this.showingThumb,i=this.thumbnails[e],{urlPrefix:s}=i,n=i.frames[t],a=i.frames[t].text,l=s+a;if(this.currentImageElement&&this.currentImageElement.dataset.filename===a)this.showImage(this.currentImageElement,n,e,t,a,!1),this.currentImageElement.dataset.index=t,this.removeOldImages(this.currentImageElement);else{this.loadingImage&&this.usingSprites&&(this.loadingImage.onload=null);const i=new Image;i.src=l,i.dataset.index=t,i.dataset.filename=a,this.showingThumbFilename=a,this.player.debug.log(`Loading image: ${l}`),i.onload=()=>this.showImage(i,n,e,t,a,!0),this.loadingImage=i,this.removeOldImages(i)}})),e(this,"showImage",((e,t,i,s,n,a=!0)=>{this.player.debug.log(`Showing thumb: ${n}. num: ${s}. qual: ${i}. newimg: ${a}`),this.setImageSizeAndOffset(e,t),a&&(this.currentImageContainer.appendChild(e),this.currentImageElement=e,this.loadedImages.includes(n)||this.loadedImages.push(n)),this.preloadNearby(s,!0).then(this.preloadNearby(s,!1)).then(this.getHigherQuality(i,e,t,n))})),e(this,"removeOldImages",(e=>{Array.from(this.currentImageContainer.children).forEach((t=>{if("img"!==t.tagName.toLowerCase())return;const i=this.usingSprites?500:1e3;if(t.dataset.index!==e.dataset.index&&!t.dataset.deleting){t.dataset.deleting=!0;const{currentImageContainer:e}=this;setTimeout((()=>{e.removeChild(t),this.player.debug.log(`Removing thumb: ${t.dataset.filename}`)}),i)}}))})),e(this,"preloadNearby",((e,t=!0)=>new Promise((i=>{setTimeout((()=>{const s=this.thumbnails[0].frames[e].text;if(this.showingThumbFilename===s){let n;n=t?this.thumbnails[0].frames.slice(e):this.thumbnails[0].frames.slice(0,e).reverse();let a=!1;n.forEach((e=>{const t=e.text;if(t!==s&&!this.loadedImages.includes(t)){a=!0,this.player.debug.log(`Preloading thumb filename: ${t}`);const{urlPrefix:e}=this.thumbnails[0],s=e+t,n=new Image;n.src=s,n.onload=()=>{this.player.debug.log(`Preloaded thumb filename: ${t}`),this.loadedImages.includes(t)||this.loadedImages.push(t),i()}}})),a||i()}}),300)})))),e(this,"getHigherQuality",((e,t,i,s)=>{if(e{this.showingThumbFilename===s&&(this.player.debug.log(`Showing higher quality thumb for: ${s}`),this.loadImage(e+1))}),300)}})),e(this,"toggleThumbContainer",((e=!1,t=!1)=>{const i=this.player.config.classNames.previewThumbnails.thumbContainerShown;this.elements.thumb.container.classList.toggle(i,e),!e&&t&&(this.showingThumb=null,this.showingThumbFilename=null)})),e(this,"toggleScrubbingContainer",((e=!1)=>{const t=this.player.config.classNames.previewThumbnails.scrubbingContainerShown;this.elements.scrubbing.container.classList.toggle(t,e),e||(this.showingThumb=null,this.showingThumbFilename=null)})),e(this,"determineContainerAutoSizing",(()=>{(this.elements.thumb.imageContainer.clientHeight>20||this.elements.thumb.imageContainer.clientWidth>20)&&(this.sizeSpecifiedInCSS=!0)})),e(this,"setThumbContainerSizeAndPos",(()=>{const{imageContainer:e}=this.elements.thumb;if(this.sizeSpecifiedInCSS){if(e.clientHeight>20&&e.clientWidth<20){const t=Math.floor(e.clientHeight*this.thumbAspectRatio);e.style.width=`${t}px`}else if(e.clientHeight<20&&e.clientWidth>20){const t=Math.floor(e.clientWidth/this.thumbAspectRatio);e.style.height=`${t}px`}}else{const t=Math.floor(this.thumbContainerHeight*this.thumbAspectRatio);e.style.height=`${this.thumbContainerHeight}px`,e.style.width=`${t}px`}this.setThumbContainerPos()})),e(this,"setThumbContainerPos",(()=>{const e=this.player.elements.progress.getBoundingClientRect(),t=this.player.elements.container.getBoundingClientRect(),{container:i}=this.elements.thumb,s=t.left-e.left+10,n=t.right-e.left-i.clientWidth-10,a=this.mousePosX-e.left-i.clientWidth/2,l=Ge(a,s,n);i.style.left=`${l}px`,i.style.setProperty("--preview-arrow-offset",a-l+"px")})),e(this,"setScrubbingContainerSize",(()=>{const{width:e,height:t}=et(this.thumbAspectRatio,{width:this.player.media.clientWidth,height:this.player.media.clientHeight});this.elements.scrubbing.container.style.width=`${e}px`,this.elements.scrubbing.container.style.height=`${t}px`})),e(this,"setImageSizeAndOffset",((e,t)=>{if(!this.usingSprites)return;const i=this.thumbContainerHeight/t.h;e.style.height=e.naturalHeight*i+"px",e.style.width=e.naturalWidth*i+"px",e.style.left=`-${t.x*i}px`,e.style.top=`-${t.y*i}px`})),this.player=t,this.thumbnails=[],this.loaded=!1,this.lastMouseMoveTime=Date.now(),this.mouseDown=!1,this.loadedImages=[],this.elements={thumb:{},scrubbing:{}},this.load()}get enabled(){return this.player.isHTML5&&this.player.isVideo&&this.player.config.previewThumbnails.enabled}get currentImageContainer(){return this.mouseDown?this.elements.scrubbing.container:this.elements.thumb.imageContainer}get usingSprites(){return Object.keys(this.thumbnails[0].frames[0]).includes("w")}get thumbAspectRatio(){return this.usingSprites?this.thumbnails[0].frames[0].w/this.thumbnails[0].frames[0].h:this.thumbnails[0].width/this.thumbnails[0].height}get thumbContainerHeight(){if(this.mouseDown){const{height:e}=et(this.thumbAspectRatio,{width:this.player.media.clientWidth,height:this.player.media.clientHeight});return e}return this.sizeSpecifiedInCSS?this.elements.thumb.imageContainer.clientHeight:Math.floor(this.player.media.clientWidth/this.thumbAspectRatio/4)}get currentImageElement(){return this.mouseDown?this.currentScrubbingImageElement:this.currentThumbnailImageElement}set currentImageElement(e){this.mouseDown?this.currentScrubbingImageElement=e:this.currentThumbnailImageElement=e}}const it={insertElements(e,t){S.string(t)?_(e,this.media,{src:t}):S.array(t)&&t.forEach((t=>{_(e,this.media,t)}))},change(e){N(e,"sources.length")?(de.cancelRequests.call(this),this.destroy.call(this,(()=>{this.options.quality=[],O(this.media),this.media=null,S.element(this.elements.container)&&this.elements.container.removeAttribute("class");const{sources:t,type:i}=e,[{provider:s=_e.html5,src:n}]=t,a="html5"===s?i:"div",l="html5"===s?{}:{src:n};Object.assign(this,{provider:s,type:i,supported:K.check(i,s,this.config.playsinline),media:$(a,l)}),this.elements.container.appendChild(this.media),S.boolean(e.autoplay)&&(this.config.autoplay=e.autoplay),this.isHTML5&&(this.config.crossorigin&&this.media.setAttribute("crossorigin",""),this.config.autoplay&&this.media.setAttribute("autoplay",""),S.empty(e.poster)||(this.poster=e.poster),this.config.loop.active&&this.media.setAttribute("loop",""),this.config.muted&&this.media.setAttribute("muted",""),this.config.playsinline&&this.media.setAttribute("playsinline","")),Fe.addStyleHook.call(this),this.isHTML5&&it.insertElements.call(this,"source",t),this.config.title=e.title,Xe.setup.call(this),this.isHTML5&&Object.keys(e).includes("tracks")&&it.insertElements.call(this,"track",e.tracks),(this.isHTML5||this.isEmbed&&!this.supported.ui)&&Fe.build.call(this),this.isHTML5&&this.media.load(),S.empty(e.previewThumbnails)||(Object.assign(this.config.previewThumbnails,e.previewThumbnails),this.previewThumbnails&&this.previewThumbnails.loaded&&(this.previewThumbnails.destroy(),this.previewThumbnails=null),this.config.previewThumbnails.enabled&&(this.previewThumbnails=new tt(this))),this.fullscreen.update()}),!0)):this.debug.warn("Invalid source format")}};class st{constructor(t,i){if(e(this,"play",(()=>S.function(this.media.play)?(this.ads&&this.ads.enabled&&this.ads.managerPromise.then((()=>this.ads.play())).catch((()=>ie(this.media.play()))),this.media.play()):null)),e(this,"pause",(()=>this.playing&&S.function(this.media.pause)?this.media.pause():null)),e(this,"togglePlay",(e=>(S.boolean(e)?e:!this.playing)?this.play():this.pause())),e(this,"stop",(()=>{this.isHTML5?(this.pause(),this.restart()):S.function(this.media.stop)&&this.media.stop()})),e(this,"restart",(()=>{this.currentTime=0})),e(this,"rewind",(e=>{this.currentTime-=S.number(e)?e:this.config.seekTime})),e(this,"forward",(e=>{this.currentTime+=S.number(e)?e:this.config.seekTime})),e(this,"increaseVolume",(e=>{const t=this.media.muted?0:this.volume;this.volume=t+(S.number(e)?e:0)})),e(this,"decreaseVolume",(e=>{this.increaseVolume(-e)})),e(this,"airplay",(()=>{K.airplay&&this.media.webkitShowPlaybackTargetPicker()})),e(this,"toggleControls",(e=>{if(this.supported.ui&&!this.isAudio){const t=F(this.elements.container,this.config.classNames.hideControls),i=void 0===e?void 0:!e,s=R(this.elements.container,this.config.classNames.hideControls,i);if(s&&S.array(this.config.controls)&&this.config.controls.includes("settings")&&!S.empty(this.config.settings)&&Pe.toggleMenu.call(this,!1),s!==t){const e=s?"controlshidden":"controlsshown";Z.call(this,this.media,e)}return!s}return!1})),e(this,"on",((e,t)=>{X.call(this,this.elements.container,e,t)})),e(this,"once",((e,t)=>{G.call(this,this.elements.container,e,t)})),e(this,"off",((e,t)=>{J(this.elements.container,e,t)})),e(this,"destroy",((e,t=!1)=>{if(!this.ready)return;const i=()=>{document.body.style.overflow="",this.embed=null,t?(Object.keys(this.elements).length&&(O(this.elements.buttons.play),O(this.elements.captions),O(this.elements.controls),O(this.elements.wrapper),this.elements.buttons.play=null,this.elements.captions=null,this.elements.controls=null,this.elements.wrapper=null),S.function(e)&&e()):(ee.call(this),de.cancelRequests.call(this),q(this.elements.original,this.elements.container),Z.call(this,this.elements.original,"destroyed",!0),S.function(e)&&e.call(this.elements.original),this.ready=!1,setTimeout((()=>{this.elements=null,this.media=null}),200))};this.stop(),clearTimeout(this.timers.loading),clearTimeout(this.timers.controls),clearTimeout(this.timers.resized),this.isHTML5?(Fe.toggleNativeControls.call(this,!0),i()):this.isYouTube?(clearInterval(this.timers.buffering),clearInterval(this.timers.playing),null!==this.embed&&S.function(this.embed.destroy)&&this.embed.destroy(),i()):this.isVimeo&&(null!==this.embed&&this.embed.unload().then(i),setTimeout(i,200))})),e(this,"supports",(e=>K.mime.call(this,e))),this.timers={},this.ready=!1,this.loading=!1,this.failed=!1,this.touch=K.touch,this.media=t,S.string(this.media)&&(this.media=document.querySelectorAll(this.media)),(window.jQuery&&this.media instanceof jQuery||S.nodeList(this.media)||S.array(this.media))&&(this.media=this.media[0]),this.config=x({},Le,st.defaults,i||{},(()=>{try{return JSON.parse(this.media.getAttribute("data-plyr-config"))}catch(e){return{}}})()),this.elements={container:null,fullscreen:null,captions:null,buttons:{},display:{},progress:{},inputs:{},settings:{popup:null,menu:null,panels:{},buttons:{}}},this.captions={active:null,currentTrack:-1,meta:new WeakMap},this.fullscreen={active:!1},this.options={speed:[],quality:[]},this.debug=new De(this.config.debug),this.debug.log("Config",this.config),this.debug.log("Support",K),S.nullOrUndefined(this.media)||!S.element(this.media))return void this.debug.error("Setup failed: no suitable element passed");if(this.media.plyr)return void this.debug.warn("Target already setup");if(!this.config.enabled)return void this.debug.error("Setup failed: disabled by config");if(!K.check().api)return void this.debug.error("Setup failed: no support");const s=this.media.cloneNode(!0);s.autoplay=!1,this.elements.original=s;const n=this.media.tagName.toLowerCase();let a=null,l=null;switch(n){case"div":if(a=this.media.querySelector("iframe"),S.element(a)){if(l=Me(a.getAttribute("src")),this.provider=function(e){return/^(https?:\/\/)?(www\.)?(youtube\.com|youtube-nocookie\.com|youtu\.?be)\/.+$/.test(e)?_e.youtube:/^https?:\/\/player.vimeo.com\/video\/\d{0,9}(?=\b|\/)/.test(e)?_e.vimeo:null}(l.toString()),this.elements.container=this.media,this.media=a,this.elements.container.className="",l.search.length){const e=["1","true"];e.includes(l.searchParams.get("autoplay"))&&(this.config.autoplay=!0),e.includes(l.searchParams.get("loop"))&&(this.config.loop.active=!0),this.isYouTube?(this.config.playsinline=e.includes(l.searchParams.get("playsinline")),this.config.youtube.hl=l.searchParams.get("hl")):this.config.playsinline=!0}}else this.provider=this.media.getAttribute(this.config.attributes.embed.provider),this.media.removeAttribute(this.config.attributes.embed.provider);if(S.empty(this.provider)||!Object.values(_e).includes(this.provider))return void this.debug.error("Setup failed: Invalid provider");this.type=je;break;case"video":case"audio":this.type=n,this.provider=_e.html5,this.media.hasAttribute("crossorigin")&&(this.config.crossorigin=!0),this.media.hasAttribute("autoplay")&&(this.config.autoplay=!0),(this.media.hasAttribute("playsinline")||this.media.hasAttribute("webkit-playsinline"))&&(this.config.playsinline=!0),this.media.hasAttribute("muted")&&(this.config.muted=!0),this.media.hasAttribute("loop")&&(this.config.loop.active=!0);break;default:return void this.debug.error("Setup failed: unsupported type")}this.supported=K.check(this.type,this.provider),this.supported.api?(this.eventListeners=[],this.listeners=new Ve(this),this.storage=new we(this),this.media.plyr=this,S.element(this.elements.container)||(this.elements.container=$("div"),L(this.media,this.elements.container)),Fe.migrateStyles.call(this),Fe.addStyleHook.call(this),Xe.setup.call(this),this.config.debug&&X.call(this,this.elements.container,this.config.events.join(" "),(e=>{this.debug.log(`event: ${e.type}`)})),this.fullscreen=new He(this),(this.isHTML5||this.isEmbed&&!this.supported.ui)&&Fe.build.call(this),this.listeners.container(),this.listeners.global(),this.config.ads.enabled&&(this.ads=new Je(this)),this.isHTML5&&this.config.autoplay&&this.once("canplay",(()=>ie(this.play()))),this.lastSeekTime=0,this.config.previewThumbnails.enabled&&(this.previewThumbnails=new tt(this))):this.debug.error("Setup failed: no support")}get isHTML5(){return this.provider===_e.html5}get isEmbed(){return this.isYouTube||this.isVimeo}get isYouTube(){return this.provider===_e.youtube}get isVimeo(){return this.provider===_e.vimeo}get isVideo(){return this.type===je}get isAudio(){return this.type===Oe}get playing(){return Boolean(this.ready&&!this.paused&&!this.ended)}get paused(){return Boolean(this.media.paused)}get stopped(){return Boolean(this.paused&&0===this.currentTime)}get ended(){return Boolean(this.media.ended)}set currentTime(e){if(!this.duration)return;const t=S.number(e)&&e>0;this.media.currentTime=t?Math.min(e,this.duration):0,this.debug.log(`Seeking to ${this.currentTime} seconds`)}get currentTime(){return Number(this.media.currentTime)}get buffered(){const{buffered:e}=this.media;return S.number(e)?e:e&&e.length&&this.duration>0?e.end(0)/this.duration:0}get seeking(){return Boolean(this.media.seeking)}get duration(){const e=parseFloat(this.config.duration),t=(this.media||{}).duration,i=S.number(t)&&t!==1/0?t:0;return e||i}set volume(e){let t=e;S.string(t)&&(t=Number(t)),S.number(t)||(t=this.storage.get("volume")),S.number(t)||({volume:t}=this.config),t>1&&(t=1),t<0&&(t=0),this.config.volume=t,this.media.volume=t,!S.empty(e)&&this.muted&&t>0&&(this.muted=!1)}get volume(){return Number(this.media.volume)}set muted(e){let t=e;S.boolean(t)||(t=this.storage.get("muted")),S.boolean(t)||(t=this.config.muted),this.config.muted=t,this.media.muted=t}get muted(){return Boolean(this.media.muted)}get hasAudio(){return!this.isHTML5||(!!this.isAudio||(Boolean(this.media.mozHasAudio)||Boolean(this.media.webkitAudioDecodedByteCount)||Boolean(this.media.audioTracks&&this.media.audioTracks.length)))}set speed(e){let t=null;S.number(e)&&(t=e),S.number(t)||(t=this.storage.get("speed")),S.number(t)||(t=this.config.speed.selected);const{minimumSpeed:i,maximumSpeed:s}=this;t=Ge(t,i,s),this.config.speed.selected=t,setTimeout((()=>{this.media&&(this.media.playbackRate=t)}),0)}get speed(){return Number(this.media.playbackRate)}get minimumSpeed(){return this.isYouTube?Math.min(...this.options.speed):this.isVimeo?.5:.0625}get maximumSpeed(){return this.isYouTube?Math.max(...this.options.speed):this.isVimeo?2:16}set quality(e){const t=this.config.quality,i=this.options.quality;if(!i.length)return;let s=[!S.empty(e)&&Number(e),this.storage.get("quality"),t.selected,t.default].find(S.number),n=!0;if(!i.includes(s)){const e=ne(i,s);this.debug.warn(`Unsupported quality option: ${s}, using ${e} instead`),s=e,n=!1}t.selected=s,this.media.quality=s,n&&this.storage.set({quality:s})}get quality(){return this.media.quality}set loop(e){const t=S.boolean(e)?e:this.config.loop.active;this.config.loop.active=t,this.media.loop=t}get loop(){return Boolean(this.media.loop)}set source(e){it.change.call(this,e)}get source(){return this.media.currentSrc}get download(){const{download:e}=this.config.urls;return S.url(e)?e:this.source}set download(e){S.url(e)&&(this.config.urls.download=e,Pe.setDownloadUrl.call(this))}set poster(e){this.isVideo?Fe.setPoster.call(this,e,!1).catch((()=>{})):this.debug.warn("Poster can only be set for video")}get poster(){return this.isVideo?this.media.getAttribute("poster")||this.media.getAttribute("data-poster"):null}get ratio(){if(!this.isVideo)return null;const e=oe(ce.call(this));return S.array(e)?e.join(":"):e}set ratio(e){this.isVideo?S.string(e)&&re(e)?(this.config.ratio=oe(e),ue.call(this)):this.debug.error(`Invalid aspect ratio specified (${e})`):this.debug.warn("Aspect ratio can only be set for video")}set autoplay(e){this.config.autoplay=S.boolean(e)?e:this.config.autoplay}get autoplay(){return Boolean(this.config.autoplay)}toggleCaptions(e){xe.toggle.call(this,e,!1)}set currentTrack(e){xe.set.call(this,e,!1),xe.setup.call(this)}get currentTrack(){const{toggled:e,currentTrack:t}=this.captions;return e?t:-1}set language(e){xe.setLanguage.call(this,e,!1)}get language(){return(xe.getCurrentTrack.call(this)||{}).language}set pip(e){if(!K.pip)return;const t=S.boolean(e)?e:!this.pip;S.function(this.media.webkitSetPresentationMode)&&this.media.webkitSetPresentationMode(t?Ie:$e),S.function(this.media.requestPictureInPicture)&&(!this.pip&&t?this.media.requestPictureInPicture():this.pip&&!t&&document.exitPictureInPicture())}get pip(){return K.pip?S.empty(this.media.webkitPresentationMode)?this.media===document.pictureInPictureElement:this.media.webkitPresentationMode===Ie:null}setPreviewThumbnails(e){this.previewThumbnails&&this.previewThumbnails.loaded&&(this.previewThumbnails.destroy(),this.previewThumbnails=null),Object.assign(this.config.previewThumbnails,e),this.config.previewThumbnails.enabled&&(this.previewThumbnails=new tt(this))}static supported(e,t){return K.check(e,t)}static loadSprite(e,t){return ke(e,t)}static setup(e,t={}){let i=null;return S.string(e)?i=Array.from(document.querySelectorAll(e)):S.nodeList(e)?i=Array.from(e):S.array(e)&&(i=e.filter(S.element)),S.empty(i)?null:i.map((e=>new st(e,t)))}}var nt;return st.defaults=(nt=Le,JSON.parse(JSON.stringify(nt))),st})); +"object"==typeof navigator&&function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define("Plyr",t):(e="undefined"!=typeof globalThis?globalThis:e||self).Plyr=t()}(this,(function(){"use strict";function e(e,t,i){return(t=function(e){var t=function(e,t){if("object"!=typeof e||null===e)return e;var i=e[Symbol.toPrimitive];if(void 0!==i){var s=i.call(e,t||"default");if("object"!=typeof s)return s;throw new TypeError("@@toPrimitive must return a primitive value.")}return("string"===t?String:Number)(e)}(e,"string");return"symbol"==typeof t?t:String(t)}(t))in e?Object.defineProperty(e,t,{value:i,enumerable:!0,configurable:!0,writable:!0}):e[t]=i,e}function t(e,t){for(var i=0;it){var i=function(e){var t="".concat(e).match(/(?:\.(\d+))?(?:[eE]([+-]?\d+))?$/);return t?Math.max(0,(t[1]?t[1].length:0)-(t[2]?+t[2]:0)):0}(t);return parseFloat(e.toFixed(i))}return Math.round(e/t)*t}var g=function(){function e(t,i){(function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")})(this,e),m.element(t)?this.element=t:m.string(t)&&(this.element=document.querySelector(t)),m.element(this.element)&&m.empty(this.element.rangeTouch)&&(this.config=n({},a,{},i),this.init())}return function(e,i,s){i&&t(e.prototype,i),s&&t(e,s)}(e,[{key:"init",value:function(){e.enabled&&(this.config.addCSS&&(this.element.style.userSelect="none",this.element.style.webKitUserSelect="none",this.element.style.touchAction="manipulation"),this.listeners(!0),this.element.rangeTouch=this)}},{key:"destroy",value:function(){e.enabled&&(this.config.addCSS&&(this.element.style.userSelect="",this.element.style.webKitUserSelect="",this.element.style.touchAction=""),this.listeners(!1),this.element.rangeTouch=null)}},{key:"listeners",value:function(e){var t=this,i=e?"addEventListener":"removeEventListener";["touchstart","touchmove","touchend"].forEach((function(e){t.element[i](e,(function(e){return t.set(e)}),!1)}))}},{key:"get",value:function(t){if(!e.enabled||!m.event(t))return null;var i,s=t.target,n=t.changedTouches[0],a=parseFloat(s.getAttribute("min"))||0,l=parseFloat(s.getAttribute("max"))||100,r=parseFloat(s.getAttribute("step"))||1,o=s.getBoundingClientRect(),c=100/o.width*(this.config.thumbWidth/2)/100;return 0>(i=100/o.width*(n.clientX-o.left))?i=0:100i?i-=(100-2*i)*c:50null!=e?e.constructor:null,y=(e,t)=>Boolean(e&&t&&e instanceof t),b=e=>null==e,v=e=>f(e)===Object,w=e=>f(e)===String,T=e=>"function"==typeof e,k=e=>Array.isArray(e),C=e=>y(e,NodeList),A=e=>b(e)||(w(e)||k(e)||C(e))&&!e.length||v(e)&&!Object.keys(e).length;var S={nullOrUndefined:b,object:v,number:e=>f(e)===Number&&!Number.isNaN(e),string:w,boolean:e=>f(e)===Boolean,function:T,array:k,weakMap:e=>y(e,WeakMap),nodeList:C,element:e=>null!==e&&"object"==typeof e&&1===e.nodeType&&"object"==typeof e.style&&"object"==typeof e.ownerDocument,textNode:e=>f(e)===Text,event:e=>y(e,Event),keyboardEvent:e=>y(e,KeyboardEvent),cue:e=>y(e,window.TextTrackCue)||y(e,window.VTTCue),track:e=>y(e,TextTrack)||!b(e)&&w(e.kind),promise:e=>y(e,Promise)&&T(e.then),url:e=>{if(y(e,window.URL))return!0;if(!w(e))return!1;let t=e;e.startsWith("http://")&&e.startsWith("https://")||(t=`http://${e}`);try{return!A(new URL(t).hostname)}catch(e){return!1}},empty:A};const E=(()=>{const e=document.createElement("span"),t={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"},i=Object.keys(t).find((t=>void 0!==e.style[t]));return!!S.string(i)&&t[i]})();function P(e,t){setTimeout((()=>{try{e.hidden=!0,e.offsetHeight,e.hidden=!1}catch(e){}}),t)}var M={isIE:Boolean(window.document.documentMode),isEdge:/Edge/g.test(navigator.userAgent),isWebKit:"WebkitAppearance"in document.documentElement.style&&!/Edge/g.test(navigator.userAgent),isIPhone:/iPhone|iPod/gi.test(navigator.userAgent)&&navigator.maxTouchPoints>1,isIPadOS:"MacIntel"===navigator.platform&&navigator.maxTouchPoints>1,isIos:/iPad|iPhone|iPod/gi.test(navigator.userAgent)&&navigator.maxTouchPoints>1};function N(e,t){return t.split(".").reduce(((e,t)=>e&&e[t]),e)}function x(e={},...t){if(!t.length)return e;const i=t.shift();return S.object(i)?(Object.keys(i).forEach((t=>{S.object(i[t])?(Object.keys(e).includes(t)||Object.assign(e,{[t]:{}}),x(e[t],i[t])):Object.assign(e,{[t]:i[t]})})),x(e,...t)):e}function L(e,t){const i=e.length?e:[e];Array.from(i).reverse().forEach(((e,i)=>{const s=i>0?t.cloneNode(!0):t,n=e.parentNode,a=e.nextSibling;s.appendChild(e),a?n.insertBefore(s,a):n.appendChild(s)}))}function I(e,t){S.element(e)&&!S.empty(t)&&Object.entries(t).filter((([,e])=>!S.nullOrUndefined(e))).forEach((([t,i])=>e.setAttribute(t,i)))}function $(e,t,i){const s=document.createElement(e);return S.object(t)&&I(s,t),S.string(i)&&(s.innerText=i),s}function _(e,t,i,s){S.element(t)&&t.appendChild($(e,i,s))}function O(e){S.nodeList(e)||S.array(e)?Array.from(e).forEach(O):S.element(e)&&S.element(e.parentNode)&&e.parentNode.removeChild(e)}function j(e){if(!S.element(e))return;let{length:t}=e.childNodes;for(;t>0;)e.removeChild(e.lastChild),t-=1}function q(e,t){return S.element(t)&&S.element(t.parentNode)&&S.element(e)?(t.parentNode.replaceChild(e,t),e):null}function D(e,t){if(!S.string(e)||S.empty(e))return{};const i={},s=x({},t);return e.split(",").forEach((e=>{const t=e.trim(),n=t.replace(".",""),a=t.replace(/[[\]]/g,"").split("="),[l]=a,r=a.length>1?a[1].replace(/["']/g,""):"";switch(t.charAt(0)){case".":S.string(s.class)?i.class=`${s.class} ${n}`:i.class=n;break;case"#":i.id=t.replace("#","");break;case"[":i[l]=r}})),x(s,i)}function H(e,t){if(!S.element(e))return;let i=t;S.boolean(i)||(i=!e.hidden),e.hidden=i}function R(e,t,i){if(S.nodeList(e))return Array.from(e).map((e=>R(e,t,i)));if(S.element(e)){let s="toggle";return void 0!==i&&(s=i?"add":"remove"),e.classList[s](t),e.classList.contains(t)}return!1}function F(e,t){return S.element(e)&&e.classList.contains(t)}function V(e,t){const{prototype:i}=Element;return(i.matches||i.webkitMatchesSelector||i.mozMatchesSelector||i.msMatchesSelector||function(){return Array.from(document.querySelectorAll(t)).includes(this)}).call(e,t)}function U(e){return this.elements.container.querySelectorAll(e)}function B(e){return this.elements.container.querySelector(e)}function W(e=null,t=!1){S.element(e)&&e.focus({preventScroll:!0,focusVisible:t})}const z={"audio/ogg":"vorbis","audio/wav":"1","video/webm":"vp8, vorbis","video/mp4":"avc1.42E01E, mp4a.40.2","video/ogg":"theora"},K={audio:"canPlayType"in document.createElement("audio"),video:"canPlayType"in document.createElement("video"),check(e,t){const i=K[e]||"html5"!==t;return{api:i,ui:i&&K.rangeInput}},pip:!(M.isIPhone||!S.function($("video").webkitSetPresentationMode)&&(!document.pictureInPictureEnabled||$("video").disablePictureInPicture)),airplay:S.function(window.WebKitPlaybackTargetAvailabilityEvent),playsinline:"playsInline"in document.createElement("video"),mime(e){if(S.empty(e))return!1;const[t]=e.split("/");let i=e;if(!this.isHTML5||t!==this.type)return!1;Object.keys(z).includes(i)&&(i+=`; codecs="${z[e]}"`);try{return Boolean(i&&this.media.canPlayType(i).replace(/no/,""))}catch(e){return!1}},textTracks:"textTracks"in document.createElement("video"),rangeInput:(()=>{const e=document.createElement("input");return e.type="range","range"===e.type})(),touch:"ontouchstart"in document.documentElement,transitions:!1!==E,reducedMotion:"matchMedia"in window&&window.matchMedia("(prefers-reduced-motion)").matches},Y=(()=>{let e=!1;try{const t=Object.defineProperty({},"passive",{get:()=>(e=!0,null)});window.addEventListener("test",null,t),window.removeEventListener("test",null,t)}catch(e){}return e})();function Q(e,t,i,s=!1,n=!0,a=!1){if(!e||!("addEventListener"in e)||S.empty(t)||!S.function(i))return;const l=t.split(" ");let r=a;Y&&(r={passive:n,capture:a}),l.forEach((t=>{this&&this.eventListeners&&s&&this.eventListeners.push({element:e,type:t,callback:i,options:r}),e[s?"addEventListener":"removeEventListener"](t,i,r)}))}function X(e,t="",i,s=!0,n=!1){Q.call(this,e,t,i,!0,s,n)}function J(e,t="",i,s=!0,n=!1){Q.call(this,e,t,i,!1,s,n)}function G(e,t="",i,s=!0,n=!1){const a=(...l)=>{J(e,t,a,s,n),i.apply(this,l)};Q.call(this,e,t,a,!0,s,n)}function Z(e,t="",i=!1,s={}){if(!S.element(e)||S.empty(t))return;const n=new CustomEvent(t,{bubbles:i,detail:{...s,plyr:this}});e.dispatchEvent(n)}function ee(){this&&this.eventListeners&&(this.eventListeners.forEach((e=>{const{element:t,type:i,callback:s,options:n}=e;t.removeEventListener(i,s,n)})),this.eventListeners=[])}function te(){return new Promise((e=>this.ready?setTimeout(e,0):X.call(this,this.elements.container,"ready",e))).then((()=>{}))}function ie(e){S.promise(e)&&e.then(null,(()=>{}))}function se(e){return S.array(e)?e.filter(((t,i)=>e.indexOf(t)===i)):e}function ne(e,t){return S.array(e)&&e.length?e.reduce(((e,i)=>Math.abs(i-t)({...e,[t/i]:[t,i]})),{});function re(e){if(!(S.array(e)||S.string(e)&&e.includes(":")))return!1;return(S.array(e)?e:e.split(":")).map(Number).every(S.number)}function oe(e){if(!S.array(e)||!e.every(S.number))return null;const[t,i]=e,s=(e,t)=>0===t?e:s(t,e%t),n=s(t,i);return[t/n,i/n]}function ce(e){const t=e=>re(e)?e.split(":").map(Number):null;let i=t(e);if(null===i&&(i=t(this.config.ratio)),null===i&&!S.empty(this.embed)&&S.array(this.embed.ratio)&&({ratio:i}=this.embed),null===i&&this.isHTML5){const{videoWidth:e,videoHeight:t}=this.media;i=[e,t]}return oe(i)}function ue(e){if(!this.isVideo)return{};const{wrapper:t}=this.elements,i=ce.call(this,e);if(!S.array(i))return{};const[s,n]=oe(i),a=100/s*n;if(ae(`aspect-ratio: ${s}/${n}`)?t.style.aspectRatio=`${s}/${n}`:t.style.paddingBottom=`${a}%`,this.isVimeo&&!this.config.vimeo.premium&&this.supported.ui){const e=100/this.media.offsetWidth*parseInt(window.getComputedStyle(this.media).paddingBottom,10),i=(e-a)/(e/50);this.fullscreen.active?t.style.paddingBottom=null:this.media.style.transform=`translateY(-${i}%)`}else this.isHTML5&&t.classList.add(this.config.classNames.videoFixedRatio);return{padding:a,ratio:i}}function he(e,t,i=.05){const s=e/t,n=ne(Object.keys(le),s);return Math.abs(n-s)<=i?le[n]:[e,t]}const de={getSources(){if(!this.isHTML5)return[];return Array.from(this.media.querySelectorAll("source")).filter((e=>{const t=e.getAttribute("type");return!!S.empty(t)||K.mime.call(this,t)}))},getQualityOptions(){return this.config.quality.forced?this.config.quality.options:de.getSources.call(this).map((e=>Number(e.getAttribute("size")))).filter(Boolean)},setup(){if(!this.isHTML5)return;const e=this;e.options.speed=e.config.speed.options,S.empty(this.config.ratio)||ue.call(e),Object.defineProperty(e.media,"quality",{get(){const t=de.getSources.call(e).find((t=>t.getAttribute("src")===e.source));return t&&Number(t.getAttribute("size"))},set(t){if(e.quality!==t){if(e.config.quality.forced&&S.function(e.config.quality.onChange))e.config.quality.onChange(t);else{const i=de.getSources.call(e).find((e=>Number(e.getAttribute("size"))===t));if(!i)return;const{currentTime:s,paused:n,preload:a,readyState:l,playbackRate:r}=e.media;e.media.src=i.getAttribute("src"),("none"!==a||l)&&(e.once("loadedmetadata",(()=>{e.speed=r,e.currentTime=s,n||ie(e.play())})),e.media.load())}Z.call(e,e.media,"qualitychange",!1,{quality:t})}}})},cancelRequests(){this.isHTML5&&(O(de.getSources.call(this)),this.media.setAttribute("src",this.config.blankVideo),this.media.load(),this.debug.log("Cancelled network requests"))}};function me(e,...t){return S.empty(e)?e:e.toString().replace(/{(\d+)}/g,((e,i)=>t[i].toString()))}const pe=(e="",t="",i="")=>e.replace(new RegExp(t.toString().replace(/([.*+?^=!:${}()|[\]/\\])/g,"\\$1"),"g"),i.toString()),ge=(e="")=>e.toString().replace(/\w\S*/g,(e=>e.charAt(0).toUpperCase()+e.slice(1).toLowerCase()));function fe(e=""){let t=e.toString();return t=function(e=""){let t=e.toString();return t=pe(t,"-"," "),t=pe(t,"_"," "),t=ge(t),pe(t," ","")}(t),t.charAt(0).toLowerCase()+t.slice(1)}function ye(e){const t=document.createElement("div");return t.appendChild(e),t.innerHTML}const be={pip:"PIP",airplay:"AirPlay",html5:"HTML5",vimeo:"Vimeo",youtube:"YouTube"},ve={get(e="",t={}){if(S.empty(e)||S.empty(t))return"";let i=N(t.i18n,e);if(S.empty(i))return Object.keys(be).includes(e)?be[e]:"";const s={"{seektime}":t.seekTime,"{title}":t.title};return Object.entries(s).forEach((([e,t])=>{i=pe(i,e,t)})),i}};class we{constructor(t){e(this,"get",(e=>{if(!we.supported||!this.enabled)return null;const t=window.localStorage.getItem(this.key);if(S.empty(t))return null;const i=JSON.parse(t);return S.string(e)&&e.length?i[e]:i})),e(this,"set",(e=>{if(!we.supported||!this.enabled)return;if(!S.object(e))return;let t=this.get();S.empty(t)&&(t={}),x(t,e);try{window.localStorage.setItem(this.key,JSON.stringify(t))}catch(e){}})),this.enabled=t.config.storage.enabled,this.key=t.config.storage.key}static get supported(){try{if(!("localStorage"in window))return!1;const e="___test";return window.localStorage.setItem(e,e),window.localStorage.removeItem(e),!0}catch(e){return!1}}}function Te(e,t="text"){return new Promise(((i,s)=>{try{const s=new XMLHttpRequest;if(!("withCredentials"in s))return;s.addEventListener("load",(()=>{if("text"===t)try{i(JSON.parse(s.responseText))}catch(e){i(s.responseText)}else i(s.response)})),s.addEventListener("error",(()=>{throw new Error(s.status)})),s.open("GET",e,!0),s.responseType=t,s.send()}catch(e){s(e)}}))}function ke(e,t){if(!S.string(e))return;const i="cache",s=S.string(t);let n=!1;const a=()=>null!==document.getElementById(t),l=(e,t)=>{e.innerHTML=t,s&&a()||document.body.insertAdjacentElement("afterbegin",e)};if(!s||!a()){const a=we.supported,r=document.createElement("div");if(r.setAttribute("hidden",""),s&&r.setAttribute("id",t),a){const e=window.localStorage.getItem(`${i}-${t}`);if(n=null!==e,n){const t=JSON.parse(e);l(r,t.content)}}Te(e).then((e=>{if(!S.empty(e)){if(a)try{window.localStorage.setItem(`${i}-${t}`,JSON.stringify({content:e}))}catch(e){}l(r,e)}})).catch((()=>{}))}}const Ce=e=>Math.trunc(e/60/60%60,10),Ae=e=>Math.trunc(e/60%60,10),Se=e=>Math.trunc(e%60,10);function Ee(e=0,t=!1,i=!1){if(!S.number(e))return Ee(void 0,t,i);const s=e=>`0${e}`.slice(-2);let n=Ce(e);const a=Ae(e),l=Se(e);return n=t||n>0?`${n}:`:"",`${i&&e>0?"-":""}${n}${s(a)}:${s(l)}`}const Pe={getIconUrl(){const e=new URL(this.config.iconUrl,window.location),t=window.location.host?window.location.host:window.top.location.host,i=e.host!==t||M.isIE&&!window.svg4everybody;return{url:this.config.iconUrl,cors:i}},findElements(){try{return this.elements.controls=B.call(this,this.config.selectors.controls.wrapper),this.elements.buttons={play:U.call(this,this.config.selectors.buttons.play),pause:B.call(this,this.config.selectors.buttons.pause),restart:B.call(this,this.config.selectors.buttons.restart),rewind:B.call(this,this.config.selectors.buttons.rewind),fastForward:B.call(this,this.config.selectors.buttons.fastForward),mute:B.call(this,this.config.selectors.buttons.mute),pip:B.call(this,this.config.selectors.buttons.pip),airplay:B.call(this,this.config.selectors.buttons.airplay),settings:B.call(this,this.config.selectors.buttons.settings),captions:B.call(this,this.config.selectors.buttons.captions),fullscreen:B.call(this,this.config.selectors.buttons.fullscreen)},this.elements.progress=B.call(this,this.config.selectors.progress),this.elements.inputs={seek:B.call(this,this.config.selectors.inputs.seek),volume:B.call(this,this.config.selectors.inputs.volume)},this.elements.display={buffer:B.call(this,this.config.selectors.display.buffer),currentTime:B.call(this,this.config.selectors.display.currentTime),duration:B.call(this,this.config.selectors.display.duration)},S.element(this.elements.progress)&&(this.elements.display.seekTooltip=this.elements.progress.querySelector(`.${this.config.classNames.tooltip}`)),!0}catch(e){return this.debug.warn("It looks like there is a problem with your custom controls HTML",e),this.toggleNativeControls(!0),!1}},createIcon(e,t){const i="http://www.w3.org/2000/svg",s=Pe.getIconUrl.call(this),n=`${s.cors?"":s.url}#${this.config.iconPrefix}`,a=document.createElementNS(i,"svg");I(a,x(t,{"aria-hidden":"true",focusable:"false"}));const l=document.createElementNS(i,"use"),r=`${n}-${e}`;return"href"in l&&l.setAttributeNS("http://www.w3.org/1999/xlink","href",r),l.setAttributeNS("http://www.w3.org/1999/xlink","xlink:href",r),a.appendChild(l),a},createLabel(e,t={}){const i=ve.get(e,this.config);return $("span",{...t,class:[t.class,this.config.classNames.hidden].filter(Boolean).join(" ")},i)},createBadge(e){if(S.empty(e))return null;const t=$("span",{class:this.config.classNames.menu.value});return t.appendChild($("span",{class:this.config.classNames.menu.badge},e)),t},createButton(e,t){const i=x({},t);let s=fe(e);const n={element:"button",toggle:!1,label:null,icon:null,labelPressed:null,iconPressed:null};switch(["element","icon","label"].forEach((e=>{Object.keys(i).includes(e)&&(n[e]=i[e],delete i[e])})),"button"!==n.element||Object.keys(i).includes("type")||(i.type="button"),Object.keys(i).includes("class")?i.class.split(" ").some((e=>e===this.config.classNames.control))||x(i,{class:`${i.class} ${this.config.classNames.control}`}):i.class=this.config.classNames.control,e){case"play":n.toggle=!0,n.label="play",n.labelPressed="pause",n.icon="play",n.iconPressed="pause";break;case"mute":n.toggle=!0,n.label="mute",n.labelPressed="unmute",n.icon="volume",n.iconPressed="muted";break;case"captions":n.toggle=!0,n.label="enableCaptions",n.labelPressed="disableCaptions",n.icon="captions-off",n.iconPressed="captions-on";break;case"fullscreen":n.toggle=!0,n.label="enterFullscreen",n.labelPressed="exitFullscreen",n.icon="enter-fullscreen",n.iconPressed="exit-fullscreen";break;case"play-large":i.class+=` ${this.config.classNames.control}--overlaid`,s="play",n.label="play",n.icon="play";break;default:S.empty(n.label)&&(n.label=s),S.empty(n.icon)&&(n.icon=e)}const a=$(n.element);return n.toggle?(a.appendChild(Pe.createIcon.call(this,n.iconPressed,{class:"icon--pressed"})),a.appendChild(Pe.createIcon.call(this,n.icon,{class:"icon--not-pressed"})),a.appendChild(Pe.createLabel.call(this,n.labelPressed,{class:"label--pressed"})),a.appendChild(Pe.createLabel.call(this,n.label,{class:"label--not-pressed"}))):(a.appendChild(Pe.createIcon.call(this,n.icon)),a.appendChild(Pe.createLabel.call(this,n.label))),x(i,D(this.config.selectors.buttons[s],i)),I(a,i),"play"===s?(S.array(this.elements.buttons[s])||(this.elements.buttons[s]=[]),this.elements.buttons[s].push(a)):this.elements.buttons[s]=a,a},createRange(e,t){const i=$("input",x(D(this.config.selectors.inputs[e]),{type:"range",min:0,max:100,step:.01,value:0,autocomplete:"off",role:"slider","aria-label":ve.get(e,this.config),"aria-valuemin":0,"aria-valuemax":100,"aria-valuenow":0},t));return this.elements.inputs[e]=i,Pe.updateRangeFill.call(this,i),g.setup(i),i},createProgress(e,t){const i=$("progress",x(D(this.config.selectors.display[e]),{min:0,max:100,value:0,role:"progressbar","aria-hidden":!0},t));if("volume"!==e){i.appendChild($("span",null,"0"));const t={played:"played",buffer:"buffered"}[e],s=t?ve.get(t,this.config):"";i.innerText=`% ${s.toLowerCase()}`}return this.elements.display[e]=i,i},createTime(e,t){const i=D(this.config.selectors.display[e],t),s=$("div",x(i,{class:`${i.class?i.class:""} ${this.config.classNames.display.time} `.trim(),"aria-label":ve.get(e,this.config),role:"timer"}),"00:00");return this.elements.display[e]=s,s},bindMenuItemShortcuts(e,t){X.call(this,e,"keydown keyup",(i=>{if(![" ","ArrowUp","ArrowDown","ArrowRight"].includes(i.key))return;if(i.preventDefault(),i.stopPropagation(),"keydown"===i.type)return;const s=V(e,'[role="menuitemradio"]');if(!s&&[" ","ArrowRight"].includes(i.key))Pe.showMenuPanel.call(this,t,!0);else{let t;" "!==i.key&&("ArrowDown"===i.key||s&&"ArrowRight"===i.key?(t=e.nextElementSibling,S.element(t)||(t=e.parentNode.firstElementChild)):(t=e.previousElementSibling,S.element(t)||(t=e.parentNode.lastElementChild)),W.call(this,t,!0))}}),!1),X.call(this,e,"keyup",(e=>{"Return"===e.key&&Pe.focusFirstMenuItem.call(this,null,!0)}))},createMenuItem({value:e,list:t,type:i,title:s,badge:n=null,checked:a=!1}){const l=D(this.config.selectors.inputs[i]),r=$("button",x(l,{type:"button",role:"menuitemradio",class:`${this.config.classNames.control} ${l.class?l.class:""}`.trim(),"aria-checked":a,value:e})),o=$("span");o.innerHTML=s,S.element(n)&&o.appendChild(n),r.appendChild(o),Object.defineProperty(r,"checked",{enumerable:!0,get:()=>"true"===r.getAttribute("aria-checked"),set(e){e&&Array.from(r.parentNode.children).filter((e=>V(e,'[role="menuitemradio"]'))).forEach((e=>e.setAttribute("aria-checked","false"))),r.setAttribute("aria-checked",e?"true":"false")}}),this.listeners.bind(r,"click keyup",(t=>{if(!S.keyboardEvent(t)||" "===t.key){switch(t.preventDefault(),t.stopPropagation(),r.checked=!0,i){case"language":this.currentTrack=Number(e);break;case"quality":this.quality=e;break;case"speed":this.speed=parseFloat(e)}Pe.showMenuPanel.call(this,"home",S.keyboardEvent(t))}}),i,!1),Pe.bindMenuItemShortcuts.call(this,r,i),t.appendChild(r)},formatTime(e=0,t=!1){if(!S.number(e))return e;return Ee(e,Ce(this.duration)>0,t)},updateTimeDisplay(e=null,t=0,i=!1){S.element(e)&&S.number(t)&&(e.innerText=Pe.formatTime(t,i))},updateVolume(){this.supported.ui&&(S.element(this.elements.inputs.volume)&&Pe.setRange.call(this,this.elements.inputs.volume,this.muted?0:this.volume),S.element(this.elements.buttons.mute)&&(this.elements.buttons.mute.pressed=this.muted||0===this.volume))},setRange(e,t=0){S.element(e)&&(e.value=t,Pe.updateRangeFill.call(this,e))},updateProgress(e){if(!this.supported.ui||!S.event(e))return;let t=0;const i=(e,t)=>{const i=S.number(t)?t:0,s=S.element(e)?e:this.elements.display.buffer;if(S.element(s)){s.value=i;const e=s.getElementsByTagName("span")[0];S.element(e)&&(e.childNodes[0].nodeValue=i)}};if(e)switch(e.type){case"timeupdate":case"seeking":case"seeked":s=this.currentTime,n=this.duration,t=0===s||0===n||Number.isNaN(s)||Number.isNaN(n)?0:(s/n*100).toFixed(2),"timeupdate"===e.type&&Pe.setRange.call(this,this.elements.inputs.seek,t);break;case"playing":case"progress":i(this.elements.display.buffer,100*this.buffered)}var s,n},updateRangeFill(e){const t=S.event(e)?e.target:e;if(S.element(t)&&"range"===t.getAttribute("type")){if(V(t,this.config.selectors.inputs.seek)){t.setAttribute("aria-valuenow",this.currentTime);const e=Pe.formatTime(this.currentTime),i=Pe.formatTime(this.duration),s=ve.get("seekLabel",this.config);t.setAttribute("aria-valuetext",s.replace("{currentTime}",e).replace("{duration}",i))}else if(V(t,this.config.selectors.inputs.volume)){const e=100*t.value;t.setAttribute("aria-valuenow",e),t.setAttribute("aria-valuetext",`${e.toFixed(1)}%`)}else t.setAttribute("aria-valuenow",t.value);(M.isWebKit||M.isIPadOS)&&t.style.setProperty("--value",t.value/t.max*100+"%")}},updateSeekTooltip(e){var t,i;if(!this.config.tooltips.seek||!S.element(this.elements.inputs.seek)||!S.element(this.elements.display.seekTooltip)||0===this.duration)return;const s=this.elements.display.seekTooltip,n=`${this.config.classNames.tooltip}--visible`,a=e=>R(s,n,e);if(this.touch)return void a(!1);let l=0;const r=this.elements.progress.getBoundingClientRect();if(S.event(e))l=100/r.width*(e.pageX-r.left);else{if(!F(s,n))return;l=parseFloat(s.style.left,10)}l<0?l=0:l>100&&(l=100);const o=this.duration/100*l;s.innerText=Pe.formatTime(o);const c=null===(t=this.config.markers)||void 0===t||null===(i=t.points)||void 0===i?void 0:i.find((({time:e})=>e===Math.round(o)));c&&s.insertAdjacentHTML("afterbegin",`${c.label}
`),s.style.left=`${l}%`,S.event(e)&&["mouseenter","mouseleave"].includes(e.type)&&a("mouseenter"===e.type)},timeUpdate(e){const t=!S.element(this.elements.display.duration)&&this.config.invertTime;Pe.updateTimeDisplay.call(this,this.elements.display.currentTime,t?this.duration-this.currentTime:this.currentTime,t),e&&"timeupdate"===e.type&&this.media.seeking||Pe.updateProgress.call(this,e)},durationUpdate(){if(!this.supported.ui||!this.config.invertTime&&this.currentTime)return;if(this.duration>=2**32)return H(this.elements.display.currentTime,!0),void H(this.elements.progress,!0);S.element(this.elements.inputs.seek)&&this.elements.inputs.seek.setAttribute("aria-valuemax",this.duration);const e=S.element(this.elements.display.duration);!e&&this.config.displayDuration&&this.paused&&Pe.updateTimeDisplay.call(this,this.elements.display.currentTime,this.duration),e&&Pe.updateTimeDisplay.call(this,this.elements.display.duration,this.duration),this.config.markers.enabled&&Pe.setMarkers.call(this),Pe.updateSeekTooltip.call(this)},toggleMenuButton(e,t){H(this.elements.settings.buttons[e],!t)},updateSetting(e,t,i){const s=this.elements.settings.panels[e];let n=null,a=t;if("captions"===e)n=this.currentTrack;else{if(n=S.empty(i)?this[e]:i,S.empty(n)&&(n=this.config[e].default),!S.empty(this.options[e])&&!this.options[e].includes(n))return void this.debug.warn(`Unsupported value of '${n}' for ${e}`);if(!this.config[e].options.includes(n))return void this.debug.warn(`Disabled value of '${n}' for ${e}`)}if(S.element(a)||(a=s&&s.querySelector('[role="menu"]')),!S.element(a))return;this.elements.settings.buttons[e].querySelector(`.${this.config.classNames.menu.value}`).innerHTML=Pe.getLabel.call(this,e,n);const l=a&&a.querySelector(`[value="${n}"]`);S.element(l)&&(l.checked=!0)},getLabel(e,t){switch(e){case"speed":return 1===t?ve.get("normal",this.config):`${t}×`;case"quality":if(S.number(t)){const e=ve.get(`qualityLabel.${t}`,this.config);return e.length?e:`${t}p`}return ge(t);case"captions":return xe.getLabel.call(this);default:return null}},setQualityMenu(e){if(!S.element(this.elements.settings.panels.quality))return;const t="quality",i=this.elements.settings.panels.quality.querySelector('[role="menu"]');S.array(e)&&(this.options.quality=se(e).filter((e=>this.config.quality.options.includes(e))));const s=!S.empty(this.options.quality)&&this.options.quality.length>1;if(Pe.toggleMenuButton.call(this,t,s),j(i),Pe.checkMenu.call(this),!s)return;const n=e=>{const t=ve.get(`qualityBadge.${e}`,this.config);return t.length?Pe.createBadge.call(this,t):null};this.options.quality.sort(((e,t)=>{const i=this.config.quality.options;return i.indexOf(e)>i.indexOf(t)?1:-1})).forEach((e=>{Pe.createMenuItem.call(this,{value:e,list:i,type:t,title:Pe.getLabel.call(this,"quality",e),badge:n(e)})})),Pe.updateSetting.call(this,t,i)},setCaptionsMenu(){if(!S.element(this.elements.settings.panels.captions))return;const e="captions",t=this.elements.settings.panels.captions.querySelector('[role="menu"]'),i=xe.getTracks.call(this),s=Boolean(i.length);if(Pe.toggleMenuButton.call(this,e,s),j(t),Pe.checkMenu.call(this),!s)return;const n=i.map(((e,i)=>({value:i,checked:this.captions.toggled&&this.currentTrack===i,title:xe.getLabel.call(this,e),badge:e.language&&Pe.createBadge.call(this,e.language.toUpperCase()),list:t,type:"language"})));n.unshift({value:-1,checked:!this.captions.toggled,title:ve.get("disabled",this.config),list:t,type:"language"}),n.forEach(Pe.createMenuItem.bind(this)),Pe.updateSetting.call(this,e,t)},setSpeedMenu(){if(!S.element(this.elements.settings.panels.speed))return;const e="speed",t=this.elements.settings.panels.speed.querySelector('[role="menu"]');this.options.speed=this.options.speed.filter((e=>e>=this.minimumSpeed&&e<=this.maximumSpeed));const i=!S.empty(this.options.speed)&&this.options.speed.length>1;Pe.toggleMenuButton.call(this,e,i),j(t),Pe.checkMenu.call(this),i&&(this.options.speed.forEach((i=>{Pe.createMenuItem.call(this,{value:i,list:t,type:e,title:Pe.getLabel.call(this,"speed",i)})})),Pe.updateSetting.call(this,e,t))},checkMenu(){const{buttons:e}=this.elements.settings,t=!S.empty(e)&&Object.values(e).some((e=>!e.hidden));H(this.elements.settings.menu,!t)},focusFirstMenuItem(e,t=!1){if(this.elements.settings.popup.hidden)return;let i=e;S.element(i)||(i=Object.values(this.elements.settings.panels).find((e=>!e.hidden)));const s=i.querySelector('[role^="menuitem"]');W.call(this,s,t)},toggleMenu(e){const{popup:t}=this.elements.settings,i=this.elements.buttons.settings;if(!S.element(t)||!S.element(i))return;const{hidden:s}=t;let n=s;if(S.boolean(e))n=e;else if(S.keyboardEvent(e)&&"Escape"===e.key)n=!1;else if(S.event(e)){const s=S.function(e.composedPath)?e.composedPath()[0]:e.target,a=t.contains(s);if(a||!a&&e.target!==i&&n)return}i.setAttribute("aria-expanded",n),H(t,!n),R(this.elements.container,this.config.classNames.menu.open,n),n&&S.keyboardEvent(e)?Pe.focusFirstMenuItem.call(this,null,!0):n||s||W.call(this,i,S.keyboardEvent(e))},getMenuSize(e){const t=e.cloneNode(!0);t.style.position="absolute",t.style.opacity=0,t.removeAttribute("hidden"),e.parentNode.appendChild(t);const i=t.scrollWidth,s=t.scrollHeight;return O(t),{width:i,height:s}},showMenuPanel(e="",t=!1){const i=this.elements.container.querySelector(`#plyr-settings-${this.id}-${e}`);if(!S.element(i))return;const s=i.parentNode,n=Array.from(s.children).find((e=>!e.hidden));if(K.transitions&&!K.reducedMotion){s.style.width=`${n.scrollWidth}px`,s.style.height=`${n.scrollHeight}px`;const e=Pe.getMenuSize.call(this,i),t=e=>{e.target===s&&["width","height"].includes(e.propertyName)&&(s.style.width="",s.style.height="",J.call(this,s,E,t))};X.call(this,s,E,t),s.style.width=`${e.width}px`,s.style.height=`${e.height}px`}H(n,!0),H(i,!1),Pe.focusFirstMenuItem.call(this,i,t)},setDownloadUrl(){const e=this.elements.buttons.download;S.element(e)&&e.setAttribute("href",this.download)},create(e){const{bindMenuItemShortcuts:t,createButton:i,createProgress:s,createRange:n,createTime:a,setQualityMenu:l,setSpeedMenu:r,showMenuPanel:o}=Pe;this.elements.controls=null,S.array(this.config.controls)&&this.config.controls.includes("play-large")&&this.elements.container.appendChild(i.call(this,"play-large"));const c=$("div",D(this.config.selectors.controls.wrapper));this.elements.controls=c;const u={class:"plyr__controls__item"};return se(S.array(this.config.controls)?this.config.controls:[]).forEach((l=>{if("restart"===l&&c.appendChild(i.call(this,"restart",u)),"rewind"===l&&c.appendChild(i.call(this,"rewind",u)),"play"===l&&c.appendChild(i.call(this,"play",u)),"fast-forward"===l&&c.appendChild(i.call(this,"fast-forward",u)),"progress"===l){const t=$("div",{class:`${u.class} plyr__progress__container`}),i=$("div",D(this.config.selectors.progress));if(i.appendChild(n.call(this,"seek",{id:`plyr-seek-${e.id}`})),i.appendChild(s.call(this,"buffer")),this.config.tooltips.seek){const e=$("span",{class:this.config.classNames.tooltip},"00:00");i.appendChild(e),this.elements.display.seekTooltip=e}this.elements.progress=i,t.appendChild(this.elements.progress),c.appendChild(t)}if("current-time"===l&&c.appendChild(a.call(this,"currentTime",u)),"duration"===l&&c.appendChild(a.call(this,"duration",u)),"mute"===l||"volume"===l){let{volume:t}=this.elements;if(S.element(t)&&c.contains(t)||(t=$("div",x({},u,{class:`${u.class} plyr__volume`.trim()})),this.elements.volume=t,c.appendChild(t)),"mute"===l&&t.appendChild(i.call(this,"mute")),"volume"===l&&!M.isIos&&!M.isIPadOS){const i={max:1,step:.05,value:this.config.volume};t.appendChild(n.call(this,"volume",x(i,{id:`plyr-volume-${e.id}`})))}}if("captions"===l&&c.appendChild(i.call(this,"captions",u)),"settings"===l&&!S.empty(this.config.settings)){const s=$("div",x({},u,{class:`${u.class} plyr__menu`.trim(),hidden:""}));s.appendChild(i.call(this,"settings",{"aria-haspopup":!0,"aria-controls":`plyr-settings-${e.id}`,"aria-expanded":!1}));const n=$("div",{class:"plyr__menu__container",id:`plyr-settings-${e.id}`,hidden:""}),a=$("div"),l=$("div",{id:`plyr-settings-${e.id}-home`}),r=$("div",{role:"menu"});l.appendChild(r),a.appendChild(l),this.elements.settings.panels.home=l,this.config.settings.forEach((i=>{const s=$("button",x(D(this.config.selectors.buttons.settings),{type:"button",class:`${this.config.classNames.control} ${this.config.classNames.control}--forward`,role:"menuitem","aria-haspopup":!0,hidden:""}));t.call(this,s,i),X.call(this,s,"click",(()=>{o.call(this,i,!1)}));const n=$("span",null,ve.get(i,this.config)),l=$("span",{class:this.config.classNames.menu.value});l.innerHTML=e[i],n.appendChild(l),s.appendChild(n),r.appendChild(s);const c=$("div",{id:`plyr-settings-${e.id}-${i}`,hidden:""}),u=$("button",{type:"button",class:`${this.config.classNames.control} ${this.config.classNames.control}--back`});u.appendChild($("span",{"aria-hidden":!0},ve.get(i,this.config))),u.appendChild($("span",{class:this.config.classNames.hidden},ve.get("menuBack",this.config))),X.call(this,c,"keydown",(e=>{"ArrowLeft"===e.key&&(e.preventDefault(),e.stopPropagation(),o.call(this,"home",!0))}),!1),X.call(this,u,"click",(()=>{o.call(this,"home",!1)})),c.appendChild(u),c.appendChild($("div",{role:"menu"})),a.appendChild(c),this.elements.settings.buttons[i]=s,this.elements.settings.panels[i]=c})),n.appendChild(a),s.appendChild(n),c.appendChild(s),this.elements.settings.popup=n,this.elements.settings.menu=s}if("pip"===l&&K.pip&&c.appendChild(i.call(this,"pip",u)),"airplay"===l&&K.airplay&&c.appendChild(i.call(this,"airplay",u)),"download"===l){const e=x({},u,{element:"a",href:this.download,target:"_blank"});this.isHTML5&&(e.download="");const{download:t}=this.config.urls;!S.url(t)&&this.isEmbed&&x(e,{icon:`logo-${this.provider}`,label:this.provider}),c.appendChild(i.call(this,"download",e))}"fullscreen"===l&&c.appendChild(i.call(this,"fullscreen",u))})),this.isHTML5&&l.call(this,de.getQualityOptions.call(this)),r.call(this),c},inject(){if(this.config.loadSprite){const e=Pe.getIconUrl.call(this);e.cors&&ke(e.url,"sprite-plyr")}this.id=Math.floor(1e4*Math.random());let e=null;this.elements.controls=null;const t={id:this.id,seektime:this.config.seekTime,title:this.config.title};let i=!0;S.function(this.config.controls)&&(this.config.controls=this.config.controls.call(this,t)),this.config.controls||(this.config.controls=[]),S.element(this.config.controls)||S.string(this.config.controls)?e=this.config.controls:(e=Pe.create.call(this,{id:this.id,seektime:this.config.seekTime,speed:this.speed,quality:this.quality,captions:xe.getLabel.call(this)}),i=!1);let s;i&&S.string(this.config.controls)&&(e=(e=>{let i=e;return Object.entries(t).forEach((([e,t])=>{i=pe(i,`{${e}}`,t)})),i})(e)),S.string(this.config.selectors.controls.container)&&(s=document.querySelector(this.config.selectors.controls.container)),S.element(s)||(s=this.elements.container);if(s[S.element(e)?"insertAdjacentElement":"insertAdjacentHTML"]("afterbegin",e),S.element(this.elements.controls)||Pe.findElements.call(this),!S.empty(this.elements.buttons)){const e=e=>{const t=this.config.classNames.controlPressed;e.setAttribute("aria-pressed","false"),Object.defineProperty(e,"pressed",{configurable:!0,enumerable:!0,get:()=>F(e,t),set(i=!1){R(e,t,i),e.setAttribute("aria-pressed",i?"true":"false")}})};Object.values(this.elements.buttons).filter(Boolean).forEach((t=>{S.array(t)||S.nodeList(t)?Array.from(t).filter(Boolean).forEach(e):e(t)}))}if(M.isEdge&&P(s),this.config.tooltips.controls){const{classNames:e,selectors:t}=this.config,i=`${t.controls.wrapper} ${t.labels} .${e.hidden}`,s=U.call(this,i);Array.from(s).forEach((e=>{R(e,this.config.classNames.hidden,!1),R(e,this.config.classNames.tooltip,!0)}))}},setMediaMetadata(){try{"mediaSession"in navigator&&(navigator.mediaSession.metadata=new window.MediaMetadata({title:this.config.mediaMetadata.title,artist:this.config.mediaMetadata.artist,album:this.config.mediaMetadata.album,artwork:this.config.mediaMetadata.artwork}))}catch(e){}},setMarkers(){var e,t;if(!this.duration||this.elements.markers)return;const i=null===(e=this.config.markers)||void 0===e||null===(t=e.points)||void 0===t?void 0:t.filter((({time:e})=>e>0&&eR(a,l,e);i.forEach((e=>{const t=$("span",{class:this.config.classNames.marker},""),i=e.time/this.duration*100+"%";a&&(t.addEventListener("mouseenter",(()=>{e.label||(a.style.left=i,a.innerHTML=e.label,r(!0))})),t.addEventListener("mouseleave",(()=>{r(!1)}))),t.addEventListener("click",(()=>{this.currentTime=e.time})),t.style.left=i,n.appendChild(t)})),s.appendChild(n),this.config.tooltips.seek||(a=$("span",{class:this.config.classNames.tooltip},""),s.appendChild(a)),this.elements.markers={points:n,tip:a},this.elements.progress.appendChild(s)}};function Me(e,t=!0){let i=e;if(t){const e=document.createElement("a");e.href=i,i=e.href}try{return new URL(i)}catch(e){return null}}function Ne(e){const t=new URLSearchParams;return S.object(e)&&Object.entries(e).forEach((([e,i])=>{t.set(e,i)})),t}const xe={setup(){if(!this.supported.ui)return;if(!this.isVideo||this.isYouTube||this.isHTML5&&!K.textTracks)return void(S.array(this.config.controls)&&this.config.controls.includes("settings")&&this.config.settings.includes("captions")&&Pe.setCaptionsMenu.call(this));var e,t;if(S.element(this.elements.captions)||(this.elements.captions=$("div",D(this.config.selectors.captions)),this.elements.captions.setAttribute("dir","auto"),e=this.elements.captions,t=this.elements.wrapper,S.element(e)&&S.element(t)&&t.parentNode.insertBefore(e,t.nextSibling)),M.isIE&&window.URL){const e=this.media.querySelectorAll("track");Array.from(e).forEach((e=>{const t=e.getAttribute("src"),i=Me(t);null!==i&&i.hostname!==window.location.href.hostname&&["http:","https:"].includes(i.protocol)&&Te(t,"blob").then((t=>{e.setAttribute("src",window.URL.createObjectURL(t))})).catch((()=>{O(e)}))}))}const i=se((navigator.languages||[navigator.language||navigator.userLanguage||"en"]).map((e=>e.split("-")[0])));let s=(this.storage.get("language")||this.config.captions.language||"auto").toLowerCase();"auto"===s&&([s]=i);let n=this.storage.get("captions");if(S.boolean(n)||({active:n}=this.config.captions),Object.assign(this.captions,{toggled:!1,active:n,language:s,languages:i}),this.isHTML5){const e=this.config.captions.update?"addtrack removetrack":"removetrack";X.call(this,this.media.textTracks,e,xe.update.bind(this))}setTimeout(xe.update.bind(this),0)},update(){const e=xe.getTracks.call(this,!0),{active:t,language:i,meta:s,currentTrackNode:n}=this.captions,a=Boolean(e.find((e=>e.language===i)));this.isHTML5&&this.isVideo&&e.filter((e=>!s.get(e))).forEach((e=>{this.debug.log("Track added",e),s.set(e,{default:"showing"===e.mode}),"showing"===e.mode&&(e.mode="hidden"),X.call(this,e,"cuechange",(()=>xe.updateCues.call(this)))})),(a&&this.language!==i||!e.includes(n))&&(xe.setLanguage.call(this,i),xe.toggle.call(this,t&&a)),this.elements&&R(this.elements.container,this.config.classNames.captions.enabled,!S.empty(e)),S.array(this.config.controls)&&this.config.controls.includes("settings")&&this.config.settings.includes("captions")&&Pe.setCaptionsMenu.call(this)},toggle(e,t=!0){if(!this.supported.ui)return;const{toggled:i}=this.captions,s=this.config.classNames.captions.active,n=S.nullOrUndefined(e)?!i:e;if(n!==i){if(t||(this.captions.active=n,this.storage.set({captions:n})),!this.language&&n&&!t){const e=xe.getTracks.call(this),t=xe.findTrack.call(this,[this.captions.language,...this.captions.languages],!0);return this.captions.language=t.language,void xe.set.call(this,e.indexOf(t))}this.elements.buttons.captions&&(this.elements.buttons.captions.pressed=n),R(this.elements.container,s,n),this.captions.toggled=n,Pe.updateSetting.call(this,"captions"),Z.call(this,this.media,n?"captionsenabled":"captionsdisabled")}setTimeout((()=>{n&&this.captions.toggled&&(this.captions.currentTrackNode.mode="hidden")}))},set(e,t=!0){const i=xe.getTracks.call(this);if(-1!==e)if(S.number(e))if(e in i){if(this.captions.currentTrack!==e){this.captions.currentTrack=e;const s=i[e],{language:n}=s||{};this.captions.currentTrackNode=s,Pe.updateSetting.call(this,"captions"),t||(this.captions.language=n,this.storage.set({language:n})),this.isVimeo&&this.embed.enableTextTrack(n),Z.call(this,this.media,"languagechange")}xe.toggle.call(this,!0,t),this.isHTML5&&this.isVideo&&xe.updateCues.call(this)}else this.debug.warn("Track not found",e);else this.debug.warn("Invalid caption argument",e);else xe.toggle.call(this,!1,t)},setLanguage(e,t=!0){if(!S.string(e))return void this.debug.warn("Invalid language argument",e);const i=e.toLowerCase();this.captions.language=i;const s=xe.getTracks.call(this),n=xe.findTrack.call(this,[i]);xe.set.call(this,s.indexOf(n),t)},getTracks(e=!1){return Array.from((this.media||{}).textTracks||[]).filter((t=>!this.isHTML5||e||this.captions.meta.has(t))).filter((e=>["captions","subtitles"].includes(e.kind)))},findTrack(e,t=!1){const i=xe.getTracks.call(this),s=e=>Number((this.captions.meta.get(e)||{}).default),n=Array.from(i).sort(((e,t)=>s(t)-s(e)));let a;return e.every((e=>(a=n.find((t=>t.language===e)),!a))),a||(t?n[0]:void 0)},getCurrentTrack(){return xe.getTracks.call(this)[this.currentTrack]},getLabel(e){let t=e;return!S.track(t)&&K.textTracks&&this.captions.toggled&&(t=xe.getCurrentTrack.call(this)),S.track(t)?S.empty(t.label)?S.empty(t.language)?ve.get("enabled",this.config):e.language.toUpperCase():t.label:ve.get("disabled",this.config)},updateCues(e){if(!this.supported.ui)return;if(!S.element(this.elements.captions))return void this.debug.warn("No captions element to render to");if(!S.nullOrUndefined(e)&&!Array.isArray(e))return void this.debug.warn("updateCues: Invalid input",e);let t=e;if(!t){const e=xe.getCurrentTrack.call(this);t=Array.from((e||{}).activeCues||[]).map((e=>e.getCueAsHTML())).map(ye)}const i=t.map((e=>e.trim())).join("\n");if(i!==this.elements.captions.innerHTML){j(this.elements.captions);const e=$("span",D(this.config.selectors.caption));e.innerHTML=i,this.elements.captions.appendChild(e),Z.call(this,this.media,"cuechange")}}},Le={enabled:!0,title:"",debug:!1,autoplay:!1,autopause:!0,playsinline:!0,seekTime:10,volume:1,muted:!1,duration:null,displayDuration:!0,invertTime:!0,toggleInvert:!0,ratio:null,clickToPlay:!0,hideControls:!0,resetOnEnd:!1,disableContextMenu:!0,loadSprite:!0,iconPrefix:"plyr",iconUrl:"https://cdn.plyr.io/3.7.8/plyr.svg",blankVideo:"https://cdn.plyr.io/static/blank.mp4",quality:{default:576,options:[4320,2880,2160,1440,1080,720,576,480,360,240],forced:!1,onChange:null},loop:{active:!1},speed:{selected:1,options:[.5,.75,1,1.25,1.5,1.75,2,4]},keyboard:{focused:!0,global:!1},tooltips:{controls:!1,seek:!0},captions:{active:!1,language:"auto",update:!1},fullscreen:{enabled:!0,fallback:!0,iosNative:!1},storage:{enabled:!0,key:"plyr"},controls:["play-large","play","progress","current-time","mute","volume","captions","settings","pip","airplay","fullscreen"],settings:["captions","quality","speed"],i18n:{restart:"Restart",rewind:"Rewind {seektime}s",play:"Play",pause:"Pause",fastForward:"Forward {seektime}s",seek:"Seek",seekLabel:"{currentTime} of {duration}",played:"Played",buffered:"Buffered",currentTime:"Current time",duration:"Duration",volume:"Volume",mute:"Mute",unmute:"Unmute",enableCaptions:"Enable captions",disableCaptions:"Disable captions",download:"Download",enterFullscreen:"Enter fullscreen",exitFullscreen:"Exit fullscreen",frameTitle:"Player for {title}",captions:"Captions",settings:"Settings",pip:"PIP",menuBack:"Go back to previous menu",speed:"Speed",normal:"Normal",quality:"Quality",loop:"Loop",start:"Start",end:"End",all:"All",reset:"Reset",disabled:"Disabled",enabled:"Enabled",advertisement:"Ad",qualityBadge:{2160:"4K",1440:"HD",1080:"HD",720:"HD",576:"SD",480:"SD"}},urls:null,listeners:{seek:null,play:null,pause:null,restart:null,rewind:null,fastForward:null,mute:null,volume:null,captions:null,download:null,fullscreen:null,pip:null,airplay:null,speed:null,quality:null,loop:null,language:null},events:["ended","progress","stalled","playing","waiting","canplay","canplaythrough","loadstart","loadeddata","loadedmetadata","timeupdate","volumechange","play","pause","error","seeking","seeked","emptied","ratechange","cuechange","download","enterfullscreen","exitfullscreen","captionsenabled","captionsdisabled","languagechange","controlshidden","controlsshown","ready","statechange","qualitychange","adsloaded","adscontentpause","adscontentresume","adstarted","adsmidpoint","adscomplete","adsallcomplete","adsimpression","adsclick"],selectors:{editable:"input, textarea, select, [contenteditable]",container:".plyr",controls:{container:null,wrapper:".plyr__controls"},labels:"[data-plyr]",buttons:{play:'[data-plyr="play"]',pause:'[data-plyr="pause"]',restart:'[data-plyr="restart"]',rewind:'[data-plyr="rewind"]',fastForward:'[data-plyr="fast-forward"]',mute:'[data-plyr="mute"]',captions:'[data-plyr="captions"]',download:'[data-plyr="download"]',fullscreen:'[data-plyr="fullscreen"]',pip:'[data-plyr="pip"]',airplay:'[data-plyr="airplay"]',settings:'[data-plyr="settings"]',loop:'[data-plyr="loop"]'},inputs:{seek:'[data-plyr="seek"]',volume:'[data-plyr="volume"]',speed:'[data-plyr="speed"]',language:'[data-plyr="language"]',quality:'[data-plyr="quality"]'},display:{currentTime:".plyr__time--current",duration:".plyr__time--duration",buffer:".plyr__progress__buffer",loop:".plyr__progress__loop",volume:".plyr__volume--display"},progress:".plyr__progress",captions:".plyr__captions",caption:".plyr__caption"},classNames:{type:"plyr--{0}",provider:"plyr--{0}",video:"plyr__video-wrapper",embed:"plyr__video-embed",videoFixedRatio:"plyr__video-wrapper--fixed-ratio",embedContainer:"plyr__video-embed__container",poster:"plyr__poster",posterEnabled:"plyr__poster-enabled",ads:"plyr__ads",control:"plyr__control",controlPressed:"plyr__control--pressed",playing:"plyr--playing",paused:"plyr--paused",stopped:"plyr--stopped",loading:"plyr--loading",hover:"plyr--hover",tooltip:"plyr__tooltip",cues:"plyr__cues",marker:"plyr__progress__marker",hidden:"plyr__sr-only",hideControls:"plyr--hide-controls",isTouch:"plyr--is-touch",uiSupported:"plyr--full-ui",noTransition:"plyr--no-transition",display:{time:"plyr__time"},menu:{value:"plyr__menu__value",badge:"plyr__badge",open:"plyr--menu-open"},captions:{enabled:"plyr--captions-enabled",active:"plyr--captions-active"},fullscreen:{enabled:"plyr--fullscreen-enabled",fallback:"plyr--fullscreen-fallback"},pip:{supported:"plyr--pip-supported",active:"plyr--pip-active"},airplay:{supported:"plyr--airplay-supported",active:"plyr--airplay-active"},previewThumbnails:{thumbContainer:"plyr__preview-thumb",thumbContainerShown:"plyr__preview-thumb--is-shown",imageContainer:"plyr__preview-thumb__image-container",timeContainer:"plyr__preview-thumb__time-container",scrubbingContainer:"plyr__preview-scrubbing",scrubbingContainerShown:"plyr__preview-scrubbing--is-shown"}},attributes:{embed:{provider:"data-plyr-provider",id:"data-plyr-embed-id",hash:"data-plyr-embed-hash"}},ads:{enabled:!1,publisherId:"",tagUrl:""},previewThumbnails:{enabled:!1,src:""},vimeo:{byline:!1,portrait:!1,title:!1,speed:!0,transparent:!1,customControls:!0,referrerPolicy:null,premium:!1},youtube:{rel:0,showinfo:0,iv_load_policy:3,modestbranding:1,customControls:!0,noCookie:!1},mediaMetadata:{title:"",artist:"",album:"",artwork:[]},markers:{enabled:!1,points:[]}},Ie="picture-in-picture",$e="inline",_e={html5:"html5",youtube:"youtube",vimeo:"vimeo"},Oe="audio",je="video";const qe=()=>{};class De{constructor(e=!1){this.enabled=window.console&&e,this.enabled&&this.log("Debugging enabled")}get log(){return this.enabled?Function.prototype.bind.call(console.log,console):qe}get warn(){return this.enabled?Function.prototype.bind.call(console.warn,console):qe}get error(){return this.enabled?Function.prototype.bind.call(console.error,console):qe}}class He{constructor(t){e(this,"onChange",(()=>{if(!this.supported)return;const e=this.player.elements.buttons.fullscreen;S.element(e)&&(e.pressed=this.active);const t=this.target===this.player.media?this.target:this.player.elements.container;Z.call(this.player,t,this.active?"enterfullscreen":"exitfullscreen",!0)})),e(this,"toggleFallback",((e=!1)=>{if(e?this.scrollPosition={x:window.scrollX??0,y:window.scrollY??0}:window.scrollTo(this.scrollPosition.x,this.scrollPosition.y),document.body.style.overflow=e?"hidden":"",R(this.target,this.player.config.classNames.fullscreen.fallback,e),M.isIos){let t=document.head.querySelector('meta[name="viewport"]');const i="viewport-fit=cover";t||(t=document.createElement("meta"),t.setAttribute("name","viewport"));const s=S.string(t.content)&&t.content.includes(i);e?(this.cleanupViewport=!s,s||(t.content+=`,${i}`)):this.cleanupViewport&&(t.content=t.content.split(",").filter((e=>e.trim()!==i)).join(","))}this.onChange()})),e(this,"trapFocus",(e=>{if(M.isIos||M.isIPadOS||!this.active||"Tab"!==e.key)return;const t=document.activeElement,i=U.call(this.player,"a[href], button:not(:disabled), input:not(:disabled), [tabindex]"),[s]=i,n=i[i.length-1];t!==n||e.shiftKey?t===s&&e.shiftKey&&(n.focus(),e.preventDefault()):(s.focus(),e.preventDefault())})),e(this,"update",(()=>{if(this.supported){let e;e=this.forceFallback?"Fallback (forced)":He.nativeSupported?"Native":"Fallback",this.player.debug.log(`${e} fullscreen enabled`)}else this.player.debug.log("Fullscreen not supported and fallback disabled");R(this.player.elements.container,this.player.config.classNames.fullscreen.enabled,this.supported)})),e(this,"enter",(()=>{this.supported&&(M.isIos&&this.player.config.fullscreen.iosNative?this.player.isVimeo?this.player.embed.requestFullscreen():this.target.webkitEnterFullscreen():!He.nativeSupported||this.forceFallback?this.toggleFallback(!0):this.prefix?S.empty(this.prefix)||this.target[`${this.prefix}Request${this.property}`]():this.target.requestFullscreen({navigationUI:"hide"}))})),e(this,"exit",(()=>{if(this.supported)if(M.isIos&&this.player.config.fullscreen.iosNative)this.player.isVimeo?this.player.embed.exitFullscreen():this.target.webkitEnterFullscreen(),ie(this.player.play());else if(!He.nativeSupported||this.forceFallback)this.toggleFallback(!1);else if(this.prefix){if(!S.empty(this.prefix)){const e="moz"===this.prefix?"Cancel":"Exit";document[`${this.prefix}${e}${this.property}`]()}}else(document.cancelFullScreen||document.exitFullscreen).call(document)})),e(this,"toggle",(()=>{this.active?this.exit():this.enter()})),this.player=t,this.prefix=He.prefix,this.property=He.property,this.scrollPosition={x:0,y:0},this.forceFallback="force"===t.config.fullscreen.fallback,this.player.elements.fullscreen=t.config.fullscreen.container&&function(e,t){const{prototype:i}=Element;return(i.closest||function(){let e=this;do{if(V.matches(e,t))return e;e=e.parentElement||e.parentNode}while(null!==e&&1===e.nodeType);return null}).call(e,t)}(this.player.elements.container,t.config.fullscreen.container),X.call(this.player,document,"ms"===this.prefix?"MSFullscreenChange":`${this.prefix}fullscreenchange`,(()=>{this.onChange()})),X.call(this.player,this.player.elements.container,"dblclick",(e=>{S.element(this.player.elements.controls)&&this.player.elements.controls.contains(e.target)||this.player.listeners.proxy(e,this.toggle,"fullscreen")})),X.call(this,this.player.elements.container,"keydown",(e=>this.trapFocus(e))),this.update()}static get nativeSupported(){return!!(document.fullscreenEnabled||document.webkitFullscreenEnabled||document.mozFullScreenEnabled||document.msFullscreenEnabled)}get useNative(){return He.nativeSupported&&!this.forceFallback}static get prefix(){if(S.function(document.exitFullscreen))return"";let e="";return["webkit","moz","ms"].some((t=>!(!S.function(document[`${t}ExitFullscreen`])&&!S.function(document[`${t}CancelFullScreen`]))&&(e=t,!0))),e}static get property(){return"moz"===this.prefix?"FullScreen":"Fullscreen"}get supported(){return[this.player.config.fullscreen.enabled,this.player.isVideo,He.nativeSupported||this.player.config.fullscreen.fallback,!this.player.isYouTube||He.nativeSupported||!M.isIos||this.player.config.playsinline&&!this.player.config.fullscreen.iosNative].every(Boolean)}get active(){if(!this.supported)return!1;if(!He.nativeSupported||this.forceFallback)return F(this.target,this.player.config.classNames.fullscreen.fallback);const e=this.prefix?this.target.getRootNode()[`${this.prefix}${this.property}Element`]:this.target.getRootNode().fullscreenElement;return e&&e.shadowRoot?e===this.target.getRootNode().host:e===this.target}get target(){return M.isIos&&this.player.config.fullscreen.iosNative?this.player.media:this.player.elements.fullscreen??this.player.elements.container}}function Re(e,t=1){return new Promise(((i,s)=>{const n=new Image,a=()=>{delete n.onload,delete n.onerror,(n.naturalWidth>=t?i:s)(n)};Object.assign(n,{onload:a,onerror:a,src:e})}))}const Fe={addStyleHook(){R(this.elements.container,this.config.selectors.container.replace(".",""),!0),R(this.elements.container,this.config.classNames.uiSupported,this.supported.ui)},toggleNativeControls(e=!1){e&&this.isHTML5?this.media.setAttribute("controls",""):this.media.removeAttribute("controls")},build(){if(this.listeners.media(),!this.supported.ui)return this.debug.warn(`Basic support only for ${this.provider} ${this.type}`),void Fe.toggleNativeControls.call(this,!0);S.element(this.elements.controls)||(Pe.inject.call(this),this.listeners.controls()),Fe.toggleNativeControls.call(this),this.isHTML5&&xe.setup.call(this),this.volume=null,this.muted=null,this.loop=null,this.quality=null,this.speed=null,Pe.updateVolume.call(this),Pe.timeUpdate.call(this),Pe.durationUpdate.call(this),Fe.checkPlaying.call(this),R(this.elements.container,this.config.classNames.pip.supported,K.pip&&this.isHTML5&&this.isVideo),R(this.elements.container,this.config.classNames.airplay.supported,K.airplay&&this.isHTML5),R(this.elements.container,this.config.classNames.isTouch,this.touch),this.ready=!0,setTimeout((()=>{Z.call(this,this.media,"ready")}),0),Fe.setTitle.call(this),this.poster&&Fe.setPoster.call(this,this.poster,!1).catch((()=>{})),this.config.duration&&Pe.durationUpdate.call(this),this.config.mediaMetadata&&Pe.setMediaMetadata.call(this)},setTitle(){let e=ve.get("play",this.config);if(S.string(this.config.title)&&!S.empty(this.config.title)&&(e+=`, ${this.config.title}`),Array.from(this.elements.buttons.play||[]).forEach((t=>{t.setAttribute("aria-label",e)})),this.isEmbed){const e=B.call(this,"iframe");if(!S.element(e))return;const t=S.empty(this.config.title)?"video":this.config.title,i=ve.get("frameTitle",this.config);e.setAttribute("title",i.replace("{title}",t))}},togglePoster(e){R(this.elements.container,this.config.classNames.posterEnabled,e)},setPoster(e,t=!0){return t&&this.poster?Promise.reject(new Error("Poster already set")):(this.media.setAttribute("data-poster",e),this.elements.poster.removeAttribute("hidden"),te.call(this).then((()=>Re(e))).catch((t=>{throw e===this.poster&&Fe.togglePoster.call(this,!1),t})).then((()=>{if(e!==this.poster)throw new Error("setPoster cancelled by later call to setPoster")})).then((()=>(Object.assign(this.elements.poster.style,{backgroundImage:`url('${e}')`,backgroundSize:""}),Fe.togglePoster.call(this,!0),e))))},checkPlaying(e){R(this.elements.container,this.config.classNames.playing,this.playing),R(this.elements.container,this.config.classNames.paused,this.paused),R(this.elements.container,this.config.classNames.stopped,this.stopped),Array.from(this.elements.buttons.play||[]).forEach((e=>{Object.assign(e,{pressed:this.playing}),e.setAttribute("aria-label",ve.get(this.playing?"pause":"play",this.config))})),S.event(e)&&"timeupdate"===e.type||Fe.toggleControls.call(this)},checkLoading(e){this.loading=["stalled","waiting"].includes(e.type),clearTimeout(this.timers.loading),this.timers.loading=setTimeout((()=>{R(this.elements.container,this.config.classNames.loading,this.loading),Fe.toggleControls.call(this)}),this.loading?250:0)},toggleControls(e){const{controls:t}=this.elements;if(t&&this.config.hideControls){const i=this.touch&&this.lastSeekTime+2e3>Date.now();this.toggleControls(Boolean(e||this.loading||this.paused||t.pressed||t.hover||i))}},migrateStyles(){Object.values({...this.media.style}).filter((e=>!S.empty(e)&&S.string(e)&&e.startsWith("--plyr"))).forEach((e=>{this.elements.container.style.setProperty(e,this.media.style.getPropertyValue(e)),this.media.style.removeProperty(e)})),S.empty(this.media.style)&&this.media.removeAttribute("style")}};class Ve{constructor(t){e(this,"firstTouch",(()=>{const{player:e}=this,{elements:t}=e;e.touch=!0,R(t.container,e.config.classNames.isTouch,!0)})),e(this,"global",((e=!0)=>{const{player:t}=this;t.config.keyboard.global&&Q.call(t,window,"keydown keyup",this.handleKey,e,!1),Q.call(t,document.body,"click",this.toggleMenu,e),G.call(t,document.body,"touchstart",this.firstTouch)})),e(this,"container",(()=>{const{player:e}=this,{config:t,elements:i,timers:s}=e;!t.keyboard.global&&t.keyboard.focused&&X.call(e,i.container,"keydown keyup",this.handleKey,!1),X.call(e,i.container,"mousemove mouseleave touchstart touchmove enterfullscreen exitfullscreen",(t=>{const{controls:n}=i;n&&"enterfullscreen"===t.type&&(n.pressed=!1,n.hover=!1);let a=0;["touchstart","touchmove","mousemove"].includes(t.type)&&(Fe.toggleControls.call(e,!0),a=e.touch?3e3:2e3),clearTimeout(s.controls),s.controls=setTimeout((()=>Fe.toggleControls.call(e,!1)),a)}));const n=()=>{if(!e.isVimeo||e.config.vimeo.premium)return;const t=i.wrapper,{active:s}=e.fullscreen,[n,a]=ce.call(e),l=ae(`aspect-ratio: ${n} / ${a}`);if(!s)return void(l?(t.style.width=null,t.style.height=null):(t.style.maxWidth=null,t.style.margin=null));const[r,o]=[Math.max(document.documentElement.clientWidth||0,window.innerWidth||0),Math.max(document.documentElement.clientHeight||0,window.innerHeight||0)],c=r/o>n/a;l?(t.style.width=c?"auto":"100%",t.style.height=c?"100%":"auto"):(t.style.maxWidth=c?o/a*n+"px":null,t.style.margin=c?"0 auto":null)},a=()=>{clearTimeout(s.resized),s.resized=setTimeout(n,50)};X.call(e,i.container,"enterfullscreen exitfullscreen",(t=>{const{target:s}=e.fullscreen;if(s!==i.container)return;if(!e.isEmbed&&S.empty(e.config.ratio))return;n();("enterfullscreen"===t.type?X:J).call(e,window,"resize",a)}))})),e(this,"media",(()=>{const{player:e}=this,{elements:t}=e;if(X.call(e,e.media,"timeupdate seeking seeked",(t=>Pe.timeUpdate.call(e,t))),X.call(e,e.media,"durationchange loadeddata loadedmetadata",(t=>Pe.durationUpdate.call(e,t))),X.call(e,e.media,"ended",(()=>{e.isHTML5&&e.isVideo&&e.config.resetOnEnd&&(e.restart(),e.pause())})),X.call(e,e.media,"progress playing seeking seeked",(t=>Pe.updateProgress.call(e,t))),X.call(e,e.media,"volumechange",(t=>Pe.updateVolume.call(e,t))),X.call(e,e.media,"playing play pause ended emptied timeupdate",(t=>Fe.checkPlaying.call(e,t))),X.call(e,e.media,"waiting canplay seeked playing",(t=>Fe.checkLoading.call(e,t))),e.supported.ui&&e.config.clickToPlay&&!e.isAudio){const i=B.call(e,`.${e.config.classNames.video}`);if(!S.element(i))return;X.call(e,t.container,"click",(s=>{([t.container,i].includes(s.target)||i.contains(s.target))&&(e.touch&&e.config.hideControls||(e.ended?(this.proxy(s,e.restart,"restart"),this.proxy(s,(()=>{ie(e.play())}),"play")):this.proxy(s,(()=>{ie(e.togglePlay())}),"play")))}))}e.supported.ui&&e.config.disableContextMenu&&X.call(e,t.wrapper,"contextmenu",(e=>{e.preventDefault()}),!1),X.call(e,e.media,"volumechange",(()=>{e.storage.set({volume:e.volume,muted:e.muted})})),X.call(e,e.media,"ratechange",(()=>{Pe.updateSetting.call(e,"speed"),e.storage.set({speed:e.speed})})),X.call(e,e.media,"qualitychange",(t=>{Pe.updateSetting.call(e,"quality",null,t.detail.quality)})),X.call(e,e.media,"ready qualitychange",(()=>{Pe.setDownloadUrl.call(e)}));const i=e.config.events.concat(["keyup","keydown"]).join(" ");X.call(e,e.media,i,(i=>{let{detail:s={}}=i;"error"===i.type&&(s=e.media.error),Z.call(e,t.container,i.type,!0,s)}))})),e(this,"proxy",((e,t,i)=>{const{player:s}=this,n=s.config.listeners[i];let a=!0;S.function(n)&&(a=n.call(s,e)),!1!==a&&S.function(t)&&t.call(s,e)})),e(this,"bind",((e,t,i,s,n=!0)=>{const{player:a}=this,l=a.config.listeners[s],r=S.function(l);X.call(a,e,t,(e=>this.proxy(e,i,s)),n&&!r)})),e(this,"controls",(()=>{const{player:e}=this,{elements:t}=e,i=M.isIE?"change":"input";if(t.buttons.play&&Array.from(t.buttons.play).forEach((t=>{this.bind(t,"click",(()=>{ie(e.togglePlay())}),"play")})),this.bind(t.buttons.restart,"click",e.restart,"restart"),this.bind(t.buttons.rewind,"click",(()=>{e.lastSeekTime=Date.now(),e.rewind()}),"rewind"),this.bind(t.buttons.fastForward,"click",(()=>{e.lastSeekTime=Date.now(),e.forward()}),"fastForward"),this.bind(t.buttons.mute,"click",(()=>{e.muted=!e.muted}),"mute"),this.bind(t.buttons.captions,"click",(()=>e.toggleCaptions())),this.bind(t.buttons.download,"click",(()=>{Z.call(e,e.media,"download")}),"download"),this.bind(t.buttons.fullscreen,"click",(()=>{e.fullscreen.toggle()}),"fullscreen"),this.bind(t.buttons.pip,"click",(()=>{e.pip="toggle"}),"pip"),this.bind(t.buttons.airplay,"click",e.airplay,"airplay"),this.bind(t.buttons.settings,"click",(t=>{t.stopPropagation(),t.preventDefault(),Pe.toggleMenu.call(e,t)}),null,!1),this.bind(t.buttons.settings,"keyup",(t=>{[" ","Enter"].includes(t.key)&&("Enter"!==t.key?(t.preventDefault(),t.stopPropagation(),Pe.toggleMenu.call(e,t)):Pe.focusFirstMenuItem.call(e,null,!0))}),null,!1),this.bind(t.settings.menu,"keydown",(t=>{"Escape"===t.key&&Pe.toggleMenu.call(e,t)})),this.bind(t.inputs.seek,"mousedown mousemove",(e=>{const i=t.progress.getBoundingClientRect(),s=100/i.width*(e.pageX-i.left);e.currentTarget.setAttribute("seek-value",s)})),this.bind(t.inputs.seek,"mousedown mouseup keydown keyup touchstart touchend",(t=>{const i=t.currentTarget,s="play-on-seeked";if(S.keyboardEvent(t)&&!["ArrowLeft","ArrowRight"].includes(t.key))return;e.lastSeekTime=Date.now();const n=i.hasAttribute(s),a=["mouseup","touchend","keyup"].includes(t.type);n&&a?(i.removeAttribute(s),ie(e.play())):!a&&e.playing&&(i.setAttribute(s,""),e.pause())})),M.isIos){const t=U.call(e,'input[type="range"]');Array.from(t).forEach((e=>this.bind(e,i,(e=>P(e.target)))))}this.bind(t.inputs.seek,i,(t=>{const i=t.currentTarget;let s=i.getAttribute("seek-value");S.empty(s)&&(s=i.value),i.removeAttribute("seek-value"),e.currentTime=s/i.max*e.duration}),"seek"),this.bind(t.progress,"mouseenter mouseleave mousemove",(t=>Pe.updateSeekTooltip.call(e,t))),this.bind(t.progress,"mousemove touchmove",(t=>{const{previewThumbnails:i}=e;i&&i.loaded&&i.startMove(t)})),this.bind(t.progress,"mouseleave touchend click",(()=>{const{previewThumbnails:t}=e;t&&t.loaded&&t.endMove(!1,!0)})),this.bind(t.progress,"mousedown touchstart",(t=>{const{previewThumbnails:i}=e;i&&i.loaded&&i.startScrubbing(t)})),this.bind(t.progress,"mouseup touchend",(t=>{const{previewThumbnails:i}=e;i&&i.loaded&&i.endScrubbing(t)})),M.isWebKit&&Array.from(U.call(e,'input[type="range"]')).forEach((t=>{this.bind(t,"input",(t=>Pe.updateRangeFill.call(e,t.target)))})),e.config.toggleInvert&&!S.element(t.display.duration)&&this.bind(t.display.currentTime,"click",(()=>{0!==e.currentTime&&(e.config.invertTime=!e.config.invertTime,Pe.timeUpdate.call(e))})),this.bind(t.inputs.volume,i,(t=>{e.volume=t.target.value}),"volume"),this.bind(t.controls,"mouseenter mouseleave",(i=>{t.controls.hover=!e.touch&&"mouseenter"===i.type})),t.fullscreen&&Array.from(t.fullscreen.children).filter((e=>!e.contains(t.container))).forEach((i=>{this.bind(i,"mouseenter mouseleave",(i=>{t.controls&&(t.controls.hover=!e.touch&&"mouseenter"===i.type)}))})),this.bind(t.controls,"mousedown mouseup touchstart touchend touchcancel",(e=>{t.controls.pressed=["mousedown","touchstart"].includes(e.type)})),this.bind(t.controls,"focusin",(()=>{const{config:i,timers:s}=e;R(t.controls,i.classNames.noTransition,!0),Fe.toggleControls.call(e,!0),setTimeout((()=>{R(t.controls,i.classNames.noTransition,!1)}),0);const n=this.touch?3e3:4e3;clearTimeout(s.controls),s.controls=setTimeout((()=>Fe.toggleControls.call(e,!1)),n)})),this.bind(t.inputs.volume,"wheel",(t=>{const i=t.webkitDirectionInvertedFromDevice,[s,n]=[t.deltaX,-t.deltaY].map((e=>i?-e:e)),a=Math.sign(Math.abs(s)>Math.abs(n)?s:n);e.increaseVolume(a/50);const{volume:l}=e.media;(1===a&&l<1||-1===a&&l>0)&&t.preventDefault()}),"volume",!1)})),this.player=t,this.lastKey=null,this.focusTimer=null,this.lastKeyDown=null,this.handleKey=this.handleKey.bind(this),this.toggleMenu=this.toggleMenu.bind(this),this.firstTouch=this.firstTouch.bind(this)}handleKey(e){const{player:t}=this,{elements:i}=t,{key:s,type:n,altKey:a,ctrlKey:l,metaKey:r,shiftKey:o}=e,c="keydown"===n,u=c&&s===this.lastKey;if(a||l||r||o)return;if(!s)return;if(c){const n=document.activeElement;if(S.element(n)){const{editable:s}=t.config.selectors,{seek:a}=i.inputs;if(n!==a&&V(n,s))return;if(" "===e.key&&V(n,'button, [role^="menuitem"]'))return}switch([" ","ArrowLeft","ArrowUp","ArrowRight","ArrowDown","0","1","2","3","4","5","6","7","8","9","c","f","k","l","m"].includes(s)&&(e.preventDefault(),e.stopPropagation()),s){case"0":case"1":case"2":case"3":case"4":case"5":case"6":case"7":case"8":case"9":u||(h=parseInt(s,10),t.currentTime=t.duration/10*h);break;case" ":case"k":u||ie(t.togglePlay());break;case"ArrowUp":t.increaseVolume(.1);break;case"ArrowDown":t.decreaseVolume(.1);break;case"m":u||(t.muted=!t.muted);break;case"ArrowRight":t.forward();break;case"ArrowLeft":t.rewind();break;case"f":t.fullscreen.toggle();break;case"c":u||t.toggleCaptions();break;case"l":t.loop=!t.loop}"Escape"===s&&!t.fullscreen.usingNative&&t.fullscreen.active&&t.fullscreen.toggle(),this.lastKey=s}else this.lastKey=null;var h}toggleMenu(e){Pe.toggleMenu.call(this.player,e)}}"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self&&self;var Ue=function(e,t){return e(t={exports:{}},t.exports),t.exports}((function(e,t){e.exports=function(){var e=function(){},t={},i={},s={};function n(e,t){e=e.push?e:[e];var n,a,l,r=[],o=e.length,c=o;for(n=function(e,i){i.length&&r.push(e),--c||t(r)};o--;)a=e[o],(l=i[a])?n(a,l):(s[a]=s[a]||[]).push(n)}function a(e,t){if(e){var n=s[e];if(i[e]=t,n)for(;n.length;)n[0](e,t),n.splice(0,1)}}function l(t,i){t.call&&(t={success:t}),i.length?(t.error||e)(i):(t.success||e)(t)}function r(t,i,s,n){var a,l,o=document,c=s.async,u=(s.numRetries||0)+1,h=s.before||e,d=t.replace(/[\?|#].*$/,""),m=t.replace(/^(css|img)!/,"");n=n||0,/(^css!|\.css$)/.test(d)?((l=o.createElement("link")).rel="stylesheet",l.href=m,(a="hideFocus"in l)&&l.relList&&(a=0,l.rel="preload",l.as="style")):/(^img!|\.(png|gif|jpg|svg|webp)$)/.test(d)?(l=o.createElement("img")).src=m:((l=o.createElement("script")).src=t,l.async=void 0===c||c),l.onload=l.onerror=l.onbeforeload=function(e){var o=e.type[0];if(a)try{l.sheet.cssText.length||(o="e")}catch(e){18!=e.code&&(o="e")}if("e"==o){if((n+=1){Ue(e,{success:t,error:i})}))}function We(e){e&&!this.embed.hasPlayed&&(this.embed.hasPlayed=!0),this.media.paused===e&&(this.media.paused=!e,Z.call(this,this.media,e?"play":"pause"))}const ze={setup(){const e=this;R(e.elements.wrapper,e.config.classNames.embed,!0),e.options.speed=e.config.speed.options,ue.call(e),S.object(window.Vimeo)?ze.ready.call(e):Be(e.config.urls.vimeo.sdk).then((()=>{ze.ready.call(e)})).catch((t=>{e.debug.warn("Vimeo SDK (player.js) failed to load",t)}))},ready(){const e=this,t=e.config.vimeo,{premium:i,referrerPolicy:s,...n}=t;let a=e.media.getAttribute("src"),l="";S.empty(a)?(a=e.media.getAttribute(e.config.attributes.embed.id),l=e.media.getAttribute(e.config.attributes.embed.hash)):l=function(e){const t=e.match(/^.*(vimeo.com\/|video\/)(\d+)(\?.*&*h=|\/)+([\d,a-f]+)/);return t&&5===t.length?t[4]:null}(a);const r=l?{h:l}:{};i&&Object.assign(n,{controls:!1,sidedock:!1});const o=Ne({loop:e.config.loop.active,autoplay:e.autoplay,muted:e.muted,gesture:"media",playsinline:e.config.playsinline,...r,...n}),c=(u=a,S.empty(u)?null:S.number(Number(u))?u:u.match(/^.*(vimeo.com\/|video\/)(\d+).*/)?RegExp.$2:u);var u;const h=$("iframe"),d=me(e.config.urls.vimeo.iframe,c,o);if(h.setAttribute("src",d),h.setAttribute("allowfullscreen",""),h.setAttribute("allow",["autoplay","fullscreen","picture-in-picture","encrypted-media","accelerometer","gyroscope"].join("; ")),S.empty(s)||h.setAttribute("referrerPolicy",s),i||!t.customControls)h.setAttribute("data-poster",e.poster),e.media=q(h,e.media);else{const t=$("div",{class:e.config.classNames.embedContainer,"data-poster":e.poster});t.appendChild(h),e.media=q(t,e.media)}t.customControls||Te(me(e.config.urls.vimeo.api,d)).then((t=>{!S.empty(t)&&t.thumbnail_url&&Fe.setPoster.call(e,t.thumbnail_url).catch((()=>{}))})),e.embed=new window.Vimeo.Player(h,{autopause:e.config.autopause,muted:e.muted}),e.media.paused=!0,e.media.currentTime=0,e.supported.ui&&e.embed.disableTextTrack(),e.media.play=()=>(We.call(e,!0),e.embed.play()),e.media.pause=()=>(We.call(e,!1),e.embed.pause()),e.media.stop=()=>{e.pause(),e.currentTime=0};let{currentTime:m}=e.media;Object.defineProperty(e.media,"currentTime",{get:()=>m,set(t){const{embed:i,media:s,paused:n,volume:a}=e,l=n&&!i.hasPlayed;s.seeking=!0,Z.call(e,s,"seeking"),Promise.resolve(l&&i.setVolume(0)).then((()=>i.setCurrentTime(t))).then((()=>l&&i.pause())).then((()=>l&&i.setVolume(a))).catch((()=>{}))}});let p=e.config.speed.selected;Object.defineProperty(e.media,"playbackRate",{get:()=>p,set(t){e.embed.setPlaybackRate(t).then((()=>{p=t,Z.call(e,e.media,"ratechange")})).catch((()=>{e.options.speed=[1]}))}});let{volume:g}=e.config;Object.defineProperty(e.media,"volume",{get:()=>g,set(t){e.embed.setVolume(t).then((()=>{g=t,Z.call(e,e.media,"volumechange")}))}});let{muted:f}=e.config;Object.defineProperty(e.media,"muted",{get:()=>f,set(t){const i=!!S.boolean(t)&&t;e.embed.setMuted(!!i||e.config.muted).then((()=>{f=i,Z.call(e,e.media,"volumechange")}))}});let y,{loop:b}=e.config;Object.defineProperty(e.media,"loop",{get:()=>b,set(t){const i=S.boolean(t)?t:e.config.loop.active;e.embed.setLoop(i).then((()=>{b=i}))}}),e.embed.getVideoUrl().then((t=>{y=t,Pe.setDownloadUrl.call(e)})).catch((e=>{this.debug.warn(e)})),Object.defineProperty(e.media,"currentSrc",{get:()=>y}),Object.defineProperty(e.media,"ended",{get:()=>e.currentTime===e.duration}),Promise.all([e.embed.getVideoWidth(),e.embed.getVideoHeight()]).then((t=>{const[i,s]=t;e.embed.ratio=he(i,s),ue.call(this)})),e.embed.setAutopause(e.config.autopause).then((t=>{e.config.autopause=t})),e.embed.getVideoTitle().then((t=>{e.config.title=t,Fe.setTitle.call(this)})),e.embed.getCurrentTime().then((t=>{m=t,Z.call(e,e.media,"timeupdate")})),e.embed.getDuration().then((t=>{e.media.duration=t,Z.call(e,e.media,"durationchange")})),e.embed.getTextTracks().then((t=>{e.media.textTracks=t,xe.setup.call(e)})),e.embed.on("cuechange",(({cues:t=[]})=>{const i=t.map((e=>function(e){const t=document.createDocumentFragment(),i=document.createElement("div");return t.appendChild(i),i.innerHTML=e,t.firstChild.innerText}(e.text)));xe.updateCues.call(e,i)})),e.embed.on("loaded",(()=>{if(e.embed.getPaused().then((t=>{We.call(e,!t),t||Z.call(e,e.media,"playing")})),S.element(e.embed.element)&&e.supported.ui){e.embed.element.setAttribute("tabindex",-1)}})),e.embed.on("bufferstart",(()=>{Z.call(e,e.media,"waiting")})),e.embed.on("bufferend",(()=>{Z.call(e,e.media,"playing")})),e.embed.on("play",(()=>{We.call(e,!0),Z.call(e,e.media,"playing")})),e.embed.on("pause",(()=>{We.call(e,!1)})),e.embed.on("timeupdate",(t=>{e.media.seeking=!1,m=t.seconds,Z.call(e,e.media,"timeupdate")})),e.embed.on("progress",(t=>{e.media.buffered=t.percent,Z.call(e,e.media,"progress"),1===parseInt(t.percent,10)&&Z.call(e,e.media,"canplaythrough"),e.embed.getDuration().then((t=>{t!==e.media.duration&&(e.media.duration=t,Z.call(e,e.media,"durationchange"))}))})),e.embed.on("seeked",(()=>{e.media.seeking=!1,Z.call(e,e.media,"seeked")})),e.embed.on("ended",(()=>{e.media.paused=!0,Z.call(e,e.media,"ended")})),e.embed.on("error",(t=>{e.media.error=t,Z.call(e,e.media,"error")})),t.customControls&&setTimeout((()=>Fe.build.call(e)),0)}};function Ke(e){e&&!this.embed.hasPlayed&&(this.embed.hasPlayed=!0),this.media.paused===e&&(this.media.paused=!e,Z.call(this,this.media,e?"play":"pause"))}function Ye(e){return e.noCookie?"https://www.youtube-nocookie.com":"http:"===window.location.protocol?"http://www.youtube.com":void 0}const Qe={setup(){if(R(this.elements.wrapper,this.config.classNames.embed,!0),S.object(window.YT)&&S.function(window.YT.Player))Qe.ready.call(this);else{const e=window.onYouTubeIframeAPIReady;window.onYouTubeIframeAPIReady=()=>{S.function(e)&&e(),Qe.ready.call(this)},Be(this.config.urls.youtube.sdk).catch((e=>{this.debug.warn("YouTube API failed to load",e)}))}},getTitle(e){Te(me(this.config.urls.youtube.api,e)).then((e=>{if(S.object(e)){const{title:t,height:i,width:s}=e;this.config.title=t,Fe.setTitle.call(this),this.embed.ratio=he(s,i)}ue.call(this)})).catch((()=>{ue.call(this)}))},ready(){const e=this,t=e.config.youtube,i=e.media&&e.media.getAttribute("id");if(!S.empty(i)&&i.startsWith("youtube-"))return;let s=e.media.getAttribute("src");S.empty(s)&&(s=e.media.getAttribute(this.config.attributes.embed.id));const n=(a=s,S.empty(a)?null:a.match(/^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|&v=)([^#&?]*).*/)?RegExp.$2:a);var a;const l=$("div",{id:`${e.provider}-${Math.floor(1e4*Math.random())}`,"data-poster":t.customControls?e.poster:void 0});if(e.media=q(l,e.media),t.customControls){const t=e=>`https://i.ytimg.com/vi/${n}/${e}default.jpg`;Re(t("maxres"),121).catch((()=>Re(t("sd"),121))).catch((()=>Re(t("hq")))).then((t=>Fe.setPoster.call(e,t.src))).then((t=>{t.includes("maxres")||(e.elements.poster.style.backgroundSize="cover")})).catch((()=>{}))}e.embed=new window.YT.Player(e.media,{videoId:n,host:Ye(t),playerVars:x({},{autoplay:e.config.autoplay?1:0,hl:e.config.hl,controls:e.supported.ui&&t.customControls?0:1,disablekb:1,playsinline:e.config.playsinline&&!e.config.fullscreen.iosNative?1:0,cc_load_policy:e.captions.active?1:0,cc_lang_pref:e.config.captions.language,widget_referrer:window?window.location.href:null},t),events:{onError(t){if(!e.media.error){const i=t.data,s={2:"The request contains an invalid parameter value. For example, this error occurs if you specify a video ID that does not have 11 characters, or if the video ID contains invalid characters, such as exclamation points or asterisks.",5:"The requested content cannot be played in an HTML5 player or another error related to the HTML5 player has occurred.",100:"The video requested was not found. This error occurs when a video has been removed (for any reason) or has been marked as private.",101:"The owner of the requested video does not allow it to be played in embedded players.",150:"The owner of the requested video does not allow it to be played in embedded players."}[i]||"An unknown error occurred";e.media.error={code:i,message:s},Z.call(e,e.media,"error")}},onPlaybackRateChange(t){const i=t.target;e.media.playbackRate=i.getPlaybackRate(),Z.call(e,e.media,"ratechange")},onReady(i){if(S.function(e.media.play))return;const s=i.target;Qe.getTitle.call(e,n),e.media.play=()=>{Ke.call(e,!0),s.playVideo()},e.media.pause=()=>{Ke.call(e,!1),s.pauseVideo()},e.media.stop=()=>{s.stopVideo()},e.media.duration=s.getDuration(),e.media.paused=!0,e.media.currentTime=0,Object.defineProperty(e.media,"currentTime",{get:()=>Number(s.getCurrentTime()),set(t){e.paused&&!e.embed.hasPlayed&&e.embed.mute(),e.media.seeking=!0,Z.call(e,e.media,"seeking"),s.seekTo(t)}}),Object.defineProperty(e.media,"playbackRate",{get:()=>s.getPlaybackRate(),set(e){s.setPlaybackRate(e)}});let{volume:a}=e.config;Object.defineProperty(e.media,"volume",{get:()=>a,set(t){a=t,s.setVolume(100*a),Z.call(e,e.media,"volumechange")}});let{muted:l}=e.config;Object.defineProperty(e.media,"muted",{get:()=>l,set(t){const i=S.boolean(t)?t:l;l=i,s[i?"mute":"unMute"](),s.setVolume(100*a),Z.call(e,e.media,"volumechange")}}),Object.defineProperty(e.media,"currentSrc",{get:()=>s.getVideoUrl()}),Object.defineProperty(e.media,"ended",{get:()=>e.currentTime===e.duration});const r=s.getAvailablePlaybackRates();e.options.speed=r.filter((t=>e.config.speed.options.includes(t))),e.supported.ui&&t.customControls&&e.media.setAttribute("tabindex",-1),Z.call(e,e.media,"timeupdate"),Z.call(e,e.media,"durationchange"),clearInterval(e.timers.buffering),e.timers.buffering=setInterval((()=>{e.media.buffered=s.getVideoLoadedFraction(),(null===e.media.lastBuffered||e.media.lastBufferedFe.build.call(e)),50)},onStateChange(i){const s=i.target;clearInterval(e.timers.playing);switch(e.media.seeking&&[1,2].includes(i.data)&&(e.media.seeking=!1,Z.call(e,e.media,"seeked")),i.data){case-1:Z.call(e,e.media,"timeupdate"),e.media.buffered=s.getVideoLoadedFraction(),Z.call(e,e.media,"progress");break;case 0:Ke.call(e,!1),e.media.loop?(s.stopVideo(),s.playVideo()):Z.call(e,e.media,"ended");break;case 1:t.customControls&&!e.config.autoplay&&e.media.paused&&!e.embed.hasPlayed?e.media.pause():(Ke.call(e,!0),Z.call(e,e.media,"playing"),e.timers.playing=setInterval((()=>{Z.call(e,e.media,"timeupdate")}),50),e.media.duration!==s.getDuration()&&(e.media.duration=s.getDuration(),Z.call(e,e.media,"durationchange")));break;case 2:e.muted||e.embed.unMute(),Ke.call(e,!1);break;case 3:Z.call(e,e.media,"waiting")}Z.call(e,e.elements.container,"statechange",!1,{code:i.data})}}})}},Xe={setup(){this.media?(R(this.elements.container,this.config.classNames.type.replace("{0}",this.type),!0),R(this.elements.container,this.config.classNames.provider.replace("{0}",this.provider),!0),this.isEmbed&&R(this.elements.container,this.config.classNames.type.replace("{0}","video"),!0),this.isVideo&&(this.elements.wrapper=$("div",{class:this.config.classNames.video}),L(this.media,this.elements.wrapper),this.elements.poster=$("div",{class:this.config.classNames.poster}),this.elements.wrapper.appendChild(this.elements.poster)),this.isHTML5?de.setup.call(this):this.isYouTube?Qe.setup.call(this):this.isVimeo&&ze.setup.call(this)):this.debug.warn("No media element found!")}};class Je{constructor(t){e(this,"load",(()=>{this.enabled&&(S.object(window.google)&&S.object(window.google.ima)?this.ready():Be(this.player.config.urls.googleIMA.sdk).then((()=>{this.ready()})).catch((()=>{this.trigger("error",new Error("Google IMA SDK failed to load"))})))})),e(this,"ready",(()=>{var e;this.enabled||((e=this).manager&&e.manager.destroy(),e.elements.displayContainer&&e.elements.displayContainer.destroy(),e.elements.container.remove()),this.startSafetyTimer(12e3,"ready()"),this.managerPromise.then((()=>{this.clearSafetyTimer("onAdsManagerLoaded()")})),this.listeners(),this.setupIMA()})),e(this,"setupIMA",(()=>{this.elements.container=$("div",{class:this.player.config.classNames.ads}),this.player.elements.container.appendChild(this.elements.container),google.ima.settings.setVpaidMode(google.ima.ImaSdkSettings.VpaidMode.ENABLED),google.ima.settings.setLocale(this.player.config.ads.language),google.ima.settings.setDisableCustomPlaybackForIOS10Plus(this.player.config.playsinline),this.elements.displayContainer=new google.ima.AdDisplayContainer(this.elements.container,this.player.media),this.loader=new google.ima.AdsLoader(this.elements.displayContainer),this.loader.addEventListener(google.ima.AdsManagerLoadedEvent.Type.ADS_MANAGER_LOADED,(e=>this.onAdsManagerLoaded(e)),!1),this.loader.addEventListener(google.ima.AdErrorEvent.Type.AD_ERROR,(e=>this.onAdError(e)),!1),this.requestAds()})),e(this,"requestAds",(()=>{const{container:e}=this.player.elements;try{const t=new google.ima.AdsRequest;t.adTagUrl=this.tagUrl,t.linearAdSlotWidth=e.offsetWidth,t.linearAdSlotHeight=e.offsetHeight,t.nonLinearAdSlotWidth=e.offsetWidth,t.nonLinearAdSlotHeight=e.offsetHeight,t.forceNonLinearFullSlot=!1,t.setAdWillPlayMuted(!this.player.muted),this.loader.requestAds(t)}catch(e){this.onAdError(e)}})),e(this,"pollCountdown",((e=!1)=>{if(!e)return clearInterval(this.countdownTimer),void this.elements.container.removeAttribute("data-badge-text");this.countdownTimer=setInterval((()=>{const e=Ee(Math.max(this.manager.getRemainingTime(),0)),t=`${ve.get("advertisement",this.player.config)} - ${e}`;this.elements.container.setAttribute("data-badge-text",t)}),100)})),e(this,"onAdsManagerLoaded",(e=>{if(!this.enabled)return;const t=new google.ima.AdsRenderingSettings;t.restoreCustomPlaybackStateOnAdBreakComplete=!0,t.enablePreloading=!0,this.manager=e.getAdsManager(this.player,t),this.cuePoints=this.manager.getCuePoints(),this.manager.addEventListener(google.ima.AdErrorEvent.Type.AD_ERROR,(e=>this.onAdError(e))),Object.keys(google.ima.AdEvent.Type).forEach((e=>{this.manager.addEventListener(google.ima.AdEvent.Type[e],(e=>this.onAdEvent(e)))})),this.trigger("loaded")})),e(this,"addCuePoints",(()=>{S.empty(this.cuePoints)||this.cuePoints.forEach((e=>{if(0!==e&&-1!==e&&e{const{container:t}=this.player.elements,i=e.getAd(),s=e.getAdData();switch((e=>{Z.call(this.player,this.player.media,`ads${e.replace(/_/g,"").toLowerCase()}`)})(e.type),e.type){case google.ima.AdEvent.Type.LOADED:this.trigger("loaded"),this.pollCountdown(!0),i.isLinear()||(i.width=t.offsetWidth,i.height=t.offsetHeight);break;case google.ima.AdEvent.Type.STARTED:this.manager.setVolume(this.player.volume);break;case google.ima.AdEvent.Type.ALL_ADS_COMPLETED:this.player.ended?this.loadAds():this.loader.contentComplete();break;case google.ima.AdEvent.Type.CONTENT_PAUSE_REQUESTED:this.pauseContent();break;case google.ima.AdEvent.Type.CONTENT_RESUME_REQUESTED:this.pollCountdown(),this.resumeContent();break;case google.ima.AdEvent.Type.LOG:s.adError&&this.player.debug.warn(`Non-fatal ad error: ${s.adError.getMessage()}`)}})),e(this,"onAdError",(e=>{this.cancel(),this.player.debug.warn("Ads error",e)})),e(this,"listeners",(()=>{const{container:e}=this.player.elements;let t;this.player.on("canplay",(()=>{this.addCuePoints()})),this.player.on("ended",(()=>{this.loader.contentComplete()})),this.player.on("timeupdate",(()=>{t=this.player.currentTime})),this.player.on("seeked",(()=>{const e=this.player.currentTime;S.empty(this.cuePoints)||this.cuePoints.forEach(((i,s)=>{t{this.manager&&this.manager.resize(e.offsetWidth,e.offsetHeight,google.ima.ViewMode.NORMAL)}))})),e(this,"play",(()=>{const{container:e}=this.player.elements;this.managerPromise||this.resumeContent(),this.managerPromise.then((()=>{this.manager.setVolume(this.player.volume),this.elements.displayContainer.initialize();try{this.initialized||(this.manager.init(e.offsetWidth,e.offsetHeight,google.ima.ViewMode.NORMAL),this.manager.start()),this.initialized=!0}catch(e){this.onAdError(e)}})).catch((()=>{}))})),e(this,"resumeContent",(()=>{this.elements.container.style.zIndex="",this.playing=!1,ie(this.player.media.play())})),e(this,"pauseContent",(()=>{this.elements.container.style.zIndex=3,this.playing=!0,this.player.media.pause()})),e(this,"cancel",(()=>{this.initialized&&this.resumeContent(),this.trigger("error"),this.loadAds()})),e(this,"loadAds",(()=>{this.managerPromise.then((()=>{this.manager&&this.manager.destroy(),this.managerPromise=new Promise((e=>{this.on("loaded",e),this.player.debug.log(this.manager)})),this.initialized=!1,this.requestAds()})).catch((()=>{}))})),e(this,"trigger",((e,...t)=>{const i=this.events[e];S.array(i)&&i.forEach((e=>{S.function(e)&&e.apply(this,t)}))})),e(this,"on",((e,t)=>(S.array(this.events[e])||(this.events[e]=[]),this.events[e].push(t),this))),e(this,"startSafetyTimer",((e,t)=>{this.player.debug.log(`Safety timer invoked from: ${t}`),this.safetyTimer=setTimeout((()=>{this.cancel(),this.clearSafetyTimer("startSafetyTimer()")}),e)})),e(this,"clearSafetyTimer",(e=>{S.nullOrUndefined(this.safetyTimer)||(this.player.debug.log(`Safety timer cleared from: ${e}`),clearTimeout(this.safetyTimer),this.safetyTimer=null)})),this.player=t,this.config=t.config.ads,this.playing=!1,this.initialized=!1,this.elements={container:null,displayContainer:null},this.manager=null,this.loader=null,this.cuePoints=null,this.events={},this.safetyTimer=null,this.countdownTimer=null,this.managerPromise=new Promise(((e,t)=>{this.on("loaded",e),this.on("error",t)})),this.load()}get enabled(){const{config:e}=this;return this.player.isHTML5&&this.player.isVideo&&e.enabled&&(!S.empty(e.publisherId)||S.url(e.tagUrl))}get tagUrl(){const{config:e}=this;if(S.url(e.tagUrl))return e.tagUrl;return`https://go.aniview.com/api/adserver6/vast/?${Ne({AV_PUBLISHERID:"58c25bb0073ef448b1087ad6",AV_CHANNELID:"5a0458dc28a06145e4519d21",AV_URL:window.location.hostname,cb:Date.now(),AV_WIDTH:640,AV_HEIGHT:480,AV_CDIM2:e.publisherId})}`}}function Ge(e=0,t=0,i=255){return Math.min(Math.max(e,t),i)}const Ze=e=>{const t=[];return e.split(/\r\n\r\n|\n\n|\r\r/).forEach((e=>{const i={};e.split(/\r\n|\n|\r/).forEach((e=>{if(S.number(i.startTime)){if(!S.empty(e.trim())&&S.empty(i.text)){const t=e.trim().split("#xywh=");[i.text]=t,t[1]&&([i.x,i.y,i.w,i.h]=t[1].split(","))}}else{const t=e.match(/([0-9]{2})?:?([0-9]{2}):([0-9]{2}).([0-9]{2,3})( ?--> ?)([0-9]{2})?:?([0-9]{2}):([0-9]{2}).([0-9]{2,3})/);t&&(i.startTime=60*Number(t[1]||0)*60+60*Number(t[2])+Number(t[3])+Number(`0.${t[4]}`),i.endTime=60*Number(t[6]||0)*60+60*Number(t[7])+Number(t[8])+Number(`0.${t[9]}`))}})),i.text&&t.push(i)})),t},et=(e,t)=>{const i={};return e>t.width/t.height?(i.width=t.width,i.height=1/e*t.width):(i.height=t.height,i.width=e*t.height),i};class tt{constructor(t){e(this,"load",(()=>{this.player.elements.display.seekTooltip&&(this.player.elements.display.seekTooltip.hidden=this.enabled),this.enabled&&this.getThumbnails().then((()=>{this.enabled&&(this.render(),this.determineContainerAutoSizing(),this.listeners(),this.loaded=!0)}))})),e(this,"getThumbnails",(()=>new Promise((e=>{const{src:t}=this.player.config.previewThumbnails;if(S.empty(t))throw new Error("Missing previewThumbnails.src config attribute");const i=()=>{this.thumbnails.sort(((e,t)=>e.height-t.height)),this.player.debug.log("Preview thumbnails",this.thumbnails),e()};if(S.function(t))t((e=>{this.thumbnails=e,i()}));else{const e=(S.string(t)?[t]:t).map((e=>this.getThumbnail(e)));Promise.all(e).then(i)}})))),e(this,"getThumbnail",(e=>new Promise((t=>{Te(e).then((i=>{const s={frames:Ze(i),height:null,urlPrefix:""};s.frames[0].text.startsWith("/")||s.frames[0].text.startsWith("http://")||s.frames[0].text.startsWith("https://")||(s.urlPrefix=e.substring(0,e.lastIndexOf("/")+1));const n=new Image;n.onload=()=>{s.height=n.naturalHeight,s.width=n.naturalWidth,this.thumbnails.push(s),t()},n.src=s.urlPrefix+s.frames[0].text}))})))),e(this,"startMove",(e=>{if(this.loaded&&S.event(e)&&["touchmove","mousemove"].includes(e.type)&&this.player.media.duration){if("touchmove"===e.type)this.seekTime=this.player.media.duration*(this.player.elements.inputs.seek.value/100);else{var t,i;const s=this.player.elements.progress.getBoundingClientRect(),n=100/s.width*(e.pageX-s.left);this.seekTime=this.player.media.duration*(n/100),this.seekTime<0&&(this.seekTime=0),this.seekTime>this.player.media.duration-1&&(this.seekTime=this.player.media.duration-1),this.mousePosX=e.pageX,this.elements.thumb.time.innerText=Ee(this.seekTime);const a=null===(t=this.player.config.markers)||void 0===t||null===(i=t.points)||void 0===i?void 0:i.find((({time:e})=>e===Math.round(this.seekTime)));a&&this.elements.thumb.time.insertAdjacentHTML("afterbegin",`${a.label}
`)}this.showImageAtCurrentTime()}})),e(this,"endMove",(()=>{this.toggleThumbContainer(!1,!0)})),e(this,"startScrubbing",(e=>{(S.nullOrUndefined(e.button)||!1===e.button||0===e.button)&&(this.mouseDown=!0,this.player.media.duration&&(this.toggleScrubbingContainer(!0),this.toggleThumbContainer(!1,!0),this.showImageAtCurrentTime()))})),e(this,"endScrubbing",(()=>{this.mouseDown=!1,Math.ceil(this.lastTime)===Math.ceil(this.player.media.currentTime)?this.toggleScrubbingContainer(!1):G.call(this.player,this.player.media,"timeupdate",(()=>{this.mouseDown||this.toggleScrubbingContainer(!1)}))})),e(this,"listeners",(()=>{this.player.on("play",(()=>{this.toggleThumbContainer(!1,!0)})),this.player.on("seeked",(()=>{this.toggleThumbContainer(!1)})),this.player.on("timeupdate",(()=>{this.lastTime=this.player.media.currentTime}))})),e(this,"render",(()=>{this.elements.thumb.container=$("div",{class:this.player.config.classNames.previewThumbnails.thumbContainer}),this.elements.thumb.imageContainer=$("div",{class:this.player.config.classNames.previewThumbnails.imageContainer}),this.elements.thumb.container.appendChild(this.elements.thumb.imageContainer);const e=$("div",{class:this.player.config.classNames.previewThumbnails.timeContainer});this.elements.thumb.time=$("span",{},"00:00"),e.appendChild(this.elements.thumb.time),this.elements.thumb.imageContainer.appendChild(e),S.element(this.player.elements.progress)&&this.player.elements.progress.appendChild(this.elements.thumb.container),this.elements.scrubbing.container=$("div",{class:this.player.config.classNames.previewThumbnails.scrubbingContainer}),this.player.elements.wrapper.appendChild(this.elements.scrubbing.container)})),e(this,"destroy",(()=>{this.elements.thumb.container&&this.elements.thumb.container.remove(),this.elements.scrubbing.container&&this.elements.scrubbing.container.remove()})),e(this,"showImageAtCurrentTime",(()=>{this.mouseDown?this.setScrubbingContainerSize():this.setThumbContainerSizeAndPos();const e=this.thumbnails[0].frames.findIndex((e=>this.seekTime>=e.startTime&&this.seekTime<=e.endTime)),t=e>=0;let i=0;this.mouseDown||this.toggleThumbContainer(t),t&&(this.thumbnails.forEach(((t,s)=>{this.loadedImages.includes(t.frames[e].text)&&(i=s)})),e!==this.showingThumb&&(this.showingThumb=e,this.loadImage(i)))})),e(this,"loadImage",((e=0)=>{const t=this.showingThumb,i=this.thumbnails[e],{urlPrefix:s}=i,n=i.frames[t],a=i.frames[t].text,l=s+a;if(this.currentImageElement&&this.currentImageElement.dataset.filename===a)this.showImage(this.currentImageElement,n,e,t,a,!1),this.currentImageElement.dataset.index=t,this.removeOldImages(this.currentImageElement);else{this.loadingImage&&this.usingSprites&&(this.loadingImage.onload=null);const i=new Image;i.src=l,i.dataset.index=t,i.dataset.filename=a,this.showingThumbFilename=a,this.player.debug.log(`Loading image: ${l}`),i.onload=()=>this.showImage(i,n,e,t,a,!0),this.loadingImage=i,this.removeOldImages(i)}})),e(this,"showImage",((e,t,i,s,n,a=!0)=>{this.player.debug.log(`Showing thumb: ${n}. num: ${s}. qual: ${i}. newimg: ${a}`),this.setImageSizeAndOffset(e,t),a&&(this.currentImageContainer.appendChild(e),this.currentImageElement=e,this.loadedImages.includes(n)||this.loadedImages.push(n)),this.preloadNearby(s,!0).then(this.preloadNearby(s,!1)).then(this.getHigherQuality(i,e,t,n))})),e(this,"removeOldImages",(e=>{Array.from(this.currentImageContainer.children).forEach((t=>{if("img"!==t.tagName.toLowerCase())return;const i=this.usingSprites?500:1e3;if(t.dataset.index!==e.dataset.index&&!t.dataset.deleting){t.dataset.deleting=!0;const{currentImageContainer:e}=this;setTimeout((()=>{e.removeChild(t),this.player.debug.log(`Removing thumb: ${t.dataset.filename}`)}),i)}}))})),e(this,"preloadNearby",((e,t=!0)=>new Promise((i=>{setTimeout((()=>{const s=this.thumbnails[0].frames[e].text;if(this.showingThumbFilename===s){let n;n=t?this.thumbnails[0].frames.slice(e):this.thumbnails[0].frames.slice(0,e).reverse();let a=!1;n.forEach((e=>{const t=e.text;if(t!==s&&!this.loadedImages.includes(t)){a=!0,this.player.debug.log(`Preloading thumb filename: ${t}`);const{urlPrefix:e}=this.thumbnails[0],s=e+t,n=new Image;n.src=s,n.onload=()=>{this.player.debug.log(`Preloaded thumb filename: ${t}`),this.loadedImages.includes(t)||this.loadedImages.push(t),i()}}})),a||i()}}),300)})))),e(this,"getHigherQuality",((e,t,i,s)=>{if(e{this.showingThumbFilename===s&&(this.player.debug.log(`Showing higher quality thumb for: ${s}`),this.loadImage(e+1))}),300)}})),e(this,"toggleThumbContainer",((e=!1,t=!1)=>{const i=this.player.config.classNames.previewThumbnails.thumbContainerShown;this.elements.thumb.container.classList.toggle(i,e),!e&&t&&(this.showingThumb=null,this.showingThumbFilename=null)})),e(this,"toggleScrubbingContainer",((e=!1)=>{const t=this.player.config.classNames.previewThumbnails.scrubbingContainerShown;this.elements.scrubbing.container.classList.toggle(t,e),e||(this.showingThumb=null,this.showingThumbFilename=null)})),e(this,"determineContainerAutoSizing",(()=>{(this.elements.thumb.imageContainer.clientHeight>20||this.elements.thumb.imageContainer.clientWidth>20)&&(this.sizeSpecifiedInCSS=!0)})),e(this,"setThumbContainerSizeAndPos",(()=>{const{imageContainer:e}=this.elements.thumb;if(this.sizeSpecifiedInCSS){if(e.clientHeight>20&&e.clientWidth<20){const t=Math.floor(e.clientHeight*this.thumbAspectRatio);e.style.width=`${t}px`}else if(e.clientHeight<20&&e.clientWidth>20){const t=Math.floor(e.clientWidth/this.thumbAspectRatio);e.style.height=`${t}px`}}else{const t=Math.floor(this.thumbContainerHeight*this.thumbAspectRatio);e.style.height=`${this.thumbContainerHeight}px`,e.style.width=`${t}px`}this.setThumbContainerPos()})),e(this,"setThumbContainerPos",(()=>{const e=this.player.elements.progress.getBoundingClientRect(),t=this.player.elements.container.getBoundingClientRect(),{container:i}=this.elements.thumb,s=t.left-e.left+10,n=t.right-e.left-i.clientWidth-10,a=this.mousePosX-e.left-i.clientWidth/2,l=Ge(a,s,n);i.style.left=`${l}px`,i.style.setProperty("--preview-arrow-offset",a-l+"px")})),e(this,"setScrubbingContainerSize",(()=>{const{width:e,height:t}=et(this.thumbAspectRatio,{width:this.player.media.clientWidth,height:this.player.media.clientHeight});this.elements.scrubbing.container.style.width=`${e}px`,this.elements.scrubbing.container.style.height=`${t}px`})),e(this,"setImageSizeAndOffset",((e,t)=>{if(!this.usingSprites)return;const i=this.thumbContainerHeight/t.h;e.style.height=e.naturalHeight*i+"px",e.style.width=e.naturalWidth*i+"px",e.style.left=`-${t.x*i}px`,e.style.top=`-${t.y*i}px`})),this.player=t,this.thumbnails=[],this.loaded=!1,this.lastMouseMoveTime=Date.now(),this.mouseDown=!1,this.loadedImages=[],this.elements={thumb:{},scrubbing:{}},this.load()}get enabled(){return this.player.isHTML5&&this.player.isVideo&&this.player.config.previewThumbnails.enabled}get currentImageContainer(){return this.mouseDown?this.elements.scrubbing.container:this.elements.thumb.imageContainer}get usingSprites(){return Object.keys(this.thumbnails[0].frames[0]).includes("w")}get thumbAspectRatio(){return this.usingSprites?this.thumbnails[0].frames[0].w/this.thumbnails[0].frames[0].h:this.thumbnails[0].width/this.thumbnails[0].height}get thumbContainerHeight(){if(this.mouseDown){const{height:e}=et(this.thumbAspectRatio,{width:this.player.media.clientWidth,height:this.player.media.clientHeight});return e}return this.sizeSpecifiedInCSS?this.elements.thumb.imageContainer.clientHeight:Math.floor(this.player.media.clientWidth/this.thumbAspectRatio/4)}get currentImageElement(){return this.mouseDown?this.currentScrubbingImageElement:this.currentThumbnailImageElement}set currentImageElement(e){this.mouseDown?this.currentScrubbingImageElement=e:this.currentThumbnailImageElement=e}}const it={insertElements(e,t){S.string(t)?_(e,this.media,{src:t}):S.array(t)&&t.forEach((t=>{_(e,this.media,t)}))},change(e){N(e,"sources.length")?(de.cancelRequests.call(this),this.destroy.call(this,(()=>{this.options.quality=[],O(this.media),this.media=null,S.element(this.elements.container)&&this.elements.container.removeAttribute("class");const{sources:t,type:i}=e,[{provider:s=_e.html5,src:n}]=t,a="html5"===s?i:"div",l="html5"===s?{}:{src:n};Object.assign(this,{provider:s,type:i,supported:K.check(i,s,this.config.playsinline),media:$(a,l)}),this.elements.container.appendChild(this.media),S.boolean(e.autoplay)&&(this.config.autoplay=e.autoplay),this.isHTML5&&(this.config.crossorigin&&this.media.setAttribute("crossorigin",""),this.config.autoplay&&this.media.setAttribute("autoplay",""),S.empty(e.poster)||(this.poster=e.poster),this.config.loop.active&&this.media.setAttribute("loop",""),this.config.muted&&this.media.setAttribute("muted",""),this.config.playsinline&&this.media.setAttribute("playsinline","")),Fe.addStyleHook.call(this),this.isHTML5&&it.insertElements.call(this,"source",t),this.config.title=e.title,Xe.setup.call(this),this.isHTML5&&Object.keys(e).includes("tracks")&&it.insertElements.call(this,"track",e.tracks),(this.isHTML5||this.isEmbed&&!this.supported.ui)&&Fe.build.call(this),this.isHTML5&&this.media.load(),S.empty(e.previewThumbnails)||(Object.assign(this.config.previewThumbnails,e.previewThumbnails),this.previewThumbnails&&this.previewThumbnails.loaded&&(this.previewThumbnails.destroy(),this.previewThumbnails=null),this.config.previewThumbnails.enabled&&(this.previewThumbnails=new tt(this))),this.fullscreen.update()}),!0)):this.debug.warn("Invalid source format")}};class st{constructor(t,i){if(e(this,"play",(()=>S.function(this.media.play)?(this.ads&&this.ads.enabled&&this.ads.managerPromise.then((()=>this.ads.play())).catch((()=>ie(this.media.play()))),this.media.play()):null)),e(this,"pause",(()=>this.playing&&S.function(this.media.pause)?this.media.pause():null)),e(this,"togglePlay",(e=>(S.boolean(e)?e:!this.playing)?this.play():this.pause())),e(this,"stop",(()=>{this.isHTML5?(this.pause(),this.restart()):S.function(this.media.stop)&&this.media.stop()})),e(this,"restart",(()=>{this.currentTime=0})),e(this,"rewind",(e=>{this.currentTime-=S.number(e)?e:this.config.seekTime})),e(this,"forward",(e=>{this.currentTime+=S.number(e)?e:this.config.seekTime})),e(this,"increaseVolume",(e=>{const t=this.media.muted?0:this.volume;this.volume=t+(S.number(e)?e:0)})),e(this,"decreaseVolume",(e=>{this.increaseVolume(-e)})),e(this,"airplay",(()=>{K.airplay&&this.media.webkitShowPlaybackTargetPicker()})),e(this,"toggleControls",(e=>{if(this.supported.ui&&!this.isAudio){const t=F(this.elements.container,this.config.classNames.hideControls),i=void 0===e?void 0:!e,s=R(this.elements.container,this.config.classNames.hideControls,i);if(s&&S.array(this.config.controls)&&this.config.controls.includes("settings")&&!S.empty(this.config.settings)&&Pe.toggleMenu.call(this,!1),s!==t){const e=s?"controlshidden":"controlsshown";Z.call(this,this.media,e)}return!s}return!1})),e(this,"on",((e,t)=>{X.call(this,this.elements.container,e,t)})),e(this,"once",((e,t)=>{G.call(this,this.elements.container,e,t)})),e(this,"off",((e,t)=>{J(this.elements.container,e,t)})),e(this,"destroy",((e,t=!1)=>{if(!this.ready)return;const i=()=>{document.body.style.overflow="",this.embed=null,t?(Object.keys(this.elements).length&&(O(this.elements.buttons.play),O(this.elements.captions),O(this.elements.controls),O(this.elements.wrapper),this.elements.buttons.play=null,this.elements.captions=null,this.elements.controls=null,this.elements.wrapper=null),S.function(e)&&e()):(ee.call(this),de.cancelRequests.call(this),q(this.elements.original,this.elements.container),Z.call(this,this.elements.original,"destroyed",!0),S.function(e)&&e.call(this.elements.original),this.ready=!1,setTimeout((()=>{this.elements=null,this.media=null}),200))};this.stop(),clearTimeout(this.timers.loading),clearTimeout(this.timers.controls),clearTimeout(this.timers.resized),this.isHTML5?(Fe.toggleNativeControls.call(this,!0),i()):this.isYouTube?(clearInterval(this.timers.buffering),clearInterval(this.timers.playing),null!==this.embed&&S.function(this.embed.destroy)&&this.embed.destroy(),i()):this.isVimeo&&(null!==this.embed&&this.embed.unload().then(i),setTimeout(i,200))})),e(this,"supports",(e=>K.mime.call(this,e))),this.timers={},this.ready=!1,this.loading=!1,this.failed=!1,this.touch=K.touch,this.media=t,S.string(this.media)&&(this.media=document.querySelectorAll(this.media)),(window.jQuery&&this.media instanceof jQuery||S.nodeList(this.media)||S.array(this.media))&&(this.media=this.media[0]),this.config=x({},Le,st.defaults,i||{},(()=>{try{return JSON.parse(this.media.getAttribute("data-plyr-config"))}catch(e){return{}}})()),this.elements={container:null,fullscreen:null,captions:null,buttons:{},display:{},progress:{},inputs:{},settings:{popup:null,menu:null,panels:{},buttons:{}}},this.captions={active:null,currentTrack:-1,meta:new WeakMap},this.fullscreen={active:!1},this.options={speed:[],quality:[]},this.debug=new De(this.config.debug),this.debug.log("Config",this.config),this.debug.log("Support",K),S.nullOrUndefined(this.media)||!S.element(this.media))return void this.debug.error("Setup failed: no suitable element passed");if(this.media.plyr)return void this.debug.warn("Target already setup");if(!this.config.enabled)return void this.debug.error("Setup failed: disabled by config");if(!K.check().api)return void this.debug.error("Setup failed: no support");const s=this.media.cloneNode(!0);s.autoplay=!1,this.elements.original=s;const n=this.media.tagName.toLowerCase();let a=null,l=null;switch(n){case"div":if(a=this.media.querySelector("iframe"),S.element(a)){if(l=Me(a.getAttribute("src")),this.provider=function(e){return/^(https?:\/\/)?(www\.)?(youtube\.com|youtube-nocookie\.com|youtu\.?be)\/.+$/.test(e)?_e.youtube:/^https?:\/\/player.vimeo.com\/video\/\d{0,9}(?=\b|\/)/.test(e)?_e.vimeo:null}(l.toString()),this.elements.container=this.media,this.media=a,this.elements.container.className="",l.search.length){const e=["1","true"];e.includes(l.searchParams.get("autoplay"))&&(this.config.autoplay=!0),e.includes(l.searchParams.get("loop"))&&(this.config.loop.active=!0),this.isYouTube?(this.config.playsinline=e.includes(l.searchParams.get("playsinline")),this.config.youtube.hl=l.searchParams.get("hl")):this.config.playsinline=!0}}else this.provider=this.media.getAttribute(this.config.attributes.embed.provider),this.media.removeAttribute(this.config.attributes.embed.provider);if(S.empty(this.provider)||!Object.values(_e).includes(this.provider))return void this.debug.error("Setup failed: Invalid provider");this.type=je;break;case"video":case"audio":this.type=n,this.provider=_e.html5,this.media.hasAttribute("crossorigin")&&(this.config.crossorigin=!0),this.media.hasAttribute("autoplay")&&(this.config.autoplay=!0),(this.media.hasAttribute("playsinline")||this.media.hasAttribute("webkit-playsinline"))&&(this.config.playsinline=!0),this.media.hasAttribute("muted")&&(this.config.muted=!0),this.media.hasAttribute("loop")&&(this.config.loop.active=!0);break;default:return void this.debug.error("Setup failed: unsupported type")}this.supported=K.check(this.type,this.provider),this.supported.api?(this.eventListeners=[],this.listeners=new Ve(this),this.storage=new we(this),this.media.plyr=this,S.element(this.elements.container)||(this.elements.container=$("div"),L(this.media,this.elements.container)),Fe.migrateStyles.call(this),Fe.addStyleHook.call(this),Xe.setup.call(this),this.config.debug&&X.call(this,this.elements.container,this.config.events.join(" "),(e=>{this.debug.log(`event: ${e.type}`)})),this.fullscreen=new He(this),(this.isHTML5||this.isEmbed&&!this.supported.ui)&&Fe.build.call(this),this.listeners.container(),this.listeners.global(),this.config.ads.enabled&&(this.ads=new Je(this)),this.isHTML5&&this.config.autoplay&&this.once("canplay",(()=>ie(this.play()))),this.lastSeekTime=0,this.config.previewThumbnails.enabled&&(this.previewThumbnails=new tt(this))):this.debug.error("Setup failed: no support")}get isHTML5(){return this.provider===_e.html5}get isEmbed(){return this.isYouTube||this.isVimeo}get isYouTube(){return this.provider===_e.youtube}get isVimeo(){return this.provider===_e.vimeo}get isVideo(){return this.type===je}get isAudio(){return this.type===Oe}get playing(){return Boolean(this.ready&&!this.paused&&!this.ended)}get paused(){return Boolean(this.media.paused)}get stopped(){return Boolean(this.paused&&0===this.currentTime)}get ended(){return Boolean(this.media.ended)}set currentTime(e){if(!this.duration)return;const t=S.number(e)&&e>0;this.media.currentTime=t?Math.min(e,this.duration):0,this.debug.log(`Seeking to ${this.currentTime} seconds`)}get currentTime(){return Number(this.media.currentTime)}get buffered(){const{buffered:e}=this.media;return S.number(e)?e:e&&e.length&&this.duration>0?e.end(0)/this.duration:0}get seeking(){return Boolean(this.media.seeking)}get duration(){const e=parseFloat(this.config.duration),t=(this.media||{}).duration,i=S.number(t)&&t!==1/0?t:0;return e||i}set volume(e){let t=e;S.string(t)&&(t=Number(t)),S.number(t)||(t=this.storage.get("volume")),S.number(t)||({volume:t}=this.config),t>1&&(t=1),t<0&&(t=0),this.config.volume=t,this.media.volume=t,!S.empty(e)&&this.muted&&t>0&&(this.muted=!1)}get volume(){return Number(this.media.volume)}set muted(e){let t=e;S.boolean(t)||(t=this.storage.get("muted")),S.boolean(t)||(t=this.config.muted),this.config.muted=t,this.media.muted=t}get muted(){return Boolean(this.media.muted)}get hasAudio(){return!this.isHTML5||(!!this.isAudio||(Boolean(this.media.mozHasAudio)||Boolean(this.media.webkitAudioDecodedByteCount)||Boolean(this.media.audioTracks&&this.media.audioTracks.length)))}set speed(e){let t=null;S.number(e)&&(t=e),S.number(t)||(t=this.storage.get("speed")),S.number(t)||(t=this.config.speed.selected);const{minimumSpeed:i,maximumSpeed:s}=this;t=Ge(t,i,s),this.config.speed.selected=t,setTimeout((()=>{this.media&&(this.media.playbackRate=t)}),0)}get speed(){return Number(this.media.playbackRate)}get minimumSpeed(){return this.isYouTube?Math.min(...this.options.speed):this.isVimeo?.5:.0625}get maximumSpeed(){return this.isYouTube?Math.max(...this.options.speed):this.isVimeo?2:16}set quality(e){const t=this.config.quality,i=this.options.quality;if(!i.length)return;let s=[!S.empty(e)&&Number(e),this.storage.get("quality"),t.selected,t.default].find(S.number),n=!0;if(!i.includes(s)){const e=ne(i,s);this.debug.warn(`Unsupported quality option: ${s}, using ${e} instead`),s=e,n=!1}t.selected=s,this.media.quality=s,n&&this.storage.set({quality:s})}get quality(){return this.media.quality}set loop(e){const t=S.boolean(e)?e:this.config.loop.active;this.config.loop.active=t,this.media.loop=t}get loop(){return Boolean(this.media.loop)}set source(e){it.change.call(this,e)}get source(){return this.media.currentSrc}get download(){const{download:e}=this.config.urls;return S.url(e)?e:this.source}set download(e){S.url(e)&&(this.config.urls.download=e,Pe.setDownloadUrl.call(this))}set poster(e){this.isVideo?Fe.setPoster.call(this,e,!1).catch((()=>{})):this.debug.warn("Poster can only be set for video")}get poster(){return this.isVideo?this.media.getAttribute("poster")||this.media.getAttribute("data-poster"):null}get ratio(){if(!this.isVideo)return null;const e=oe(ce.call(this));return S.array(e)?e.join(":"):e}set ratio(e){this.isVideo?S.string(e)&&re(e)?(this.config.ratio=oe(e),ue.call(this)):this.debug.error(`Invalid aspect ratio specified (${e})`):this.debug.warn("Aspect ratio can only be set for video")}set autoplay(e){this.config.autoplay=S.boolean(e)?e:this.config.autoplay}get autoplay(){return Boolean(this.config.autoplay)}toggleCaptions(e){xe.toggle.call(this,e,!1)}set currentTrack(e){xe.set.call(this,e,!1),xe.setup.call(this)}get currentTrack(){const{toggled:e,currentTrack:t}=this.captions;return e?t:-1}set language(e){xe.setLanguage.call(this,e,!1)}get language(){return(xe.getCurrentTrack.call(this)||{}).language}set pip(e){if(!K.pip)return;const t=S.boolean(e)?e:!this.pip;S.function(this.media.webkitSetPresentationMode)&&this.media.webkitSetPresentationMode(t?Ie:$e),S.function(this.media.requestPictureInPicture)&&(!this.pip&&t?this.media.requestPictureInPicture():this.pip&&!t&&document.exitPictureInPicture())}get pip(){return K.pip?S.empty(this.media.webkitPresentationMode)?this.media===document.pictureInPictureElement:this.media.webkitPresentationMode===Ie:null}setPreviewThumbnails(e){this.previewThumbnails&&this.previewThumbnails.loaded&&(this.previewThumbnails.destroy(),this.previewThumbnails=null),Object.assign(this.config.previewThumbnails,e),this.config.previewThumbnails.enabled&&(this.previewThumbnails=new tt(this))}static supported(e,t){return K.check(e,t)}static loadSprite(e,t){return ke(e,t)}static setup(e,t={}){let i=null;return S.string(e)?i=Array.from(document.querySelectorAll(e)):S.nodeList(e)?i=Array.from(e):S.array(e)&&(i=e.filter(S.element)),S.empty(i)?null:i.map((e=>new st(e,t)))}}var nt;return st.defaults=(nt=Le,JSON.parse(JSON.stringify(nt))),st})); //# sourceMappingURL=plyr.min.js.map diff --git a/node_modules/plyr/dist/plyr.min.js.map b/node_modules/plyr/dist/plyr.min.js.map index 4bf012a..7336008 100644 --- a/node_modules/plyr/dist/plyr.min.js.map +++ b/node_modules/plyr/dist/plyr.min.js.map @@ -1 +1 @@ -{"version":3,"sources":["plyr.js","node_modules/.pnpm/rangetouch@2.0.1/node_modules/rangetouch/dist/rangetouch.mjs","src/js/utils/is.js","src/js/utils/animation.js","src/js/utils/browser.js","src/js/utils/objects.js","src/js/utils/elements.js","src/js/support.js","src/js/utils/events.js","src/js/utils/promise.js","src/js/utils/arrays.js","src/js/utils/style.js","src/js/html5.js","src/js/utils/strings.js","src/js/utils/i18n.js","src/js/storage.js","src/js/utils/fetch.js","src/js/utils/load-sprite.js","src/js/utils/time.js","src/js/controls.js","src/js/utils/urls.js","src/js/captions.js","src/js/config/defaults.js","src/js/config/states.js","src/js/config/types.js","src/js/console.js","src/js/fullscreen.js","src/js/utils/load-image.js","src/js/ui.js","src/js/listeners.js","node_modules/.pnpm/loadjs@4.2.0/node_modules/loadjs/dist/loadjs.umd.js","src/js/utils/load-script.js","src/js/plugins/vimeo.js","src/js/plugins/youtube.js","src/js/media.js","src/js/plugins/ads.js","src/js/utils/numbers.js","src/js/plugins/preview-thumbnails.js","src/js/source.js","src/js/plyr.js"],"names":["navigator","global","factory","exports","module","define","amd","globalThis","self","Plyr","this","_defineProperty$1","obj","key","value","arg","input","hint","prim","Symbol","toPrimitive","undefined","res","call","TypeError","String","Number","_toPrimitive","_toPropertyKey","Object","defineProperty","enumerable","configurable","writable","_defineProperties","e","t","n","length","r","_defineProperty","ownKeys","keys","getOwnPropertySymbols","filter","getOwnPropertyDescriptor","push","apply","_objectSpread2","arguments","forEach","getOwnPropertyDescriptors","defineProperties","defaults","addCSS","thumbWidth","watch","getConstructor","constructor","instanceOf","isNullOrUndefined","isObject","isString","isArray","Array","isNodeList","NodeList","is","nullOrUndefined","object","number","isNaN","string","boolean","Boolean","function","Function","array","nodeList","element","Element","event","Event","empty","round","concat","match","Math","max","getDecimalPlaces","parseFloat","toFixed","RangeTouch","_classCallCheck","document","querySelector","rangeTouch","config","init","prototype","_createClass","enabled","style","userSelect","webKitUserSelect","touchAction","listeners","set","target","i","changedTouches","o","getAttribute","s","u","c","getBoundingClientRect","a","width","clientX","left","disabled","preventDefault","get","bubbles","dispatchEvent","trigger","type","from","querySelectorAll","MutationObserver","addedNodes","includes","matches","observe","body","childList","subtree","map","documentElement","isFunction","isEmpty","weakMap","WeakMap","nodeType","ownerDocument","textNode","Text","keyboardEvent","KeyboardEvent","cue","window","TextTrackCue","VTTCue","track","TextTrack","kind","promise","Promise","then","url","URL","startsWith","hostname","_","transitionEndEvent","createElement","events","WebkitTransition","MozTransition","OTransition","transition","find","repaint","delay","setTimeout","hidden","offsetHeight","browser","isIE","documentMode","isEdge","test","userAgent","isWebKit","isIPhone","maxTouchPoints","isIPadOS","platform","isIos","getDeep","path","split","reduce","extend","sources","source","shift","assign","wrap","elements","wrapper","targets","reverse","index","child","cloneNode","parent","parentNode","sibling","nextSibling","appendChild","insertBefore","setAttributes","attributes","entries","setAttribute","text","innerText","insertElement","removeElement","removeChild","emptyElement","childNodes","lastChild","replaceElement","newChild","oldChild","replaceChild","getAttributesFromSelector","sel","existingAttributes","existing","selector","trim","className","replace","parts","charAt","class","id","toggleHidden","hide","toggleClass","force","method","classList","contains","hasClass","webkitMatchesSelector","mozMatchesSelector","msMatchesSelector","getElements","container","getElement","setFocus","focusVisible","focus","preventScroll","defaultCodecs","support","audio","video","check","provider","api","ui","rangeInput","pip","webkitSetPresentationMode","pictureInPictureEnabled","disablePictureInPicture","airplay","WebKitPlaybackTargetAvailabilityEvent","playsinline","mime","mediaType","isHTML5","media","canPlayType","textTracks","range","touch","transitions","reducedMotion","matchMedia","supportsPassiveListeners","supported","options","addEventListener","removeEventListener","toggleListener","callback","toggle","passive","capture","eventListeners","on","off","once","onceCallback","args","triggerEvent","detail","CustomEvent","plyr","unbindListeners","item","ready","resolve","silencePromise","dedupe","indexOf","closest","prev","curr","abs","supportsCSS","declaration","CSS","supports","standardRatios","out","x","y","validateAspectRatio","every","reduceAspectRatio","ratio","height","getDivider","w","h","divider","getAspectRatio","parse","embed","videoWidth","videoHeight","setAspectRatio","isVideo","padding","aspectRatio","paddingBottom","isVimeo","vimeo","premium","offsetWidth","parseInt","getComputedStyle","offset","fullscreen","active","transform","add","classNames","videoFixedRatio","roundAspectRatio","tolerance","closestRatio","html5","getSources","getQualityOptions","quality","forced","setup","player","speed","onChange","currentTime","paused","preload","readyState","playbackRate","src","play","load","cancelRequests","blankVideo","debug","log","format","toString","replaceAll","RegExp","toTitleCase","toUpperCase","slice","toLowerCase","toCamelCase","toPascalCase","getHTML","innerHTML","resources","youtube","i18n","seekTime","title","k","v","Storage","store","localStorage","getItem","json","JSON","storage","setItem","stringify","removeItem","fetch","responseType","reject","request","XMLHttpRequest","responseText","response","Error","status","open","send","error","loadSprite","prefix","hasId","isCached","exists","getElementById","update","data","insertAdjacentElement","useStorage","cached","content","result","catch","getHours","trunc","getMinutes","getSeconds","formatTime","time","displayHours","inverted","hours","mins","secs","controls","getIconUrl","iconUrl","location","host","top","cors","svg4everybody","findElements","selectors","buttons","pause","restart","rewind","fastForward","mute","settings","captions","progress","inputs","seek","volume","display","buffer","duration","seekTooltip","tooltip","warn","toggleNativeControls","createIcon","namespace","iconPath","iconPrefix","icon","createElementNS","focusable","use","setAttributeNS","createLabel","attr","join","createBadge","badge","menu","createButton","buttonType","props","label","labelPressed","iconPressed","some","control","button","createRange","min","step","autocomplete","role","updateRangeFill","createProgress","suffixKey","played","suffix","createTime","attrs","bindMenuItemShortcuts","menuItem","stopPropagation","isRadioButton","showMenuPanel","nextElementSibling","firstElementChild","previousElementSibling","lastElementChild","focusFirstMenuItem","createMenuItem","list","checked","flex","children","node","bind","currentTrack","updateTimeDisplay","updateVolume","setRange","muted","pressed","updateProgress","setProgress","val","getElementsByTagName","nodeValue","current","buffered","percent","setProperty","updateSeekTooltip","_this$config$markers","_this$config$markers$","tooltips","tipElement","visible","show","clientRect","pageX","point","markers","points","insertAdjacentHTML","timeUpdate","invert","invertTime","seeking","durationUpdate","hasDuration","displayDuration","setMarkers","toggleMenuButton","setting","updateSetting","pane","panels","default","getLabel","setQualityMenu","checkMenu","getBadge","sort","b","sorting","setCaptionsMenu","tracks","getTracks","toggled","language","unshift","setSpeedMenu","minimumSpeed","maximumSpeed","values","popup","p","firstItem","toggleMenu","composedPath","isMenuItem","getMenuSize","tab","clone","position","opacity","removeAttribute","scrollWidth","scrollHeight","size","restore","propertyName","setDownloadUrl","download","create","defaultAttributes","progressContainer","inner","home","backButton","href","urls","isEmbed","inject","floor","random","seektime","addProperty","controlPressed","labels","setMediaMetadata","mediaSession","metadata","MediaMetadata","mediaMetadata","artist","album","artwork","_this$config$markers2","_this$config$markers3","containerFragment","createDocumentFragment","pointsFragment","tipVisible","toggleTip","markerElement","marker","tip","parseUrl","safe","parser","buildUrlParams","params","URLSearchParams","isYouTube","protocol","blob","createObjectURL","languages","userLanguage","trackEvents","meta","currentTrackNode","languageExists","mode","updateCues","setLanguage","activeClass","findTrack","enableTextTrack","has","sortIsDefault","sorted","getCurrentTrack","cues","activeCues","getCueAsHTML","cueText","caption","autoplay","autopause","toggleInvert","clickToPlay","hideControls","resetOnEnd","disableContextMenu","loop","selected","keyboard","focused","fallback","iosNative","seekLabel","unmute","enableCaptions","disableCaptions","enterFullscreen","exitFullscreen","frameTitle","menuBack","normal","start","end","all","reset","advertisement","qualityBadge","sdk","iframe","googleIMA","editable","embedContainer","poster","posterEnabled","ads","playing","stopped","loading","hover","isTouch","uiSupported","noTransition","previewThumbnails","thumbContainer","thumbContainerShown","imageContainer","timeContainer","scrubbingContainer","scrubbingContainerShown","hash","publisherId","tagUrl","byline","portrait","transparent","customControls","referrerPolicy","rel","showinfo","iv_load_policy","modestbranding","noCookie","providers","types","noop","Console","console","Fullscreen","scrollPosition","scrollX","scrollY","scrollTo","overflow","viewport","head","property","hasProperty","cleanupViewport","part","activeElement","first","last","shiftKey","forceFallback","nativeSupported","requestFullscreen","webkitEnterFullscreen","toggleFallback","navigationUI","action","cancelFullScreen","exit","enter","el","parentElement","proxy","trapFocus","fullscreenEnabled","webkitFullscreenEnabled","mozFullScreenEnabled","msFullscreenEnabled","useNative","pre","getRootNode","fullscreenElement","shadowRoot","loadImage","minWidth","image","Image","handler","onload","onerror","naturalWidth","addStyleHook","build","checkPlaying","setTitle","setPoster","togglePoster","enable","backgroundImage","backgroundSize","toggleControls","checkLoading","clearTimeout","timers","controlsElement","recentTouchSeek","lastSeekTime","Date","now","migrateStyles","getPropertyValue","removeProperty","Listeners","handleKey","firstTouch","setGutter","useNativeAspectRatio","maxWidth","margin","viewportWidth","viewportHeight","clientWidth","innerWidth","clientHeight","innerHeight","resized","isAudio","ended","togglePlay","proxyEvents","defaultHandler","customHandlerKey","customHandler","returned","hasCustomHandler","inputEvent","forward","toggleCaptions","rect","currentTarget","attribute","hasAttribute","done","seekTo","loaded","startMove","endMove","startScrubbing","endScrubbing","webkitDirectionInvertedFromDevice","deltaX","deltaY","direction","sign","increaseVolume","lastKey","focusTimer","lastKeyDown","altKey","ctrlKey","metaKey","repeat","increment","decreaseVolume","usingNative","loadjs_umd","fn","createCommonjsModule","devnull","bundleIdCache","bundleResultCache","bundleCallbackQueue","subscribe","bundleIds","callbackFn","bundleId","depsNotFound","numWaiting","pathsNotFound","publish","q","splice","executeCallbacks","success","loadFile","numTries","isLegacyIECss","doc","async","maxTries","numRetries","beforeCallbackFn","before","pathname","pathStripped","relList","as","onbeforeload","ev","sheet","cssText","code","defaultPrevented","loadFiles","paths","loadjs","arg1","arg2","loadFn","returnPromise","deps","isDefined","loadScript","assurePlaybackState","hasPlayed","Vimeo","frameParams","found","parseHash","hashParam","sidedock","gesture","$2","thumbnail_url","Player","disableTextTrack","stop","restorePause","setVolume","setCurrentTime","setPlaybackRate","setMuted","currentSrc","setLoop","getVideoUrl","getVideoWidth","getVideoHeight","dimensions","setAutopause","state","getVideoTitle","getCurrentTime","getDuration","getTextTracks","strippedCues","fragment","firstChild","stripHTML","getPaused","seconds","getHost","YT","onYouTubeIframeAPIReady","getTitle","videoId","currentId","posterSrc","playerVars","hl","disablekb","cc_load_policy","cc_lang_pref","widget_referrer","onError","message","onPlaybackRateChange","instance","getPlaybackRate","onReady","playVideo","pauseVideo","stopVideo","speeds","getAvailablePlaybackRates","clearInterval","buffering","setInterval","getVideoLoadedFraction","lastBuffered","onStateChange","unMute","Ads","google","ima","manager","destroy","displayContainer","remove","startSafetyTimer","managerPromise","clearSafetyTimer","setupIMA","setVpaidMode","ImaSdkSettings","VpaidMode","ENABLED","setLocale","setDisableCustomPlaybackForIOS10Plus","AdDisplayContainer","loader","AdsLoader","AdsManagerLoadedEvent","Type","ADS_MANAGER_LOADED","onAdsManagerLoaded","AdErrorEvent","AD_ERROR","onAdError","requestAds","AdsRequest","adTagUrl","linearAdSlotWidth","linearAdSlotHeight","nonLinearAdSlotWidth","nonLinearAdSlotHeight","forceNonLinearFullSlot","setAdWillPlayMuted","countdownTimer","getRemainingTime","AdsRenderingSettings","restoreCustomPlaybackStateOnAdBreakComplete","enablePreloading","getAdsManager","cuePoints","getCuePoints","AdEvent","onAdEvent","cuePoint","seekElement","cuePercentage","ad","getAd","adData","getAdData","LOADED","pollCountdown","isLinear","STARTED","ALL_ADS_COMPLETED","loadAds","contentComplete","CONTENT_PAUSE_REQUESTED","pauseContent","CONTENT_RESUME_REQUESTED","resumeContent","LOG","adError","getMessage","cancel","addCuePoints","seekedTime","discardAdBreak","resize","ViewMode","NORMAL","initialize","initialized","zIndex","handlers","safetyTimer","AV_PUBLISHERID","AV_CHANNELID","AV_URL","cb","AV_WIDTH","AV_HEIGHT","AV_CDIM2","clamp","parseVtt","vttDataString","processedList","frame","line","startTime","lineSplit","matchTimes","endTime","fitRatio","outer","PreviewThumbnails","getThumbnails","render","determineContainerAutoSizing","sortAndResolve","thumbnails","promises","getThumbnail","thumbnail","frames","urlPrefix","substring","lastIndexOf","tempImage","naturalHeight","_this$player$config$m","_this$player$config$m2","percentage","mousePosX","thumb","showImageAtCurrentTime","toggleThumbContainer","mouseDown","toggleScrubbingContainer","ceil","lastTime","scrubbing","setScrubbingContainerSize","setThumbContainerSizeAndPos","thumbNum","findIndex","hasThumb","qualityIndex","loadedImages","showingThumb","thumbFilename","thumbUrl","currentImageElement","dataset","filename","showImage","removeOldImages","loadingImage","usingSprites","previewImage","showingThumbFilename","newImage","setImageSizeAndOffset","currentImageContainer","preloadNearby","getHigherQuality","currentImage","tagName","removeDelay","deleting","oldThumbFilename","thumbnailsClone","foundOne","newThumbFilename","thumbURL","currentQualityIndex","previewImageHeight","thumbContainerHeight","clearShowing","sizeSpecifiedInCSS","thumbAspectRatio","thumbHeight","setThumbContainerPos","scrubberRect","containerRect","right","clamped","multiplier","lastMouseMoveTime","currentScrubbingImageElement","currentThumbnailImageElement","insertElements","change","crossorigin","webkitShowPlaybackTargetPicker","isHidden","hiding","eventName","soft","original","unload","failed","jQuery","getProviderByUrl","search","truthy","searchParams","inputIsValid","fauxDuration","realDuration","Infinity","hasAudio","mozHasAudio","webkitAudioDecodedByteCount","audioTracks","updateStorage","requestPictureInPicture","exitPictureInPicture","webkitPresentationMode","pictureInPictureElement","setPreviewThumbnails","thumbnailSource","static"],"mappings":"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","file":"plyr.min.js","sourcesContent":["typeof navigator === \"object\" && (function (global, factory) {\n typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :\n typeof define === 'function' && define.amd ? define('Plyr', factory) :\n (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Plyr = factory());\n})(this, (function () { 'use strict';\n\n function _defineProperty$1(obj, key, value) {\n key = _toPropertyKey(key);\n if (key in obj) {\n Object.defineProperty(obj, key, {\n value: value,\n enumerable: true,\n configurable: true,\n writable: true\n });\n } else {\n obj[key] = value;\n }\n return obj;\n }\n function _toPrimitive(input, hint) {\n if (typeof input !== \"object\" || input === null) return input;\n var prim = input[Symbol.toPrimitive];\n if (prim !== undefined) {\n var res = prim.call(input, hint || \"default\");\n if (typeof res !== \"object\") return res;\n throw new TypeError(\"@@toPrimitive must return a primitive value.\");\n }\n return (hint === \"string\" ? String : Number)(input);\n }\n function _toPropertyKey(arg) {\n var key = _toPrimitive(arg, \"string\");\n return typeof key === \"symbol\" ? key : String(key);\n }\n\n function _classCallCheck(e, t) {\n if (!(e instanceof t)) throw new TypeError(\"Cannot call a class as a function\");\n }\n function _defineProperties(e, t) {\n for (var n = 0; n < t.length; n++) {\n var r = t[n];\n r.enumerable = r.enumerable || !1, r.configurable = !0, \"value\" in r && (r.writable = !0), Object.defineProperty(e, r.key, r);\n }\n }\n function _createClass(e, t, n) {\n return t && _defineProperties(e.prototype, t), n && _defineProperties(e, n), e;\n }\n function _defineProperty(e, t, n) {\n return t in e ? Object.defineProperty(e, t, {\n value: n,\n enumerable: !0,\n configurable: !0,\n writable: !0\n }) : e[t] = n, e;\n }\n function ownKeys(e, t) {\n var n = Object.keys(e);\n if (Object.getOwnPropertySymbols) {\n var r = Object.getOwnPropertySymbols(e);\n t && (r = r.filter(function (t) {\n return Object.getOwnPropertyDescriptor(e, t).enumerable;\n })), n.push.apply(n, r);\n }\n return n;\n }\n function _objectSpread2(e) {\n for (var t = 1; t < arguments.length; t++) {\n var n = null != arguments[t] ? arguments[t] : {};\n t % 2 ? ownKeys(Object(n), !0).forEach(function (t) {\n _defineProperty(e, t, n[t]);\n }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(n)) : ownKeys(Object(n)).forEach(function (t) {\n Object.defineProperty(e, t, Object.getOwnPropertyDescriptor(n, t));\n });\n }\n return e;\n }\n var defaults$1 = {\n addCSS: !0,\n thumbWidth: 15,\n watch: !0\n };\n function matches$1(e, t) {\n return function () {\n return Array.from(document.querySelectorAll(t)).includes(this);\n }.call(e, t);\n }\n function trigger(e, t) {\n if (e && t) {\n var n = new Event(t, {\n bubbles: !0\n });\n e.dispatchEvent(n);\n }\n }\n var getConstructor$1 = function (e) {\n return null != e ? e.constructor : null;\n },\n instanceOf$1 = function (e, t) {\n return !!(e && t && e instanceof t);\n },\n isNullOrUndefined$1 = function (e) {\n return null == e;\n },\n isObject$1 = function (e) {\n return getConstructor$1(e) === Object;\n },\n isNumber$1 = function (e) {\n return getConstructor$1(e) === Number && !Number.isNaN(e);\n },\n isString$1 = function (e) {\n return getConstructor$1(e) === String;\n },\n isBoolean$1 = function (e) {\n return getConstructor$1(e) === Boolean;\n },\n isFunction$1 = function (e) {\n return getConstructor$1(e) === Function;\n },\n isArray$1 = function (e) {\n return Array.isArray(e);\n },\n isNodeList$1 = function (e) {\n return instanceOf$1(e, NodeList);\n },\n isElement$1 = function (e) {\n return instanceOf$1(e, Element);\n },\n isEvent$1 = function (e) {\n return instanceOf$1(e, Event);\n },\n isEmpty$1 = function (e) {\n return isNullOrUndefined$1(e) || (isString$1(e) || isArray$1(e) || isNodeList$1(e)) && !e.length || isObject$1(e) && !Object.keys(e).length;\n },\n is$1 = {\n nullOrUndefined: isNullOrUndefined$1,\n object: isObject$1,\n number: isNumber$1,\n string: isString$1,\n boolean: isBoolean$1,\n function: isFunction$1,\n array: isArray$1,\n nodeList: isNodeList$1,\n element: isElement$1,\n event: isEvent$1,\n empty: isEmpty$1\n };\n function getDecimalPlaces(e) {\n var t = \"\".concat(e).match(/(?:\\.(\\d+))?(?:[eE]([+-]?\\d+))?$/);\n return t ? Math.max(0, (t[1] ? t[1].length : 0) - (t[2] ? +t[2] : 0)) : 0;\n }\n function round(e, t) {\n if (1 > t) {\n var n = getDecimalPlaces(t);\n return parseFloat(e.toFixed(n));\n }\n return Math.round(e / t) * t;\n }\n var RangeTouch = function () {\n function e(t, n) {\n _classCallCheck(this, e), is$1.element(t) ? this.element = t : is$1.string(t) && (this.element = document.querySelector(t)), is$1.element(this.element) && is$1.empty(this.element.rangeTouch) && (this.config = _objectSpread2({}, defaults$1, {}, n), this.init());\n }\n return _createClass(e, [{\n key: \"init\",\n value: function () {\n e.enabled && (this.config.addCSS && (this.element.style.userSelect = \"none\", this.element.style.webKitUserSelect = \"none\", this.element.style.touchAction = \"manipulation\"), this.listeners(!0), this.element.rangeTouch = this);\n }\n }, {\n key: \"destroy\",\n value: function () {\n e.enabled && (this.config.addCSS && (this.element.style.userSelect = \"\", this.element.style.webKitUserSelect = \"\", this.element.style.touchAction = \"\"), this.listeners(!1), this.element.rangeTouch = null);\n }\n }, {\n key: \"listeners\",\n value: function (e) {\n var t = this,\n n = e ? \"addEventListener\" : \"removeEventListener\";\n [\"touchstart\", \"touchmove\", \"touchend\"].forEach(function (e) {\n t.element[n](e, function (e) {\n return t.set(e);\n }, !1);\n });\n }\n }, {\n key: \"get\",\n value: function (t) {\n if (!e.enabled || !is$1.event(t)) return null;\n var n,\n r = t.target,\n i = t.changedTouches[0],\n o = parseFloat(r.getAttribute(\"min\")) || 0,\n s = parseFloat(r.getAttribute(\"max\")) || 100,\n u = parseFloat(r.getAttribute(\"step\")) || 1,\n c = r.getBoundingClientRect(),\n a = 100 / c.width * (this.config.thumbWidth / 2) / 100;\n return 0 > (n = 100 / c.width * (i.clientX - c.left)) ? n = 0 : 100 < n && (n = 100), 50 > n ? n -= (100 - 2 * n) * a : 50 < n && (n += 2 * (n - 50) * a), o + round(n / 100 * (s - o), u);\n }\n }, {\n key: \"set\",\n value: function (t) {\n e.enabled && is$1.event(t) && !t.target.disabled && (t.preventDefault(), t.target.value = this.get(t), trigger(t.target, \"touchend\" === t.type ? \"change\" : \"input\"));\n }\n }], [{\n key: \"setup\",\n value: function (t) {\n var n = 1 < arguments.length && void 0 !== arguments[1] ? arguments[1] : {},\n r = null;\n if (is$1.empty(t) || is$1.string(t) ? r = Array.from(document.querySelectorAll(is$1.string(t) ? t : 'input[type=\"range\"]')) : is$1.element(t) ? r = [t] : is$1.nodeList(t) ? r = Array.from(t) : is$1.array(t) && (r = t.filter(is$1.element)), is$1.empty(r)) return null;\n var i = _objectSpread2({}, defaults$1, {}, n);\n if (is$1.string(t) && i.watch) {\n var o = new MutationObserver(function (n) {\n Array.from(n).forEach(function (n) {\n Array.from(n.addedNodes).forEach(function (n) {\n is$1.element(n) && matches$1(n, t) && new e(n, i);\n });\n });\n });\n o.observe(document.body, {\n childList: !0,\n subtree: !0\n });\n }\n return r.map(function (t) {\n return new e(t, n);\n });\n }\n }, {\n key: \"enabled\",\n get: function () {\n return \"ontouchstart\" in document.documentElement;\n }\n }]), e;\n }();\n\n // ==========================================================================\n // Type checking utils\n // ==========================================================================\n\n const getConstructor = input => input !== null && typeof input !== 'undefined' ? input.constructor : null;\n const instanceOf = (input, constructor) => Boolean(input && constructor && input instanceof constructor);\n const isNullOrUndefined = input => input === null || typeof input === 'undefined';\n const isObject = input => getConstructor(input) === Object;\n const isNumber = input => getConstructor(input) === Number && !Number.isNaN(input);\n const isString = input => getConstructor(input) === String;\n const isBoolean = input => getConstructor(input) === Boolean;\n const isFunction = input => typeof input === 'function';\n const isArray = input => Array.isArray(input);\n const isWeakMap = input => instanceOf(input, WeakMap);\n const isNodeList = input => instanceOf(input, NodeList);\n const isTextNode = input => getConstructor(input) === Text;\n const isEvent = input => instanceOf(input, Event);\n const isKeyboardEvent = input => instanceOf(input, KeyboardEvent);\n const isCue = input => instanceOf(input, window.TextTrackCue) || instanceOf(input, window.VTTCue);\n const isTrack = input => instanceOf(input, TextTrack) || !isNullOrUndefined(input) && isString(input.kind);\n const isPromise = input => instanceOf(input, Promise) && isFunction(input.then);\n const isElement = input => input !== null && typeof input === 'object' && input.nodeType === 1 && typeof input.style === 'object' && typeof input.ownerDocument === 'object';\n const isEmpty = input => isNullOrUndefined(input) || (isString(input) || isArray(input) || isNodeList(input)) && !input.length || isObject(input) && !Object.keys(input).length;\n const isUrl = input => {\n // Accept a URL object\n if (instanceOf(input, window.URL)) {\n return true;\n }\n\n // Must be string from here\n if (!isString(input)) {\n return false;\n }\n\n // Add the protocol if required\n let string = input;\n if (!input.startsWith('http://') || !input.startsWith('https://')) {\n string = `http://${input}`;\n }\n try {\n return !isEmpty(new URL(string).hostname);\n } catch (_) {\n return false;\n }\n };\n var is = {\n nullOrUndefined: isNullOrUndefined,\n object: isObject,\n number: isNumber,\n string: isString,\n boolean: isBoolean,\n function: isFunction,\n array: isArray,\n weakMap: isWeakMap,\n nodeList: isNodeList,\n element: isElement,\n textNode: isTextNode,\n event: isEvent,\n keyboardEvent: isKeyboardEvent,\n cue: isCue,\n track: isTrack,\n promise: isPromise,\n url: isUrl,\n empty: isEmpty\n };\n\n // ==========================================================================\n const transitionEndEvent = (() => {\n const element = document.createElement('span');\n const events = {\n WebkitTransition: 'webkitTransitionEnd',\n MozTransition: 'transitionend',\n OTransition: 'oTransitionEnd otransitionend',\n transition: 'transitionend'\n };\n const type = Object.keys(events).find(event => element.style[event] !== undefined);\n return is.string(type) ? events[type] : false;\n })();\n\n // Force repaint of element\n function repaint(element, delay) {\n setTimeout(() => {\n try {\n // eslint-disable-next-line no-param-reassign\n element.hidden = true;\n\n // eslint-disable-next-line no-unused-expressions\n element.offsetHeight;\n\n // eslint-disable-next-line no-param-reassign\n element.hidden = false;\n } catch (_) {\n // Do nothing\n }\n }, delay);\n }\n\n // ==========================================================================\n // Browser sniffing\n // Unfortunately, due to mixed support, UA sniffing is required\n // ==========================================================================\n\n const isIE = Boolean(window.document.documentMode);\n const isEdge = /Edge/g.test(navigator.userAgent);\n const isWebKit = 'WebkitAppearance' in document.documentElement.style && !/Edge/g.test(navigator.userAgent);\n const isIPhone = /iPhone|iPod/gi.test(navigator.userAgent) && navigator.maxTouchPoints > 1;\n // navigator.platform may be deprecated but this check is still required\n const isIPadOS = navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1;\n const isIos = /iPad|iPhone|iPod/gi.test(navigator.userAgent) && navigator.maxTouchPoints > 1;\n var browser = {\n isIE,\n isEdge,\n isWebKit,\n isIPhone,\n isIPadOS,\n isIos\n };\n\n // ==========================================================================\n\n // Clone nested objects\n function cloneDeep(object) {\n return JSON.parse(JSON.stringify(object));\n }\n\n // Get a nested value in an object\n function getDeep(object, path) {\n return path.split('.').reduce((obj, key) => obj && obj[key], object);\n }\n\n // Deep extend destination object with N more objects\n function extend(target = {}, ...sources) {\n if (!sources.length) {\n return target;\n }\n const source = sources.shift();\n if (!is.object(source)) {\n return target;\n }\n Object.keys(source).forEach(key => {\n if (is.object(source[key])) {\n if (!Object.keys(target).includes(key)) {\n Object.assign(target, {\n [key]: {}\n });\n }\n extend(target[key], source[key]);\n } else {\n Object.assign(target, {\n [key]: source[key]\n });\n }\n });\n return extend(target, ...sources);\n }\n\n // ==========================================================================\n\n // Wrap an element\n function wrap(elements, wrapper) {\n // Convert `elements` to an array, if necessary.\n const targets = elements.length ? elements : [elements];\n\n // Loops backwards to prevent having to clone the wrapper on the\n // first element (see `child` below).\n Array.from(targets).reverse().forEach((element, index) => {\n const child = index > 0 ? wrapper.cloneNode(true) : wrapper;\n // Cache the current parent and sibling.\n const parent = element.parentNode;\n const sibling = element.nextSibling;\n\n // Wrap the element (is automatically removed from its current\n // parent).\n child.appendChild(element);\n\n // If the element had a sibling, insert the wrapper before\n // the sibling to maintain the HTML structure; otherwise, just\n // append it to the parent.\n if (sibling) {\n parent.insertBefore(child, sibling);\n } else {\n parent.appendChild(child);\n }\n });\n }\n\n // Set attributes\n function setAttributes(element, attributes) {\n if (!is.element(element) || is.empty(attributes)) return;\n\n // Assume null and undefined attributes should be left out,\n // Setting them would otherwise convert them to \"null\" and \"undefined\"\n Object.entries(attributes).filter(([, value]) => !is.nullOrUndefined(value)).forEach(([key, value]) => element.setAttribute(key, value));\n }\n\n // Create a DocumentFragment\n function createElement(type, attributes, text) {\n // Create a new \n const element = document.createElement(type);\n\n // Set all passed attributes\n if (is.object(attributes)) {\n setAttributes(element, attributes);\n }\n\n // Add text node\n if (is.string(text)) {\n element.innerText = text;\n }\n\n // Return built element\n return element;\n }\n\n // Insert an element after another\n function insertAfter(element, target) {\n if (!is.element(element) || !is.element(target)) return;\n target.parentNode.insertBefore(element, target.nextSibling);\n }\n\n // Insert a DocumentFragment\n function insertElement(type, parent, attributes, text) {\n if (!is.element(parent)) return;\n parent.appendChild(createElement(type, attributes, text));\n }\n\n // Remove element(s)\n function removeElement(element) {\n if (is.nodeList(element) || is.array(element)) {\n Array.from(element).forEach(removeElement);\n return;\n }\n if (!is.element(element) || !is.element(element.parentNode)) {\n return;\n }\n element.parentNode.removeChild(element);\n }\n\n // Remove all child elements\n function emptyElement(element) {\n if (!is.element(element)) return;\n let {\n length\n } = element.childNodes;\n while (length > 0) {\n element.removeChild(element.lastChild);\n length -= 1;\n }\n }\n\n // Replace element\n function replaceElement(newChild, oldChild) {\n if (!is.element(oldChild) || !is.element(oldChild.parentNode) || !is.element(newChild)) return null;\n oldChild.parentNode.replaceChild(newChild, oldChild);\n return newChild;\n }\n\n // Get an attribute object from a string selector\n function getAttributesFromSelector(sel, existingAttributes) {\n // For example:\n // '.test' to { class: 'test' }\n // '#test' to { id: 'test' }\n // '[data-test=\"test\"]' to { 'data-test': 'test' }\n\n if (!is.string(sel) || is.empty(sel)) return {};\n const attributes = {};\n const existing = extend({}, existingAttributes);\n sel.split(',').forEach(s => {\n // Remove whitespace\n const selector = s.trim();\n const className = selector.replace('.', '');\n const stripped = selector.replace(/[[\\]]/g, '');\n // Get the parts and value\n const parts = stripped.split('=');\n const [key] = parts;\n const value = parts.length > 1 ? parts[1].replace(/[\"']/g, '') : '';\n // Get the first character\n const start = selector.charAt(0);\n switch (start) {\n case '.':\n // Add to existing classname\n if (is.string(existing.class)) {\n attributes.class = `${existing.class} ${className}`;\n } else {\n attributes.class = className;\n }\n break;\n case '#':\n // ID selector\n attributes.id = selector.replace('#', '');\n break;\n case '[':\n // Attribute selector\n attributes[key] = value;\n break;\n }\n });\n return extend(existing, attributes);\n }\n\n // Toggle hidden\n function toggleHidden(element, hidden) {\n if (!is.element(element)) return;\n let hide = hidden;\n if (!is.boolean(hide)) {\n hide = !element.hidden;\n }\n\n // eslint-disable-next-line no-param-reassign\n element.hidden = hide;\n }\n\n // Mirror Element.classList.toggle, with IE compatibility for \"force\" argument\n function toggleClass(element, className, force) {\n if (is.nodeList(element)) {\n return Array.from(element).map(e => toggleClass(e, className, force));\n }\n if (is.element(element)) {\n let method = 'toggle';\n if (typeof force !== 'undefined') {\n method = force ? 'add' : 'remove';\n }\n element.classList[method](className);\n return element.classList.contains(className);\n }\n return false;\n }\n\n // Has class name\n function hasClass(element, className) {\n return is.element(element) && element.classList.contains(className);\n }\n\n // Element matches selector\n function matches(element, selector) {\n const {\n prototype\n } = Element;\n function match() {\n return Array.from(document.querySelectorAll(selector)).includes(this);\n }\n const method = prototype.matches || prototype.webkitMatchesSelector || prototype.mozMatchesSelector || prototype.msMatchesSelector || match;\n return method.call(element, selector);\n }\n\n // Closest ancestor element matching selector (also tests element itself)\n function closest$1(element, selector) {\n const {\n prototype\n } = Element;\n\n // https://developer.mozilla.org/en-US/docs/Web/API/Element/closest#Polyfill\n function closestElement() {\n let el = this;\n do {\n if (matches.matches(el, selector)) return el;\n el = el.parentElement || el.parentNode;\n } while (el !== null && el.nodeType === 1);\n return null;\n }\n const method = prototype.closest || closestElement;\n return method.call(element, selector);\n }\n\n // Find all elements\n function getElements(selector) {\n return this.elements.container.querySelectorAll(selector);\n }\n\n // Find a single element\n function getElement(selector) {\n return this.elements.container.querySelector(selector);\n }\n\n // Set focus and tab focus class\n function setFocus(element = null, focusVisible = false) {\n if (!is.element(element)) return;\n\n // Set regular focus\n element.focus({\n preventScroll: true,\n focusVisible\n });\n }\n\n // ==========================================================================\n\n // Default codecs for checking mimetype support\n const defaultCodecs = {\n 'audio/ogg': 'vorbis',\n 'audio/wav': '1',\n 'video/webm': 'vp8, vorbis',\n 'video/mp4': 'avc1.42E01E, mp4a.40.2',\n 'video/ogg': 'theora'\n };\n\n // Check for feature support\n const support = {\n // Basic support\n audio: 'canPlayType' in document.createElement('audio'),\n video: 'canPlayType' in document.createElement('video'),\n // Check for support\n // Basic functionality vs full UI\n check(type, provider) {\n const api = support[type] || provider !== 'html5';\n const ui = api && support.rangeInput;\n return {\n api,\n ui\n };\n },\n // Picture-in-picture support\n // Safari & Chrome only currently\n pip: (() => {\n // While iPhone's support picture-in-picture for some apps, seemingly Safari isn't one of them\n // It will throw the following error when trying to enter picture-in-picture\n // `NotSupportedError: The Picture-in-Picture mode is not supported.`\n if (browser.isIPhone) {\n return false;\n }\n\n // Safari\n // https://developer.apple.com/documentation/webkitjs/adding_picture_in_picture_to_your_safari_media_controls\n if (is.function(createElement('video').webkitSetPresentationMode)) {\n return true;\n }\n\n // Chrome\n // https://developers.google.com/web/updates/2018/10/watch-video-using-picture-in-picture\n if (document.pictureInPictureEnabled && !createElement('video').disablePictureInPicture) {\n return true;\n }\n return false;\n })(),\n // Airplay support\n // Safari only currently\n airplay: is.function(window.WebKitPlaybackTargetAvailabilityEvent),\n // Inline playback support\n // https://webkit.org/blog/6784/new-video-policies-for-ios/\n playsinline: 'playsInline' in document.createElement('video'),\n // Check for mime type support against a player instance\n // Credits: http://diveintohtml5.info/everything.html\n // Related: http://www.leanbackplayer.com/test/h5mt.html\n mime(input) {\n if (is.empty(input)) {\n return false;\n }\n const [mediaType] = input.split('/');\n let type = input;\n\n // Verify we're using HTML5 and there's no media type mismatch\n if (!this.isHTML5 || mediaType !== this.type) {\n return false;\n }\n\n // Add codec if required\n if (Object.keys(defaultCodecs).includes(type)) {\n type += `; codecs=\"${defaultCodecs[input]}\"`;\n }\n try {\n return Boolean(type && this.media.canPlayType(type).replace(/no/, ''));\n } catch (_) {\n return false;\n }\n },\n // Check for textTracks support\n textTracks: 'textTracks' in document.createElement('video'),\n // Sliders\n rangeInput: (() => {\n const range = document.createElement('input');\n range.type = 'range';\n return range.type === 'range';\n })(),\n // Touch\n // NOTE: Remember a device can be mouse + touch enabled so we check on first touch event\n touch: 'ontouchstart' in document.documentElement,\n // Detect transitions support\n transitions: transitionEndEvent !== false,\n // Reduced motion iOS & MacOS setting\n // https://webkit.org/blog/7551/responsive-design-for-motion/\n reducedMotion: 'matchMedia' in window && window.matchMedia('(prefers-reduced-motion)').matches\n };\n\n // ==========================================================================\n\n // Check for passive event listener support\n // https://github.com/WICG/EventListenerOptions/blob/gh-pages/explainer.md\n // https://www.youtube.com/watch?v=NPM6172J22g\n const supportsPassiveListeners = (() => {\n // Test via a getter in the options object to see if the passive property is accessed\n let supported = false;\n try {\n const options = Object.defineProperty({}, 'passive', {\n get() {\n supported = true;\n return null;\n }\n });\n window.addEventListener('test', null, options);\n window.removeEventListener('test', null, options);\n } catch (_) {\n // Do nothing\n }\n return supported;\n })();\n\n // Toggle event listener\n function toggleListener(element, event, callback, toggle = false, passive = true, capture = false) {\n // Bail if no element, event, or callback\n if (!element || !('addEventListener' in element) || is.empty(event) || !is.function(callback)) {\n return;\n }\n\n // Allow multiple events\n const events = event.split(' ');\n // Build options\n // Default to just the capture boolean for browsers with no passive listener support\n let options = capture;\n\n // If passive events listeners are supported\n if (supportsPassiveListeners) {\n options = {\n // Whether the listener can be passive (i.e. default never prevented)\n passive,\n // Whether the listener is a capturing listener or not\n capture\n };\n }\n\n // If a single node is passed, bind the event listener\n events.forEach(type => {\n if (this && this.eventListeners && toggle) {\n // Cache event listener\n this.eventListeners.push({\n element,\n type,\n callback,\n options\n });\n }\n element[toggle ? 'addEventListener' : 'removeEventListener'](type, callback, options);\n });\n }\n\n // Bind event handler\n function on(element, events = '', callback, passive = true, capture = false) {\n toggleListener.call(this, element, events, callback, true, passive, capture);\n }\n\n // Unbind event handler\n function off(element, events = '', callback, passive = true, capture = false) {\n toggleListener.call(this, element, events, callback, false, passive, capture);\n }\n\n // Bind once-only event handler\n function once(element, events = '', callback, passive = true, capture = false) {\n const onceCallback = (...args) => {\n off(element, events, onceCallback, passive, capture);\n callback.apply(this, args);\n };\n toggleListener.call(this, element, events, onceCallback, true, passive, capture);\n }\n\n // Trigger event\n function triggerEvent(element, type = '', bubbles = false, detail = {}) {\n // Bail if no element\n if (!is.element(element) || is.empty(type)) {\n return;\n }\n\n // Create and dispatch the event\n const event = new CustomEvent(type, {\n bubbles,\n detail: {\n ...detail,\n plyr: this\n }\n });\n\n // Dispatch the event\n element.dispatchEvent(event);\n }\n\n // Unbind all cached event listeners\n function unbindListeners() {\n if (this && this.eventListeners) {\n this.eventListeners.forEach(item => {\n const {\n element,\n type,\n callback,\n options\n } = item;\n element.removeEventListener(type, callback, options);\n });\n this.eventListeners = [];\n }\n }\n\n // Run method when / if player is ready\n function ready() {\n return new Promise(resolve => this.ready ? setTimeout(resolve, 0) : on.call(this, this.elements.container, 'ready', resolve)).then(() => {});\n }\n\n /**\n * Silence a Promise-like object.\n * This is useful for avoiding non-harmful, but potentially confusing \"uncaught\n * play promise\" rejection error messages.\n * @param {Object} value An object that may or may not be `Promise`-like.\n */\n function silencePromise(value) {\n if (is.promise(value)) {\n value.then(null, () => {});\n }\n }\n\n // ==========================================================================\n\n // Remove duplicates in an array\n function dedupe(array) {\n if (!is.array(array)) {\n return array;\n }\n return array.filter((item, index) => array.indexOf(item) === index);\n }\n\n // Get the closest value in an array\n function closest(array, value) {\n if (!is.array(array) || !array.length) {\n return null;\n }\n return array.reduce((prev, curr) => Math.abs(curr - value) < Math.abs(prev - value) ? curr : prev);\n }\n\n // ==========================================================================\n\n // Check support for a CSS declaration\n function supportsCSS(declaration) {\n if (!window || !window.CSS) {\n return false;\n }\n return window.CSS.supports(declaration);\n }\n\n // Standard/common aspect ratios\n const standardRatios = [[1, 1], [4, 3], [3, 4], [5, 4], [4, 5], [3, 2], [2, 3], [16, 10], [10, 16], [16, 9], [9, 16], [21, 9], [9, 21], [32, 9], [9, 32]].reduce((out, [x, y]) => ({\n ...out,\n [x / y]: [x, y]\n }), {});\n\n // Validate an aspect ratio\n function validateAspectRatio(input) {\n if (!is.array(input) && (!is.string(input) || !input.includes(':'))) {\n return false;\n }\n const ratio = is.array(input) ? input : input.split(':');\n return ratio.map(Number).every(is.number);\n }\n\n // Reduce an aspect ratio to it's lowest form\n function reduceAspectRatio(ratio) {\n if (!is.array(ratio) || !ratio.every(is.number)) {\n return null;\n }\n const [width, height] = ratio;\n const getDivider = (w, h) => h === 0 ? w : getDivider(h, w % h);\n const divider = getDivider(width, height);\n return [width / divider, height / divider];\n }\n\n // Calculate an aspect ratio\n function getAspectRatio(input) {\n const parse = ratio => validateAspectRatio(ratio) ? ratio.split(':').map(Number) : null;\n // Try provided ratio\n let ratio = parse(input);\n\n // Get from config\n if (ratio === null) {\n ratio = parse(this.config.ratio);\n }\n\n // Get from embed\n if (ratio === null && !is.empty(this.embed) && is.array(this.embed.ratio)) {\n ({\n ratio\n } = this.embed);\n }\n\n // Get from HTML5 video\n if (ratio === null && this.isHTML5) {\n const {\n videoWidth,\n videoHeight\n } = this.media;\n ratio = [videoWidth, videoHeight];\n }\n return reduceAspectRatio(ratio);\n }\n\n // Set aspect ratio for responsive container\n function setAspectRatio(input) {\n if (!this.isVideo) {\n return {};\n }\n const {\n wrapper\n } = this.elements;\n const ratio = getAspectRatio.call(this, input);\n if (!is.array(ratio)) {\n return {};\n }\n const [x, y] = reduceAspectRatio(ratio);\n const useNative = supportsCSS(`aspect-ratio: ${x}/${y}`);\n const padding = 100 / x * y;\n if (useNative) {\n wrapper.style.aspectRatio = `${x}/${y}`;\n } else {\n wrapper.style.paddingBottom = `${padding}%`;\n }\n\n // For Vimeo we have an extra
to hide the standard controls and UI\n if (this.isVimeo && !this.config.vimeo.premium && this.supported.ui) {\n const height = 100 / this.media.offsetWidth * parseInt(window.getComputedStyle(this.media).paddingBottom, 10);\n const offset = (height - padding) / (height / 50);\n if (this.fullscreen.active) {\n wrapper.style.paddingBottom = null;\n } else {\n this.media.style.transform = `translateY(-${offset}%)`;\n }\n } else if (this.isHTML5) {\n wrapper.classList.add(this.config.classNames.videoFixedRatio);\n }\n return {\n padding,\n ratio\n };\n }\n\n // Round an aspect ratio to closest standard ratio\n function roundAspectRatio(x, y, tolerance = 0.05) {\n const ratio = x / y;\n const closestRatio = closest(Object.keys(standardRatios), ratio);\n\n // Check match is within tolerance\n if (Math.abs(closestRatio - ratio) <= tolerance) {\n return standardRatios[closestRatio];\n }\n\n // No match\n return [x, y];\n }\n\n // Get the size of the viewport\n // https://stackoverflow.com/questions/1248081/how-to-get-the-browser-viewport-dimensions\n function getViewportSize() {\n const width = Math.max(document.documentElement.clientWidth || 0, window.innerWidth || 0);\n const height = Math.max(document.documentElement.clientHeight || 0, window.innerHeight || 0);\n return [width, height];\n }\n\n // ==========================================================================\n const html5 = {\n getSources() {\n if (!this.isHTML5) {\n return [];\n }\n const sources = Array.from(this.media.querySelectorAll('source'));\n\n // Filter out unsupported sources (if type is specified)\n return sources.filter(source => {\n const type = source.getAttribute('type');\n if (is.empty(type)) {\n return true;\n }\n return support.mime.call(this, type);\n });\n },\n // Get quality levels\n getQualityOptions() {\n // Whether we're forcing all options (e.g. for streaming)\n if (this.config.quality.forced) {\n return this.config.quality.options;\n }\n\n // Get sizes from elements\n return html5.getSources.call(this).map(source => Number(source.getAttribute('size'))).filter(Boolean);\n },\n setup() {\n if (!this.isHTML5) {\n return;\n }\n const player = this;\n\n // Set speed options from config\n player.options.speed = player.config.speed.options;\n\n // Set aspect ratio if fixed\n if (!is.empty(this.config.ratio)) {\n setAspectRatio.call(player);\n }\n\n // Quality\n Object.defineProperty(player.media, 'quality', {\n get() {\n // Get sources\n const sources = html5.getSources.call(player);\n const source = sources.find(s => s.getAttribute('src') === player.source);\n\n // Return size, if match is found\n return source && Number(source.getAttribute('size'));\n },\n set(input) {\n if (player.quality === input) {\n return;\n }\n\n // If we're using an external handler...\n if (player.config.quality.forced && is.function(player.config.quality.onChange)) {\n player.config.quality.onChange(input);\n } else {\n // Get sources\n const sources = html5.getSources.call(player);\n // Get first match for requested size\n const source = sources.find(s => Number(s.getAttribute('size')) === input);\n\n // No matching source found\n if (!source) {\n return;\n }\n\n // Get current state\n const {\n currentTime,\n paused,\n preload,\n readyState,\n playbackRate\n } = player.media;\n\n // Set new source\n player.media.src = source.getAttribute('src');\n\n // Prevent loading if preload=\"none\" and the current source isn't loaded (#1044)\n if (preload !== 'none' || readyState) {\n // Restore time\n player.once('loadedmetadata', () => {\n player.speed = playbackRate;\n player.currentTime = currentTime;\n\n // Resume playing\n if (!paused) {\n silencePromise(player.play());\n }\n });\n\n // Load new source\n player.media.load();\n }\n }\n\n // Trigger change event\n triggerEvent.call(player, player.media, 'qualitychange', false, {\n quality: input\n });\n }\n });\n },\n // Cancel current network requests\n // See https://github.com/sampotts/plyr/issues/174\n cancelRequests() {\n if (!this.isHTML5) {\n return;\n }\n\n // Remove child sources\n removeElement(html5.getSources.call(this));\n\n // Set blank video src attribute\n // This is to prevent a MEDIA_ERR_SRC_NOT_SUPPORTED error\n // Info: http://stackoverflow.com/questions/32231579/how-to-properly-dispose-of-an-html5-video-and-close-socket-or-connection\n this.media.setAttribute('src', this.config.blankVideo);\n\n // Load the new empty source\n // This will cancel existing requests\n // See https://github.com/sampotts/plyr/issues/174\n this.media.load();\n\n // Debugging\n this.debug.log('Cancelled network requests');\n }\n };\n\n // ==========================================================================\n\n // Generate a random ID\n function generateId(prefix) {\n return `${prefix}-${Math.floor(Math.random() * 10000)}`;\n }\n\n // Format string\n function format(input, ...args) {\n if (is.empty(input)) return input;\n return input.toString().replace(/{(\\d+)}/g, (_, i) => args[i].toString());\n }\n\n // Get percentage\n function getPercentage(current, max) {\n if (current === 0 || max === 0 || Number.isNaN(current) || Number.isNaN(max)) {\n return 0;\n }\n return (current / max * 100).toFixed(2);\n }\n\n // Replace all occurrences of a string in a string\n const replaceAll = (input = '', find = '', replace = '') => input.replace(new RegExp(find.toString().replace(/([.*+?^=!:${}()|[\\]/\\\\])/g, '\\\\$1'), 'g'), replace.toString());\n\n // Convert to title case\n const toTitleCase = (input = '') => input.toString().replace(/\\w\\S*/g, text => text.charAt(0).toUpperCase() + text.slice(1).toLowerCase());\n\n // Convert string to pascalCase\n function toPascalCase(input = '') {\n let string = input.toString();\n\n // Convert kebab case\n string = replaceAll(string, '-', ' ');\n\n // Convert snake case\n string = replaceAll(string, '_', ' ');\n\n // Convert to title case\n string = toTitleCase(string);\n\n // Convert to pascal case\n return replaceAll(string, ' ', '');\n }\n\n // Convert string to pascalCase\n function toCamelCase(input = '') {\n let string = input.toString();\n\n // Convert to pascal case\n string = toPascalCase(string);\n\n // Convert first character to lowercase\n return string.charAt(0).toLowerCase() + string.slice(1);\n }\n\n // Remove HTML from a string\n function stripHTML(source) {\n const fragment = document.createDocumentFragment();\n const element = document.createElement('div');\n fragment.appendChild(element);\n element.innerHTML = source;\n return fragment.firstChild.innerText;\n }\n\n // Like outerHTML, but also works for DocumentFragment\n function getHTML(element) {\n const wrapper = document.createElement('div');\n wrapper.appendChild(element);\n return wrapper.innerHTML;\n }\n\n // ==========================================================================\n\n // Skip i18n for abbreviations and brand names\n const resources = {\n pip: 'PIP',\n airplay: 'AirPlay',\n html5: 'HTML5',\n vimeo: 'Vimeo',\n youtube: 'YouTube'\n };\n const i18n = {\n get(key = '', config = {}) {\n if (is.empty(key) || is.empty(config)) {\n return '';\n }\n let string = getDeep(config.i18n, key);\n if (is.empty(string)) {\n if (Object.keys(resources).includes(key)) {\n return resources[key];\n }\n return '';\n }\n const replace = {\n '{seektime}': config.seekTime,\n '{title}': config.title\n };\n Object.entries(replace).forEach(([k, v]) => {\n string = replaceAll(string, k, v);\n });\n return string;\n }\n };\n\n class Storage {\n constructor(player) {\n _defineProperty$1(this, \"get\", key => {\n if (!Storage.supported || !this.enabled) {\n return null;\n }\n const store = window.localStorage.getItem(this.key);\n if (is.empty(store)) {\n return null;\n }\n const json = JSON.parse(store);\n return is.string(key) && key.length ? json[key] : json;\n });\n _defineProperty$1(this, \"set\", object => {\n // Bail if we don't have localStorage support or it's disabled\n if (!Storage.supported || !this.enabled) {\n return;\n }\n\n // Can only store objectst\n if (!is.object(object)) {\n return;\n }\n\n // Get current storage\n let storage = this.get();\n\n // Default to empty object\n if (is.empty(storage)) {\n storage = {};\n }\n\n // Update the working copy of the values\n extend(storage, object);\n\n // Update storage\n try {\n window.localStorage.setItem(this.key, JSON.stringify(storage));\n } catch (_) {\n // Do nothing\n }\n });\n this.enabled = player.config.storage.enabled;\n this.key = player.config.storage.key;\n }\n\n // Check for actual support (see if we can use it)\n static get supported() {\n try {\n if (!('localStorage' in window)) {\n return false;\n }\n const test = '___test';\n\n // Try to use it (it might be disabled, e.g. user is in private mode)\n // see: https://github.com/sampotts/plyr/issues/131\n window.localStorage.setItem(test, test);\n window.localStorage.removeItem(test);\n return true;\n } catch (_) {\n return false;\n }\n }\n }\n\n // ==========================================================================\n // Fetch wrapper\n // Using XHR to avoid issues with older browsers\n // ==========================================================================\n\n function fetch(url, responseType = 'text') {\n return new Promise((resolve, reject) => {\n try {\n const request = new XMLHttpRequest();\n\n // Check for CORS support\n if (!('withCredentials' in request)) {\n return;\n }\n request.addEventListener('load', () => {\n if (responseType === 'text') {\n try {\n resolve(JSON.parse(request.responseText));\n } catch (_) {\n resolve(request.responseText);\n }\n } else {\n resolve(request.response);\n }\n });\n request.addEventListener('error', () => {\n throw new Error(request.status);\n });\n request.open('GET', url, true);\n\n // Set the required response type\n request.responseType = responseType;\n request.send();\n } catch (error) {\n reject(error);\n }\n });\n }\n\n // ==========================================================================\n\n // Load an external SVG sprite\n function loadSprite(url, id) {\n if (!is.string(url)) {\n return;\n }\n const prefix = 'cache';\n const hasId = is.string(id);\n let isCached = false;\n const exists = () => document.getElementById(id) !== null;\n const update = (container, data) => {\n // eslint-disable-next-line no-param-reassign\n container.innerHTML = data;\n\n // Check again incase of race condition\n if (hasId && exists()) {\n return;\n }\n\n // Inject the SVG to the body\n document.body.insertAdjacentElement('afterbegin', container);\n };\n\n // Only load once if ID set\n if (!hasId || !exists()) {\n const useStorage = Storage.supported;\n // Create container\n const container = document.createElement('div');\n container.setAttribute('hidden', '');\n if (hasId) {\n container.setAttribute('id', id);\n }\n\n // Check in cache\n if (useStorage) {\n const cached = window.localStorage.getItem(`${prefix}-${id}`);\n isCached = cached !== null;\n if (isCached) {\n const data = JSON.parse(cached);\n update(container, data.content);\n }\n }\n\n // Get the sprite\n fetch(url).then(result => {\n if (is.empty(result)) {\n return;\n }\n if (useStorage) {\n try {\n window.localStorage.setItem(`${prefix}-${id}`, JSON.stringify({\n content: result\n }));\n } catch (_) {\n // Do nothing\n }\n }\n update(container, result);\n }).catch(() => {});\n }\n }\n\n // ==========================================================================\n\n // Time helpers\n const getHours = value => Math.trunc(value / 60 / 60 % 60, 10);\n const getMinutes = value => Math.trunc(value / 60 % 60, 10);\n const getSeconds = value => Math.trunc(value % 60, 10);\n\n // Format time to UI friendly string\n function formatTime(time = 0, displayHours = false, inverted = false) {\n // Bail if the value isn't a number\n if (!is.number(time)) {\n return formatTime(undefined, displayHours, inverted);\n }\n\n // Format time component to add leading zero\n const format = value => `0${value}`.slice(-2);\n // Breakdown to hours, mins, secs\n let hours = getHours(time);\n const mins = getMinutes(time);\n const secs = getSeconds(time);\n\n // Do we need to display hours?\n if (displayHours || hours > 0) {\n hours = `${hours}:`;\n } else {\n hours = '';\n }\n\n // Render\n return `${inverted && time > 0 ? '-' : ''}${hours}${format(mins)}:${format(secs)}`;\n }\n\n // ==========================================================================\n\n // TODO: Don't export a massive object - break down and create class\n const controls = {\n // Get icon URL\n getIconUrl() {\n const url = new URL(this.config.iconUrl, window.location);\n const host = window.location.host ? window.location.host : window.top.location.host;\n const cors = url.host !== host || browser.isIE && !window.svg4everybody;\n return {\n url: this.config.iconUrl,\n cors\n };\n },\n // Find the UI controls\n findElements() {\n try {\n this.elements.controls = getElement.call(this, this.config.selectors.controls.wrapper);\n\n // Buttons\n this.elements.buttons = {\n play: getElements.call(this, this.config.selectors.buttons.play),\n pause: getElement.call(this, this.config.selectors.buttons.pause),\n restart: getElement.call(this, this.config.selectors.buttons.restart),\n rewind: getElement.call(this, this.config.selectors.buttons.rewind),\n fastForward: getElement.call(this, this.config.selectors.buttons.fastForward),\n mute: getElement.call(this, this.config.selectors.buttons.mute),\n pip: getElement.call(this, this.config.selectors.buttons.pip),\n airplay: getElement.call(this, this.config.selectors.buttons.airplay),\n settings: getElement.call(this, this.config.selectors.buttons.settings),\n captions: getElement.call(this, this.config.selectors.buttons.captions),\n fullscreen: getElement.call(this, this.config.selectors.buttons.fullscreen)\n };\n\n // Progress\n this.elements.progress = getElement.call(this, this.config.selectors.progress);\n\n // Inputs\n this.elements.inputs = {\n seek: getElement.call(this, this.config.selectors.inputs.seek),\n volume: getElement.call(this, this.config.selectors.inputs.volume)\n };\n\n // Display\n this.elements.display = {\n buffer: getElement.call(this, this.config.selectors.display.buffer),\n currentTime: getElement.call(this, this.config.selectors.display.currentTime),\n duration: getElement.call(this, this.config.selectors.display.duration)\n };\n\n // Seek tooltip\n if (is.element(this.elements.progress)) {\n this.elements.display.seekTooltip = this.elements.progress.querySelector(`.${this.config.classNames.tooltip}`);\n }\n return true;\n } catch (error) {\n // Log it\n this.debug.warn('It looks like there is a problem with your custom controls HTML', error);\n\n // Restore native video controls\n this.toggleNativeControls(true);\n return false;\n }\n },\n // Create icon\n createIcon(type, attributes) {\n const namespace = 'http://www.w3.org/2000/svg';\n const iconUrl = controls.getIconUrl.call(this);\n const iconPath = `${!iconUrl.cors ? iconUrl.url : ''}#${this.config.iconPrefix}`;\n // Create \n const icon = document.createElementNS(namespace, 'svg');\n setAttributes(icon, extend(attributes, {\n 'aria-hidden': 'true',\n focusable: 'false'\n }));\n\n // Create the to reference sprite\n const use = document.createElementNS(namespace, 'use');\n const path = `${iconPath}-${type}`;\n\n // Set `href` attributes\n // https://github.com/sampotts/plyr/issues/460\n // https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/xlink:href\n if ('href' in use) {\n use.setAttributeNS('http://www.w3.org/1999/xlink', 'href', path);\n }\n\n // Always set the older attribute even though it's \"deprecated\" (it'll be around for ages)\n use.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', path);\n\n // Add to \n icon.appendChild(use);\n return icon;\n },\n // Create hidden text label\n createLabel(key, attr = {}) {\n const text = i18n.get(key, this.config);\n const attributes = {\n ...attr,\n class: [attr.class, this.config.classNames.hidden].filter(Boolean).join(' ')\n };\n return createElement('span', attributes, text);\n },\n // Create a badge\n createBadge(text) {\n if (is.empty(text)) {\n return null;\n }\n const badge = createElement('span', {\n class: this.config.classNames.menu.value\n });\n badge.appendChild(createElement('span', {\n class: this.config.classNames.menu.badge\n }, text));\n return badge;\n },\n // Create a
`);\n }\n\n // Set position\n tipElement.style.left = `${percent}%`;\n\n // Show/hide the tooltip\n // If the event is a moues in/out and percentage is inside bounds\n if (is.event(event) && ['mouseenter', 'mouseleave'].includes(event.type)) {\n toggle(event.type === 'mouseenter');\n }\n },\n // Handle time change event\n timeUpdate(event) {\n // Only invert if only one time element is displayed and used for both duration and currentTime\n const invert = !is.element(this.elements.display.duration) && this.config.invertTime;\n\n // Duration\n controls.updateTimeDisplay.call(this, this.elements.display.currentTime, invert ? this.duration - this.currentTime : this.currentTime, invert);\n\n // Ignore updates while seeking\n if (event && event.type === 'timeupdate' && this.media.seeking) {\n return;\n }\n\n // Playing progress\n controls.updateProgress.call(this, event);\n },\n // Show the duration on metadataloaded or durationchange events\n durationUpdate() {\n // Bail if no UI or durationchange event triggered after playing/seek when invertTime is false\n if (!this.supported.ui || !this.config.invertTime && this.currentTime) {\n return;\n }\n\n // If duration is the 2**32 (shaka), Infinity (HLS), DASH-IF (Number.MAX_SAFE_INTEGER || Number.MAX_VALUE) indicating live we hide the currentTime and progressbar.\n // https://github.com/video-dev/hls.js/blob/5820d29d3c4c8a46e8b75f1e3afa3e68c1a9a2db/src/controller/buffer-controller.js#L415\n // https://github.com/google/shaka-player/blob/4d889054631f4e1cf0fbd80ddd2b71887c02e232/lib/media/streaming_engine.js#L1062\n // https://github.com/Dash-Industry-Forum/dash.js/blob/69859f51b969645b234666800d4cb596d89c602d/src/dash/models/DashManifestModel.js#L338\n if (this.duration >= 2 ** 32) {\n toggleHidden(this.elements.display.currentTime, true);\n toggleHidden(this.elements.progress, true);\n return;\n }\n\n // Update ARIA values\n if (is.element(this.elements.inputs.seek)) {\n this.elements.inputs.seek.setAttribute('aria-valuemax', this.duration);\n }\n\n // If there's a spot to display duration\n const hasDuration = is.element(this.elements.display.duration);\n\n // If there's only one time display, display duration there\n if (!hasDuration && this.config.displayDuration && this.paused) {\n controls.updateTimeDisplay.call(this, this.elements.display.currentTime, this.duration);\n }\n\n // If there's a duration element, update content\n if (hasDuration) {\n controls.updateTimeDisplay.call(this, this.elements.display.duration, this.duration);\n }\n if (this.config.markers.enabled) {\n controls.setMarkers.call(this);\n }\n\n // Update the tooltip (if visible)\n controls.updateSeekTooltip.call(this);\n },\n // Hide/show a tab\n toggleMenuButton(setting, toggle) {\n toggleHidden(this.elements.settings.buttons[setting], !toggle);\n },\n // Update the selected setting\n updateSetting(setting, container, input) {\n const pane = this.elements.settings.panels[setting];\n let value = null;\n let list = container;\n if (setting === 'captions') {\n value = this.currentTrack;\n } else {\n value = !is.empty(input) ? input : this[setting];\n\n // Get default\n if (is.empty(value)) {\n value = this.config[setting].default;\n }\n\n // Unsupported value\n if (!is.empty(this.options[setting]) && !this.options[setting].includes(value)) {\n this.debug.warn(`Unsupported value of '${value}' for ${setting}`);\n return;\n }\n\n // Disabled value\n if (!this.config[setting].options.includes(value)) {\n this.debug.warn(`Disabled value of '${value}' for ${setting}`);\n return;\n }\n }\n\n // Get the list if we need to\n if (!is.element(list)) {\n list = pane && pane.querySelector('[role=\"menu\"]');\n }\n\n // If there's no list it means it's not been rendered...\n if (!is.element(list)) {\n return;\n }\n\n // Update the label\n const label = this.elements.settings.buttons[setting].querySelector(`.${this.config.classNames.menu.value}`);\n label.innerHTML = controls.getLabel.call(this, setting, value);\n\n // Find the radio option and check it\n const target = list && list.querySelector(`[value=\"${value}\"]`);\n if (is.element(target)) {\n target.checked = true;\n }\n },\n // Translate a value into a nice label\n getLabel(setting, value) {\n switch (setting) {\n case 'speed':\n return value === 1 ? i18n.get('normal', this.config) : `${value}×`;\n case 'quality':\n if (is.number(value)) {\n const label = i18n.get(`qualityLabel.${value}`, this.config);\n if (!label.length) {\n return `${value}p`;\n }\n return label;\n }\n return toTitleCase(value);\n case 'captions':\n return captions.getLabel.call(this);\n default:\n return null;\n }\n },\n // Set the quality menu\n setQualityMenu(options) {\n // Menu required\n if (!is.element(this.elements.settings.panels.quality)) {\n return;\n }\n const type = 'quality';\n const list = this.elements.settings.panels.quality.querySelector('[role=\"menu\"]');\n\n // Set options if passed and filter based on uniqueness and config\n if (is.array(options)) {\n this.options.quality = dedupe(options).filter(quality => this.config.quality.options.includes(quality));\n }\n\n // Toggle the pane and tab\n const toggle = !is.empty(this.options.quality) && this.options.quality.length > 1;\n controls.toggleMenuButton.call(this, type, toggle);\n\n // Empty the menu\n emptyElement(list);\n\n // Check if we need to toggle the parent\n controls.checkMenu.call(this);\n\n // If we're hiding, nothing more to do\n if (!toggle) {\n return;\n }\n\n // Get the badge HTML for HD, 4K etc\n const getBadge = quality => {\n const label = i18n.get(`qualityBadge.${quality}`, this.config);\n if (!label.length) {\n return null;\n }\n return controls.createBadge.call(this, label);\n };\n\n // Sort options by the config and then render options\n this.options.quality.sort((a, b) => {\n const sorting = this.config.quality.options;\n return sorting.indexOf(a) > sorting.indexOf(b) ? 1 : -1;\n }).forEach(quality => {\n controls.createMenuItem.call(this, {\n value: quality,\n list,\n type,\n title: controls.getLabel.call(this, 'quality', quality),\n badge: getBadge(quality)\n });\n });\n controls.updateSetting.call(this, type, list);\n },\n // Set the looping options\n /* setLoopMenu() {\n // Menu required\n if (!is.element(this.elements.settings.panels.loop)) {\n return;\n }\n const options = ['start', 'end', 'all', 'reset'];\n const list = this.elements.settings.panels.loop.querySelector('[role=\"menu\"]');\n // Show the pane and tab\n toggleHidden(this.elements.settings.buttons.loop, false);\n toggleHidden(this.elements.settings.panels.loop, false);\n // Toggle the pane and tab\n const toggle = !is.empty(this.loop.options);\n controls.toggleMenuButton.call(this, 'loop', toggle);\n // Empty the menu\n emptyElement(list);\n options.forEach(option => {\n const item = createElement('li');\n const button = createElement(\n 'button',\n extend(getAttributesFromSelector(this.config.selectors.buttons.loop), {\n type: 'button',\n class: this.config.classNames.control,\n 'data-plyr-loop-action': option,\n }),\n i18n.get(option, this.config)\n );\n if (['start', 'end'].includes(option)) {\n const badge = controls.createBadge.call(this, '00:00');\n button.appendChild(badge);\n }\n item.appendChild(button);\n list.appendChild(item);\n });\n }, */\n\n // Get current selected caption language\n // TODO: rework this to user the getter in the API?\n\n // Set a list of available captions languages\n setCaptionsMenu() {\n // Menu required\n if (!is.element(this.elements.settings.panels.captions)) {\n return;\n }\n\n // TODO: Captions or language? Currently it's mixed\n const type = 'captions';\n const list = this.elements.settings.panels.captions.querySelector('[role=\"menu\"]');\n const tracks = captions.getTracks.call(this);\n const toggle = Boolean(tracks.length);\n\n // Toggle the pane and tab\n controls.toggleMenuButton.call(this, type, toggle);\n\n // Empty the menu\n emptyElement(list);\n\n // Check if we need to toggle the parent\n controls.checkMenu.call(this);\n\n // If there's no captions, bail\n if (!toggle) {\n return;\n }\n\n // Generate options data\n const options = tracks.map((track, value) => ({\n value,\n checked: this.captions.toggled && this.currentTrack === value,\n title: captions.getLabel.call(this, track),\n badge: track.language && controls.createBadge.call(this, track.language.toUpperCase()),\n list,\n type: 'language'\n }));\n\n // Add the \"Disabled\" option to turn off captions\n options.unshift({\n value: -1,\n checked: !this.captions.toggled,\n title: i18n.get('disabled', this.config),\n list,\n type: 'language'\n });\n\n // Generate options\n options.forEach(controls.createMenuItem.bind(this));\n controls.updateSetting.call(this, type, list);\n },\n // Set a list of available captions languages\n setSpeedMenu() {\n // Menu required\n if (!is.element(this.elements.settings.panels.speed)) {\n return;\n }\n const type = 'speed';\n const list = this.elements.settings.panels.speed.querySelector('[role=\"menu\"]');\n\n // Filter out invalid speeds\n this.options.speed = this.options.speed.filter(o => o >= this.minimumSpeed && o <= this.maximumSpeed);\n\n // Toggle the pane and tab\n const toggle = !is.empty(this.options.speed) && this.options.speed.length > 1;\n controls.toggleMenuButton.call(this, type, toggle);\n\n // Empty the menu\n emptyElement(list);\n\n // Check if we need to toggle the parent\n controls.checkMenu.call(this);\n\n // If we're hiding, nothing more to do\n if (!toggle) {\n return;\n }\n\n // Create items\n this.options.speed.forEach(speed => {\n controls.createMenuItem.call(this, {\n value: speed,\n list,\n type,\n title: controls.getLabel.call(this, 'speed', speed)\n });\n });\n controls.updateSetting.call(this, type, list);\n },\n // Check if we need to hide/show the settings menu\n checkMenu() {\n const {\n buttons\n } = this.elements.settings;\n const visible = !is.empty(buttons) && Object.values(buttons).some(button => !button.hidden);\n toggleHidden(this.elements.settings.menu, !visible);\n },\n // Focus the first menu item in a given (or visible) menu\n focusFirstMenuItem(pane, focusVisible = false) {\n if (this.elements.settings.popup.hidden) {\n return;\n }\n let target = pane;\n if (!is.element(target)) {\n target = Object.values(this.elements.settings.panels).find(p => !p.hidden);\n }\n const firstItem = target.querySelector('[role^=\"menuitem\"]');\n setFocus.call(this, firstItem, focusVisible);\n },\n // Show/hide menu\n toggleMenu(input) {\n const {\n popup\n } = this.elements.settings;\n const button = this.elements.buttons.settings;\n\n // Menu and button are required\n if (!is.element(popup) || !is.element(button)) {\n return;\n }\n\n // True toggle by default\n const {\n hidden\n } = popup;\n let show = hidden;\n if (is.boolean(input)) {\n show = input;\n } else if (is.keyboardEvent(input) && input.key === 'Escape') {\n show = false;\n } else if (is.event(input)) {\n // If Plyr is in a shadowDOM, the event target is set to the component, instead of the\n // Element in the shadowDOM. The path, if available, is complete.\n const target = is.function(input.composedPath) ? input.composedPath()[0] : input.target;\n const isMenuItem = popup.contains(target);\n\n // If the click was inside the menu or if the click\n // wasn't the button or menu item and we're trying to\n // show the menu (a doc click shouldn't show the menu)\n if (isMenuItem || !isMenuItem && input.target !== button && show) {\n return;\n }\n }\n\n // Set button attributes\n button.setAttribute('aria-expanded', show);\n\n // Show the actual popup\n toggleHidden(popup, !show);\n\n // Add class hook\n toggleClass(this.elements.container, this.config.classNames.menu.open, show);\n\n // Focus the first item if key interaction\n if (show && is.keyboardEvent(input)) {\n controls.focusFirstMenuItem.call(this, null, true);\n } else if (!show && !hidden) {\n // If closing, re-focus the button\n setFocus.call(this, button, is.keyboardEvent(input));\n }\n },\n // Get the natural size of a menu panel\n getMenuSize(tab) {\n const clone = tab.cloneNode(true);\n clone.style.position = 'absolute';\n clone.style.opacity = 0;\n clone.removeAttribute('hidden');\n\n // Append to parent so we get the \"real\" size\n tab.parentNode.appendChild(clone);\n\n // Get the sizes before we remove\n const width = clone.scrollWidth;\n const height = clone.scrollHeight;\n\n // Remove from the DOM\n removeElement(clone);\n return {\n width,\n height\n };\n },\n // Show a panel in the menu\n showMenuPanel(type = '', focusVisible = false) {\n const target = this.elements.container.querySelector(`#plyr-settings-${this.id}-${type}`);\n\n // Nothing to show, bail\n if (!is.element(target)) {\n return;\n }\n\n // Hide all other panels\n const container = target.parentNode;\n const current = Array.from(container.children).find(node => !node.hidden);\n\n // If we can do fancy animations, we'll animate the height/width\n if (support.transitions && !support.reducedMotion) {\n // Set the current width as a base\n container.style.width = `${current.scrollWidth}px`;\n container.style.height = `${current.scrollHeight}px`;\n\n // Get potential sizes\n const size = controls.getMenuSize.call(this, target);\n\n // Restore auto height/width\n const restore = event => {\n // We're only bothered about height and width on the container\n if (event.target !== container || !['width', 'height'].includes(event.propertyName)) {\n return;\n }\n\n // Revert back to auto\n container.style.width = '';\n container.style.height = '';\n\n // Only listen once\n off.call(this, container, transitionEndEvent, restore);\n };\n\n // Listen for the transition finishing and restore auto height/width\n on.call(this, container, transitionEndEvent, restore);\n\n // Set dimensions to target\n container.style.width = `${size.width}px`;\n container.style.height = `${size.height}px`;\n }\n\n // Set attributes on current tab\n toggleHidden(current, true);\n\n // Set attributes on target\n toggleHidden(target, false);\n\n // Focus the first item\n controls.focusFirstMenuItem.call(this, target, focusVisible);\n },\n // Set the download URL\n setDownloadUrl() {\n const button = this.elements.buttons.download;\n\n // Bail if no button\n if (!is.element(button)) {\n return;\n }\n\n // Set attribute\n button.setAttribute('href', this.download);\n },\n // Build the default HTML\n create(data) {\n const {\n bindMenuItemShortcuts,\n createButton,\n createProgress,\n createRange,\n createTime,\n setQualityMenu,\n setSpeedMenu,\n showMenuPanel\n } = controls;\n this.elements.controls = null;\n\n // Larger overlaid play button\n if (is.array(this.config.controls) && this.config.controls.includes('play-large')) {\n this.elements.container.appendChild(createButton.call(this, 'play-large'));\n }\n\n // Create the container\n const container = createElement('div', getAttributesFromSelector(this.config.selectors.controls.wrapper));\n this.elements.controls = container;\n\n // Default item attributes\n const defaultAttributes = {\n class: 'plyr__controls__item'\n };\n\n // Loop through controls in order\n dedupe(is.array(this.config.controls) ? this.config.controls : []).forEach(control => {\n // Restart button\n if (control === 'restart') {\n container.appendChild(createButton.call(this, 'restart', defaultAttributes));\n }\n\n // Rewind button\n if (control === 'rewind') {\n container.appendChild(createButton.call(this, 'rewind', defaultAttributes));\n }\n\n // Play/Pause button\n if (control === 'play') {\n container.appendChild(createButton.call(this, 'play', defaultAttributes));\n }\n\n // Fast forward button\n if (control === 'fast-forward') {\n container.appendChild(createButton.call(this, 'fast-forward', defaultAttributes));\n }\n\n // Progress\n if (control === 'progress') {\n const progressContainer = createElement('div', {\n class: `${defaultAttributes.class} plyr__progress__container`\n });\n const progress = createElement('div', getAttributesFromSelector(this.config.selectors.progress));\n\n // Seek range slider\n progress.appendChild(createRange.call(this, 'seek', {\n id: `plyr-seek-${data.id}`\n }));\n\n // Buffer progress\n progress.appendChild(createProgress.call(this, 'buffer'));\n\n // TODO: Add loop display indicator\n\n // Seek tooltip\n if (this.config.tooltips.seek) {\n const tooltip = createElement('span', {\n class: this.config.classNames.tooltip\n }, '00:00');\n progress.appendChild(tooltip);\n this.elements.display.seekTooltip = tooltip;\n }\n this.elements.progress = progress;\n progressContainer.appendChild(this.elements.progress);\n container.appendChild(progressContainer);\n }\n\n // Media current time display\n if (control === 'current-time') {\n container.appendChild(createTime.call(this, 'currentTime', defaultAttributes));\n }\n\n // Media duration display\n if (control === 'duration') {\n container.appendChild(createTime.call(this, 'duration', defaultAttributes));\n }\n\n // Volume controls\n if (control === 'mute' || control === 'volume') {\n let {\n volume\n } = this.elements;\n\n // Create the volume container if needed\n if (!is.element(volume) || !container.contains(volume)) {\n volume = createElement('div', extend({}, defaultAttributes, {\n class: `${defaultAttributes.class} plyr__volume`.trim()\n }));\n this.elements.volume = volume;\n container.appendChild(volume);\n }\n\n // Toggle mute button\n if (control === 'mute') {\n volume.appendChild(createButton.call(this, 'mute'));\n }\n\n // Volume range control\n // Ignored on iOS as it's handled globally\n // https://developer.apple.com/library/safari/documentation/AudioVideo/Conceptual/Using_HTML5_Audio_Video/Device-SpecificConsiderations/Device-SpecificConsiderations.html\n if (control === 'volume' && !browser.isIos && !browser.isIPadOS) {\n // Set the attributes\n const attributes = {\n max: 1,\n step: 0.05,\n value: this.config.volume\n };\n\n // Create the volume range slider\n volume.appendChild(createRange.call(this, 'volume', extend(attributes, {\n id: `plyr-volume-${data.id}`\n })));\n }\n }\n\n // Toggle captions button\n if (control === 'captions') {\n container.appendChild(createButton.call(this, 'captions', defaultAttributes));\n }\n\n // Settings button / menu\n if (control === 'settings' && !is.empty(this.config.settings)) {\n const wrapper = createElement('div', extend({}, defaultAttributes, {\n class: `${defaultAttributes.class} plyr__menu`.trim(),\n hidden: ''\n }));\n wrapper.appendChild(createButton.call(this, 'settings', {\n 'aria-haspopup': true,\n 'aria-controls': `plyr-settings-${data.id}`,\n 'aria-expanded': false\n }));\n const popup = createElement('div', {\n class: 'plyr__menu__container',\n id: `plyr-settings-${data.id}`,\n hidden: ''\n });\n const inner = createElement('div');\n const home = createElement('div', {\n id: `plyr-settings-${data.id}-home`\n });\n\n // Create the menu\n const menu = createElement('div', {\n role: 'menu'\n });\n home.appendChild(menu);\n inner.appendChild(home);\n this.elements.settings.panels.home = home;\n\n // Build the menu items\n this.config.settings.forEach(type => {\n // TODO: bundle this with the createMenuItem helper and bindings\n const menuItem = createElement('button', extend(getAttributesFromSelector(this.config.selectors.buttons.settings), {\n type: 'button',\n class: `${this.config.classNames.control} ${this.config.classNames.control}--forward`,\n role: 'menuitem',\n 'aria-haspopup': true,\n hidden: ''\n }));\n\n // Bind menu shortcuts for keyboard users\n bindMenuItemShortcuts.call(this, menuItem, type);\n\n // Show menu on click\n on.call(this, menuItem, 'click', () => {\n showMenuPanel.call(this, type, false);\n });\n const flex = createElement('span', null, i18n.get(type, this.config));\n const value = createElement('span', {\n class: this.config.classNames.menu.value\n });\n\n // Speed contains HTML entities\n value.innerHTML = data[type];\n flex.appendChild(value);\n menuItem.appendChild(flex);\n menu.appendChild(menuItem);\n\n // Build the panes\n const pane = createElement('div', {\n id: `plyr-settings-${data.id}-${type}`,\n hidden: ''\n });\n\n // Back button\n const backButton = createElement('button', {\n type: 'button',\n class: `${this.config.classNames.control} ${this.config.classNames.control}--back`\n });\n\n // Visible label\n backButton.appendChild(createElement('span', {\n 'aria-hidden': true\n }, i18n.get(type, this.config)));\n\n // Screen reader label\n backButton.appendChild(createElement('span', {\n class: this.config.classNames.hidden\n }, i18n.get('menuBack', this.config)));\n\n // Go back via keyboard\n on.call(this, pane, 'keydown', event => {\n if (event.key !== 'ArrowLeft') return;\n\n // Prevent seek\n event.preventDefault();\n event.stopPropagation();\n\n // Show the respective menu\n showMenuPanel.call(this, 'home', true);\n }, false);\n\n // Go back via button click\n on.call(this, backButton, 'click', () => {\n showMenuPanel.call(this, 'home', false);\n });\n\n // Add to pane\n pane.appendChild(backButton);\n\n // Menu\n pane.appendChild(createElement('div', {\n role: 'menu'\n }));\n inner.appendChild(pane);\n this.elements.settings.buttons[type] = menuItem;\n this.elements.settings.panels[type] = pane;\n });\n popup.appendChild(inner);\n wrapper.appendChild(popup);\n container.appendChild(wrapper);\n this.elements.settings.popup = popup;\n this.elements.settings.menu = wrapper;\n }\n\n // Picture in picture button\n if (control === 'pip' && support.pip) {\n container.appendChild(createButton.call(this, 'pip', defaultAttributes));\n }\n\n // Airplay button\n if (control === 'airplay' && support.airplay) {\n container.appendChild(createButton.call(this, 'airplay', defaultAttributes));\n }\n\n // Download button\n if (control === 'download') {\n const attributes = extend({}, defaultAttributes, {\n element: 'a',\n href: this.download,\n target: '_blank'\n });\n\n // Set download attribute for HTML5 only\n if (this.isHTML5) {\n attributes.download = '';\n }\n const {\n download\n } = this.config.urls;\n if (!is.url(download) && this.isEmbed) {\n extend(attributes, {\n icon: `logo-${this.provider}`,\n label: this.provider\n });\n }\n container.appendChild(createButton.call(this, 'download', attributes));\n }\n\n // Toggle fullscreen button\n if (control === 'fullscreen') {\n container.appendChild(createButton.call(this, 'fullscreen', defaultAttributes));\n }\n });\n\n // Set available quality levels\n if (this.isHTML5) {\n setQualityMenu.call(this, html5.getQualityOptions.call(this));\n }\n setSpeedMenu.call(this);\n return container;\n },\n // Insert controls\n inject() {\n // Sprite\n if (this.config.loadSprite) {\n const icon = controls.getIconUrl.call(this);\n\n // Only load external sprite using AJAX\n if (icon.cors) {\n loadSprite(icon.url, 'sprite-plyr');\n }\n }\n\n // Create a unique ID\n this.id = Math.floor(Math.random() * 10000);\n\n // Null by default\n let container = null;\n this.elements.controls = null;\n\n // Set template properties\n const props = {\n id: this.id,\n seektime: this.config.seekTime,\n title: this.config.title\n };\n let update = true;\n\n // If function, run it and use output\n if (is.function(this.config.controls)) {\n this.config.controls = this.config.controls.call(this, props);\n }\n\n // Convert falsy controls to empty array (primarily for empty strings)\n if (!this.config.controls) {\n this.config.controls = [];\n }\n if (is.element(this.config.controls) || is.string(this.config.controls)) {\n // HTMLElement or Non-empty string passed as the option\n container = this.config.controls;\n } else {\n // Create controls\n container = controls.create.call(this, {\n id: this.id,\n seektime: this.config.seekTime,\n speed: this.speed,\n quality: this.quality,\n captions: captions.getLabel.call(this)\n // TODO: Looping\n // loop: 'None',\n });\n\n update = false;\n }\n\n // Replace props with their value\n const replace = input => {\n let result = input;\n Object.entries(props).forEach(([key, value]) => {\n result = replaceAll(result, `{${key}}`, value);\n });\n return result;\n };\n\n // Update markup\n if (update) {\n if (is.string(this.config.controls)) {\n container = replace(container);\n }\n }\n\n // Controls container\n let target;\n\n // Inject to custom location\n if (is.string(this.config.selectors.controls.container)) {\n target = document.querySelector(this.config.selectors.controls.container);\n }\n\n // Inject into the container by default\n if (!is.element(target)) {\n target = this.elements.container;\n }\n\n // Inject controls HTML (needs to be before captions, hence \"afterbegin\")\n const insertMethod = is.element(container) ? 'insertAdjacentElement' : 'insertAdjacentHTML';\n target[insertMethod]('afterbegin', container);\n\n // Find the elements if need be\n if (!is.element(this.elements.controls)) {\n controls.findElements.call(this);\n }\n\n // Add pressed property to buttons\n if (!is.empty(this.elements.buttons)) {\n const addProperty = button => {\n const className = this.config.classNames.controlPressed;\n button.setAttribute('aria-pressed', 'false');\n Object.defineProperty(button, 'pressed', {\n configurable: true,\n enumerable: true,\n get() {\n return hasClass(button, className);\n },\n set(pressed = false) {\n toggleClass(button, className, pressed);\n button.setAttribute('aria-pressed', pressed ? 'true' : 'false');\n }\n });\n };\n\n // Toggle classname when pressed property is set\n Object.values(this.elements.buttons).filter(Boolean).forEach(button => {\n if (is.array(button) || is.nodeList(button)) {\n Array.from(button).filter(Boolean).forEach(addProperty);\n } else {\n addProperty(button);\n }\n });\n }\n\n // Edge sometimes doesn't finish the paint so force a repaint\n if (browser.isEdge) {\n repaint(target);\n }\n\n // Setup tooltips\n if (this.config.tooltips.controls) {\n const {\n classNames,\n selectors\n } = this.config;\n const selector = `${selectors.controls.wrapper} ${selectors.labels} .${classNames.hidden}`;\n const labels = getElements.call(this, selector);\n Array.from(labels).forEach(label => {\n toggleClass(label, this.config.classNames.hidden, false);\n toggleClass(label, this.config.classNames.tooltip, true);\n });\n }\n },\n // Set media metadata\n setMediaMetadata() {\n try {\n if ('mediaSession' in navigator) {\n navigator.mediaSession.metadata = new window.MediaMetadata({\n title: this.config.mediaMetadata.title,\n artist: this.config.mediaMetadata.artist,\n album: this.config.mediaMetadata.album,\n artwork: this.config.mediaMetadata.artwork\n });\n }\n } catch (_) {\n // Do nothing\n }\n },\n // Add markers\n setMarkers() {\n var _this$config$markers2, _this$config$markers3;\n if (!this.duration || this.elements.markers) return;\n\n // Get valid points\n const points = (_this$config$markers2 = this.config.markers) === null || _this$config$markers2 === void 0 ? void 0 : (_this$config$markers3 = _this$config$markers2.points) === null || _this$config$markers3 === void 0 ? void 0 : _this$config$markers3.filter(({\n time\n }) => time > 0 && time < this.duration);\n if (!(points !== null && points !== void 0 && points.length)) return;\n const containerFragment = document.createDocumentFragment();\n const pointsFragment = document.createDocumentFragment();\n let tipElement = null;\n const tipVisible = `${this.config.classNames.tooltip}--visible`;\n const toggleTip = show => toggleClass(tipElement, tipVisible, show);\n\n // Inject markers to progress container\n points.forEach(point => {\n const markerElement = createElement('span', {\n class: this.config.classNames.marker\n }, '');\n const left = `${point.time / this.duration * 100}%`;\n if (tipElement) {\n // Show on hover\n markerElement.addEventListener('mouseenter', () => {\n if (point.label) return;\n tipElement.style.left = left;\n tipElement.innerHTML = point.label;\n toggleTip(true);\n });\n\n // Hide on leave\n markerElement.addEventListener('mouseleave', () => {\n toggleTip(false);\n });\n }\n markerElement.addEventListener('click', () => {\n this.currentTime = point.time;\n });\n markerElement.style.left = left;\n pointsFragment.appendChild(markerElement);\n });\n containerFragment.appendChild(pointsFragment);\n\n // Inject a tooltip if needed\n if (!this.config.tooltips.seek) {\n tipElement = createElement('span', {\n class: this.config.classNames.tooltip\n }, '');\n containerFragment.appendChild(tipElement);\n }\n this.elements.markers = {\n points: pointsFragment,\n tip: tipElement\n };\n this.elements.progress.appendChild(containerFragment);\n }\n };\n\n // ==========================================================================\n\n /**\n * Parse a string to a URL object\n * @param {String} input - the URL to be parsed\n * @param {Boolean} safe - failsafe parsing\n */\n function parseUrl(input, safe = true) {\n let url = input;\n if (safe) {\n const parser = document.createElement('a');\n parser.href = url;\n url = parser.href;\n }\n try {\n return new URL(url);\n } catch (_) {\n return null;\n }\n }\n\n // Convert object to URLSearchParams\n function buildUrlParams(input) {\n const params = new URLSearchParams();\n if (is.object(input)) {\n Object.entries(input).forEach(([key, value]) => {\n params.set(key, value);\n });\n }\n return params;\n }\n\n // ==========================================================================\n const captions = {\n // Setup captions\n setup() {\n // Requires UI support\n if (!this.supported.ui) {\n return;\n }\n\n // Only Vimeo and HTML5 video supported at this point\n if (!this.isVideo || this.isYouTube || this.isHTML5 && !support.textTracks) {\n // Clear menu and hide\n if (is.array(this.config.controls) && this.config.controls.includes('settings') && this.config.settings.includes('captions')) {\n controls.setCaptionsMenu.call(this);\n }\n return;\n }\n\n // Inject the container\n if (!is.element(this.elements.captions)) {\n this.elements.captions = createElement('div', getAttributesFromSelector(this.config.selectors.captions));\n this.elements.captions.setAttribute('dir', 'auto');\n insertAfter(this.elements.captions, this.elements.wrapper);\n }\n\n // Fix IE captions if CORS is used\n // Fetch captions and inject as blobs instead (data URIs not supported!)\n if (browser.isIE && window.URL) {\n const elements = this.media.querySelectorAll('track');\n Array.from(elements).forEach(track => {\n const src = track.getAttribute('src');\n const url = parseUrl(src);\n if (url !== null && url.hostname !== window.location.href.hostname && ['http:', 'https:'].includes(url.protocol)) {\n fetch(src, 'blob').then(blob => {\n track.setAttribute('src', window.URL.createObjectURL(blob));\n }).catch(() => {\n removeElement(track);\n });\n }\n });\n }\n\n // Get and set initial data\n // The \"preferred\" options are not realized unless / until the wanted language has a match\n // * languages: Array of user's browser languages.\n // * language: The language preferred by user settings or config\n // * active: The state preferred by user settings or config\n // * toggled: The real captions state\n\n const browserLanguages = navigator.languages || [navigator.language || navigator.userLanguage || 'en'];\n const languages = dedupe(browserLanguages.map(language => language.split('-')[0]));\n let language = (this.storage.get('language') || this.config.captions.language || 'auto').toLowerCase();\n\n // Use first browser language when language is 'auto'\n if (language === 'auto') {\n [language] = languages;\n }\n let active = this.storage.get('captions');\n if (!is.boolean(active)) {\n ({\n active\n } = this.config.captions);\n }\n Object.assign(this.captions, {\n toggled: false,\n active,\n language,\n languages\n });\n\n // Watch changes to textTracks and update captions menu\n if (this.isHTML5) {\n const trackEvents = this.config.captions.update ? 'addtrack removetrack' : 'removetrack';\n on.call(this, this.media.textTracks, trackEvents, captions.update.bind(this));\n }\n\n // Update available languages in list next tick (the event must not be triggered before the listeners)\n setTimeout(captions.update.bind(this), 0);\n },\n // Update available language options in settings based on tracks\n update() {\n const tracks = captions.getTracks.call(this, true);\n // Get the wanted language\n const {\n active,\n language,\n meta,\n currentTrackNode\n } = this.captions;\n const languageExists = Boolean(tracks.find(track => track.language === language));\n\n // Handle tracks (add event listener and \"pseudo\"-default)\n if (this.isHTML5 && this.isVideo) {\n tracks.filter(track => !meta.get(track)).forEach(track => {\n this.debug.log('Track added', track);\n\n // Attempt to store if the original dom element was \"default\"\n meta.set(track, {\n default: track.mode === 'showing'\n });\n\n // Turn off native caption rendering to avoid double captions\n // Note: mode='hidden' forces a track to download. To ensure every track\n // isn't downloaded at once, only 'showing' tracks should be reassigned\n // eslint-disable-next-line no-param-reassign\n if (track.mode === 'showing') {\n // eslint-disable-next-line no-param-reassign\n track.mode = 'hidden';\n }\n\n // Add event listener for cue changes\n on.call(this, track, 'cuechange', () => captions.updateCues.call(this));\n });\n }\n\n // Update language first time it matches, or if the previous matching track was removed\n if (languageExists && this.language !== language || !tracks.includes(currentTrackNode)) {\n captions.setLanguage.call(this, language);\n captions.toggle.call(this, active && languageExists);\n }\n\n // Enable or disable captions based on track length\n if (this.elements) {\n toggleClass(this.elements.container, this.config.classNames.captions.enabled, !is.empty(tracks));\n }\n\n // Update available languages in list\n if (is.array(this.config.controls) && this.config.controls.includes('settings') && this.config.settings.includes('captions')) {\n controls.setCaptionsMenu.call(this);\n }\n },\n // Toggle captions display\n // Used internally for the toggleCaptions method, with the passive option forced to false\n toggle(input, passive = true) {\n // If there's no full support\n if (!this.supported.ui) {\n return;\n }\n const {\n toggled\n } = this.captions; // Current state\n const activeClass = this.config.classNames.captions.active;\n // Get the next state\n // If the method is called without parameter, toggle based on current value\n const active = is.nullOrUndefined(input) ? !toggled : input;\n\n // Update state and trigger event\n if (active !== toggled) {\n // When passive, don't override user preferences\n if (!passive) {\n this.captions.active = active;\n this.storage.set({\n captions: active\n });\n }\n\n // Force language if the call isn't passive and there is no matching language to toggle to\n if (!this.language && active && !passive) {\n const tracks = captions.getTracks.call(this);\n const track = captions.findTrack.call(this, [this.captions.language, ...this.captions.languages], true);\n\n // Override user preferences to avoid switching languages if a matching track is added\n this.captions.language = track.language;\n\n // Set caption, but don't store in localStorage as user preference\n captions.set.call(this, tracks.indexOf(track));\n return;\n }\n\n // Toggle button if it's enabled\n if (this.elements.buttons.captions) {\n this.elements.buttons.captions.pressed = active;\n }\n\n // Add class hook\n toggleClass(this.elements.container, activeClass, active);\n this.captions.toggled = active;\n\n // Update settings menu\n controls.updateSetting.call(this, 'captions');\n\n // Trigger event (not used internally)\n triggerEvent.call(this, this.media, active ? 'captionsenabled' : 'captionsdisabled');\n }\n\n // Wait for the call stack to clear before setting mode='hidden'\n // on the active track - forcing the browser to download it\n setTimeout(() => {\n if (active && this.captions.toggled) {\n this.captions.currentTrackNode.mode = 'hidden';\n }\n });\n },\n // Set captions by track index\n // Used internally for the currentTrack setter with the passive option forced to false\n set(index, passive = true) {\n const tracks = captions.getTracks.call(this);\n\n // Disable captions if setting to -1\n if (index === -1) {\n captions.toggle.call(this, false, passive);\n return;\n }\n if (!is.number(index)) {\n this.debug.warn('Invalid caption argument', index);\n return;\n }\n if (!(index in tracks)) {\n this.debug.warn('Track not found', index);\n return;\n }\n if (this.captions.currentTrack !== index) {\n this.captions.currentTrack = index;\n const track = tracks[index];\n const {\n language\n } = track || {};\n\n // Store reference to node for invalidation on remove\n this.captions.currentTrackNode = track;\n\n // Update settings menu\n controls.updateSetting.call(this, 'captions');\n\n // When passive, don't override user preferences\n if (!passive) {\n this.captions.language = language;\n this.storage.set({\n language\n });\n }\n\n // Handle Vimeo captions\n if (this.isVimeo) {\n this.embed.enableTextTrack(language);\n }\n\n // Trigger event\n triggerEvent.call(this, this.media, 'languagechange');\n }\n\n // Show captions\n captions.toggle.call(this, true, passive);\n if (this.isHTML5 && this.isVideo) {\n // If we change the active track while a cue is already displayed we need to update it\n captions.updateCues.call(this);\n }\n },\n // Set captions by language\n // Used internally for the language setter with the passive option forced to false\n setLanguage(input, passive = true) {\n if (!is.string(input)) {\n this.debug.warn('Invalid language argument', input);\n return;\n }\n // Normalize\n const language = input.toLowerCase();\n this.captions.language = language;\n\n // Set currentTrack\n const tracks = captions.getTracks.call(this);\n const track = captions.findTrack.call(this, [language]);\n captions.set.call(this, tracks.indexOf(track), passive);\n },\n // Get current valid caption tracks\n // If update is false it will also ignore tracks without metadata\n // This is used to \"freeze\" the language options when captions.update is false\n getTracks(update = false) {\n // Handle media or textTracks missing or null\n const tracks = Array.from((this.media || {}).textTracks || []);\n // For HTML5, use cache instead of current tracks when it exists (if captions.update is false)\n // Filter out removed tracks and tracks that aren't captions/subtitles (for example metadata)\n return tracks.filter(track => !this.isHTML5 || update || this.captions.meta.has(track)).filter(track => ['captions', 'subtitles'].includes(track.kind));\n },\n // Match tracks based on languages and get the first\n findTrack(languages, force = false) {\n const tracks = captions.getTracks.call(this);\n const sortIsDefault = track => Number((this.captions.meta.get(track) || {}).default);\n const sorted = Array.from(tracks).sort((a, b) => sortIsDefault(b) - sortIsDefault(a));\n let track;\n languages.every(language => {\n track = sorted.find(t => t.language === language);\n return !track; // Break iteration if there is a match\n });\n\n // If no match is found but is required, get first\n return track || (force ? sorted[0] : undefined);\n },\n // Get the current track\n getCurrentTrack() {\n return captions.getTracks.call(this)[this.currentTrack];\n },\n // Get UI label for track\n getLabel(track) {\n let currentTrack = track;\n if (!is.track(currentTrack) && support.textTracks && this.captions.toggled) {\n currentTrack = captions.getCurrentTrack.call(this);\n }\n if (is.track(currentTrack)) {\n if (!is.empty(currentTrack.label)) {\n return currentTrack.label;\n }\n if (!is.empty(currentTrack.language)) {\n return track.language.toUpperCase();\n }\n return i18n.get('enabled', this.config);\n }\n return i18n.get('disabled', this.config);\n },\n // Update captions using current track's active cues\n // Also optional array argument in case there isn't any track (ex: vimeo)\n updateCues(input) {\n // Requires UI\n if (!this.supported.ui) {\n return;\n }\n if (!is.element(this.elements.captions)) {\n this.debug.warn('No captions element to render to');\n return;\n }\n\n // Only accept array or empty input\n if (!is.nullOrUndefined(input) && !Array.isArray(input)) {\n this.debug.warn('updateCues: Invalid input', input);\n return;\n }\n let cues = input;\n\n // Get cues from track\n if (!cues) {\n const track = captions.getCurrentTrack.call(this);\n cues = Array.from((track || {}).activeCues || []).map(cue => cue.getCueAsHTML()).map(getHTML);\n }\n\n // Set new caption text\n const content = cues.map(cueText => cueText.trim()).join('\\n');\n const changed = content !== this.elements.captions.innerHTML;\n if (changed) {\n // Empty the container and create a new child element\n emptyElement(this.elements.captions);\n const caption = createElement('span', getAttributesFromSelector(this.config.selectors.caption));\n caption.innerHTML = content;\n this.elements.captions.appendChild(caption);\n\n // Trigger event\n triggerEvent.call(this, this.media, 'cuechange');\n }\n }\n };\n\n // ==========================================================================\n // Plyr default config\n // ==========================================================================\n\n const defaults = {\n // Disable\n enabled: true,\n // Custom media title\n title: '',\n // Logging to console\n debug: false,\n // Auto play (if supported)\n autoplay: false,\n // Only allow one media playing at once (vimeo only)\n autopause: true,\n // Allow inline playback on iOS\n playsinline: true,\n // Default time to skip when rewind/fast forward\n seekTime: 10,\n // Default volume\n volume: 1,\n muted: false,\n // Pass a custom duration\n duration: null,\n // Display the media duration on load in the current time position\n // If you have opted to display both duration and currentTime, this is ignored\n displayDuration: true,\n // Invert the current time to be a countdown\n invertTime: true,\n // Clicking the currentTime inverts it's value to show time left rather than elapsed\n toggleInvert: true,\n // Force an aspect ratio\n // The format must be `'w:h'` (e.g. `'16:9'`)\n ratio: null,\n // Click video container to play/pause\n clickToPlay: true,\n // Auto hide the controls\n hideControls: true,\n // Reset to start when playback ended\n resetOnEnd: false,\n // Disable the standard context menu\n disableContextMenu: true,\n // Sprite (for icons)\n loadSprite: true,\n iconPrefix: 'plyr',\n iconUrl: 'https://cdn.plyr.io/3.7.8/plyr.svg',\n // Blank video (used to prevent errors on source change)\n blankVideo: 'https://cdn.plyr.io/static/blank.mp4',\n // Quality default\n quality: {\n default: 576,\n // The options to display in the UI, if available for the source media\n options: [4320, 2880, 2160, 1440, 1080, 720, 576, 480, 360, 240],\n forced: false,\n onChange: null\n },\n // Set loops\n loop: {\n active: false\n // start: null,\n // end: null,\n },\n\n // Speed default and options to display\n speed: {\n selected: 1,\n // The options to display in the UI, if available for the source media (e.g. Vimeo and YouTube only support 0.5x-4x)\n options: [0.5, 0.75, 1, 1.25, 1.5, 1.75, 2, 4]\n },\n // Keyboard shortcut settings\n keyboard: {\n focused: true,\n global: false\n },\n // Display tooltips\n tooltips: {\n controls: false,\n seek: true\n },\n // Captions settings\n captions: {\n active: false,\n language: 'auto',\n // Listen to new tracks added after Plyr is initialized.\n // This is needed for streaming captions, but may result in unselectable options\n update: false\n },\n // Fullscreen settings\n fullscreen: {\n enabled: true,\n // Allow fullscreen?\n fallback: true,\n // Fallback using full viewport/window\n iosNative: false // Use the native fullscreen in iOS (disables custom controls)\n // Selector for the fullscreen container so contextual / non-player content can remain visible in fullscreen mode\n // Non-ancestors of the player element will be ignored\n // container: null, // defaults to the player element\n },\n\n // Local storage\n storage: {\n enabled: true,\n key: 'plyr'\n },\n // Default controls\n controls: ['play-large',\n // 'restart',\n // 'rewind',\n 'play',\n // 'fast-forward',\n 'progress', 'current-time',\n // 'duration',\n 'mute', 'volume', 'captions', 'settings', 'pip', 'airplay',\n // 'download',\n 'fullscreen'],\n settings: ['captions', 'quality', 'speed'],\n // Localisation\n i18n: {\n restart: 'Restart',\n rewind: 'Rewind {seektime}s',\n play: 'Play',\n pause: 'Pause',\n fastForward: 'Forward {seektime}s',\n seek: 'Seek',\n seekLabel: '{currentTime} of {duration}',\n played: 'Played',\n buffered: 'Buffered',\n currentTime: 'Current time',\n duration: 'Duration',\n volume: 'Volume',\n mute: 'Mute',\n unmute: 'Unmute',\n enableCaptions: 'Enable captions',\n disableCaptions: 'Disable captions',\n download: 'Download',\n enterFullscreen: 'Enter fullscreen',\n exitFullscreen: 'Exit fullscreen',\n frameTitle: 'Player for {title}',\n captions: 'Captions',\n settings: 'Settings',\n pip: 'PIP',\n menuBack: 'Go back to previous menu',\n speed: 'Speed',\n normal: 'Normal',\n quality: 'Quality',\n loop: 'Loop',\n start: 'Start',\n end: 'End',\n all: 'All',\n reset: 'Reset',\n disabled: 'Disabled',\n enabled: 'Enabled',\n advertisement: 'Ad',\n qualityBadge: {\n 2160: '4K',\n 1440: 'HD',\n 1080: 'HD',\n 720: 'HD',\n 576: 'SD',\n 480: 'SD'\n }\n },\n // URLs\n urls: {\n download: null,\n vimeo: {\n sdk: 'https://player.vimeo.com/api/player.js',\n iframe: 'https://player.vimeo.com/video/{0}?{1}',\n api: 'https://vimeo.com/api/oembed.json?url={0}'\n },\n youtube: {\n sdk: 'https://www.youtube.com/iframe_api',\n api: 'https://noembed.com/embed?url=https://www.youtube.com/watch?v={0}'\n },\n googleIMA: {\n sdk: 'https://imasdk.googleapis.com/js/sdkloader/ima3.js'\n }\n },\n // Custom control listeners\n listeners: {\n seek: null,\n play: null,\n pause: null,\n restart: null,\n rewind: null,\n fastForward: null,\n mute: null,\n volume: null,\n captions: null,\n download: null,\n fullscreen: null,\n pip: null,\n airplay: null,\n speed: null,\n quality: null,\n loop: null,\n language: null\n },\n // Events to watch and bubble\n events: [\n // Events to watch on HTML5 media elements and bubble\n // https://developer.mozilla.org/en/docs/Web/Guide/Events/Media_events\n 'ended', 'progress', 'stalled', 'playing', 'waiting', 'canplay', 'canplaythrough', 'loadstart', 'loadeddata', 'loadedmetadata', 'timeupdate', 'volumechange', 'play', 'pause', 'error', 'seeking', 'seeked', 'emptied', 'ratechange', 'cuechange',\n // Custom events\n 'download', 'enterfullscreen', 'exitfullscreen', 'captionsenabled', 'captionsdisabled', 'languagechange', 'controlshidden', 'controlsshown', 'ready',\n // YouTube\n 'statechange',\n // Quality\n 'qualitychange',\n // Ads\n 'adsloaded', 'adscontentpause', 'adscontentresume', 'adstarted', 'adsmidpoint', 'adscomplete', 'adsallcomplete', 'adsimpression', 'adsclick'],\n // Selectors\n // Change these to match your template if using custom HTML\n selectors: {\n editable: 'input, textarea, select, [contenteditable]',\n container: '.plyr',\n controls: {\n container: null,\n wrapper: '.plyr__controls'\n },\n labels: '[data-plyr]',\n buttons: {\n play: '[data-plyr=\"play\"]',\n pause: '[data-plyr=\"pause\"]',\n restart: '[data-plyr=\"restart\"]',\n rewind: '[data-plyr=\"rewind\"]',\n fastForward: '[data-plyr=\"fast-forward\"]',\n mute: '[data-plyr=\"mute\"]',\n captions: '[data-plyr=\"captions\"]',\n download: '[data-plyr=\"download\"]',\n fullscreen: '[data-plyr=\"fullscreen\"]',\n pip: '[data-plyr=\"pip\"]',\n airplay: '[data-plyr=\"airplay\"]',\n settings: '[data-plyr=\"settings\"]',\n loop: '[data-plyr=\"loop\"]'\n },\n inputs: {\n seek: '[data-plyr=\"seek\"]',\n volume: '[data-plyr=\"volume\"]',\n speed: '[data-plyr=\"speed\"]',\n language: '[data-plyr=\"language\"]',\n quality: '[data-plyr=\"quality\"]'\n },\n display: {\n currentTime: '.plyr__time--current',\n duration: '.plyr__time--duration',\n buffer: '.plyr__progress__buffer',\n loop: '.plyr__progress__loop',\n // Used later\n volume: '.plyr__volume--display'\n },\n progress: '.plyr__progress',\n captions: '.plyr__captions',\n caption: '.plyr__caption'\n },\n // Class hooks added to the player in different states\n classNames: {\n type: 'plyr--{0}',\n provider: 'plyr--{0}',\n video: 'plyr__video-wrapper',\n embed: 'plyr__video-embed',\n videoFixedRatio: 'plyr__video-wrapper--fixed-ratio',\n embedContainer: 'plyr__video-embed__container',\n poster: 'plyr__poster',\n posterEnabled: 'plyr__poster-enabled',\n ads: 'plyr__ads',\n control: 'plyr__control',\n controlPressed: 'plyr__control--pressed',\n playing: 'plyr--playing',\n paused: 'plyr--paused',\n stopped: 'plyr--stopped',\n loading: 'plyr--loading',\n hover: 'plyr--hover',\n tooltip: 'plyr__tooltip',\n cues: 'plyr__cues',\n marker: 'plyr__progress__marker',\n hidden: 'plyr__sr-only',\n hideControls: 'plyr--hide-controls',\n isTouch: 'plyr--is-touch',\n uiSupported: 'plyr--full-ui',\n noTransition: 'plyr--no-transition',\n display: {\n time: 'plyr__time'\n },\n menu: {\n value: 'plyr__menu__value',\n badge: 'plyr__badge',\n open: 'plyr--menu-open'\n },\n captions: {\n enabled: 'plyr--captions-enabled',\n active: 'plyr--captions-active'\n },\n fullscreen: {\n enabled: 'plyr--fullscreen-enabled',\n fallback: 'plyr--fullscreen-fallback'\n },\n pip: {\n supported: 'plyr--pip-supported',\n active: 'plyr--pip-active'\n },\n airplay: {\n supported: 'plyr--airplay-supported',\n active: 'plyr--airplay-active'\n },\n previewThumbnails: {\n // Tooltip thumbs\n thumbContainer: 'plyr__preview-thumb',\n thumbContainerShown: 'plyr__preview-thumb--is-shown',\n imageContainer: 'plyr__preview-thumb__image-container',\n timeContainer: 'plyr__preview-thumb__time-container',\n // Scrubbing\n scrubbingContainer: 'plyr__preview-scrubbing',\n scrubbingContainerShown: 'plyr__preview-scrubbing--is-shown'\n }\n },\n // Embed attributes\n attributes: {\n embed: {\n provider: 'data-plyr-provider',\n id: 'data-plyr-embed-id',\n hash: 'data-plyr-embed-hash'\n }\n },\n // Advertisements plugin\n // Register for an account here: http://vi.ai/publisher-video-monetization/?aid=plyrio\n ads: {\n enabled: false,\n publisherId: '',\n tagUrl: ''\n },\n // Preview Thumbnails plugin\n previewThumbnails: {\n enabled: false,\n src: ''\n },\n // Vimeo plugin\n vimeo: {\n byline: false,\n portrait: false,\n title: false,\n speed: true,\n transparent: false,\n // Custom settings from Plyr\n customControls: true,\n referrerPolicy: null,\n // https://developer.mozilla.org/en-US/docs/Web/API/HTMLIFrameElement/referrerPolicy\n // Whether the owner of the video has a Pro or Business account\n // (which allows us to properly hide controls without CSS hacks, etc)\n premium: false\n },\n // YouTube plugin\n youtube: {\n rel: 0,\n // No related vids\n showinfo: 0,\n // Hide info\n iv_load_policy: 3,\n // Hide annotations\n modestbranding: 1,\n // Hide logos as much as possible (they still show one in the corner when paused)\n // Custom settings from Plyr\n customControls: true,\n noCookie: false // Whether to use an alternative version of YouTube without cookies\n },\n\n // Media Metadata\n mediaMetadata: {\n title: '',\n artist: '',\n album: '',\n artwork: []\n },\n // Markers\n markers: {\n enabled: false,\n points: []\n }\n };\n\n // ==========================================================================\n // Plyr states\n // ==========================================================================\n\n const pip = {\n active: 'picture-in-picture',\n inactive: 'inline'\n };\n\n // ==========================================================================\n // Plyr supported types and providers\n // ==========================================================================\n\n const providers = {\n html5: 'html5',\n youtube: 'youtube',\n vimeo: 'vimeo'\n };\n const types = {\n audio: 'audio',\n video: 'video'\n };\n\n /**\n * Get provider by URL\n * @param {String} url\n */\n function getProviderByUrl(url) {\n // YouTube\n if (/^(https?:\\/\\/)?(www\\.)?(youtube\\.com|youtube-nocookie\\.com|youtu\\.?be)\\/.+$/.test(url)) {\n return providers.youtube;\n }\n\n // Vimeo\n if (/^https?:\\/\\/player.vimeo.com\\/video\\/\\d{0,9}(?=\\b|\\/)/.test(url)) {\n return providers.vimeo;\n }\n return null;\n }\n\n // ==========================================================================\n // Console wrapper\n // ==========================================================================\n\n const noop = () => {};\n class Console {\n constructor(enabled = false) {\n this.enabled = window.console && enabled;\n if (this.enabled) {\n this.log('Debugging enabled');\n }\n }\n get log() {\n // eslint-disable-next-line no-console\n return this.enabled ? Function.prototype.bind.call(console.log, console) : noop;\n }\n get warn() {\n // eslint-disable-next-line no-console\n return this.enabled ? Function.prototype.bind.call(console.warn, console) : noop;\n }\n get error() {\n // eslint-disable-next-line no-console\n return this.enabled ? Function.prototype.bind.call(console.error, console) : noop;\n }\n }\n\n class Fullscreen {\n constructor(player) {\n _defineProperty$1(this, \"onChange\", () => {\n if (!this.supported) return;\n\n // Update toggle button\n const button = this.player.elements.buttons.fullscreen;\n if (is.element(button)) {\n button.pressed = this.active;\n }\n\n // Always trigger events on the plyr / media element (not a fullscreen container) and let them bubble up\n const target = this.target === this.player.media ? this.target : this.player.elements.container;\n // Trigger an event\n triggerEvent.call(this.player, target, this.active ? 'enterfullscreen' : 'exitfullscreen', true);\n });\n _defineProperty$1(this, \"toggleFallback\", (toggle = false) => {\n // Store or restore scroll position\n if (toggle) {\n this.scrollPosition = {\n x: window.scrollX ?? 0,\n y: window.scrollY ?? 0\n };\n } else {\n window.scrollTo(this.scrollPosition.x, this.scrollPosition.y);\n }\n\n // Toggle scroll\n document.body.style.overflow = toggle ? 'hidden' : '';\n\n // Toggle class hook\n toggleClass(this.target, this.player.config.classNames.fullscreen.fallback, toggle);\n\n // Force full viewport on iPhone X+\n if (browser.isIos) {\n let viewport = document.head.querySelector('meta[name=\"viewport\"]');\n const property = 'viewport-fit=cover';\n\n // Inject the viewport meta if required\n if (!viewport) {\n viewport = document.createElement('meta');\n viewport.setAttribute('name', 'viewport');\n }\n\n // Check if the property already exists\n const hasProperty = is.string(viewport.content) && viewport.content.includes(property);\n if (toggle) {\n this.cleanupViewport = !hasProperty;\n if (!hasProperty) viewport.content += `,${property}`;\n } else if (this.cleanupViewport) {\n viewport.content = viewport.content.split(',').filter(part => part.trim() !== property).join(',');\n }\n }\n\n // Toggle button and fire events\n this.onChange();\n });\n // Trap focus inside container\n _defineProperty$1(this, \"trapFocus\", event => {\n // Bail if iOS/iPadOS, not active, not the tab key\n if (browser.isIos || browser.isIPadOS || !this.active || event.key !== 'Tab') return;\n\n // Get the current focused element\n const focused = document.activeElement;\n const focusable = getElements.call(this.player, 'a[href], button:not(:disabled), input:not(:disabled), [tabindex]');\n const [first] = focusable;\n const last = focusable[focusable.length - 1];\n if (focused === last && !event.shiftKey) {\n // Move focus to first element that can be tabbed if Shift isn't used\n first.focus();\n event.preventDefault();\n } else if (focused === first && event.shiftKey) {\n // Move focus to last element that can be tabbed if Shift is used\n last.focus();\n event.preventDefault();\n }\n });\n // Update UI\n _defineProperty$1(this, \"update\", () => {\n if (this.supported) {\n let mode;\n if (this.forceFallback) mode = 'Fallback (forced)';else if (Fullscreen.nativeSupported) mode = 'Native';else mode = 'Fallback';\n this.player.debug.log(`${mode} fullscreen enabled`);\n } else {\n this.player.debug.log('Fullscreen not supported and fallback disabled');\n }\n\n // Add styling hook to show button\n toggleClass(this.player.elements.container, this.player.config.classNames.fullscreen.enabled, this.supported);\n });\n // Make an element fullscreen\n _defineProperty$1(this, \"enter\", () => {\n if (!this.supported) return;\n\n // iOS native fullscreen doesn't need the request step\n if (browser.isIos && this.player.config.fullscreen.iosNative) {\n if (this.player.isVimeo) {\n this.player.embed.requestFullscreen();\n } else {\n this.target.webkitEnterFullscreen();\n }\n } else if (!Fullscreen.nativeSupported || this.forceFallback) {\n this.toggleFallback(true);\n } else if (!this.prefix) {\n this.target.requestFullscreen({\n navigationUI: 'hide'\n });\n } else if (!is.empty(this.prefix)) {\n this.target[`${this.prefix}Request${this.property}`]();\n }\n });\n // Bail from fullscreen\n _defineProperty$1(this, \"exit\", () => {\n if (!this.supported) return;\n\n // iOS native fullscreen\n if (browser.isIos && this.player.config.fullscreen.iosNative) {\n if (this.player.isVimeo) {\n this.player.embed.exitFullscreen();\n } else {\n this.target.webkitEnterFullscreen();\n }\n silencePromise(this.player.play());\n } else if (!Fullscreen.nativeSupported || this.forceFallback) {\n this.toggleFallback(false);\n } else if (!this.prefix) {\n (document.cancelFullScreen || document.exitFullscreen).call(document);\n } else if (!is.empty(this.prefix)) {\n const action = this.prefix === 'moz' ? 'Cancel' : 'Exit';\n document[`${this.prefix}${action}${this.property}`]();\n }\n });\n // Toggle state\n _defineProperty$1(this, \"toggle\", () => {\n if (!this.active) this.enter();else this.exit();\n });\n // Keep reference to parent\n this.player = player;\n\n // Get prefix\n this.prefix = Fullscreen.prefix;\n this.property = Fullscreen.property;\n\n // Scroll position\n this.scrollPosition = {\n x: 0,\n y: 0\n };\n\n // Force the use of 'full window/browser' rather than fullscreen\n this.forceFallback = player.config.fullscreen.fallback === 'force';\n\n // Get the fullscreen element\n // Checks container is an ancestor, defaults to null\n this.player.elements.fullscreen = player.config.fullscreen.container && closest$1(this.player.elements.container, player.config.fullscreen.container);\n\n // Register event listeners\n // Handle event (incase user presses escape etc)\n on.call(this.player, document, this.prefix === 'ms' ? 'MSFullscreenChange' : `${this.prefix}fullscreenchange`, () => {\n // TODO: Filter for target??\n this.onChange();\n });\n\n // Fullscreen toggle on double click\n on.call(this.player, this.player.elements.container, 'dblclick', event => {\n // Ignore double click in controls\n if (is.element(this.player.elements.controls) && this.player.elements.controls.contains(event.target)) {\n return;\n }\n this.player.listeners.proxy(event, this.toggle, 'fullscreen');\n });\n\n // Tap focus when in fullscreen\n on.call(this, this.player.elements.container, 'keydown', event => this.trapFocus(event));\n\n // Update the UI\n this.update();\n }\n\n // Determine if native supported\n static get nativeSupported() {\n return !!(document.fullscreenEnabled || document.webkitFullscreenEnabled || document.mozFullScreenEnabled || document.msFullscreenEnabled);\n }\n\n // If we're actually using native\n get useNative() {\n return Fullscreen.nativeSupported && !this.forceFallback;\n }\n\n // Get the prefix for handlers\n static get prefix() {\n // No prefix\n if (is.function(document.exitFullscreen)) return '';\n\n // Check for fullscreen support by vendor prefix\n let value = '';\n const prefixes = ['webkit', 'moz', 'ms'];\n prefixes.some(pre => {\n if (is.function(document[`${pre}ExitFullscreen`]) || is.function(document[`${pre}CancelFullScreen`])) {\n value = pre;\n return true;\n }\n return false;\n });\n return value;\n }\n static get property() {\n return this.prefix === 'moz' ? 'FullScreen' : 'Fullscreen';\n }\n\n // Determine if fullscreen is supported\n get supported() {\n return [\n // Fullscreen is enabled in config\n this.player.config.fullscreen.enabled,\n // Must be a video\n this.player.isVideo,\n // Either native is supported or fallback enabled\n Fullscreen.nativeSupported || this.player.config.fullscreen.fallback,\n // YouTube has no way to trigger fullscreen, so on devices with no native support, playsinline\n // must be enabled and iosNative fullscreen must be disabled to offer the fullscreen fallback\n !this.player.isYouTube || Fullscreen.nativeSupported || !browser.isIos || this.player.config.playsinline && !this.player.config.fullscreen.iosNative].every(Boolean);\n }\n\n // Get active state\n get active() {\n if (!this.supported) return false;\n\n // Fallback using classname\n if (!Fullscreen.nativeSupported || this.forceFallback) {\n return hasClass(this.target, this.player.config.classNames.fullscreen.fallback);\n }\n const element = !this.prefix ? this.target.getRootNode().fullscreenElement : this.target.getRootNode()[`${this.prefix}${this.property}Element`];\n return element && element.shadowRoot ? element === this.target.getRootNode().host : element === this.target;\n }\n\n // Get target element\n get target() {\n return browser.isIos && this.player.config.fullscreen.iosNative ? this.player.media : this.player.elements.fullscreen ?? this.player.elements.container;\n }\n }\n\n // ==========================================================================\n // Load image avoiding xhr/fetch CORS issues\n // Server status can't be obtained this way unfortunately, so this uses \"naturalWidth\" to determine if the image has loaded\n // By default it checks if it is at least 1px, but you can add a second argument to change this\n // ==========================================================================\n\n function loadImage(src, minWidth = 1) {\n return new Promise((resolve, reject) => {\n const image = new Image();\n const handler = () => {\n delete image.onload;\n delete image.onerror;\n (image.naturalWidth >= minWidth ? resolve : reject)(image);\n };\n Object.assign(image, {\n onload: handler,\n onerror: handler,\n src\n });\n });\n }\n\n // ==========================================================================\n const ui = {\n addStyleHook() {\n toggleClass(this.elements.container, this.config.selectors.container.replace('.', ''), true);\n toggleClass(this.elements.container, this.config.classNames.uiSupported, this.supported.ui);\n },\n // Toggle native HTML5 media controls\n toggleNativeControls(toggle = false) {\n if (toggle && this.isHTML5) {\n this.media.setAttribute('controls', '');\n } else {\n this.media.removeAttribute('controls');\n }\n },\n // Setup the UI\n build() {\n // Re-attach media element listeners\n // TODO: Use event bubbling?\n this.listeners.media();\n\n // Don't setup interface if no support\n if (!this.supported.ui) {\n this.debug.warn(`Basic support only for ${this.provider} ${this.type}`);\n\n // Restore native controls\n ui.toggleNativeControls.call(this, true);\n\n // Bail\n return;\n }\n\n // Inject custom controls if not present\n if (!is.element(this.elements.controls)) {\n // Inject custom controls\n controls.inject.call(this);\n\n // Re-attach control listeners\n this.listeners.controls();\n }\n\n // Remove native controls\n ui.toggleNativeControls.call(this);\n\n // Setup captions for HTML5\n if (this.isHTML5) {\n captions.setup.call(this);\n }\n\n // Reset volume\n this.volume = null;\n\n // Reset mute state\n this.muted = null;\n\n // Reset loop state\n this.loop = null;\n\n // Reset quality setting\n this.quality = null;\n\n // Reset speed\n this.speed = null;\n\n // Reset volume display\n controls.updateVolume.call(this);\n\n // Reset time display\n controls.timeUpdate.call(this);\n\n // Reset duration display\n controls.durationUpdate.call(this);\n\n // Update the UI\n ui.checkPlaying.call(this);\n\n // Check for picture-in-picture support\n toggleClass(this.elements.container, this.config.classNames.pip.supported, support.pip && this.isHTML5 && this.isVideo);\n\n // Check for airplay support\n toggleClass(this.elements.container, this.config.classNames.airplay.supported, support.airplay && this.isHTML5);\n\n // Add touch class\n toggleClass(this.elements.container, this.config.classNames.isTouch, this.touch);\n\n // Ready for API calls\n this.ready = true;\n\n // Ready event at end of execution stack\n setTimeout(() => {\n triggerEvent.call(this, this.media, 'ready');\n }, 0);\n\n // Set the title\n ui.setTitle.call(this);\n\n // Assure the poster image is set, if the property was added before the element was created\n if (this.poster) {\n ui.setPoster.call(this, this.poster, false).catch(() => {});\n }\n\n // Manually set the duration if user has overridden it.\n // The event listeners for it doesn't get called if preload is disabled (#701)\n if (this.config.duration) {\n controls.durationUpdate.call(this);\n }\n\n // Media metadata\n if (this.config.mediaMetadata) {\n controls.setMediaMetadata.call(this);\n }\n },\n // Setup aria attribute for play and iframe title\n setTitle() {\n // Find the current text\n let label = i18n.get('play', this.config);\n\n // If there's a media title set, use that for the label\n if (is.string(this.config.title) && !is.empty(this.config.title)) {\n label += `, ${this.config.title}`;\n }\n\n // If there's a play button, set label\n Array.from(this.elements.buttons.play || []).forEach(button => {\n button.setAttribute('aria-label', label);\n });\n\n // Set iframe title\n // https://github.com/sampotts/plyr/issues/124\n if (this.isEmbed) {\n const iframe = getElement.call(this, 'iframe');\n if (!is.element(iframe)) {\n return;\n }\n\n // Default to media type\n const title = !is.empty(this.config.title) ? this.config.title : 'video';\n const format = i18n.get('frameTitle', this.config);\n iframe.setAttribute('title', format.replace('{title}', title));\n }\n },\n // Toggle poster\n togglePoster(enable) {\n toggleClass(this.elements.container, this.config.classNames.posterEnabled, enable);\n },\n // Set the poster image (async)\n // Used internally for the poster setter, with the passive option forced to false\n setPoster(poster, passive = true) {\n // Don't override if call is passive\n if (passive && this.poster) {\n return Promise.reject(new Error('Poster already set'));\n }\n\n // Set property synchronously to respect the call order\n this.media.setAttribute('data-poster', poster);\n\n // Show the poster\n this.elements.poster.removeAttribute('hidden');\n\n // Wait until ui is ready\n return ready.call(this)\n // Load image\n .then(() => loadImage(poster)).catch(error => {\n // Hide poster on error unless it's been set by another call\n if (poster === this.poster) {\n ui.togglePoster.call(this, false);\n }\n // Rethrow\n throw error;\n }).then(() => {\n // Prevent race conditions\n if (poster !== this.poster) {\n throw new Error('setPoster cancelled by later call to setPoster');\n }\n }).then(() => {\n Object.assign(this.elements.poster.style, {\n backgroundImage: `url('${poster}')`,\n // Reset backgroundSize as well (since it can be set to \"cover\" for padded thumbnails for youtube)\n backgroundSize: ''\n });\n ui.togglePoster.call(this, true);\n return poster;\n });\n },\n // Check playing state\n checkPlaying(event) {\n // Class hooks\n toggleClass(this.elements.container, this.config.classNames.playing, this.playing);\n toggleClass(this.elements.container, this.config.classNames.paused, this.paused);\n toggleClass(this.elements.container, this.config.classNames.stopped, this.stopped);\n\n // Set state\n Array.from(this.elements.buttons.play || []).forEach(target => {\n Object.assign(target, {\n pressed: this.playing\n });\n target.setAttribute('aria-label', i18n.get(this.playing ? 'pause' : 'play', this.config));\n });\n\n // Only update controls on non timeupdate events\n if (is.event(event) && event.type === 'timeupdate') {\n return;\n }\n\n // Toggle controls\n ui.toggleControls.call(this);\n },\n // Check if media is loading\n checkLoading(event) {\n this.loading = ['stalled', 'waiting'].includes(event.type);\n\n // Clear timer\n clearTimeout(this.timers.loading);\n\n // Timer to prevent flicker when seeking\n this.timers.loading = setTimeout(() => {\n // Update progress bar loading class state\n toggleClass(this.elements.container, this.config.classNames.loading, this.loading);\n\n // Update controls visibility\n ui.toggleControls.call(this);\n }, this.loading ? 250 : 0);\n },\n // Toggle controls based on state and `force` argument\n toggleControls(force) {\n const {\n controls: controlsElement\n } = this.elements;\n if (controlsElement && this.config.hideControls) {\n // Don't hide controls if a touch-device user recently seeked. (Must be limited to touch devices, or it occasionally prevents desktop controls from hiding.)\n const recentTouchSeek = this.touch && this.lastSeekTime + 2000 > Date.now();\n\n // Show controls if force, loading, paused, button interaction, or recent seek, otherwise hide\n this.toggleControls(Boolean(force || this.loading || this.paused || controlsElement.pressed || controlsElement.hover || recentTouchSeek));\n }\n },\n // Migrate any custom properties from the media to the parent\n migrateStyles() {\n // Loop through values (as they are the keys when the object is spread 🤔)\n Object.values({\n ...this.media.style\n })\n // We're only fussed about Plyr specific properties\n .filter(key => !is.empty(key) && is.string(key) && key.startsWith('--plyr')).forEach(key => {\n // Set on the container\n this.elements.container.style.setProperty(key, this.media.style.getPropertyValue(key));\n\n // Clean up from media element\n this.media.style.removeProperty(key);\n });\n\n // Remove attribute if empty\n if (is.empty(this.media.style)) {\n this.media.removeAttribute('style');\n }\n }\n };\n\n class Listeners {\n constructor(_player) {\n // Device is touch enabled\n _defineProperty$1(this, \"firstTouch\", () => {\n const {\n player\n } = this;\n const {\n elements\n } = player;\n player.touch = true;\n\n // Add touch class\n toggleClass(elements.container, player.config.classNames.isTouch, true);\n });\n // Global window & document listeners\n _defineProperty$1(this, \"global\", (toggle = true) => {\n const {\n player\n } = this;\n\n // Keyboard shortcuts\n if (player.config.keyboard.global) {\n toggleListener.call(player, window, 'keydown keyup', this.handleKey, toggle, false);\n }\n\n // Click anywhere closes menu\n toggleListener.call(player, document.body, 'click', this.toggleMenu, toggle);\n\n // Detect touch by events\n once.call(player, document.body, 'touchstart', this.firstTouch);\n });\n // Container listeners\n _defineProperty$1(this, \"container\", () => {\n const {\n player\n } = this;\n const {\n config,\n elements,\n timers\n } = player;\n\n // Keyboard shortcuts\n if (!config.keyboard.global && config.keyboard.focused) {\n on.call(player, elements.container, 'keydown keyup', this.handleKey, false);\n }\n\n // Toggle controls on mouse events and entering fullscreen\n on.call(player, elements.container, 'mousemove mouseleave touchstart touchmove enterfullscreen exitfullscreen', event => {\n const {\n controls: controlsElement\n } = elements;\n\n // Remove button states for fullscreen\n if (controlsElement && event.type === 'enterfullscreen') {\n controlsElement.pressed = false;\n controlsElement.hover = false;\n }\n\n // Show, then hide after a timeout unless another control event occurs\n const show = ['touchstart', 'touchmove', 'mousemove'].includes(event.type);\n let delay = 0;\n if (show) {\n ui.toggleControls.call(player, true);\n // Use longer timeout for touch devices\n delay = player.touch ? 3000 : 2000;\n }\n\n // Clear timer\n clearTimeout(timers.controls);\n\n // Set new timer to prevent flicker when seeking\n timers.controls = setTimeout(() => ui.toggleControls.call(player, false), delay);\n });\n\n // Set a gutter for Vimeo\n const setGutter = () => {\n if (!player.isVimeo || player.config.vimeo.premium) {\n return;\n }\n const target = elements.wrapper;\n const {\n active\n } = player.fullscreen;\n const [videoWidth, videoHeight] = getAspectRatio.call(player);\n const useNativeAspectRatio = supportsCSS(`aspect-ratio: ${videoWidth} / ${videoHeight}`);\n\n // If not active, remove styles\n if (!active) {\n if (useNativeAspectRatio) {\n target.style.width = null;\n target.style.height = null;\n } else {\n target.style.maxWidth = null;\n target.style.margin = null;\n }\n return;\n }\n\n // Determine which dimension will overflow and constrain view\n const [viewportWidth, viewportHeight] = getViewportSize();\n const overflow = viewportWidth / viewportHeight > videoWidth / videoHeight;\n if (useNativeAspectRatio) {\n target.style.width = overflow ? 'auto' : '100%';\n target.style.height = overflow ? '100%' : 'auto';\n } else {\n target.style.maxWidth = overflow ? `${viewportHeight / videoHeight * videoWidth}px` : null;\n target.style.margin = overflow ? '0 auto' : null;\n }\n };\n\n // Handle resizing\n const resized = () => {\n clearTimeout(timers.resized);\n timers.resized = setTimeout(setGutter, 50);\n };\n on.call(player, elements.container, 'enterfullscreen exitfullscreen', event => {\n const {\n target\n } = player.fullscreen;\n\n // Ignore events not from target\n if (target !== elements.container) {\n return;\n }\n\n // If it's not an embed and no ratio specified\n if (!player.isEmbed && is.empty(player.config.ratio)) {\n return;\n }\n\n // Set Vimeo gutter\n setGutter();\n\n // Watch for resizes\n const method = event.type === 'enterfullscreen' ? on : off;\n method.call(player, window, 'resize', resized);\n });\n });\n // Listen for media events\n _defineProperty$1(this, \"media\", () => {\n const {\n player\n } = this;\n const {\n elements\n } = player;\n\n // Time change on media\n on.call(player, player.media, 'timeupdate seeking seeked', event => controls.timeUpdate.call(player, event));\n\n // Display duration\n on.call(player, player.media, 'durationchange loadeddata loadedmetadata', event => controls.durationUpdate.call(player, event));\n\n // Handle the media finishing\n on.call(player, player.media, 'ended', () => {\n // Show poster on end\n if (player.isHTML5 && player.isVideo && player.config.resetOnEnd) {\n // Restart\n player.restart();\n\n // Call pause otherwise IE11 will start playing the video again\n player.pause();\n }\n });\n\n // Check for buffer progress\n on.call(player, player.media, 'progress playing seeking seeked', event => controls.updateProgress.call(player, event));\n\n // Handle volume changes\n on.call(player, player.media, 'volumechange', event => controls.updateVolume.call(player, event));\n\n // Handle play/pause\n on.call(player, player.media, 'playing play pause ended emptied timeupdate', event => ui.checkPlaying.call(player, event));\n\n // Loading state\n on.call(player, player.media, 'waiting canplay seeked playing', event => ui.checkLoading.call(player, event));\n\n // Click video\n if (player.supported.ui && player.config.clickToPlay && !player.isAudio) {\n // Re-fetch the wrapper\n const wrapper = getElement.call(player, `.${player.config.classNames.video}`);\n\n // Bail if there's no wrapper (this should never happen)\n if (!is.element(wrapper)) {\n return;\n }\n\n // On click play, pause or restart\n on.call(player, elements.container, 'click', event => {\n const targets = [elements.container, wrapper];\n\n // Ignore if click if not container or in video wrapper\n if (!targets.includes(event.target) && !wrapper.contains(event.target)) {\n return;\n }\n\n // Touch devices will just show controls (if hidden)\n if (player.touch && player.config.hideControls) {\n return;\n }\n if (player.ended) {\n this.proxy(event, player.restart, 'restart');\n this.proxy(event, () => {\n silencePromise(player.play());\n }, 'play');\n } else {\n this.proxy(event, () => {\n silencePromise(player.togglePlay());\n }, 'play');\n }\n });\n }\n\n // Disable right click\n if (player.supported.ui && player.config.disableContextMenu) {\n on.call(player, elements.wrapper, 'contextmenu', event => {\n event.preventDefault();\n }, false);\n }\n\n // Volume change\n on.call(player, player.media, 'volumechange', () => {\n // Save to storage\n player.storage.set({\n volume: player.volume,\n muted: player.muted\n });\n });\n\n // Speed change\n on.call(player, player.media, 'ratechange', () => {\n // Update UI\n controls.updateSetting.call(player, 'speed');\n\n // Save to storage\n player.storage.set({\n speed: player.speed\n });\n });\n\n // Quality change\n on.call(player, player.media, 'qualitychange', event => {\n // Update UI\n controls.updateSetting.call(player, 'quality', null, event.detail.quality);\n });\n\n // Update download link when ready and if quality changes\n on.call(player, player.media, 'ready qualitychange', () => {\n controls.setDownloadUrl.call(player);\n });\n\n // Proxy events to container\n // Bubble up key events for Edge\n const proxyEvents = player.config.events.concat(['keyup', 'keydown']).join(' ');\n on.call(player, player.media, proxyEvents, event => {\n let {\n detail = {}\n } = event;\n\n // Get error details from media\n if (event.type === 'error') {\n detail = player.media.error;\n }\n triggerEvent.call(player, elements.container, event.type, true, detail);\n });\n });\n // Run default and custom handlers\n _defineProperty$1(this, \"proxy\", (event, defaultHandler, customHandlerKey) => {\n const {\n player\n } = this;\n const customHandler = player.config.listeners[customHandlerKey];\n const hasCustomHandler = is.function(customHandler);\n let returned = true;\n\n // Execute custom handler\n if (hasCustomHandler) {\n returned = customHandler.call(player, event);\n }\n\n // Only call default handler if not prevented in custom handler\n if (returned !== false && is.function(defaultHandler)) {\n defaultHandler.call(player, event);\n }\n });\n // Trigger custom and default handlers\n _defineProperty$1(this, \"bind\", (element, type, defaultHandler, customHandlerKey, passive = true) => {\n const {\n player\n } = this;\n const customHandler = player.config.listeners[customHandlerKey];\n const hasCustomHandler = is.function(customHandler);\n on.call(player, element, type, event => this.proxy(event, defaultHandler, customHandlerKey), passive && !hasCustomHandler);\n });\n // Listen for control events\n _defineProperty$1(this, \"controls\", () => {\n const {\n player\n } = this;\n const {\n elements\n } = player;\n // IE doesn't support input event, so we fallback to change\n const inputEvent = browser.isIE ? 'change' : 'input';\n\n // Play/pause toggle\n if (elements.buttons.play) {\n Array.from(elements.buttons.play).forEach(button => {\n this.bind(button, 'click', () => {\n silencePromise(player.togglePlay());\n }, 'play');\n });\n }\n\n // Pause\n this.bind(elements.buttons.restart, 'click', player.restart, 'restart');\n\n // Rewind\n this.bind(elements.buttons.rewind, 'click', () => {\n // Record seek time so we can prevent hiding controls for a few seconds after rewind\n player.lastSeekTime = Date.now();\n player.rewind();\n }, 'rewind');\n\n // Rewind\n this.bind(elements.buttons.fastForward, 'click', () => {\n // Record seek time so we can prevent hiding controls for a few seconds after fast forward\n player.lastSeekTime = Date.now();\n player.forward();\n }, 'fastForward');\n\n // Mute toggle\n this.bind(elements.buttons.mute, 'click', () => {\n player.muted = !player.muted;\n }, 'mute');\n\n // Captions toggle\n this.bind(elements.buttons.captions, 'click', () => player.toggleCaptions());\n\n // Download\n this.bind(elements.buttons.download, 'click', () => {\n triggerEvent.call(player, player.media, 'download');\n }, 'download');\n\n // Fullscreen toggle\n this.bind(elements.buttons.fullscreen, 'click', () => {\n player.fullscreen.toggle();\n }, 'fullscreen');\n\n // Picture-in-Picture\n this.bind(elements.buttons.pip, 'click', () => {\n player.pip = 'toggle';\n }, 'pip');\n\n // Airplay\n this.bind(elements.buttons.airplay, 'click', player.airplay, 'airplay');\n\n // Settings menu - click toggle\n this.bind(elements.buttons.settings, 'click', event => {\n // Prevent the document click listener closing the menu\n event.stopPropagation();\n event.preventDefault();\n controls.toggleMenu.call(player, event);\n }, null, false); // Can't be passive as we're preventing default\n\n // Settings menu - keyboard toggle\n // We have to bind to keyup otherwise Firefox triggers a click when a keydown event handler shifts focus\n // https://bugzilla.mozilla.org/show_bug.cgi?id=1220143\n this.bind(elements.buttons.settings, 'keyup', event => {\n if (![' ', 'Enter'].includes(event.key)) {\n return;\n }\n\n // Because return triggers a click anyway, all we need to do is set focus\n if (event.key === 'Enter') {\n controls.focusFirstMenuItem.call(player, null, true);\n return;\n }\n\n // Prevent scroll\n event.preventDefault();\n\n // Prevent playing video (Firefox)\n event.stopPropagation();\n\n // Toggle menu\n controls.toggleMenu.call(player, event);\n }, null, false // Can't be passive as we're preventing default\n );\n\n // Escape closes menu\n this.bind(elements.settings.menu, 'keydown', event => {\n if (event.key === 'Escape') {\n controls.toggleMenu.call(player, event);\n }\n });\n\n // Set range input alternative \"value\", which matches the tooltip time (#954)\n this.bind(elements.inputs.seek, 'mousedown mousemove', event => {\n const rect = elements.progress.getBoundingClientRect();\n const percent = 100 / rect.width * (event.pageX - rect.left);\n event.currentTarget.setAttribute('seek-value', percent);\n });\n\n // Pause while seeking\n this.bind(elements.inputs.seek, 'mousedown mouseup keydown keyup touchstart touchend', event => {\n const seek = event.currentTarget;\n const attribute = 'play-on-seeked';\n if (is.keyboardEvent(event) && !['ArrowLeft', 'ArrowRight'].includes(event.key)) {\n return;\n }\n\n // Record seek time so we can prevent hiding controls for a few seconds after seek\n player.lastSeekTime = Date.now();\n\n // Was playing before?\n const play = seek.hasAttribute(attribute);\n // Done seeking\n const done = ['mouseup', 'touchend', 'keyup'].includes(event.type);\n\n // If we're done seeking and it was playing, resume playback\n if (play && done) {\n seek.removeAttribute(attribute);\n silencePromise(player.play());\n } else if (!done && player.playing) {\n seek.setAttribute(attribute, '');\n player.pause();\n }\n });\n\n // Fix range inputs on iOS\n // Super weird iOS bug where after you interact with an ,\n // it takes over further interactions on the page. This is a hack\n if (browser.isIos) {\n const inputs = getElements.call(player, 'input[type=\"range\"]');\n Array.from(inputs).forEach(input => this.bind(input, inputEvent, event => repaint(event.target)));\n }\n\n // Seek\n this.bind(elements.inputs.seek, inputEvent, event => {\n const seek = event.currentTarget;\n // If it exists, use seek-value instead of \"value\" for consistency with tooltip time (#954)\n let seekTo = seek.getAttribute('seek-value');\n if (is.empty(seekTo)) {\n seekTo = seek.value;\n }\n seek.removeAttribute('seek-value');\n player.currentTime = seekTo / seek.max * player.duration;\n }, 'seek');\n\n // Seek tooltip\n this.bind(elements.progress, 'mouseenter mouseleave mousemove', event => controls.updateSeekTooltip.call(player, event));\n\n // Preview thumbnails plugin\n // TODO: Really need to work on some sort of plug-in wide event bus or pub-sub for this\n this.bind(elements.progress, 'mousemove touchmove', event => {\n const {\n previewThumbnails\n } = player;\n if (previewThumbnails && previewThumbnails.loaded) {\n previewThumbnails.startMove(event);\n }\n });\n\n // Hide thumbnail preview - on mouse click, mouse leave, and video play/seek. All four are required, e.g., for buffering\n this.bind(elements.progress, 'mouseleave touchend click', () => {\n const {\n previewThumbnails\n } = player;\n if (previewThumbnails && previewThumbnails.loaded) {\n previewThumbnails.endMove(false, true);\n }\n });\n\n // Show scrubbing preview\n this.bind(elements.progress, 'mousedown touchstart', event => {\n const {\n previewThumbnails\n } = player;\n if (previewThumbnails && previewThumbnails.loaded) {\n previewThumbnails.startScrubbing(event);\n }\n });\n this.bind(elements.progress, 'mouseup touchend', event => {\n const {\n previewThumbnails\n } = player;\n if (previewThumbnails && previewThumbnails.loaded) {\n previewThumbnails.endScrubbing(event);\n }\n });\n\n // Polyfill for lower fill in for webkit\n if (browser.isWebKit) {\n Array.from(getElements.call(player, 'input[type=\"range\"]')).forEach(element => {\n this.bind(element, 'input', event => controls.updateRangeFill.call(player, event.target));\n });\n }\n\n // Current time invert\n // Only if one time element is used for both currentTime and duration\n if (player.config.toggleInvert && !is.element(elements.display.duration)) {\n this.bind(elements.display.currentTime, 'click', () => {\n // Do nothing if we're at the start\n if (player.currentTime === 0) {\n return;\n }\n player.config.invertTime = !player.config.invertTime;\n controls.timeUpdate.call(player);\n });\n }\n\n // Volume\n this.bind(elements.inputs.volume, inputEvent, event => {\n player.volume = event.target.value;\n }, 'volume');\n\n // Update controls.hover state (used for ui.toggleControls to avoid hiding when interacting)\n this.bind(elements.controls, 'mouseenter mouseleave', event => {\n elements.controls.hover = !player.touch && event.type === 'mouseenter';\n });\n\n // Also update controls.hover state for any non-player children of fullscreen element (as above)\n if (elements.fullscreen) {\n Array.from(elements.fullscreen.children).filter(c => !c.contains(elements.container)).forEach(child => {\n this.bind(child, 'mouseenter mouseleave', event => {\n if (elements.controls) {\n elements.controls.hover = !player.touch && event.type === 'mouseenter';\n }\n });\n });\n }\n\n // Update controls.pressed state (used for ui.toggleControls to avoid hiding when interacting)\n this.bind(elements.controls, 'mousedown mouseup touchstart touchend touchcancel', event => {\n elements.controls.pressed = ['mousedown', 'touchstart'].includes(event.type);\n });\n\n // Show controls when they receive focus (e.g., when using keyboard tab key)\n this.bind(elements.controls, 'focusin', () => {\n const {\n config,\n timers\n } = player;\n\n // Skip transition to prevent focus from scrolling the parent element\n toggleClass(elements.controls, config.classNames.noTransition, true);\n\n // Toggle\n ui.toggleControls.call(player, true);\n\n // Restore transition\n setTimeout(() => {\n toggleClass(elements.controls, config.classNames.noTransition, false);\n }, 0);\n\n // Delay a little more for mouse users\n const delay = this.touch ? 3000 : 4000;\n\n // Clear timer\n clearTimeout(timers.controls);\n\n // Hide again after delay\n timers.controls = setTimeout(() => ui.toggleControls.call(player, false), delay);\n });\n\n // Mouse wheel for volume\n this.bind(elements.inputs.volume, 'wheel', event => {\n // Detect \"natural\" scroll - supported on OS X Safari only\n // Other browsers on OS X will be inverted until support improves\n const inverted = event.webkitDirectionInvertedFromDevice;\n // Get delta from event. Invert if `inverted` is true\n const [x, y] = [event.deltaX, -event.deltaY].map(value => inverted ? -value : value);\n // Using the biggest delta, normalize to 1 or -1 (or 0 if no delta)\n const direction = Math.sign(Math.abs(x) > Math.abs(y) ? x : y);\n\n // Change the volume by 2%\n player.increaseVolume(direction / 50);\n\n // Don't break page scrolling at max and min\n const {\n volume\n } = player.media;\n if (direction === 1 && volume < 1 || direction === -1 && volume > 0) {\n event.preventDefault();\n }\n }, 'volume', false);\n });\n this.player = _player;\n this.lastKey = null;\n this.focusTimer = null;\n this.lastKeyDown = null;\n this.handleKey = this.handleKey.bind(this);\n this.toggleMenu = this.toggleMenu.bind(this);\n this.firstTouch = this.firstTouch.bind(this);\n }\n\n // Handle key presses\n handleKey(event) {\n const {\n player\n } = this;\n const {\n elements\n } = player;\n const {\n key,\n type,\n altKey,\n ctrlKey,\n metaKey,\n shiftKey\n } = event;\n const pressed = type === 'keydown';\n const repeat = pressed && key === this.lastKey;\n\n // Bail if a modifier key is set\n if (altKey || ctrlKey || metaKey || shiftKey) {\n return;\n }\n\n // If the event is bubbled from the media element\n // Firefox doesn't get the key for whatever reason\n if (!key) {\n return;\n }\n\n // Seek by increment\n const seekByIncrement = increment => {\n // Divide the max duration into 10th's and times by the number value\n player.currentTime = player.duration / 10 * increment;\n };\n\n // Handle the key on keydown\n // Reset on keyup\n if (pressed) {\n // Check focused element\n // and if the focused element is not editable (e.g. text input)\n // and any that accept key input http://webaim.org/techniques/keyboard/\n const focused = document.activeElement;\n if (is.element(focused)) {\n const {\n editable\n } = player.config.selectors;\n const {\n seek\n } = elements.inputs;\n if (focused !== seek && matches(focused, editable)) {\n return;\n }\n if (event.key === ' ' && matches(focused, 'button, [role^=\"menuitem\"]')) {\n return;\n }\n }\n\n // Which keys should we prevent default\n const preventDefault = [' ', 'ArrowLeft', 'ArrowUp', 'ArrowRight', 'ArrowDown', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'c', 'f', 'k', 'l', 'm'];\n\n // If the key is found prevent default (e.g. prevent scrolling for arrows)\n if (preventDefault.includes(key)) {\n event.preventDefault();\n event.stopPropagation();\n }\n switch (key) {\n case '0':\n case '1':\n case '2':\n case '3':\n case '4':\n case '5':\n case '6':\n case '7':\n case '8':\n case '9':\n if (!repeat) {\n seekByIncrement(parseInt(key, 10));\n }\n break;\n case ' ':\n case 'k':\n if (!repeat) {\n silencePromise(player.togglePlay());\n }\n break;\n case 'ArrowUp':\n player.increaseVolume(0.1);\n break;\n case 'ArrowDown':\n player.decreaseVolume(0.1);\n break;\n case 'm':\n if (!repeat) {\n player.muted = !player.muted;\n }\n break;\n case 'ArrowRight':\n player.forward();\n break;\n case 'ArrowLeft':\n player.rewind();\n break;\n case 'f':\n player.fullscreen.toggle();\n break;\n case 'c':\n if (!repeat) {\n player.toggleCaptions();\n }\n break;\n case 'l':\n player.loop = !player.loop;\n break;\n }\n\n // Escape is handle natively when in full screen\n // So we only need to worry about non native\n if (key === 'Escape' && !player.fullscreen.usingNative && player.fullscreen.active) {\n player.fullscreen.toggle();\n }\n\n // Store last key for next cycle\n this.lastKey = key;\n } else {\n this.lastKey = null;\n }\n }\n\n // Toggle menu\n toggleMenu(event) {\n controls.toggleMenu.call(this.player, event);\n }\n }\n\n var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};\n\n function createCommonjsModule(fn, module) {\n \treturn module = { exports: {} }, fn(module, module.exports), module.exports;\n }\n\n var loadjs_umd = createCommonjsModule(function (module, exports) {\n (function (root, factory) {\n {\n module.exports = factory();\n }\n })(commonjsGlobal, function () {\n /**\n * Global dependencies.\n * @global {Object} document - DOM\n */\n\n var devnull = function () {},\n bundleIdCache = {},\n bundleResultCache = {},\n bundleCallbackQueue = {};\n\n /**\n * Subscribe to bundle load event.\n * @param {string[]} bundleIds - Bundle ids\n * @param {Function} callbackFn - The callback function\n */\n function subscribe(bundleIds, callbackFn) {\n // listify\n bundleIds = bundleIds.push ? bundleIds : [bundleIds];\n var depsNotFound = [],\n i = bundleIds.length,\n numWaiting = i,\n fn,\n bundleId,\n r,\n q;\n\n // define callback function\n fn = function (bundleId, pathsNotFound) {\n if (pathsNotFound.length) depsNotFound.push(bundleId);\n numWaiting--;\n if (!numWaiting) callbackFn(depsNotFound);\n };\n\n // register callback\n while (i--) {\n bundleId = bundleIds[i];\n\n // execute callback if in result cache\n r = bundleResultCache[bundleId];\n if (r) {\n fn(bundleId, r);\n continue;\n }\n\n // add to callback queue\n q = bundleCallbackQueue[bundleId] = bundleCallbackQueue[bundleId] || [];\n q.push(fn);\n }\n }\n\n /**\n * Publish bundle load event.\n * @param {string} bundleId - Bundle id\n * @param {string[]} pathsNotFound - List of files not found\n */\n function publish(bundleId, pathsNotFound) {\n // exit if id isn't defined\n if (!bundleId) return;\n var q = bundleCallbackQueue[bundleId];\n\n // cache result\n bundleResultCache[bundleId] = pathsNotFound;\n\n // exit if queue is empty\n if (!q) return;\n\n // empty callback queue\n while (q.length) {\n q[0](bundleId, pathsNotFound);\n q.splice(0, 1);\n }\n }\n\n /**\n * Execute callbacks.\n * @param {Object or Function} args - The callback args\n * @param {string[]} depsNotFound - List of dependencies not found\n */\n function executeCallbacks(args, depsNotFound) {\n // accept function as argument\n if (args.call) args = {\n success: args\n };\n\n // success and error callbacks\n if (depsNotFound.length) (args.error || devnull)(depsNotFound);else (args.success || devnull)(args);\n }\n\n /**\n * Load individual file.\n * @param {string} path - The file path\n * @param {Function} callbackFn - The callback function\n */\n function loadFile(path, callbackFn, args, numTries) {\n var doc = document,\n async = args.async,\n maxTries = (args.numRetries || 0) + 1,\n beforeCallbackFn = args.before || devnull,\n pathname = path.replace(/[\\?|#].*$/, ''),\n pathStripped = path.replace(/^(css|img)!/, ''),\n isLegacyIECss,\n e;\n numTries = numTries || 0;\n if (/(^css!|\\.css$)/.test(pathname)) {\n // css\n e = doc.createElement('link');\n e.rel = 'stylesheet';\n e.href = pathStripped;\n\n // tag IE9+\n isLegacyIECss = 'hideFocus' in e;\n\n // use preload in IE Edge (to detect load errors)\n if (isLegacyIECss && e.relList) {\n isLegacyIECss = 0;\n e.rel = 'preload';\n e.as = 'style';\n }\n } else if (/(^img!|\\.(png|gif|jpg|svg|webp)$)/.test(pathname)) {\n // image\n e = doc.createElement('img');\n e.src = pathStripped;\n } else {\n // javascript\n e = doc.createElement('script');\n e.src = path;\n e.async = async === undefined ? true : async;\n }\n e.onload = e.onerror = e.onbeforeload = function (ev) {\n var result = ev.type[0];\n\n // treat empty stylesheets as failures to get around lack of onerror\n // support in IE9-11\n if (isLegacyIECss) {\n try {\n if (!e.sheet.cssText.length) result = 'e';\n } catch (x) {\n // sheets objects created from load errors don't allow access to\n // `cssText` (unless error is Code:18 SecurityError)\n if (x.code != 18) result = 'e';\n }\n }\n\n // handle retries in case of load failure\n if (result == 'e') {\n // increment counter\n numTries += 1;\n\n // exit function and try again\n if (numTries < maxTries) {\n return loadFile(path, callbackFn, args, numTries);\n }\n } else if (e.rel == 'preload' && e.as == 'style') {\n // activate preloaded stylesheets\n return e.rel = 'stylesheet'; // jshint ignore:line\n }\n\n // execute callback\n callbackFn(path, result, ev.defaultPrevented);\n };\n\n // add to document (unless callback returns `false`)\n if (beforeCallbackFn(path, e) !== false) doc.head.appendChild(e);\n }\n\n /**\n * Load multiple files.\n * @param {string[]} paths - The file paths\n * @param {Function} callbackFn - The callback function\n */\n function loadFiles(paths, callbackFn, args) {\n // listify paths\n paths = paths.push ? paths : [paths];\n var numWaiting = paths.length,\n x = numWaiting,\n pathsNotFound = [],\n fn,\n i;\n\n // define callback function\n fn = function (path, result, defaultPrevented) {\n // handle error\n if (result == 'e') pathsNotFound.push(path);\n\n // handle beforeload event. If defaultPrevented then that means the load\n // will be blocked (ex. Ghostery/ABP on Safari)\n if (result == 'b') {\n if (defaultPrevented) pathsNotFound.push(path);else return;\n }\n numWaiting--;\n if (!numWaiting) callbackFn(pathsNotFound);\n };\n\n // load scripts\n for (i = 0; i < x; i++) loadFile(paths[i], fn, args);\n }\n\n /**\n * Initiate script load and register bundle.\n * @param {(string|string[])} paths - The file paths\n * @param {(string|Function|Object)} [arg1] - The (1) bundleId or (2) success\n * callback or (3) object literal with success/error arguments, numRetries,\n * etc.\n * @param {(Function|Object)} [arg2] - The (1) success callback or (2) object\n * literal with success/error arguments, numRetries, etc.\n */\n function loadjs(paths, arg1, arg2) {\n var bundleId, args;\n\n // bundleId (if string)\n if (arg1 && arg1.trim) bundleId = arg1;\n\n // args (default is {})\n args = (bundleId ? arg2 : arg1) || {};\n\n // throw error if bundle is already defined\n if (bundleId) {\n if (bundleId in bundleIdCache) {\n throw \"LoadJS\";\n } else {\n bundleIdCache[bundleId] = true;\n }\n }\n function loadFn(resolve, reject) {\n loadFiles(paths, function (pathsNotFound) {\n // execute callbacks\n executeCallbacks(args, pathsNotFound);\n\n // resolve Promise\n if (resolve) {\n executeCallbacks({\n success: resolve,\n error: reject\n }, pathsNotFound);\n }\n\n // publish bundle load event\n publish(bundleId, pathsNotFound);\n }, args);\n }\n if (args.returnPromise) return new Promise(loadFn);else loadFn();\n }\n\n /**\n * Execute callbacks when dependencies have been satisfied.\n * @param {(string|string[])} deps - List of bundle ids\n * @param {Object} args - success/error arguments\n */\n loadjs.ready = function ready(deps, args) {\n // subscribe to bundle load event\n subscribe(deps, function (depsNotFound) {\n // execute callbacks\n executeCallbacks(args, depsNotFound);\n });\n return loadjs;\n };\n\n /**\n * Manually satisfy bundle dependencies.\n * @param {string} bundleId - The bundle id\n */\n loadjs.done = function done(bundleId) {\n publish(bundleId, []);\n };\n\n /**\n * Reset loadjs dependencies statuses\n */\n loadjs.reset = function reset() {\n bundleIdCache = {};\n bundleResultCache = {};\n bundleCallbackQueue = {};\n };\n\n /**\n * Determine if bundle has already been defined\n * @param String} bundleId - The bundle id\n */\n loadjs.isDefined = function isDefined(bundleId) {\n return bundleId in bundleIdCache;\n };\n\n // export\n return loadjs;\n });\n });\n\n // ==========================================================================\n function loadScript(url) {\n return new Promise((resolve, reject) => {\n loadjs_umd(url, {\n success: resolve,\n error: reject\n });\n });\n }\n\n // ==========================================================================\n\n // Parse Vimeo ID from URL\n function parseId$1(url) {\n if (is.empty(url)) {\n return null;\n }\n if (is.number(Number(url))) {\n return url;\n }\n const regex = /^.*(vimeo.com\\/|video\\/)(\\d+).*/;\n return url.match(regex) ? RegExp.$2 : url;\n }\n\n // Try to extract a hash for private videos from the URL\n function parseHash(url) {\n /* This regex matches a hexadecimal hash if given in any of these forms:\n * - [https://player.]vimeo.com/video/{id}/{hash}[?params]\n * - [https://player.]vimeo.com/video/{id}?h={hash}[¶ms]\n * - [https://player.]vimeo.com/video/{id}?[params]&h={hash}\n * - video/{id}/{hash}\n * If matched, the hash is available in capture group 4\n */\n const regex = /^.*(vimeo.com\\/|video\\/)(\\d+)(\\?.*&*h=|\\/)+([\\d,a-f]+)/;\n const found = url.match(regex);\n return found && found.length === 5 ? found[4] : null;\n }\n\n // Set playback state and trigger change (only on actual change)\n function assurePlaybackState$1(play) {\n if (play && !this.embed.hasPlayed) {\n this.embed.hasPlayed = true;\n }\n if (this.media.paused === play) {\n this.media.paused = !play;\n triggerEvent.call(this, this.media, play ? 'play' : 'pause');\n }\n }\n const vimeo = {\n setup() {\n const player = this;\n\n // Add embed class for responsive\n toggleClass(player.elements.wrapper, player.config.classNames.embed, true);\n\n // Set speed options from config\n player.options.speed = player.config.speed.options;\n\n // Set intial ratio\n setAspectRatio.call(player);\n\n // Load the SDK if not already\n if (!is.object(window.Vimeo)) {\n loadScript(player.config.urls.vimeo.sdk).then(() => {\n vimeo.ready.call(player);\n }).catch(error => {\n player.debug.warn('Vimeo SDK (player.js) failed to load', error);\n });\n } else {\n vimeo.ready.call(player);\n }\n },\n // API Ready\n ready() {\n const player = this;\n const config = player.config.vimeo;\n const {\n premium,\n referrerPolicy,\n ...frameParams\n } = config;\n // Get the source URL or ID\n let source = player.media.getAttribute('src');\n let hash = '';\n // Get from
if needed\n if (is.empty(source)) {\n source = player.media.getAttribute(player.config.attributes.embed.id);\n // hash can also be set as attribute on the
\n hash = player.media.getAttribute(player.config.attributes.embed.hash);\n } else {\n hash = parseHash(source);\n }\n const hashParam = hash ? {\n h: hash\n } : {};\n\n // If the owner has a pro or premium account then we can hide controls etc\n if (premium) {\n Object.assign(frameParams, {\n controls: false,\n sidedock: false\n });\n }\n\n // Get Vimeo params for the iframe\n const params = buildUrlParams({\n loop: player.config.loop.active,\n autoplay: player.autoplay,\n muted: player.muted,\n gesture: 'media',\n playsinline: player.config.playsinline,\n // hash has to be added to iframe-URL\n ...hashParam,\n ...frameParams\n });\n const id = parseId$1(source);\n // Build an iframe\n const iframe = createElement('iframe');\n const src = format(player.config.urls.vimeo.iframe, id, params);\n iframe.setAttribute('src', src);\n iframe.setAttribute('allowfullscreen', '');\n iframe.setAttribute('allow', ['autoplay', 'fullscreen', 'picture-in-picture', 'encrypted-media', 'accelerometer', 'gyroscope'].join('; '));\n\n // Set the referrer policy if required\n if (!is.empty(referrerPolicy)) {\n iframe.setAttribute('referrerPolicy', referrerPolicy);\n }\n\n // Inject the package\n if (premium || !config.customControls) {\n iframe.setAttribute('data-poster', player.poster);\n player.media = replaceElement(iframe, player.media);\n } else {\n const wrapper = createElement('div', {\n class: player.config.classNames.embedContainer,\n 'data-poster': player.poster\n });\n wrapper.appendChild(iframe);\n player.media = replaceElement(wrapper, player.media);\n }\n\n // Get poster image\n if (!config.customControls) {\n fetch(format(player.config.urls.vimeo.api, src)).then(response => {\n if (is.empty(response) || !response.thumbnail_url) {\n return;\n }\n\n // Set and show poster\n ui.setPoster.call(player, response.thumbnail_url).catch(() => {});\n });\n }\n\n // Setup instance\n // https://github.com/vimeo/player.js\n player.embed = new window.Vimeo.Player(iframe, {\n autopause: player.config.autopause,\n muted: player.muted\n });\n player.media.paused = true;\n player.media.currentTime = 0;\n\n // Disable native text track rendering\n if (player.supported.ui) {\n player.embed.disableTextTrack();\n }\n\n // Create a faux HTML5 API using the Vimeo API\n player.media.play = () => {\n assurePlaybackState$1.call(player, true);\n return player.embed.play();\n };\n player.media.pause = () => {\n assurePlaybackState$1.call(player, false);\n return player.embed.pause();\n };\n player.media.stop = () => {\n player.pause();\n player.currentTime = 0;\n };\n\n // Seeking\n let {\n currentTime\n } = player.media;\n Object.defineProperty(player.media, 'currentTime', {\n get() {\n return currentTime;\n },\n set(time) {\n // Vimeo will automatically play on seek if the video hasn't been played before\n\n // Get current paused state and volume etc\n const {\n embed,\n media,\n paused,\n volume\n } = player;\n const restorePause = paused && !embed.hasPlayed;\n\n // Set seeking state and trigger event\n media.seeking = true;\n triggerEvent.call(player, media, 'seeking');\n\n // If paused, mute until seek is complete\n Promise.resolve(restorePause && embed.setVolume(0))\n // Seek\n .then(() => embed.setCurrentTime(time))\n // Restore paused\n .then(() => restorePause && embed.pause())\n // Restore volume\n .then(() => restorePause && embed.setVolume(volume)).catch(() => {\n // Do nothing\n });\n }\n });\n\n // Playback speed\n let speed = player.config.speed.selected;\n Object.defineProperty(player.media, 'playbackRate', {\n get() {\n return speed;\n },\n set(input) {\n player.embed.setPlaybackRate(input).then(() => {\n speed = input;\n triggerEvent.call(player, player.media, 'ratechange');\n }).catch(() => {\n // Cannot set Playback Rate, Video is probably not on Pro account\n player.options.speed = [1];\n });\n }\n });\n\n // Volume\n let {\n volume\n } = player.config;\n Object.defineProperty(player.media, 'volume', {\n get() {\n return volume;\n },\n set(input) {\n player.embed.setVolume(input).then(() => {\n volume = input;\n triggerEvent.call(player, player.media, 'volumechange');\n });\n }\n });\n\n // Muted\n let {\n muted\n } = player.config;\n Object.defineProperty(player.media, 'muted', {\n get() {\n return muted;\n },\n set(input) {\n const toggle = is.boolean(input) ? input : false;\n player.embed.setMuted(toggle ? true : player.config.muted).then(() => {\n muted = toggle;\n triggerEvent.call(player, player.media, 'volumechange');\n });\n }\n });\n\n // Loop\n let {\n loop\n } = player.config;\n Object.defineProperty(player.media, 'loop', {\n get() {\n return loop;\n },\n set(input) {\n const toggle = is.boolean(input) ? input : player.config.loop.active;\n player.embed.setLoop(toggle).then(() => {\n loop = toggle;\n });\n }\n });\n\n // Source\n let currentSrc;\n player.embed.getVideoUrl().then(value => {\n currentSrc = value;\n controls.setDownloadUrl.call(player);\n }).catch(error => {\n this.debug.warn(error);\n });\n Object.defineProperty(player.media, 'currentSrc', {\n get() {\n return currentSrc;\n }\n });\n\n // Ended\n Object.defineProperty(player.media, 'ended', {\n get() {\n return player.currentTime === player.duration;\n }\n });\n\n // Set aspect ratio based on video size\n Promise.all([player.embed.getVideoWidth(), player.embed.getVideoHeight()]).then(dimensions => {\n const [width, height] = dimensions;\n player.embed.ratio = roundAspectRatio(width, height);\n setAspectRatio.call(this);\n });\n\n // Set autopause\n player.embed.setAutopause(player.config.autopause).then(state => {\n player.config.autopause = state;\n });\n\n // Get title\n player.embed.getVideoTitle().then(title => {\n player.config.title = title;\n ui.setTitle.call(this);\n });\n\n // Get current time\n player.embed.getCurrentTime().then(value => {\n currentTime = value;\n triggerEvent.call(player, player.media, 'timeupdate');\n });\n\n // Get duration\n player.embed.getDuration().then(value => {\n player.media.duration = value;\n triggerEvent.call(player, player.media, 'durationchange');\n });\n\n // Get captions\n player.embed.getTextTracks().then(tracks => {\n player.media.textTracks = tracks;\n captions.setup.call(player);\n });\n player.embed.on('cuechange', ({\n cues = []\n }) => {\n const strippedCues = cues.map(cue => stripHTML(cue.text));\n captions.updateCues.call(player, strippedCues);\n });\n player.embed.on('loaded', () => {\n // Assure state and events are updated on autoplay\n player.embed.getPaused().then(paused => {\n assurePlaybackState$1.call(player, !paused);\n if (!paused) {\n triggerEvent.call(player, player.media, 'playing');\n }\n });\n if (is.element(player.embed.element) && player.supported.ui) {\n const frame = player.embed.element;\n\n // Fix keyboard focus issues\n // https://github.com/sampotts/plyr/issues/317\n frame.setAttribute('tabindex', -1);\n }\n });\n player.embed.on('bufferstart', () => {\n triggerEvent.call(player, player.media, 'waiting');\n });\n player.embed.on('bufferend', () => {\n triggerEvent.call(player, player.media, 'playing');\n });\n player.embed.on('play', () => {\n assurePlaybackState$1.call(player, true);\n triggerEvent.call(player, player.media, 'playing');\n });\n player.embed.on('pause', () => {\n assurePlaybackState$1.call(player, false);\n });\n player.embed.on('timeupdate', data => {\n player.media.seeking = false;\n currentTime = data.seconds;\n triggerEvent.call(player, player.media, 'timeupdate');\n });\n player.embed.on('progress', data => {\n player.media.buffered = data.percent;\n triggerEvent.call(player, player.media, 'progress');\n\n // Check all loaded\n if (parseInt(data.percent, 10) === 1) {\n triggerEvent.call(player, player.media, 'canplaythrough');\n }\n\n // Get duration as if we do it before load, it gives an incorrect value\n // https://github.com/sampotts/plyr/issues/891\n player.embed.getDuration().then(value => {\n if (value !== player.media.duration) {\n player.media.duration = value;\n triggerEvent.call(player, player.media, 'durationchange');\n }\n });\n });\n player.embed.on('seeked', () => {\n player.media.seeking = false;\n triggerEvent.call(player, player.media, 'seeked');\n });\n player.embed.on('ended', () => {\n player.media.paused = true;\n triggerEvent.call(player, player.media, 'ended');\n });\n player.embed.on('error', detail => {\n player.media.error = detail;\n triggerEvent.call(player, player.media, 'error');\n });\n\n // Rebuild UI\n if (config.customControls) {\n setTimeout(() => ui.build.call(player), 0);\n }\n }\n };\n\n // ==========================================================================\n\n // Parse YouTube ID from URL\n function parseId(url) {\n if (is.empty(url)) {\n return null;\n }\n const regex = /^.*(youtu.be\\/|v\\/|u\\/\\w\\/|embed\\/|watch\\?v=|&v=)([^#&?]*).*/;\n return url.match(regex) ? RegExp.$2 : url;\n }\n\n // Set playback state and trigger change (only on actual change)\n function assurePlaybackState(play) {\n if (play && !this.embed.hasPlayed) {\n this.embed.hasPlayed = true;\n }\n if (this.media.paused === play) {\n this.media.paused = !play;\n triggerEvent.call(this, this.media, play ? 'play' : 'pause');\n }\n }\n function getHost(config) {\n if (config.noCookie) {\n return 'https://www.youtube-nocookie.com';\n }\n if (window.location.protocol === 'http:') {\n return 'http://www.youtube.com';\n }\n\n // Use YouTube's default\n return undefined;\n }\n const youtube = {\n setup() {\n // Add embed class for responsive\n toggleClass(this.elements.wrapper, this.config.classNames.embed, true);\n\n // Setup API\n if (is.object(window.YT) && is.function(window.YT.Player)) {\n youtube.ready.call(this);\n } else {\n // Reference current global callback\n const callback = window.onYouTubeIframeAPIReady;\n\n // Set callback to process queue\n window.onYouTubeIframeAPIReady = () => {\n // Call global callback if set\n if (is.function(callback)) {\n callback();\n }\n youtube.ready.call(this);\n };\n\n // Load the SDK\n loadScript(this.config.urls.youtube.sdk).catch(error => {\n this.debug.warn('YouTube API failed to load', error);\n });\n }\n },\n // Get the media title\n getTitle(videoId) {\n const url = format(this.config.urls.youtube.api, videoId);\n fetch(url).then(data => {\n if (is.object(data)) {\n const {\n title,\n height,\n width\n } = data;\n\n // Set title\n this.config.title = title;\n ui.setTitle.call(this);\n\n // Set aspect ratio\n this.embed.ratio = roundAspectRatio(width, height);\n }\n setAspectRatio.call(this);\n }).catch(() => {\n // Set aspect ratio\n setAspectRatio.call(this);\n });\n },\n // API ready\n ready() {\n const player = this;\n const config = player.config.youtube;\n // Ignore already setup (race condition)\n const currentId = player.media && player.media.getAttribute('id');\n if (!is.empty(currentId) && currentId.startsWith('youtube-')) {\n return;\n }\n\n // Get the source URL or ID\n let source = player.media.getAttribute('src');\n\n // Get from
if needed\n if (is.empty(source)) {\n source = player.media.getAttribute(this.config.attributes.embed.id);\n }\n\n // Replace the )} {contentState.hasOpenedBefore && ( )} {contentState.zoomEnabled && } {contentState.showExtension || contentState.recording ? (
{!contentState.recording && !contentState.drawingMode && !contentState.blurMode && (
{ const onboardingActive = document.documentElement.classList.contains( "screenity-driver-active" ) || Boolean(document.querySelector(".driver-overlay")); if (onboardingActive) return; if ( window.location.href.indexOf( chrome.runtime.getURL("setup.html") ) === -1 && window.location.href.indexOf( chrome.runtime.getURL("playground.html") ) === -1 && !contentState.pendingRecording && !contentState.customRegion ) { setContentState((prevContentState) => ({ ...prevContentState, showExtension: false, showPopup: false, })); } }} >
)}
{contentState.recordingType === "region" && contentState.customRegion && } {shadowRef.current && } {contentState.preparingRecording && ( )} {contentState.recordingType != "camera" && !contentState.onboarding && !( contentState.isSubscribed === false && contentState.isLoggedIn === true ) && !(!contentState.isLoggedIn && contentState.wasLoggedIn) && ( )} {contentState.recordingType === "camera" && ( )} {!(contentState.hideToolbar && contentState.hideUI) && !contentState.onboarding && !( contentState.isSubscribed === false && contentState.isLoggedIn === true ) && !(!contentState.isLoggedIn && contentState.wasLoggedIn) && ( )} {contentState.showPopup && ( )}
) : (
)}
); }; export default Wrapper; ================================================ FILE: src/pages/Content/camera/Camera.jsx ================================================ import React, { useContext, useEffect } from "react"; import CameraWrap from "./layout/CameraWrap"; import { contentStateContext } from "../context/ContentState"; const Camera = (props) => { const [contentState, setContentState] = useContext(contentStateContext); return (
{contentState.defaultVideoInput != "none" && contentState.cameraActive && }
); }; export default Camera; ================================================ FILE: src/pages/Content/camera/components/ResizeHandle.jsx ================================================ import React from "react"; import { CameraResizeIcon } from "../../toolbar/components/SVG"; const ResizeHandle = ({ position }) => { return (
); }; export default ResizeHandle; ================================================ FILE: src/pages/Content/camera/layout/CameraToolbar.jsx ================================================ import React, { useState, useEffect, useContext } from "react"; import * as Toolbar from "@radix-ui/react-toolbar"; import TooltipWrap from "../../toolbar/components/TooltipWrap"; import { CameraCloseIcon, Pip } from "../../toolbar/components/SVG"; import { contentStateContext } from "../../context/ContentState"; const CameraToolbar = () => { const [contentState, setContentState] = useContext(contentStateContext); return ( { setContentState((prevContentState) => ({ ...prevContentState, cameraActive: false, })); chrome.storage.local.set({ cameraActive: false }); }} > {contentState.recording && contentState.surface === "monitor" && ( { chrome.runtime.sendMessage({ type: "toggle-pip" }); }} > )} ); }; export default CameraToolbar; ================================================ FILE: src/pages/Content/camera/layout/CameraWrap.jsx ================================================ import React, { useEffect, useContext, useRef, useState, useLayoutEffect, } from "react"; import { Rnd } from "react-rnd"; import { contentStateContext } from "../../context/ContentState"; import CameraToolbar from "./CameraToolbar"; import ResizeHandle from "../components/ResizeHandle"; const CameraWrap = (props) => { const [contentState, setContentState] = React.useContext(contentStateContext); const cameraRef = React.useRef(); const [cx, setCx] = useState(200); const [cy, setCy] = useState(200); const [w, setW] = useState(200); const [h, setH] = useState(200); const updateUIPosition = () => { const ref = props.shadowRef.current.shadowRoot.querySelector(".camera-draggable"); const circleCenterX = ref.getBoundingClientRect().left + ref.getBoundingClientRect().width / 2; const circleCenterY = ref.getBoundingClientRect().top + ref.getBoundingClientRect().height / 2; const circleRadius = ref.getBoundingClientRect().width / 2; const squareBottomRightX = ref.getBoundingClientRect().left + ref.getBoundingClientRect().width; const squareBottomRightY = ref.getBoundingClientRect().top + ref.getBoundingClientRect().height; const handle = props.shadowRef.current.shadowRoot.querySelector(".camera-resize"); const toolbar = props.shadowRef.current.shadowRoot.querySelector(".camera-toolbar"); const c = Math.sqrt( Math.pow(circleCenterX - squareBottomRightX, 2) + Math.pow(circleCenterY - squareBottomRightY, 2) ); const a = circleRadius / Math.sqrt(2); const r = (c + Math.sqrt(c ** 2 + 16 * a ** 2)) / 4; const x = r - r / Math.sqrt(2); const y = r - r / Math.sqrt(2); handle.style.bottom = `${y - handle.getBoundingClientRect().width / 2}px`; handle.style.right = `${x - handle.getBoundingClientRect().height / 2}px`; toolbar.style.top = `${y - toolbar.getBoundingClientRect().width / 2}px`; toolbar.style.left = `${x - toolbar.getBoundingClientRect().height / 2}px`; }; const saveDimensions = () => { const ref = props.shadowRef.current.shadowRoot.querySelector(".camera-draggable"); setContentState((prevContentState) => ({ ...prevContentState, cameraDimensions: { size: ref.getBoundingClientRect().width, x: ref.getBoundingClientRect().x, y: ref.getBoundingClientRect().y, }, })); chrome.storage.local.set({ cameraDimensions: { size: ref.getBoundingClientRect().width, x: ref.getBoundingClientRect().x, y: ref.getBoundingClientRect().y, }, }); }; useEffect(() => { if (!cameraRef.current) return; if (!props.shadowRef.current.shadowRoot.querySelector(".camera-resize")) return; if (!props.shadowRef.current.shadowRoot.querySelector(".camera-toolbar")) return; updateUIPosition(); }, [cameraRef.current]); // I need to make sure the camera is never offscreen (if the user resizes the window) useLayoutEffect(() => { const updateCameraPosition = () => { if ( !props.shadowRef.current.shadowRoot.querySelector(".camera-draggable") ) return; const ref = props.shadowRef.current.shadowRoot.querySelector(".camera-draggable"); let xpos = cameraRef.current.getDraggablePosition().x; let ypos = cameraRef.current.getDraggablePosition().y; // Width and height of camera const width = ref.getBoundingClientRect().width; const height = ref.getBoundingClientRect().height; const { innerWidth, innerHeight } = window; // Keep camera positioned relative to the bottom and right of the screen, proportionally if (xpos + width > innerWidth) { xpos = innerWidth - width; } if (ypos + height > innerHeight) { ypos = innerHeight - height; } cameraRef.current.updatePosition({ x: xpos, y: ypos }); saveDimensions(); }; updateCameraPosition(); window.addEventListener("resize", updateCameraPosition); return () => { window.removeEventListener("resize", updateCameraPosition); }; }, []); return (
, }} minHeight={150} minWidth={150} enableResizing={{ bottom: false, bottomRight: true, bottomLeft: false, left: false, right: false, top: false, topRight: false, topLeft: false, }} onResize={(e, direction, ref, delta, position) => { updateUIPosition(); }} onResizeStop={(e, direction, ref, delta, position) => { saveDimensions(); }} onDragStop={(node, x, y) => { saveDimensions(); }} lockAspectRatio={1} bounds={"window"} >
); }; export default CameraWrap; ================================================ FILE: src/pages/Content/camera/styles/_Camera.scss ================================================ @use '../../styles/_variables' as *; @use './layout/CameraWrap'; @use './layout/CameraToolbar'; @use './components/ResizeHandle'; ================================================ FILE: src/pages/Content/camera/styles/components/_ResizeHandle.scss ================================================ @use '../../../styles/_variables' as *; .camera-resize { position: absolute; bottom: 20px; right: 20px; z-index: $z-index-max; height: 28px; width: 28px; border-radius: 50%; background-color: rgba(30, 30, 30, .8); box-shadow: 0 2px 10px rgba(0, 0, 0, .15); border: 3px solid rgba(255, 255, 255, .2); backdrop-filter: blur(10px); align-items: center; box-sizing: border-box; display: flex; justify-content: center; align-items: center; opacity: 0; svg { color: #9797A4; text-align: center; margin: auto; display: block; } } ================================================ FILE: src/pages/Content/camera/styles/layout/_CameraToolbar.scss ================================================ @use '../../../styles/_variables' as *; .camera-toolbar { display: flex; align-items: center; padding-left: 4px; padding-right: 4px; transition: opacity .25s cubic-bezier(.61,.11,.08,.96); min-width: max-content; background-color: rgba(30, 30, 30, .8); box-shadow: 0 2px 10px rgba(0, 0, 0, .15); height: 28px; position: absolute; left: 10px; top: 10px; border-radius: $container-border-radius; backdrop-filter: blur(10px); z-index: $z-index-max; opacity: 0; border: 3px solid rgba(255, 255, 255, .2); } .camera-draggable:hover { .camera-toolbar, .camera-resize { opacity: 1!important; } } .camera-toolbar:hover, .camera-resize:hover { opacity: 1!important; } .CameraToolbarSeparator { width: 1px; height: 18px; background-color: rgba(255, 255, 255, .3); margin: 0 4px; } .CameraToggleItem, .CameraToolbarButton { display: flex; justify-content: center; align-items: center; color: #000; height: 22px; width: 22px; text-align: center; font-size: 13px; line-height: 1; border-radius: 50%; transition: background-color .25s ease-in-out; background-color: rgba(124, 139, 165, 0); svg { color: $color-icon; } &:hover { background-color: rgba(124, 139, 165, 0.2)!important; cursor: pointer; } &:disabled { opacity: 0.5; pointer-events: none; } &[data-state='on'] { color: #FFF; svg { color: #FFF; } } } .CameraToggleItem:hover, .CameraToggleButton:hover { cursor: pointer; } .CameraToggleItem:focus-visible, .CameraToggleButton:focus-visible { position: relative; box-shadow: $focus-border; } .CameraToggleGroup { display: flex; align-items: center; justify-content: center; } .CameraToggleGroup, .CameraToolbarSeparator { display: none; } ================================================ FILE: src/pages/Content/camera/styles/layout/_CameraWrap.scss ================================================ @use '../../../styles/_variables' as *; .camera-draggable { width: 100%; height: 100%; transform-origin: left top; border-radius: 50%; } .camera-grab { width: 100%; height: 100%; position: absolute; border-radius: 50%; z-index: 99999999!important; cursor: grab; } .camera-flipped { transform: scaleX(-1); } ================================================ FILE: src/pages/Content/camera-only/CameraOnly.jsx ================================================ import React, { useContext } from "react"; import CameraWrap from "./layout/CameraWrap"; import { contentStateContext } from "../context/ContentState"; const CameraOnly = (props) => { const [contentState, setContentState] = useContext(contentStateContext); return (
{contentState.defaultVideoInput != "none" && contentState.cameraActive && contentState.recordingType === "camera" && ( )}
); }; export default CameraOnly; ================================================ FILE: src/pages/Content/camera-only/layout/CameraWrap.jsx ================================================ import React, { useEffect, useContext, useRef, useState } from "react"; import { contentStateContext } from "../../context/ContentState"; const CameraWrap = (props) => { const [contentState, setContentState] = React.useContext(contentStateContext); return (
); }; export default CameraWrap; ================================================ FILE: src/pages/Content/camera-only/styles/_CameraOnly.scss ================================================ @use '../../styles/_variables' as *; ================================================ FILE: src/pages/Content/canvas/Canvas.jsx ================================================ import React, { useContext } from "react"; import CanvasWrap from "./layout/CanvasWrap"; const Canvas = () => { return (
); }; export default Canvas; ================================================ FILE: src/pages/Content/canvas/layout/CanvasWrap.jsx ================================================ import React, { useEffect, useRef, useContext } from "react"; import { fabric } from "fabric"; import { contentStateContext } from "../../context/ContentState"; import CustomControls from "../modules/CustomControls"; import ArrowTool from "../modules/ArrowTool"; import EraserTool from "../modules/EraserTool"; import ShapeTool from "../modules/ShapeTool"; import TextTool from "../modules/TextTool"; import PenTool from "../modules/PenTool"; import SelectTool from "../modules/SelectTool"; import { undoCanvas, redoCanvas, saveCanvas, checkChanges, } from "../modules/History"; const CanvasWrap = (props) => { const [contentState, setContentState] = useContext(contentStateContext); const contentStateRef = useRef(null); const canvasContainer = useRef(); const canvasRef = useRef(); const fabricRef = useRef(); useEffect(() => { contentStateRef.current = contentState; }, [contentState]); // INIT useEffect(() => { if (!canvasRef.current) return; if (fabricRef.current) return; const canvas = new fabric.Canvas("canvas-screenity", { perPixelTargetFind: true, }); fabricRef.current = canvas; canvas.getContext("2d", { willReadFrequently: true }); // Set width and height of canvas to full size of document canvas.setWidth(window.document.body.offsetWidth); // set max height of 2000px //canvas.setHeight(Math.min(window.document.body.offsetHeight, 2500)); // set height to viewport canvas.setHeight(window.innerHeight); canvas.renderAll(); setContentState((prevContentState) => ({ ...prevContentState, canvas: canvas, })); CustomControls(canvas); saveCanvas( { ...contentState, canvas: canvas, }, setContentState, ); }, []); useEffect(() => { if (!fabricRef.current) return; const resizeCanvas = () => { fabricRef.current.setWidth(window.document.body.offsetWidth); fabricRef.current.setHeight(window.innerHeight); fabricRef.current.renderAll(); }; window.addEventListener("resize", resizeCanvas); return () => { window.removeEventListener("resize", resizeCanvas); }; }, []); useEffect(() => { const canvas = fabricRef.current; if (!canvas) return; const tool = contentState.tool; const shouldDraw = contentState.drawingMode && (tool === "pen" || tool === "highlighter"); canvas.isDrawingMode = shouldDraw; // Important: when leaving pen/highlighter, clear any in-progress brush stroke if (!shouldDraw && canvas.freeDrawingBrush) { const brush = canvas.freeDrawingBrush; if (Array.isArray(brush._points)) brush._points.length = 0; if (Array.isArray(brush.points)) brush.points.length = 0; if (typeof brush._reset === "function") brush._reset(); } canvas.requestRenderAll(); }, [contentState.tool, contentState.drawingMode]); useEffect(() => { const canvas = fabricRef.current; if (!canvas) return; // Only discard selection when leaving select (or when entering a drawing tool) if (contentState.tool !== "select") { canvas.discardActiveObject(); canvas.requestRenderAll(); } }, [contentState.tool]); const panXRef = useRef(window.scrollX); const panYRef = useRef(window.scrollY); useEffect(() => { if (!fabricRef.current) return; panXRef.current = window.scrollX; panYRef.current = window.scrollY; // Function to update canvas panning const updateCanvasPan = () => { fabricRef.current.setZoom(fabricRef.current.getZoom()); fabricRef.current.absolutePan({ x: panXRef.current, y: panYRef.current, }); }; // Event listener for window scroll const handleScroll = () => { // Update canvas pan position based on scroll position panXRef.current = window.scrollX; panYRef.current = window.scrollY; updateCanvasPan(); }; updateCanvasPan(); // Attach scroll event listener window.addEventListener("scroll", handleScroll, { passive: true }); // Cleanup function return () => { // Remove scroll event listener window.removeEventListener("scroll", handleScroll); // Reset canvas pan position panXRef.current = 0; panYRef.current = 0; }; }, []); useEffect(() => { if (!fabricRef.current) return; const canvas = fabricRef.current; // Pass a ref getter instead of snapshot state const arrowDrawing = ArrowTool( canvas, contentStateRef, setContentState, saveCanvas, ); const eraserDrawing = EraserTool(canvas, contentStateRef, setContentState); const shapeDrawing = ShapeTool( canvas, contentStateRef, setContentState, saveCanvas, ); const textDrawing = TextTool( canvas, contentStateRef, setContentState, saveCanvas, ); const penDrawing = PenTool( canvas, contentStateRef, setContentState, saveCanvas, ); return () => { arrowDrawing.removeEventListeners(); eraserDrawing.removeEventListeners(); shapeDrawing.removeEventListeners(); textDrawing.removeEventListeners(); penDrawing.removeEventListeners(); }; }, []); useEffect(() => { if (!fabricRef.current) return; const canvas = fabricRef.current; const selection = SelectTool(canvas, contentStateRef, setContentState); const objectChanges = checkChanges( canvas, contentStateRef, setContentState, ); return () => { selection.removeEventListeners(); objectChanges.removeEventListeners(); }; }, []); useEffect(() => { // Prevent selecting elements unless in select mode if (!fabricRef.current) return; if (contentState.tool !== "select") { // De-select all objects on canvas fabricRef.current.discardActiveObject(); fabricRef.current.selection = false; fabricRef.current.forEachObject((obj) => { obj.selectable = false; }); fabricRef.current.renderAll(); } else { fabricRef.current.selection = true; fabricRef.current.forEachObject((obj) => { obj.selectable = true; }); fabricRef.current.renderAll(); } }, [contentState.tool]); useEffect(() => { const onKeyDown = (event) => { const state = contentStateRef.current; if (!state) return; if ( (event.ctrlKey || event.metaKey) && event.key.toLowerCase() === "z" && !event.shiftKey ) { undoCanvas(state, setContentState); } if ( (event.ctrlKey || event.metaKey) && (event.key.toLowerCase() === "y" || (event.shiftKey && event.key.toLowerCase() === "z")) ) { redoCanvas(state, setContentState); } }; document.addEventListener("keydown", onKeyDown); return () => document.removeEventListener("keydown", onKeyDown); }, []); useEffect(() => { if (!fabricRef.current) return; // De-select all objects on canvas fabricRef.current.discardActiveObject(); fabricRef.current.requestRenderAll(); }, [contentState.drawingMode]); return (
); }; export default CanvasWrap; ================================================ FILE: src/pages/Content/canvas/layout/TextToolbar.jsx ================================================ import React, { useRef, useContext, useEffect, useState } from "react"; import * as Toolbar from "@radix-ui/react-toolbar"; import * as Select from "@radix-ui/react-select"; // Context import { contentStateContext } from "../../context/ContentState"; const TextToolbar = (props) => { const toolbarRef = useRef(null); const [contentState, setContentState] = useContext(contentStateContext); const [left, setLeft] = useState(0); const [top, setTop] = useState(0); useEffect(() => { if (!contentState.canvas) return; const canvas = contentState.canvas; const toolbar = toolbarRef.current; function positionToolbarOnTextbox(textbox) { // place the toolbar on top of the textbox, centered, with a vertical offset const textCoords = textbox.getBoundingRect(); const textOffset = textCoords.left; const textWidth = textCoords.width; const textHeight = textCoords.height; const textTop = textCoords.top; const textLeft = textCoords.left; const textCenter = textLeft + textWidth / 2; const textCenterOffset = textCenter; const toolbarWidth = toolbar.clientWidth; const toolbarHeight = toolbar.clientHeight; const toolbarLeft = textCenterOffset - toolbarWidth / 2; const toolbarTop = textTop - toolbarHeight - 10; setLeft(toolbarLeft); setTop(toolbarTop); } function hideToolbar() { toolbar.style.display = "none"; } canvas.on("selection:created", function (e) { const selectedObject = canvas.getActiveObject(); if (selectedObject === null || selectedObject === undefined) return; if (selectedObject.type === "textbox") { positionToolbarOnTextbox(selectedObject); } }); canvas.on("selection:cleared", function () { hideToolbar(); }); canvas.on("object:moving", function (e) { const movedObject = e.target; if ( movedObject.type === "textbox" && canvas.getActiveObject() === movedObject ) { positionToolbarOnTextbox(movedObject); } }); canvas.on("object:scaling", function (e) { const resizedObject = e.target; if ( resizedObject.type === "textbox" && canvas.getActiveObject() === resizedObject ) { positionToolbarOnTextbox(resizedObject); } }); }, [contentState.canvas]); return (
oioi
); }; export default TextToolbar; ================================================ FILE: src/pages/Content/canvas/modules/ArrowTool.jsx ================================================ import { fabric } from "fabric"; const createArrowLine = (x, y, color, strokeWidth) => { return new fabric.Line([x, y, x, y], { strokeWidth: (strokeWidth || 2) * 6, stroke: color, originX: "center", originY: "center", selectable: false, evented: false, id: "arrowLine", objectCaching: false, }); }; const createArrowHead = (x, y, color, strokeWidth) => { const size = (strokeWidth || 2) * 16; return new fabric.Triangle({ width: size, height: size, left: x, top: y, fill: color, originX: "center", originY: "center", selectable: false, evented: false, id: "arrowHead", objectCaching: false, }); }; const createArrowCircle = (x, y, id) => { return new fabric.Circle({ radius: 5, fill: "white", stroke: "#0D99FF", strokeWidth: 2, left: x, top: y, selectable: false, evented: true, id, opacity: 0, objectCaching: false, }); }; const createArrowLineControl = (x, y) => { return new fabric.Line([x, y, x, y], { strokeWidth: 2, stroke: "#0D99FF", originX: "center", originY: "center", selectable: false, evented: false, id: "arrowLineControl", opacity: 0, objectCaching: false, }); }; const ArrowTool = (canvas, contentStateRef, setContentState, saveCanvas) => { const getState = () => contentStateRef.current; let arrowPoints = []; let arrowLine = null; let arrowHead = null; let arrowCircle1 = null; let arrowCircle2 = null; let arrowLineControl = null; // --- endpoint drag handler (store ref so we can remove safely) const onEndpointMouseDown = (e) => { // Only when selecting / interacting with existing arrows if (!e?.subTargets?.length) return; const state = getState(); // Allow endpoint dragging even if tool != arrow, but only in drawingMode if (!state?.drawingMode) return; const hit = e.subTargets.find( (obj) => obj?.id === "arrowCircle1" || obj?.id === "arrowCircle2", ); if (!hit) return; moveArrowCircle(canvas, hit, getState, saveCanvas, setContentState); }; const moveArrowCircle = ( canvas, arrowCircle, getState, saveCanvas, setContentState, ) => { let isDown = true; const group = arrowCircle.group; const items = group._objects; const arrowCircle1 = group._objects.find( (item) => item.id === "arrowCircle1", ); const arrowCircle2 = group._objects.find( (item) => item.id === "arrowCircle2", ); const arrowLine = group._objects.find((item) => item.id === "arrowLine"); const arrowHead = group._objects.find((item) => item.id === "arrowHead"); const arrowLineControl = group._objects.find( (item) => item.id === "arrowLineControl", ); arrowLineControl.set({ opacity: 0 }); // Ungroup temporarily so we can edit in canvas coords group._restoreObjectsState(); canvas.remove(group); items.forEach((item) => canvas.add(item)); canvas.requestRenderAll(); const updateGeometry = () => { const x1 = arrowCircle1.left + 5; const y1 = arrowCircle1.top + 5; const x2 = arrowCircle2.left + 5; const y2 = arrowCircle2.top + 5; arrowLine.set({ x1, y1, x2, y2 }); arrowLineControl.set({ x1, y1, x2, y2 }); arrowLine.setCoords(); arrowLineControl.setCoords(); const angle = (Math.atan2(y2 - y1, x2 - x1) * 180) / Math.PI; arrowHead.set({ angle: angle + 90, left: x2, top: y2 }); arrowHead.setCoords(); }; const onMove = (o) => { if (!isDown) return; const pointer = canvas.getPointer(o.e); arrowCircle.set({ left: pointer.x - 5, top: pointer.y - 5 }); arrowCircle.setCoords(); updateGeometry(); canvas.requestRenderAll(); }; const onUp = () => { if (!isDown) return; isDown = false; canvas.off("mouse:move", onMove); canvas.off("mouse:up", onUp); arrowLineControl.set({ opacity: 1 }); const newGroup = new fabric.Group( [arrowLine, arrowHead, arrowLineControl, arrowCircle1, arrowCircle2], { selectable: true, evented: true, id: "arrowGroup", hasControls: false, hasBorders: false, hasRotatingPoint: false, subTargetCheck: true, originX: "left", originY: "top", perPixelTargetFind: true, }, ); canvas.add(newGroup); canvas.remove(arrowLine); canvas.remove(arrowHead); canvas.remove(arrowCircle1); canvas.remove(arrowCircle2); canvas.remove(arrowLineControl); canvas.requestRenderAll(); canvas.discardActiveObject(); canvas.requestRenderAll(); const state = getState(); saveCanvas({ ...state, tool: "select" }, setContentState); canvas.setActiveObject(newGroup); canvas.requestRenderAll(); }; canvas.on("mouse:move", onMove); canvas.on("mouse:up", onUp); }; // --- draw handlers const onMouseDown = (o) => { const state = getState(); if (!state?.drawingMode) return; if (state.tool !== "arrow") return; if (arrowPoints.length) return; canvas.selection = false; canvas.requestRenderAll(); const { x, y } = canvas.getPointer(o.e); arrowPoints = [{ x, y }]; arrowLine = createArrowLine(x, y, state.color, state.strokeWidth); arrowHead = createArrowHead(x, y, state.color, state.strokeWidth); arrowCircle1 = createArrowCircle(x - 5, y - 5, "arrowCircle1"); arrowCircle2 = createArrowCircle(x - 5, y - 5, "arrowCircle2"); arrowLineControl = createArrowLineControl(x, y); canvas.add(arrowLine); canvas.add(arrowHead); canvas.add(arrowLineControl); canvas.add(arrowCircle1); canvas.add(arrowCircle2); canvas.requestRenderAll(); }; const onMouseMove = (o) => { const state = getState(); if (!state?.drawingMode) return; if (state.tool !== "arrow") return; if (!arrowPoints.length) return; if ( !arrowLine || !arrowHead || !arrowCircle1 || !arrowCircle2 || !arrowLineControl ) return; const { x, y } = canvas.getPointer(o.e); const startX = arrowPoints[0].x; const startY = arrowPoints[0].y; arrowLine.set({ x1: startX, y1: startY, x2: x, y2: y }); arrowLineControl.set({ x1: startX, y1: startY, x2: x, y2: y }); arrowLine.setCoords(); arrowLineControl.setCoords(); const angle = (Math.atan2(y - startY, x - startX) * 180) / Math.PI; arrowHead.set({ left: x, top: y, angle: angle + 90 }); arrowHead.setCoords(); arrowCircle1.set({ left: startX - 5, top: startY - 5 }); // keep your “slight offset” if you want, but simplest is: arrowCircle2.set({ left: x - 5, top: y - 5 }); arrowCircle1.setCoords(); arrowCircle2.setCoords(); canvas.requestRenderAll(); }; const onMouseUp = () => { const state = getState(); if (!state?.drawingMode) return; if (state.tool !== "arrow") return; if (!arrowPoints.length) return; canvas.selection = true; arrowPoints = []; arrowCircle1.set({ opacity: 1 }); arrowCircle2.set({ opacity: 1 }); arrowLineControl.set({ opacity: 1 }); const group = new fabric.Group( [arrowLine, arrowHead, arrowLineControl, arrowCircle1, arrowCircle2], { selectable: true, evented: true, id: "arrowGroup", hasControls: false, hasBorders: false, hasRotatingPoint: false, subTargetCheck: true, originX: "left", originY: "top", perPixelTargetFind: true, }, ); canvas.add(group); canvas.remove(arrowLine); canvas.remove(arrowHead); canvas.remove(arrowCircle1); canvas.remove(arrowCircle2); canvas.remove(arrowLineControl); canvas.requestRenderAll(); saveCanvas({ ...state, tool: "select" }, setContentState); canvas.setActiveObject(group); canvas.requestRenderAll(); }; // --- selection visibility handlers (keep refs for proper off) const onBeforeSelectionCleared = () => { const activeObject = canvas.getActiveObject(); if (activeObject && activeObject.id === "arrowGroup") { activeObject._objects.forEach((obj) => { if ( obj.id === "arrowCircle1" || obj.id === "arrowCircle2" || obj.id === "arrowLineControl" ) { obj.set({ opacity: 0 }); } }); canvas.requestRenderAll(); } }; const onSelectionCleared = () => { canvas.getObjects().forEach((obj) => { if (obj.type === "group") { obj._objects.forEach((obj2) => { if ( obj2.id === "arrowCircle1" || obj2.id === "arrowCircle2" || obj2.id === "arrowLineControl" ) { obj2.set({ opacity: 0 }); } }); } }); canvas.requestRenderAll(); }; const onSelectionChanged = () => { const activeObject = canvas.getActiveObject(); if (activeObject && activeObject.id === "arrowGroup") { activeObject._objects.forEach((obj) => { if ( obj.id === "arrowCircle1" || obj.id === "arrowCircle2" || obj.id === "arrowLineControl" ) { obj.set({ opacity: 1 }); } }); canvas.requestRenderAll(); } if (activeObject && activeObject.type === "activeSelection") { activeObject._objects.forEach((obj) => { if (obj.id === "arrowGroup") { obj._objects.forEach((obj2) => { if (obj2.id === "arrowLineControl") obj2.set({ opacity: 1 }); }); } }); canvas.requestRenderAll(); } }; // attach canvas.on("mouse:down", onEndpointMouseDown); // endpoint dragging canvas.on("mouse:down", onMouseDown); canvas.on("mouse:move", onMouseMove); canvas.on("mouse:up", onMouseUp); canvas.on("before:selection:cleared", onBeforeSelectionCleared); canvas.on("selection:cleared", onSelectionCleared); canvas.on("selection:created", onSelectionChanged); canvas.on("selection:updated", onSelectionChanged); return { removeEventListeners: function () { canvas.off("mouse:down", onEndpointMouseDown); canvas.off("mouse:down", onMouseDown); canvas.off("mouse:move", onMouseMove); canvas.off("mouse:up", onMouseUp); canvas.off("before:selection:cleared", onBeforeSelectionCleared); canvas.off("selection:cleared", onSelectionCleared); canvas.off("selection:created", onSelectionChanged); canvas.off("selection:updated", onSelectionChanged); }, }; }; export default ArrowTool; ================================================ FILE: src/pages/Content/canvas/modules/CustomControls.jsx ================================================ import React from "react"; import { fabric } from "fabric"; import { HandleControl, RotateControl, MiddleHandleControl, MiddleHandleControlV, } from "./../../images/popup/images.js"; // Custom controls for the canvas, with rounded square handles and a circular rotate handle const CustomControls = (canvas) => { fabric.Object.prototype.set({ transparentCorners: false, borderColor: "#0D99FF", cornerColor: "#FFF", borderScaleFactor: 2, cornerStyle: "circle", cornerStrokeColor: "#0D99FF", borderOpacityWhenMoving: 1, }); fabric.Textbox.prototype.set({ transparentCorners: false, borderColor: "#0D99FF", cornerColor: "#FFF", borderScaleFactor: 2, cornerStyle: "circle", cornerStrokeColor: "#0D99FF", borderOpacityWhenMoving: 1, }); canvas.selectionColor = "rgba(46, 115, 252, 0.11)"; canvas.selectionBorderColor = "rgba(98, 155, 255, 0.81)"; canvas.selectionLineWidth = 1.5; // Handle control var img = new Image(); img.src = HandleControl; function renderIcon(ctx, left, top, styleOverride, fabricObject) { const size = 25; ctx.save(); ctx.translate(left, top); ctx.rotate(fabric.util.degreesToRadians(fabricObject.angle)); ctx.drawImage(img, -size / 2, -size / 2, size, size); ctx.restore(); } // Rotate control var img2 = new Image(); img2.src = RotateControl; function renderIconRotate(ctx, left, top, styleOverride, fabricObject) { const wsize = 30; const hsize = 30; ctx.save(); ctx.translate(left, top); ctx.rotate(fabric.util.degreesToRadians(fabricObject.angle)); ctx.drawImage(img2, -wsize / 2, -hsize / 2, wsize, hsize); ctx.restore(); } // Middle handle control var img3 = new Image(); img3.src = MiddleHandleControl; function renderIconMiddle(ctx, left, top, styleOverride, fabricObject) { const size = 18; ctx.save(); ctx.translate(left, top); ctx.rotate(fabric.util.degreesToRadians(fabricObject.angle)); ctx.drawImage(img3, -(size + 14) / 2, -size / 2, size + 14, size); ctx.restore(); } // Middle handle control vertical var img4 = new Image(); img4.src = MiddleHandleControlV; function renderIconMiddleV(ctx, left, top, styleOverride, fabricObject) { const size = 18; ctx.save(); ctx.translate(left, top); ctx.rotate(fabric.util.degreesToRadians(fabricObject.angle)); ctx.drawImage(img4, -size / 2, -(size + 14) / 2, size, size + 14); ctx.restore(); } fabric.Object.prototype.controls.tl = new fabric.Control({ x: -0.5, y: -0.5, offsetX: -1, offsetY: -1, cursorStyleHandler: fabric.controlsUtils.scaleCursorStyleHandler, actionHandler: fabric.controlsUtils.scalingEqually, render: renderIcon, }); fabric.Object.prototype.controls.tr = new fabric.Control({ x: 0.5, y: -0.5, offsetX: 1, offsetY: -1, cursorStyleHandler: fabric.controlsUtils.scaleCursorStyleHandler, actionHandler: fabric.controlsUtils.scalingEqually, render: renderIcon, }); fabric.Object.prototype.controls.bl = new fabric.Control({ x: -0.5, y: 0.5, offsetX: -1, offsetY: 1, cursorStyleHandler: fabric.controlsUtils.scaleCursorStyleHandler, actionHandler: fabric.controlsUtils.scalingEqually, render: renderIcon, }); fabric.Object.prototype.controls.br = new fabric.Control({ x: 0.5, y: 0.5, offsetX: 1, offsetY: 1, cursorStyleHandler: fabric.controlsUtils.scaleCursorStyleHandler, actionHandler: fabric.controlsUtils.scalingEqually, render: renderIcon, }); fabric.Object.prototype.controls.mr = new fabric.Control({ x: 0.5, y: 0, offsetX: 1, offsetY: 0, cursorStyleHandler: fabric.controlsUtils.scaleCursorStyleHandler, actionHandler: fabric.controlsUtils.scalingXOrSkewingY, render: renderIconMiddleV, }); fabric.Object.prototype.controls.ml = new fabric.Control({ x: -0.5, y: 0, offsetX: -1, offsetY: 0, cursorStyleHandler: fabric.controlsUtils.scaleCursorStyleHandler, actionHandler: fabric.controlsUtils.scalingXOrSkewingY, render: renderIconMiddleV, }); fabric.Object.prototype.controls.mb = new fabric.Control({ x: 0, y: 0.5, offsetX: 0, offsetY: 1, cursorStyleHandler: fabric.controlsUtils.scaleCursorStyleHandler, actionHandler: fabric.controlsUtils.scalingYOrSkewingX, render: renderIconMiddle, }); fabric.Object.prototype.controls.mt = new fabric.Control({ x: 0, y: -0.5, offsetX: 0, offsetY: -1, cursorStyleHandler: fabric.controlsUtils.scaleCursorStyleHandler, actionHandler: fabric.controlsUtils.scalingYOrSkewingX, render: renderIconMiddle, }); fabric.Object.prototype.controls.mtr = new fabric.Control({ x: 0, y: 0.5, cursorStyleHandler: fabric.controlsUtils.rotationStyleHandler, actionHandler: fabric.controlsUtils.rotationWithSnapping, offsetY: 26, withConnecton: false, actionName: "rotate", render: renderIconRotate, }); // Also use same controls for Textbox fabric.Textbox.prototype.controls.tl = new fabric.Control({ x: -0.5, y: -0.5, offsetX: -1, offsetY: -1, cursorStyleHandler: fabric.controlsUtils.scaleCursorStyleHandler, actionHandler: fabric.controlsUtils.scalingEqually, render: renderIcon, }); fabric.Textbox.prototype.controls.tr = new fabric.Control({ x: 0.5, y: -0.5, offsetX: 1, offsetY: -1, cursorStyleHandler: fabric.controlsUtils.scaleCursorStyleHandler, actionHandler: fabric.controlsUtils.scalingEqually, render: renderIcon, }); fabric.Textbox.prototype.controls.bl = new fabric.Control({ x: -0.5, y: 0.5, offsetX: -1, offsetY: 1, cursorStyleHandler: fabric.controlsUtils.scaleCursorStyleHandler, actionHandler: fabric.controlsUtils.scalingEqually, render: renderIcon, }); fabric.Textbox.prototype.controls.br = new fabric.Control({ x: 0.5, y: 0.5, offsetX: 1, offsetY: 1, cursorStyleHandler: fabric.controlsUtils.scaleCursorStyleHandler, actionHandler: fabric.controlsUtils.scalingEqually, render: renderIcon, }); // Ml and mr controls for Textbox. The actionhandler should be for resizing the textbox horizontally, not skewing it fabric.Textbox.prototype.controls.ml = new fabric.Control({ x: -0.5, y: 0, offsetX: -1, offsetY: 0, cursorStyleHandler: fabric.controlsUtils.scaleCursorStyleHandler, actionHandler: fabric.controlsUtils.changeWidth, render: renderIconMiddleV, }); fabric.Textbox.prototype.controls.mr = new fabric.Control({ x: 0.5, y: 0, offsetX: 1, offsetY: 0, cursorStyleHandler: fabric.controlsUtils.scaleCursorStyleHandler, actionHandler: fabric.controlsUtils.changeWidth, render: renderIconMiddleV, }); fabric.Textbox.prototype.controls.mb = new fabric.Control({ x: 0, y: 0.5, offsetX: 0, visible: false, offsetY: 1, cursorStyleHandler: fabric.controlsUtils.scaleCursorStyleHandler, actionHandler: fabric.controlsUtils.changeHeight, render: renderIconMiddle, }); fabric.Textbox.prototype.controls.mt = new fabric.Control({ x: 0, y: -0.5, offsetX: 0, offsetY: -1, visible: false, cursorStyleHandler: fabric.controlsUtils.scaleCursorStyleHandler, actionHandler: fabric.controlsUtils.changeHeight, render: renderIconMiddle, }); fabric.Textbox.prototype.controls.mtr = new fabric.Control({ x: 0, y: 0.5, cursorStyleHandler: fabric.controlsUtils.rotationStyleHandler, actionHandler: fabric.controlsUtils.rotationWithSnapping, offsetY: 26, withConnecton: false, actionName: "rotate", render: renderIconRotate, }); // Update canvas rendering canvas.renderAll(); }; export default CustomControls; /* fabric.Object.prototype.set({ transparentCorners: false, borderColor: '#51B9F9', cornerColor: '#FFF', borderScaleFactor: 2.5, cornerStyle: 'circle', cornerStrokeColor: '#0E98FC', borderOpacityWhenMoving: 1, }); canvas.selectionColor = 'rgba(46, 115, 252, 0.11)'; canvas.selectionBorderColor = 'rgba(98, 155, 255, 0.81)'; canvas.selectionLineWidth = 1.5; var img = document.createElement('img'); img.src = 'assets/middlecontrol.svg'; var img2 = document.createElement('img'); img2.src = 'assets/middlecontrolhoz.svg'; var img3 = document.createElement('img'); img3.src = 'assets/edgecontrol.svg'; var img4 = document.createElement('img'); img4.src = 'assets/rotateicon.svg'; function renderIcon(ctx, left, top, styleOverride, fabricObject) { const wsize = 20; const hsize = 25; ctx.save(); ctx.translate(left, top); ctx.rotate(fabric.util.degreesToRadians(fabricObject.angle)); ctx.drawImage(img, -wsize / 2, -hsize / 2, wsize, hsize); ctx.restore(); } function renderIconHoz(ctx, left, top, styleOverride, fabricObject) { const wsize = 25; const hsize = 20; ctx.save(); ctx.translate(left, top); ctx.rotate(fabric.util.degreesToRadians(fabricObject.angle)); ctx.drawImage(img2, -wsize / 2, -hsize / 2, wsize, hsize); ctx.restore(); } function renderIconEdge(ctx, left, top, styleOverride, fabricObject) { const wsize = 25; const hsize = 25; ctx.save(); ctx.translate(left, top); ctx.rotate(fabric.util.degreesToRadians(fabricObject.angle)); ctx.drawImage(img3, -wsize / 2, -hsize / 2, wsize, hsize); ctx.restore(); } function renderIconRotate( ctx, left, top, styleOverride, fabricObject ) { const wsize = 40; const hsize = 40; ctx.save(); ctx.translate(left, top); ctx.rotate(fabric.util.degreesToRadians(fabricObject.angle)); ctx.drawImage(img4, -wsize / 2, -hsize / 2, wsize, hsize); ctx.restore(); } function resetControls() { fabric.Object.prototype.controls.ml = new fabric.Control({ x: -0.5, y: 0, offsetX: -1, cursorStyleHandler: fabric.controlsUtils.scaleSkewCursorStyleHandler, actionHandler: fabric.controlsUtils.scalingXOrSkewingY, getActionName: fabric.controlsUtils.scaleOrSkewActionName, render: renderIcon, }); fabric.Object.prototype.controls.mr = new fabric.Control({ x: 0.5, y: 0, offsetX: 1, cursorStyleHandler: fabric.controlsUtils.scaleSkewCursorStyleHandler, actionHandler: fabric.controlsUtils.scalingXOrSkewingY, getActionName: fabric.controlsUtils.scaleOrSkewActionName, render: renderIcon, }); fabric.Object.prototype.controls.mb = new fabric.Control({ x: 0, y: 0.5, offsetY: 1, cursorStyleHandler: fabric.controlsUtils.scaleSkewCursorStyleHandler, actionHandler: fabric.controlsUtils.scalingYOrSkewingX, getActionName: fabric.controlsUtils.scaleOrSkewActionName, render: renderIconHoz, }); fabric.Object.prototype.controls.mt = new fabric.Control({ x: 0, y: -0.5, offsetY: -1, cursorStyleHandler: fabric.controlsUtils.scaleSkewCursorStyleHandler, actionHandler: fabric.controlsUtils.scalingYOrSkewingX, getActionName: fabric.controlsUtils.scaleOrSkewActionName, render: renderIconHoz, }); fabric.Object.prototype.controls.tl = new fabric.Control({ x: -0.5, y: -0.5, cursorStyleHandler: fabric.controlsUtils.scaleCursorStyleHandler, actionHandler: fabric.controlsUtils.scalingEqually, render: renderIconEdge, }); fabric.Object.prototype.controls.tr = new fabric.Control({ x: 0.5, y: -0.5, cursorStyleHandler: fabric.controlsUtils.scaleCursorStyleHandler, actionHandler: fabric.controlsUtils.scalingEqually, render: renderIconEdge, }); fabric.Object.prototype.controls.bl = new fabric.Control({ x: -0.5, y: 0.5, cursorStyleHandler: fabric.controlsUtils.scaleCursorStyleHandler, actionHandler: fabric.controlsUtils.scalingEqually, render: renderIconEdge, }); fabric.Object.prototype.controls.br = new fabric.Control({ x: 0.5, y: 0.5, cursorStyleHandler: fabric.controlsUtils.scaleCursorStyleHandler, actionHandler: fabric.controlsUtils.scalingEqually, render: renderIconEdge, }); fabric.Object.prototype.controls.mtr = new fabric.Control({ x: 0, y: 0.5, cursorStyleHandler: fabric.controlsUtils.rotationStyleHandler, actionHandler: fabric.controlsUtils.rotationWithSnapping, offsetY: 30, withConnecton: false, actionName: 'rotate', render: renderIconRotate, }); } resetControls(); */ ================================================ FILE: src/pages/Content/canvas/modules/EraserTool.jsx ================================================ import { saveCanvas } from "./History"; const EraserTool = (canvas, contentStateRef, setContentState) => { const getState = () => contentStateRef.current; let objectsToDelete = []; let isDown = false; const setEraserHitTestMode = (enabled) => { // enabled: perPixelTargetFind true, selectable false canvas.forEachObject((object) => { object.set({ selectable: false, perPixelTargetFind: enabled ? true : false, }); }); }; const onMouseDown = (o) => { const state = getState(); if (!state?.drawingMode) return; if (state.tool !== "eraser") return; objectsToDelete = []; isDown = true; setEraserHitTestMode(true); if (!o.target) return; objectsToDelete.push(o.target); o.target.set({ opacity: 0.5 }); canvas.requestRenderAll(); }; const onMouseMove = (o) => { const state = getState(); if (!state?.drawingMode) return; if (state.tool !== "eraser") return; if (!isDown) return; if (!o.target) return; // avoid duplicates (optional but nice) if (!objectsToDelete.includes(o.target)) { objectsToDelete.push(o.target); o.target.set({ opacity: 0.5 }); } canvas.requestRenderAll(); }; const onMouseUp = () => { const state = getState(); if (!state?.drawingMode) return; if (state.tool !== "eraser") return; // delete collected objectsToDelete.forEach((object) => { if (!object) return; if (object.type === "group") { // remove group children then group object._objects?.forEach((child) => canvas.remove(child)); canvas.remove(object); } else { canvas.remove(object); } }); // restore opacities if any remained (in case duplicates / removed objects) canvas.getObjects().forEach((obj) => { if (obj?.opacity === 0.5) obj.set({ opacity: 1 }); }); objectsToDelete = []; isDown = false; // Selection logic is handled by your CanvasWrap effect (based on tool), // so we don't force selectable=true here. canvas.requestRenderAll(); saveCanvas(contentStateRef, setContentState); }; const onKeyDown = (e) => { const state = getState(); if (!state?.drawingMode) return; if (state.tool !== "eraser") return; if (e.key === "Backspace" || e.key === "Delete") { const activeObjects = canvas.getActiveObjects(); const activeObject = canvas.getActiveObject(); // If text editing, don't delete if (activeObject?.isEditing) return; if (activeObjects && activeObjects.length > 1 && !activeObject) { canvas.discardActiveObject(); activeObjects.forEach((obj) => canvas.remove(obj)); saveCanvas(contentStateRef, setContentState); canvas.requestRenderAll(); return; } if (activeObject) { canvas.remove(activeObject); saveCanvas(contentStateRef, setContentState); canvas.requestRenderAll(); } } }; // Attach listeners once document.addEventListener("keydown", onKeyDown); canvas.on("mouse:down", onMouseDown); canvas.on("mouse:move", onMouseMove); canvas.on("mouse:up", onMouseUp); return { removeEventListeners: () => { canvas.off("mouse:down", onMouseDown); canvas.off("mouse:move", onMouseMove); canvas.off("mouse:up", onMouseUp); document.removeEventListener("keydown", onKeyDown); }, }; }; export default EraserTool; ================================================ FILE: src/pages/Content/canvas/modules/History.jsx ================================================ import { fabric } from "fabric"; const getState = (stateOrRef) => stateOrRef && stateOrRef.current ? stateOrRef.current : stateOrRef; // Undo and redo functionality for Fabric.js const undoCanvas = (stateOrRef, setToolSettings) => { const state = getState(stateOrRef); if (!state?.canvas) return; const canvas = state.canvas; if (state.undoStack?.length > 0) { const undoStack = [...state.undoStack]; const redoStack = [...(state.redoStack || [])]; const lastItem = undoStack.pop(); redoStack.push(lastItem); const penultimateItem = undoStack[undoStack.length - 1]; if (!penultimateItem) { // nothing meaningful to load setToolSettings({ ...state, undoStack, redoStack }); return; } canvas.clear(); canvas.renderAll(); canvas.loadFromJSON(penultimateItem, () => { canvas.discardActiveObject(); canvas.renderAll(); }); setToolSettings({ ...state, undoStack, redoStack }); } }; const redoCanvas = (stateOrRef, setToolSettings) => { const state = getState(stateOrRef); if (!state?.canvas) return; const canvas = state.canvas; if (state.redoStack?.length > 0) { const undoStack = [...(state.undoStack || [])]; const redoStack = [...state.redoStack]; const lastItem = redoStack.pop(); undoStack.push(lastItem); canvas.loadFromJSON(lastItem, () => { canvas.discardActiveObject(); canvas.renderAll(); }); setToolSettings({ ...state, undoStack, redoStack }); } }; const saveCanvas = (stateOrRef, setToolSettings) => { const state = getState(stateOrRef); if (!state?.canvas) return; const canvas = state.canvas; const json = canvas.toJSON([ "id", "selectable", "evented", "hasControls", "hasBorders", "hasRotatingPoint", "subTargetCheck", "originX", "originY", "perPixelTargetFind", "skipAutoWidthAdjustment", ]); const jsonString = JSON.stringify(json); const undoStack = [...(state.undoStack || []), jsonString]; setToolSettings({ ...state, undoStack, redoStack: [], }); }; const checkChanges = (canvas, stateRef, setToolSettings) => { const onChange = () => { // always save with latest state saveCanvas(stateRef, setToolSettings); }; canvas.on("object:modified", onChange); return { removeEventListeners: function () { canvas.off("object:modified", onChange); }, }; }; export { undoCanvas, redoCanvas, saveCanvas, checkChanges }; ================================================ FILE: src/pages/Content/canvas/modules/ImageTool.jsx ================================================ import { fabric } from "fabric"; const ImageTool = ( canvas, src, toolSettings, setToolSettings, saveCanvas, contentState ) => { const image = new Image(); let fabricImage = null; image.src = src; const state = { isPlacing: true, }; const cleanup = () => { state.isPlacing = false; canvas.off("mouse:move", onMouseMove); canvas.off("mouse:down", onMouseDown); canvas.off("mouse:up", onMouseUp); }; const onMouseMove = (o) => { if (!fabricImage || !state.isPlacing) return; const pointer = canvas.getPointer(o.e); fabricImage.set({ left: pointer.x, top: pointer.y }); fabricImage.setCoords(); canvas.requestRenderAll(); }; const onMouseDown = () => { if (!fabricImage || !state.isPlacing) return; state.isPlacing = false; fabricImage.set({ opacity: 1, selectable: true, }); fabricImage.setCoords(); canvas.setActiveObject(fabricImage); canvas.requestRenderAll(); saveCanvas(toolSettings, setToolSettings); setToolSettings({ ...toolSettings, tool: "select", isAddingImage: false }); // Make other objects selectable again canvas.forEachObject((obj) => { obj.selectable = true; }); cleanup(); }; const onMouseUp = () => {}; image.onload = () => { fabricImage = new fabric.Image(image); fabricImage.set({ left: 0, top: 0, originX: "left", originY: "top", strokeUniform: true, fill: "transparent", angle: 0, noScaleCache: false, opacity: 0.5, selectable: false, }); // Scale down const maxWidth = 500; const maxHeight = 500; const width = fabricImage.width; const height = fabricImage.height; const ratio = Math.min(maxWidth / width, maxHeight / height); fabricImage.scale(ratio); canvas.add(fabricImage); canvas.bringToFront(fabricImage); canvas.requestRenderAll(); // Make other objects unselectable canvas.forEachObject((obj) => { obj.selectable = false; }); canvas.on("mouse:move", onMouseMove); canvas.on("mouse:down", onMouseDown); canvas.on("mouse:up", onMouseUp); // Open toast cancel handler contentState.openToast(chrome.i18n.getMessage("addImageToastTitle"), () => { if (fabricImage) { canvas.remove(fabricImage); canvas.requestRenderAll(); } setToolSettings({ ...toolSettings, tool: "select", isAddingImage: false, }); canvas.forEachObject((obj) => (obj.selectable = true)); cleanup(); }); }; return { removeEventListeners: cleanup, }; }; export default ImageTool; ================================================ FILE: src/pages/Content/canvas/modules/PenTool.jsx ================================================ import { fabric } from "fabric"; const PenTool = (canvas, contentStateRef, setContentState, saveCanvas) => { const getState = () => contentStateRef.current; const resetBrushStroke = () => { const brush = canvas.freeDrawingBrush; if (!brush) return; // Fabric versions differ; do all safe resets. if (Array.isArray(brush._points)) brush._points.length = 0; if (Array.isArray(brush.points)) brush.points.length = 0; // Some brushes keep a "latest" pointer brush._reset && brush._reset(); }; const ensureBrush = () => { // fabric creates freeDrawingBrush lazily; make sure it's there if (!canvas.freeDrawingBrush) { canvas.freeDrawingBrush = new fabric.PencilBrush(canvas); } return canvas.freeDrawingBrush; }; const syncBrushFromState = () => { const state = getState(); if (!state) return; const brush = ensureBrush(); // reset defaults brush.drawStraightLine = false; brush.straightLineKey = "none"; brush.strokeLineCap = "round"; brush.globalCompositeOperation = "source-over"; if (state.tool === "pen") { brush.width = (state.strokeWidth || 2) * 4; brush.color = state.color; resetBrushStroke(); return; } if (state.tool === "highlighter") { brush.width = (state.strokeWidth || 2) * 10; brush.color = new fabric.Color(state.color).setAlpha(0.5).toRgba(); brush.globalCompositeOperation = "destination-over"; brush.strokeLineCap = "square"; resetBrushStroke(); } }; // Sync brush on interactions so tool switches apply immediately const onMouseDown = () => { syncBrushFromState(); }; const onMouseUp = () => { const state = getState(); if (!state) return; if (state.tool !== "pen" && state.tool !== "highlighter") return; // Save with latest state // If you updated saveCanvas to accept ref, use: saveCanvas(contentStateRef, setContentState) saveCanvas(state, setContentState); }; const onPathCreated = (o) => { // Only wrap paths if we were drawing (pen/highlighter) const state = getState(); if (!state) return; if (state.tool !== "pen" && state.tool !== "highlighter") return; const path = o.path; if (!path) return; const pathCopy = new fabric.Path(path.path, { id: "select-stroke", stroke: "#0D99FF", strokeWidth: 2, fill: null, opacity: 0, selectable: false, evented: false, }); const group = new fabric.Group([path, pathCopy], { selectable: true, evented: true, id: "select-group", subTargetCheck: true, perPixelTargetFind: true, hasControls: false, hasBorders: false, }); canvas.add(group); canvas.remove(path); canvas.requestRenderAll(); }; const hideAllSelectStrokes = () => { canvas.getObjects().forEach((obj) => { if (obj.type === "group") { obj._objects?.forEach((child) => { if (child.id === "select-stroke") child.set({ opacity: 0 }); }); } }); }; const activateStroke = () => { const state = getState(); if (!state) return; if (state.tool !== "select") return; hideAllSelectStrokes(); const activeObject = canvas.getActiveObject(); if (activeObject && activeObject.id === "select-group") { activeObject._objects?.forEach((child) => { if (child.id === "select-stroke") child.set({ opacity: 1 }); }); } const activeObjects = canvas.getActiveObjects(); if (activeObjects && activeObjects.length > 1) { activeObjects.forEach((obj) => { if (obj.id === "select-group") { obj._objects?.forEach((child) => { if (child.id === "select-stroke") child.set({ opacity: 1 }); }); } }); } canvas.requestRenderAll(); }; const deactivateStroke = () => { hideAllSelectStrokes(); canvas.requestRenderAll(); }; // Attach once canvas.on("mouse:down", onMouseDown); canvas.on("mouse:up", onMouseUp); canvas.on("path:created", onPathCreated); canvas.on("selection:created", activateStroke); canvas.on("selection:updated", activateStroke); canvas.on("selection:cleared", deactivateStroke); // Initial sync syncBrushFromState(); return { removeEventListeners: () => { canvas.off("mouse:down", onMouseDown); canvas.off("mouse:up", onMouseUp); canvas.off("path:created", onPathCreated); canvas.off("selection:created", activateStroke); canvas.off("selection:updated", activateStroke); canvas.off("selection:cleared", deactivateStroke); }, }; }; export default PenTool; ================================================ FILE: src/pages/Content/canvas/modules/SelectTool.jsx ================================================ import { fabric } from "fabric"; const SelectTool = (canvas, contentStateRef, setContentState) => { const getState = () => contentStateRef.current; // On mouse over object const onMouseOver = (o) => { const state = getState(); if (!state) return; if (state.tool !== "select") return; if (state.isAddingImage) return; if (!o.target) return; if (o.target !== canvas.getActiveObject()) { if (o.target.type === "group" && o.target.id === "select-group") { const selectStroke = o.target._objects?.find( (object) => object.id === "select-stroke", ); if (selectStroke) selectStroke.set({ opacity: 1 }); } else if (o.target.type === "group" && o.target.id === "arrowGroup") { o.target._objects?.forEach((obj) => { if (obj.id === "arrowLineControl") obj.set({ opacity: 1 }); }); } else { // draws fabric controls on the top context o.target._renderControls(o.target.canvas.contextTop, { hasControls: false, }); } canvas.requestRenderAll(); } }; // On mouse out object const onMouseOut = (o) => { const state = getState(); if (!state) return; if (state.tool !== "select") return; if (!o.target) return; if (o.target !== canvas.getActiveObject()) { if (o.target.type === "group" && o.target.id === "select-group") { const selectStroke = o.target._objects?.find( (object) => object.id === "select-stroke", ); if (selectStroke) selectStroke.set({ opacity: 0 }); } else if (o.target.type === "group" && o.target.id === "arrowGroup") { o.target._objects?.forEach((obj) => { if (obj.id === "arrowLineControl") obj.set({ opacity: 0 }); }); } } if (o.target?.canvas?.contextTop) { o.target.canvas.clearContext(o.target.canvas.contextTop); } canvas.requestRenderAll(); }; const onMouseDown = (o) => { const state = getState(); if (!state) return; if (state.isAddingImage) return; if (state.tool !== "select") return; if (!o.target?.canvas?.contextTop) return; o.target.canvas.clearContext(o.target.canvas.contextTop); }; canvas.on("mouse:over", onMouseOver); canvas.on("mouse:out", onMouseOut); canvas.on("mouse:down", onMouseDown); return { removeEventListeners: function () { canvas.off("mouse:over", onMouseOver); canvas.off("mouse:out", onMouseOut); canvas.off("mouse:down", onMouseDown); }, }; }; export default SelectTool; ================================================ FILE: src/pages/Content/canvas/modules/ShapeTool.jsx ================================================ import { fabric } from "fabric"; const ShapeTool = (canvas, contentStateRef, setContentState, saveCanvas) => { const getState = () => contentStateRef.current; let shape = null; let isDown = false; let origX = 0; let origY = 0; const makeShape = (state, pointer) => { const color = state.color; const strokeWidth = (state.strokeWidth || 2) * 6; const fill = state.shapeFill ? color : "transparent"; if (state.shape === "rectangle") { return new fabric.Rect({ left: origX, top: origY, originX: "left", originY: "top", width: 0, height: 0, strokeUniform: true, angle: 0, fill, noScaleCache: false, stroke: color, strokeWidth, }); } if (state.shape === "triangle") { return new fabric.Triangle({ left: origX, top: origY, originX: "left", originY: "top", strokeMilterLimit: 8, objectCaching: false, width: 0, height: 0, strokeUniform: true, angle: 0, fill, noScaleCache: false, stroke: color, strokeWidth, }); } // circle return new fabric.Circle({ left: origX, top: origY, originX: "left", originY: "top", radius: 0, strokeUniform: true, angle: 0, fill, noScaleCache: false, stroke: color, strokeWidth, }); }; const onMouseDown = (o) => { const state = getState(); if (!state?.drawingMode) return; if (state.tool !== "shape") return; isDown = true; const pointer = canvas.getPointer(o.e); origX = pointer.x; origY = pointer.y; shape = makeShape(state, pointer); canvas.add(shape); canvas.requestRenderAll(); }; const onMouseMove = (o) => { const state = getState(); if (!state?.drawingMode) return; if (state.tool !== "shape") return; if (!isDown || !shape) return; const pointer = canvas.getPointer(o.e); // Handle dragging “backwards” const left = Math.min(origX, pointer.x); const top = Math.min(origY, pointer.y); const w = Math.abs(pointer.x - origX); const h = Math.abs(pointer.y - origY); shape.set({ left, top }); if (state.shape === "rectangle" || state.shape === "triangle") { shape.set({ width: w, height: h }); } else if (state.shape === "circle") { // Use min dimension so it stays circular const r = Math.min(w, h) / 2; shape.set({ radius: r }); } shape.setCoords(); canvas.requestRenderAll(); }; const onMouseUp = () => { const state = getState(); if (!state?.drawingMode) return; if (state.tool !== "shape") return; if (!isDown) return; isDown = false; canvas.requestRenderAll(); // Save with latest state (not stale snapshot) saveCanvas({ ...state, tool: "select" }, setContentState); if (shape) { canvas.setActiveObject(shape); canvas.requestRenderAll(); } }; canvas.on("mouse:down", onMouseDown); canvas.on("mouse:move", onMouseMove); canvas.on("mouse:up", onMouseUp); return { removeEventListeners: () => { canvas.off("mouse:down", onMouseDown); canvas.off("mouse:move", onMouseMove); canvas.off("mouse:up", onMouseUp); }, }; }; export default ShapeTool; ================================================ FILE: src/pages/Content/canvas/modules/TextTool.jsx ================================================ import { fabric } from "fabric"; const TextTool = (canvas, contentStateRef, setContentState, saveCanvas) => { const getState = () => contentStateRef.current; // Track the currently-created textbox so we can finalize it cleanly let activeCreatedText = null; const finalizeIfNeeded = () => { if (!activeCreatedText) return; const text = activeCreatedText; const currentActive = canvas.getActiveObject(); // If user clicked away / ended editing if (currentActive !== text) { if ((text.text || "").trim() === "") { canvas.remove(text); } else { // Save using latest state saveCanvas({ ...getState(), canvas }, setContentState); } activeCreatedText = null; canvas.requestRenderAll(); } }; const onCanvasMouseDownCapture = (o) => { // This runs on any mouse down to potentially finalize the last text. // But don't interfere if user is still editing that text. finalizeIfNeeded(); }; const onMouseDown = (o) => { const state = getState(); if (!state?.drawingMode) return; if (state.tool !== "text") return; // Before creating a new one, finalize previous if any finalizeIfNeeded(); const pointer = canvas.getPointer(o.e); const x = pointer.x; const y = pointer.y; const text = new fabric.Textbox("", { left: x, top: y, fontFamily: "Satoshi-Medium", fontSize: 20, fill: state.color, fontWeight: "normal", fontStyle: "normal", originX: "left", originY: "top", textAlign: "center", lockUniScaling: true, centeredScaling: true, skipAutoWidthAdjustment: false, perPixelTargetFind: false, }); text.on("editing:entered", () => { text.borderColor = "#0D99FF"; }); canvas.add(text); canvas.setActiveObject(text); // Start editing text.enterEditing(); text.selectAll(); activeCreatedText = text; // Set tool back to select (use latest state) setContentState((prev) => ({ ...prev, tool: "select", })); canvas.requestRenderAll(); }; const onKeyPress = (event) => { const obj = canvas.getActiveObject(); if (!obj || typeof obj !== "object") return; if (obj.type !== "textbox" || !obj.isEditing) return; const text = obj; // If user explicitly resized, skip if (text.skipAutoWidthAdjustment) return; // Guard: _textLines might not exist at some moments const lines = text._textLines || []; if (lines.length === 0) return; let currentLine = lines[lines.length - 1].join(""); // Backspace if (event.key === "Backspace" && currentLine.length > 0) { currentLine = currentLine.slice(0, -1); } else if (event.key && event.key.length === 1) { currentLine += event.key; } else { // ignore other keys return; } // Measure const tempCanvas = document.createElement("canvas"); const tempCtx = tempCanvas.getContext("2d"); tempCtx.font = `${text.fontSize}px ${text.fontFamily}`; const textMetrics = tempCtx.measureText(currentLine); const maxLineWidth = getMaxLineWidth(lines, text); // Expand width if needed if (textMetrics.width > text.width) { const nextWidth = Math.max(text.width, textMetrics.width + 2); text.set({ left: text.left - (nextWidth - text.width) / 2, width: nextWidth, }); } else if (textMetrics.width < maxLineWidth) { text.set({ left: text.left + (text.width - maxLineWidth) / 2, width: maxLineWidth, }); } canvas.requestRenderAll(); }; function getMaxLineWidth(textLines, text) { let maxLineWidth = 0; const tempCanvas = document.createElement("canvas"); const tempCtx = tempCanvas.getContext("2d"); tempCtx.font = `${text.fontSize}px ${text.fontFamily}`; for (let i = 0; i < textLines.length; i++) { const line = textLines[i].join(""); const lineWidth = tempCtx.measureText(line).width; maxLineWidth = Math.max(maxLineWidth, lineWidth); } return maxLineWidth; } const onResize = (e) => { if (e?.target?.type !== "textbox") return; e.target.skipAutoWidthAdjustment = true; canvas.requestRenderAll(); }; // Hover logic: DON'T use anonymous mouse:move that you later nuke. const onMouseMove = (event) => { const pointer = canvas.getPointer(event.e); let hoveringTextbox = false; canvas.forEachObject((obj) => { if (obj.type === "textbox" && obj.containsPoint(pointer)) { hoveringTextbox = true; } }); canvas.perPixelTargetFind = !hoveringTextbox; }; // Attach listeners once // Note: we attach the “finalize” handler too, but it’s cheap. canvas.on("mouse:down", onCanvasMouseDownCapture); canvas.on("mouse:down", onMouseDown); document.addEventListener("keydown", onKeyPress); canvas.on("object:resizing", onResize); canvas.on("mouse:move", onMouseMove); return { removeEventListeners: () => { canvas.off("mouse:down", onCanvasMouseDownCapture); canvas.off("mouse:down", onMouseDown); document.removeEventListener("keydown", onKeyPress); canvas.off("object:resizing", onResize); canvas.off("mouse:move", onMouseMove); }, }; }; export default TextTool; ================================================ FILE: src/pages/Content/canvas/styles/_Canvas.scss ================================================ @use '../../styles/_variables' as *; @use './layout/Canvas'; ================================================ FILE: src/pages/Content/canvas/styles/layout/_Canvas.scss ================================================ @use '../../../styles/_variables' as *; .CanvasContainer { width: 100%; height: 100%; position: absolute; pointer-events: all!important; top: 0px!important; left: 0px!important; z-index: 99999999999!important; } .canvas { width: 100%; height: 100%; position: absolute; top: 0px!important; left: 0px!important; z-index: 99999999999!important; } .canvas-container { width: 100vw!important; height: 100vh!important; top: 0px!important; left: 0px!important; z-index: 99999999999; position: absolute!important; } ================================================ FILE: src/pages/Content/context/ContentState.jsx ================================================ import React, { createContext, useState, useEffect, useCallback, useRef, } from "react"; import { updateFromStorage } from "./utils/updateFromStorage"; // Shortcuts import Shortcuts from "../shortcuts/Shortcuts"; import DevHUD from "../DevHUD"; // import { initializeContentMessageListener } from "./messaging/messageListener"; import { setupHandlers } from "./messaging/handlers"; import { checkAuthStatus } from "./utils/checkAuthStatus"; import { initStartFlowTrace, traceStep, setStartFlowOutcome, } from "../../utils/startFlowTrace"; //create a context, with createContext api export const contentStateContext = createContext(); export const contentStateRef = { current: null }; export let setContentState = () => {}; export let setTimer = () => {}; const CURSOR_EFFECTS = ["target", "highlight", "spotlight"]; const normalizeCursorEffects = (effects) => { if (!Array.isArray(effects)) return []; return effects.filter((effect) => CURSOR_EFFECTS.includes(effect)); }; const deriveCursorMode = (effects, fallbackMode) => { if (effects.length === 0) return "none"; if (effects.length === 1) return effects[0]; if (fallbackMode && effects.includes(fallbackMode)) return fallbackMode; return effects[0] || "none"; }; const ContentState = (props) => { const [timer, setTimerInternal] = React.useState(0); const CLOUD_FEATURES_ENABLED = process.env.SCREENITY_ENABLE_CLOUD_FEATURES === "true"; setTimer = setTimerInternal; const [URL, setURL] = useState( "https://help.screenity.io/getting-started/77KizPC8MHVGfpKpqdux9D/why-does-screenity-ask-for-permissions/9AAE8zJ6iiUtCAtjn4SUT1", ); const [URL2, setURL2] = useState( "https://help.screenity.io/troubleshooting/9Jy5RGjNrBB42hqUdREQ7W/how-to-grant-screenity-permission-to-record-your-camera-and-microphone/x6U69TnrbMjy5CQ96Er2E9", ); const startBeepRef = useRef(null); const stopBeepRef = useRef(null); const prevRecordingRef = useRef(null); const hydratedRef = useRef(false); const suppressStopBeepRef = useRef(false); const suppressStartBeepRef = useRef(false); const tabIdRef = useRef(null); const activeTabRef = useRef(null); const tabRecordedIdRef = useRef(null); const recordingUiTabRef = useRef(null); const recordingStartTimeRef = useRef(null); const timerReadSeqRef = useRef(0); const lastBeepStartTimeRef = useRef(null); const recordingBeepTabIdRef = useRef(null); const verifyDebounceRef = useRef(null); const isTargetTab = useCallback(() => { const tabId = tabIdRef.current; const tabRecordedID = tabRecordedIdRef.current; const recordingUiTabId = recordingUiTabRef.current; const activeTab = activeTabRef.current; if (tabRecordedID != null) { return tabId != null && tabId === tabRecordedID; } if (recordingUiTabId != null) { return tabId != null && tabId === recordingUiTabId; } if (activeTab != null) { return tabId != null && tabId === activeTab; } return true; }, []); // Check if the user is logged in const verifyUser = useCallback(async () => { if (!CLOUD_FEATURES_ENABLED) return; const result = await checkAuthStatus(); setContentState((prev) => ({ ...prev, isLoggedIn: result.authenticated, screenityUser: result.user, isSubscribed: result.subscribed, hasSubscribedBefore: result.hasSubscribedBefore, proSubscription: result.proSubscription, ...(result.authenticated ? { wasLoggedIn: false } : {}), })); if (result.authenticated) { // Offscreen recording and client-side zoom are not available setContentState((prev) => ({ ...prev, offscreenRecording: false, zoomEnabled: false, })); chrome.storage.local.set({ offscreenRecording: false, zoomEnabled: false, wasLoggedIn: false, }); } }, [CLOUD_FEATURES_ENABLED]); useEffect(() => { verifyUser(); }, [verifyUser]); useEffect(() => { chrome.runtime.sendMessage({ type: "get-tab-id" }, (response) => { if (response?.tabId !== undefined && response?.tabId !== null) { tabIdRef.current = response.tabId; } }); chrome.storage.local.get( [ "activeTab", "tabRecordedID", "recordingUiTabId", "recordingStartTime", "recordingBeepTabId", "recordingNow", ], (result) => { activeTabRef.current = result.activeTab ?? null; tabRecordedIdRef.current = result.tabRecordedID ?? null; recordingUiTabRef.current = result.recordingUiTabId ?? null; recordingStartTimeRef.current = result.recordingStartTime ?? null; recordingBeepTabIdRef.current = result.recordingBeepTabId ?? null; }, ); }, []); useEffect(() => { const locale = chrome.i18n.getMessage("@@ui_locale"); if (!locale.includes("en")) { setURL( "https://translate.google.com/translate?sl=en&tl=" + locale + "&u=https://help.screenity.io/getting-started/77KizPC8MHVGfpKpqdux9D/why-does-screenity-ask-for-permissions/9AAE8zJ6iiUtCAtjn4SUT1", ); setURL2( "https://translate.google.com/translate?sl=en&tl=" + locale + "&u=https://help.screenity.io/troubleshooting/9Jy5RGjNrBB42hqUdREQ7W/how-to-grant-screenity-permission-to-record-your-camera-and-microphone/x6U69TnrbMjy5CQ96Er2E9", ); } }, []); const startRecording = useCallback(() => { const shouldClearCountdown = !contentStateRef.current?.isCountdownVisible && !contentStateRef.current?.countdownActive; if (contentStateRef.current.alarm) { if (contentStateRef.current.alarmTime === 0) { setContentState((prevContentState) => ({ ...prevContentState, alarm: false, })); chrome.storage.local.set({ alarm: false }); setTimer(0); } else { setTimer(contentStateRef.current.alarmTime); } } else { setTimer(0); } setContentState((prevContentState) => ({ ...prevContentState, recording: true, paused: false, timeWarning: false, pendingRecording: false, preparingRecording: false, ...(shouldClearCountdown ? { countdownActive: false, isCountdownVisible: false } : {}), })); if (tabIdRef.current != null) { const preferredTab = tabRecordedIdRef.current ?? recordingUiTabRef.current ?? tabIdRef.current; chrome.storage.local.set({ recordingBeepTabId: preferredTab }); recordingBeepTabIdRef.current = preferredTab; } chrome.storage.local.set({ restarting: false }); traceStep("recordingStarted"); setStartFlowOutcome("ok"); // This cannot be triggered from here because the user might not have the page focused //chrome.runtime.sendMessage({ type: "start-recording" }); }, []); const restartRecording = useCallback(() => { // Restart transitions recording true -> false briefly; suppress the normal // "stop" beep because this is not a final stop/save action. suppressStopBeepRef.current = true; const sourceTabId = tabIdRef.current ?? activeTabRef.current ?? null; chrome.storage.local.set({ restarting: true }); setTimeout(() => { chrome.runtime.sendMessage({ type: "discard-backup-restart" }); chrome.runtime.sendMessage({ type: "handle-restart", sourceTabId }); if (contentStateRef.current.alarm) { setTimer(contentStateRef.current.alarmTime); } else { setTimer(0); } setContentState((prevContentState) => ({ ...prevContentState, recording: false, time: 0, paused: false, })); }, 100); }, []); useEffect(() => { (async () => { let realSupport = false; if ("VideoEncoder" in window) { try { const support = await VideoEncoder.isConfigSupported({ codec: "vp8", width: 16, height: 16, }); realSupport = support.supported; } catch {} } chrome.storage.local.set({ realWebCodecsSupport: realSupport }); })(); }, []); const stopRecording = useCallback(() => { chrome.runtime.sendMessage({ type: "clear-recording-alarm" }); chrome.storage.local.set({ restarting: false, tabRecordedID: null, drawingMode: false, blurMode: false, cursorMode: "none", cursorEffects: [], }); setContentState((prevContentState) => ({ ...prevContentState, recording: false, paused: false, timeWarning: false, showExtension: false, blurMode: false, showPopup: true, pendingRecording: false, tabCaptureFrame: false, pipEnded: false, time: 0, timer: 0, preparingRecording: false, drawingMode: false, blurMode: false, toolbarMode: "", cursorMode: "none", cursorEffects: [], })); // Remove blur from all elements const elements = document.querySelectorAll(".screenity-blur"); elements.forEach((element) => { element.classList.remove("screenity-blur"); }); setTimer(0); chrome.runtime.sendMessage( { type: "stop-recording-tab", reason: "content-toolbar-stop" }, (res) => { if (!res || res.ok !== true) { console.warn("Stop command not acknowledged, retrying…"); setTimeout(() => { chrome.runtime.sendMessage({ type: "stop-recording-tab", reason: "content-toolbar-stop-retry", }); }, 200); } }, ); }); const pauseRecording = useCallback((dismiss) => { if (contentStateRef.current?.paused) return; chrome.runtime.sendMessage({ type: "pause-recording-tab" }); setTimeout(() => { setContentState((prev) => ({ ...prev, paused: true, })); if (!dismiss) { contentStateRef.current.openToast( chrome.i18n.getMessage("pausedRecordingToast"), function () {}, ); } }, 100); }); const resumeRecording = useCallback(() => { if (!contentStateRef.current?.paused) return; chrome.runtime.sendMessage({ type: "resume-recording-tab" }); setContentState((prev) => ({ ...prev, paused: false, })); }); const dismissRecording = useCallback(() => { setStartFlowOutcome("cancelled"); suppressStopBeepRef.current = true; chrome.storage.local.set({ restarting: false }); chrome.runtime.sendMessage({ type: "dismiss-recording-tab" }); setContentState((prevContentState) => ({ ...prevContentState, recording: false, paused: false, showExtension: false, timeWarning: false, showPopup: true, time: 0, timer: 0, tabCaptureFrame: false, pendingRecording: false, preparingRecording: false, pipEnded: false, blurMode: false, drawingMode: false, })); // Remove blur from all elements const elements = document.querySelectorAll(".screenity-blur"); elements.forEach((element) => { element.classList.remove("screenity-blur"); }); setTimer(0); }); const checkChromeCapturePermissions = useCallback(async () => { const permissions = ["desktopCapture", "alarms", "offscreen"]; // Only request clipboardWrite if the user is logged in and subscribed if ( contentStateRef.current?.isLoggedIn && contentStateRef.current?.isSubscribed ) { permissions.push("clipboardWrite"); } const containsPromise = new Promise((resolve) => { chrome.permissions.contains({ permissions }, (result) => { resolve(result); }); }); const result = await containsPromise; if (!result) { const requestPromise = new Promise((resolve) => { chrome.permissions.request({ permissions }, (granted) => { resolve(granted); }); }); const granted = await requestPromise; if (!granted) { return false; } else { chrome.runtime.sendMessage({ type: "add-alarm-listener" }); return true; } } else { return true; } }, []); const checkChromeCapturePermissionsSW = useCallback(async () => { const { isLoggedIn, isSubscribed } = contentStateRef.current; return new Promise((resolve) => { chrome.runtime.sendMessage( { type: "check-capture-permissions", isLoggedIn, isSubscribed, }, (response) => { resolve(response.status === "ok"); }, ); }); }, []); const startStreaming = useCallback(async () => { // Init start-flow trace for this attempt const attemptId = `ra-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`; await initStartFlowTrace(attemptId, { recordingType: contentStateRef.current.recordingType, isPro: Boolean( contentStateRef.current.isLoggedIn && contentStateRef.current.isSubscribed && CLOUD_FEATURES_ENABLED, ), countdown: Boolean(contentStateRef.current.countdown), }); traceStep("startStreaming"); // Overlay becomes non-blocking while pending. Popup stays open until the // recorder tab takes focus. Also clear countdownCancelled so a stale // cancellation from a previous attempt can't block this one. // Write to storage immediately (not via useEffect) so the tab activation // listener sees pendingRecording=true before the recorder tab opens. chrome.storage.local.set({ pendingRecording: true }); setContentState((prev) => ({ ...prev, pendingRecording: true, countdownCancelled: false, })); let permission = false; if ( contentStateRef.current?.isLoggedIn && contentStateRef.current?.isSubscribed && CLOUD_FEATURES_ENABLED ) { const storageResponse = await chrome.runtime.sendMessage({ type: "check-storage-quota", }); const { success, canUpload, error } = storageResponse; if (success && canUpload === false) { contentStateRef.current.openModal( chrome.i18n.getMessage("storageLimitReachedTitle"), chrome.i18n.getMessage("storageLimitReachedDescription"), chrome.i18n.getMessage("manageStorageButtonLabel"), chrome.i18n.getMessage("closeModalLabel"), () => { window.open(process.env.SCREENITY_APP_BASE, "_blank"); }, () => {}, ); } else if (!success) { const isSubError = error === "Subscription inactive"; const isAuthError = error === "Not authenticated"; // Update content state if subscription is inactive if (isSubError) { contentStateRef.current.setContentState((prev) => ({ ...prev, isSubscribed: false, })); } else if (isAuthError) { contentStateRef.current.setContentState((prev) => ({ ...prev, isSubscribed: false, isLoggedIn: false, screenityUser: null, proSubscription: null, })); } const message = isAuthError ? chrome.i18n.getMessage("storageCheckFailAuthDescription") : chrome.i18n.getMessage("storageCheckFailDescription"); contentStateRef.current.openModal( chrome.i18n.getMessage("storageCheckFailTitle"), message, chrome.i18n.getMessage("retryButtonLabel"), chrome.i18n.getMessage("closeModalLabel"), async () => { window.location.reload(); // or retry logic }, () => {}, ); } if (!success || (success && canUpload === false)) { setStartFlowOutcome("error", { error: canUpload === false ? "storage-limit" : (error || "quota-check-failed"), }); setContentState((prev) => ({ ...prev, pendingRecording: false, preparingRecording: false, })); return; // Stop recording setup } } // Check if in content script or extension page (Chrome) if (window.location.href.includes("chrome-extension://")) { permission = await checkChromeCapturePermissions(); } else { permission = await checkChromeCapturePermissionsSW(); } if (!permission) { contentStateRef.current.openModal( chrome.i18n.getMessage("chromePermissionsModalTitle"), chrome.i18n.getMessage("chromePermissionsModalDescription"), chrome.i18n.getMessage("chromePermissionsModalAction"), chrome.i18n.getMessage("chromePermissionsModalCancel"), async () => { await checkChromeCapturePermissionsSW(); startStreaming(); // Retry streaming }, () => {}, null, chrome.i18n.getMessage("learnMoreDot"), URL, true, ); setStartFlowOutcome("cancelled", { error: "permission-denied" }); setContentState((prevContentState) => ({ ...prevContentState, pendingRecording: false, preparingRecording: false, })); return; } const data = await chrome.runtime.sendMessage({ type: "available-memory" }); if ( data.quota < 524288000 && !contentStateRef.current.isLoggedIn && !contentStateRef.current.isSubscribed ) { if (typeof contentStateRef.current.openModal === "function") { let clear = null; let clearAction = () => {}; const locale = chrome.i18n.getMessage("@@ui_locale"); let helpURL = "https://help.screenity.io/troubleshooting/9Jy5RGjNrBB42hqUdREQ7W/what-does-%E2%80%9Cmemory-limit-reached%E2%80%9D-mean-when-recording/8WkwHbt3puuXunYqQnyPcb"; if (!locale.includes("en")) { helpURL = "https://translate.google.com/translate?sl=en&tl=" + locale + "&u=https://help.screenity.io/troubleshooting/9Jy5RGjNrBB42hqUdREQ7W/what-does-%E2%80%9Cmemory-limit-reached%E2%80%9D-mean-when-recording/8WkwHbt3puuXunYqQnyPcb"; } const response = await chrome.runtime.sendMessage({ type: "check-restore", }); if (response.restore) { clear = chrome.i18n.getMessage("clearSpaceButton"); clearAction = () => { chrome.runtime.sendMessage({ type: "clear-recordings" }); }; } contentStateRef.current.openModal( chrome.i18n.getMessage("notEnoughSpaceTitle"), chrome.i18n.getMessage("notEnoughSpaceDescription"), clear, chrome.i18n.getMessage("permissionsModalDismiss"), clearAction, () => {}, null, chrome.i18n.getMessage("learnMoreDot"), helpURL, false, // colorSafe chrome.i18n.getMessage("getHelpButton"), () => { chrome.runtime.sendMessage({ type: "report-error", errorCode: "REC_RUN_MEMORY", source: "not-enough-space", }); }, ); } setStartFlowOutcome("error", { error: "insufficient-memory" }); setContentState((prevContentState) => ({ ...prevContentState, pendingRecording: false, preparingRecording: false, })); return; } chrome.storage.local.set({ tabRecordedID: null, }); if ( contentStateRef.current.recordingType === "region" && contentStateRef.current.cropTarget ) { contentStateRef.current.regionCaptureRef.contentWindow.postMessage( { type: "crop-target", target: contentStateRef.current.cropTarget, width: contentStateRef.current.regionWidth, height: contentStateRef.current.regionHeight, }, "*", ); } setContentState((prevContentState) => ({ ...prevContentState, showOnboardingArrow: false, })); if ( !contentStateRef.current.micActive && contentStateRef.current.askMicrophone ) { contentStateRef.current.openModal( chrome.i18n.getMessage("micMutedModalTitle"), chrome.i18n.getMessage("micMutedModalDescription"), chrome.i18n.getMessage("micMutedModalAction"), chrome.i18n.getMessage("micMutedModalCancel"), () => { chrome.runtime.sendMessage({ type: "desktop-capture", region: contentStateRef.current.recordingType === "region" ? true : false, customRegion: contentStateRef.current.customRegion, offscreenRecording: contentStateRef.current.offscreenRecording, camera: contentStateRef.current.recordingType === "camera" ? true : false, }); setContentState((prevContentState) => ({ ...prevContentState, surface: "default", pipEnded: false, })); }, () => {}, false, false, false, false, chrome.i18n.getMessage("noShowAgain"), () => { setContentState((prevContentState) => ({ ...prevContentState, askMicrophone: false, })); chrome.storage.local.set({ askMicrophone: false }); }, ); } else { chrome.runtime.sendMessage({ type: "desktop-capture", region: contentStateRef.current.recordingType === "region" ? true : false, customRegion: contentStateRef.current.customRegion, offscreenRecording: contentStateRef.current.offscreenRecording, camera: contentStateRef.current.recordingType === "camera" ? true : false, }); traceStep("desktopCaptureSent"); setContentState((prevContentState) => ({ ...prevContentState, surface: "default", pipEnded: false, })); } }, [contentStateRef]); const tryRestartRecording = useCallback(() => { if (!contentStateRef.current.paused) { contentStateRef.current.pauseRecording(); } contentStateRef.current.openModal( chrome.i18n.getMessage("restartModalTitle"), chrome.i18n.getMessage("restartModalDescription"), chrome.i18n.getMessage("restartModalRestart"), chrome.i18n.getMessage("restartModalResume"), () => { contentStateRef.current.restartRecording(); }, () => { contentStateRef.current.resumeRecording(); }, ); }, []); const tryDismissRecording = useCallback(() => { if (contentStateRef.current.askDismiss) { if (!contentStateRef.current.paused) { contentStateRef.current.pauseRecording(true); } contentStateRef.current.openModal( chrome.i18n.getMessage("discardModalTitle"), chrome.i18n.getMessage("discardModalDescription"), chrome.i18n.getMessage("discardModalDiscard"), chrome.i18n.getMessage("discardModalResume"), () => { contentStateRef.current.dismissRecording(); }, () => { contentStateRef.current.resumeRecording(); }, ); } else { contentStateRef.current.dismissRecording(); } }, [contentStateRef]); const handleDevicePermissions = (data) => { if (data && data != undefined && data.success) { // I need to convert to a regular array of objects const audioInput = data.audioinput; const videoInput = data.videoinput; const cameraPermission = data.cameraPermission; const microphonePermission = data.microphonePermission; setContentState((prevContentState) => ({ ...prevContentState, audioInput: audioInput, videoInput: videoInput, cameraPermission: cameraPermission, microphonePermission: microphonePermission, })); const audioInputById = Array.isArray(audioInput) ? Object.fromEntries( audioInput.map((device) => [device.deviceId, device.label]), ) : {}; const videoInputById = Array.isArray(videoInput) ? Object.fromEntries( videoInput.map((device) => [device.deviceId, device.label]), ) : {}; const defaultAudioInputLabel = audioInputById[contentStateRef.current.defaultAudioInput] || ""; const defaultVideoInputLabel = videoInputById[contentStateRef.current.defaultVideoInput] || ""; setContentState((prevContentState) => ({ ...prevContentState, defaultAudioInputLabel: defaultAudioInputLabel || prevContentState.defaultAudioInputLabel, defaultVideoInputLabel: defaultVideoInputLabel || prevContentState.defaultVideoInputLabel, })); chrome.storage.local.set({ defaultAudioInputLabel: defaultAudioInputLabel || contentStateRef.current.defaultAudioInputLabel, defaultVideoInputLabel: defaultVideoInputLabel || contentStateRef.current.defaultVideoInputLabel, }); chrome.runtime.sendMessage({ type: "switch-camera", id: contentStateRef.current.defaultVideoInput, }); // Check if first time setting devices if (!contentStateRef.current.setDevices) { // Set default devices // Check if audio devices exist if (audioInput.length > 0) { setContentState((prevContentState) => ({ ...prevContentState, defaultAudioInput: audioInput[0].deviceId, defaultAudioInputLabel: audioInput[0].label || "", micActive: true, })); chrome.storage.local.set({ defaultAudioInput: audioInput[0].deviceId, defaultAudioInputLabel: audioInput[0].label || "", micActive: true, }); } if (videoInput.length > 0) { setContentState((prevContentState) => ({ ...prevContentState, defaultVideoInput: videoInput[0].deviceId, defaultVideoInputLabel: videoInput[0].label || "", cameraActive: true, })); chrome.storage.local.set({ defaultVideoInput: videoInput[0].deviceId, defaultVideoInputLabel: videoInput[0].label || "", cameraActive: true, }); } if (audioInput.length > 0 || videoInput.length > 0) { setContentState((prevContentState) => ({ ...prevContentState, setDevices: true, })); chrome.storage.local.set({ setDevices: true, }); } } } else { setContentState((prevContentState) => ({ ...prevContentState, cameraPermission: false, microphonePermission: false, })); if (contentStateRef.current.askForPermissions) { contentStateRef.current.openModal( chrome.i18n.getMessage("permissionsModalTitle"), chrome.i18n.getMessage("permissionsModalDescription"), chrome.i18n.getMessage("permissionsModalDismiss"), chrome.i18n.getMessage("permissionsModalNoShowAgain"), () => {}, () => { noMorePermissions(); }, chrome.runtime.getURL("assets/helper/permissions.webp"), chrome.i18n.getMessage("learnMoreDot"), URL2, true, false, ); } } }; const noMorePermissions = useCallback(() => { setContentState((prevContentState) => ({ ...prevContentState, askForPermissions: false, })); chrome.storage.local.set({ askForPermissions: false }); }); useEffect(() => { const handleMessage = (event) => { if (event.data.type === "screenity-permissions") { handleDevicePermissions(event.data); } else if (event.data.type === "screenity-permissions-loaded") { setContentState((prevContentState) => ({ ...prevContentState, permissionsLoaded: true, })); } }; window.addEventListener("message", handleMessage); return () => { window.removeEventListener("message", handleMessage); }; }, []); // These settings are available throughout the Content const [contentState, setContentStateInternal] = useState({ color: "#4597F7", strokeWidth: 2, drawingMode: false, tool: "pen", undoStack: [], redoStack: [], canvas: null, swatch: 1, time: 0, timer: 0, processingProgress: 0, recording: false, startRecording: startRecording, restartRecording: restartRecording, stopRecording: stopRecording, pauseRecording: pauseRecording, resumeRecording: resumeRecording, dismissRecording: dismissRecording, startStreaming: startStreaming, setToolbarMode: null, openModal: null, openToast: null, timeWarning: false, audioInput: [], videoInput: [], setDevices: false, defaultAudioInput: "none", defaultVideoInput: "none", defaultAudioInputLabel: "", defaultVideoInputLabel: "", cameraActive: false, micActive: false, sortBy: "newest", paused: false, toolbarPosition: { left: true, right: false, bottom: true, top: false, offsetX: 0, offsetY: 100, }, popupPosition: { left: false, right: true, top: true, bottom: false, offsetX: 0, offsetY: 0, fixed: true, }, cameraDimensions: { size: 200, x: 100, y: 100, }, cameraFlipped: false, backgroundEffect: "blur", backgroundEffectsActive: false, countdown: true, showExtension: false, showPopup: false, blurMode: false, recordingType: "screen", customRegion: false, regionWidth: 800, surface: "default", regionHeight: 500, regionX: 100, regionY: 100, fromRegion: false, cropTarget: null, hideToolbar: false, alarm: false, alarmTime: 5 * 60, fromAlarm: false, pendingRecording: false, askForPermissions: true, cameraPermission: true, microphonePermission: true, askMicrophone: true, recordingShortcut: "⌥⇧W", recordingShortcut: "⌥⇧D", toggleDrawingModeShortcut: "", toggleBlurModeShortcut: "", toggleCursorModeShortcut: "", cursorMode: "none", cursorEffects: [], shape: "rectangle", shapeFill: false, pushToTalk: false, zoomEnabled: false, offscreenRecording: false, isAddingImage: false, pipEnded: false, tabCaptureFrame: false, showOnboardingArrow: false, offline: false, updateChrome: false, permissionsChecked: false, permissionsLoaded: false, parentRef: null, shadowRef: null, settingsOpen: false, hideUIAlerts: false, toolbarHover: false, hideUI: false, bigTab: "record", askDismiss: true, quality: "max", systemAudio: true, backup: false, backupSetup: false, openWarning: false, hasOpenedBefore: false, qualityValue: "1080p", fpsValue: "30", fastRecorderBeta: null, fastRecorderStatus: null, useWebCodecsRecorder: false, countdownActive: false, countdownCancelled: false, multiMode: false, isCountdownVisible: false, multiSceneCount: 0, preparingRecording: false, wasLoggedIn: false, hasSeenInstantModeModal: false, instantMode: false, onboarding: false, showProSplash: false, hasSubscribedBefore: false, startRecordingAfterCountdown: () => { if (!contentStateRef.current.countdownCancelled) { contentStateRef.current.startRecording(); } }, cancelCountdown: () => { setStartFlowOutcome("cancelled"); // Eagerly clear recording flags in storage so the action button never // sees a stale isRecordingActive state between now and when the // background processes dismiss-recording-tab. chrome.storage.local.set({ pendingRecording: false, recording: false, restarting: false, }); chrome.runtime.sendMessage({ type: "diag-countdown-cancelled" }).catch(() => {}); setContentState((prev) => ({ ...prev, countdownActive: false, countdownCancelled: true, isCountdownVisible: false, recording: false, showPopup: true, showExtension: true, })); // Call dismissRecording to ensure everything is properly cleaned up contentStateRef.current.dismissRecording(); }, resetCountdown: () => { setContentState((prev) => ({ ...prev, countdownCancelled: false, })); }, onCountdownFinished: () => { if (!contentStateRef.current?.countdownCancelled && isTargetTab()) { suppressStartBeepRef.current = true; playBeep(startBeepRef, "assets/sounds/beep2.mp3"); } }, }); contentStateRef.current = contentState; setContentState = (updater) => { if (typeof updater === "function") { setContentStateInternal((prevState) => { const newState = updater(prevState); contentStateRef.current = newState; return newState; }); } else { setContentStateInternal(updater); contentStateRef.current = updater; } }; const playBeep = (ref, filename) => { if (!ref.current) { ref.current = new Audio(chrome.runtime.getURL(filename)); } const audio = ref.current; audio.volume = 0.5; try { audio.currentTime = 0; } catch {} const playPromise = audio.play(); if (playPromise?.catch) { playPromise.catch((error) => { console.warn("Beep playback failed:", error); }); } }; useEffect(() => { chrome.runtime.sendMessage({ type: "sync-recording-state" }, (state) => { if (!state) return; setContentState((prev) => ({ ...prev, ...state })); setTimeout(() => { hydratedRef.current = true; prevRecordingRef.current = Boolean(state.recording); }, 0); }); }, []); useEffect(() => { if (!contentState.preparingRecording) return; let canceled = false; let pollTimer = null; const getStatus = async () => { const { fastRecorderActiveRecordingId, postStopRecordingId } = await chrome.storage.local.get([ "fastRecorderActiveRecordingId", "postStopRecordingId", ]); const recordingId = fastRecorderActiveRecordingId || postStopRecordingId || null; if (!recordingId) return null; const key = `freeFinalizeStatus:${recordingId}`; const res = await chrome.storage.local.get([key]); return res[key] || null; }; const applyStatus = (status) => { if (!status || canceled) return; const pct = typeof status.percent === "number" ? Math.round(status.percent) : 0; setContentState((prev) => ({ ...prev, processingProgress: pct, })); }; const onChanged = (changes, area) => { if (area !== "local") return; const entry = Object.keys(changes).find((k) => k.startsWith("freeFinalizeStatus:"), ); if (!entry) return; applyStatus(changes[entry].newValue); }; chrome.storage.onChanged.addListener(onChanged); (async () => { const status = await getStatus(); applyStatus(status); pollTimer = setInterval(async () => { const s = await getStatus(); applyStatus(s); }, 500); })(); return () => { canceled = true; chrome.storage.onChanged.removeListener(onChanged); if (pollTimer) clearInterval(pollTimer); }; }, [contentState.preparingRecording]); // Stuck-state detector (diagnostics only). // Writes to startFlowTrace if pending/preparing stays active too long. useEffect(() => { const PENDING_TIMEOUT_MS = 30000; const PREPARING_TIMEOUT_MS = 45000; if (contentState.recording) return; // not stuck if recording started let timer = null; let fired = false; if ( contentState.pendingRecording && !contentState.preparingRecording ) { timer = setTimeout(() => { if (fired) return; fired = true; setStartFlowOutcome("stuck", { stuck: { state: "pending", since: Date.now() - PENDING_TIMEOUT_MS, durationMs: PENDING_TIMEOUT_MS, }, }); }, PENDING_TIMEOUT_MS); } else if (contentState.preparingRecording) { timer = setTimeout(() => { if (fired) return; fired = true; setStartFlowOutcome("stuck", { stuck: { state: "preparing", since: Date.now() - PREPARING_TIMEOUT_MS, durationMs: PREPARING_TIMEOUT_MS, }, }); }, PREPARING_TIMEOUT_MS); } return () => { if (timer) clearTimeout(timer); }; }, [ contentState.pendingRecording, contentState.preparingRecording, contentState.recording, ]); useEffect(() => { const isRecording = Boolean(contentState.recording); if (!hydratedRef.current) { prevRecordingRef.current = isRecording; return; } if (prevRecordingRef.current === null) { prevRecordingRef.current = isRecording; return; } if (!isTargetTab()) { prevRecordingRef.current = isRecording; return; } if (prevRecordingRef.current === false && isRecording === true) { if ( recordingBeepTabIdRef.current != null && tabIdRef.current != null && recordingBeepTabIdRef.current !== tabIdRef.current ) { prevRecordingRef.current = isRecording; return; } const startTime = recordingStartTimeRef.current; const hasStartTime = Number.isFinite(startTime) && startTime > 0; const sessionMarker = hasStartTime ? startTime : Date.now(); const isNewSession = sessionMarker !== lastBeepStartTimeRef.current; if (!isNewSession) { prevRecordingRef.current = isRecording; return; } lastBeepStartTimeRef.current = sessionMarker; if (suppressStartBeepRef.current) { suppressStartBeepRef.current = false; } else { playBeep(startBeepRef, "assets/sounds/beep2.mp3"); } } else if (prevRecordingRef.current === true && isRecording === false) { if (suppressStopBeepRef.current) { suppressStopBeepRef.current = false; } else { playBeep(stopBeepRef, "assets/sounds/beep.mp3"); } } prevRecordingRef.current = isRecording; }, [contentState.recording, isTargetTab]); // Check Chrome version useEffect(() => { const version = navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./); const MIN_CHROME_VERSION = 109; if (version && parseInt(version[2], 10) < MIN_CHROME_VERSION) { setContentState((prevContentState) => ({ ...prevContentState, updateChrome: true, })); } }, []); useEffect(() => { if (typeof contentState.openWarning === "function") { const isMac = navigator.platform.toUpperCase().indexOf("MAC") >= 0; const warningList = [ "youtube.com", "meet.google.com", "zoom.us", "hangouts.google.com", "teams.microsoft.com", "web.whatsapp.com", "web.skype.com", "discord.com", "vimeo.com", ]; if ( !contentState.recording && isMac && warningList.some((el) => window.location.href.includes(el)) && contentState.recordingType != "region" && contentState.recordingType != "camera" ) { contentState.openWarning( chrome.i18n.getMessage("audioWarningTitle"), chrome.i18n.getMessage( "audioWarningDescription", chrome.i18n.getMessage("tabType"), ), "AudioIcon", 10000, ); // Check if url contains "playground.html" and "chrome-extension://" } else if ( window.location.href.includes("playground.html") && window.location.href.includes("chrome-extension://") && !contentState.recording ) { contentState.openWarning( chrome.i18n.getMessage("extensionNotSupportedTitle"), chrome.i18n.getMessage("extensionNotSupportedDescription"), "NotSupportedIcon", 10000, ); } } }, [ contentState.openWarning, contentState.recording, contentState.recordingType, ]); useEffect(() => { if (!contentState) return; if (typeof contentState.openModal === "function") { setContentState((prevContentState) => ({ ...prevContentState, tryRestartRecording: tryRestartRecording, tryDismissRecording: tryDismissRecording, })); } }, [contentState.openModal]); const updateTimerFromStorage = useCallback(async () => { const seq = ++timerReadSeqRef.current; const { recording, recordingStartTime, paused, pausedAt, totalPausedMs } = await chrome.storage.local.get([ "recording", "recordingStartTime", "paused", "pausedAt", "totalPausedMs", ]); if (seq !== timerReadSeqRef.current) return; if (!recording || !recordingStartTime) { setTimer(0); return; } const now = Date.now(); const basePaused = totalPausedMs || 0; const extraPaused = paused && pausedAt ? Math.max(0, now - pausedAt) : 0; const elapsedSeconds = Math.max( 0, Math.floor((now - recordingStartTime - basePaused - extraPaused) / 1000), ); if (contentStateRef.current?.alarm) { const alarmTime = contentStateRef.current?.alarmTime || 0; const nextRemaining = Math.max(0, alarmTime - elapsedSeconds); setTimer((prev) => (prev === nextRemaining ? prev : nextRemaining)); return; } setTimer((prev) => (prev === elapsedSeconds ? prev : elapsedSeconds)); }, []); useEffect(() => { updateTimerFromStorage(); const interval = setInterval(() => { updateTimerFromStorage(); }, 1000); const handleVisibility = () => { if (!document.hidden) { updateTimerFromStorage(); } }; const handleFocus = () => updateTimerFromStorage(); document.addEventListener("visibilitychange", handleVisibility); window.addEventListener("focus", handleFocus); return () => { clearInterval(interval); document.removeEventListener("visibilitychange", handleVisibility); window.removeEventListener("focus", handleFocus); }; }, [updateTimerFromStorage]); useEffect(() => { const onChanged = (changes, area) => { if (area !== "local") return; let shouldUpdateTimer = false; if (changes.activeTab) { activeTabRef.current = changes.activeTab.newValue ?? null; } if (changes.tabRecordedID) { tabRecordedIdRef.current = changes.tabRecordedID.newValue ?? null; } if (changes.recordingUiTabId) { recordingUiTabRef.current = changes.recordingUiTabId.newValue ?? null; } if (changes.recordingStartTime) { recordingStartTimeRef.current = changes.recordingStartTime.newValue ?? null; } if (changes.recordingBeepTabId) { recordingBeepTabIdRef.current = changes.recordingBeepTabId.newValue ?? null; } if ( changes.screenityToken || changes.screenityUser || changes.isSubscribed || changes.proSubscription || changes.lastAuthCheck || changes.isLoggedIn ) { // Debounce: multiple auth-related storage keys change in quick // succession (e.g. loginWithWebsite writes isLoggedIn + isSubscribed // + lastAuthCheck). Coalesce into one verify call. clearTimeout(verifyDebounceRef.current); verifyDebounceRef.current = setTimeout(verifyUser, 2000); } if (changes.recordingNow) { shouldUpdateTimer = true; } if (changes.paused) { setContentState((prev) => ({ ...prev, paused: Boolean(changes.paused.newValue), })); shouldUpdateTimer = true; } if (changes.recording) { const isRecording = Boolean(changes.recording.newValue); if (isRecording && !isTargetTab()) { setContentState((prev) => ({ ...prev, recording: false, })); return; } const shouldHideCountdown = isRecording && !contentStateRef.current?.isCountdownVisible && !contentStateRef.current?.countdownActive; setContentState((prev) => ({ ...prev, recording: isRecording, ...(shouldHideCountdown ? { countdownActive: false, isCountdownVisible: false, } : {}), })); shouldUpdateTimer = true; } if (changes.cursorEffects) { const nextEffects = normalizeCursorEffects( changes.cursorEffects.newValue, ); const fallbackMode = changes.cursorMode?.newValue || contentStateRef.current?.cursorMode || "none"; const nextMode = deriveCursorMode(nextEffects, fallbackMode); setContentState((prev) => ({ ...prev, cursorEffects: nextEffects, cursorMode: nextMode, })); } else if (changes.cursorMode) { const mode = changes.cursorMode.newValue || "none"; const fallbackEffects = mode !== "none" ? [mode] : []; setContentState((prev) => ({ ...prev, cursorMode: mode, cursorEffects: Array.isArray(prev.cursorEffects) && prev.cursorEffects.length > 0 ? prev.cursorEffects : fallbackEffects, })); } if ( changes.recordingNow || changes.paused || changes.recording || changes.recordingStartTime || changes.pausedAt || changes.totalPausedMs ) { shouldUpdateTimer = true; } if (shouldUpdateTimer) { updateTimerFromStorage(); } }; chrome.storage.onChanged.addListener(onChanged); return () => chrome.storage.onChanged.removeListener(onChanged); }, [isTargetTab, updateTimerFromStorage, verifyUser]); useEffect(() => { if (!contentState.customRegion) { setContentState((prevContentState) => ({ ...prevContentState, cropTarget: null, })); } }, [contentState.customRegion]); // Check when hiding the toolbar useEffect(() => { if (contentState.hideToolbar && contentState.hideUI) { setContentState((prevContentState) => ({ ...prevContentState, drawingMode: false, blurMode: false, })); } }, [contentState.hideToolbar, contentState.hideUI]); useEffect(() => { if (window.__screenitySetupHandlersInitialized) return; window.__screenitySetupHandlersInitialized = true; setupHandlers(); }, []); useEffect(() => { chrome.storage.local.set({ pendingRecording: contentState.pendingRecording, }); }, [contentState.pendingRecording]); // Check if user has enough RAM to record for each quality option useEffect(() => { if (!contentState.qualityValue) { const suggested = "1080p"; // safe and high enough quality setContentState((prev) => ({ ...prev, qualityValue: suggested })); chrome.storage.local.set({ qualityValue: suggested }); } }, []); useEffect(() => { if (contentState.pushToTalk) { setContentState((prevContentState) => ({ ...prevContentState, micActive: false, })); chrome.storage.local.set({ micActive: false, }); chrome.runtime.sendMessage({ type: "set-mic-active-tab", active: false, defaultAudioInput: contentState.defaultAudioInput, }); } }, [contentState.pushToTalk]); useEffect(() => { if (contentState.backgroundEffectsActive) { chrome.runtime.sendMessage({ type: "background-effects-active" }); } else { chrome.runtime.sendMessage({ type: "background-effects-inactive" }); } }, [contentState.backgroundEffectsActive]); useEffect(() => { if (contentState.backgroundEffectsActive) { chrome.runtime.sendMessage({ type: "set-background-effect", effect: contentState.backgroundEffect, }); } }, [contentState.backgroundEffect, contentState.backgroundEffectsActive]); // Programmatically add custom scrollbars useEffect(() => { if (!contentState.parentRef) return; // Check if on mac const isMac = navigator.platform.toUpperCase().indexOf("MAC") >= 0; if (isMac) return; const parentDiv = contentState.parentRef; const elements = parentDiv.querySelectorAll("*"); elements.forEach((element) => { element.classList.add("screenity-scrollbar"); }); const observer = new MutationObserver((mutationsList) => { for (const mutation of mutationsList) { if (mutation.type === "childList") { const addedNodes = Array.from(mutation.addedNodes); const removedNodes = Array.from(mutation.removedNodes); addedNodes.forEach((node) => { if (node.nodeType === Node.ELEMENT_NODE) { node.classList.add("screenity-scrollbar"); } }); removedNodes.forEach((node) => { if (node.nodeType === Node.ELEMENT_NODE) { node.classList.remove("screenity-scrollbar"); } }); } } }); observer.observe(parentDiv, { childList: true, subtree: true }); return () => { observer.disconnect(); }; }, [contentState.parentRef]); // Programmatically add custom scrollbars useEffect(() => { if (!contentState.shadowRef) return; // Check if on mac const isMac = navigator.platform.toUpperCase().indexOf("MAC") >= 0; if (isMac) return; const shadowRoot = contentState.shadowRef.shadowRoot; const elements = shadowRoot.querySelectorAll("*"); elements.forEach((element) => { element.classList.add("screenity-scrollbar"); }); const observer = new MutationObserver((mutationsList) => { for (const mutation of mutationsList) { if (mutation.type === "childList") { const addedNodes = Array.from(mutation.addedNodes); const removedNodes = Array.from(mutation.removedNodes); addedNodes.forEach((node) => { if (node.nodeType === Node.ELEMENT_NODE) { node.classList.add("screenity-scrollbar"); } }); removedNodes.forEach((node) => { if (node.nodeType === Node.ELEMENT_NODE) { node.classList.remove("screenity-scrollbar"); } }); } } }); observer.observe(shadowRoot, { childList: true, subtree: true }); return () => { observer.disconnect(); }; }, [ contentState.parentRef, contentState.shadowRef, contentState.bigTab, contentState.recordingType, ]); useEffect(() => { if (!contentState.hideUI) { setContentState((prevContentState) => ({ ...prevContentState, hideUIAlerts: false, hideToolbar: false, toolbarHover: false, })); } }, [contentState.hideUI]); useEffect(() => { updateFromStorage(); }, []); // (No storage fallback listener here; use direct messaging flow) return ( // this is the provider providing state {props.children} {process.env.SCREENITY_DEV_MODE === "true" && ( )} ); }; export default ContentState; ================================================ FILE: src/pages/Content/context/messaging/handlers.js ================================================ // src/content/handlers/recordingHandlers.js import { registerMessage, messageRouter, } from "../../../../messaging/messageRouter"; import { setContentState, contentStateRef } from "../ContentState"; import { updateFromStorage } from "../utils/updateFromStorage"; import { checkAuthStatus } from "../utils/checkAuthStatus"; import { traceStep, setStartFlowOutcome } from "../../../utils/startFlowTrace"; import JSZip from "jszip"; const CLOUD_FEATURES_ENABLED = process.env.SCREENITY_ENABLE_CLOUD_FEATURES === "true"; const getState = () => contentStateRef.current; export const setupHandlers = () => { if (window.__screenitySetupHandlersRan) return; window.__screenitySetupHandlersRan = true; let lastToggleDrawingAt = 0; const TOGGLE_DRAWING_COOLDOWN_MS = 400; let projectReadySeq = 0; const LOCAL_PLAYBACK_MAX_BYTES = 250 * 1024 * 1024; let latestLocalPlaybackOffer = null; let latestLocalPlaybackProjectId = null; let latestLocalPlaybackSceneId = null; let localPlaybackBuildPromise = null; let localPlaybackBuildOfferId = null; let activeLocalPlaybackSource = null; const TRUSTED_APP_ORIGIN = (() => { try { const appBase = process.env.SCREENITY_APP_BASE; return appBase ? new URL(appBase).origin : null; } catch { return null; } })(); const getProjectMessageTargetOrigin = () => { if (!TRUSTED_APP_ORIGIN) return null; return window.location.origin === TRUSTED_APP_ORIGIN ? TRUSTED_APP_ORIGIN : null; }; const postProjectHandoff = (payload) => { const targetOrigin = getProjectMessageTargetOrigin(); if (!targetOrigin) { console.warn( "[Screenity][Content] Ignoring project handoff on untrusted origin", { source: payload?.source || "unknown", pageOrigin: window.location.origin, trustedOrigin: TRUSTED_APP_ORIGIN, projectId: payload?.projectId || null, }, ); return false; } window.postMessage(payload, targetOrigin); // Replay once shortly after first post to reduce race conditions with late listeners. setTimeout(() => { window.postMessage( { ...payload, replay: true, replayAt: Date.now(), }, targetOrigin, ); }, 250); return true; }; const revokeActiveLocalPlaybackSource = (reason = "unknown") => { if (activeLocalPlaybackSource?.url) { URL.revokeObjectURL(activeLocalPlaybackSource.url); console.info("[Screenity][Content] Revoked local screen playback URL", { reason, offerId: activeLocalPlaybackSource.offerId || null, }); } activeLocalPlaybackSource = null; }; const markLocalPlaybackFallback = async ({ offerId, projectId, sceneId, reason, }) => { if (!offerId) return; try { await chrome.runtime.sendMessage({ type: "cloud-local-playback-mark-fallback", offerId, projectId: projectId || null, sceneId: sceneId || null, reason: reason || "unknown", }); } catch { // best effort } }; const fetchLocalPlaybackSourceFromExtension = async ({ offerId, projectId, sceneId, }) => { const offerRes = await chrome.runtime.sendMessage({ type: "cloud-local-playback-get-offer", offerId, projectId, sceneId, }); if (!offerRes?.ok || !offerRes.offer) { throw new Error("local-playback-offer-unavailable"); } const offer = offerRes.offer; if ( !offer.chunkCount || !offer.estimatedBytes || offer.estimatedBytes > LOCAL_PLAYBACK_MAX_BYTES ) { throw new Error("local-playback-offer-too-large-or-empty"); } const parts = []; for (let i = 0; i < offer.chunkCount; i += 1) { // eslint-disable-next-line no-await-in-loop const chunkRes = await chrome.runtime.sendMessage({ type: "cloud-local-playback-read-chunk", offerId: offer.offerId, projectId: offer.projectId, sceneId: offer.sceneId, index: i, }); if (!chunkRes?.ok || !chunkRes.chunk?.base64) { throw new Error(`local-playback-chunk-read-failed:${i}`); } const binary = atob(chunkRes.chunk.base64); const bytes = new Uint8Array(binary.length); for (let j = 0; j < binary.length; j += 1) { bytes[j] = binary.charCodeAt(j); } const mimeType = chunkRes.chunk.mimeType || "video/webm"; parts.push(new Blob([bytes], { type: mimeType })); } const blob = new Blob(parts, { type: parts[0]?.type || "video/webm", }); const url = URL.createObjectURL(blob); return { offer, url, size: blob.size || 0, mimeType: blob.type || "video/webm", chunkCount: parts.length, }; }; const ensureLocalPlaybackReady = async ({ projectId, sceneId, offer }) => { if (!offer?.offerId) { throw new Error("local-playback-offer-missing"); } if ( activeLocalPlaybackSource?.offerId === offer.offerId && activeLocalPlaybackSource?.url ) { return activeLocalPlaybackSource; } if ( localPlaybackBuildPromise && localPlaybackBuildOfferId === offer.offerId ) { return localPlaybackBuildPromise; } localPlaybackBuildOfferId = offer.offerId; localPlaybackBuildPromise = (async () => { const source = await fetchLocalPlaybackSourceFromExtension({ offerId: offer.offerId, projectId, sceneId, }); revokeActiveLocalPlaybackSource("new-offer"); activeLocalPlaybackSource = { offerId: source.offer.offerId, projectId: source.offer.projectId || projectId || null, sceneId: source.offer.sceneId || sceneId || null, url: source.url, mimeType: source.mimeType, size: source.size, chunkCount: source.chunkCount, expiresAt: source.offer.expiresAt || null, }; try { await chrome.runtime.sendMessage({ type: "cloud-local-playback-mark-used", offerId: source.offer.offerId, projectId: source.offer.projectId || null, sceneId: source.offer.sceneId || null, usedBy: "app-editor", }); } catch { // best effort } return activeLocalPlaybackSource; })(); try { const ready = await localPlaybackBuildPromise; return ready; } finally { localPlaybackBuildPromise = null; localPlaybackBuildOfferId = null; } }; const postLocalPlaybackHandoff = ({ projectId, sceneId, offer, readySource = null, fallbackReason = null, forceRefresh = true, }) => postProjectHandoff({ source: "update-project-ready-local-playback", projectId: projectId || null, sceneId: sceneId || null, forceRefresh, handoffAt: Date.now(), localPlayback: { available: Boolean(offer?.offerId), trackType: "screen", offerId: offer?.offerId || null, chunkCount: offer?.chunkCount || 0, estimatedBytes: offer?.estimatedBytes || 0, expiresAt: offer?.expiresAt || null, source: offer?.source || "indexeddb-screen-chunks", ready: Boolean(readySource?.url), url: readySource?.url || null, mimeType: readySource?.mimeType || null, localBytes: readySource?.size || null, fallbackReason: fallbackReason || null, }, }); const onWindowProjectMessage = (event) => { if (event.source !== window) return; if (event.origin !== TRUSTED_APP_ORIGIN) return; const data = event?.data || {}; if (data?.type !== "screenity-local-playback-request") return; const requestedProjectId = data?.projectId || null; const requestedSceneId = data?.sceneId || null; const requestId = data?.requestId || null; const offer = latestLocalPlaybackOffer; if ( !offer?.offerId || !offer.available || !requestedProjectId || requestedProjectId !== latestLocalPlaybackProjectId || (requestedSceneId && latestLocalPlaybackSceneId && requestedSceneId !== latestLocalPlaybackSceneId) ) { postProjectHandoff({ source: "screenity-local-playback-response", requestId, projectId: requestedProjectId, sceneId: requestedSceneId, localPlayback: { available: false, trackType: "screen", fallbackReason: "offer-unavailable", }, }); return; } void ensureLocalPlaybackReady({ projectId: requestedProjectId, sceneId: requestedSceneId || latestLocalPlaybackSceneId, offer, }) .then((readySource) => { console.info("[Screenity][Content] Local screen playback used", { projectId: requestedProjectId, sceneId: requestedSceneId || latestLocalPlaybackSceneId || null, offerId: offer.offerId, bytes: readySource?.size || 0, }); postProjectHandoff({ source: "screenity-local-playback-response", requestId, projectId: requestedProjectId, sceneId: requestedSceneId || latestLocalPlaybackSceneId || null, forceRefresh: true, localPlayback: { available: true, ready: true, trackType: "screen", offerId: offer.offerId, url: readySource?.url || null, mimeType: readySource?.mimeType || null, localBytes: readySource?.size || null, chunkCount: offer.chunkCount || 0, estimatedBytes: offer.estimatedBytes || 0, expiresAt: offer.expiresAt || null, source: offer.source || "indexeddb-screen-chunks", }, }); }) .catch((err) => { const reason = err?.message || "local-playback-build-failed"; console.warn( "[Screenity][Content] Local screen playback fallback", { projectId: requestedProjectId, sceneId: requestedSceneId || latestLocalPlaybackSceneId || null, offerId: offer.offerId, reason, }, ); void markLocalPlaybackFallback({ offerId: offer.offerId, projectId: requestedProjectId, sceneId: requestedSceneId || latestLocalPlaybackSceneId || null, reason, }); postProjectHandoff({ source: "screenity-local-playback-response", requestId, projectId: requestedProjectId, sceneId: requestedSceneId || latestLocalPlaybackSceneId || null, forceRefresh: true, localPlayback: { available: true, ready: false, trackType: "screen", offerId: offer.offerId, fallbackReason: reason, }, }); }); }; window.addEventListener("message", onWindowProjectMessage); window.addEventListener("beforeunload", () => { revokeActiveLocalPlaybackSource("content-beforeunload"); }); // Initialize message router if (!window.__screenityHandlersInitialized) { messageRouter(); window.__screenityHandlersInitialized = true; } // Register content message handlers registerMessage("time", () => { // Timer is driven by ContentState's storage-based tick. // Ignore external timer pushes to avoid jitter/skips. }); registerMessage("toggle-popup", () => { setContentState((prev) => ({ ...prev, showExtension: !prev.showExtension, hasOpenedBefore: true, showPopup: true, })); setTimer(0); updateFromStorage(); }); registerMessage("ready-to-record", () => { traceStep("readyToRecordReceived"); setContentState((prev) => ({ ...prev, showPopup: false, showExtension: true, preparingRecording: false, pendingRecording: true, })); const state = getState(); if (state.countdown) { // Start countdown traceStep("countdownStart"); setContentState((prev) => ({ ...prev, countdownActive: true, isCountdownVisible: true, countdownCancelled: false, })); chrome.runtime.sendMessage({ type: "diag-countdown-started" }).catch(() => {}); } else { // No countdown, start immediately. countdownCancelled is cleared // in startStreaming so it can't be stale here. state.startRecordingAfterCountdown(); } }); registerMessage("stop-recording-tab", () => { const state = getState(); if (!state.recording) return; state.stopRecording(); }); registerMessage("toggle-drawing-mode", () => { const now = Date.now(); if (now - lastToggleDrawingAt < TOGGLE_DRAWING_COOLDOWN_MS) { return; } lastToggleDrawingAt = now; if (document.hidden || !document.hasFocus()) { return; } const nextDrawingMode = !contentStateRef.current.drawingMode; setContentState((prev) => ({ ...prev, drawingMode: nextDrawingMode, blurMode: nextDrawingMode ? false : prev.blurMode, })); registerMessage("toggle-drawing-mode", () => { const now = Date.now(); if (now - lastToggleDrawingAt < TOGGLE_DRAWING_COOLDOWN_MS) return; lastToggleDrawingAt = now; if (document.hidden || !document.hasFocus()) return; const nextDrawingMode = !contentStateRef.current.drawingMode; setContentState((prev) => ({ ...prev, drawingMode: nextDrawingMode, blurMode: nextDrawingMode ? false : prev.blurMode, })); chrome.storage.local.set({ drawingMode: nextDrawingMode, ...(nextDrawingMode ? { blurMode: false } : {}), }); }); }); registerMessage("toggle-blur-mode", () => { const nextBlurMode = !contentStateRef.current.blurMode; setContentState((prev) => ({ ...prev, blurMode: nextBlurMode, drawingMode: nextBlurMode ? false : prev.drawingMode, })); chrome.storage.local.set({ blurMode: nextBlurMode, drawingMode: nextBlurMode ? false : contentStateRef.current.drawingMode, }); }); registerMessage("toggle-hide-ui", () => { const nextHideUI = !contentStateRef.current.hideUI; setContentState((prev) => ({ ...prev, hideUI: nextHideUI, hideToolbar: nextHideUI ? true : prev.hideToolbar, hideUIAlerts: nextHideUI ? true : prev.hideUIAlerts, })); chrome.storage.local.set({ hideUI: nextHideUI, ...(nextHideUI ? { hideToolbar: true, hideUIAlerts: true } : {}), }); }); registerMessage("toggle-cursor-mode", () => { const state = getState(); const nextMode = contentStateRef.current.cursorMode === "none" ? "cursor" : ""; if (state?.setToolbarMode) { state.setToolbarMode(nextMode); } else { setContentState((prev) => ({ ...prev, toolbarMode: nextMode, })); } }); registerMessage("recording-ended", async () => { const state = getState(); // Double-check with storage before resetting UI // This prevents false positives when service worker restarts with stale state const { recording, recorderSession, pendingRecording } = await chrome.storage.local.get([ "recording", "recorderSession", "pendingRecording", ]); const isActuallyRecording = recording || (recorderSession && recorderSession.status === "recording"); // Only reset if we're truly not recording if (isActuallyRecording || pendingRecording) { // Recording is actually still active - ignore this stale message console.warn( "Ignoring stale recording-ended message - recording still active", ); return; } if (!state.showPopup) { setContentState((prev) => ({ ...prev, showExtension: false, recording: false, paused: false, pipEnded: false, time: 0, timer: 0, })); } }); registerMessage("recording-error", () => { setStartFlowOutcome("error"); setContentState((prev) => ({ ...prev, pendingRecording: false, preparingRecording: false, pipEnded: false, })); }); registerMessage("start-stream", () => { const state = getState(); if ( state.preparingRecording || state.pendingRecording || state.recording || state.pipEnded ) { console.warn("[Screenity][Content] start-stream BLOCKED by guard state:", { preparingRecording: state.preparingRecording, pendingRecording: state.pendingRecording, recording: state.recording, pipEnded: state.pipEnded, }); return; } setContentState((prev) => ({ ...prev, showExtension: true, showPopup: true, })); if (state.recordingType !== "camera") { state.startStreaming(); } else if (state.defaultVideoInput !== "none" && state.cameraActive) { state.startStreaming(); } }); registerMessage("commands", (message) => { if (!message) return; const startRecordingCommand = message.commands.find( (command) => command.name === "start-recording", ); const cancelRecordingCommand = message.commands.find( (command) => command.name === "cancel-recording", ); const toggleDrawingModeCommand = message.commands.find( (command) => command.name === "toggle-drawing-mode", ); const toggleBlurModeCommand = message.commands.find( (command) => command.name === "toggle-blur-mode", ); const toggleCursorModeCommand = message.commands.find( (command) => command.name === "toggle-cursor-mode", ); setContentState((prev) => ({ ...prev, recordingShortcut: startRecordingCommand.shortcut, dismissRecordingShortcut: cancelRecordingCommand.shortcut, toggleDrawingModeShortcut: toggleDrawingModeCommand?.shortcut || "", toggleBlurModeShortcut: toggleBlurModeCommand?.shortcut || "", toggleCursorModeShortcut: toggleCursorModeCommand?.shortcut || "", })); }); registerMessage("cancel-recording", () => { const state = getState(); state.dismissRecording(); }); registerMessage("pause-recording", () => { const state = getState(); if (state.paused) { state.resumeRecording(); } else { state.pauseRecording(); } }); registerMessage("set-surface", (message) => { setContentState((prev) => ({ ...prev, surface: message.surface, })); }); registerMessage("pip-ended", () => { const state = getState(); if (state.recording || state.pendingRecording) { setContentState((prev) => ({ ...prev, pipEnded: true, })); } }); registerMessage("pip-started", () => { const state = getState(); if (state.recording || state.pendingRecording) { setContentState((prev) => ({ ...prev, pipEnded: false, })); } }); registerMessage("setup-complete", () => { setContentState((prev) => ({ ...prev, showOnboardingArrow: true, })); }); registerMessage("hide-popup-recording", () => { setContentState((prev) => ({ ...prev, showPopup: false, showExtension: false, })); }); registerMessage("stream-error", (message) => { const state = getState(); const errorCode = message?.errorCode || null; const errorWhy = message?.why || message?.error || null; state.openModal( chrome.i18n.getMessage("streamErrorModalTitle"), chrome.i18n.getMessage("streamErrorModalDescription"), chrome.i18n.getMessage("permissionsModalDismiss"), null, () => { state.dismissRecording(); }, () => { state.dismissRecording(); }, null, // image null, // learnMore null, // learnMoreLink false, // colorSafe chrome.i18n.getMessage("getHelpButton"), () => { chrome.runtime.sendMessage({ type: "report-error", errorCode, errorWhy, source: "stream-error", }); }, ); }); registerMessage("stream-ended-warning", (message) => { const state = getState(); // Show a toast warning but don't stop the recording // The user can decide whether to continue or stop manually if (state.openToast) { state.openToast( message.message || chrome.i18n.getMessage("streamEndedWarningToast"), () => {}, 10000, // Show for 10 seconds ); } }); registerMessage("show-toast", (message) => { const state = getState(); if (typeof state.openToast !== "function") return; state.openToast(message?.message || "", () => {}, message?.timeout || 5000); }); registerMessage("backup-error", () => { const state = getState(); state.openModal( chrome.i18n.getMessage("backupPermissionFailTitle"), chrome.i18n.getMessage("backupPermissionFailDescription"), chrome.i18n.getMessage("permissionsModalDismiss"), null, () => { state.dismissRecording(); }, () => { state.dismissRecording(); }, ); }); registerMessage("fast-recorder-hard-fail", async () => { const state = getState(); if (typeof state.openModal !== "function") return; const downloadBundle = async () => { const userAgent = navigator.userAgent; let platformInfo = {}; try { platformInfo = await chrome.runtime.sendMessage({ type: "get-platform-info", }); } catch {} const manifestInfo = chrome.runtime.getManifest().version; const fastRecorderData = await chrome.storage.local.get([ "fastRecorderBeta", "fastRecorderDecision", "fastRecorderDisabledForDevice", "fastRecorderDisabledReason", "fastRecorderDisabledDetails", "fastRecorderDisabledAt", "fastRecorderProbe", "fastRecorderValidation", "fastRecorderValidationFailed", "fastRecorderInUse", "fastRecorderActiveRecordingId", ]); const data = { userAgent: userAgent, platformInfo: platformInfo, manifestInfo: manifestInfo, defaultAudioInput: state.defaultAudioInput, defaultAudioOutput: state.defaultAudioOutput, defaultVideoInput: state.defaultVideoInput, quality: state.quality, systemAudio: state.systemAudio, audioInput: state.audioInput, audioOutput: state.audioOutput, backgroundEffectsActive: state.backgroundEffectsActive, recording: state.recording, recordingType: state.recordingType, askForPermissions: state.askForPermissions, cameraPermission: state.cameraPermission, microphonePermission: state.microphonePermission, askMicrophone: state.askMicrophone, cursorMode: state.cursorMode, zoomEnabled: state.zoomEnabled, offscreenRecording: state.offscreenRecording, updateChrome: state.updateChrome, permissionsChecked: state.permissionsChecked, permissionsLoaded: state.permissionsLoaded, hideUI: state.hideUI, alarm: state.alarm, alarmTime: state.alarmTime, surface: state.surface, blurMode: state.blurMode, fastRecorder: fastRecorderData, }; const zip = new JSZip(); zip.file("troubleshooting.json", JSON.stringify(data)); const blob = await zip.generateAsync({ type: "blob" }); const url = window.URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = "screenity-troubleshooting.zip"; a.click(); window.URL.revokeObjectURL(url); chrome.runtime.sendMessage({ type: "indexed-db-download" }); }; state.openModal( chrome.i18n.getMessage("fastRecorderFailedTitle"), chrome.i18n.getMessage("fastRecorderFailedDescription"), chrome.i18n.getMessage("downloadAnywayButton"), chrome.i18n.getMessage("cancelButton"), () => { chrome.runtime.sendMessage({ type: "open-download-mp4" }); }, () => {}, null, null, null, true, false, () => {}, ); }); registerMessage("recording-check", (message, sender) => { const state = getState(); if (!message.force) { if (!state.showExtension && !state.recording) { updateFromStorage(true, sender.id); } } else { // After navigation, PiP is always destroyed (the iframe that owned it // was torn down with the old page). Set pipEnded: true so the inline // camera overlay is visible immediately. If the camera iframe // successfully re-enters PiP later, a "pip-started" message will flip // this back to false. setContentState((prev) => ({ ...prev, showExtension: true, recording: true, pipEnded: true, })); updateFromStorage(false, sender.id); } }); registerMessage("stop-pending", () => { setStartFlowOutcome("error"); setContentState((prev) => ({ ...prev, pendingRecording: false, preparingRecording: false, pipEnded: false, })); }); registerMessage("reopen-popup-multi", (message) => { setContentState((prev) => ({ ...prev, showExtension: true, showPopup: true, })); updateFromStorage(false, message.senderId); setTimeout(() => { const state = getState(); if (state.openToast) { state.openToast(chrome.i18n.getMessage("addedToMultiToast"), () => {}); } }, 1000); }); registerMessage("open-popup-project", (message) => { setContentState((prev) => ({ ...prev, showExtension: true, showPopup: true, recordingProjectTitle: message.projectTitle, projectId: message.projectId, recordingToScene: message.recordingToScene, activeSceneId: message.activeSceneId, })); updateFromStorage(false, message.senderId); setTimeout(() => { const state = getState(); if (state.openToast) { state.openToast( chrome.i18n.getMessage("readyRecordSceneToast"), () => {}, ); } }, 1000); }); registerMessage("time-warning", () => { // Only trigger when actively recording const state = getState(); if (state.recording && !state.paused) { setContentState((prev) => ({ ...prev, timeWarning: true, })); if (state.openToast) { state.openToast( chrome.i18n.getMessage("reachingRecordingLimitToast"), () => {}, 5000, ); } } }); registerMessage("time-stopped", () => { const state = getState(); // Only trigger when actively recording if (state.recording && !state.paused) { setContentState((prev) => ({ ...prev, timeWarning: false, })); if (state.openToast) { state.openToast( chrome.i18n.getMessage("recordingLimitReachedToast"), () => {}, 5000, ); } } }); registerMessage("get-project-info", (message) => { const payload = { source: "get-project-info", requestedAt: Date.now(), }; if (activeLocalPlaybackSource?.url && latestLocalPlaybackOffer?.offerId) { payload.localPlayback = { available: true, ready: true, trackType: "screen", offerId: latestLocalPlaybackOffer.offerId, url: activeLocalPlaybackSource.url, mimeType: activeLocalPlaybackSource.mimeType || "video/webm", localBytes: activeLocalPlaybackSource.size || null, chunkCount: latestLocalPlaybackOffer.chunkCount || 0, estimatedBytes: latestLocalPlaybackOffer.estimatedBytes || 0, expiresAt: latestLocalPlaybackOffer.expiresAt || null, }; } postProjectHandoff(payload); }); registerMessage("check-auth", async (message) => { if (!CLOUD_FEATURES_ENABLED) { // Default to local user const { recording } = await chrome.storage.local.get("recording"); setContentState((prev) => ({ ...prev, isLoggedIn: false, screenityUser: null, isSubscribed: false, proSubscription: null, showExtension: true, showPopup: !recording, })); return; } const result = await checkAuthStatus(); const { recording } = await chrome.storage.local.get("recording"); setContentState((prev) => ({ ...prev, isLoggedIn: result.authenticated, screenityUser: result.user, isSubscribed: result.subscribed, proSubscription: result.proSubscription, ...(result.authenticated ? { wasLoggedIn: false } : {}), showExtension: true, showPopup: !recording, })); if (result.authenticated) { // Offscreen recording and client-side zoom are not available setContentState((prev) => ({ ...prev, offscreenRecording: false, onboarding: false, showProSplash: false, zoomEnabled: false, })); chrome.storage.local.set({ offscreenRecording: false, zoomEnabled: false, wasLoggedIn: false, }); } }); registerMessage("update-project-loading", (message, sender) => { window.postMessage( { source: "update-project-loading", multiMode: message.multiMode }, "*", ); if (!message.multiMode) { setContentState((prev) => ({ ...prev, showExtension: false, showPopup: false, })); } updateFromStorage(true, sender.id); }); registerMessage("update-project-ready", (message, sender) => { const projectId = message?.projectId || null; if (!projectId) { console.warn( "[Screenity][Content] Ignoring update-project-ready without projectId", ); return; } projectReadySeq += 1; const handoffAt = Date.now(); const handoffId = `${projectId}:${handoffAt}:${projectReadySeq}`; const localPlayback = message?.localPlayback || null; latestLocalPlaybackOffer = localPlayback?.available && localPlayback?.trackType === "screen" ? localPlayback : null; latestLocalPlaybackProjectId = latestLocalPlaybackOffer ? projectId : null; latestLocalPlaybackSceneId = latestLocalPlaybackOffer ? message.sceneId || null : null; const posted = postProjectHandoff({ source: "update-project-ready", share: message.share, newProject: message.newProject, sceneId: message.sceneId, projectId, localPlayback: localPlayback?.available && localPlayback?.trackType === "screen" ? { ...localPlayback, ready: activeLocalPlaybackSource?.offerId === localPlayback.offerId && Boolean(activeLocalPlaybackSource?.url), url: activeLocalPlaybackSource?.offerId === localPlayback.offerId ? activeLocalPlaybackSource.url : null, mimeType: activeLocalPlaybackSource?.offerId === localPlayback.offerId ? activeLocalPlaybackSource.mimeType || "video/webm" : null, localBytes: activeLocalPlaybackSource?.offerId === localPlayback.offerId ? activeLocalPlaybackSource.size || null : null, } : { available: false, trackType: "screen", }, handoffAt, handoffId, handoffSeq: projectReadySeq, forceRefresh: true, }); if (posted) { window.__screenityLastProjectReady = { projectId, sceneId: message.sceneId || null, handoffAt, handoffId, localPlaybackOfferId: localPlayback?.offerId || null, }; updateFromStorage(false, sender?.id); } const capturedOffer = latestLocalPlaybackOffer; if (posted && capturedOffer?.offerId) { const capturedSceneId = message.sceneId || null; console.info("[Screenity][Content] Local screen playback offered", { projectId, sceneId: capturedSceneId, offerId: capturedOffer.offerId, chunkCount: capturedOffer.chunkCount || 0, estimatedBytes: capturedOffer.estimatedBytes || 0, }); void ensureLocalPlaybackReady({ projectId, sceneId: capturedSceneId, offer: capturedOffer, }) .then((readySource) => { console.info("[Screenity][Content] Local screen playback ready", { projectId, sceneId: capturedSceneId, offerId: capturedOffer.offerId, bytes: readySource?.size || 0, }); postLocalPlaybackHandoff({ projectId, sceneId: capturedSceneId, offer: capturedOffer, readySource, }); }) .catch((err) => { const reason = err?.message || "local-playback-build-failed"; console.warn("[Screenity][Content] Local screen playback fallback", { projectId, sceneId: capturedSceneId, offerId: capturedOffer.offerId, reason, }); void markLocalPlaybackFallback({ offerId: capturedOffer.offerId, projectId, sceneId: capturedSceneId, reason, }); postLocalPlaybackHandoff({ projectId, sceneId: capturedSceneId, offer: capturedOffer, fallbackReason: reason, }); }); } }); registerMessage("clear-project-recording", (message) => { updateFromStorage(false, message.senderId); }); registerMessage("preparing-recording", () => { traceStep("preparingReceived"); setContentState((prev) => ({ ...prev, preparingRecording: true, showExtension: true, showPopup: false, })); }); }; ================================================ FILE: src/pages/Content/context/utils/checkAuthStatus.js ================================================ export const checkAuthStatus = async () => { return new Promise((resolve) => { chrome.runtime.sendMessage({ type: "check-auth-status" }, (response) => { if (chrome.runtime.lastError) { console.error( "❌ Error checking auth status:", chrome.runtime.lastError.message ); resolve({ authenticated: false }); } else { resolve({ authenticated: !!response?.authenticated, user: response?.user ?? null, subscribed: !!response?.subscribed, proSubscription: response?.proSubscription ?? null, cached: response?.cached ?? false, }); } }); }); }; ================================================ FILE: src/pages/Content/context/utils/checkRecording.js ================================================ import { setContentState } from "../ContentState"; export const checkRecording = async (id) => { const { recording } = await chrome.storage.local.get("recording"); const { tabRecordedID } = await chrome.storage.local.get("tabRecordedID"); if (id == null && tabRecordedID) { setContentState((prevContentState) => ({ ...prevContentState, recording: false, })); } else if (recording && tabRecordedID) { if (id != tabRecordedID) { setContentState((prevContentState) => ({ ...prevContentState, recording: false, })); } } }; ================================================ FILE: src/pages/Content/context/utils/updateFromStorage.js ================================================ import { setContentState } from "../ContentState"; import { checkRecording } from "./checkRecording"; const CURSOR_EFFECTS = ["target", "highlight", "spotlight"]; const normalizeCursorEffects = (effects) => { if (!Array.isArray(effects)) return []; return effects.filter((effect) => CURSOR_EFFECTS.includes(effect)); }; const deriveCursorMode = (effects, fallbackMode) => { if (effects.length === 0) return "none"; if (effects.length === 1) return effects[0]; if (fallbackMode && effects.includes(fallbackMode)) return fallbackMode; return effects[0] || "none"; }; export const updateFromStorage = (check = true, id = null) => { chrome.storage.local.get( [ "audioInput", "videoInput", "defaultAudioInput", "defaultVideoInput", "defaultAudioInputLabel", "defaultVideoInputLabel", "cameraDimensions", "cameraFlipped", "cameraActive", "micActive", "recording", "paused", "backgroundEffect", "backgroundEffectsActive", "toolbarPosition", "countdown", "recordingType", "customRegion", "regionWidth", "regionHeight", "regionX", "regionY", "hideToolbar", "alarm", "alarmTime", "pendingRecording", "askForPermissions", "cursorMode", "cursorEffects", "pushToTalk", "askMicrophone", "offscreenRecording", "zoomEnabled", "setDevices", "popupPosition", "surface", "hideUIAlerts", "hideUI", "bigTab", "toolbarHover", "askDismiss", "swatch", "color", "strokeWidth", "quality", "systemAudio", "backup", "backupSetup", "qualityValue", "fpsValue", "fastRecorderBeta", "fastRecorderStatus", "useWebCodecsRecorder", "multiMode", "multiSceneCount", "sortBy", "wasLoggedIn", "instantMode", "hasSeenInstantModeModal", "hasSubscribedBefore", ], (result) => { const storedEffects = normalizeCursorEffects(result.cursorEffects); const hasStoredEffects = Array.isArray(result.cursorEffects); const legacyMode = result.cursorMode !== undefined && result.cursorMode !== null ? result.cursorMode : "none"; const cursorEffects = hasStoredEffects ? storedEffects : legacyMode !== "none" ? [legacyMode] : []; const cursorMode = deriveCursorMode(cursorEffects, legacyMode); setContentState((prevContentState) => ({ ...prevContentState, audioInput: result.audioInput !== undefined && result.audioInput !== null ? result.audioInput : prevContentState.audioInput, videoInput: result.videoInput !== undefined && result.videoInput !== null ? result.videoInput : prevContentState.videoInput, defaultAudioInput: result.defaultAudioInput !== undefined && result.defaultAudioInput !== null ? result.defaultAudioInput : prevContentState.defaultAudioInput, defaultVideoInput: result.defaultVideoInput !== undefined && result.defaultVideoInput !== null ? result.defaultVideoInput : prevContentState.defaultVideoInput, defaultAudioInputLabel: result.defaultAudioInputLabel !== undefined && result.defaultAudioInputLabel !== null ? result.defaultAudioInputLabel : prevContentState.defaultAudioInputLabel, defaultVideoInputLabel: result.defaultVideoInputLabel !== undefined && result.defaultVideoInputLabel !== null ? result.defaultVideoInputLabel : prevContentState.defaultVideoInputLabel, cameraDimensions: result.cameraDimensions !== undefined && result.cameraDimensions !== null ? result.cameraDimensions : prevContentState.cameraDimensions, cameraFlipped: result.cameraFlipped !== undefined && result.cameraFlipped !== null ? result.cameraFlipped : prevContentState.cameraFlipped, cameraActive: result.cameraActive !== undefined && result.cameraActive !== null ? result.cameraActive : prevContentState.cameraActive, micActive: result.micActive !== undefined && result.micActive !== null ? result.micActive : prevContentState.micActive, backgroundEffect: result.backgroundEffect !== undefined && result.backgroundEffect !== null ? result.backgroundEffect : prevContentState.backgroundEffect, backgroundEffectsActive: result.backgroundEffectsActive !== undefined && result.backgroundEffectsActive !== null ? result.backgroundEffectsActive : prevContentState.backgroundEffectsActive, toolbarPosition: result.toolbarPosition !== undefined && result.toolbarPosition !== null ? result.toolbarPosition : prevContentState.toolbarPosition, countdown: result.countdown !== undefined && result.countdown !== null ? result.countdown : prevContentState.countdown, recording: result.recording !== undefined && result.recording !== null ? result.recording : prevContentState.recording, paused: result.paused !== undefined && result.paused !== null ? result.paused : prevContentState.paused, recordingType: result.recordingType !== undefined && result.recordingType !== null ? result.recordingType : prevContentState.recordingType, customRegion: result.customRegion !== undefined && result.customRegion !== null ? result.customRegion : prevContentState.customRegion, regionWidth: result.regionWidth !== undefined && result.regionWidth !== null ? result.regionWidth : prevContentState.regionWidth, regionHeight: result.regionHeight !== undefined && result.regionHeight !== null ? result.regionHeight : prevContentState.regionHeight, regionX: result.regionX !== undefined && result.regionX !== null ? result.regionX : prevContentState.regionX, regionY: result.regionY !== undefined && result.regionY !== null ? result.regionY : prevContentState.regionY, hideToolbar: result.hideToolbar !== undefined && result.hideToolbar !== null ? result.hideToolbar : prevContentState.hideToolbar, alarm: result.alarm !== undefined && result.alarm !== null ? result.alarm : prevContentState.alarm, alarmTime: result.alarmTime !== undefined && result.alarmTime !== null ? result.alarmTime : prevContentState.alarmTime, pendingRecording: result.pendingRecording !== undefined && result.pendingRecording !== null ? result.pendingRecording : prevContentState.pendingRecording, askForPermissions: result.askForPermissions !== undefined && result.askForPermissions !== null ? result.askForPermissions : prevContentState.askForPermissions, cursorMode: cursorMode || prevContentState.cursorMode, cursorEffects: cursorEffects.length > 0 || hasStoredEffects ? cursorEffects : prevContentState.cursorEffects, pushToTalk: result.pushToTalk !== undefined && result.pushToTalk !== null ? result.pushToTalk : prevContentState.pushToTalk, zoomEnabled: result.zoomEnabled !== undefined && result.zoomEnabled !== null ? result.zoomEnabled : prevContentState.zoomEnabled, askMicrophone: result.askMicrophone !== undefined && result.askMicrophone !== null ? result.askMicrophone : prevContentState.askMicrophone, offscreenRecording: result.offscreenRecording !== undefined && result.offscreenRecording !== null ? result.offscreenRecording : prevContentState.offscreenRecording, setDevices: result.setDevices !== undefined && result.setDevices !== null ? result.setDevices : prevContentState.setDevices, popupPosition: result.popupPosition !== undefined && result.popupPosition !== null ? result.popupPosition : prevContentState.popupPosition, surface: result.surface !== undefined && result.surface !== null ? result.surface : prevContentState.surface, hideUIAlerts: result.hideUIAlerts !== undefined && result.hideUIAlerts !== null ? result.hideUIAlerts : prevContentState.hideUIAlerts, hideUI: result.hideUI !== undefined && result.hideUI !== null ? result.hideUI : prevContentState.hideUI, bigTab: result.bigTab !== undefined && result.bigTab !== null ? result.bigTab : prevContentState.bigTab, toolbarHover: result.toolbarHover !== undefined && result.toolbarHover !== null ? result.toolbarHover : prevContentState.toolbarHover, askDismiss: result.askDismiss !== undefined && result.askDismiss !== null ? result.askDismiss : prevContentState.askDismiss, swatch: result.swatch !== undefined && result.swatch !== null ? result.swatch : prevContentState.swatch, color: result.color !== undefined && result.color !== null ? result.color : prevContentState.color, strokeWidth: result.strokeWidth !== undefined && result.strokeWidth !== null ? result.strokeWidth : prevContentState.strokeWidth, quality: result.quality !== undefined && result.quality !== null ? result.quality : prevContentState.quality, systemAudio: result.systemAudio !== undefined && result.systemAudio !== null ? result.systemAudio : prevContentState.systemAudio, backup: result.backup !== undefined && result.backup !== null ? result.backup : prevContentState.backup, backupSetup: result.backupSetup !== undefined && result.backupSetup !== null ? result.backupSetup : prevContentState.backupSetup, qualityValue: result.qualityValue !== undefined && result.qualityValue !== null ? result.qualityValue : prevContentState.qualityValue, fpsValue: result.fpsValue !== undefined && result.fpsValue !== null ? result.fpsValue : prevContentState.fpsValue, fastRecorderBeta: result.fastRecorderBeta !== undefined && result.fastRecorderBeta !== null ? result.fastRecorderBeta : prevContentState.fastRecorderBeta, fastRecorderStatus: result.fastRecorderStatus !== undefined && result.fastRecorderStatus !== null ? result.fastRecorderStatus : prevContentState.fastRecorderStatus, useWebCodecsRecorder: result.useWebCodecsRecorder !== undefined && result.useWebCodecsRecorder !== null ? result.useWebCodecsRecorder : prevContentState.useWebCodecsRecorder, multiMode: result.multiMode || false, multiSceneCount: result.multiSceneCount || 0, wasLoggedIn: result.wasLoggedIn || false, sortBy: result.sortBy || "newest", instantMode: result.instantMode || false, hasSeenInstantModeModal: result.hasSeenInstantModeModal || false, onboarding: result.onboarding || false, hasSubscribedBefore: result.hasSubscribedBefore || false, showProSplash: result.showProSplash || false, })); if (result.systemAudio === undefined || result.systemAudio === null) { chrome.storage.local.set({ systemAudio: true }); } if ( result.backgroundEffect === undefined || result.backgroundEffect === null ) { chrome.storage.local.set({ backgroundEffect: "blur" }); } if (result.backup === undefined || result.backup === null) { chrome.storage.local.set({ backup: false }); } if (result.countdown === undefined || result.countdown === null) { chrome.storage.local.set({ countdown: true }); } if (result.backupSetup === undefined || result.backupSetup === null) { chrome.storage.local.set({ backupSetup: false }); } if (!hasStoredEffects && legacyMode) { chrome.storage.local.set({ cursorEffects: cursorEffects, cursorMode: cursorMode, }); } if (result.backgroundEffectsActive) { chrome.runtime.sendMessage({ type: "backgroundEffectsActive" }); } if (check) { checkRecording(id); } if (result.alarm) { setContentState((prevContentState) => ({ ...prevContentState, time: parseFloat(result.alarmTime), timer: parseFloat(result.alarmTime), })); } else if (!result.recording) { setContentState((prevContentState) => ({ ...prevContentState, time: 0, timer: 0, })); } } ); }; ================================================ FILE: src/pages/Content/countdown/Countdown.jsx ================================================ import React, { useState, useEffect, useContext, useRef } from "react"; // Context import { contentStateContext } from "../context/ContentState"; const COUNTDOWN_TIME = 3; const DEBUG_START_FLOW = typeof window !== "undefined" ? !!window.SCREENITY_DEBUG_RECORDER : false; const Countdown = () => { const [contentState, setContentState] = useContext(contentStateContext); const [count, setCount] = useState(COUNTDOWN_TIME); const [isTransforming, setIsTransforming] = useState(false); const [isRotating, setIsRotating] = useState(false); const END_HOLD_MS = 1000; const POST_HIDE_START_DELAY_MS = 150; const HIDE_AFTER_END_MS = 0; const intervalRef = useRef(null); const activeRef = useRef(false); const cancelledRef = useRef(false); const wasVisibleRef = useRef(false); const finishRef = useRef(null); const resetRef = useRef(null); const completedRef = useRef(false); const hideTimeoutRef = useRef(null); const finishTimeoutRef = useRef(null); const startTimeoutRef = useRef(null); const startAtRef = useRef(0); const runIdRef = useRef(0); const cleanupTimers = () => { if (intervalRef.current) { clearInterval(intervalRef.current); intervalRef.current = null; } if (hideTimeoutRef.current) { clearTimeout(hideTimeoutRef.current); hideTimeoutRef.current = null; } if (finishTimeoutRef.current) { clearTimeout(finishTimeoutRef.current); finishTimeoutRef.current = null; } if (startTimeoutRef.current) { clearTimeout(startTimeoutRef.current); startTimeoutRef.current = null; } }; const logStartFlow = (event, data = {}) => { if (!DEBUG_START_FLOW) return; const payload = { ts: Date.now(), event, ...data }; console.info("[Screenity][StartFlow]", payload); try { const update = { startFlowDebug: { ...(data || {}), event, ts: payload.ts, }, }; if (event === "countdown_start") { update.countdownVisibleAt = payload.ts; } else if (event === "countdown_end") { update.countdownHiddenAt = payload.ts; } chrome.storage.local.set(update); } catch {} }; useEffect(() => { activeRef.current = contentState.countdownActive; cancelledRef.current = contentState.countdownCancelled; finishRef.current = contentState.onCountdownFinished; resetRef.current = contentState.resetCountdown; }, [ contentState.countdownActive, contentState.countdownCancelled, contentState.onCountdownFinished, contentState.resetCountdown, ]); // Handle cancellation const handleCancel = () => { if (contentState.countdownActive || contentState.isCountdownVisible) { cleanupTimers(); setCount(COUNTDOWN_TIME); setIsTransforming(false); setIsRotating(false); completedRef.current = false; logStartFlow("countdown_cancel", { visible: false }); contentState.cancelCountdown(); } }; // Countdown logic useEffect(() => { if (!contentState.countdownActive) { cleanupTimers(); completedRef.current = false; return; } cleanupTimers(); // Reset immediately at run start so a stale previous `1` cannot // short-circuit the next countdown cycle. setCount(COUNTDOWN_TIME); completedRef.current = false; runIdRef.current += 1; const runId = runIdRef.current; startAtRef.current = performance.now(); logStartFlow("countdown_start", { visible: true }); const tick = () => { if (runIdRef.current !== runId) return; if (!activeRef.current || cancelledRef.current) return; const elapsedMs = performance.now() - startAtRef.current; const remainingMs = Math.max(0, COUNTDOWN_TIME * 1000 - elapsedMs); const nextCount = Math.max(1, Math.ceil(remainingMs / 1000)); setCount(nextCount); if (remainingMs <= 0) { return; } intervalRef.current = setTimeout(tick, 100); }; intervalRef.current = setTimeout(tick, 100); return cleanupTimers; }, [contentState.countdownActive]); useEffect(() => { if (!activeRef.current || cancelledRef.current) { return; } if (count !== 1) { return; } const elapsedMs = performance.now() - startAtRef.current; // Ignore stale count=1 carried from a previous run; valid final-second // entry happens after ~2s for a 3..2..1 countdown. if (elapsedMs < (COUNTDOWN_TIME - 1) * 1000) { return; } if (completedRef.current) { return; } completedRef.current = true; if (intervalRef.current) { clearInterval(intervalRef.current); intervalRef.current = null; } if (!cancelledRef.current && activeRef.current) { finishTimeoutRef.current = setTimeout(() => { if (cancelledRef.current || !activeRef.current) { finishTimeoutRef.current = null; return; } setContentState((prev) => ({ ...prev, isCountdownVisible: false, countdownActive: false, })); const endedAt = Date.now(); const dispatchStartAfterHide = () => { if (cancelledRef.current) return; const startDispatchedAt = Date.now(); logStartFlow("countdown_end", { visible: false, endedAt, startDispatchedAt, }); chrome.storage.local.set({ countdownFinishedAt: endedAt, lastCountdownStartGate: { endedAt, countdownHiddenAt: endedAt, startDispatchedAt, overlayVisible: false, ts: startDispatchedAt, }, }); finishRef.current?.(); // Countdown-enabled flow should start from background only. // Waiting briefly after hide avoids capturing the countdown frame. startTimeoutRef.current = setTimeout(() => { if (cancelledRef.current) { startTimeoutRef.current = null; return; } chrome.runtime .sendMessage({ type: "countdown-finished", endedAt, }) .catch(() => {}); startTimeoutRef.current = null; }, POST_HIDE_START_DELAY_MS); }; // Wait for at least one paint after hidden state commit. requestAnimationFrame(() => { requestAnimationFrame(dispatchStartAfterHide); }); hideTimeoutRef.current = setTimeout(() => { hideTimeoutRef.current = null; }, HIDE_AFTER_END_MS); finishTimeoutRef.current = null; }, END_HOLD_MS); } }, [count, setContentState]); // Start animation when countdown becomes visible useEffect(() => { if (contentState.isCountdownVisible && !wasVisibleRef.current) { resetRef.current?.(); completedRef.current = false; setCount(COUNTDOWN_TIME); setIsTransforming(true); const rotateId = setTimeout(() => { if (!cancelledRef.current) { setIsRotating(true); } }, 10); const transformId = setTimeout(() => { if (!cancelledRef.current) { setIsTransforming(false); } }, (COUNTDOWN_TIME * 1000) / 2); return () => { clearTimeout(rotateId); clearTimeout(transformId); }; } if (!contentState.isCountdownVisible) { setIsRotating(false); setIsTransforming(false); } }, [contentState.isCountdownVisible]); useEffect(() => { wasVisibleRef.current = contentState.isCountdownVisible; }, [contentState.isCountdownVisible]); // Cleanup on unmount useEffect(() => { return cleanupTimers; }, []); return (
{contentState.isCountdownVisible && (
{count}
{chrome.i18n.getMessage("countdownMessage")}
)}
); }; export default Countdown; ================================================ FILE: src/pages/Content/countdown/styles/_Countdown.scss ================================================ @use "../../styles/_variables" as *; .countdown { width: 100%; height: 100%; position: absolute; top: 0px; left: 0px; z-index: $z-index-max; } .countdown-circle { width: 200px; height: 200px; display: flex; justify-content: center; align-items: center; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); z-index: 999; text-align: center; } .countdown-overlay { width: 100%; height: 100%; background: rgba(0, 0, 0, 0.5); position: absolute; top: 0px; left: 0px; z-index: 99; display: flex; justify-content: center; align-items: center; } .countdown-number { position: absolute; width: 20px; height: 60px; z-index: 9999999; left: 0px; right: 0px; top: 0px; bottom: 0px; margin: auto; font-weight: 300 !important; font-family: $font-light !important; font-size: 48px !important; line-height: 60px !important; letter-spacing: normal !important; text-transform: none !important; word-spacing: normal !important; color: $color-text-contrast; text-align: center; display: block; transition: all 0.6s ease-in-out; } .background { width: 100%; height: 100%; position: absolute; left: 0px; top: 0px; filter: url("#goo"); transform: rotate(0deg); transition: all 3s ease-in-out; } .circle { z-index: 9; position: absolute; transform: scale(0.8); top: 0px; left: 0px; right: 0px; bottom: 0px; margin: auto; border-radius: 50%; background: radial-gradient( 118.3% 119.01% at 35.44% 0%, #2baef8 23.13%, #3582f6 46.35%, #486def 74.48%, #7b9aea 100% ); width: 200px; height: 200px; transition: all 1.5s ease-in-out; } .c { width: 50px; height: 50px; z-index: 999; border-radius: 50%; position: absolute; top: 0px; right: 0px; left: 0px; bottom: 0px; margin: auto; transition: cubic-bezier(0.82, 0.1, 0.24, 0.99) 1.5s; opacity: 1; } .c2 { background: radial-gradient( 118.3% 119.01% at 35.44% 0%, #2b96f8 23.13%, #356bf6 64.58% ); transform: translate(20px, 20px); } .c3 { background: radial-gradient( 118.3% 119.01% at 35.44% 0%, #4884ca 15.3%, #2b89f8 78.83% ); transform: translate(-30px, -40px); } .c3:after { content: ""; position: absolute; width: 150px; height: 150px; filter: blur(50px); border-radius: 50%; top: 0px; right: 0px; left: 0px; bottom: 0px; margin: auto; transition: cubic-bezier(0.82, 0.1, 0.24, 0.99) 1.5s; background: #cbe8f7; z-index: -1; } .recording-countdown { pointer-events: all; } .recording-countdown .c2 { transform: translate(-15px, 15px); } .recording-countdown .c3 { transform: translate(-10px, -5px); } .countdown-info { position: absolute; left: 0px; right: 0px; margin: auto; bottom: 20px; border-radius: 30px; border: 2px solid rgba(255, 255, 255, 0.3); text-align: center; display: block; padding: 10px 20px; font-family: $font-medium; font-size: $font-size-normal; line-height: 1.4; letter-spacing: normal; text-transform: none; color: $color-text-contrast; z-index: $z-index-max; width: fit-content; } ================================================ FILE: src/pages/Content/cursor/trackClicks.js ================================================ export function startClickTracking( isRegion = false, regionWidth = 0, regionHeight = 0, regionX = 0, regionY = 0, contentStateRef = null // <- optional ) { const handleClick = async (e) => { // Skip if blur mode is active if (contentStateRef?.current?.blurMode) return; // Ignore clicks inside the toolbar if ( e.target.closest(".ToolbarRoot") || e.target.closest(".ToolbarRecordingControls") || e.target.closest(".ToolbarToggleWrap") || e.target.closest(".ToolbarPaused") || e.target.closest(".Toast") || e.target.closest("#screenity-root-container") ) { return; } const canvasWrapper = document.getElementById("canvas-wrapper-screenity"); if (canvasWrapper && canvasWrapper.contains(e.target)) { return; } const { surface, recordingWindowId, recordingType } = await chrome.storage.local.get([ "surface", "recordingWindowId", "recordingType", ]); if (recordingType === "camera") { return; } let clickX = e.clientX; let clickY = e.clientY; if (isRegion) { // Check if click is inside region bounds const inRegion = clickX >= regionX && clickX <= regionX + regionWidth && clickY >= regionY && clickY <= regionY + regionHeight; if (!inRegion) { return; } // Normalize to region-relative coordinates clickX = clickX - regionX; clickY = clickY - regionY; } chrome.runtime.sendMessage({ type: "click-event", payload: { x: clickX, y: clickY, relativeToRegion: isRegion, surface: surface || "unknown", recordingWindowId, timestamp: Date.now(), region: isRegion, isTab: recordingType === "region", }, }); }; window.addEventListener("mousedown", handleClick, true); return () => window.removeEventListener("mousedown", handleClick, true); } ================================================ FILE: src/pages/Content/images/popup/images.js ================================================ // I need to make this work for a Chrome extension, so I can't import images, instead it needs to be a string with the path to the image const URL = "chrome-extension://" + chrome.i18n.getMessage("@@extension_id") + "/assets"; const DropdownIcon = `${URL}/dropdown.svg`; const MicOnIcon = `${URL}/mic-on.svg`; const MicOffIcon = `${URL}/mic-off.svg`; const CameraOnIcon = `${URL}/camera-on.svg`; const CameraOffIcon = `${URL}/camera-off.svg`; const CheckWhiteIcon = `${URL}/check-white.svg`; const Waveform = `${URL}/waveform.svg`; const RecordTabActive = `${URL}/record-tab-active.svg`; const RecordTabInactive = `${URL}/record-tab-inactive.svg`; const VideoTabActive = `${URL}/video-tab-active.svg`; const VideoTabInactive = `${URL}/video-tab-inactive.svg`; const ScreenTabOn = `${URL}/screen-tab-on.svg`; const ScreenTabOff = `${URL}/screen-tab-off.svg`; const RegionTabOn = `${URL}/region-tab-on.svg`; const RegionTabOff = `${URL}/region-tab-off.svg`; const AudioTabOn = `${URL}/audio-tab-on.svg`; const AudioTabOff = `${URL}/audio-tab-off.svg`; const MockupTabOn = `${URL}/mockup-tab-on.svg`; const MockupTabOff = `${URL}/mockup-tab-off.svg`; // const TempLogo = `${URL}/temp-logo.png`; const TempLogo = `${URL}/new-logo.svg`; const TempFigma = `${URL}/temp/figma.webp`; const TempTwitter = `${URL}/temp/twitter.webp`; const TempDesignSystem = `${URL}/temp/designsystem.webp`; const TempMarketing = `${URL}/temp/marketing.webp`; const TempSubstack = `${URL}/temp/substack.webp`; const CopyLinkIcon = `${URL}/copy-link.svg`; const MoreActionsIcon = `${URL}/more-actions.svg`; const ProfilePic = `${URL}/pfp.png`; const HandleControl = `${URL}/canvas/handle.png`; const RotateControl = `${URL}/canvas/rotate.png`; const MiddleHandleControl = `${URL}/canvas/middle-handle.png`; const MiddleHandleControlV = `${URL}/canvas/middle-handle-v.png`; const DefaultCursor = `${URL}/cursors/default.svg`; const CameraTabIconOn = `${URL}/camera-tab-icon-on.svg`; const CameraTabIconOff = `${URL}/camera-tab-icon-off.svg`; const CameraOffBlue = `${URL}/camera-off-blue.svg`; const MicOffBlue = `${URL}/mic-off-blue.svg`; const DropdownGroup = `${URL}/dropdown-group.svg`; const PlaceholderThumb = `${URL}/placeholder-thumb.png`; const CloseWhiteIcon = `${URL}/close-white.svg`; export { DropdownIcon, MicOnIcon, MicOffIcon, CameraOnIcon, CameraOffIcon, CheckWhiteIcon, Waveform, RecordTabActive, RecordTabInactive, VideoTabActive, VideoTabInactive, ScreenTabOn, ScreenTabOff, RegionTabOn, RegionTabOff, AudioTabOn, AudioTabOff, MockupTabOn, MockupTabOff, TempLogo, TempFigma, TempTwitter, TempDesignSystem, TempMarketing, TempSubstack, CopyLinkIcon, MoreActionsIcon, ProfilePic, HandleControl, RotateControl, MiddleHandleControl, MiddleHandleControlV, DefaultCursor, CameraTabIconOn, CameraTabIconOff, CameraOffBlue, MicOffBlue, DropdownGroup, PlaceholderThumb, CloseWhiteIcon, }; ================================================ FILE: src/pages/Content/index.css ================================================ body { margin: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } code { font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace; } ================================================ FILE: src/pages/Content/index.js ================================================ import React from "react"; import ReactDOM from "react-dom/client"; import "./styles/app.scss"; import Content from "./Content"; const root = ReactDOM.createRoot(document.getElementById("root")); root.render( ); ================================================ FILE: src/pages/Content/index.jsx ================================================ import React from "react"; import { createRoot } from "react-dom/client"; import Content from "./Content"; // Check if screenity-ui already exists, if so, remove it const existingRoot = document.getElementById("screenity-ui"); if (existingRoot) { document.body.removeChild(existingRoot); } const root = document.createElement("div"); root.id = "screenity-ui"; document.body.appendChild(root); const appRoot = createRoot(root); appRoot.render(); ================================================ FILE: src/pages/Content/modal/Modal.jsx ================================================ import React, { useState, useEffect, useContext, useCallback } from "react"; import * as AlertDialog from "@radix-ui/react-alert-dialog"; // Context import { contentStateContext } from "../context/ContentState"; const Modal = (props) => { const [contentState, setContentState] = useContext(contentStateContext); const [title, setTitle] = useState("Test"); const [description, setDescription] = useState("Description here"); const [button1, setButton1] = useState("Submit"); const [button2, setButton2] = useState("Cancel"); const [trigger, setTrigger] = useState(() => {}); const [trigger2, setTrigger2] = useState(() => {}); const [showModal, setShowModal] = useState(false); const [image, setImage] = useState(null); const [learnmore, setLearnMore] = useState(null); const [learnMoreLink, setLearnMoreLink] = useState(() => {}); const [colorSafe, setColorSafe] = useState(false); const [sideButton, setSideButton] = useState(false); const [sideButtonAction, setSideButtonAction] = useState(() => {}); const openModal = useCallback( ( title, description, button1, button2, action, action2, image = null, learnMore = null, learnMoreLink = null, colorSafe = false, sideButton = false, sideButtonAction = () => {} ) => { setTitle(title); setDescription(description); setButton1(button1); setButton2(button2); setShowModal(true); setTrigger(() => action); setTrigger2(() => action2); setImage(image); setLearnMore(learnMore); setLearnMoreLink(() => learnMoreLink); setColorSafe(colorSafe); setSideButton(sideButton); setSideButtonAction(() => sideButtonAction); } ); useEffect(() => { setContentState((prevContentState) => ({ ...prevContentState, openModal: openModal, })); return () => { setContentState((prevContentState) => ({ ...prevContentState, openModal: null, })); }; }, []); return ( { setShowModal(open); }} >
{title} {description.split("\n").map((line, idx) => ( {line}
))} {learnmore && ( <> {" "} {learnmore} )}
{image && ( )}
{sideButton && ( )} {button2 && ( )} {button1 && ( )}
); }; export default Modal; ================================================ FILE: src/pages/Content/modal/styles/_Modal.scss ================================================ @use "../../styles/_variables" as *; /* reset */ button { all: unset; } .AlertDialogOverlay { background-color: rgba(0, 0, 0, 0.5); position: fixed; inset: 0; animation: overlayShow 150ms cubic-bezier(0.16, 1, 0.3, 1); z-index: 99999999999; } .AlertDialogContent { overflow: auto !important; background-color: white; border-radius: 30px; box-shadow: hsl(206 22% 7% / 35%) 0px 10px 38px -10px, hsl(206 22% 7% / 20%) 0px 10px 20px -15px; position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 90vw; max-width: 500px; max-height: 85vh; padding: 35px 25px; animation: contentShow 150ms cubic-bezier(0.16, 1, 0.3, 1); z-index: $z-index-max; } .AlertDialogContent:focus { outline: none; } .AlertDialogTitle { margin: 0; color: $color-text-primary; font-size: $font-size-normal; line-height: 1.4; font-family: $font-bold; font-weight: 700; } .AlertDialogDescription { margin-bottom: 20px; color: $color-text-secondary; font-size: $font-size-normal; line-height: 1.5; a { color: $color-primary !important; font-weight: 600 !important; text-decoration: none !important; display: inline-block; cursor: pointer; } } .Button { display: inline-flex; align-items: center; justify-content: center; border-radius: 30px; padding: 0 15px; font-size: 14px; line-height: 1; font-weight: 500; height: 35px; } .Button.blue { background-color: rgba(48, 128, 248, 0.1); color: $color-primary; &:hover { background-color: rgba(48, 128, 248, 0.15); cursor: pointer; } &:focus { box-shadow: $focus-border; } } .Button.red { background-color: rgba(247, 56, 90, 0.1); color: rgba(247, 56, 90, 1); } .Button.red:hover { background-color: rgba(247, 56, 90, 0.15); cursor: pointer; } .Button.red:focus { box-shadow: $focus-border; } .Button.grey { background: rgba(110, 118, 132, 0.1); color: $color-text-secondary; } .Button.grey:hover { background: rgba(110, 118, 132, 0.15); cursor: pointer; } .Button.grey:focus { box-shadow: $focus-border; } @keyframes overlayShow { from { opacity: 0; } to { opacity: 1; } } @keyframes contentShow { from { opacity: 0; transform: translate(-50%, -48%) scale(0.96); } to { opacity: 1; transform: translate(-50%, -50%) scale(1); } } .SideButtonModal { display: inline-flex; align-items: center; justify-content: center; border-radius: 30px; padding: 0 15px; font-size: 14px; line-height: 1; font-weight: 500; height: 35px; color: $color-text-secondary; font-family: $font-medium; &:hover { cursor: pointer; background: rgba(110, 118, 132, 0.05); } } ================================================ FILE: src/pages/Content/popup/PopupContainer.jsx ================================================ import React, { useState, useEffect, useContext, useLayoutEffect, useRef, } from "react"; import * as Tabs from "@radix-ui/react-tabs"; import { RecordTabActive, RecordTabInactive, VideoTabActive, VideoTabInactive, TempLogo, ProfilePic, } from "../images/popup/images"; import { Rnd } from "react-rnd"; import { CloseIconPopup, GrabIconPopup, HelpIconPopup, } from "../toolbar/components/SVG"; /* Component import */ import RecordingTab from "./layout/RecordingTab"; import VideosTab from "./layout/VideosTab"; // Layouts import SettingsMenu from "./layout/SettingsMenu"; import InactiveSubscription from "./layout/InactiveSubscription"; import LoggedOut from "./layout/LoggedOut"; import Welcome from "./layout/Welcome"; import { runProPopupOnboardingIfNeeded, runProCameraOnboardingIfNeeded, } from "./onboarding/proOnboarding"; // Context import { contentStateContext } from "../context/ContentState"; import { supportContextQuery } from "../../utils/buildSupportContext"; const PopupContainer = (props) => { const [contentState, setContentState] = useContext(contentStateContext); const contentStateRef = useRef(contentState); const [tab, setTab] = useState("record"); const [badge, setBadge] = useState(TempLogo); const DragRef = useRef(null); const PopupRef = useRef(null); const [elastic, setElastic] = React.useState(""); const [shake, setShake] = React.useState(""); const [dragging, setDragging] = React.useState(""); const [onboarding, setOnboarding] = useState(false); const [showProSplash, setShowProSplash] = useState(false); const [open, setOpen] = useState(false); const recordTabRef = useRef(null); const videoTabRef = useRef(null); const pillRef = useRef(null); const [URL, setURL] = useState("https://help.screenity.io/"); const isCloudBuild = process.env.SCREENITY_ENABLE_CLOUD_FEATURES === "true"; const wasCameraActiveRef = useRef(null); useEffect(() => { chrome.storage.local.get(["onboarding", "showProSplash"], (result) => { const nextOnboarding = Boolean(result.onboarding); const nextShowProSplash = Boolean(result.showProSplash); setOnboarding(nextOnboarding); setShowProSplash(nextShowProSplash); setContentState((prevContentState) => ({ ...prevContentState, onboarding: nextOnboarding, showProSplash: nextShowProSplash, })); }); }, [setContentState]); useEffect(() => { if (contentState.isLoggedIn) { setOnboarding(false); setShowProSplash(false); return; } setOnboarding(Boolean(contentState.onboarding)); setShowProSplash(Boolean(contentState.showProSplash)); }, [ contentState.isLoggedIn, contentState.onboarding, contentState.showProSplash, ]); useEffect(() => { const buildURL = async () => { const locale = chrome.i18n.getMessage("@@ui_locale"); // Default URL let baseURL = "https://help.screenity.io/"; // If logged in, switch to Tally with prefilled params if (contentState?.isLoggedIn && contentState?.screenityUser) { const { name, email } = contentState.screenityUser; const qs = await supportContextQuery({ includeRecordingState: true, source: "popup", user: { name, email }, }); baseURL = `https://tally.so/r/310MNg?extension=true&${qs}`; } // If non-English locale, wrap with Google Translate if (!locale.includes("en")) { setURL( `https://translate.google.com/translate?sl=en&tl=${locale}&u=${encodeURIComponent( baseURL )}` ); } else { setURL(baseURL); } }; buildURL(); }, [contentState]); const onValueChange = (tab) => { setTab(tab); if (contentState.isLoggedIn && contentState.isSubscribed === false) { setBadge( "data:image/svg+xml,⚠️" ); } else if (tab === "record" && !contentState.isLoggedIn) { setBadge(TempLogo); } else { const avatar = contentState?.screenityUser?.avatar; setBadge(avatar || ProfilePic); } setContentState((prevContentState) => ({ ...prevContentState, bigTab: tab, })); }; useEffect(() => { setTab(contentState.bigTab); }, []); useEffect(() => { if (contentState.isLoggedIn && contentState.isSubscribed === false) { setBadge( "data:image/svg+xml,⚠️" ); } else if (tab === "record" && !contentState.isLoggedIn) { setBadge(TempLogo); } else { const avatar = contentState?.screenityUser?.avatar; setBadge(avatar || ProfilePic); } }, [ contentState.isLoggedIn, contentState.isSubscribed, contentState.wasLoggedIn, tab, ]); const showWelcomeSplash = Boolean( isCloudBuild && !contentState.isLoggedIn && !contentState.wasLoggedIn && ( onboarding || showProSplash || contentState.onboarding || contentState.showProSplash ), ); useLayoutEffect(() => { if (!recordTabRef.current || !videoTabRef.current || !pillRef.current) return; const tabRef = tab === "record" ? recordTabRef.current : videoTabRef.current; pillRef.current.style.left = `${tabRef.offsetLeft}px`; pillRef.current.style.width = `${tabRef.getBoundingClientRect().width}px`; }, [tab]); useEffect(() => { contentStateRef.current = contentState; }, [contentState]); useLayoutEffect(() => { function setPopupPosition(e) { let xpos = DragRef.current.getDraggablePosition().x; let ypos = DragRef.current.getDraggablePosition().y; // Width and height of popup const width = PopupRef.current.getBoundingClientRect().width; const height = PopupRef.current.getBoundingClientRect().height; // Keep popup positioned relative to the bottom and right of the screen, proportionally if (xpos > window.innerWidth + 10) { xpos = window.innerWidth + 10; } if (ypos + height + 40 > window.innerHeight) { ypos = window.innerHeight - height - 40; } // Check if attached to right or bottom, if so, keep it there if (contentStateRef.current.popupPosition.fixed) { if (xpos < window.innerWidth) { xpos = window.innerWidth + 10; } } DragRef.current.updatePosition({ x: xpos, y: ypos }); } window.addEventListener("resize", setPopupPosition); setPopupPosition(); return () => window.removeEventListener("resize", setPopupPosition); }, []); const handleDragStart = (e, d) => { setDragging("ToolbarDragging"); }; const handleDrag = (e, d) => { // Width and height const width = PopupRef.current.getBoundingClientRect().width; const height = PopupRef.current.getBoundingClientRect().height; if ( d.x - 40 < width || d.x > window.innerWidth + 10 || d.y < 0 || d.y + height + 40 > window.innerHeight ) { setShake("ToolbarShake"); } else { setShake(""); } }; const handleDrop = (e, d) => { let anim = "ToolbarElastic"; if (e === null) { anim = ""; } setShake(""); setDragging(""); let xpos = d.x; let ypos = d.y; // Width and height const width = PopupRef.current.getBoundingClientRect().width; const height = PopupRef.current.getBoundingClientRect().height; // Check if popup is off screen if (d.x - 40 < width) { setElastic(anim); xpos = width + 40; } else if (d.x + 10 > window.innerWidth) { setElastic(anim); xpos = window.innerWidth + 10; } if (d.y < 0) { setElastic(anim); ypos = 0; } else if (d.y + height + 40 > window.innerHeight) { setElastic(anim); ypos = window.innerHeight - height - 40; } DragRef.current.updatePosition({ x: xpos, y: ypos }); setTimeout(() => { setElastic(""); }, 250); setContentState((prevContentState) => ({ ...prevContentState, popupPosition: { ...prevContentState.popupPosition, offsetX: xpos, offsetY: ypos, left: xpos < window.innerWidth / 2 ? true : false, right: xpos < window.innerWidth / 2 ? false : true, top: ypos < window.innerHeight / 2 ? true : false, bottom: ypos < window.innerHeight / 2 ? false : true, }, })); // Is it on the left or right, also top or bottom let left = xpos < window.innerWidth / 2 ? true : false; let right = xpos < window.innerWidth / 2 ? false : true; let top = ypos < window.innerHeight / 2 ? true : false; let bottom = ypos < window.innerHeight / 2 ? false : true; let offsetX = xpos; let offsetY = ypos; let fixed = d.x + 9 > window.innerWidth ? true : false; if (right) { offsetX = window.innerWidth - xpos; } if (bottom) { offsetY = window.innerHeight - ypos; } setContentState((prevContentState) => ({ ...prevContentState, popupPosition: { ...prevContentState.popupPosition, offsetX: offsetX, offsetY: offsetY, left: left, right: right, top: top, bottom: bottom, fixed: fixed, }, })); chrome.storage.local.set({ popupPosition: { offsetX: offsetX, offsetY: offsetY, left: left, right: right, top: top, bottom: bottom, fixed: fixed, }, }); }; useEffect(() => { let x = contentState.popupPosition.offsetX; let y = contentState.popupPosition.offsetY; if (contentState.popupPosition.bottom) { y = window.innerHeight - contentState.popupPosition.offsetY; } if (contentState.popupPosition.right) { x = window.innerWidth - contentState.popupPosition.offsetX; } DragRef.current.updatePosition({ x: x, y: y }); handleDrop(null, { x: x, y: y }); }, []); useEffect(() => { requestAnimationFrame(() => { const tabRef = contentState.bigTab === "record" ? recordTabRef.current : videoTabRef.current; if (tabRef && pillRef.current) { pillRef.current.style.left = `${tabRef.offsetLeft}px`; pillRef.current.style.width = `${ tabRef.getBoundingClientRect().width }px`; } }); }, [ contentState.isLoggedIn, contentState.bigTab, contentState.wasLoggedIn, pillRef.current, ]); useEffect(() => { const isPro = Boolean(contentState.isLoggedIn && contentState.isSubscribed); if (!isCloudBuild || !isPro) return; runProPopupOnboardingIfNeeded({ rootContext: props.shadowRef?.current?.shadowRoot || document, isPro, isLoggedIn: Boolean(contentState.isLoggedIn), popupOpen: Boolean(contentState.showPopup && contentState.showExtension), cameraEnabled: Boolean(contentState.cameraActive), pendingRecording: Boolean(contentState.pendingRecording), preparingRecording: Boolean(contentState.preparingRecording), recording: Boolean(contentState.recording), countdownActive: Boolean(contentState.countdownActive), isCountdownVisible: Boolean(contentState.isCountdownVisible), }); }, [ isCloudBuild, contentState.isLoggedIn, contentState.isSubscribed, contentState.showPopup, contentState.showExtension, contentState.recordingToScene, contentState.cameraActive, contentState.pendingRecording, contentState.preparingRecording, contentState.recording, contentState.countdownActive, contentState.isCountdownVisible, props.shadowRef, ]); useEffect(() => { const isPro = Boolean(contentState.isLoggedIn && contentState.isSubscribed); const cameraEnabled = Boolean(contentState.cameraActive); if (wasCameraActiveRef.current === null) { wasCameraActiveRef.current = cameraEnabled; return; } const becameEnabled = cameraEnabled && !wasCameraActiveRef.current; wasCameraActiveRef.current = cameraEnabled; if (!becameEnabled || !isCloudBuild || !isPro) return; runProCameraOnboardingIfNeeded({ rootContext: props.shadowRef?.current?.shadowRoot || document, isPro, isLoggedIn: Boolean(contentState.isLoggedIn), popupOpen: Boolean(contentState.showPopup && contentState.showExtension), cameraEnabled, pendingRecording: Boolean(contentState.pendingRecording), preparingRecording: Boolean(contentState.preparingRecording), recording: Boolean(contentState.recording), countdownActive: Boolean(contentState.countdownActive), isCountdownVisible: Boolean(contentState.isCountdownVisible), }); }, [ isCloudBuild, contentState.cameraActive, contentState.isLoggedIn, contentState.isSubscribed, contentState.showPopup, contentState.showExtension, contentState.pendingRecording, contentState.preparingRecording, contentState.recording, contentState.countdownActive, contentState.isCountdownVisible, props.shadowRef, ]); return (
{ window.open(URL, "_blank"); }} >
{ setContentState((prevContentState) => ({ ...prevContentState, showExtension: false, })); }} >
{contentState.isLoggedIn && contentState.isSubscribed === false ? (
⚠️
) : ( )}
{showWelcomeSplash ? ( { setOnboarding(false); setShowProSplash(false); chrome.storage.local.set({ onboarding: false, showProSplash: false, firstTimePro: false, }); setContentState((prev) => ({ ...prev, onboarding: false, showProSplash: false, })); }} isBack={showProSplash} clearBack={() => { setShowProSplash(false); setContentState((prev) => ({ ...prev, showProSplash: false, })); chrome.storage.local.set({ showProSplash: false }); }} setContentState={setContentState} /> ) : isCloudBuild && contentState.isSubscribed === false && contentState.isLoggedIn === true ? ( { const type = contentState.hasSubscribedBefore ? "handle-reactivate" : "handle-upgrade"; chrome.runtime.sendMessage({ type }); }} onDowngradeClick={async () => { chrome.runtime.sendMessage({ type: "handle-logout" }); setContentState((prev) => ({ ...prev, isLoggedIn: false, isSubscribed: false, screenityUser: null, proSubscription: null, wasLoggedIn: false, bigTab: "record", })); contentState.openToast( chrome.i18n.getMessage("loggedOutToastTitle"), () => {}, 2000 ); }} /> ) : isCloudBuild && !contentState.isLoggedIn && contentState.wasLoggedIn ? ( { // Log back in chrome.runtime.sendMessage({ type: "handle-login" }); }} onDowngradeClick={() => { chrome.storage.local.set({ wasLoggedIn: false, stayLoggedOut: true, }); setContentState((prev) => ({ ...prev, isLoggedIn: false, wasLoggedIn: false, bigTab: "record", // Ensure UI state sync })); setTab("record"); // Switch immediately requestAnimationFrame(() => { if (recordTabRef.current && pillRef.current) { const tabRef = recordTabRef.current; pillRef.current.style.left = `${tabRef.offsetLeft}px`; pillRef.current.style.width = `${ tabRef.getBoundingClientRect().width }px`; } }); }} /> ) : (
{chrome.i18n.getMessage("recordTab")}
{chrome.i18n.getMessage("videosTab")}
)}
{contentState.settingsOpen && (
{ window.open(URL, "_blank"); }} > {chrome.i18n.getMessage("helpPopup")}
)}
); }; export default PopupContainer; ================================================ FILE: src/pages/Content/popup/components/BackgroundEffects.jsx ================================================ import React, { useContext } from "react"; import * as ToggleGroup from "@radix-ui/react-toggle-group"; // Context import { contentStateContext } from "../../context/ContentState"; const BackgroundEffects = () => { const [contentState, setContentState] = React.useContext(contentStateContext); // Background images const URL = "chrome-extension://" + chrome.i18n.getMessage("@@extension_id") + "/assets/"; const images = [ URL + "backgrounds/back1.webp", URL + "backgrounds/back2.webp", URL + "backgrounds/back3.webp", URL + "backgrounds/back4.webp", URL + "backgrounds/back5.webp", URL + "backgrounds/back6.webp", ]; return (
{ if (value) { setContentState((prevContentState) => ({ ...prevContentState, backgroundEffect: value, })); chrome.storage.local.set({ backgroundEffect: value }); } }} > {chrome.i18n.getMessage("blurTypeLabel")} blur {images.map((image, index) => ( background ))}
); }; export default BackgroundEffects; ================================================ FILE: src/pages/Content/popup/components/Dropdown.jsx ================================================ import React, { useEffect, useState, useContext, useRef } from "react"; import * as Select from "@radix-ui/react-select"; import { DropdownIcon, CheckWhiteIcon, CameraOnIcon, CameraOffIcon, MicOnIcon, MicOffIcon, } from "../../images/popup/images"; // Context import { contentStateContext } from "../../context/ContentState"; const Dropdown = (props) => { const [contentState, setContentState] = useContext(contentStateContext); const [label, setLabel] = useState(chrome.i18n.getMessage("None")); const [open, setOpen] = useState(false); const cameraAnchorId = props.type === "camera" ? "pro-onboarding-camera-toggle" : undefined; const updateItems = () => { if (props.type === "camera") { if ( contentState.defaultVideoInput === "none" || !contentState.cameraActive ) { setLabel(chrome.i18n.getMessage("noCameraDropdownLabel")); } else { // Check if defaultVideoInput is in camdevices, if not set to none if ( contentState.videoInput.find( (device) => device.deviceId === contentState.defaultVideoInput ) ) { setLabel( contentState.videoInput.find( (device) => device.deviceId === contentState.defaultVideoInput ).label ); } else { setLabel(chrome.i18n.getMessage("noCameraDropdownLabel")); } } } else { if ( contentState.defaultAudioInput === "none" || (!contentState.micActive && !contentState.pushToTalk) ) { setLabel(chrome.i18n.getMessage("noMicrophoneDropdownLabel")); } else { // Check if defaultAudioInput is in micdevices, if not set to none if ( contentState.audioInput.find( (device) => device.deviceId === contentState.defaultAudioInput ) ) { setLabel( contentState.audioInput.find( (device) => device.deviceId === contentState.defaultAudioInput ).label ); } else { setLabel(chrome.i18n.getMessage("noMicrophoneDropdownLabel")); } } } }; useEffect(() => { updateItems(); }, [ contentState.defaultAudioInput, contentState.defaultVideoInput, contentState.audioInput, contentState.videoInput, contentState.cameraActive, contentState.micActive, ]); useEffect(() => { updateItems(); }, []); const toggleActive = (e) => { e.preventDefault(); e.stopPropagation(); setOpen(false); if (props.type === "camera") { if (contentState.cameraActive) { setContentState((prevContentState) => ({ ...prevContentState, cameraActive: false, })); chrome.storage.local.set({ cameraActive: false, }); setLabel(chrome.i18n.getMessage("noCameraDropdownLabel")); } else { setContentState((prevContentState) => ({ ...prevContentState, cameraActive: true, })); chrome.storage.local.set({ cameraActive: true, }); setLabel( contentState.videoInput.find( (device) => device.deviceId === contentState.defaultVideoInput ).label ); } } else { if (contentState.micActive) { setContentState((prevContentState) => ({ ...prevContentState, micActive: false, })); chrome.storage.local.set({ micActive: false, }); setLabel(chrome.i18n.getMessage("noMicrophoneDropdownLabel")); } else { setContentState((prevContentState) => ({ ...prevContentState, micActive: true, })); chrome.storage.local.set({ micActive: true, }); setLabel( contentState.audioInput.find( (device) => device.deviceId === contentState.defaultAudioInput ).label ); } } }; const clickedIcon = useRef(false); return ( { if (clickedIcon.current) return; setOpen(open); }} value={ props.type === "camera" && contentState.cameraActive ? contentState.defaultVideoInput : props.type === "camera" && !contentState.cameraActive ? "none" : props.type === "mic" && (contentState.micActive || contentState.pushToTalk) ? contentState.defaultAudioInput : props.type === "mic" && !contentState.micActive ? "none" : "none" } onValueChange={(newValue) => { if (props.type === "camera") { if (newValue === "none") { setContentState((prevContentState) => ({ ...prevContentState, cameraActive: false, })); chrome.storage.local.set({ cameraActive: false, }); setLabel(chrome.i18n.getMessage("noCameraDropdownLabel")); } else { const selectedLabel = contentState.videoInput.find( (device) => device.deviceId === newValue )?.label || ""; setContentState((prevContentState) => ({ ...prevContentState, defaultVideoInput: newValue, defaultVideoInputLabel: selectedLabel, cameraActive: true, })); chrome.storage.local.set({ defaultVideoInput: newValue, defaultVideoInputLabel: selectedLabel, cameraActive: true, }); chrome.runtime.sendMessage({ type: "switch-camera", id: newValue, }); setLabel(selectedLabel); } } else { if (newValue === "none") { setContentState((prevContentState) => ({ ...prevContentState, micActive: false, })); chrome.storage.local.set({ micActive: false, }); setLabel(chrome.i18n.getMessage("noMicrophoneDropdownLabel")); } else { const selectedLabel = contentState.audioInput.find( (device) => device.deviceId === newValue )?.label || ""; setContentState((prevContentState) => ({ ...prevContentState, defaultAudioInput: newValue, defaultAudioInputLabel: selectedLabel, micActive: true, })); chrome.storage.local.set({ defaultAudioInput: newValue, defaultAudioInputLabel: selectedLabel, micActive: true, }); setLabel(selectedLabel); } } }} > { e.stopPropagation(); e.preventDefault(); setOpen(false); clickedIcon.current = true; }} onMouseDown={(e) => { e.stopPropagation(); e.preventDefault(); setOpen(false); clickedIcon.current = true; }} onMouseUp={(e) => { clickedIcon.current = false; }} >
{ e.stopPropagation(); setOpen(false); toggleActive(e); clickedIcon.current = true; }} onMouseDown={(e) => { e.stopPropagation(); e.preventDefault(); setOpen(false); clickedIcon.current = true; }} onContextMenu={(e) => { e.preventDefault(); e.stopPropagation(); }} onMouseUp={(e) => { clickedIcon.current = false; }} > {props.type == "camera" && ( )} {props.type == "mic" && ( )}
{label}
{props.type == "camera" && (contentState.defaultVideoInput == "none" || !contentState.cameraActive) && (
{chrome.i18n.getMessage("offLabel")}
)} {props.type == "mic" && (contentState.defaultAudioInput == "none" || (!contentState.micActive && !contentState.pushToTalk)) && (
{chrome.i18n.getMessage("offLabel")}
)}
{props.type == "camera" ? chrome.i18n.getMessage("noCameraDropdownLabel") : chrome.i18n.getMessage("noMicrophoneDropdownLabel")} {props.type == "camera" && contentState.videoInput.length > 0 && ( )} {props.type == "mic" && contentState.audioInput.length > 0 && ( )} {props.type == "camera" && contentState.videoInput.map((device) => ( {device.label} ))} {props.type == "mic" && contentState.audioInput.map((device) => ( {device.label} ))}
); }; const SelectItem = React.forwardRef( ({ children, className, ...props }, forwardedRef) => { return ( {children} ); } ); export default Dropdown; ================================================ FILE: src/pages/Content/popup/components/RegionDimensions.jsx ================================================ import React, { useState, useRef, useContext, useEffect } from "react"; // Context import { contentStateContext } from "../../context/ContentState"; const RegionDimensions = () => { const [contentState, setContentState] = useContext(contentStateContext); const handleWidth = (e) => { let value = e.target.value; if (isNaN(value)) { return; } if (value < 0) { return; } setContentState((prevContentState) => ({ ...prevContentState, regionWidth: value, fromRegion: false, })); chrome.storage.local.set({ regionWidth: value, }); }; const handleHeight = (e) => { let value = e.target.value; if (isNaN(value)) { return; } if (value < 0) { return; } setContentState((prevContentState) => ({ ...prevContentState, regionHeight: value, fromRegion: false, })); chrome.storage.local.set({ regionHeight: value, }); }; return (
handleWidth(e)} onBlur={(e) => { if (e.target.value === "") { setContentState((prevContentState) => ({ ...prevContentState, regionWidth: 100, fromRegion: false, })); chrome.storage.local.set({ regionWidth: 100, }); } }} value={contentState.regionWidth} /> W
handleHeight(e)} onBlur={(e) => { if (e.target.value === "") { setContentState((prevContentState) => ({ ...prevContentState, regionHeight: 100, fromRegion: false, })); chrome.storage.local.set({ regionHeight: 100, }); } }} value={contentState.regionHeight} /> H
); }; export default RegionDimensions; ================================================ FILE: src/pages/Content/popup/components/Switch.jsx ================================================ import React, { useContext, useEffect, useState, useRef } from "react"; import * as S from "@radix-ui/react-switch"; // Components import { DropdownIcon } from "../../images/popup/images"; // Context import { contentStateContext } from "../../context/ContentState"; export const BaseSwitch = ({ value, checked, onChange }) => ( ); const Switch = (props) => { const [contentState, setContentState] = useContext(contentStateContext); const switchRef = useRef(null); const switchId = props.anchorId || props.value || props.name; const switchRowId = props.rowAnchorId || (props.anchorId ? `${props.anchorId}-row` : undefined); const [hideToolbarLabel, setHideToolbarLabel] = useState( chrome.i18n.getMessage("hideToolbarLabel") ); const [hideToolbarState, setHideToolbarState] = useState(1); useEffect(() => { // Check click outside const handleClickOutside = (event) => { if (props.name != "hideUI") return; if ( dropdownRef.current && !dropdownRef.current.contains(event.target) && !dropdownInRef.current.contains(event.target) ) { if (dropdownRef.current.querySelector(":hover")) return; if (dropdownInRef.current.querySelector(":hover")) return; // Check if any children of dropdownref are clicked also let children = dropdownRef.current.querySelectorAll("*"); for (let i = 0; i < children.length; i++) { if (children[i].contains(event.target)) return; } dropdownRef.current.classList.remove("labelDropdownActive"); } }; // Bind the event listener document.addEventListener("click", handleClickOutside); return () => { // Unbind the event listener on clean up document.removeEventListener("click", handleClickOutside); }; }, []); useEffect(() => { if (props.name === "hideUI") { if (contentState.hideUIAlerts) { setHideToolbarLabel(chrome.i18n.getMessage("hideUIAlerts")); setHideToolbarState(2); } else if (contentState.hideToolbar) { setHideToolbarLabel(chrome.i18n.getMessage("hideToolbarLabel")); setHideToolbarState(1); } else if (contentState.toolbarHover) { setHideToolbarLabel(chrome.i18n.getMessage("toolbarHoverOnly")); setHideToolbarState(3); } } }, [contentState.hideToolbar]); const dropdownRef = useRef(null); const dropdownInRef = useRef(null); return (
{props.value ? ( { if (props.disabled) return; setContentState((prevContentState) => ({ ...prevContentState, [props.value]: checked, })); chrome.storage.local.set({ [props.value]: checked }); if (props.value === "customRegion") { if (checked) { chrome.storage.local.set({ region: true, }); } } if (props.name === "hideUI") { if (hideToolbarState === 1) { setContentState((prevContentState) => ({ ...prevContentState, hideToolbar: true, hideUIAlerts: false, toolbarHover: false, })); chrome.storage.local.set({ hideToolbar: true, hideUIAlerts: false, toolbarHover: false, }); } else if (hideToolbarState === 2) { setContentState((prevContentState) => ({ ...prevContentState, hideToolbar: false, hideUIAlerts: true, toolbarHover: false, })); chrome.storage.local.set({ hideToolbar: false, hideUIAlerts: true, toolbarHover: false, }); } else if (hideToolbarState === 3) { setContentState((prevContentState) => ({ ...prevContentState, hideToolbar: false, hideUIAlerts: false, toolbarHover: true, })); chrome.storage.local.set({ hideToolbar: false, hideUIAlerts: false, toolbarHover: true, }); } } else if (props.name === "pushToTalk") { if (!checked) { setContentState((prevContentState) => ({ ...prevContentState, micActive: true, })); } } if (typeof props.onChange === "function") { props.onChange(checked); } }} > ) : ( )}
); }; export default Switch; ================================================ FILE: src/pages/Content/popup/components/TimeSetter.jsx ================================================ import React, { useContext, useEffect, useState, useRef } from "react"; // Context import { contentStateContext } from "../../context/ContentState"; const TimeSetter = () => { const [contentState, setContentState] = useContext(contentStateContext); const [hours, setHours] = useState(Math.floor(contentState.alarmTime / 3600)); const [minutes, setMinutes] = useState( Math.floor((contentState.alarmTime % 3600) / 60) ); const [seconds, setSeconds] = useState( Math.floor((contentState.alarmTime % 3600) % 60) ); useEffect(() => { // Get from contentState setHours(Math.floor(contentState.alarmTime / 3600)); setMinutes(Math.floor((contentState.alarmTime % 3600) / 60)); setSeconds(Math.floor((contentState.alarmTime % 3600) % 60)); }, []); useEffect(() => { if (!contentState.fromAlarm) return; // Set the time in seconds setHours(Math.floor(contentState.alarmTime / 3600)); setMinutes(Math.floor((contentState.alarmTime % 3600) / 60)); setSeconds(Math.floor((contentState.alarmTime % 3600) % 60)); }, [contentState.alarmTime]); useEffect(() => { if (isNaN(hours) || isNaN(minutes) || isNaN(seconds)) return; if (hours === "" || minutes === "" || seconds === "") return; setHours(parseFloat(hours)); setMinutes(parseFloat(minutes)); setSeconds(parseFloat(seconds)); // Set the time in seconds setContentState((prevContentState) => ({ ...prevContentState, alarmTime: hours * 3600 + minutes * 60 + seconds, fromAlarm: false, time: hours * 3600 + minutes * 60 + seconds, timer: hours * 3600 + minutes * 60 + seconds, })); chrome.storage.local.set({ alarmTime: hours * 3600 + minutes * 60 + seconds, }); }, [hours, minutes, seconds]); useEffect(() => { if (isNaN(hours) || isNaN(minutes) || isNaN(seconds)) return; if (contentState.alarm) { setContentState((prevContentState) => ({ ...prevContentState, time: hours * 3600 + minutes * 60 + seconds, timer: hours * 3600 + minutes * 60 + seconds, fromAlarm: false, })); } else { setContentState((prevContentState) => ({ ...prevContentState, time: 0, timer: 0, })); } }, [contentState.alarm]); const handleHours = (e) => { // Limit between 0 to 4, number only // Only 1 digit if (e.target.value.length > 1) { if (e.target.value[0] === "0") { e.target.value = parseFloat(e.target.value[1]); } else { return; } } if (isNaN(e.target.value)) { return; } if (e.target.value > 4) { e.target.value = 4; } setContentState((prevContentState) => ({ ...prevContentState, fromAlarm: true, })); setHours(e.target.value); }; const handleMinutes = (e) => { // Limit between 0 to 59, number only if (isNaN(e.target.value)) { return; } if (e.target.value > 59) { e.target.value = 59; } setContentState((prevContentState) => ({ ...prevContentState, fromAlarm: true, })); setMinutes(e.target.value); }; const handleSeconds = (e) => { // Limit between 0 to 59, number only if (isNaN(e.target.value)) { return; } if (e.target.value > 59) { e.target.value = 59; } setContentState((prevContentState) => ({ ...prevContentState, fromAlarm: true, })); setSeconds(e.target.value); }; return (
{ if (e.target.value === "") { e.target.value = 0; setMinutes(0); } }} onFocus={(e) => { e.target.select(); }} /> M
{ if (e.target.value === "") { e.target.value = 0; setSeconds(0); } }} onFocus={(e) => { e.target.select(); }} /> S
); }; export default TimeSetter; ================================================ FILE: src/pages/Content/popup/components/TooltipWrap.jsx ================================================ import React, { useEffect, useContext, useState } from "react"; import * as Tooltip from "@radix-ui/react-tooltip"; // Context import { contentStateContext } from "../../context/ContentState"; const TooltipWrap = (props) => { const [contentState, setContentState] = useContext(contentStateContext); const classname = props.name ? props.name : ""; const [override, setOverride] = useState(""); useEffect(() => { // Check if hideUI is set if (contentState.hideUI) { setOverride("override"); } else { setOverride(""); } }, [contentState.hideUI]); return (
{props.content == "" ? (
{props.children}
) : ( {props.children} {props.content} )}
); }; export default TooltipWrap; ================================================ FILE: src/pages/Content/popup/components/VideoItem.jsx ================================================ import React from "react"; import { CopyLinkIcon, MoreActionsIcon } from "../../images/popup/images"; const VideoItem = ({ title, date, thumbnail, onOpen, onCopyLink }) => { const formatRelativeTime = (timestamp) => { const now = new Date(); const then = new Date(timestamp); const diffInSeconds = Math.floor((now - then) / 1000); const thresholds = [ { unit: "year", seconds: 31536000 }, { unit: "month", seconds: 2592000 }, { unit: "week", seconds: 604800 }, { unit: "day", seconds: 86400 }, { unit: "hour", seconds: 3600 }, { unit: "minute", seconds: 60 }, { unit: "second", seconds: 1 }, ]; for (const { unit, seconds } of thresholds) { const value = Math.floor(diffInSeconds / seconds); if (value >= 1) { return `${value} ${unit}${value !== 1 ? "s" : ""} ago`; } } return "just now"; }; return (
{ if ( e.target.closest(".copy-link") || e.target.closest(".more-actions") ) { e.stopPropagation(); return; } onOpen(); }} >
{/* Need a better way to handle thumbnails - proxy from server?
*/}
{title}
{formatRelativeTime(date)}
{/* */}
); }; export default VideoItem; ================================================ FILE: src/pages/Content/popup/layout/Announcement.jsx ================================================ import React, { useState, useEffect } from "react"; const Announcement = (props) => { const [URL, setURL] = useState( "https://help.screenity.io/getting-started/77KizPC8MHVGfpKpqdux9D/what%E2%80%99s-changed-in-the-new-version-of-screenity/bDtvcwAtw9PPesQeNH4zjE" ); useEffect(() => { // check i18n locale, and set URL accordingly w/ google translate const locale = chrome.i18n.getMessage("@@ui_locale"); if (!locale.includes("en")) { setURL( "https://translate.google.com/translate?sl=en&tl=" + locale + "&u=" + URL ); } }, []); return (
{chrome.i18n.getMessage("updateAnnouncementTitle")} 👋
{chrome.i18n.getMessage("updateAnnouncementDescription")}{" "} {chrome.i18n.getMessage("updateAnnouncementLearnMore")}
{ props.setOnboarding(false); chrome.storage.local.set({ updatingFromOld: false }); }} > {chrome.i18n.getMessage("updateAnnouncementButton")}
); }; export default Announcement; ================================================ FILE: src/pages/Content/popup/layout/InactiveSubscription.jsx ================================================ import React from "react"; const formatDeletionDate = (isoDate) => { try { const date = new Date(isoDate); // Get the extension locale (fallback to 'en') const locale = chrome?.i18n?.getUILanguage?.() || "en"; // Use numeric-only format if not English const useFallback = !locale.startsWith("en"); return date.toLocaleDateString(undefined, { year: "numeric", month: useFallback ? "2-digit" : "long", day: "2-digit", }); } catch { return "unknown date"; } }; const InactiveSubscription = ({ onManageClick, onDowngradeClick, subscription, hasSubscribedBefore, }) => { const deletionDate = subscription?.deletionAt || subscription?.endsAt; const formattedDate = deletionDate ? formatDeletionDate(deletionDate) : null; return (
{hasSubscribedBefore ? chrome.i18n.getMessage("inactiveSubscriptionTitle") || "Your Pro subscription is inactive" : chrome.i18n.getMessage("noSubscriptionYetTitle") || "Unlock Pro to get started"}
{chrome.i18n.getMessage("inactiveSubscriptionDescription") || "Your Screenity Pro subscription is inactive."}
{hasSubscribedBefore ? chrome.i18n.getMessage("inactiveSubscriptionReactivation") || "Please reactivate to resume access." : chrome.i18n.getMessage("noSubscriptionYetDescription") || "Please subscribe to access Pro features."}
{formattedDate && hasSubscribedBefore && (
{chrome.i18n.getMessage("inactiveSubscriptionDeletionWarning") || "Your videos and data will be permanently deleted on "} {formattedDate}
)}
{hasSubscribedBefore ? chrome.i18n.getMessage("manageSubscriptionButton") || "Manage your subscription" : chrome.i18n.getMessage("upgradeToProButton") || "Upgrade to Pro"}
{chrome.i18n.getMessage("inactiveSubscriptionFreeVersion") || "Want to keep using the free version?"}
{chrome.i18n.getMessage("downgradeToFreeButton") || "Log out and downgrade"}
); }; export default InactiveSubscription; ================================================ FILE: src/pages/Content/popup/layout/LoggedOut.jsx ================================================ import React from "react"; const LoggedOut = ({ onManageClick, onDowngradeClick }) => { return (
{chrome.i18n.getMessage("loggedOutTitle") || "You’ve been logged out"}
{chrome.i18n.getMessage("loggedOutDescription") || "To keep your recordings synced to your account, and access premium features, you’ll need to log back in."}
{chrome.i18n.getMessage("logBackInButton") || "Log back in"}
{chrome.i18n.getMessage("loggedOutFreeVersion") || "You can keep using the extension without an account - but your recordings won’t be saved and can’t be recovered later."}
{chrome.i18n.getMessage("continueWithoutLogin") || "Continue without login"}
); }; export default LoggedOut; ================================================ FILE: src/pages/Content/popup/layout/RecordingTab.jsx ================================================ import React, { useEffect, useState, useContext } from "react"; import * as Tabs from "@radix-ui/react-tabs"; import RecordingType from "./RecordingType"; import { ScreenTabOn, ScreenTabOff, RegionTabOn, RegionTabOff, MockupTabOn, MockupTabOff, CameraTabIconOn, CameraTabIconOff, CheckWhiteIcon, CloseWhiteIcon, } from "../../images/popup/images"; import { BaseSwitch } from "../components/Switch"; import TooltipWrap from "../components/TooltipWrap"; // Context import { contentStateContext } from "../../context/ContentState"; const RecordingTab = (props) => { const [contentState, setContentState] = useContext(contentStateContext); const [tabRecordingDisabled, setTabRecordingDisabled] = useState(false); const [showModalSoon, setShowModalSoon] = useState(false); // 👈 NEW useEffect(() => { if (tabRecordingDisabled && contentState.recordingType === "region") { setContentState((prev) => ({ ...prev, recordingType: "screen", })); chrome.storage.local.set({ recordingType: "screen" }); contentState.openToast?.( chrome.i18n.getMessage("tabRecordingDisabledToast"), 4000 ); } }, [tabRecordingDisabled]); useEffect(() => { const currentUrl = window.location.href; const isBlocked = currentUrl.includes(process.env.SCREENITY_APP_BASE); setTabRecordingDisabled(isBlocked); if (isBlocked && contentState.recordingType === "region") { setContentState((prev) => ({ ...prev, recordingType: "screen", })); chrome.storage.local.set({ recordingType: "screen" }); contentState.openToast?.( chrome.i18n.getMessage("tabRecordingDisabledToast"), 4000 ); } }, [contentState.recordingType]); const onValueChange = (tab) => { setContentState((prevContentState) => ({ ...prevContentState, recordingType: tab, })); chrome.storage.local.set({ recordingType: tab }); if (tab === "camera") { chrome.runtime.sendMessage({ type: "camera-only-update" }); } else { chrome.runtime.sendMessage({ type: "screen-update" }); } }; useEffect(() => { const onKey = (e) => { if (e.key === "Escape") setShowModalSoon(false); }; if (showModalSoon) window.addEventListener("keydown", onKey); return () => window.removeEventListener("keydown", onKey); }, [showModalSoon]); return (
{contentState.recordingToScene && (
{chrome.i18n.getMessage("addingToLabel") || "Adding to: "} {contentState.recordingProjectTitle}
{(!contentState.multiMode || contentState.multiSceneCount === 0) && (
{ setContentState((prev) => ({ ...prev, projectTitle: "", projectId: null, activeSceneId: null, recordingToScene: false, multiMode: false, multiSceneCount: 0, multiProjectId: null, })); // also in chrome local storage chrome.storage.local.set({ recordingProjectTitle: "", projectId: null, activeSceneId: null, recordingToScene: false, multiMode: false, multiProjectId: null, }); // show toast contentState.openToast( chrome.i18n.getMessage("projectRecordingCancelledToast"), 3000 ); }} > Close
)}
)}
{chrome.i18n.getMessage("screenType")}
{ if (tabRecordingDisabled) { e.preventDefault(); e.stopPropagation(); } }} style={ tabRecordingDisabled ? { cursor: "not-allowed", opacity: 0.5 } : {} } >
{chrome.i18n.getMessage("tabType")}
{chrome.i18n.getMessage("cameraType")}
{ // If not logged in, show the modal instead of toggling if (!contentState.isLoggedIn) { setShowModalSoon(true); } }} >
{contentState.multiMode && contentState.multiSceneCount > 0 ? (
{ // Send finish message to background to finalize multi project chrome.runtime.sendMessage({ type: "finish-multi-recording", }); setContentState((prev) => ({ ...prev, showExtension: false, hasOpenedBefore: true, showPopup: false, })); }} style={{ cursor: "pointer", width: "28px", height: "28px", background: "#3080F8", borderRadius: "50%", display: "flex", alignItems: "center", justifyContent: "center", }} > Finish
{contentState.multiSceneCount}
) : ( { setContentState((prevContentState) => ({ ...prevContentState, multiMode: checked, })); chrome.storage.local.set({ multiMode: checked }); if (checked) { chrome.storage.local .get(["hasSeenMultiRecordingInfo"]) .then((res) => { if (!res.hasSeenMultiRecordingInfo) { contentState.openModal( chrome.i18n.getMessage( "multiRecordingModeTitle" ) || "Multi-recording mode", chrome.i18n.getMessage( "multiRecordingModeDescription" ) || "Record multiple scenes, like your screen, camera, or both, one after another. This is great for doing multiple takes, switching views, or breaking your recording into parts. When you’re done, click Finish to open the editor with all your scenes combined in one project.", "Got it", chrome.i18n.getMessage( "permissionsModalDismiss" ) || "Dismiss", () => {}, () => {}, null, "", "", true, false ); // Mark as seen chrome.storage.local.set({ hasSeenMultiRecordingInfo: true, }); } }); chrome.storage.local.set({ instantMode: false }); setContentState((prevContentState) => ({ ...prevContentState, instantMode: false, })); } }} /> )}
{contentState.multiMode && contentState.multiSceneCount > 0 ? chrome.i18n.getMessage("finishLabelMulti") || "Finish" : chrome.i18n.getMessage("multiLabel") || "Multi"}
{/*
{chrome.i18n.getMessage("MockupType")}
*/}
{showModalSoon && (
{/* 👇 Embed the video here */}
)}
); }; export default RecordingTab; ================================================ FILE: src/pages/Content/popup/layout/RecordingType.jsx ================================================ import React, { useEffect, useContext, useState, useRef } from "react"; import Dropdown from "../components/Dropdown"; import Switch from "../components/Switch"; import RegionDimensions from "../components/RegionDimensions"; import Settings from "./Settings"; import { contentStateContext } from "../../context/ContentState"; import { CameraOffBlue, MicOffBlue } from "../../images/popup/images"; import TooltipWrap from "../components/TooltipWrap"; import BackgroundEffects from "../components/BackgroundEffects"; import { AlertIcon, TimeIcon, NoInternet } from "../../toolbar/components/SVG"; const CLOUD_FEATURES_ENABLED = process.env.SCREENITY_ENABLE_CLOUD_FEATURES === "true"; const RecordingType = (props) => { const [contentState, setContentState] = useContext(contentStateContext); const [cropActive, setCropActive] = useState(false); const [time, setTime] = useState(0); const [URL, setURL] = useState( "https://help.screenity.io/getting-started/77KizPC8MHVGfpKpqdux9D/what-are-the-technical-requirements-for-using-screenity/6kdB6qru6naVD8ZLFvX3m9" ); const [URL2, setURL2] = useState( "https://help.screenity.io/troubleshooting/9Jy5RGjNrBB42hqUdREQ7W/how-to-grant-screenity-permission-to-record-your-camera-and-microphone/x6U69TnrbMjy5CQ96Er2E9" ); const buttonRef = useRef(null); const isMac = navigator.platform.toUpperCase().indexOf("MAC") >= 0; useEffect(() => { const locale = chrome.i18n.getMessage("@@ui_locale"); if (!locale.includes("en")) { setURL( `https://translate.google.com/translate?sl=en&tl=${locale}&u=https://help.screenity.io/getting-started/77KizPC8MHVGfpKpqdux9D/what-are-the-technical-requirements-for-using-screenity/6kdB6qru6naVD8ZLFvX3m9` ); setURL2( `https://translate.google.com/translate?sl=en&tl=${locale}&u=https://help.screenity.io/troubleshooting/9Jy5RGjNrBB42hqUdREQ7W/how-to-grant-screenity-permission-to-record-your-camera-and-microphone/x6U69TnrbMjy5CQ96Er2E9` ); } }, []); useEffect(() => { // Convert seconds to mm:ss let minutes = Math.floor(contentState.alarmTime / 60); let seconds = contentState.alarmTime - minutes * 60; if (seconds < 10) { seconds = "0" + seconds; } setTime(minutes + ":" + seconds); }, []); useEffect(() => { // Convert seconds to mm:ss let minutes = Math.floor(contentState.alarmTime / 60); let seconds = contentState.alarmTime - minutes * 60; if (seconds < 10) { seconds = "0" + seconds; } setTime(minutes + ":" + seconds); }, [contentState.alarmTime]); // Start recording const startStreaming = () => { contentState.startStreaming(); }; useEffect(() => { // Check if CropTarget is null if (typeof CropTarget === "undefined") { setCropActive(false); setContentState((prevContentState) => ({ ...prevContentState, customRegion: false, })); } else { setCropActive(true); } }, []); useEffect(() => { if (contentState.recording) { setContentState((prevContentState) => ({ ...prevContentState, pendingRecording: false, })); } }, [contentState.recording]); return (
{contentState.updateChrome && (
{chrome.i18n.getMessage("customAreaRecordingDisabledTitle")}
{chrome.i18n.getMessage("customAreaRecordingDisabledDescription")}
)} {/*contentState.offline && (
You are currently offline
Some features are unavailable
)*/} {!cropActive && contentState.recordingType === "region" && !contentState.offline && (
{chrome.i18n.getMessage("customAreaRecordingDisabledTitle")}
{chrome.i18n.getMessage( "customAreaRecordingDisabledDescription" )}
)} {!contentState.cameraPermission && ( )} {contentState.cameraPermission && ( )} {contentState.cameraPermission && contentState.defaultVideoInput != "none" && contentState.cameraActive && (
{(!contentState.isLoggedIn || contentState.instantMode) && (
)} {contentState.backgroundEffectsActive && (!contentState.isLoggedIn || contentState.instantMode) && ( )}
)} {!contentState.microphonePermission && ( )} {contentState.microphonePermission && ( )} {((!contentState.isLoggedIn && contentState.microphonePermission && contentState.defaultAudioInput != "none" && contentState.micActive) || (contentState.microphonePermission && contentState.pushToTalk)) && (
)} {contentState.recordingType === "region" && cropActive && (
{contentState.customRegion && }
)} {contentState.isLoggedIn && !contentState.recordingToScene && CLOUD_FEATURES_ENABLED && ( <>
{ if (checked) { contentState.openModal( chrome.i18n.getMessage("instantRecordingModeTitle") || "Instant recording mode", chrome.i18n.getMessage( "instantRecordingModeDescription" ) || "This records everything into one video for instant download and sharing. You won’t be able to change the camera layout afterward, but other edits are still possible.", chrome.i18n.getMessage("instantRecordingModeAction") || "Got it", chrome.i18n.getMessage("permissionsModalDismiss") || "Dismiss", () => {}, () => {}, null, "", "", true, false ); } else { // Turn off background effects in chrome.storage chrome.storage.local.set({ backgroundEffectsActive: false, }); // Update in memory setContentState((prev) => ({ ...prev, backgroundEffectsActive: false, })); } }} />
)}
); }; export default RecordingType; ================================================ FILE: src/pages/Content/popup/layout/Settings.jsx ================================================ import React, { useState, useContext, useEffect } from "react"; import * as Collapsible from "@radix-ui/react-collapsible"; import { DropdownIcon } from "../../images/popup/images"; // Components import Switch from "../components/Switch"; import TimeSetter from "../components/TimeSetter"; // Context import { contentStateContext } from "../../context/ContentState"; const Settings = () => { const [open, setOpen] = useState(false); const [contentState, setContentState] = useContext(contentStateContext); const [chromeVersion, setChromeVersion] = useState(null); // Check if Mac const isMac = navigator.platform.toUpperCase().indexOf("MAC") >= 0; // Set shortcut to Option+Shift+E on Mac and Alt+Shift+E on Windows, using character codes const shortcut = isMac ? "⌥⇧E" : "Alt⇧E"; // Get Chrome version const getChromeVersion = () => { var raw = navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./); return raw ? parseInt(raw[2], 10) : false; }; useEffect(() => { setChromeVersion(getChromeVersion()); }, []); useEffect(() => { setContentState((prevContentState) => ({ ...prevContentState, settingsOpen: open, })); }, [open]); return (
✨ {chrome.i18n.getMessage("showMoreOptionsLabel")}{" "}
{contentState.alarm && } {contentState.recordingType != "region" && contentState.recordingType != "camera" && !contentState.isSubscribed && (chromeVersion === null || chromeVersion >= 109) && ( )} {contentState.recordingType != "camera" && !contentState.isSubscribed && ( )}
); }; export default Settings; ================================================ FILE: src/pages/Content/popup/layout/SettingsMenu.jsx ================================================ // Work in progress - settings for the recording import React, { useState, useContext, useRef, useEffect } from "react"; import * as DropdownMenu from "@radix-ui/react-dropdown-menu"; import { MoreIconPopup } from "../../toolbar/components/SVG"; import TooltipWrap from "../components/TooltipWrap"; import { CheckWhiteIcon, DropdownGroup } from "../../images/popup/images"; import { buildDiagnosticZip } from "../../../utils/buildDiagnosticZip"; // Context import { contentStateContext } from "../../context/ContentState"; import { probeFastRecorderSupport, shouldUseFastRecorder, getFastRecorderStickyState, } from "../../../../media/fastRecorderGate"; import { resetOnboardingSeen } from "../onboarding/storage"; import { runProPopupOnboardingIfNeeded } from "../onboarding/proOnboarding"; const CLOUD_FEATURES_ENABLED = process.env.SCREENITY_ENABLE_CLOUD_FEATURES === "true"; const SettingsMenu = (props) => { const [contentState, setContentState] = useContext(contentStateContext); const [restore, setRestore] = useState(false); const [cloudRestore, setCloudRestore] = useState(false); const [oldChrome, setOldChrome] = useState(false); const [openQuality, setOpenQuality] = useState(false); const [openResize, setOpenResize] = useState(false); const [openFPS, setOpenFPS] = useState(false); const [RAM, setRAM] = useState(0); const [width, setWidth] = useState(0); const [height, setHeight] = useState(0); const [fastRecorderInfo, setFastRecorderInfo] = useState({ status: null, probe: null, decision: null, disabled: false, disabledReason: null, disabledDetails: null, disabledAt: null, }); useEffect(() => { // Check chrome version const chromeVersion = navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./); const MIN_CHROME_VERSION = 110; if (chromeVersion && parseInt(chromeVersion[2], 10) < MIN_CHROME_VERSION) { setOldChrome(true); } }, []); const handleTroubleshooting = () => { if (typeof contentState.openModal === "function") { contentState.openModal( chrome.i18n.getMessage("troubleshootModalTitle"), chrome.i18n.getMessage("troubleshootModalDescription"), chrome.i18n.getMessage("troubleshootModalButton"), chrome.i18n.getMessage("sandboxEditorCancelButton"), async () => { try { const { blob, filename } = await buildDiagnosticZip({ source: "popup-settings", extraConfig: { defaultAudioInput: contentState.defaultAudioInput, defaultAudioOutput: contentState.defaultAudioOutput, defaultVideoInput: contentState.defaultVideoInput, quality: contentState.quality, systemAudio: contentState.systemAudio, audioInput: contentState.audioInput, audioOutput: contentState.audioOutput, backgroundEffectsActive: contentState.backgroundEffectsActive, recording: contentState.recording, recordingType: contentState.recordingType, askForPermissions: contentState.askForPermissions, cameraPermission: contentState.cameraPermission, microphonePermission: contentState.microphonePermission, askMicrophone: contentState.askMicrophone, cursorMode: contentState.cursorMode, zoomEnabled: contentState.zoomEnabled, offscreenRecording: contentState.offscreenRecording, updateChrome: contentState.updateChrome, permissionsChecked: contentState.permissionsChecked, permissionsLoaded: contentState.permissionsLoaded, hideUI: contentState.hideUI, alarm: contentState.alarm, alarmTime: contentState.alarmTime, surface: contentState.surface, blurMode: contentState.blurMode, }, }); const url = window.URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = filename; a.click(); window.URL.revokeObjectURL(url); } catch (err) { console.error("[Screenity] Troubleshooting export failed:", err); } }, () => {}, ); } }; useEffect(() => { // More accurate screen detection const w = window.screen.availWidth * window.devicePixelRatio; const h = window.screen.availHeight * window.devicePixelRatio; setWidth(Math.round(w)); setHeight(Math.round(h)); // Fix RAM detection on macOS const isMac = navigator.userAgent.includes("Macintosh"); const ram = isMac ? 32 : Number(navigator.deviceMemory) || 4; setRAM(ram); }, []); const getFastRecDebug = () => { try { const params = new URLSearchParams(window.location.search); return params.get("fastRecDebug") === "1"; } catch { return false; } }; const hasLadderFields = (probe) => { if (!probe || !probe.details) return false; return ( Array.isArray(probe.details.attemptSummary) && probe.details.attemptSummary.length > 0 && Boolean(probe.details.selectedVideoConfig) ); }; const runFastRecorderProbe = async (source = "auto") => { if (!contentState) return; const userSetting = contentState.useWebCodecsRecorder === true ? true : contentState.useWebCodecsRecorder === false ? false : null; const sticky = await getFastRecorderStickyState(); const probe = await probeFastRecorderSupport(); const useFast = shouldUseFastRecorder(userSetting, probe, sticky); const decision = { useFast, why: userSetting === false ? "user_disabled" : sticky?.disabled && userSetting !== true ? "sticky_disabled" : probe.ok ? "probe_ok" : "probe_failed", at: Date.now(), }; const status = { userSetting, probe: { ...probe, at: probe.at || Date.now() }, decision, disabled: Boolean(sticky?.disabled), disabledReason: sticky?.reason || null, disabledDetails: sticky?.details || null, disabledAt: null, updatedAt: Date.now(), }; try { await chrome.storage.local.set({ fastRecorderStatus: status }); } catch {} setFastRecorderInfo({ status, probe: status.probe, decision, disabled: status.disabled, disabledReason: status.disabledReason, disabledDetails: status.disabledDetails, disabledAt: status.disabledAt, }); if (getFastRecDebug()) { console.log("[FastRecorderStatus]", { source, status }); } return status; }; const migrateFastRecorderStatus = async () => { const existing = await chrome.storage.local.get(["fastRecorderStatus"]); if (existing.fastRecorderStatus) { const status = existing.fastRecorderStatus; if (!hasLadderFields(status?.probe)) { await runFastRecorderProbe("stale-status"); const refreshed = await chrome.storage.local.get(["fastRecorderStatus"]); return refreshed.fastRecorderStatus || status; } return status; } const legacy = await chrome.storage.local.get([ "fastRecorderBeta", "fastRecorderProbe", "fastRecorderDecision", "fastRecorderDisabledForDevice", "fastRecorderDisabledReason", "fastRecorderDisabledDetails", "fastRecorderDisabledAt", ]); const status = { userSetting: legacy.fastRecorderBeta === true ? true : legacy.fastRecorderBeta === false ? false : null, probe: legacy.fastRecorderProbe || null, decision: legacy.fastRecorderDecision || null, disabled: Boolean(legacy.fastRecorderDisabledForDevice), disabledReason: legacy.fastRecorderDisabledReason || null, disabledDetails: legacy.fastRecorderDisabledDetails || null, disabledAt: legacy.fastRecorderDisabledAt || null, updatedAt: Date.now(), }; try { await chrome.storage.local.set({ fastRecorderStatus: status }); } catch {} if (!hasLadderFields(status.probe)) { await runFastRecorderProbe("stale-legacy"); const refreshed = await chrome.storage.local.get(["fastRecorderStatus"]); return refreshed.fastRecorderStatus || status; } return status; }; useEffect(() => { let canceled = false; (async () => { const status = await migrateFastRecorderStatus(); if (canceled) return; setFastRecorderInfo((prev) => ({ ...prev, status, probe: status?.probe || null, decision: status?.decision || null, disabled: Boolean(status?.disabled), disabledReason: status?.disabledReason || null, disabledDetails: status?.disabledDetails || null, disabledAt: status?.disabledAt || null, })); const staleHours = 12; const probeAgeMs = status?.probe?.at && Number.isFinite(status.probe.at) ? Date.now() - status.probe.at : Infinity; const shouldRun = !status?.probe || probeAgeMs > staleHours * 60 * 60 * 1000 || !hasLadderFields(status?.probe); if (shouldRun) { await runFastRecorderProbe("effect"); } })(); return () => { canceled = true; }; }, [contentState?.useWebCodecsRecorder]); return ( { props.setOpen(open); chrome.runtime .sendMessage({ type: "check-restore" }) .then((response) => { setRestore(response.restore); }); if (CLOUD_FEATURES_ENABLED && contentState.isSubscribed) { chrome.runtime .sendMessage({ type: "check-cloud-restore" }) .then((response) => { setCloudRestore(response?.cloudRestore ?? false); }) .catch(() => setCloudRestore(false)); } chrome.storage.local.get(["fastRecorderStatus"], (result) => { const status = result.fastRecorderStatus || null; setFastRecorderInfo((prev) => ({ ...prev, status, probe: status?.probe || null, decision: status?.decision || null, disabled: Boolean(status?.disabled), disabledReason: status?.disabledReason || null, disabledDetails: status?.disabledDetails || null, disabledAt: status?.disabledAt || null, })); }); }} > {!contentState.isSubscribed && !contentState.isLoggedIn && ( { if (open) { setOpenFPS(false); setOpenQuality(false); } setOpenResize(open); }} > {chrome.i18n.getMessage("resizeWindowLabel")}
{ chrome.runtime.sendMessage({ type: "resize-window", width: 3840, height: 2160, }); }} disabled={width < 3840 || height < 2160} > 3840 x 2160 (4k) { chrome.runtime.sendMessage({ type: "resize-window", width: 1920, height: 1080, }); }} disabled={width < 1920 || height < 1080} > 1920 x 1080 (1080p) { chrome.runtime.sendMessage({ type: "resize-window", width: 1280, height: 720, }); }} disabled={width < 1280 || height < 720} > 1280 x 720 (720p) { chrome.runtime.sendMessage({ type: "resize-window", width: 640, height: 480, }); }} disabled={width < 640 || height < 480} > 640 x 480 (480p) { chrome.runtime.sendMessage({ type: "resize-window", width: 480, height: 360, }); }} disabled={width < 480 || height < 360} > 480 x 360 (360p) { chrome.runtime.sendMessage({ type: "resize-window", width: 320, height: 240, }); }} disabled={width < 320 || height < 240} > 320 x 240 (240p)
)} {!contentState.isSubscribed && !contentState.isLoggedIn && ( { if (open) { setOpenFPS(false); setOpenResize(false); } setOpenQuality(open); }} > {chrome.i18n.getMessage("maxResolutionLabel") + " (" + contentState.qualityValue + ")"}
{ setContentState((prevContentState) => ({ ...prevContentState, qualityValue: value, })); chrome.storage.local.set({ qualityValue: value, }); }} > 4k 1080p 720p 480p 360p 240p
)} {!contentState.isSubscribed && !contentState.isLoggedIn && ( { if (open) { setOpenQuality(false); setOpenResize(false); } setOpenFPS(open); }} > {chrome.i18n.getMessage("maxFPSLabel") + " (" + contentState.fpsValue + " fps)"}
{ setContentState((prevContentState) => ({ ...prevContentState, fpsValue: value, })); chrome.storage.local.set({ fpsValue: value, }); }} > 60 fps 30 fps 24 fps 15 fps 10 fps 5 fps
)} { e.preventDefault(); }} onCheckedChange={(checked) => { setContentState((prevContentState) => ({ ...prevContentState, systemAudio: checked, })); chrome.storage.local.set({ systemAudio: checked, }); }} checked={contentState.systemAudio} > {chrome.i18n.getMessage("systemAudioLabel")} {!contentState.isSubscribed && !contentState.isLoggedIn && fastRecorderInfo?.probe?.ok === true && fastRecorderInfo?.probe?.details?.selectedVideoConfig && ( { e.preventDefault(); }} onCheckedChange={(checked) => { setContentState((prevContentState) => ({ ...prevContentState, useWebCodecsRecorder: checked, })); chrome.storage.local.set({ useWebCodecsRecorder: checked, ...(checked ? { lastWebCodecsFailureAt: null, lastWebCodecsFailureCode: null, } : {}), }); }} checked={contentState.useWebCodecsRecorder === true} > {chrome.i18n.getMessage("webcodecsToggleLabel")} )} {!oldChrome && !contentState.isSubscribed && !contentState.isLoggedIn && ( { e.preventDefault(); }} onCheckedChange={(checked) => { if (!checked) { chrome.runtime.sendMessage({ type: "close-backup-tab" }); } setContentState((prevContentState) => ({ ...prevContentState, backup: checked, backupSetup: false, })); chrome.storage.local.set({ backup: checked, backupSetup: false, }); }} checked={contentState.backup} > {chrome.i18n.getMessage("backupsToggle")} )} {!contentState.isSubscribed && !contentState.isLoggedIn && ( { e.preventDefault(); chrome.runtime.sendMessage({ type: "restore-recording" }); }} disabled={!restore} > {chrome.i18n.getMessage("restoreRecording")} )} {!contentState.isSubscribed && !contentState.isLoggedIn && ( { e.preventDefault(); handleTroubleshooting(); }} > {chrome.i18n.getMessage("downloadForTroubleshootingOption")} )} {contentState.isLoggedIn && !CLOUD_FEATURES_ENABLED && ( { e.preventDefault(); chrome.runtime.sendMessage({ type: "open-account-settings" }); }} > {chrome.i18n.getMessage("accountSettingsOption")} )} {contentState.isLoggedIn && !CLOUD_FEATURES_ENABLED && ( { e.preventDefault(); chrome.runtime.sendMessage({ type: "open-support", name: contentState.screenityUser?.name || "", email: contentState.screenityUser?.email || "", }); }} > {chrome.i18n.getMessage("supportSettingsOption")} )} {CLOUD_FEATURES_ENABLED && ( <> {contentState.isLoggedIn && contentState.isSubscribed && ( { e.preventDefault(); await resetOnboardingSeen(["proPopupCore", "proCameraInfo"]); props.setOpen(false); runProPopupOnboardingIfNeeded({ rootContext: props.shadowRef?.current?.shadowRoot || document, isPro: Boolean( contentState.isLoggedIn && contentState.isSubscribed ), isLoggedIn: Boolean(contentState.isLoggedIn), popupOpen: Boolean( contentState.showPopup && contentState.showExtension ), cameraEnabled: Boolean(contentState.cameraActive), pendingRecording: Boolean(contentState.pendingRecording), preparingRecording: Boolean(contentState.preparingRecording), recording: Boolean(contentState.recording), countdownActive: Boolean(contentState.countdownActive), isCountdownVisible: Boolean(contentState.isCountdownVisible), forceStart: true, }); }} > {chrome.i18n.getMessage("resetOnboardingOption") || "Reset onboarding"} )} {contentState.isLoggedIn && contentState.isSubscribed && ( { e.preventDefault(); chrome.runtime.sendMessage({ type: "restore-cloud-recording", }); }} disabled={!cloudRestore} > {chrome.i18n.getMessage("recoverLastRecordingOption")} )} )} {CLOUD_FEATURES_ENABLED && ( { e.preventDefault(); if (contentState.isLoggedIn) { // Log out flow chrome.runtime.sendMessage({ type: "handle-logout" }); setContentState((prev) => ({ ...prev, isLoggedIn: false, wasLoggedIn: true, isSubscribed: false, screenityUser: null, proSubscription: null, bigTab: "record", })); contentState.openToast( chrome.i18n.getMessage("loggedOutToastTitle"), () => {}, 2000 ); } else { // Log in flow (open login page) chrome.runtime.sendMessage({ type: "handle-login" }); } props.setOpen(false); // Close the menu after action }} > {contentState.isLoggedIn ? chrome.i18n.getMessage("logoutButtonLabel") || "Log out" : chrome.i18n.getMessage("loginButtonLabel") || "Log in or sign up"} )}
); }; export default SettingsMenu; ================================================ FILE: src/pages/Content/popup/layout/VideosTab.jsx ================================================ import React, { useState, useEffect, useRef, useCallback } from "react"; import * as Tabs from "@radix-ui/react-tabs"; import VideoItem from "../components/VideoItem"; import { PlaceholderThumb } from "../../images/popup/images"; import { useContext } from "react"; import { contentStateContext } from "../../context/ContentState"; import * as DropdownMenu from "@radix-ui/react-dropdown-menu"; import { DropdownIcon, CheckWhiteIcon } from "../../images/popup/images"; import { TempTwitter, TempFigma, TempDesignSystem, TempMarketing, TempSubstack, } from "../../images/popup/images"; const CLOUD_FEATURES_ENABLED = process.env.SCREENITY_ENABLE_CLOUD_FEATURES === "true"; const VideosTab = (props) => { const [videos, setVideos] = useState([]); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [hasMore, setHasMore] = useState(true); const [contentState, setContentState] = useContext(contentStateContext); const fetchedPagesRef = useRef(new Set()); // key = `${sortBy}-${page}` const videoCacheRef = useRef({}); // key: `${sortBy}-${page}` → video[] const VIDEO_CACHE_STORAGE_KEY = "cachedVideosBySort"; const pageRef = useRef(0); // Track page here without triggering re-renders const observerRef = useRef(); const PAGE_SIZE = 8; const sortBy = contentState.sortBy || "newest"; const filter = "all"; const lastFetchTimeRef = useRef(0); const FETCH_COOLDOWN_MS = 1500; useEffect(() => { if (!CLOUD_FEATURES_ENABLED) { // show only local placeholder videos return; } chrome.storage.local.get(VIDEO_CACHE_STORAGE_KEY, (result) => { if (result?.[VIDEO_CACHE_STORAGE_KEY]) { videoCacheRef.current = result[VIDEO_CACHE_STORAGE_KEY]; // Hydrate fetchedPagesRef with cached keys fetchedPagesRef.current = new Set(Object.keys(videoCacheRef.current)); // Collect all cached videos for current sort const matchingKeys = Object.keys(videoCacheRef.current).filter((key) => key.startsWith(`${sortBy}-`) ); // Sort by page number (e.g. newest-0, newest-1, newest-2...) const sortedKeys = matchingKeys.sort((a, b) => { const aPage = parseInt(a.split("-")[1], 10); const bPage = parseInt(b.split("-")[1], 10); return aPage - bPage; }); const allCachedVideos = sortedKeys.flatMap( (key) => videoCacheRef.current[key] || [] ); setVideos(allCachedVideos); // Update pageRef to next page pageRef.current = sortedKeys.length; // If the last page is smaller than PAGE_SIZE, we're done const lastPageKey = sortedKeys[sortedKeys.length - 1]; const lastPage = videoCacheRef.current[lastPageKey] || []; setHasMore(lastPage.length === PAGE_SIZE); } else { // No cache found, do initial fetch pageRef.current = 0; setHasMore(true); fetchVideos(); } }); }, []); useEffect(() => { if (!CLOUD_FEATURES_ENABLED) { // show only local placeholder videos return; } if (contentState.isSubscribed) { setVideos([]); pageRef.current = 0; setHasMore(true); fetchedPagesRef.current = new Set(); fetchVideos(); } }, [sortBy]); const fetchVideos = useCallback(async () => { if (!CLOUD_FEATURES_ENABLED) { // show only local placeholder videos return; } const now = Date.now(); if ( !contentState.isSubscribed || loading || !hasMore || now - lastFetchTimeRef.current < FETCH_COOLDOWN_MS ) { return; } lastFetchTimeRef.current = now; const cacheKey = `${sortBy}-${pageRef.current}`; if (fetchedPagesRef.current.has(cacheKey)) return; fetchedPagesRef.current.add(cacheKey); setLoading(true); try { if (videoCacheRef.current[cacheKey]) { const cachedVideos = videoCacheRef.current[cacheKey]; setVideos((prev) => [...prev, ...cachedVideos]); if (cachedVideos.length < PAGE_SIZE) { setHasMore(false); } else { pageRef.current += 1; } return; } const response = await chrome.runtime.sendMessage({ type: "fetch-videos", page: pageRef.current, pageSize: PAGE_SIZE, sort: sortBy, filter, }); if (!response?.success) { console.error("❌ Failed to fetch videos:", response?.error); setError(response?.error || "Failed to load videos"); setHasMore(false); return; } const newVideos = response.videos || []; setVideos((prev) => [...prev, ...newVideos]); if (newVideos.length > 0) { videoCacheRef.current[cacheKey] = newVideos; chrome.storage.local.set({ [VIDEO_CACHE_STORAGE_KEY]: videoCacheRef.current, }); } if (newVideos.length < PAGE_SIZE) { setHasMore(false); } else { pageRef.current += 1; } } catch (err) { console.error("❌ Unexpected error:", err); setError("Failed to load videos"); } finally { setLoading(false); } }, [loading, hasMore, contentState.isSubscribed, sortBy]); // useEffect(() => { // if (contentState.isSubscribed) { // fetchVideos(); // } // }, []); // Run once on mount useEffect(() => { if (!CLOUD_FEATURES_ENABLED) { // show only local placeholder videos return; } if (!observerRef.current || !hasMore || !contentState.isSubscribed) return; const observer = new IntersectionObserver( (entries) => { if (entries[0].isIntersecting) { fetchVideos(); } }, { threshold: 1.0 } ); observer.observe(observerRef.current); return () => observer.disconnect(); }, [fetchVideos, hasMore]); const handleVideoClick = (videoId) => { if (!CLOUD_FEATURES_ENABLED) { // show only local placeholder videos return; } const url = process.env.SCREENITY_APP_BASE + `/editor/${videoId}/edit`; window.open(url, "_blank"); }; const handleCopyLink = (videoId) => { if (!CLOUD_FEATURES_ENABLED) { // show only local placeholder videos return; } const link = process.env.SCREENITY_APP_BASE + `/view/${videoId}`; navigator.clipboard .writeText(link) .then(() => { contentState.openToast( chrome.i18n.getMessage("copiedToClipboardToast"), 3000 ); }) .catch((err) => { console.error("❌ Failed to copy:", err); contentState.openToast( chrome.i18n.getMessage("failedToCopyToClipboardToast"), 3000 ); }); }; useEffect(() => { if (!CLOUD_FEATURES_ENABLED) { // show only local placeholder videos return; } chrome.storage.local.get(["sortBy"], (result) => { if (result.sortBy && !contentState.sortBy) { setContentState((prev) => ({ ...prev, sortBy: result.sortBy })); } }); }, []); const sortLabelMap = { newest: chrome.i18n.getMessage("newestSortLabel"), oldest: chrome.i18n.getMessage("oldestSortLabel"), alphabetical: "A–Z", "reverse-alphabetical": "Z–A", }; return (
{!contentState.isSubscribed && (
{/* 👇 Embed the video here */}
)}
{chrome.i18n.getMessage("allVideosHeading")}
{/*
Team
Shared
*/}
{ setContentState((prev) => ({ ...prev, sortBy: value })); chrome.storage.local.set({ sortBy: value }); }} > {chrome.i18n.getMessage("newestSortLabel")} {chrome.i18n.getMessage("oldestSortLabel")} A–Z Z–A
{error &&

{error}

} {videos.length === 0 && !loading && !error && contentState.isSubscribed && (
👻
{chrome.i18n.getMessage("noVideosFound")}
)} {(contentState.isSubscribed ? videos : [ { title: "Bug report", createdAt: "3 minutes ago", data: { thumbnail: TempTwitter }, }, { title: "Figma async review", createdAt: "1 hour ago", data: { thumbnail: TempFigma }, }, { title: "Design systems onboarding", createdAt: "4 days ago", data: { thumbnail: TempDesignSystem }, }, { title: "Cool SaaS resources", createdAt: "Feb 12", data: { thumbnail: TempMarketing }, }, { title: "Newsletter promo", createdAt: "Jan 23", data: { thumbnail: TempSubstack }, }, { title: "Product demo", createdAt: "Jan 15", data: { thumbnail: PlaceholderThumb }, }, ] ).map((video, i) => ( handleVideoClick(video._id) : undefined } onCopyLink={ contentState.isSubscribed ? () => handleCopyLink(video._id) : undefined } /> ))} {loading && (
{chrome.i18n.getMessage("loadingVideosLabel")}
)}
); }; export default VideosTab; ================================================ FILE: src/pages/Content/popup/layout/Welcome.jsx ================================================ import React, { useEffect, useState } from "react"; import SoloDev from "../../../../assets/solo-dev.png"; // import EditorPreview from "../../../../assets/editor-preview.png"; // replace with actual screenshot file const Welcome = (props) => { const isUpdated = props.isBack; const clearBack = props.clearBack; const [learntAboutPro, setLearntAboutPro] = useState(false); useEffect(() => { // if (isUpdated) { chrome.storage.local.get("learntAboutPro", (res) => { if (res.learntAboutPro) setLearntAboutPro(true); }); // } }, []); return (
{!isUpdated ? chrome.i18n.getMessage("welcomePopupTitle") : chrome.i18n.getMessage("welcomeBackPopupTitle")}
{chrome.i18n.getMessage("welcomePopupDescriptionTop")}
{chrome.i18n.getMessage("welcomePopupDescriptionBottom")}
{ if (isUpdated) clearBack(); props.setOnboarding(false); props.setContentState((prev) => ({ ...prev, onboarding: false, })); chrome.storage.local.set({ onboarding: false }); }} > 👋 {chrome.i18n.getMessage("welcomePopupCTA")}
{!isUpdated ? chrome.i18n.getMessage("welcomeProTitle") : chrome.i18n.getMessage("welcomeBackProTitle") || "Want to do more with your recordings?"}

{learntAboutPro ? chrome.i18n.getMessage("welcomeProDescription") || "Sign in to save your videos to the cloud, share with a link, and access advanced editing features." : chrome.i18n.getMessage("welcomeBackProDescription") || "Sign in to save your videos to the cloud, share with a link, and access advanced editing features."}

{/*
{ if (!isUpdated || learntAboutPro) { chrome.runtime.sendMessage({ type: "handle-login" }); } else { chrome.storage.local.set({ learntAboutPro: true }); chrome.runtime.sendMessage({ type: "pricing" }); setLearntAboutPro(true); } }} role="button" className="main-button dashboard-button" tabIndex="0" style={{ zIndex: 99, marginTop: "25px", }} > {!isUpdated || learntAboutPro ? chrome.i18n.getMessage("welcomeProButton") || "Sign in to unlock paid features" : !learntAboutPro ? chrome.i18n.getMessage("welcomeBackProCTA") || "Learn more about Screenity Pro" : chrome.i18n.getMessage("welcomeBackProCTAAfterLearn") || "Sign in to unlock paid features"}
*/}
{ if (learntAboutPro) { // After they've seen the Pro info, trigger sign in chrome.runtime.sendMessage({ type: "handle-login" }); } else { // First click: show pricing page and mark as "learnt" chrome.storage.local.set({ learntAboutPro: true }); chrome.runtime.sendMessage({ type: "pricing" }); setLearntAboutPro(true); } }} role="button" className="main-button dashboard-button" tabIndex="0" style={{ zIndex: 99, marginTop: "25px", }} > {learntAboutPro ? chrome.i18n.getMessage("welcomeProButton") || "Sign in to unlock paid features" : chrome.i18n.getMessage("welcomeBackProCTA") || "Learn more about Screenity Pro"}
{chrome.i18n.getMessage("welcomeProSupport") || "Support development by a solo indie maker "} Alyssa X
); }; export default Welcome; ================================================ FILE: src/pages/Content/popup/layout/WelcomeAlternate.jsx ================================================ import React, { useState, useEffect } from "react"; import CheckBlueIcon from "../../../../assets/check-blue.svg"; import SoloDev from "../../../../assets/solo-dev.png"; const FeatureItem = ({ text }) => { // render with a checkmark icon on left and text on right return (
Checkmark
{text}
); }; const Welcome = (props) => { return (
{/*
*/}
{chrome.i18n.getMessage("welcomePopupTitle")}
{chrome.i18n.getMessage("welcomePopupDescriptionTop")}
{chrome.i18n.getMessage("welcomePopupDescriptionBottom")}
{ props.setOnboarding(false); chrome.storage.local.set({ updatingFromOld: false }); }} > 👋 {chrome.i18n.getMessage("welcomePopupCTA")}
); }; export default Welcome; ================================================ FILE: src/pages/Content/popup/onboarding/proOnboarding.js ================================================ import { driver } from "driver.js"; import "driver.js/dist/driver.css"; import { hasSeenOnboarding, markOnboardingSeen } from "./storage"; const CORE_KEY = "proPopupCore"; const CAMERA_KEY = "proCameraInfo"; const POPOVER_CLASS = "ScreenityOnboardingPopover onboarding-popover"; const DRIVER_STYLE_ID = "screenity-driver-onboarding-style"; const TOOLBAR_HELP_URL = "https://help.screenity.io/recording/how-to-hide-the-toolbar"; const IDLE_START_DELAY_MS = 420; const STEP_IDS = { WELCOME: "welcome", TOOLBAR: "toolbar", CAMERA: "camera", INSTANT: "instant", }; const TOOLBAR_SELECTORS = [ "#pro-onboarding-recording-toolbar-root", "#pro-onboarding-recording-toolbar .ToolbarRoot", "#pro-onboarding-recording-toolbar", "#pro-onboarding-recording-toolbar-controls", ".react-draggable .ToolbarRoot", ]; const INSTANT_PRIMARY_SELECTORS = [ "#pro-onboarding-instant-mode-toggle-row", "#pro-onboarding-instant-mode-field", "#pro-onboarding-instant-mode-toggle", ]; const INSTANT_FALLBACK_SELECTORS = [ "#pro-onboarding-popup-container", ".popup-container", ]; const CAMERA_SELECTORS = [ ".camera-page .camera-draggable", ".camera-draggable", ".camera-page", ]; const CONFLICT_SELECTORS = [ ".AlertDialogContent", ".AlertDialogOverlay", ".countdown-overlay", ".countdown-circle", ".recording-countdown", ]; const START_CANCEL_EVENTS = ["mousedown", "click", "keydown"]; const DEBUG = typeof window !== "undefined" && Boolean(window.SCREENITY_DEBUG_ONBOARDING); let activeDriver = null; let activeRun = null; let restoreRootStyle = null; let pendingStartTimer = null; let cancelPendingStartListeners = null; let onboardingInProgress = false; let pendingStartToken = 0; const logDebug = (event, payload = {}) => { if (!DEBUG) return; // eslint-disable-next-line no-console console.debug("[Screenity][Onboarding]", event, payload); }; const getRoot = (context) => (context?.querySelector ? context : document); const t = (key, fallback) => { try { return chrome.i18n.getMessage(key) || fallback; } catch { return fallback; } }; const getOnboardingText = () => ({ welcomeTitle: t( "proOnboardingWelcomeTitle", "Welcome to the Screenity Pro extension", ), welcomeDescription: t( "proOnboardingWelcomeDescription", "A few quick tips before you record.
Auto-zooms only work for clicks inside Chrome tabs. You can still add zooms manually later.", ), toolbarTitle: t("proOnboardingToolbarTitle", "Recording toolbar & effects"), toolbarDescription: t( "proOnboardingToolbarDescription", "Use this toolbar for drawing, cursor effects, blur, and recording controls.

This toolbar is included in the video unless you hide it.", ), cameraTitle: t("proOnboardingCameraTitle", "Camera is captured separately"), cameraDescription: t( "proOnboardingCameraDescription", "Your camera may hide or move to PiP while recording, that’s normal.

It’s captured separately so you can position it later.", ), instantTitle: t("proOnboardingInstantTitle", "Instant mode"), instantDescription: t( "proOnboardingInstantDescription", "Best for quick sharing with unlimited downloads.

Advanced layouts/editor options aren’t available in this mode.", ), doneBtnText: t("proOnboardingDone", "Got it"), nextBtnText: t("proOnboardingNext", "Next"), prevBtnText: t("proOnboardingBack", "Back"), learnMoreLabel: t("proOnboardingToolbarLearnMore", "Learn more"), }); const find = (root, selector) => { try { return root.querySelector(selector); } catch { return null; } }; const findFirst = (root, selectors = []) => { for (const selector of selectors) { const el = find(root, selector); if (el) return el; } return null; }; const isElementVisible = (el) => { if (!el || !el.isConnected) return false; if ( typeof el.getClientRects === "function" && el.getClientRects().length === 0 ) { return false; } const style = window.getComputedStyle(el); return style.display !== "none" && style.visibility !== "hidden"; }; const waitForElement = async (root, selectors, timeoutMs = 1200) => { const start = Date.now(); while (Date.now() - start < timeoutMs) { const el = findFirst(root, selectors); if (isElementVisible(el)) return el; await new Promise((resolve) => setTimeout(resolve, 60)); } return null; }; const clearScheduledStart = () => { if (pendingStartTimer) { clearTimeout(pendingStartTimer); pendingStartTimer = null; } if (typeof cancelPendingStartListeners === "function") { cancelPendingStartListeners(); cancelPendingStartListeners = null; } }; const clearDriverUiState = (root) => { document.documentElement.classList.remove("screenity-driver-modal-step"); document.documentElement.classList.remove("screenity-driver-active"); setToolbarCloseVisible(root, false); if (typeof restoreRootStyle === "function") restoreRootStyle(); restoreRootStyle = null; }; const hasConflictSelectorsVisible = (root) => { for (const selector of CONFLICT_SELECTORS) { if (isElementVisible(find(root, selector))) return true; if (root !== document && isElementVisible(find(document, selector))) { return true; } } return false; }; const hasBlockingFlags = (state = {}) => Boolean( state.pendingRecording || state.preparingRecording || state.recording || state.countdownActive || state.isCountdownVisible, ); const shouldSkip = ({ root, popupOpen, isPro, isLoggedIn, state = {} }) => { if (!popupOpen || !isPro || !isLoggedIn) return true; if (document.hidden) return true; if (hasBlockingFlags(state)) return true; if (hasConflictSelectorsVisible(root)) return true; return false; }; const isRecordingNow = async () => { try { const result = await chrome.storage.local.get([ "recording", "pendingRecording", "preparingRecording", "countdownActive", "isCountdownVisible", ]); return Boolean( result.recording || result.pendingRecording || result.preparingRecording || result.countdownActive || result.isCountdownVisible, ); } catch { return false; } }; const describeNode = (node) => { if (!node) return null; const rootNode = node.getRootNode?.(); const rootType = rootNode === document ? "document" : rootNode?.host ? "shadow-root" : "other"; let rect = null; try { const r = node.getBoundingClientRect(); rect = { x: r.x, y: r.y, width: r.width, height: r.height }; } catch {} return { tag: node.tagName, id: node.id || null, className: node.className || null, isConnected: Boolean(node.isConnected), rootType, parentTag: node.parentElement?.tagName || null, parentClass: node.parentElement?.className || null, rect, }; }; const getDriverDomSnapshot = () => { const overlay = document.querySelector(".driver-overlay"); const stage = document.querySelector(".driver-stage"); const popover = document.querySelector(".driver-popover"); return { overlay: describeNode(overlay), stage: describeNode(stage), popover: describeNode(popover), }; }; const resolveStepElement = (step) => { const raw = step?.element; if (!raw) return null; if (typeof raw === "function") { try { return raw(); } catch { return null; } } if (typeof raw === "string") { try { return document.querySelector(raw); } catch { return null; } } return raw; }; const logStepState = (event, step, options) => { const state = options?.state || activeDriver?.getState?.() || {}; const activeStep = step || state.activeStep || activeDriver?.getActiveStep?.(); const target = resolveStepElement(activeStep); logDebug(event, { activeIndex: state.activeIndex ?? activeDriver?.getActiveIndex?.(), title: activeStep?.popover?.title || null, target: describeNode(target), targetVisible: isElementVisible(target), targetRootType: target?.getRootNode?.()?.host != null ? "shadow-root" : "document", driverDom: getDriverDomSnapshot(), }); }; const ensureDriverStyles = () => { if (document.getElementById(DRIVER_STYLE_ID)) return; // Driver computes coordinates in document viewport space. // Keeping driver DOM in document.body avoids shadow-root stage/popover drift. const style = document.createElement("style"); style.id = DRIVER_STYLE_ID; style.textContent = ` .driver-overlay { z-index: 2147483645 !important; } .driver-stage { z-index: 2147483646 !important; } .driver-popover.ScreenityOnboardingPopover, .ScreenityOnboardingPopover { z-index: 2147483647 !important; border-radius: 30px !important; max-width: 340px !important; background: var(--color-background, #f9fafb) !important; color: var(--color-text-primary, #1f2430) !important; font-family: "Satoshi-Medium", sans-serif !important; box-shadow: 0px 4px 30px rgba(30, 31, 37, 0.12) !important; padding: 20px !important; font-size: 14px !important; } .driver-popover.ScreenityOnboardingPopover .driver-popover-title, .ScreenityOnboardingPopover .driver-popover-title { font-size: 1rem !important; font-family: "Satoshi-Medium", sans-serif !important; font-weight: 500 !important; margin-bottom: 12px !important; color: var(--color-text-primary, #1f2430) !important; } .driver-popover.ScreenityOnboardingPopover .driver-popover-description, .ScreenityOnboardingPopover .driver-popover-description { font-size: 14px !important; font-family: "Satoshi-Medium", sans-serif !important; font-weight: 500 !important; color: var(--color-text-secondary, #667085) !important; line-height: 1.5 !important; margin-bottom: 18px !important; } .driver-popover.ScreenityOnboardingPopover .driver-popover-close-btn, .ScreenityOnboardingPopover .driver-popover-close-btn { display: none !important; } .driver-popover.ScreenityOnboardingPopover .driver-popover-description a, .ScreenityOnboardingPopover .driver-popover-description a { color: #3b82f6 !important; text-decoration: none !important; cursor: pointer !important; } .driver-popover.ScreenityOnboardingPopover .driver-popover-progress-text, .ScreenityOnboardingPopover .driver-popover-progress-text { font-size: 12px !important; font-family: "Satoshi-Medium", sans-serif !important; color: var(--color-text-secondary, #667085) !important; opacity: 0.7 !important; } .driver-popover.ScreenityOnboardingPopover .driver-popover-footer .driver-popover-navigation-btns, .ScreenityOnboardingPopover .driver-popover-footer .driver-popover-navigation-btns { gap: 6px !important; } .driver-popover.ScreenityOnboardingPopover .driver-popover-footer .driver-popover-navigation-btns .driver-popover-next-btn, .driver-popover.ScreenityOnboardingPopover .driver-popover-footer .driver-popover-navigation-btns .driver-popover-prev-btn, .ScreenityOnboardingPopover .driver-popover-footer .driver-popover-navigation-btns .driver-popover-next-btn, .ScreenityOnboardingPopover .driver-popover-footer .driver-popover-navigation-btns .driver-popover-prev-btn { border-radius: 30px !important; padding: 10px 14px !important; font-size: 14px !important; text-shadow: none !important; } .driver-popover.ScreenityOnboardingPopover .driver-popover-footer .driver-popover-navigation-btns .driver-popover-next-btn, .ScreenityOnboardingPopover .driver-popover-footer .driver-popover-navigation-btns .driver-popover-next-btn { background-color: var(--color-primary, #3b82f6) !important; color: white !important; border: none !important; } .driver-popover.ScreenityOnboardingPopover .driver-popover-footer .driver-popover-navigation-btns .driver-popover-prev-btn, .ScreenityOnboardingPopover .driver-popover-footer .driver-popover-navigation-btns .driver-popover-prev-btn { background-color: transparent !important; color: var(--color-text-primary, #1f2430) !important; border: 1px solid var(--color-border, #d0d5dd) !important; } /* Modal step: hide stage cutout + center popover */ .screenity-driver-modal-step .driver-stage, .screenity-driver-modal-step .driver-stage-wrapper { display: none !important; } .screenity-driver-modal-step .driver-popover-arrow { display: none !important; } .screenity-driver-modal-step .driver-popover.ScreenityOnboardingPopover, .screenity-driver-modal-step .ScreenityOnboardingPopover { position: fixed !important; top: 50% !important; left: 50% !important; transform: translate(-50%, -50%) !important; margin: 0 !important; } .screenity-driver-modal-step .driver-popover.ScreenityOnboardingPopover, .screenity-driver-modal-step .ScreenityOnboardingPopover { max-width: 420px !important; padding: 26px !important; } /* Welcome icon */ .screenity-driver-modal-step .screenity-welcome-emoji { font-size: 24px; line-height: 1; margin-bottom: 10px; } `; document.head.appendChild(style); }; const lowerRootContainerZIndex = (root) => { const container = find(root, "#screenity-root-container") || root?.host || document.getElementById("screenity-root-container"); if (!container) return () => {}; const prevValue = container.style.zIndex; const prevPriority = container.style.getPropertyPriority("z-index"); container.style.setProperty("z-index", "10000", "important"); return () => { if (prevValue) { container.style.setProperty("z-index", prevValue, prevPriority || ""); } else { container.style.removeProperty("z-index"); } }; }; const setToolbarCloseVisible = (root, visible) => { if (!root?.querySelectorAll) return; root.querySelectorAll(".toolbar-controls").forEach((node) => { if (visible) node.classList.add("open"); else node.classList.remove("open"); }); }; const buildWelcomeStep = (copy) => ({ id: STEP_IDS.WELCOME, // No element => no cutout highlight (acts like a modal when centered in onPopoverRender) popover: { title: copy.welcomeTitle, showButtons: ["next"], description: copy.welcomeDescription, }, }); const buildToolbarStep = (element, copy) => ({ id: STEP_IDS.TOOLBAR, element, popover: { title: copy.toolbarTitle, side: "top", align: "center", showButtons: ["previous", "next"], description: copy.toolbarDescription, }, }); const buildCameraStep = (element, copy) => ({ id: STEP_IDS.CAMERA, element, popover: { title: copy.cameraTitle, side: "top", align: "center", showButtons: ["previous", "next"], description: copy.cameraDescription, }, }); const buildInstantStep = (element, copy) => ({ id: STEP_IDS.INSTANT, element, popover: { title: copy.instantTitle, side: "left", align: "center", showButtons: ["previous", "next"], description: copy.instantDescription, }, }); const shouldMarkSeen = (abortReason) => abortReason == null || abortReason === "dismissed"; const destroyActive = (abortReason = "external") => { if (!activeRun) return; if (!activeRun.abortReason) activeRun.abortReason = abortReason; if (!activeDriver) { clearDriverUiState(activeRun.root); if (typeof activeRun.stopObserver === "function") activeRun.stopObserver(); activeRun = null; onboardingInProgress = false; return; } try { activeDriver.destroy(); } catch {} }; const setModalStep = (enabled) => { document.documentElement.classList.toggle( "screenity-driver-modal-step", enabled, ); }; const startBlockingObserver = ({ root, getState }) => { const observer = new MutationObserver(() => { if (!activeDriver) return; const runtimeState = getState?.() || {}; if ( hasConflictSelectorsVisible(root) || hasBlockingFlags(runtimeState) || document.hidden ) { destroyActive("conflict"); } }); observer.observe(document.documentElement, { childList: true, subtree: true, attributes: true, }); return () => observer.disconnect(); }; const addInteractionCancelListeners = (root, onCancel) => { const popup = find(root, "#pro-onboarding-popup-container") || find(root, ".popup-container"); const target = popup || document; const handler = (event) => { if (event.type === "keydown" && event.key === "Tab") return; onCancel(); }; START_CANCEL_EVENTS.forEach((eventName) => { target.addEventListener(eventName, handler, { capture: true }); }); return () => { START_CANCEL_EVENTS.forEach((eventName) => { target.removeEventListener(eventName, handler, { capture: true }); }); }; }; const resolveTourSteps = ({ toolbarEl, cameraEl, instantEl, copy, includeCamera = false, }) => { const steps = [buildWelcomeStep(copy)]; if (isElementVisible(toolbarEl)) steps.push(buildToolbarStep(toolbarEl, copy)); if (includeCamera && isElementVisible(cameraEl)) { steps.push(buildCameraStep(cameraEl, copy)); } if (isElementVisible(instantEl)) steps.push(buildInstantStep(instantEl, copy)); return steps; }; const startDriver = ({ steps, root, getState, onFinish, copy }) => { if (!steps.length) return; ensureDriverStyles(); restoreRootStyle = lowerRootContainerZIndex(root); document.documentElement.classList.add("screenity-driver-active"); activeRun = { root, cameraStepShown: false, abortReason: null, stopObserver: startBlockingObserver({ root, getState }), onFinish, }; const d = driver({ allowClose: true, overlayClickBehavior: "close", showProgress: true, popoverOffset: 18, stagePadding: 12, popoverClass: POPOVER_CLASS, doneBtnText: copy.doneBtnText, nextBtnText: copy.nextBtnText, prevBtnText: copy.prevBtnText, steps, onCloseClick: () => { destroyActive("dismissed"); }, onHighlightStarted: (element, step) => { const isWelcome = step?.id === STEP_IDS.WELCOME; setModalStep(isWelcome); const isToolbarStep = step?.id === STEP_IDS.TOOLBAR; setToolbarCloseVisible(root, isToolbarStep); }, onHighlighted: (element, step, options) => { if (step?.id === STEP_IDS.CAMERA && activeRun) { activeRun.cameraStepShown = true; } logStepState("highlighted", step, options); }, onPopoverRender: (popover, options) => { const step = options?.state?.activeStep || {}; if (step.id === STEP_IDS.WELCOME && popover?.title) { // Only insert once if ( !popover.title.parentElement?.querySelector( ".screenity-welcome-emoji", ) ) { const emoji = document.createElement("div"); emoji.className = "screenity-welcome-emoji"; emoji.textContent = "👋"; popover.title.parentElement.insertBefore(emoji, popover.title); } } if ( step.id === STEP_IDS.TOOLBAR && popover?.description && !popover.description.querySelector("a") ) { const desc = popover.description; desc.appendChild(document.createTextNode(" ")); const link = document.createElement("a"); link.href = TOOLBAR_HELP_URL; link.target = "_blank"; link.rel = "noopener noreferrer"; link.textContent = copy.learnMoreLabel; desc.appendChild(link); } logStepState("popover_render", options?.state?.activeStep, options); }, onDestroyed: async (element, step, options) => { const run = activeRun; clearDriverUiState(root); if (run && typeof run.stopObserver === "function") run.stopObserver(); activeDriver = null; activeRun = null; onboardingInProgress = false; logStepState("destroyed", step, options); run?.onFinish?.({ cameraStepShown: Boolean(run?.cameraStepShown), shouldMarkSeen: shouldMarkSeen(run?.abortReason), }); }, }); activeDriver = d; d.drive(); logStepState("drive_started", steps[0], { state: d.getState?.() || {} }); }; export const runProPopupOnboardingIfNeeded = async ({ rootContext = null, isPro = false, isLoggedIn = false, popupOpen = false, cameraEnabled = false, pendingRecording = false, preparingRecording = false, recording = false, countdownActive = false, isCountdownVisible = false, forceStart = false, } = {}) => { const root = getRoot(rootContext); const state = { pendingRecording, preparingRecording, recording, countdownActive, isCountdownVisible, }; const copy = getOnboardingText(); if (!popupOpen || !isPro || !isLoggedIn) { clearScheduledStart(); if (activeDriver) destroyActive("popup-closed"); return; } if ( shouldSkip({ root, popupOpen, isPro, isLoggedIn, state, }) ) { clearScheduledStart(); if ( activeDriver && (hasBlockingFlags(state) || hasConflictSelectorsVisible(root)) ) { destroyActive("conflict"); } return; } if (await hasSeenOnboarding(CORE_KEY)) return; if (pendingStartTimer || onboardingInProgress || activeDriver) return; const startDelayMs = forceStart ? 0 : IDLE_START_DELAY_MS; const token = ++pendingStartToken; if (!forceStart) { cancelPendingStartListeners = addInteractionCancelListeners(root, () => { clearScheduledStart(); }); } pendingStartTimer = setTimeout(async () => { if (token !== pendingStartToken) return; clearScheduledStart(); const latestSkip = shouldSkip({ root, popupOpen, isPro, isLoggedIn, state, }); if (latestSkip) return; if (await isRecordingNow()) return; if (await hasSeenOnboarding(CORE_KEY)) return; onboardingInProgress = true; const toolbarEl = await waitForElement(root, TOOLBAR_SELECTORS, 900); const cameraEl = await waitForElement(root, CAMERA_SELECTORS, 700); const instantPrimaryEl = await waitForElement( root, INSTANT_PRIMARY_SELECTORS, 900, ); const instantEl = instantPrimaryEl || (await waitForElement(root, INSTANT_FALLBACK_SELECTORS, 250)); const includeCamera = cameraEnabled && !(await hasSeenOnboarding(CAMERA_KEY)) && isElementVisible(cameraEl); const steps = resolveTourSteps({ toolbarEl, cameraEl, instantEl, copy, includeCamera, }); if (!steps.length) { onboardingInProgress = false; return; } startDriver({ steps, root, copy, getState: () => state, onFinish: async ({ cameraStepShown, shouldMarkSeen }) => { if (!shouldMarkSeen) return; await markOnboardingSeen(CORE_KEY); if (includeCamera || cameraStepShown) { await markOnboardingSeen(CAMERA_KEY); } }, }); }, startDelayMs); }; export const runProCameraOnboardingIfNeeded = async ({ rootContext = null, isPro = false, isLoggedIn = false, popupOpen = false, cameraEnabled = false, pendingRecording = false, preparingRecording = false, recording = false, countdownActive = false, isCountdownVisible = false, } = {}) => { const root = getRoot(rootContext); const state = { pendingRecording, preparingRecording, recording, countdownActive, isCountdownVisible, }; const copy = getOnboardingText(); if ( shouldSkip({ root, popupOpen, isPro, isLoggedIn, state, }) ) { if ( activeDriver && (hasBlockingFlags(state) || hasConflictSelectorsVisible(root)) ) { destroyActive("conflict"); } return; } if (!cameraEnabled) return; if (await hasSeenOnboarding(CAMERA_KEY)) return; if (pendingStartTimer || onboardingInProgress || activeDriver) return; const cameraEl = await waitForElement(root, CAMERA_SELECTORS, 900); if (!isElementVisible(cameraEl)) return; setTimeout(() => { if ( shouldSkip({ root, popupOpen, isPro, isLoggedIn, state, }) ) { return; } onboardingInProgress = true; startDriver({ steps: [ { id: STEP_IDS.CAMERA, element: cameraEl, popover: { title: copy.cameraTitle, side: "top", align: "center", showButtons: ["next"], description: copy.cameraDescription, }, }, ], root, copy, getState: () => state, onFinish: async ({ shouldMarkSeen }) => { if (!shouldMarkSeen) return; await markOnboardingSeen(CAMERA_KEY); }, }); }, 260); }; ================================================ FILE: src/pages/Content/popup/onboarding/storage.js ================================================ const ONBOARDING_SEEN_KEY = "onboardingSeen"; export const hasSeenOnboarding = async (key) => { try { const result = await chrome.storage.sync.get([ONBOARDING_SEEN_KEY]); const seen = result?.[ONBOARDING_SEEN_KEY]; return Boolean(seen && typeof seen === "object" && seen[key] === true); } catch { return false; } }; export const markOnboardingSeen = async (key) => { try { const result = await chrome.storage.sync.get([ONBOARDING_SEEN_KEY]); const current = result?.[ONBOARDING_SEEN_KEY] && typeof result[ONBOARDING_SEEN_KEY] === "object" ? result[ONBOARDING_SEEN_KEY] : {}; await chrome.storage.sync.set({ [ONBOARDING_SEEN_KEY]: { ...current, [key]: true, }, }); } catch {} }; export const resetOnboardingSeen = async (keys = []) => { try { const result = await chrome.storage.sync.get([ONBOARDING_SEEN_KEY]); const current = result?.[ONBOARDING_SEEN_KEY] && typeof result[ONBOARDING_SEEN_KEY] === "object" ? { ...result[ONBOARDING_SEEN_KEY] } : {}; if (Array.isArray(keys) && keys.length > 0) { keys.forEach((key) => { delete current[key]; }); } else { Object.keys(current).forEach((key) => { delete current[key]; }); } await chrome.storage.sync.set({ [ONBOARDING_SEEN_KEY]: current, }); } catch {} }; ================================================ FILE: src/pages/Content/popup/styles/_Popup.scss ================================================ @use "../../styles/_variables" as *; @use "./layout/_PopupContainer.scss"; @use "./layout/_Settings.scss"; @use "./layout/_VideosTab.scss"; @use "./layout/_Announcement.scss"; @use "./layout/_Welcome"; @use "./layout/_SettingsMenu.scss"; @use "./components/_Dropdown.scss"; @use "./components/_Tabs.scss"; @use "./components/_Switch"; @use "./components/_VideoItem"; @use "./components/_MainButton"; @use "./components/_BackgroundEffects"; @use "./components/_RegionDimensions"; @use "./components/_TimeSetter"; @use "./components/_Tooltip"; ================================================ FILE: src/pages/Content/popup/styles/components/_BackgroundEffects.scss ================================================ @use '../../../styles/_variables' as *; .background-effects-toggle-group { display: flex; height: 40px; width: 100%; gap: 8px; margin-bottom: 8px; margin-top: 8px; } .background-effect { display: flex; width: 40px; height: 40px; align-items: center; justify-content: center; border-radius: 30px; position: relative; color: #FFF; span { position: absolute; top: 0px; left: 0px; width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; font-size: 12px; font-weight: 600; z-index: 99999; font-weight: 500; } &[data-state='on']::after { content: ""; border-radius: 50%; display: block; width: 46px; height: 46px; position: absolute; border: 2px solid $color-primary; box-sizing: border-box; } img { width: 100%; height: 100%; border-radius: 50%; position: absolute; top: 0px; left: 0px; } &:hover:not([data-state='on']) { cursor: pointer; &::after { content: ""; border-radius: 50%; display: block; width: 46px; height: 46px; position: absolute; border: 2px solid $color-primary; opacity: .5; box-sizing: border-box; } } &:focus-visible { box-shadow: $focus-border; } } ================================================ FILE: src/pages/Content/popup/styles/components/_Dropdown.scss ================================================ @use "../../../styles/_variables" as *; /* reset */ button { all: unset; } .SelectTrigger { display: inline-flex; align-items: center; justify-content: space-between; border-radius: $container-border-radius; line-height: 1; height: 44px; gap: 5px; background-color: $color-background; color: $color-text-primary; width: 100%; box-sizing: border-box; margin-top: $spacing-02; margin-bottom: $spacing-02; } .SelectTrigger:hover { box-shadow: $container-shadow-focus; cursor: pointer; } .SelectTrigger:focus { box-shadow: $focus-border !important; } .SelectTrigger[data-placeholder] { color: var(--violet9); } .SelectTrigger[data-state="open"] { box-shadow: $focus-border; } .SelectValue { text-align: left; flex: 1; display: block; width: 100%; height: 100%; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; box-sizing: border-box; span { overflow: hidden; text-overflow: ellipsis; height: 100%; line-height: 44px; } } .SelectIconDrop, .SelectIconType { text-align: center; } .SelectIconType { padding-left: 6px; padding-right: 0px; } .SelectIconDrop { padding-right: $spacing-05; } .SelectTrigger[data-state="open"] .SelectIconDrop img { transform: rotate(180deg); } .SelectContent { overflow: hidden; z-index: $z-index-max; width: var(--radix-select-trigger-width); max-height: var(--radix-select-content-available-height); font-family: $font-medium; background-color: white; border-radius: 15px; margin-top: $spacing-02; box-shadow: $container-shadow-focus; } .SelectItem { font-size: $font-size-normal; line-height: 1; color: var(--violet11); display: flex; align-items: center; height: 44px; padding-left: $spacing-05; padding-right: $spacing-05; color: $color-text-primary; position: relative; user-select: none; } .SelectItem[data-disabled] { color: $color-text-secondary; pointer-events: none; } .SelectItem[data-highlighted] { background: $color-light-grey; outline: none !important; } .SelectItem:hover { background: $color-light-grey; cursor: pointer; } .SelectSeparator { height: 1px; background-color: $color-border; width: calc(100% - $spacing-04 * 2); margin: auto; border-radius: $container-border-radius; margin-top: $spacing-02; margin-bottom: $spacing-02; } .SelectItemIndicator { position: absolute; right: $spacing-04; width: 24px; height: 24px; background: $color-primary; border-radius: 50%; display: inline-flex; align-items: center; justify-content: center; } .SelectScrollButton { display: flex; align-items: center; justify-content: center; height: 25px; background-color: white; color: var(--violet11); cursor: default; } .SelectOff { background: $color-red-light; color: $color-red; padding-left: $spacing-04; padding-right: $spacing-04; padding-top: $spacing-03; padding-bottom: $spacing-03; margin-right: $spacing-02; border-radius: $container-border-radius; font-size: $font-size-small; font-weight: $font-weight-bold; } .SelectIconButton { border-radius: $container-border-radius; position: relative; padding: 8px; &:hover { background-color: $color-light-grey; } } ================================================ FILE: src/pages/Content/popup/styles/components/_MainButton.scss ================================================ @use '../../../styles/_variables' as *; .main-button { width: 100%; height: 45px; border-radius: $container-border-radius; display: flex; flex-direction: row; justify-content: center; align-items: center; position: relative; box-sizing: border-box; .main-button-label { color: $color-text-contrast; text-align: center; vertical-align: middle; align-items: center; } .main-button-shortcut { position: absolute; font-size: $font-size-small; right: $spacing-05; color: $color-text-contrast; opacity: .7; } &:hover { cursor: pointer; } &:disabled { cursor: not-allowed; opacity: .5; } } .main-button:focus { box-shadow: $focus-border!important; } @property --x { syntax: ''; inherits: false; initial-value: 35.44%; } @property --y { syntax: ''; inherits: false; initial-value: 0%; } .recording-button { margin-top: $spacing-03; filter: $gradient-shadow-primary; background:radial-gradient(127.41% 127.78% at 35.44% 0%, #2BAEF8 23.13%, #3582F6 46.35%, #486DEF 74.48%, #7B9AEA 100%); animation: 0; animation: background-size 6s ease-in-out infinite; animation-play-state: paused; position: relative; z-index: 2; } @keyframes background-size { /* Animate scale and position in and out looping */ 0% { background-size: 100% 100%; background-position: 0% 0%; } 50% { background-size: 150% 150%; background-position: 100% 0%; } 100% { background-size: 100% 100%; background-position: 0% 0%; } } .recording-button:hover { animation-play-state: running!important; } .recording-button:before { content: ""; position: absolute; display: block; top: 0px; left: 0px; width: 100%; height: 100%; box-sizing: border-box; border-radius: $container-border-radius; transition: all .25s ease-in-out; } .recording-button:hover:before { box-shadow: 0px 0px 0px 4px rgba(52, 138, 247, 0.25); } @keyframes pulse-animation { 0% { box-shadow: 0px 0px 0px 2px rgba(52, 138, 247, 0.25); } 25% { box-shadow: 0px 0px 0px 6px rgba(52, 138, 247, 0.25); } 50% { box-shadow: 0px 0px 0px 2px rgba(52, 138, 247, 0.25); } 100% { box-shadow: 0px 0px 0px 2px rgba(52, 138, 247, 0.25); } } @keyframes gradient-animation { 0% { --x: 35.44%; --y: 0%; } 25% { --x: 100%; --y: 30%; } 50% { --x: 70%; --y: 100%; } 75% { --x: 30%; --y: 90%; } 100% { --x: 35.44%; --y: 0%; } } .dashboard-button { background: $color-text-primary; box-shadow: 0px 0px 0px 0px rgba(41, 41, 47, 0.25); transition: all .25s ease-in-out; } .dashboard-button:hover { box-shadow: 0px 0px 0px 4px rgba(41, 41, 47, 0.25); } .alarm-time-button { display: flex; justify-content: center; align-items: center; border-radius: 15px; padding: 4px 8px; position: absolute; color: $color-text-contrast; opacity: .7; font-family: $font-medium; font-size: 12px; left: 6px; svg { margin-top: 4px; margin-right: 4px; width: 14px; } } ================================================ FILE: src/pages/Content/popup/styles/components/_RegionDimensions.scss ================================================ @use '../../../styles/_variables' as *; .region-dimensions { width: 100%; display: flex; gap: 10px; margin-top: 10px; margin-bottom: 10px; } .region-input { flex: 1; position: relative; input { color: $color-text-primary!important; border-radius: $container-border-radius; height: 40px; box-sizing: border-box; position: relative; width: 100%; padding-left: 18px; padding-right: 40px; font-family: $font-medium; background-color: $color-background; &:focus-visible { outline: none; box-shadow: $focus-border; } } span { color: $color-text-secondary; font-family: $font-medium; position: absolute; right: 18px; bottom: 12px; user-select: none; } } ================================================ FILE: src/pages/Content/popup/styles/components/_Switch.scss ================================================ @use "../../../styles/_variables" as *; /* reset */ button { all: unset; } .SwitchRow { width: 100%; display: flex; align-items: center; justify-content: space-between; height: 40px; user-select: none; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; -o-user-select: none; } .SwitchRow * { user-select: none; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; -o-user-select: none; } .SwitchRoot { width: 34px; height: 22px; background-color: $color-border; border-radius: 9999px; position: relative; -webkit-tap-highlight-color: rgba(0, 0, 0, 0); } .SwitchRoot[disabled] { opacity: 0.5; cursor: not-allowed; } .SwitchRoot:focus { box-shadow: $focus-border; } .SwitchRoot[data-state="checked"] { background-color: $color-primary; } .SwitchRoot:hover { cursor: pointer; } .SwitchThumb { display: block; width: 14px; height: 14px; background-color: white; border-radius: 9999px; box-shadow: 0px 1px 10px rgba(0, 0, 0, 0.1); transition: transform 100ms; transform: translateX(4px); will-change: transform; } .SwitchThumb[data-state="checked"] { transform: translateX(16px); } .Label { color: $color-text-secondary; display: inline-block !important; /* Ellipsis */ text-overflow: clip; white-space: nowrap; &:hover { text-overflow: clip; } } .ExperimentalLabel { color: $color-text-contrast; font-size: 12px; background-color: $color-primary; border-radius: 15px; padding: 2px 8px; display: inline-block !important; margin-left: 8px; } .labelDropdownWrap { display: inline-block; vertical-align: middle; position: relative; border-radius: $container-border-radius; box-sizing: border-box; img { display: inline-block; margin-left: 6px; } .labelDropdown { display: inline-block; } &:hover { cursor: pointer; } &::after { content: ""; display: block; position: absolute; top: 0px; left: 0px; width: 100%; height: 100%; border-radius: $container-border-radius; border-left: 8px solid transparent; border-right: 8px solid transparent; border-top: 4px solid transparent; border-bottom: 4px solid transparent; margin-top: -4px; margin-left: -8px; } &:hover::after { border-color: #fff; } &:hover { background-color: #fff; } } .labelDropdownActive { .labelDropdownContent { display: block !important; } img { transform: rotate(180deg); } } .labelDropdownContent { position: absolute; background-color: $color-background; min-width: 160px; box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2); z-index: 9999999999; border-radius: 15px; padding: 8px 0px; margin-top: 4px; border: 1px solid $color-border; display: none; .labelDropdownContentItem { color: $color-text-primary; padding: 12px 16px; text-decoration: none; display: block; &:hover { background-color: $color-light-grey; cursor: pointer; } } } ================================================ FILE: src/pages/Content/popup/styles/components/_Tabs.scss ================================================ @use "../../../styles/_variables" as *; /* Radix tabs navigation */ /* reset */ button, fieldset, input { all: unset; } .TabsRoot { width: 100%; margin: auto; flex: 1 1 auto; display: flex; flex-direction: column; } .TabsList { margin: auto; flex-shrink: 0; display: flex; width: fit-content; align-items: center; } .hiddenTabs { display: none !important; pointer-events: none !important; user-select: none !important; opacity: 0 !important; height: 0 !important; overflow: hidden !important; transition: opacity 0.3s ease !important; } .TabsTrigger { padding-left: 14px; padding-right: 14px; color: $color-text-secondary; user-select: none; cursor: pointer; } .TabsTrigger[data-state="active"] { color: $color-text-primary; } .TabsTrigger:focus-visible { position: relative; box-shadow: $focus-border !important; } .TabsTriggerSpacer { height: 50px; width: 1px; background: $color-border; flex-shrink: 0; margin-left: 8px; margin-right: 8px; } /* Content of the Radix tabs */ .TabsContent { width: 100%; display: block; height: 100%; box-sizing: border-box; flex: 1 1 auto; &::after { content: ""; display: block; clear: both; position: absolute; bottom: 0; left: 0; width: 100%; height: 30px; background: linear-gradient( to bottom, transparent 0%, rgba(255, 255, 255, 0.5) 100% ); pointer-events: none; /* Allow content behind the gradient to be clickable */ } } .TabsContent:focus { outline: none; } .TabsContent:focus-visible { box-shadow: $focus-inner-border; } .TabsContent[data-state="inactive"] { display: none; } /* Pill animation */ .pill-anim { position: absolute; height: 32px; top: 0px; bottom: 0px; margin-top: auto; margin-bottom: auto; border-radius: 30px; background: $color-background; box-shadow: $container-shadow-focus; transition: all 0.2s cubic-bezier(0.25, 0.46, 0.45, 0.94); } /* .TabsList[data-value="record"] { .pill-anim { left: $spacing-03; width: 102px; } } .TabsList[data-value="dashboard"] { .pill-anim { left: 109px; width: 132px; } } */ /* Specific to the top level tabs */ .TabsRoot.tl { height: calc(100% - 40px); margin-top: 40px; } .TabsList.tl { border-radius: 30px; background: $color-light-grey; padding: 6px; font-family: $font-bold; position: relative; } .TabsTrigger.tl { border-radius: 30px; background: transparent; height: 32px; display: flex; align-items: center; padding-left: 16px; padding-right: 17px; z-index: 2; position: relative; } .TabsTrigger.tl[data-state="inactive"]:hover :before { content: ""; position: absolute; display: block; box-sizing: border-box; height: 100%; width: calc(100% - 10px); margin-left: 5px; background: #edeef3; z-index: -2; left: 0px; border-radius: $container-border-radius; } .TabsTriggerIcon { width: 20px; height: 20px; text-align: center; display: inline-flex; align-items: center; justify-content: center; border-radius: 30px; margin-right: $spacing-02; } /* Specific to recording tab context */ .recording-ui { width: 100%; flex: 1 1 auto; display: flex; flex-direction: column; height: 100%; .TabsRoot { margin-top: $spacing-03; } .TabsList { width: 100%; border-bottom: $container-border; margin: auto; justify-content: center; } .TabsTrigger { padding-top: $spacing-03; padding-bottom: $spacing-04; box-sizing: border-box; position: relative; display: block; padding-left: 16px; padding-right: 16px; } .TabsTrigger:hover { background: $color-light-grey; border-top-right-radius: 15px; border-top-left-radius: 15px; } .TabsTrigger:focus-visible { border-radius: 10px 10px 0px 0px !important; } .TabsTrigger[data-state="active"]::after { content: ""; display: block; position: absolute; width: 80%; left: 0px; right: 0px; bottom: 0px; margin: auto; height: 2px; border-radius: 30px; background: $color-primary; } .TabsTrigger[data-state="active"] > .TabsTriggerLabel { color: $color-text-primary !important; } .TabsTriggerLabel { text-align: center; } .TabsTriggerIcon { width: 20px; height: 20px; display: inline-flex; align-items: center; justify-content: center; text-align: center; margin: auto; margin-bottom: $spacing-03; border-radius: 30px; } .TabsContent { background: $color-light-grey; padding: $spacing-05; border-bottom-left-radius: $container-border-radius; border-bottom-right-radius: $container-border-radius; max-height: calc(95vh - 200px); overflow-y: overlay; } span { display: block; } } .video-ui { width: 100%; flex: 1 1 auto; display: flex; flex-direction: column; height: 100%; .TabsRoot { margin-top: $spacing-03; } .TabsList { width: 100%; border-bottom: $container-border; margin: auto; justify-content: space-between; padding-left: $spacing-04; padding-right: $spacing-04; box-sizing: border-box; } .TabsTriggerWrap { display: flex !important; align-items: center; flex-direction: row; justify-content: left; position: relative; display: block; box-sizing: border-box; } .TabsTrigger { padding-top: $spacing-03; padding-bottom: $spacing-04; box-sizing: border-box; position: relative; display: block; padding-left: 20px; padding-right: 20px; } .TabsTrigger:hover { background: $color-light-grey; border-top-right-radius: 15px; border-top-left-radius: 15px; } .TabsTrigger:focus-visible { border-radius: 10px 10px 0px 0px !important; } .TabsTrigger[data-state="active"]::after { content: ""; display: block; position: absolute; width: 80%; left: 0px; right: 0px; bottom: 0px; margin: auto; height: 2px; border-radius: 30px; background: $color-primary; } .TabsTrigger[data-state="active"] > .TabsTriggerLabel { color: $color-text-primary !important; } .TabsTriggerLabel { text-align: center; } .TabsContent { background: $color-light-grey; border-bottom-left-radius: $container-border-radius; border-bottom-right-radius: $container-border-radius; } span { display: block; } .TabsSort { margin-right: $spacing-04; border-radius: $container-border-radius; padding-left: $spacing-03; padding-right: $spacing-03; padding-top: $spacing-03; padding-bottom: $spacing-03; margin-bottom: 5px; } .TabsSort:hover { cursor: pointer; background: $color-light-grey; } .TabsSortLabel { display: flex; flex-direction: row; justify-content: right; align-items: center; color: $color-text-secondary; white-space: nowrap; } .TabsSortLabel img { margin-left: $spacing-03; } .TabsSort:focus-visible { box-shadow: $focus-border; outline: none !important; } } .projectActiveBanner { display: flex; align-items: center; justify-content: space-between; background-color: #29292f; /* dark background */ color: white; border-radius: 100px; /* pill shape */ padding: 10px 16px; font-weight: 600; font-size: 15px; line-height: 1.3; max-width: 80%; user-select: none; cursor: default; gap: 16px; top: 41px; z-index: 999999; margin: auto; left: 0; right: 0; position: absolute; } .projectActiveBannerLeft { /* text container */ white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .projectActiveBannerRight { display: flex; align-items: center; gap: 8px; } .projectActiveBannerDivider { width: 1px; height: 24px; background-color: #44444d; /* subtle divider color */ } .projectActiveBannerClose { cursor: pointer; display: flex; align-items: center; justify-content: center; } .projectActiveBannerClose img { width: 14px; height: 14px; opacity: 0.6; &:hover { opacity: 1; } } ================================================ FILE: src/pages/Content/popup/styles/components/_TimeSetter.scss ================================================ @use '../../../styles/_variables' as *; .time-set-parent { width: 100%; display: flex; gap: 10px; margin-top: 10px; margin-bottom: 10px; } .time-set-input { flex: 1; position: relative; input { border-radius: $container-border-radius; height: 40px; box-sizing: border-box; position: relative; width: 100%; padding-left: 18px; padding-right: 40px; font-family: $font-medium; background-color: $color-background; -webkit-appearance: textfield; -moz-appearance: textfield; appearance: textfield; &:focus-visible { outline: none; box-shadow: $focus-border; } } span { color: $color-text-secondary; font-family: $font-medium; position: absolute; right: 18px; bottom: 12px; user-select: none; } } ================================================ FILE: src/pages/Content/popup/styles/components/_Tooltip.scss ================================================ @use "../../../styles/_variables" as *; .TooltipContent { border-radius: $container-border-radius; background-color: $color-text-primary; padding: 10px 15px; font-size: 12px; line-height: 1; font-family: $font-medium; z-index: 99999999 !important; color: $color-text-contrast; box-shadow: hsl(206 22% 7% / 35%) 0px 10px 38px -10px, hsl(206 22% 7% / 20%) 0px 10px 20px -15px; user-select: none; transition: opacity 0.3 ease-in-out !important; will-change: transform, opacity; animation-duration: 400ms; animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1); will-change: transform, opacity; } .hide-tooltip { display: none !important; } .tooltip-tall { margin-bottom: 20px; } .tooltip-small { margin-bottom: 5px; } .TooltipContent[data-state="delayed-open"][data-side="top"] { animation-name: slideDownAndFade; } .TooltipContent[data-state="delayed-open"][data-side="right"] { animation-name: slideLeftAndFade; } .TooltipContent[data-state="delayed-open"][data-side="bottom"] { animation-name: slideUpAndFade; } .TooltipContent[data-state="delayed-open"][data-side="left"] { animation-name: slideRightAndFade; } @keyframes slideUpAndFade { from { opacity: 0; transform: translateY(2px); } to { opacity: 1; transform: translateY(0); } } @keyframes slideRightAndFade { from { opacity: 0; transform: translateX(-2px); } to { opacity: 1; transform: translateX(0); } } @keyframes slideDownAndFade { from { opacity: 0; transform: translateY(-2px); } to { opacity: 1; transform: translateY(0); } } @keyframes slideLeftAndFade { from { opacity: 0; transform: translateX(2px); } to { opacity: 1; transform: translateX(0); } } #screenity-ui [data-radix-popper-content-wrapper] { z-index: $z-index-max !important; } .override { display: none !important; opacity: 0 !important; visibility: hidden !important; } ================================================ FILE: src/pages/Content/popup/styles/components/_VideoItem.scss ================================================ @use "../../../styles/_variables" as *; .video-item-root { width: calc(100% - 2 * #{$spacing-03}); border-radius: 15px; padding: $spacing-03; display: block; .video-item { width: 100%; display: flex; flex-direction: row; align-items: center; justify-content: space-between; } .video-item-left { display: flex; flex-grow: 1; flex-direction: row; align-items: center; min-width: 0; justify-content: left; } .video-item-thumbnail { min-width: 48px; width: 48px; height: 38px; background: grey; border-radius: 5px; margin-right: $spacing-04; } .video-item-info { display: block; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; padding-right: $spacing-02; } .video-item-info-title { color: $color-text-primary; white-space: nowrap; text-overflow: ellipsis; overflow: hidden; } .video-item-info-date { margin-top: $spacing-02; color: $color-text-secondary; font-size: $font-size-small; white-space: nowrap; text-overflow: ellipsis; overflow: hidden; } } .video-item-root:hover { cursor: pointer; background: #e9eaee; } .video-item-root:focus-visible { box-shadow: $focus-border; outline: none !important; } /* Actions */ .video-item-right { display: flex; align-items: center; justify-content: right; gap: $spacing-03; min-width: max-content; opacity: 0; .copy-link { background: $color-background; height: 32px; width: 32px; border-radius: 10px; display: flex; flex-direction: row; justify-content: center; align-items: center; z-index: 999999; } .copy-link:focus-visible { box-shadow: $focus-border; outline: none !important; } .more-actions { height: 32px; width: 32px; background: $color-background; text-align: center; border-radius: 10px; display: flex; align-items: center; justify-content: center; } .more-actions:focus-visible { box-shadow: $focus-border; outline: none !important; } } .video-item-root:hover, .video-item-root:focus-visible { .video-item-right { opacity: 1; } } // .video-item-right:focus-within { // opacity: 1; // } ================================================ FILE: src/pages/Content/popup/styles/layout/_Announcement.scss ================================================ @use "../../../styles/_variables" as *; .announcement { width: 100%; top: 0px; left: 0px; padding-bottom: 24px; } .announcement-wrap { width: 85%; margin: auto; } .announcement-hero { width: 100%; margin-bottom: 16px; margin-top: 44px; img { width: 100%; border-radius: 15px; } } .announcement-details { text-align: center; } .announcement-title { font-family: $font-bold; font-size: 16px; color: $color-text-primary; letter-spacing: -0.8px; margin-bottom: 12px; text-align: center; } .announcement-description { font-family: $font-medium; font-size: 14px; color: $color-text-secondary; text-align: center; margin-bottom: 24px; line-height: 1.6; letter-spacing: -0.6px; a { text-decoration: none !important; color: $color-primary !important; cursor: pointer !important; } } .announcement-cta { display: flex; align-items: center; justify-content: center; gap: 8px; width: 100%; height: 44px; border-radius: $container-border-radius; background: $gradient-primary; color: $color-text-contrast; font-family: $font-medium; font-size: 14px; cursor: pointer; transition: all 0.2s ease-in-out; animation: background-size 6s ease-in-out infinite; animation-play-state: paused; filter: $gradient-shadow-primary; &:hover { animation-play-state: running !important; } } @keyframes background-size { /* Animate scale and position in and out looping */ 0% { background-size: 100% 100%; background-position: 0% 0%; } 50% { background-size: 150% 150%; background-position: 100% 0%; } 100% { background-size: 100% 100%; background-position: 0% 0%; } } ================================================ FILE: src/pages/Content/popup/styles/layout/_PopupContainer.scss ================================================ @use "../../../styles/_variables" as *; .popup-container:hover .popup-controls { opacity: 1; } .open { opacity: 1 !important; } .popup-drag-head { position: fixed; top: 0px; left: 0px; width: 100%; height: 100px; z-index: 1; border-radius: 30px 30px 0px 0px; opacity: 0; } .popup-controls { opacity: 0; position: absolute; top: -10px; right: -10px; box-sizing: border-box; border-radius: $container-border-radius; border: 1px solid $color-border; display: flex; align-items: center; justify-content: center; gap: 8px; z-index: 999999999; background: rgba(240, 238, 238, 1); backdrop-filter: blur(10px); padding-left: 8px; padding-right: 8px; padding-top: 6px; padding-bottom: 6px; transition: opacity 0.2s cubic-bezier(0.4, 0, 0.2, 1); .popup-control { svg { color: $color-icon; margin-bottom: -2px; } } .popup-grab { cursor: grab; } .popup-close { cursor: pointer; } } .tempimg { height: 100%; opacity: 1; position: fixed; right: 0px; top: 20px; } .container { width: 100%; height: 100%; position: fixed; top: 0px; left: 0px; z-index: 999999999; font-family: $font-medium; font-size: $font-size-normal; } .ToolbarDragging .popup-container { transform: scale(1.02); filter: drop-shadow(0px 20px 50px rgba(0, 0, 0, 0.4)) !important; } /* Recording popup parent */ .popup-container { width: 356px; position: fixed; top: 32px; right: 28px; z-index: $z-index-max; filter: $container-shadow; pointer-events: all; transition: transform 0.25s cubic-bezier(0.61, 0.11, 0.08, 0.96), filter 0.2s cubic-bezier(0.61, 0.11, 0.08, 0.96); } .popup-container::before { content: ""; display: block; width: 100%; height: 100%; transition: 2s; background: $color-background; background-clip: content-box; -webkit-mask-image: radial-gradient( circle at center top, transparent 31px, #000 31px ); mask-image: radial-gradient( circle at center top, transparent 31px, #000 31px ); background-position: center bottom 50px; border-radius: $container-border-radius; position: absolute; top: 0px; left: 0px; } /* .popup-shape { width: 100%; height: 100%; background: $color-background; background-clip: content-box; -webkit-mask-image: radial-gradient(circle at center top, transparent 31px, #000 31px); mask-image: radial-gradient(circle at center top, transparent 31px, #000 31px); background-position: center bottom 50px; border-radius: $container-border-radius; position: relative; } */ .popup-cutout { width: 44px; height: 44px; border-radius: 50%; text-align: center; position: absolute; display: flex; justify-content: center; align-items: center; top: -22px; left: 0px; right: 0px; margin: auto; } .popup-cutout img { text-align: center; margin: auto; display: inline-block; width: 100%; border-radius: 50%; } /* Recording nav area */ .popup-nav { width: 100%; position: relative; } /* Recording content area */ .popup-content { position: relative; width: 100%; height: 100%; border-radius: $container-border-radius; overflow: hidden; } .waveform { width: 100%; margin-top: $spacing-04; margin-bottom: $spacing-04; } .popup-content-divider { width: 100%; height: 1px; background: $color-border; margin-top: $spacing-04; margin-bottom: $spacing-04; } .popup-warning { display: flex; width: calc(100% + 32px); height: 80px; justify-content: space-between; align-items: center; position: relative; overflow: hidden; background-color: rgba(56, 126, 247, 0.1); margin-left: -16px; margin-top: -16px; margin-bottom: 8px; .popup-warning-right { color: $color-primary; font-family: $font-medium; width: 90px; } } .popup-warning-left, .popup-warning-right { width: 50px; display: flex; align-items: center; text-align: center; height: 100%; justify-content: center; svg { color: $color-primary; } } .popup-warning-right { cursor: pointer; } .popup-warning-middle { flex: 1; .popup-warning-title { font-family: $font-bold; color: $color-text-primary; } .popup-warning-description { font-family: $font-medium; color: $color-text-secondary; margin-top: 4px; } } .permission-button { background: rgba(48, 128, 248, 0.1); border-radius: $container-border-radius; color: $color-primary; display: flex; align-items: center; justify-content: center; width: 100%; height: 44px; gap: 8px; margin-top: $spacing-03; margin-bottom: $spacing-03; &:first-child { margin-top: $spacing-02 !important; } &:last-child { margin-bottom: $spacing-02 !important; } &:hover { background: rgba(48, 128, 248, 0.15); cursor: pointer; } svg { color: $color-primary; } } .HelpSection { position: absolute; left: 0px; right: 0px; margin: auto; bottom: -40px; background: #edeef2; border-radius: 30px; padding: 4px 12px; text-align: center; font-family: $font-medium; color: $color-text-secondary; display: flex; align-items: center; justify-content: center; gap: 6px; width: fit-content; .HelpIcon { margin-top: 3px; } &:hover { cursor: pointer; background: #fefeff; } } ================================================ FILE: src/pages/Content/popup/styles/layout/_Settings.scss ================================================ @use '../../../styles/_variables' as *; .CollapsibleTrigger { margin-top: $spacing-04; font-weight: $font-bold; padding-top: $spacing-02; padding-bottom: $spacing-02; padding-left: $spacing-04; padding-right: $spacing-04; margin-left: auto; margin-right: auto; text-align: center; display: block; border-radius: $container-border-radius; } .CollapsibleTrigger:focus-visible { box-shadow: $focus-border; } .CollapsibleLabel { color: $color-text-secondary; font-weight: $font-bold; text-align: center; display: inline-block; } .CollapsibleLabel img { margin-left: 4px; } .CollapsibleTrigger:hover { cursor: pointer; background: #FFF; } .CollapsibleRoot[data-state='open'] > .CollapsibleTrigger > .CollapsibleLabel img { transform: scaleY(-1); margin-bottom: 2px; } ================================================ FILE: src/pages/Content/popup/styles/layout/_SettingsMenu.scss ================================================ @use "../../../styles/_variables" as *; .DropdownMenuContent, .DropdownMenuSubContent { min-width: 200px; background-color: white; margin-top: 4px; margin-right: 8px; padding-top: 12px; padding-bottom: 12px; border-radius: 15px; z-index: 99999; font-family: $font-medium; color: $color-text-primary; box-shadow: 0px 10px 38px -10px rgba(22, 23, 24, 0.35), 0px 10px 20px -15px rgba(22, 23, 24, 0.2); animation-duration: 400ms; animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1); will-change: transform, opacity; } .DropdownMenuContent[data-side="top"], .DropdownMenuSubContent[data-side="top"] { animation-name: slideDownAndFade; } .DropdownMenuContent[data-side="right"], .DropdownMenuSubContent[data-side="right"] { animation-name: slideLeftAndFade; } .DropdownMenuContent[data-side="bottom"], .DropdownMenuSubContent[data-side="bottom"] { animation-name: slideUpAndFade; } .DropdownMenuContent[data-side="left"], .DropdownMenuSubContent[data-side="left"] { animation-name: slideRightAndFade; } .ItemIndicator, .ItemIndicatorArrow { position: absolute; right: $spacing-04; width: 18px; height: 18px; background: $color-primary; border-radius: 50%; display: inline-flex; align-items: center; justify-content: center; } .ItemIndicatorArrow { background: transparent !important; } .DropdownMenuItem, .DropdownMenuCheckboxItem, .DropdownMenuRadioItem, .DropdownMenuSubTrigger { font-size: 14px; line-height: 1; display: flex; align-items: center; height: 40px; padding: 0 5px; position: relative; padding-left: 22px; padding-right: 22px; user-select: none; outline: none; &:hover { background-color: $color-light-grey !important; cursor: pointer; } } .DropdownMenuSubTrigger[data-state="open"] { background-color: var(--violet-4); color: var(--violet-11); } .DropdownMenuItem[data-disabled], .DropdownMenuCheckboxItem[data-disabled], .DropdownMenuRadioItem[data-disabled], .DropdownMenuSubTrigger[data-disabled] { color: $color-text-secondary !important; cursor: not-allowed; background-color: $color-light-grey !important; } .DropdownMenuItem[data-highlighted], .DropdownMenuCheckboxItem[data-highlighted], .DropdownMenuRadioItem[data-highlighted], .DropdownMenuSubTrigger[data-highlighted] { background-color: var(--violet-9); color: var(--violet-1); } .DropdownMenuLabel { padding-left: 25px; font-size: 12px; line-height: 25px; color: var(--mauve-11); } .DropdownMenuSeparator { height: 1px; background-color: var(--violet-6); margin: 5px; } .DropdownMenuItemIndicator { position: absolute; left: 0; width: 25px; display: inline-flex; align-items: center; justify-content: center; } .DropdownMenuArrow { fill: white; } .IconButton { font-family: inherit; border-radius: 100%; display: inline-flex; align-items: center; justify-content: center; margin-top: 4px; cursor: pointer; svg { color: #9797a4; } } .IconButton:hover { background-color: var(--violet-3); } .IconButton:focus { } .RightSlot { margin-left: auto; padding-left: 20px; color: var(--mauve-11); } [data-highlighted] > .RightSlot { color: white; } [data-disabled] .RightSlot { color: var(--mauve-8); } @keyframes slideUpAndFade { from { opacity: 0; transform: translateY(2px); } to { opacity: 1; transform: translateY(0); } } @keyframes slideRightAndFade { from { opacity: 0; transform: translateX(-2px); } to { opacity: 1; transform: translateX(0); } } @keyframes slideDownAndFade { from { opacity: 0; transform: translateY(-2px); } to { opacity: 1; transform: translateY(0); } } @keyframes slideLeftAndFade { from { opacity: 0; transform: translateX(2px); } to { opacity: 1; transform: translateX(0); } } ================================================ FILE: src/pages/Content/popup/styles/layout/_VideosTab.scss ================================================ @use "../../../styles/_variables" as *; .video-ui { position: relative; /* Blur the background */ // &:before { // content: ''; // position: absolute; // backdrop-filter: blur(5px); // top: 0px; // left: 0px; // width: 100%; // height: 100%; // background: rgba(255, 255, 255, 0.5); // z-index: 999; // } } .blurred { /* Blur the background */ &:before { content: ""; position: absolute; backdrop-filter: blur(5px); top: 0px; left: 0px; width: 100%; height: 100%; background: rgba(255, 255, 255, 0.5); z-index: 999; } } .videos-list { padding-bottom: 10px !important; max-height: calc(96vh - 260px); overflow-y: overlay; padding: $spacing-05; } .bottom-section { width: 100%; bottom: 0px; left: 0px; box-sizing: border-box; padding-top: $spacing-03 !important; padding-bottom: $spacing-04 !important; z-index: 999999; padding: $spacing-05; } .ModalSoon { display: flex; flex-direction: column; align-items: center; justify-content: center; height: fit-content; width: 70%; padding: 20px $spacing-05; position: absolute; left: 0px; right: 0px; top: 0px; bottom: 0px; margin: auto; border-radius: $container-border-radius; background: white; box-shadow: 0px 4px 100px 0px rgba(0, 0, 0, 0.15); z-index: 9999999; } .strong { box-shadow: 0px 4px 100px -20px rgba(0, 0, 0, 0.75) !important; } .ModalSoonEmoji { font-size: 20px; margin-bottom: $spacing-03; } .ModalSoonTitle { font-weight: 700; color: $color-text-primary; margin-bottom: $spacing-03; text-align: center; } .ModalSoonDescription { text-align: center; color: $color-text-secondary; text-align: center; margin-bottom: $spacing-03; } .ModalSoonButton { margin-top: $spacing-03; color: $color-text-contrast; text-align: center; border-radius: 30px; background: $gradient-primary; padding: $spacing-03 $spacing-05; &:hover { cursor: pointer; } } .spinner-container, .empty-state { display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 60px 0; color: #6e7684; font-size: 14px; } .spinner { width: 32px; height: 32px; border: 3px solid rgba(0, 0, 0, 0.1); border-top-color: #6e7684; border-radius: 50%; animation: spin 0.8s linear infinite; margin-bottom: 10px; } @keyframes spin { to { transform: rotate(360deg); } } ================================================ FILE: src/pages/Content/popup/styles/layout/_Welcome.scss ================================================ @use "../../../styles/_variables" as *; .welcome-title { font-family: $font-bold; font-size: 18px; color: $color-text-primary; letter-spacing: -0.8px; margin-bottom: 12px; text-align: center; } .welcome-description { font-family: $font-medium; font-size: 16px; color: $color-text-secondary; text-align: center; margin-bottom: 20px; line-height: 1.6; letter-spacing: -0.6px; a { text-decoration: none !important; color: $color-primary !important; cursor: pointer !important; } } .welcome-cta { display: flex; align-items: center; justify-content: center; gap: 8px; width: 100%; height: 44px; border-radius: $container-border-radius; background: white; border: 1px solid $color-border; color: $color-text-primary; font-family: $font-medium; font-size: 14px; cursor: pointer; transition: all 0.2s ease-in-out; filter: drop-shadow(0px 1px 2px rgba(53, 87, 98, 0.1)); } .welcome-content { background: $color-light-grey; border-bottom-left-radius: $container-border-radius; border-bottom-right-radius: $container-border-radius; max-height: calc(95vh - 300px); overflow-y: overlay; padding-top: 20px; padding-bottom: 20px; border-top: 1px solid #e8e8e8; } .welcome-content-wrap { width: 85%; margin: auto; } .welcome-content-title { font-family: $font-bold; font-size: 14px; color: $color-text-primary; margin-bottom: 8px; margin-top: 8px; text-align: center; line-height: 1.4; } .welcome-feature-list { display: flex; flex-direction: column; gap: 16px; margin-top: 12px; } .welcome-feature-item { display: flex; align-items: center; gap: 12px; } .welcome-feature-icon { width: 20px; height: 20px; background: rgba(48, 128, 248, 0.1); border-radius: 50%; display: flex; align-items: center; justify-content: center; } .welcome-feature-icon-text { font-size: 14px; color: $color-text-secondary; font-family: $font-medium; } .welcome-video { margin: 25px 0 !important; width: 100%; } .video-wrapper { border-radius: 10px; } .welcome-support { font-family: $font-medium; font-size: 12px; color: $color-text-secondary !important; text-align: center; margin-top: 20px; display: flex; align-items: center; justify-content: center; gap: 6px; text-decoration: none !important; img { width: 20px; } } ================================================ FILE: src/pages/Content/region/Region.jsx ================================================ import React, { useRef, useContext, useEffect } from "react"; import { Rnd } from "react-rnd"; // Context import { contentStateContext } from "../context/ContentState"; const ResizableBox = () => { const regionRef = useRef(null); const parentRef = useRef(null); const cropRef = useRef(null); const recordingRef = useRef(null); const [contentState, setContentState] = useContext(contentStateContext); useEffect(() => { recordingRef.current = contentState.recording; }, [contentState.recording]); // Check for contentState.regionDimensions to update the Rnd component width and height useEffect(() => { if (contentState.recordingType != "region") return; if (!contentState.customRegion) return; if (regionRef.current === null) return; if ( contentState.regionWidth === 0 || contentState.regionWidth === undefined ) return; if ( contentState.regionHeight === 0 || contentState.regionHeight === undefined ) return; if (contentState.regionX === undefined) return; if (contentState.regionY === undefined) return; if (contentState.fromRegion) return; // Get parent element dimensions const parentWidth = parentRef.current.offsetWidth; const parentHeight = parentRef.current.offsetHeight; // Calculate maximum size that fits within parent element const maxWidth = parentWidth - contentState.regionX; const maxHeight = parentHeight - contentState.regionY; const newWidth = Math.min(contentState.regionWidth, maxWidth); const newHeight = Math.min(contentState.regionHeight, maxHeight); // Update content state with new size setContentState((prevContentState) => ({ ...prevContentState, regionWidth: newWidth, regionHeight: newHeight, fromRegion: true, })); chrome.storage.local.set({ regionWidth: newWidth, regionHeight: newHeight, }); regionRef.current.updateSize({ width: newWidth, height: newHeight, x: contentState.regionX, y: contentState.regionY, }); setCropTarget(); }, [ contentState.recordingType, contentState.customRegion, contentState.regionWidth, contentState.regionHeight, contentState.regionX, contentState.regionY, ]); const setCropTarget = async () => { const target = await CropTarget.fromElement(cropRef.current); setContentState((prevContentState) => ({ ...prevContentState, cropTarget: target, })); }; const handleResize = (e, direction, ref, delta, position) => { // Get numeric values of width and height const width = parseInt(ref.style.width, 10); const height = parseInt(ref.style.height, 10); // Update content state setContentState((prevContentState) => ({ ...prevContentState, regionWidth: width, regionHeight: height, regionX: position.x, regionY: position.y, fromRegion: true, })); chrome.storage.local.set({ regionWidth: width, regionHeight: height, regionX: position.x, regionY: position.y, }); setCropTarget(); }; const handleMove = (e, d) => { setContentState((prevContentState) => ({ ...prevContentState, regionX: d.x, regionY: d.y, fromRegion: true, })); chrome.storage.local.set({ regionX: d.x, regionY: d.y, }); setCropTarget(); }; useEffect(() => { setCropTarget(); }, []); useEffect(() => { const parent = parentRef.current; if (!parent) return; const handleContextMenu = (e) => { if (e.target.className.includes("resize-handle")) { e.preventDefault(); } }; parent.addEventListener("contextmenu", handleContextMenu); return () => { parent.removeEventListener("contextmenu", handleContextMenu); }; }, []); // Shadow DOM event forwarding: mouse events from inside the Shadow DOM // get retargeted to the shadow host and stop at `document` — they never // reach `window`. re-resizable listens on `window`, so we forward the // events that originate from our shadow container. useEffect(() => { const shadowHostId = "screenity-root-container"; const forwardToWindow = (e) => { if ( e.target?.id === shadowHostId || e.target?.closest?.("#" + shadowHostId) ) { window.dispatchEvent( new MouseEvent(e.type, { bubbles: false, cancelable: true, clientX: e.clientX, clientY: e.clientY, screenX: e.screenX, screenY: e.screenY, button: e.button, buttons: e.buttons, }) ); } }; document.addEventListener("mouseup", forwardToWindow); document.addEventListener("mousemove", forwardToWindow); return () => { document.removeEventListener("mouseup", forwardToWindow); document.removeEventListener("mousemove", forwardToWindow); }; }, []); return (
{ // showExtension false, as long as not clicking on the region if ( e.target.className.indexOf("resize-handle") === -1 && e.target.className.indexOf("react-draggable") === -1 && e.target.className.indexOf("region-rect") === -1 ) { // setContentState((prevContentState) => ({ // ...prevContentState, // showExtension: false, // })); } }} ref={parentRef} >
, top:
, topRight:
, right:
, bottomRight:
, bottom:
, bottomLeft:
, left:
, }} bounds="parent" onResizeStop={handleResize} onDragStop={handleMove} disableDragging={ contentState.recording || contentState.drawingMode || contentState.blurMode } // Disable dragging when recording enableResizing={ !contentState.recording && !contentState.drawingMode && !contentState.blurMode } // Disable resizing when recording >
); }; export default ResizableBox; ================================================ FILE: src/pages/Content/region/components/RegionHandles.jsx ================================================ import React, { useState, useContext, useRef, useEffect } from "react"; import { Rnd } from "react-rnd"; // Context import { contentStateContext } from "../../context/ContentState"; const ResizableBox = () => { return (
), top: (
), topRight: (
), right: (
), bottomRight: (
), bottom: (
), bottomLeft: (
), left: (
), }} >
); }; export default ResizableBox; ================================================ FILE: src/pages/Content/region/layout/CameraToolbar.jsx ================================================ import React, { useState, useEffect, useContext } from "react"; import * as Toolbar from "@radix-ui/react-toolbar"; import { CameraCloseIcon, CameraMoreIcon } from "../../toolbar/components/SVG"; // Context import { contentStateContext } from "../../context/ContentState"; const CameraToolbar = () => { const [contentState, setContentState] = useContext(contentStateContext); return ( { setContentState((prevContentState) => ({ ...prevContentState, cameraActive: false, })); chrome.storage.local.set({ cameraActive: false }); }} > ); }; export default CameraToolbar; ================================================ FILE: src/pages/Content/region/layout/CameraWrap.jsx ================================================ import React, { useEffect, useContext, useRef, useState } from "react"; import { Rnd } from "react-rnd"; // Context import { contentStateContext } from "../../context/ContentState"; import CameraToolbar from "./CameraToolbar"; import ResizeHandle from "../components/ResizeHandle"; const CameraWrap = (props) => { const [contentState, setContentState] = React.useContext(contentStateContext); const cameraRef = React.useRef(); const [cx, setCx] = useState(200); const [cy, setCy] = useState(200); const [w, setW] = useState(200); const [h, setH] = useState(200); const updateUIPosition = () => { const ref = props.shadowRef.current.shadowRoot.querySelector(".camera-draggable"); const circleCenterX = ref.getBoundingClientRect().left + ref.getBoundingClientRect().width / 2; const circleCenterY = ref.getBoundingClientRect().top + ref.getBoundingClientRect().height / 2; const circleRadius = ref.getBoundingClientRect().width / 2; const squareBottomRightX = ref.getBoundingClientRect().left + ref.getBoundingClientRect().width; const squareBottomRightY = ref.getBoundingClientRect().top + ref.getBoundingClientRect().height; const handle = props.shadowRef.current.shadowRoot.querySelector(".camera-resize"); const toolbar = props.shadowRef.current.shadowRoot.querySelector(".camera-toolbar"); // Calculate 'r' using the formula we derived earlier const c = Math.sqrt( Math.pow(circleCenterX - squareBottomRightX, 2) + Math.pow(circleCenterY - squareBottomRightY, 2) ); const a = circleRadius / Math.sqrt(2); const r = (c + Math.sqrt(c ** 2 + 16 * a ** 2)) / 4; // Calculate the handle position const x = r - r / Math.sqrt(2); const y = r - r / Math.sqrt(2); // Position the handle element to the calculated coordinates handle.style.bottom = `${y - handle.getBoundingClientRect().width / 2}px`; handle.style.right = `${x - handle.getBoundingClientRect().height / 2}px`; toolbar.style.top = `${y - toolbar.getBoundingClientRect().width / 2}px`; toolbar.style.left = `${x - toolbar.getBoundingClientRect().height / 2}px`; }; const saveDimensions = () => { const ref = props.shadowRef.current.shadowRoot.querySelector(".camera-draggable"); setContentState((prevContentState) => ({ ...prevContentState, cameraDimensions: { size: ref.getBoundingClientRect().width, x: ref.getBoundingClientRect().x, y: ref.getBoundingClientRect().y, }, })); chrome.storage.local.set({ cameraDimensions: { size: ref.getBoundingClientRect().width, x: ref.getBoundingClientRect().x, y: ref.getBoundingClientRect().y, }, }); }; useEffect(() => { if (!cameraRef.current) return; if (!props.shadowRef.current.shadowRoot.querySelector(".camera-resize")) return; if (!props.shadowRef.current.shadowRoot.querySelector(".camera-toolbar")) return; updateUIPosition(); }, [cameraRef.current]); return (
, }} minHeight={150} minWidth={150} enableResizing={{ bottom: false, bottomRight: true, bottomLeft: false, left: false, right: false, top: false, topRight: false, topLeft: false, }} onResize={(e, direction, ref, delta, position) => { updateUIPosition(); }} onResizeStop={(e, direction, ref, delta, position) => { saveDimensions(); }} onDragStop={(node, x, y) => { saveDimensions(); }} lockAspectRatio={1} bounds={"window"} >
); }; export default CameraWrap; ================================================ FILE: src/pages/Content/region/styles/_Region.scss ================================================ @use '../../styles/_variables' as *; .box-hole { position: absolute; top: 0; left: 0; width: 100%; height: 100%; z-index: 9999999999; } .resize-handle { position: absolute; width: 10px; height: 10px; border-radius: 50%; background-color: white; border: 2px solid rgba(0, 0, 0, .5); box-sizing: border-box; } .resize-handle.top-left { bottom: 0; left: 0; top: 0; right: 0; margin: auto; cursor: nwse-resize; } .resize-handle.top { top: 0; left: 50%; transform: translateX(-50%); cursor: ns-resize; } .resize-handle.top-right { bottom: 0; left: 0; top: 0; right: 0; margin: auto; cursor: nesw-resize; } .resize-handle.right { top: 50%; right: 0; transform: translateY(-50%); cursor: ew-resize; } .resize-handle.bottom-right { bottom: 0; left: 0; top: 0; right: 0; margin: auto; cursor: nwse-resize; } .resize-handle.bottom { bottom: 0; left: 50%; transform: translateX(-50%); cursor: ns-resize; } .resize-handle.bottom-left { bottom: 0; left: 0; top: 0; right: 0; margin: auto; cursor: nesw-resize; } .resize-handle.left { top: 50%; left: 0; transform: translateY(-50%); cursor: ew-resize; } .region-recording * { pointer-events: none!important; } ================================================ FILE: src/pages/Content/region/styles/layout/_CameraToolbar.scss ================================================ @use '../../../styles/_variables' as *; .camera-toolbar { display: flex; align-items: center; padding-left: 4px; padding-right: 4px; transition: opacity .25s cubic-bezier(.61,.11,.08,.96); min-width: max-content; background-color: rgba(30, 30, 30, .8); box-shadow: 0 2px 10px rgba(0, 0, 0, .15); height: 28px; position: absolute; left: 10px; top: 10px; border-radius: $container-border-radius; backdrop-filter: blur(10px); z-index: $z-index-max; opacity: 0; border: 3px solid rgba(255, 255, 255, .2); } .camera-draggable:hover { .camera-toolbar, .camera-resize { opacity: 1!important; } } .camera-toolbar:hover, .camera-resize:hover { opacity: 1!important; } .CameraToolbarSeparator { width: 1px; height: 18px; background-color: rgba(255, 255, 255, .3); margin: 0 4px; } .CameraToggleItem, .CameraToolbarButton { display: flex; justify-content: center; align-items: center; color: #000; height: 22px; width: 22px; text-align: center; font-size: 13px; line-height: 1; border-radius: 50%; transition: background-color .25s ease-in-out; background-color: rgba(124, 139, 165, 0); svg { color: $color-icon; } &:hover { background-color: rgba(124, 139, 165, 0.2)!important; cursor: pointer; } &:disabled { opacity: 0.5; pointer-events: none; } &[data-state='on'] { color: #FFF; svg { color: #FFF; } } } .CameraToggleItem:hover, .CameraToggleButton:hover { cursor: pointer; } .CameraToggleItem:focus-visible, .CameraToggleButton:focus-visible { position: relative; box-shadow: $focus-border; } .CameraToggleGroup { display: flex; align-items: center; justify-content: center; } .CameraToggleGroup, .CameraToolbarSeparator { display: none; } ================================================ FILE: src/pages/Content/region/styles/layout/_CameraWrap.scss ================================================ @use '../../../styles/_variables' as *; .camera-draggable { width: 100%; height: 100%; transform-origin: left top; border-radius: 50%; } .camera-grab { width: 100%; height: 100%; position: absolute; border-radius: 50%; z-index: 99999999!important; cursor: grab; } .camera-flipped { transform: scaleX(-1); } ================================================ FILE: src/pages/Content/shortcuts/Shortcuts.jsx ================================================ import React, { useEffect, useContext, useRef } from "react"; // Context import { contentStateContext } from "../context/ContentState"; import { undoCanvas, redoCanvas, saveCanvas } from "../canvas/modules/History"; const Shortcuts = ({ shortcuts }) => { const [contentState, setContentState] = useContext(contentStateContext); const contentStateRef = useRef(contentState); useEffect(() => { contentStateRef.current = contentState; }, [contentState]); /* For the record, this is the shortcuts object: shortcuts: { "start-recording": "ctrl+shift+1", "stop-recording": "ctrl+shift+2", "pause-recording": "ctrl+shift+3", "resume-recording": "ctrl+shift+4", "dismiss-recording": "ctrl+shift+5", "restart-recording": "ctrl+shift+6", "toggle-drawing-mode": "ctrl+shift+7", }, */ // Set up all the hotkeys programmatically from the shortcuts object, without using useEffect /* useHotkeys(shortcuts["toggle-drawing-mode"], () => { setContentState((prevContentState) => ({ ...prevContentState, drawingMode: !prevContentState.drawingMode, })); }); */ // in-app shortcuts for screenity tools useEffect(() => { const getDeepActiveElement = () => { let active = document.activeElement; while (active && active.shadowRoot && active.shadowRoot.activeElement) { active = active.shadowRoot.activeElement; } return active; }; const isEditableElement = (element) => { if (!element) return false; if (element.isContentEditable) return true; const tag = element.tagName ? element.tagName.toLowerCase() : ""; return tag === "input" || tag === "textarea" || tag === "select"; }; const isTextEditingActive = () => { const state = contentStateRef.current; if (state?.tool !== "text") return false; const activeObj = state?.canvas?.getActiveObject?.(); return Boolean(activeObj && activeObj.isEditing); }; const shouldHandleShortcut = (event) => { const state = contentStateRef.current; if (!state?.drawingMode && !state?.blurMode) return false; if (event.altKey || event.ctrlKey || event.metaKey) return false; const active = getDeepActiveElement(); if (isEditableElement(active)) return false; if (isTextEditingActive()) return false; return true; }; const getDigitKey = (event) => { if (event.code && event.code.startsWith("Digit")) { return event.code.replace("Digit", ""); } if (event.code && event.code.startsWith("Numpad")) { return event.code.replace("Numpad", ""); } if (event.key >= "0" && event.key <= "9") { return event.key; } return null; }; const setTool = (tool, nextShape) => { setContentState((prev) => ({ ...prev, tool, ...(nextShape ? nextShape : {}), })); }; const clearDrawings = () => { const state = contentStateRef.current; if (!state?.canvas) return; state.canvas.clear(); state.canvas.renderAll(); state.canvas.requestRenderAll(); saveCanvas(state, setContentState); }; const clearBlurMasks = () => { const blurredElements = document.querySelectorAll(".screenity-blur"); blurredElements.forEach((element) => { element.classList.remove("screenity-blur"); }); }; const getShadowRoot = () => contentStateRef.current?.shadowRef?.shadowRoot || null; const toggleColorPicker = () => { const shadowRoot = getShadowRoot(); const trigger = shadowRoot ? shadowRoot.querySelector("[data-color-trigger]") : document.querySelector("[data-color-trigger]"); if (trigger) { trigger.click(); } }; const openImagePicker = () => { const shadowRoot = getShadowRoot(); const fileInput = shadowRoot ? shadowRoot.querySelector('[data-image-upload="true"]') : document.querySelector('[data-image-upload="true"]'); if (fileInput) { fileInput.click(); } }; const deriveCursorMode = (effects, fallback) => { if (effects.length === 0) return "none"; if (effects.length === 1) return effects[0]; return fallback || effects[0] || "none"; }; const clearCursorEffects = () => { setContentState((prev) => ({ ...prev, cursorEffects: [], cursorMode: "none", })); chrome.storage.local.set({ cursorEffects: [], cursorMode: "none", }); }; const toggleCursorEffect = (effect) => { const state = contentStateRef.current; const current = Array.isArray(state.cursorEffects) ? state.cursorEffects : []; const next = current.includes(effect) ? current.filter((item) => item !== effect) : [...current, effect]; const nextMode = deriveCursorMode(next, effect); setContentState((prev) => ({ ...prev, cursorEffects: next, cursorMode: nextMode, })); chrome.storage.local.set({ cursorEffects: next, cursorMode: nextMode, }); }; const handleKeyDown = (event) => { const state = contentStateRef.current; if (state?.drawingMode && (event.ctrlKey || event.metaKey)) { const key = event.key.toLowerCase(); if (key === "z" && !event.shiftKey) { event.preventDefault(); event.stopPropagation(); event.stopImmediatePropagation(); undoCanvas(state, setContentState); return; } if ((key === "z" && event.shiftKey) || key === "y") { event.preventDefault(); event.stopPropagation(); event.stopImmediatePropagation(); redoCanvas(state, setContentState); return; } } if (!shouldHandleShortcut(event)) return; const digitKey = getDigitKey(event); if (!digitKey) return; let handled = true; if (state?.drawingMode) { switch (digitKey) { case "1": setTool("select"); break; case "2": setTool("pen"); break; case "3": setTool("highlighter"); break; case "4": setTool("eraser"); break; case "5": toggleColorPicker(); break; case "6": setTool("text"); break; case "7": setTool("shape", { shape: "rectangle", shapeFill: false }); break; case "8": setTool("arrow"); break; case "9": openImagePicker(); break; case "0": clearDrawings(); break; default: handled = false; } } else { switch (digitKey) { case "0": if (state?.blurMode) { clearBlurMasks(); } clearCursorEffects(); break; case "1": toggleCursorEffect("target"); break; case "2": toggleCursorEffect("highlight"); break; case "3": toggleCursorEffect("spotlight"); break; default: handled = false; } } if (handled) { event.preventDefault(); event.stopPropagation(); event.stopImmediatePropagation(); } }; document.addEventListener("keydown", handleKeyDown, true); return () => { document.removeEventListener("keydown", handleKeyDown, true); }; }, []); // Push to talk (while Alt/Option + Shift + J key is pressed enable microphone, disable on key up) useEffect(() => { const handleKeyDown = (event) => { if (!contentStateRef.current.pushToTalk) return; if (event.code === "KeyU" && event.altKey && event.shiftKey) { setContentState((prevContentState) => ({ ...prevContentState, micActive: true, })); chrome.storage.local.set({ micActive: true, }); chrome.runtime.sendMessage({ type: "set-mic-active-tab", active: true, defaultAudioInput: contentState.defaultAudioInput, }); } }; const handleKeyUp = (event) => { if (!contentStateRef.current.pushToTalk) return; if (event.code === "KeyU" && event.altKey && event.shiftKey) { setContentState((prevContentState) => ({ ...prevContentState, micActive: false, })); chrome.storage.local.set({ micActive: false, }); chrome.runtime.sendMessage({ type: "set-mic-active-tab", active: false, defaultAudioInput: contentState.defaultAudioInput, }); } }; window.addEventListener("keydown", handleKeyDown); window.addEventListener("keyup", handleKeyUp); return () => { window.removeEventListener("keydown", handleKeyDown); window.removeEventListener("keyup", handleKeyUp); }; }, []); return <>; }; export default Shortcuts; ================================================ FILE: src/pages/Content/styles/_variables.scss ================================================ @mixin font($font-family, $font-file) { @font-face { font-family: $font-family; src: url($font-file + ".ttf") format("truetype"); font-weight: normal; font-style: normal; } } @include font( "Satoshi-Light", "chrome-extension://__MSG_@@extension_id__/assets/fonts/Satoshi-Light" ); @include font( "Satoshi-Medium", "chrome-extension://__MSG_@@extension_id__/assets/fonts/Satoshi-Medium" ); @include font( "Satoshi-Bold", "chrome-extension://__MSG_@@extension_id__/assets/fonts/Satoshi-Bold" ); @include font( "Gloria Hallelujah", "chrome-extension://__MSG_@@extension_id__/assets/fonts/GloriaHallelujah-Regular" ); /* Colors */ $color-background: #fff; $color-light-grey: #f6f7fb; $color-primary: #3080f8; $color-border: #e8e8e8; $color-text-contrast: #fff; $color-text-primary: #29292f; $color-text-secondary: #6e7684; $color-red: #d2234d; $color-red-light: #faf0f4; $color-primary-transparent: rgba(48, 128, 248, 0.1); $color-icon: #9797a4; /* Font */ //$font-size-normal: 0.875rem; //$font-size-small: 0.75rem; $font-size-normal: 14px; $font-size-detail: 0.8rem; $font-size-small: 12px; $font-weight-bold: 700; $font-weight-normal: 500; $font-weight-light: 400; $font-light: "Satoshi-Light", sans-serif; $font-medium: "Satoshi-Medium", sans-serif; $font-regular: "Satoshi-Regular", sans-serif; $font-bold: "Satoshi-Bold", sans-serif; /* Spacing */ /* $spacing-01: 0.125rem; $spacing-02: 0.25rem; $spacing-03: 0.5rem; $spacing-04: 0.75rem; $spacing-05: 1rem; */ $spacing-01: 2px; $spacing-02: 4px; $spacing-03: 8px; $spacing-04: 12px; $spacing-05: 16px; /* Container */ $container-border-radius: 30px; $container-border: 1px solid $color-border; $container-shadow: drop-shadow(0px 4px 100px rgba(0, 0, 0, 0.35)); $container-shadow-focus: 2px 2px 10px rgba(0, 0, 0, 0.1); $container-soft-shadow: 0 4px 20px rgb(38 38 52 / 8%); /* Gradients */ $gradient-primary: radial-gradient( 117.41% 117.78% at 35.44% 0%, #2baef8 23.13%, #3582f6 46.35%, #486def 74.48%, #7b9aea 100% ); $gradient-shadow-primary: drop-shadow(0px 4px 20px rgba(86, 123, 218, 0.5)); /* Events */ $focus-border: 0px 0px 0px 2px rgba(48, 128, 248, 0.5); $focus-inner-border: inset 0px 0px 0px 2px rgba(48, 128, 248, 0.5); /* Z-index */ $z-index-max: 99999999999; ================================================ FILE: src/pages/Content/styles/app.css ================================================ @font-face { font-family: "Satoshi-Light"; src: url("chrome-extension://__MSG_@@extension_id__/assets/fonts/Satoshi-Light.ttf") format("truetype"); font-weight: normal; font-style: normal; } @font-face { font-family: "Satoshi-Medium"; src: url("chrome-extension://__MSG_@@extension_id__/assets/fonts/Satoshi-Medium.ttf") format("truetype"); font-weight: normal; font-style: normal; } @font-face { font-family: "Satoshi-Bold"; src: url("chrome-extension://__MSG_@@extension_id__/assets/fonts/Satoshi-Bold.ttf") format("truetype"); font-weight: normal; font-style: normal; } @font-face { font-family: "Gloria Hallelujah"; src: url("chrome-extension://__MSG_@@extension_id__/assets/fonts/GloriaHallelujah-Regular.ttf") format("truetype"); font-weight: normal; font-style: normal; } /* Colors */ /* Font */ /* Spacing */ /* $spacing-01: 0.125rem; $spacing-02: 0.25rem; $spacing-03: 0.5rem; $spacing-04: 0.75rem; $spacing-05: 1rem; */ /* Container */ /* Gradients */ /* Events */ /* Z-index */ /* reset */ a, button { all: unset; } iframe { width: 100%; height: 100%; position: fixed; overflow: scroll; z-index: -9999; top: 0px; left: 0px; border: 0px; pointer-events: all !important; } .container { pointer-events: none !important; } .visually-hidden-toolbar { opacity: 0 !important; visibility: hidden !important; pointer-events: none !important; transition: opacity 0.3s ease, visibility 0s linear 0.3s; } .toolbar-controls { opacity: 0; cursor: pointer; position: absolute; top: -10px; right: -10px; box-sizing: border-box; border-radius: 30px; border: 1px solid #e8e8e8; display: flex; align-items: center; justify-content: center; gap: 8px; z-index: 99999999999999; background: rgb(240, 238, 238); backdrop-filter: blur(10px); padding-left: 8px; padding-right: 8px; padding-top: 6px; padding-bottom: 6px; transition: opacity 0.2s cubic-bezier(0.4, 0, 0.2, 1); pointer-events: none; } .toolbar-controls.open { opacity: 1 !important; pointer-events: auto; } .toolbar-controls .popup-control svg { color: #9797a4; margin-bottom: -2px; } .ToolbarBounds { position: fixed; top: 0px; left: 0px; box-sizing: border-box; width: 100%; height: 100%; border: 10px solid #3080f8; pointer-events: none; transform: scale(1.2); opacity: 0; transition: transform 0.25s cubic-bezier(0.61, 0.11, 0.08, 0.96), opacity 0.25s ease-in-out; } .ToolbarBounds.ToolbarShake { transform: scale(1); opacity: 0.4; } .react-draggable { pointer-events: all; } .ToolbarShake .react-draggable { width: 100%; height: 100%; } .ToolbarElastic { transition: all 0.25s cubic-bezier(0.68, -0.55, 0.265, 1.55); } .ToolbarShake .ToolbarRoot { animation: subtleshake 0.9s cubic-bezier(0.36, 0.07, 0.19, 0.97) both; animation-iteration-count: infinite !important; background-color: white !important; } .ToolbarDragging .ToolbarRoot { transform: scale(1.02); } .ToolbarDragging .ToolbarRoot::after { filter: drop-shadow(0px 20px 50px rgba(0, 0, 0, 0.5)); } @keyframes shake { 0% { transform: translate(1px, 1px) rotate(0deg); } 20% { transform: translate(-3px, 0px) rotate(1deg); } 30% { transform: translate(3px, 2px) rotate(0deg); } 40% { transform: translate(1px, -1px) rotate(1deg); } 50% { transform: translate(-1px, 2px) rotate(-1deg); } 60% { transform: translate(-3px, 1px) rotate(0deg); } 70% { transform: translate(3px, 1px) rotate(-1deg); } 80% { transform: translate(-1px, -1px) rotate(1deg); } 90% { transform: translate(1px, 2px) rotate(0deg); } 100% { transform: translate(1px, -2px) rotate(-1deg); } } @keyframes subtleshake { 0% { transform: translate(0px, 0px) rotate(0deg); } 10% { transform: translate(-1px, 1px) rotate(1deg); } 20% { transform: translate(-1px, -1px) rotate(-1deg); } 30% { transform: translate(1px, 0px) rotate(0deg); } 40% { transform: translate(-1px, 1px) rotate(-1deg); } 50% { transform: translate(1px, -1px) rotate(1deg); } 60% { transform: translate(-1px, 1px) rotate(-1deg); } 70% { transform: translate(-1px, -1px) rotate(1deg); } 80% { transform: translate(1px, 1px) rotate(0deg); } 90% { transform: translate(0px, -1px) rotate(-1deg); } 100% { transform: translate(-1px, 1px) rotate(1deg); } } .ToolbarTransparent { opacity: 0; } .ToolbarTransparent:hover { opacity: 1; } .ToolbarHoverZone { display: inline-block; position: relative; padding: 4px; width: -moz-fit-content; width: fit-content; height: -moz-fit-content; height: fit-content; } .ToolbarRoot { display: flex; align-items: center; padding-left: 10px; transition: opacity 0.25s cubic-bezier(0.61, 0.11, 0.08, 0.96), transform 0.2s cubic-bezier(0.61, 0.11, 0.08, 0.96); min-width: -moz-max-content; min-width: max-content; background-color: white; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.15); padding-right: 10px; height: 48px; position: absolute; bottom: 20px; left: 20px; border-radius: 30px; } .ToolbarRoot::after { content: ""; display: block; width: 100%; height: 100%; background: #fff; z-index: -9999999; position: absolute; left: 0px; top: 0px; border-radius: 30px; filter: drop-shadow(0px 4px 50px rgba(0, 0, 0, 0.3)); transition: filter 0.2s ease-in-out; } .ForceTransparent { opacity: 0 !important; } .ToolbarRecordingControls { display: flex; justify-content: center; align-items: center; background: #f6f7fb; border-radius: 30px; font-family: "Satoshi-Medium", sans-serif; height: calc(100% - 12px); padding-left: 2px; padding-right: 2px; } .ToolbarRecordingTime { margin-right: 4px; width: 42px; color: #29292f; font-size: 13px; line-height: 1; user-select: none; -webkit-user-select: none; -moz-user-select: none; } .TimerWarning { color: #ff4c4c; /* or add a pulsing effect */ font-weight: bold; animation: pulse 1s infinite alternate; } @keyframes pulse { from { opacity: 1; } to { opacity: 0.6; } } .ToolbarToggleGroup { display: flex; align-items: center; } .ToolbarToggleWrap { flex: 1 1 auto; align-items: center; justify-content: flex-start; position: relative; flex: 0 0 auto; width: 32px; height: 32px; display: inline-flex; line-height: 1; align-items: center; justify-content: center; } .ToolbarToggleItem, .ToolbarModeItem, .ToolbarModeItemSingle, .ToolbarLink, .ToolbarButton { display: flex; justify-content: center; align-items: center; color: #000; height: 32px; width: 32px; text-align: center; font-size: 13px; line-height: 1; border-radius: 50%; transition: background-color 0.25s ease-in-out; background-color: rgba(124, 139, 165, 0); } .ToolbarToggleItem svg, .ToolbarModeItem svg, .ToolbarModeItemSingle svg, .ToolbarLink svg, .ToolbarButton svg { color: #9797a4; } .ToolbarToggleItem:disabled, .ToolbarModeItem:disabled, .ToolbarModeItemSingle:disabled, .ToolbarLink:disabled, .ToolbarButton:disabled { opacity: 0.5; cursor: not-allowed !important; background: none !important; } .ToolbarToggleItem.resume svg, .ToolbarModeItem.resume svg, .ToolbarModeItemSingle.resume svg, .ToolbarLink.resume svg, .ToolbarButton.resume svg { color: #f7387d !important; } .ToolbarToggleItem:hover, .ToolbarModeItem:hover, .ToolbarModeItemSingle:hover, .ToolbarLink:hover, .ToolbarButton:hover { cursor: pointer; background-color: rgba(124, 139, 165, 0.1) !important; } .ToolbarToggleItem:focus-visible, .ToolbarModeItemSingle:focus-visible, .ToolbarModeItem:focus-visible, .ToolbarLink:focus-visible, .ToolbarButton:focus-visible { position: relative; box-shadow: 0px 0px 0px 2px rgba(48, 128, 248, 0.5); } .ToolbarModeItemSingle { display: flex; justify-content: center; align-items: center; z-index: 99999; position: relative; } .ToolbarModeItemSingle:first-child { margin-left: 0; } .ToolbarModeItemSingle[data-state=on] { background: rgba(120, 192, 114, 0.1); } .ToolbarModeItemSingle[data-state=on] svg { color: #78c072; } .ToolbarModeItemSingle[data-state=on]::before { transform: translateY(0px) scale(1) !important; opacity: 1 !important; } .ToolbarModeItem { display: flex; justify-content: center; align-items: center; z-index: 99999; position: relative; } .ToolbarModeItem:first-child { margin-left: 0; } .ToolbarModeItem::before { content: ""; display: block; width: 100%; height: 50%; border-radius: 80px 80px 0% 0%; box-sizing: border-box; position: absolute; top: -16px; left: 0; z-index: -999999; transition: transform 0.25s cubic-bezier(0.61, 0.11, 0.08, 0.96), opacity 0.25s ease-in-out; transform: translateY(5px) scale(0) !important; border-right: 3px solid white; border-top: 9px solid white; border-left: 3px solid white; background-color: transparent; opacity: 0; } .ToolbarModeItem[data-state=on] { background: rgba(56, 126, 247, 0.1); } .ToolbarModeItem[data-state=on] svg { color: #3080f8; } .ToolbarModeItem[data-state=on]::before { transform: translateY(0px) scale(1) !important; opacity: 1 !important; } .ToolbarBottom .ToolbarModeItem::before { transform: translateY(-5px) scale(0.5) !important; bottom: -16px; top: unset !important; border-bottom: 9px solid white !important; border-radius: 0% 0% 80px 80px !important; border-top: none !important; } .ToolbarBottom .ToolbarModeItem[data-state=on]::before { transform: translateY(0px) scale(1) !important; } .ToolbarToggleItem { display: flex; justify-content: center; align-items: center; z-index: 99999; position: relative; } .ToolbarToggleItem:first-child { margin-left: 0; } .ToolbarToggleItem[data-state=on] { background: radial-gradient(117.41% 117.78% at 35.44% 0%, #2baef8 23.13%, #3582f6 46.35%, #486def 74.48%, #7b9aea 100%); color: #fff; } .ToolbarToggleItem[data-state=on] svg { color: #fff; } .ToolbarSeparator { width: 1px; height: 19px; background-color: #e8e8e8; margin: 0 8px; } .ToolbarLink { background-color: transparent; color: var(--mauve11); display: inline-flex; justify-content: center; align-items: center; } .ToolbarLink:hover { background-color: transparent; cursor: pointer; } .ToolbarPaused { position: fixed; top: 0px; left: 0px; width: 100%; height: 100%; box-sizing: border-box; border: 10px solid #f7387d; pointer-events: none; opacity: 0.5; } .ToolbarPaused.hidden { display: none; } .OnboardingArrow { position: absolute; z-index: 99999999999; width: -moz-max-content; width: max-content; display: flex; flex-direction: column; align-items: center; gap: 16px; left: -69px; bottom: 23px; transform: rotate(12deg); pointer-events: none !important; } .OnboardingText { font-size: 32px; color: #9797a4; font-family: "Gloria-Hallelujah", sans-serif !important; } .ArrowShape { margin-left: -15px; } .driver-popover.ScreenityOnboardingPopover, .ScreenityOnboardingPopover, .onboarding-popover { border-radius: 30px !important; background: #fff !important; color: #29292f !important; font-family: "Satoshi-Regular", sans-serif !important; box-shadow: 0 4px 20px rgba(38, 38, 52, 0.08) !important; padding: 20px !important; max-width: 340px !important; font-size: 14px !important; z-index: 99999999999 !important; } .driver-popover.ScreenityOnboardingPopover .driver-popover-title, .ScreenityOnboardingPopover .driver-popover-title, .onboarding-popover .driver-popover-title { font-size: 1rem !important; font-family: "Satoshi-Bold", sans-serif !important; margin-bottom: 12px !important; color: #29292f !important; } .driver-popover.ScreenityOnboardingPopover .driver-popover-description, .ScreenityOnboardingPopover .driver-popover-description, .onboarding-popover .driver-popover-description { font-size: 14px !important; font-family: "Satoshi-Medium", sans-serif !important; color: #6e7684 !important; line-height: 1.5 !important; margin-bottom: 18px !important; } .driver-popover.ScreenityOnboardingPopover .driver-popover-description a, .ScreenityOnboardingPopover .driver-popover-description a, .onboarding-popover .driver-popover-description a { color: #3080f8 !important; font-family: "Satoshi-Bold", sans-serif !important; text-decoration: none !important; cursor: pointer !important; } .driver-popover.ScreenityOnboardingPopover .driver-popover-close-btn, .ScreenityOnboardingPopover .driver-popover-close-btn, .onboarding-popover .driver-popover-close-btn { display: none !important; top: 14px; right: 12px; color: #6e7684 !important; font-size: 1.5rem !important; } .driver-popover.ScreenityOnboardingPopover .driver-popover-arrow, .ScreenityOnboardingPopover .driver-popover-arrow, .onboarding-popover .driver-popover-arrow { width: 0px; height: 0px; background: none !important; box-shadow: none !important; box-sizing: border-box; } .driver-popover.ScreenityOnboardingPopover .driver-popover-arrow-side-top, .ScreenityOnboardingPopover .driver-popover-arrow-side-top, .onboarding-popover .driver-popover-arrow-side-top { border-left: 8px solid transparent !important; border-right: 8px solid transparent !important; border-top: 8px solid #fff !important; } .driver-popover.ScreenityOnboardingPopover .driver-popover-arrow-side-bottom, .ScreenityOnboardingPopover .driver-popover-arrow-side-bottom, .onboarding-popover .driver-popover-arrow-side-bottom { border-left: 8px solid transparent !important; border-right: 8px solid transparent !important; border-bottom: 8px solid #fff !important; } .driver-popover.ScreenityOnboardingPopover .driver-popover-arrow-side-left, .ScreenityOnboardingPopover .driver-popover-arrow-side-left, .onboarding-popover .driver-popover-arrow-side-left { border-top: 8px solid transparent !important; border-bottom: 8px solid transparent !important; border-left: 8px solid #fff !important; } .driver-popover.ScreenityOnboardingPopover .driver-popover-arrow-side-right, .ScreenityOnboardingPopover .driver-popover-arrow-side-right, .onboarding-popover .driver-popover-arrow-side-right { border-top: 8px solid transparent !important; border-bottom: 8px solid transparent !important; border-right: 8px solid #fff !important; } .driver-popover.ScreenityOnboardingPopover .driver-popover-arrow-align-start, .ScreenityOnboardingPopover .driver-popover-arrow-align-start, .onboarding-popover .driver-popover-arrow-align-start { top: 35px !important; } .driver-popover.ScreenityOnboardingPopover .driver-popover-arrow-align-end, .ScreenityOnboardingPopover .driver-popover-arrow-align-end, .onboarding-popover .driver-popover-arrow-align-end { bottom: 35px !important; } .driver-popover.ScreenityOnboardingPopover .driver-popover-arrow-align-left, .ScreenityOnboardingPopover .driver-popover-arrow-align-left, .onboarding-popover .driver-popover-arrow-align-left { left: 35px !important; } .driver-popover.ScreenityOnboardingPopover .driver-popover-arrow-align-right, .ScreenityOnboardingPopover .driver-popover-arrow-align-right, .onboarding-popover .driver-popover-arrow-align-right { right: 35px !important; } .driver-popover.ScreenityOnboardingPopover .driver-popover-progress-text, .ScreenityOnboardingPopover .driver-popover-progress-text, .onboarding-popover .driver-popover-progress-text { font-size: 0.8rem !important; font-family: "Satoshi-Medium", sans-serif !important; color: #6e7684 !important; opacity: 0.7 !important; margin-top: 2px !important; } .driver-popover.ScreenityOnboardingPopover .driver-popover-footer, .ScreenityOnboardingPopover .driver-popover-footer, .onboarding-popover .driver-popover-footer { display: flex !important; justify-content: flex-end; gap: 8px; margin-top: 16px; } .driver-popover.ScreenityOnboardingPopover .driver-popover-footer .driver-popover-navigation-btns, .ScreenityOnboardingPopover .driver-popover-footer .driver-popover-navigation-btns, .onboarding-popover .driver-popover-footer .driver-popover-navigation-btns { gap: 6px !important; } .driver-popover.ScreenityOnboardingPopover .driver-popover-footer .driver-popover-navigation-btns .driver-popover-next-btn, .driver-popover.ScreenityOnboardingPopover .driver-popover-footer .driver-popover-navigation-btns .driver-popover-prev-btn, .driver-popover.ScreenityOnboardingPopover .driver-popover-footer .driver-popover-navigation-btns .driver-popover-close-btn, .ScreenityOnboardingPopover .driver-popover-footer .driver-popover-navigation-btns .driver-popover-next-btn, .ScreenityOnboardingPopover .driver-popover-footer .driver-popover-navigation-btns .driver-popover-prev-btn, .ScreenityOnboardingPopover .driver-popover-footer .driver-popover-navigation-btns .driver-popover-close-btn, .onboarding-popover .driver-popover-footer .driver-popover-navigation-btns .driver-popover-next-btn, .onboarding-popover .driver-popover-footer .driver-popover-navigation-btns .driver-popover-prev-btn, .onboarding-popover .driver-popover-footer .driver-popover-navigation-btns .driver-popover-close-btn { background-color: #3080f8 !important; color: white !important; border: none !important; padding: 10px 14px !important; border-radius: 30px !important; font-family: "Satoshi-Medium", sans-serif !important; font-weight: 500 !important; cursor: pointer !important; font-size: 0.875rem !important; text-shadow: none !important; } .driver-popover.ScreenityOnboardingPopover .driver-popover-footer .driver-popover-navigation-btns .driver-popover-next-btn:hover, .ScreenityOnboardingPopover .driver-popover-footer .driver-popover-navigation-btns .driver-popover-next-btn:hover, .onboarding-popover .driver-popover-footer .driver-popover-navigation-btns .driver-popover-next-btn:hover { background: rgb(23.3341121495, 112.8668224299, 247.1658878505) !important; } .driver-popover.ScreenityOnboardingPopover .driver-popover-footer .driver-popover-navigation-btns .driver-popover-prev-btn, .ScreenityOnboardingPopover .driver-popover-footer .driver-popover-navigation-btns .driver-popover-prev-btn, .onboarding-popover .driver-popover-footer .driver-popover-navigation-btns .driver-popover-prev-btn { background-color: transparent !important; color: #29292f !important; border: 1px solid #e8e8e8 !important; } .driver-popover.ScreenityOnboardingPopover .driver-popover-footer .driver-popover-navigation-btns .driver-popover-prev-btn:hover, .ScreenityOnboardingPopover .driver-popover-footer .driver-popover-navigation-btns .driver-popover-prev-btn:hover, .onboarding-popover .driver-popover-footer .driver-popover-navigation-btns .driver-popover-prev-btn:hover { background: #f6f7fb !important; } .driver-popover.ScreenityOnboardingPopover .driver-popover-footer .driver-popover-navigation-btns .driver-popover-close-btn, .ScreenityOnboardingPopover .driver-popover-footer .driver-popover-navigation-btns .driver-popover-close-btn, .onboarding-popover .driver-popover-footer .driver-popover-navigation-btns .driver-popover-close-btn { background-color: transparent !important; color: #6e7684 !important; } body { background-color: white !important; } .DrawingToolbar.show-toolbar { opacity: 1 !important; pointer-events: all !important; transform: scale(1) translate(calc(-50% + 16px), 0px) !important; } .ToolbarBottom .DrawingToolbar { transform-origin: 0 -100% !important; } .DrawingToolbar { opacity: 0; pointer-events: none; align-items: center; display: flex; min-width: -moz-max-content; min-width: max-content; padding-left: 10px; padding-right: 10px; border-radius: 6px; box-shadow: 0 2px 10px var(--blackA7); position: absolute; height: 44px; left: 0px; transform: translate(calc(-50% + 16px)); transform-origin: 0 100%; border-radius: 15px; z-index: 99999999; transition: transform 0.25s cubic-bezier(0.61, 0.11, 0.08, 0.96), opacity 0.25s cubic-bezier(0.61, 0.11, 0.08, 0.96); transform: scale(0.5) translate(calc(-50% + 16px), 10px); } .DrawingToolbar::after { content: ""; display: block; width: 100%; height: 100%; filter: blur(10px); opacity: 0.15; background-color: #000; position: absolute; left: 0px; top: 0px; z-index: -999999999999999 !important; } .DrawingToolbar::before { content: ""; display: block; width: 100%; height: 100%; background: rgba(242, 241, 242, 0.85); backdrop-filter: blur(5px); background-clip: content-box; -webkit-mask-image: radial-gradient(circle at 50% 59px, transparent 20px, #000 20px); mask-image: radial-gradient(circle at 50% 59px, transparent 20px, #000 20px); background-position: center bottom 50px; border-radius: 15px; position: absolute; top: 0px; left: 0px; z-index: -2; } .DrawingToolbar .ToolbarSeparator { background-color: #dddcdc; } .ToolbarTop .DrawingToolbar { bottom: 49px !important; } .ToolbarBottom .DrawingToolbar { top: 48px !important; } .ToolbarBottom .DrawingToolbar::before { -webkit-mask-image: radial-gradient(circle at 50% -14px, transparent 20px, #000 20px) !important; mask-image: radial-gradient(circle at 50% -14px, transparent 20px, #000 20px); background-position: center bottom 50px !important; } .ColorPicker { width: 14px; height: 14px; background: #ED6C3A; border: 1.5px solid rgba(0, 0, 0, 0.2); border-radius: 50%; } .shapeToolbar { position: absolute; display: flex; align-items: center; justify-content: center; box-shadow: 0 2px 10px var(--blackA7); left: 165px; padding: 4px; opacity: 0; bottom: 45px; transform: translateY(calc(-50% + 16px)); transform-origin: 0 100%; border-radius: 15px; z-index: 99999999; transition: transform 0.25s cubic-bezier(0.61, 0.11, 0.08, 0.96), opacity 0.25s cubic-bezier(0.61, 0.11, 0.08, 0.96); transform: scale(0.5) translatY(calc(-50% + 16px), 10px); pointer-events: none; } .shapeToolbar::after { content: ""; display: block; width: 100%; height: 100%; background: #FFF; z-index: -9999999; position: absolute; left: 0px; top: 0px; border-radius: 15px; filter: drop-shadow(0px 4px 50px rgba(0, 0, 0, 0.3)); transition: filter 0.2s ease-in-out; } .shapeToolbar.show-toolbar { opacity: 1 !important; pointer-events: all !important; transform: scale(1) translateY(calc(-50% + 16px), 0px) !important; } .TooltipContent { border-radius: 30px; background-color: #29292f; padding: 10px 15px; font-size: 12px; margin-bottom: 10px; bottom: 100px; line-height: 1; font-family: "Satoshi-Medium", sans-serif; z-index: 99999999 !important; color: #fff; box-shadow: hsla(206, 22%, 7%, 0.35) 0px 10px 38px -10px, hsla(206, 22%, 7%, 0.2) 0px 10px 20px -15px; -webkit-user-select: none; -moz-user-select: none; user-select: none; transition: opacity 0.3 ease-in-out !important; will-change: transform, opacity; animation-duration: 400ms; animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1); will-change: transform, opacity; } .hide-tooltip { display: none !important; } .tooltip-tall { margin-bottom: 20px; } .tooltip-small { margin-bottom: 5px; } .TooltipContent[data-state=delayed-open][data-side=top] { animation-name: slideDownAndFade; } .TooltipContent[data-state=delayed-open][data-side=right] { animation-name: slideLeftAndFade; } .TooltipContent[data-state=delayed-open][data-side=bottom] { animation-name: slideUpAndFade; } .TooltipContent[data-state=delayed-open][data-side=left] { animation-name: slideRightAndFade; } @keyframes slideUpAndFade { from { opacity: 0; transform: translateY(2px); } to { opacity: 1; transform: translateY(0); } } @keyframes slideRightAndFade { from { opacity: 0; transform: translateX(-2px); } to { opacity: 1; transform: translateX(0); } } @keyframes slideDownAndFade { from { opacity: 0; transform: translateY(-2px); } to { opacity: 1; transform: translateY(0); } } @keyframes slideLeftAndFade { from { opacity: 0; transform: translateX(2px); } to { opacity: 1; transform: translateX(0); } } #screenity-ui [data-radix-popper-content-wrapper] { z-index: 99999999999 !important; } .override { display: none !important; opacity: 0 !important; visibility: hidden !important; } .radial-menu { position: absolute; z-index: 9999999999999; width: 100px; height: 100px; top: -66px; left: -49px; pointer-events: none; opacity: 1; transform: scale(1); transition: transform 0.3s cubic-bezier(0.68, -0.55, 0.265, 1.55), opacity 0.25s ease-in-out; } .radial-menu::after { content: ""; position: absolute; top: 0; left: 0; width: 100%; height: 100%; z-index: -1; border: 28px solid #FFF; box-sizing: border-box; border-radius: 50%; backdrop-filter: blur(40px); opacity: 0; filter: drop-shadow(0px 4px 50px rgba(0, 0, 0, 0.3)); transform: scale(0); transition: transform 0.25s cubic-bezier(0.18, -0.55, 0.265, 1.45), opacity 0.2s ease-in-out; transition-delay: 0.05s; } .radial-menu[data-state=open] { transform: scale(1); opacity: 1; pointer-events: all !important; } .radial-menu[data-state=open]::after { transform: scale(1); border: 28px solid #FFF; opacity: 1; } .color-wheel::after { opacity: 0 !important; } .eyedropper { position: absolute; left: 0px; top: 0px; right: 0px; bottom: 0px; margin: auto; width: 16px; height: 16px; padding: 8px; z-index: 999999999; background-color: #FFF; border-radius: 50%; opacity: 0; text-align: center; transition: transform 0.25s cubic-bezier(0.68, -0.55, 0.265, 1.55), opacity 0.3s ease-in-out, background-color 0.25s ease-in-out !important; transform: scale(0); overflow: hidden; transform-style: preserve-3d; } .eyedropper svg { color: #9797a4; } .eyedropper:focus-visible { outline: none; background-color: #E6E7EA !important; } .eyedropper:hover { cursor: pointer; background-color: #E6E7EA; } .eye-active { background-color: #3080f8 !important; } .eye-active svg { color: #FFF !important; fill: #FFF !important; } .color-wheel .eyedropper { opacity: 0 !important; pointer-events: none !important; transform: scale(0) !important; } .radial-menu[data-state=open] .eyedropper { transform: scale(1) !important; opacity: 1; } .radial-menu-items { transform: rotate(10deg); z-index: 99999999; position: absolute; left: 0; right: 0; top: 0; bottom: 0; margin: auto; } .radial-menu-item { position: absolute; left: 0; right: 0; top: 0; bottom: 0; margin: auto; width: 18px; height: 18px; z-index: 999; border-radius: 50%; text-align: center; box-sizing: border-box; line-height: 50px; color: white; border: 1px solid rgba(0, 0, 0, 0.2); transition: transform 0.25s cubic-bezier(0.61, 0.11, 0.08, 0.96), opacity 0.25s cubic-bezier(0.61, 0.11, 0.08, 0.96); opacity: 0; } .radial-menu-item:hover { cursor: pointer; } .color-wheel .radial-menu-item { opacity: 0 !important; } .radial-menu-item-child { width: 100%; height: 100%; position: absolute; box-sizing: border-box; border: 0px; box-shadow: none; top: 0px; pointer-events: none; z-index: 9999999; left: 0px; border-radius: 50%; background-size: cover; } .radial-menu-item-child:focus-visible { outline: none; box-shadow: 0px 0px 0px 2px rgba(48, 128, 248, 0.5); } .radial-menu[data-state=open] .radial-menu-item-child { pointer-events: all !important; } .radial-menu-item:nth-child(1), .wheel-trigger { transform: rotate(0deg) translate(0px); } .radial-menu[data-state=open] .radial-menu-item:nth-child(1), .radial-menu[data-state=open] .wheel-trigger { transition-delay: calc(0.25s - 0s); transform: rotate(0deg) translate(36px); opacity: 1; } .radial-menu-item:nth-child(2), .wheel-trigger { transform: rotate(40deg) translate(0px); } .radial-menu[data-state=open] .radial-menu-item:nth-child(2), .radial-menu[data-state=open] .wheel-trigger { transition-delay: calc(0.25s - 0.02s); transform: rotate(40deg) translate(36px); opacity: 1; } .radial-menu-item:nth-child(3), .wheel-trigger { transform: rotate(80deg) translate(0px); } .radial-menu[data-state=open] .radial-menu-item:nth-child(3), .radial-menu[data-state=open] .wheel-trigger { transition-delay: calc(0.25s - 0.04s); transform: rotate(80deg) translate(36px); opacity: 1; } .radial-menu-item:nth-child(4), .wheel-trigger { transform: rotate(120deg) translate(0px); } .radial-menu[data-state=open] .radial-menu-item:nth-child(4), .radial-menu[data-state=open] .wheel-trigger { transition-delay: calc(0.25s - 0.06s); transform: rotate(120deg) translate(36px); opacity: 1; } .radial-menu-item:nth-child(5), .wheel-trigger { transform: rotate(160deg) translate(0px); } .radial-menu[data-state=open] .radial-menu-item:nth-child(5), .radial-menu[data-state=open] .wheel-trigger { transition-delay: calc(0.25s - 0.08s); transform: rotate(160deg) translate(36px); opacity: 1; } .radial-menu-item:nth-child(6), .wheel-trigger { transform: rotate(200deg) translate(0px); } .radial-menu[data-state=open] .radial-menu-item:nth-child(6), .radial-menu[data-state=open] .wheel-trigger { transition-delay: calc(0.25s - 0.1s); transform: rotate(200deg) translate(36px); opacity: 1; } .radial-menu-item:nth-child(7), .wheel-trigger { transform: rotate(240deg) translate(0px); } .radial-menu[data-state=open] .radial-menu-item:nth-child(7), .radial-menu[data-state=open] .wheel-trigger { transition-delay: calc(0.25s - 0.12s); transform: rotate(240deg) translate(36px); opacity: 1; } .radial-menu-item:nth-child(8), .wheel-trigger { transform: rotate(280deg) translate(0px); } .radial-menu[data-state=open] .radial-menu-item:nth-child(8), .radial-menu[data-state=open] .wheel-trigger { transition-delay: calc(0.25s - 0.14s); transform: rotate(280deg) translate(36px); opacity: 1; } .radial-menu-item:nth-child(9), .wheel-trigger { transform: rotate(320deg) translate(0px); } .radial-menu[data-state=open] .radial-menu-item:nth-child(9), .radial-menu[data-state=open] .wheel-trigger { transition-delay: calc(0.25s - 0.16s); transform: rotate(320deg) translate(36px); opacity: 1; } .color-active { border: 1px solid #FFFFFF; box-shadow: 0px 0px 0px 2px #0D99FF; } .color-wheel .color-active { border: none !important; box-shadow: none !important; } .wheel-trigger { transition: transform 0.2s cubic-bezier(0.61, 0.11, 0.08, 0.96), width 0.2s cubic-bezier(0.61, 0.11, 0.08, 0.96), height 0.2s cubic-bezier(0.61, 0.11, 0.08, 0.96), opacity 0.25s cubic-bezier(0.61, 0.11, 0.08, 0.96); position: absolute; left: 0; right: 0; top: 0; bottom: 0; opacity: 0; margin: auto; width: 18px; box-sizing: border-box; height: 18px; z-index: 9999; box-sizing: border-box; background-blend-mode: screen; border-radius: 50%; } .wheel-trigger:hover { cursor: pointer; } .wheel-trigger .radial-menu-item-child { transform: rotate(30deg); } .wheel-trigger .radial-menu-item-child:after { content: ""; position: absolute; left: 0; right: 0; width: 100%; height: 100%; z-index: 9999999; border-radius: 50%; box-sizing: border-box; border: 1px solid rgba(0, 0, 0, 0.2); } .color-wheel .wheel-trigger { width: 100px !important; height: 100px !important; transform: rotate(320deg) translate(0px) !important; z-index: 999999999999 !important; filter: drop-shadow(0px 4px 50px rgba(0, 0, 0, 0.3)); } .color-wheel-handle { width: 12px !important; height: 12px !important; border-radius: 50%; left: 20px; top: 20px; opacity: 0; background-color: #F17FD7; border: 2px solid white; z-index: 999999999999; display: none; transition: width 0.25s cubic-bezier(0.61, 0.11, 0.08, 0.96), height 0.25s cubic-bezier(0.61, 0.11, 0.08, 0.96), margin-left 0.25s cubic-bezier(0.61, 0.11, 0.08, 0.96), margin-top 0.25s cubic-bezier(0.61, 0.11, 0.08, 0.96); } .color-wheel-handle:hover { width: 18px !important; height: 18px !important; margin-left: -2px; margin-top: -2px; } .color-wheel .color-wheel-handle { opacity: 0; display: block; animation: fadeIn 0.25s cubic-bezier(0.61, 0.11, 0.08, 0.96) forwards; animation-delay: 0.5s; } .w-color-wheel { pointer-events: none; position: absolute !important; width: 100% !important; height: 100% !important; z-index: 99999999 !important; } .w-color-wheel::after { content: ""; box-sizing: border-box; display: block; width: 100%; height: 100%; position: absolute; z-index: 9999999; border: 1px solid rgba(0, 0, 0, 0.2); box-sizing: border-box; border-radius: 50%; } .color-wheel .w-color-wheel { pointer-events: all !important; } .w-color-wheel-fill { box-shadow: none !important; border: 2px solid white !important; width: 14px !important; height: 14px !important; transition: width 0.2s cubic-bezier(0.61, 0.11, 0.08, 0.96), height 0.2s cubic-bezier(0.61, 0.11, 0.08, 0.96), margin 0.2s cubic-bezier(0.61, 0.11, 0.08, 0.96) !important; box-sizing: border-box !important; margin-left: -2px !important; margin-top: -2px !important; z-index: 9999999999 !important; } .w-color-wheel-pointer { z-index: 99999999999 !important; opacity: 0; animation: none !important; } .color-wheel .w-color-wheel-pointer { animation: fadeInScale 0.25s cubic-bezier(0.215, 0.61, 0.355, 1) forwards !important; animation-delay: 0.28s !important; } .w-color-wheel-fill:hover { width: 18px !important; height: 18px !important; margin-left: -4px !important; margin-top: -4px !important; } /* Fade in keyframes */ @keyframes fadeInScale { 0% { opacity: 0; } 100% { opacity: 1; } } .color-wheel, .color-wheel .wheel-trigger, .color-wheel .wheel-trigger .radial-menu-item-child, .color-wheel-handle { cursor: pointer !important; } .color-wheel-input { background: #000; border-radius: 30px; height: 29px; color: #FFF; text-align: center; line-height: 29px; padding-left: 8px; padding-right: 8px; position: absolute; margin-top: -35px; font-family: "Satoshi-Medium", sans-serif; left: 50%; transform: translate(-50%, 0); opacity: 0; } .color-wheel .color-wheel-input { animation: fadeIn 0.3s cubic-bezier(0.61, 0.11, 0.08, 0.96) forwards; animation-delay: 0.2s; pointer-events: all !important; } .color-wheel-input { pointer-events: none; } @keyframes fadeIn { 0% { opacity: 0; margin-top: -35px; } 100% { opacity: 1; margin-top: -40px; } } .color-active .color-preview { opacity: 1; } .radial-menu[data-state=closed] .color-preview { opacity: 0 !important; } .color-preview { width: 90%; height: 90%; box-sizing: border-box; border-radius: 50%; position: absolute; left: 50%; top: 50%; z-index: 9999999999; transform: translate(-50%, -50%); opacity: 0; animation: none; pointer-events: none; border: 1px solid #FFF; box-sizing: border-box; } .color-wheel .color-preview { opacity: 0 !important; } .wheel-trigger .color-active { box-shadow: none !important; } .color-active .w-color-wheel { transform: scale(1.15) !important; } .color-active .w-color-wheel::after { border: none !important; } .color-wheel .w-color-wheel { transform: scale(1) !important; } .color-wheel .w-color-wheel::after { border: 1px solid rgba(0, 0, 0, 0.2) !important; } .radial-menu[data-state=closed] .w-color-wheel { transform: scale(1) !important; } .stroke-width-item span { width: 18px; height: 18px; display: block; } .stroke-width-item div[data-state=on] { background: #3080f8 !important; } .stroke-width-item div[data-state=on] svg { color: #FFF !important; fill: #FFF !important; } .stroke-width-item div[data-state=off] svg { fill: #201F1D; } .stroke-icon svg { text-align: center; margin: auto; display: block; width: 100%; height: 100%; } .ToastViewport { --viewport-padding: 25px; position: fixed; bottom: 0; right: 0; left: 0; margin: auto !important; display: flex; flex-direction: column; padding: var(--viewport-padding); gap: 14px; max-width: 100vw; width: -moz-fit-content; width: fit-content; list-style: none; z-index: 2147483647; outline: none; pointer-events: all !important; } .ToastRoot { background-color: #29292f; color: #fff; border-radius: 30px; box-shadow: hsla(206, 22%, 7%, 0.35) 0px 10px 38px -10px, hsla(206, 22%, 7%, 0.2) 0px 10px 20px -15px; padding: 10px 14px; display: flex; flex-direction: row; gap: 8px; font-size: 15px; line-height: 1.5; max-width: 100%; overflow: hidden; justify-content: center; align-items: center; } .ToastRoot[data-state=open] { animation: slideIn 150ms cubic-bezier(0.16, 1, 0.3, 1); } .ToastRoot[data-state=closed] { animation: hide 100ms ease-in; } .ToastRoot[data-swipe=move] { transform: translateY(var(--radix-toast-swipe-move-y)); } .ToastRoot[data-swipe=cancel] { transform: translateY(0); transition: transform 200ms ease-out; } .ToastRoot[data-swipe=end] { animation: swipeOut 100ms ease-out; } @keyframes hide { from { opacity: 1; } to { opacity: 0; } } @keyframes slideIn { from { transform: translateY(calc(100% + var(--viewport-padding))); } to { transform: translateY(0); } } @keyframes swipeOut { from { transform: translateY(var(--radix-toast-swipe-end-y)); } to { transform: translateY(calc(100% + var(--viewport-padding))); } } .ToastTitle { color: #fff; font-family: "Satoshi-Medium", sans-serif; } .ToastDescription { color: var(--slate-11); font-family: "Satoshi-Medium", sans-serif; } .ToastAction { color: #fff; font-family: "Satoshi-Medium", sans-serif; text-align: right; background-color: #51515F; padding: 0px 12px !important; height: 24px !important; cursor: pointer; } .toolbar-page { width: 100%; height: 100%; pointer-events: none !important; } .popup-container:hover .popup-controls { opacity: 1; } .open { opacity: 1 !important; } .popup-drag-head { position: fixed; top: 0px; left: 0px; width: 100%; height: 100px; z-index: 1; border-radius: 30px 30px 0px 0px; opacity: 0; } .popup-controls { opacity: 0; position: absolute; top: -10px; right: -10px; box-sizing: border-box; border-radius: 30px; border: 1px solid #e8e8e8; display: flex; align-items: center; justify-content: center; gap: 8px; z-index: 999999999; background: rgb(240, 238, 238); backdrop-filter: blur(10px); padding-left: 8px; padding-right: 8px; padding-top: 6px; padding-bottom: 6px; transition: opacity 0.2s cubic-bezier(0.4, 0, 0.2, 1); } .popup-controls .popup-control svg { color: #9797a4; margin-bottom: -2px; } .popup-controls .popup-grab { cursor: grab; } .popup-controls .popup-close { cursor: pointer; } .tempimg { height: 100%; opacity: 1; position: fixed; right: 0px; top: 20px; } .container { width: 100%; height: 100%; position: fixed; top: 0px; left: 0px; z-index: 999999999; font-family: "Satoshi-Medium", sans-serif; font-size: 14px; } .ToolbarDragging .popup-container { transform: scale(1.02); filter: drop-shadow(0px 20px 50px rgba(0, 0, 0, 0.4)) !important; } /* Recording popup parent */ .popup-container { width: 356px; position: fixed; top: 32px; right: 28px; z-index: 99999999999; filter: drop-shadow(0px 4px 100px rgba(0, 0, 0, 0.35)); pointer-events: all; transition: transform 0.25s cubic-bezier(0.61, 0.11, 0.08, 0.96), filter 0.2s cubic-bezier(0.61, 0.11, 0.08, 0.96); } .popup-container::before { content: ""; display: block; width: 100%; height: 100%; transition: 2s; background: #fff; background-clip: content-box; -webkit-mask-image: radial-gradient(circle at center top, transparent 31px, #000 31px); mask-image: radial-gradient(circle at center top, transparent 31px, #000 31px); background-position: center bottom 50px; border-radius: 30px; position: absolute; top: 0px; left: 0px; } /* .popup-shape { width: 100%; height: 100%; background: $color-background; background-clip: content-box; -webkit-mask-image: radial-gradient(circle at center top, transparent 31px, #000 31px); mask-image: radial-gradient(circle at center top, transparent 31px, #000 31px); background-position: center bottom 50px; border-radius: $container-border-radius; position: relative; } */ .popup-cutout { width: 44px; height: 44px; border-radius: 50%; text-align: center; position: absolute; display: flex; justify-content: center; align-items: center; top: -22px; left: 0px; right: 0px; margin: auto; } .popup-cutout img { text-align: center; margin: auto; display: inline-block; width: 100%; border-radius: 50%; } /* Recording nav area */ .popup-nav { width: 100%; position: relative; } /* Recording content area */ .popup-content { position: relative; width: 100%; height: 100%; border-radius: 30px; overflow: hidden; } .waveform { width: 100%; margin-top: 12px; margin-bottom: 12px; } .popup-content-divider { width: 100%; height: 1px; background: #e8e8e8; margin-top: 12px; margin-bottom: 12px; } .popup-warning { display: flex; width: calc(100% + 32px); height: 80px; justify-content: space-between; align-items: center; position: relative; overflow: hidden; background-color: rgba(56, 126, 247, 0.1); margin-left: -16px; margin-top: -16px; margin-bottom: 8px; } .popup-warning .popup-warning-right { color: #3080f8; font-family: "Satoshi-Medium", sans-serif; width: 90px; } .popup-warning-left, .popup-warning-right { width: 50px; display: flex; align-items: center; text-align: center; height: 100%; justify-content: center; } .popup-warning-left svg, .popup-warning-right svg { color: #3080f8; } .popup-warning-right { cursor: pointer; } .popup-warning-middle { flex: 1; } .popup-warning-middle .popup-warning-title { font-family: "Satoshi-Bold", sans-serif; color: #29292f; } .popup-warning-middle .popup-warning-description { font-family: "Satoshi-Medium", sans-serif; color: #6e7684; margin-top: 4px; } .permission-button { background: rgba(48, 128, 248, 0.1); border-radius: 30px; color: #3080f8; display: flex; align-items: center; justify-content: center; width: 100%; height: 44px; gap: 8px; margin-top: 8px; margin-bottom: 8px; } .permission-button:first-child { margin-top: 4px !important; } .permission-button:last-child { margin-bottom: 4px !important; } .permission-button:hover { background: rgba(48, 128, 248, 0.15); cursor: pointer; } .permission-button svg { color: #3080f8; } .HelpSection { position: absolute; left: 0px; right: 0px; margin: auto; bottom: -40px; background: #edeef2; border-radius: 30px; padding: 4px 12px; text-align: center; font-family: "Satoshi-Medium", sans-serif; color: #6e7684; display: flex; align-items: center; justify-content: center; gap: 6px; width: -moz-fit-content; width: fit-content; } .HelpSection .HelpIcon { margin-top: 3px; } .HelpSection:hover { cursor: pointer; background: #fefeff; } .CollapsibleTrigger { margin-top: 12px; font-weight: "Satoshi-Bold", sans-serif; padding-top: 4px; padding-bottom: 4px; padding-left: 12px; padding-right: 12px; margin-left: auto; margin-right: auto; text-align: center; display: block; border-radius: 30px; } .CollapsibleTrigger:focus-visible { box-shadow: 0px 0px 0px 2px rgba(48, 128, 248, 0.5); } .CollapsibleLabel { color: #6e7684; font-weight: "Satoshi-Bold", sans-serif; text-align: center; display: inline-block; } .CollapsibleLabel img { margin-left: 4px; } .CollapsibleTrigger:hover { cursor: pointer; background: #FFF; } .CollapsibleRoot[data-state=open] > .CollapsibleTrigger > .CollapsibleLabel img { transform: scaleY(-1); margin-bottom: 2px; } .video-ui { position: relative; /* Blur the background */ } .blurred { /* Blur the background */ } .blurred:before { content: ""; position: absolute; backdrop-filter: blur(5px); top: 0px; left: 0px; width: 100%; height: 100%; background: rgba(255, 255, 255, 0.5); z-index: 999; } .videos-list { padding-bottom: 10px !important; max-height: calc(96vh - 260px); overflow-y: overlay; padding: 16px; } .bottom-section { width: 100%; bottom: 0px; left: 0px; box-sizing: border-box; padding-top: 8px !important; padding-bottom: 12px !important; z-index: 999999; padding: 16px; } .ModalSoon { display: flex; flex-direction: column; align-items: center; justify-content: center; height: -moz-fit-content; height: fit-content; width: 70%; padding: 20px 16px; position: absolute; left: 0px; right: 0px; top: 0px; bottom: 0px; margin: auto; border-radius: 30px; background: white; box-shadow: 0px 4px 100px 0px rgba(0, 0, 0, 0.15); z-index: 9999999; } .strong { box-shadow: 0px 4px 100px -20px rgba(0, 0, 0, 0.75) !important; } .ModalSoonEmoji { font-size: 20px; margin-bottom: 8px; } .ModalSoonTitle { font-weight: 700; color: #29292f; margin-bottom: 8px; text-align: center; } .ModalSoonDescription { text-align: center; color: #6e7684; text-align: center; margin-bottom: 8px; } .ModalSoonButton { margin-top: 8px; color: #fff; text-align: center; border-radius: 30px; background: radial-gradient(117.41% 117.78% at 35.44% 0%, #2baef8 23.13%, #3582f6 46.35%, #486def 74.48%, #7b9aea 100%); padding: 8px 16px; } .ModalSoonButton:hover { cursor: pointer; } .spinner-container, .empty-state { display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 60px 0; color: #6e7684; font-size: 14px; } .spinner { width: 32px; height: 32px; border: 3px solid rgba(0, 0, 0, 0.1); border-top-color: #6e7684; border-radius: 50%; animation: spin 0.8s linear infinite; margin-bottom: 10px; } @keyframes spin { to { transform: rotate(360deg); } } .announcement { width: 100%; top: 0px; left: 0px; padding-bottom: 24px; } .announcement-wrap { width: 85%; margin: auto; } .announcement-hero { width: 100%; margin-bottom: 16px; margin-top: 44px; } .announcement-hero img { width: 100%; border-radius: 15px; } .announcement-details { text-align: center; } .announcement-title { font-family: "Satoshi-Bold", sans-serif; font-size: 16px; color: #29292f; letter-spacing: -0.8px; margin-bottom: 12px; text-align: center; } .announcement-description { font-family: "Satoshi-Medium", sans-serif; font-size: 14px; color: #6e7684; text-align: center; margin-bottom: 24px; line-height: 1.6; letter-spacing: -0.6px; } .announcement-description a { text-decoration: none !important; color: #3080f8 !important; cursor: pointer !important; } .announcement-cta { display: flex; align-items: center; justify-content: center; gap: 8px; width: 100%; height: 44px; border-radius: 30px; background: radial-gradient(117.41% 117.78% at 35.44% 0%, #2baef8 23.13%, #3582f6 46.35%, #486def 74.48%, #7b9aea 100%); color: #fff; font-family: "Satoshi-Medium", sans-serif; font-size: 14px; cursor: pointer; transition: all 0.2s ease-in-out; animation: background-size 6s ease-in-out infinite; animation-play-state: paused; filter: drop-shadow(0px 4px 20px rgba(86, 123, 218, 0.5)); } .announcement-cta:hover { animation-play-state: running !important; } @keyframes background-size { /* Animate scale and position in and out looping */ 0% { background-size: 100% 100%; background-position: 0% 0%; } 50% { background-size: 150% 150%; background-position: 100% 0%; } 100% { background-size: 100% 100%; background-position: 0% 0%; } } .welcome-title { font-family: "Satoshi-Bold", sans-serif; font-size: 18px; color: #29292f; letter-spacing: -0.8px; margin-bottom: 12px; text-align: center; } .welcome-description { font-family: "Satoshi-Medium", sans-serif; font-size: 16px; color: #6e7684; text-align: center; margin-bottom: 20px; line-height: 1.6; letter-spacing: -0.6px; } .welcome-description a { text-decoration: none !important; color: #3080f8 !important; cursor: pointer !important; } .welcome-cta { display: flex; align-items: center; justify-content: center; gap: 8px; width: 100%; height: 44px; border-radius: 30px; background: white; border: 1px solid #e8e8e8; color: #29292f; font-family: "Satoshi-Medium", sans-serif; font-size: 14px; cursor: pointer; transition: all 0.2s ease-in-out; filter: drop-shadow(0px 1px 2px rgba(53, 87, 98, 0.1)); } .welcome-content { background: #f6f7fb; border-bottom-left-radius: 30px; border-bottom-right-radius: 30px; max-height: calc(95vh - 300px); overflow-y: overlay; padding-top: 20px; padding-bottom: 20px; border-top: 1px solid #e8e8e8; } .welcome-content-wrap { width: 85%; margin: auto; } .welcome-content-title { font-family: "Satoshi-Bold", sans-serif; font-size: 14px; color: #29292f; margin-bottom: 8px; margin-top: 8px; text-align: center; line-height: 1.4; } .welcome-feature-list { display: flex; flex-direction: column; gap: 16px; margin-top: 12px; } .welcome-feature-item { display: flex; align-items: center; gap: 12px; } .welcome-feature-icon { width: 20px; height: 20px; background: rgba(48, 128, 248, 0.1); border-radius: 50%; display: flex; align-items: center; justify-content: center; } .welcome-feature-icon-text { font-size: 14px; color: #6e7684; font-family: "Satoshi-Medium", sans-serif; } .welcome-video { margin: 25px 0 !important; width: 100%; } .video-wrapper { border-radius: 10px; } .welcome-support { font-family: "Satoshi-Medium", sans-serif; font-size: 12px; color: #6e7684 !important; text-align: center; margin-top: 20px; display: flex; align-items: center; justify-content: center; gap: 6px; text-decoration: none !important; } .welcome-support img { width: 20px; } .DropdownMenuContent, .DropdownMenuSubContent { min-width: 200px; background-color: white; margin-top: 4px; margin-right: 8px; padding-top: 12px; padding-bottom: 12px; border-radius: 15px; z-index: 99999; font-family: "Satoshi-Medium", sans-serif; color: #29292f; box-shadow: 0px 10px 38px -10px rgba(22, 23, 24, 0.35), 0px 10px 20px -15px rgba(22, 23, 24, 0.2); animation-duration: 400ms; animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1); will-change: transform, opacity; } .DropdownMenuContent[data-side=top], .DropdownMenuSubContent[data-side=top] { animation-name: slideDownAndFade; } .DropdownMenuContent[data-side=right], .DropdownMenuSubContent[data-side=right] { animation-name: slideLeftAndFade; } .DropdownMenuContent[data-side=bottom], .DropdownMenuSubContent[data-side=bottom] { animation-name: slideUpAndFade; } .DropdownMenuContent[data-side=left], .DropdownMenuSubContent[data-side=left] { animation-name: slideRightAndFade; } .ItemIndicator, .ItemIndicatorArrow { position: absolute; right: 12px; width: 18px; height: 18px; background: #3080f8; border-radius: 50%; display: inline-flex; align-items: center; justify-content: center; } .ItemIndicatorArrow { background: transparent !important; } .DropdownMenuItem, .DropdownMenuCheckboxItem, .DropdownMenuRadioItem, .DropdownMenuSubTrigger { font-size: 14px; line-height: 1; display: flex; align-items: center; height: 40px; padding: 0 5px; position: relative; padding-left: 22px; padding-right: 22px; -webkit-user-select: none; -moz-user-select: none; user-select: none; outline: none; } .DropdownMenuItem:hover, .DropdownMenuCheckboxItem:hover, .DropdownMenuRadioItem:hover, .DropdownMenuSubTrigger:hover { background-color: #f6f7fb !important; cursor: pointer; } .DropdownMenuSubTrigger[data-state=open] { background-color: var(--violet-4); color: var(--violet-11); } .DropdownMenuItem[data-disabled], .DropdownMenuCheckboxItem[data-disabled], .DropdownMenuRadioItem[data-disabled], .DropdownMenuSubTrigger[data-disabled] { color: #6e7684 !important; cursor: not-allowed; background-color: #f6f7fb !important; } .DropdownMenuItem[data-highlighted], .DropdownMenuCheckboxItem[data-highlighted], .DropdownMenuRadioItem[data-highlighted], .DropdownMenuSubTrigger[data-highlighted] { background-color: var(--violet-9); color: var(--violet-1); } .DropdownMenuLabel { padding-left: 25px; font-size: 12px; line-height: 25px; color: var(--mauve-11); } .DropdownMenuSeparator { height: 1px; background-color: var(--violet-6); margin: 5px; } .DropdownMenuItemIndicator { position: absolute; left: 0; width: 25px; display: inline-flex; align-items: center; justify-content: center; } .DropdownMenuArrow { fill: white; } .IconButton { font-family: inherit; border-radius: 100%; display: inline-flex; align-items: center; justify-content: center; margin-top: 4px; cursor: pointer; } .IconButton svg { color: #9797a4; } .IconButton:hover { background-color: var(--violet-3); } .RightSlot { margin-left: auto; padding-left: 20px; color: var(--mauve-11); } [data-highlighted] > .RightSlot { color: white; } [data-disabled] .RightSlot { color: var(--mauve-8); } @keyframes slideUpAndFade { from { opacity: 0; transform: translateY(2px); } to { opacity: 1; transform: translateY(0); } } @keyframes slideRightAndFade { from { opacity: 0; transform: translateX(-2px); } to { opacity: 1; transform: translateX(0); } } @keyframes slideDownAndFade { from { opacity: 0; transform: translateY(-2px); } to { opacity: 1; transform: translateY(0); } } @keyframes slideLeftAndFade { from { opacity: 0; transform: translateX(2px); } to { opacity: 1; transform: translateX(0); } } /* reset */ button { all: unset; } .SelectTrigger { display: inline-flex; align-items: center; justify-content: space-between; border-radius: 30px; line-height: 1; height: 44px; gap: 5px; background-color: #fff; color: #29292f; width: 100%; box-sizing: border-box; margin-top: 4px; margin-bottom: 4px; } .SelectTrigger:hover { box-shadow: 2px 2px 10px rgba(0, 0, 0, 0.1); cursor: pointer; } .SelectTrigger:focus { box-shadow: 0px 0px 0px 2px rgba(48, 128, 248, 0.5) !important; } .SelectTrigger[data-placeholder] { color: var(--violet9); } .SelectTrigger[data-state=open] { box-shadow: 0px 0px 0px 2px rgba(48, 128, 248, 0.5); } .SelectValue { text-align: left; flex: 1; display: block; width: 100%; height: 100%; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; box-sizing: border-box; } .SelectValue span { overflow: hidden; text-overflow: ellipsis; height: 100%; line-height: 44px; } .SelectIconDrop, .SelectIconType { text-align: center; } .SelectIconType { padding-left: 6px; padding-right: 0px; } .SelectIconDrop { padding-right: 16px; } .SelectTrigger[data-state=open] .SelectIconDrop img { transform: rotate(180deg); } .SelectContent { overflow: hidden; z-index: 99999999999; width: var(--radix-select-trigger-width); max-height: var(--radix-select-content-available-height); font-family: "Satoshi-Medium", sans-serif; background-color: white; border-radius: 15px; margin-top: 4px; box-shadow: 2px 2px 10px rgba(0, 0, 0, 0.1); } .SelectItem { font-size: 14px; line-height: 1; color: var(--violet11); display: flex; align-items: center; height: 44px; padding-left: 16px; padding-right: 16px; color: #29292f; position: relative; -webkit-user-select: none; -moz-user-select: none; user-select: none; } .SelectItem[data-disabled] { color: #6e7684; pointer-events: none; } .SelectItem[data-highlighted] { background: #f6f7fb; outline: none !important; } .SelectItem:hover { background: #f6f7fb; cursor: pointer; } .SelectSeparator { height: 1px; background-color: #e8e8e8; width: calc(100% - 24px); margin: auto; border-radius: 30px; margin-top: 4px; margin-bottom: 4px; } .SelectItemIndicator { position: absolute; right: 12px; width: 24px; height: 24px; background: #3080f8; border-radius: 50%; display: inline-flex; align-items: center; justify-content: center; } .SelectScrollButton { display: flex; align-items: center; justify-content: center; height: 25px; background-color: white; color: var(--violet11); cursor: default; } .SelectOff { background: #faf0f4; color: #d2234d; padding-left: 12px; padding-right: 12px; padding-top: 8px; padding-bottom: 8px; margin-right: 4px; border-radius: 30px; font-size: 12px; font-weight: 700; } .SelectIconButton { border-radius: 30px; position: relative; padding: 8px; } .SelectIconButton:hover { background-color: #f6f7fb; } /* Radix tabs navigation */ /* reset */ button, fieldset, input { all: unset; } .TabsRoot { width: 100%; margin: auto; flex: 1 1 auto; display: flex; flex-direction: column; } .TabsList { margin: auto; flex-shrink: 0; display: flex; width: -moz-fit-content; width: fit-content; align-items: center; } .hiddenTabs { display: none !important; pointer-events: none !important; -webkit-user-select: none !important; -moz-user-select: none !important; user-select: none !important; opacity: 0 !important; height: 0 !important; overflow: hidden !important; transition: opacity 0.3s ease !important; } .TabsTrigger { padding-left: 14px; padding-right: 14px; color: #6e7684; -webkit-user-select: none; -moz-user-select: none; user-select: none; cursor: pointer; } .TabsTrigger[data-state=active] { color: #29292f; } .TabsTrigger:focus-visible { position: relative; box-shadow: 0px 0px 0px 2px rgba(48, 128, 248, 0.5) !important; } .TabsTriggerSpacer { height: 50px; width: 1px; background: #e8e8e8; flex-shrink: 0; margin-left: 8px; margin-right: 8px; } /* Content of the Radix tabs */ .TabsContent { width: 100%; display: block; height: 100%; box-sizing: border-box; flex: 1 1 auto; } .TabsContent::after { content: ""; display: block; clear: both; position: absolute; bottom: 0; left: 0; width: 100%; height: 30px; background: linear-gradient(to bottom, transparent 0%, rgba(255, 255, 255, 0.5) 100%); pointer-events: none; /* Allow content behind the gradient to be clickable */ } .TabsContent:focus { outline: none; } .TabsContent:focus-visible { box-shadow: inset 0px 0px 0px 2px rgba(48, 128, 248, 0.5); } .TabsContent[data-state=inactive] { display: none; } /* Pill animation */ .pill-anim { position: absolute; height: 32px; top: 0px; bottom: 0px; margin-top: auto; margin-bottom: auto; border-radius: 30px; background: #fff; box-shadow: 2px 2px 10px rgba(0, 0, 0, 0.1); transition: all 0.2s cubic-bezier(0.25, 0.46, 0.45, 0.94); } /* .TabsList[data-value="record"] { .pill-anim { left: $spacing-03; width: 102px; } } .TabsList[data-value="dashboard"] { .pill-anim { left: 109px; width: 132px; } } */ /* Specific to the top level tabs */ .TabsRoot.tl { height: calc(100% - 40px); margin-top: 40px; } .TabsList.tl { border-radius: 30px; background: #f6f7fb; padding: 6px; font-family: "Satoshi-Bold", sans-serif; position: relative; } .TabsTrigger.tl { border-radius: 30px; background: transparent; height: 32px; display: flex; align-items: center; padding-left: 16px; padding-right: 17px; z-index: 2; position: relative; } .TabsTrigger.tl[data-state=inactive]:hover :before { content: ""; position: absolute; display: block; box-sizing: border-box; height: 100%; width: calc(100% - 10px); margin-left: 5px; background: #edeef3; z-index: -2; left: 0px; border-radius: 30px; } .TabsTriggerIcon { width: 20px; height: 20px; text-align: center; display: inline-flex; align-items: center; justify-content: center; border-radius: 30px; margin-right: 4px; } /* Specific to recording tab context */ .recording-ui { width: 100%; flex: 1 1 auto; display: flex; flex-direction: column; height: 100%; } .recording-ui .TabsRoot { margin-top: 8px; } .recording-ui .TabsList { width: 100%; border-bottom: 1px solid #e8e8e8; margin: auto; justify-content: center; } .recording-ui .TabsTrigger { padding-top: 8px; padding-bottom: 12px; box-sizing: border-box; position: relative; display: block; padding-left: 16px; padding-right: 16px; } .recording-ui .TabsTrigger:hover { background: #f6f7fb; border-top-right-radius: 15px; border-top-left-radius: 15px; } .recording-ui .TabsTrigger:focus-visible { border-radius: 10px 10px 0px 0px !important; } .recording-ui .TabsTrigger[data-state=active]::after { content: ""; display: block; position: absolute; width: 80%; left: 0px; right: 0px; bottom: 0px; margin: auto; height: 2px; border-radius: 30px; background: #3080f8; } .recording-ui .TabsTrigger[data-state=active] > .TabsTriggerLabel { color: #29292f !important; } .recording-ui .TabsTriggerLabel { text-align: center; } .recording-ui .TabsTriggerIcon { width: 20px; height: 20px; display: inline-flex; align-items: center; justify-content: center; text-align: center; margin: auto; margin-bottom: 8px; border-radius: 30px; } .recording-ui .TabsContent { background: #f6f7fb; padding: 16px; border-bottom-left-radius: 30px; border-bottom-right-radius: 30px; max-height: calc(95vh - 200px); overflow-y: overlay; } .recording-ui span { display: block; } .video-ui { width: 100%; flex: 1 1 auto; display: flex; flex-direction: column; height: 100%; } .video-ui .TabsRoot { margin-top: 8px; } .video-ui .TabsList { width: 100%; border-bottom: 1px solid #e8e8e8; margin: auto; justify-content: space-between; padding-left: 12px; padding-right: 12px; box-sizing: border-box; } .video-ui .TabsTriggerWrap { display: flex !important; align-items: center; flex-direction: row; justify-content: left; position: relative; display: block; box-sizing: border-box; } .video-ui .TabsTrigger { padding-top: 8px; padding-bottom: 12px; box-sizing: border-box; position: relative; display: block; padding-left: 20px; padding-right: 20px; } .video-ui .TabsTrigger:hover { background: #f6f7fb; border-top-right-radius: 15px; border-top-left-radius: 15px; } .video-ui .TabsTrigger:focus-visible { border-radius: 10px 10px 0px 0px !important; } .video-ui .TabsTrigger[data-state=active]::after { content: ""; display: block; position: absolute; width: 80%; left: 0px; right: 0px; bottom: 0px; margin: auto; height: 2px; border-radius: 30px; background: #3080f8; } .video-ui .TabsTrigger[data-state=active] > .TabsTriggerLabel { color: #29292f !important; } .video-ui .TabsTriggerLabel { text-align: center; } .video-ui .TabsContent { background: #f6f7fb; border-bottom-left-radius: 30px; border-bottom-right-radius: 30px; } .video-ui span { display: block; } .video-ui .TabsSort { margin-right: 12px; border-radius: 30px; padding-left: 8px; padding-right: 8px; padding-top: 8px; padding-bottom: 8px; margin-bottom: 5px; } .video-ui .TabsSort:hover { cursor: pointer; background: #f6f7fb; } .video-ui .TabsSortLabel { display: flex; flex-direction: row; justify-content: right; align-items: center; color: #6e7684; white-space: nowrap; } .video-ui .TabsSortLabel img { margin-left: 8px; } .video-ui .TabsSort:focus-visible { box-shadow: 0px 0px 0px 2px rgba(48, 128, 248, 0.5); outline: none !important; } .projectActiveBanner { display: flex; align-items: center; justify-content: space-between; background-color: #29292f; /* dark background */ color: white; border-radius: 100px; /* pill shape */ padding: 10px 16px; font-weight: 600; font-size: 15px; line-height: 1.3; max-width: 80%; -webkit-user-select: none; -moz-user-select: none; user-select: none; cursor: default; gap: 16px; top: 41px; z-index: 999999; margin: auto; left: 0; right: 0; position: absolute; } .projectActiveBannerLeft { /* text container */ white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .projectActiveBannerRight { display: flex; align-items: center; gap: 8px; } .projectActiveBannerDivider { width: 1px; height: 24px; background-color: #44444d; /* subtle divider color */ } .projectActiveBannerClose { cursor: pointer; display: flex; align-items: center; justify-content: center; } .projectActiveBannerClose img { width: 14px; height: 14px; opacity: 0.6; } .projectActiveBannerClose img:hover { opacity: 1; } /* reset */ button { all: unset; } .SwitchRow { width: 100%; display: flex; align-items: center; justify-content: space-between; height: 40px; user-select: none; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; -o-user-select: none; } .SwitchRow * { user-select: none; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; -o-user-select: none; } .SwitchRoot { width: 34px; height: 22px; background-color: #e8e8e8; border-radius: 9999px; position: relative; -webkit-tap-highlight-color: rgba(0, 0, 0, 0); } .SwitchRoot[disabled] { opacity: 0.5; cursor: not-allowed; } .SwitchRoot:focus { box-shadow: 0px 0px 0px 2px rgba(48, 128, 248, 0.5); } .SwitchRoot[data-state=checked] { background-color: #3080f8; } .SwitchRoot:hover { cursor: pointer; } .SwitchThumb { display: block; width: 14px; height: 14px; background-color: white; border-radius: 9999px; box-shadow: 0px 1px 10px rgba(0, 0, 0, 0.1); transition: transform 100ms; transform: translateX(4px); will-change: transform; } .SwitchThumb[data-state=checked] { transform: translateX(16px); } .Label { color: #6e7684; display: inline-block !important; /* Ellipsis */ text-overflow: clip; white-space: nowrap; } .Label:hover { text-overflow: clip; } .ExperimentalLabel { color: #fff; font-size: 12px; background-color: #3080f8; border-radius: 15px; padding: 2px 8px; display: inline-block !important; margin-left: 8px; } .labelDropdownWrap { display: inline-block; vertical-align: middle; position: relative; border-radius: 30px; box-sizing: border-box; } .labelDropdownWrap img { display: inline-block; margin-left: 6px; } .labelDropdownWrap .labelDropdown { display: inline-block; } .labelDropdownWrap:hover { cursor: pointer; } .labelDropdownWrap::after { content: ""; display: block; position: absolute; top: 0px; left: 0px; width: 100%; height: 100%; border-radius: 30px; border-left: 8px solid transparent; border-right: 8px solid transparent; border-top: 4px solid transparent; border-bottom: 4px solid transparent; margin-top: -4px; margin-left: -8px; } .labelDropdownWrap:hover::after { border-color: #fff; } .labelDropdownWrap:hover { background-color: #fff; } .labelDropdownActive .labelDropdownContent { display: block !important; } .labelDropdownActive img { transform: rotate(180deg); } .labelDropdownContent { position: absolute; background-color: #fff; min-width: 160px; box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2); z-index: 9999999999; border-radius: 15px; padding: 8px 0px; margin-top: 4px; border: 1px solid #e8e8e8; display: none; } .labelDropdownContent .labelDropdownContentItem { color: #29292f; padding: 12px 16px; text-decoration: none; display: block; } .labelDropdownContent .labelDropdownContentItem:hover { background-color: #f6f7fb; cursor: pointer; } .video-item-root { width: calc(100% - 2 * 8px); border-radius: 15px; padding: 8px; display: block; } .video-item-root .video-item { width: 100%; display: flex; flex-direction: row; align-items: center; justify-content: space-between; } .video-item-root .video-item-left { display: flex; flex-grow: 1; flex-direction: row; align-items: center; min-width: 0; justify-content: left; } .video-item-root .video-item-thumbnail { min-width: 48px; width: 48px; height: 38px; background: grey; border-radius: 5px; margin-right: 12px; } .video-item-root .video-item-info { display: block; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; padding-right: 4px; } .video-item-root .video-item-info-title { color: #29292f; white-space: nowrap; text-overflow: ellipsis; overflow: hidden; } .video-item-root .video-item-info-date { margin-top: 4px; color: #6e7684; font-size: 12px; white-space: nowrap; text-overflow: ellipsis; overflow: hidden; } .video-item-root:hover { cursor: pointer; background: #e9eaee; } .video-item-root:focus-visible { box-shadow: 0px 0px 0px 2px rgba(48, 128, 248, 0.5); outline: none !important; } /* Actions */ .video-item-right { display: flex; align-items: center; justify-content: right; gap: 8px; min-width: -moz-max-content; min-width: max-content; opacity: 0; } .video-item-right .copy-link { background: #fff; height: 32px; width: 32px; border-radius: 10px; display: flex; flex-direction: row; justify-content: center; align-items: center; z-index: 999999; } .video-item-right .copy-link:focus-visible { box-shadow: 0px 0px 0px 2px rgba(48, 128, 248, 0.5); outline: none !important; } .video-item-right .more-actions { height: 32px; width: 32px; background: #fff; text-align: center; border-radius: 10px; display: flex; align-items: center; justify-content: center; } .video-item-right .more-actions:focus-visible { box-shadow: 0px 0px 0px 2px rgba(48, 128, 248, 0.5); outline: none !important; } .video-item-root:hover .video-item-right, .video-item-root:focus-visible .video-item-right { opacity: 1; } .main-button { width: 100%; height: 45px; border-radius: 30px; display: flex; flex-direction: row; justify-content: center; align-items: center; position: relative; box-sizing: border-box; } .main-button .main-button-label { color: #fff; text-align: center; vertical-align: middle; align-items: center; } .main-button .main-button-shortcut { position: absolute; font-size: 12px; right: 16px; color: #fff; opacity: 0.7; } .main-button:hover { cursor: pointer; } .main-button:disabled { cursor: not-allowed; opacity: 0.5; } .main-button:focus { box-shadow: 0px 0px 0px 2px rgba(48, 128, 248, 0.5) !important; } @property --x { syntax: ""; inherits: false; initial-value: 35.44%; } @property --y { syntax: ""; inherits: false; initial-value: 0%; } .recording-button { margin-top: 8px; filter: drop-shadow(0px 4px 20px rgba(86, 123, 218, 0.5)); background: radial-gradient(127.41% 127.78% at 35.44% 0%, #2BAEF8 23.13%, #3582F6 46.35%, #486DEF 74.48%, #7B9AEA 100%); animation: 0; animation: background-size 6s ease-in-out infinite; animation-play-state: paused; position: relative; z-index: 2; } @keyframes background-size { /* Animate scale and position in and out looping */ 0% { background-size: 100% 100%; background-position: 0% 0%; } 50% { background-size: 150% 150%; background-position: 100% 0%; } 100% { background-size: 100% 100%; background-position: 0% 0%; } } .recording-button:hover { animation-play-state: running !important; } .recording-button:before { content: ""; position: absolute; display: block; top: 0px; left: 0px; width: 100%; height: 100%; box-sizing: border-box; border-radius: 30px; transition: all 0.25s ease-in-out; } .recording-button:hover:before { box-shadow: 0px 0px 0px 4px rgba(52, 138, 247, 0.25); } @keyframes pulse-animation { 0% { box-shadow: 0px 0px 0px 2px rgba(52, 138, 247, 0.25); } 25% { box-shadow: 0px 0px 0px 6px rgba(52, 138, 247, 0.25); } 50% { box-shadow: 0px 0px 0px 2px rgba(52, 138, 247, 0.25); } 100% { box-shadow: 0px 0px 0px 2px rgba(52, 138, 247, 0.25); } } @keyframes gradient-animation { 0% { --x: 35.44%; --y: 0%; } 25% { --x: 100%; --y: 30%; } 50% { --x: 70%; --y: 100%; } 75% { --x: 30%; --y: 90%; } 100% { --x: 35.44%; --y: 0%; } } .dashboard-button { background: #29292f; box-shadow: 0px 0px 0px 0px rgba(41, 41, 47, 0.25); transition: all 0.25s ease-in-out; } .dashboard-button:hover { box-shadow: 0px 0px 0px 4px rgba(41, 41, 47, 0.25); } .alarm-time-button { display: flex; justify-content: center; align-items: center; border-radius: 15px; padding: 4px 8px; position: absolute; color: #fff; opacity: 0.7; font-family: "Satoshi-Medium", sans-serif; font-size: 12px; left: 6px; } .alarm-time-button svg { margin-top: 4px; margin-right: 4px; width: 14px; } .background-effects-toggle-group { display: flex; height: 40px; width: 100%; gap: 8px; margin-bottom: 8px; margin-top: 8px; } .background-effect { display: flex; width: 40px; height: 40px; align-items: center; justify-content: center; border-radius: 30px; position: relative; color: #FFF; } .background-effect span { position: absolute; top: 0px; left: 0px; width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; font-size: 12px; font-weight: 600; z-index: 99999; font-weight: 500; } .background-effect[data-state=on]::after { content: ""; border-radius: 50%; display: block; width: 46px; height: 46px; position: absolute; border: 2px solid #3080f8; box-sizing: border-box; } .background-effect img { width: 100%; height: 100%; border-radius: 50%; position: absolute; top: 0px; left: 0px; } .background-effect:hover:not([data-state=on]) { cursor: pointer; } .background-effect:hover:not([data-state=on])::after { content: ""; border-radius: 50%; display: block; width: 46px; height: 46px; position: absolute; border: 2px solid #3080f8; opacity: 0.5; box-sizing: border-box; } .background-effect:focus-visible { box-shadow: 0px 0px 0px 2px rgba(48, 128, 248, 0.5); } .region-dimensions { width: 100%; display: flex; gap: 10px; margin-top: 10px; margin-bottom: 10px; } .region-input { flex: 1; position: relative; } .region-input input { color: #29292f !important; border-radius: 30px; height: 40px; box-sizing: border-box; position: relative; width: 100%; padding-left: 18px; padding-right: 40px; font-family: "Satoshi-Medium", sans-serif; background-color: #fff; } .region-input input:focus-visible { outline: none; box-shadow: 0px 0px 0px 2px rgba(48, 128, 248, 0.5); } .region-input span { color: #6e7684; font-family: "Satoshi-Medium", sans-serif; position: absolute; right: 18px; bottom: 12px; -webkit-user-select: none; -moz-user-select: none; user-select: none; } .time-set-parent { width: 100%; display: flex; gap: 10px; margin-top: 10px; margin-bottom: 10px; } .time-set-input { flex: 1; position: relative; } .time-set-input input { border-radius: 30px; height: 40px; box-sizing: border-box; position: relative; width: 100%; padding-left: 18px; padding-right: 40px; font-family: "Satoshi-Medium", sans-serif; background-color: #fff; -webkit-appearance: textfield; -moz-appearance: textfield; appearance: textfield; } .time-set-input input:focus-visible { outline: none; box-shadow: 0px 0px 0px 2px rgba(48, 128, 248, 0.5); } .time-set-input span { color: #6e7684; font-family: "Satoshi-Medium", sans-serif; position: absolute; right: 18px; bottom: 12px; -webkit-user-select: none; -moz-user-select: none; user-select: none; } .TooltipContent { border-radius: 30px; background-color: #29292f; padding: 10px 15px; font-size: 12px; line-height: 1; font-family: "Satoshi-Medium", sans-serif; z-index: 99999999 !important; color: #fff; box-shadow: hsla(206, 22%, 7%, 0.35) 0px 10px 38px -10px, hsla(206, 22%, 7%, 0.2) 0px 10px 20px -15px; -webkit-user-select: none; -moz-user-select: none; user-select: none; transition: opacity 0.3 ease-in-out !important; will-change: transform, opacity; animation-duration: 400ms; animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1); will-change: transform, opacity; } .hide-tooltip { display: none !important; } .tooltip-tall { margin-bottom: 20px; } .tooltip-small { margin-bottom: 5px; } .TooltipContent[data-state=delayed-open][data-side=top] { animation-name: slideDownAndFade; } .TooltipContent[data-state=delayed-open][data-side=right] { animation-name: slideLeftAndFade; } .TooltipContent[data-state=delayed-open][data-side=bottom] { animation-name: slideUpAndFade; } .TooltipContent[data-state=delayed-open][data-side=left] { animation-name: slideRightAndFade; } @keyframes slideUpAndFade { from { opacity: 0; transform: translateY(2px); } to { opacity: 1; transform: translateY(0); } } @keyframes slideRightAndFade { from { opacity: 0; transform: translateX(-2px); } to { opacity: 1; transform: translateX(0); } } @keyframes slideDownAndFade { from { opacity: 0; transform: translateY(-2px); } to { opacity: 1; transform: translateY(0); } } @keyframes slideLeftAndFade { from { opacity: 0; transform: translateX(2px); } to { opacity: 1; transform: translateX(0); } } #screenity-ui [data-radix-popper-content-wrapper] { z-index: 99999999999 !important; } .override { display: none !important; opacity: 0 !important; visibility: hidden !important; } .CanvasContainer { width: 100%; height: 100%; position: absolute; pointer-events: all !important; top: 0px !important; left: 0px !important; z-index: 99999999999 !important; } .canvas { width: 100%; height: 100%; position: absolute; top: 0px !important; left: 0px !important; z-index: 99999999999 !important; } .canvas-container { width: 100vw !important; height: 100vh !important; top: 0px !important; left: 0px !important; z-index: 99999999999; position: absolute !important; } .camera-draggable { width: 100%; height: 100%; transform-origin: left top; border-radius: 50%; } .camera-grab { width: 100%; height: 100%; position: absolute; border-radius: 50%; z-index: 99999999 !important; cursor: grab; } .camera-flipped { transform: scaleX(-1); } .camera-toolbar { display: flex; align-items: center; padding-left: 4px; padding-right: 4px; transition: opacity 0.25s cubic-bezier(0.61, 0.11, 0.08, 0.96); min-width: -moz-max-content; min-width: max-content; background-color: rgba(30, 30, 30, 0.8); box-shadow: 0 2px 10px rgba(0, 0, 0, 0.15); height: 28px; position: absolute; left: 10px; top: 10px; border-radius: 30px; backdrop-filter: blur(10px); z-index: 99999999999; opacity: 0; border: 3px solid rgba(255, 255, 255, 0.2); } .camera-draggable:hover .camera-toolbar, .camera-draggable:hover .camera-resize { opacity: 1 !important; } .camera-toolbar:hover, .camera-resize:hover { opacity: 1 !important; } .CameraToolbarSeparator { width: 1px; height: 18px; background-color: rgba(255, 255, 255, 0.3); margin: 0 4px; } .CameraToggleItem, .CameraToolbarButton { display: flex; justify-content: center; align-items: center; color: #000; height: 22px; width: 22px; text-align: center; font-size: 13px; line-height: 1; border-radius: 50%; transition: background-color 0.25s ease-in-out; background-color: rgba(124, 139, 165, 0); } .CameraToggleItem svg, .CameraToolbarButton svg { color: #9797a4; } .CameraToggleItem:hover, .CameraToolbarButton:hover { background-color: rgba(124, 139, 165, 0.2) !important; cursor: pointer; } .CameraToggleItem:disabled, .CameraToolbarButton:disabled { opacity: 0.5; pointer-events: none; } .CameraToggleItem[data-state=on], .CameraToolbarButton[data-state=on] { color: #FFF; } .CameraToggleItem[data-state=on] svg, .CameraToolbarButton[data-state=on] svg { color: #FFF; } .CameraToggleItem:hover, .CameraToggleButton:hover { cursor: pointer; } .CameraToggleItem:focus-visible, .CameraToggleButton:focus-visible { position: relative; box-shadow: 0px 0px 0px 2px rgba(48, 128, 248, 0.5); } .CameraToggleGroup { display: flex; align-items: center; justify-content: center; } .CameraToggleGroup, .CameraToolbarSeparator { display: none; } .camera-resize { position: absolute; bottom: 20px; right: 20px; z-index: 99999999999; height: 28px; width: 28px; border-radius: 50%; background-color: rgba(30, 30, 30, 0.8); box-shadow: 0 2px 10px rgba(0, 0, 0, 0.15); border: 3px solid rgba(255, 255, 255, 0.2); backdrop-filter: blur(10px); align-items: center; box-sizing: border-box; display: flex; justify-content: center; align-items: center; opacity: 0; } .camera-resize svg { color: #9797A4; text-align: center; margin: auto; display: block; } .countdown { width: 100%; height: 100%; position: absolute; top: 0px; left: 0px; z-index: 99999999999; } .countdown-circle { width: 200px; height: 200px; display: flex; justify-content: center; align-items: center; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); z-index: 999; text-align: center; } .countdown-overlay { width: 100%; height: 100%; background: rgba(0, 0, 0, 0.5); position: absolute; top: 0px; left: 0px; z-index: 99; display: flex; justify-content: center; align-items: center; } .countdown-number { position: absolute; width: 20px; height: 60px; z-index: 9999999; left: 0px; right: 0px; top: 0px; bottom: 0px; margin: auto; font-weight: 300 !important; font-family: "Satoshi-Light", sans-serif !important; font-size: 48px !important; line-height: 60px !important; letter-spacing: normal !important; text-transform: none !important; word-spacing: normal !important; color: #fff; text-align: center; display: block; transition: all 0.6s ease-in-out; } .background { width: 100%; height: 100%; position: absolute; left: 0px; top: 0px; filter: url("#goo"); transform: rotate(0deg); transition: all 3s ease-in-out; } .circle { z-index: 9; position: absolute; transform: scale(0.8); top: 0px; left: 0px; right: 0px; bottom: 0px; margin: auto; border-radius: 50%; background: radial-gradient(118.3% 119.01% at 35.44% 0%, #2baef8 23.13%, #3582f6 46.35%, #486def 74.48%, #7b9aea 100%); width: 200px; height: 200px; transition: all 1.5s ease-in-out; } .c { width: 50px; height: 50px; z-index: 999; border-radius: 50%; position: absolute; top: 0px; right: 0px; left: 0px; bottom: 0px; margin: auto; transition: cubic-bezier(0.82, 0.1, 0.24, 0.99) 1.5s; opacity: 1; } .c2 { background: radial-gradient(118.3% 119.01% at 35.44% 0%, #2b96f8 23.13%, #356bf6 64.58%); transform: translate(20px, 20px); } .c3 { background: radial-gradient(118.3% 119.01% at 35.44% 0%, #4884ca 15.3%, #2b89f8 78.83%); transform: translate(-30px, -40px); } .c3:after { content: ""; position: absolute; width: 150px; height: 150px; filter: blur(50px); border-radius: 50%; top: 0px; right: 0px; left: 0px; bottom: 0px; margin: auto; transition: cubic-bezier(0.82, 0.1, 0.24, 0.99) 1.5s; background: #cbe8f7; z-index: -1; } .recording-countdown { pointer-events: all; } .recording-countdown .c2 { transform: translate(-15px, 15px); } .recording-countdown .c3 { transform: translate(-10px, -5px); } .countdown-info { position: absolute; left: 0px; right: 0px; margin: auto; bottom: 20px; border-radius: 30px; border: 2px solid rgba(255, 255, 255, 0.3); text-align: center; display: block; padding: 10px 20px; font-family: "Satoshi-Medium", sans-serif; font-size: 14px; line-height: 1.4; letter-spacing: normal; text-transform: none; color: #fff; z-index: 99999999999; width: -moz-fit-content; width: fit-content; } /* reset */ button { all: unset; } .AlertDialogOverlay { background-color: rgba(0, 0, 0, 0.5); position: fixed; inset: 0; animation: overlayShow 150ms cubic-bezier(0.16, 1, 0.3, 1); z-index: 99999999999; } .AlertDialogContent { overflow: auto !important; background-color: white; border-radius: 30px; box-shadow: hsla(206, 22%, 7%, 0.35) 0px 10px 38px -10px, hsla(206, 22%, 7%, 0.2) 0px 10px 20px -15px; position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 90vw; max-width: 500px; max-height: 85vh; padding: 35px 25px; animation: contentShow 150ms cubic-bezier(0.16, 1, 0.3, 1); z-index: 99999999999; } .AlertDialogContent:focus { outline: none; } .AlertDialogTitle { margin: 0; color: #29292f; font-size: 14px; line-height: 1.4; font-family: "Satoshi-Bold", sans-serif; font-weight: 700; } .AlertDialogDescription { margin-bottom: 20px; color: #6e7684; font-size: 14px; line-height: 1.5; } .AlertDialogDescription a { color: #3080f8 !important; font-weight: 600 !important; text-decoration: none !important; display: inline-block; cursor: pointer; } .Button { display: inline-flex; align-items: center; justify-content: center; border-radius: 30px; padding: 0 15px; font-size: 14px; line-height: 1; font-weight: 500; height: 35px; } .Button.blue { background-color: rgba(48, 128, 248, 0.1); color: #3080f8; } .Button.blue:hover { background-color: rgba(48, 128, 248, 0.15); cursor: pointer; } .Button.blue:focus { box-shadow: 0px 0px 0px 2px rgba(48, 128, 248, 0.5); } .Button.red { background-color: rgba(247, 56, 90, 0.1); color: rgb(247, 56, 90); } .Button.red:hover { background-color: rgba(247, 56, 90, 0.15); cursor: pointer; } .Button.red:focus { box-shadow: 0px 0px 0px 2px rgba(48, 128, 248, 0.5); } .Button.grey { background: rgba(110, 118, 132, 0.1); color: #6e7684; } .Button.grey:hover { background: rgba(110, 118, 132, 0.15); cursor: pointer; } .Button.grey:focus { box-shadow: 0px 0px 0px 2px rgba(48, 128, 248, 0.5); } @keyframes overlayShow { from { opacity: 0; } to { opacity: 1; } } @keyframes contentShow { from { opacity: 0; transform: translate(-50%, -48%) scale(0.96); } to { opacity: 1; transform: translate(-50%, -50%) scale(1); } } .SideButtonModal { display: inline-flex; align-items: center; justify-content: center; border-radius: 30px; padding: 0 15px; font-size: 14px; line-height: 1; font-weight: 500; height: 35px; color: #6e7684; font-family: "Satoshi-Medium", sans-serif; } .SideButtonModal:hover { cursor: pointer; background: rgba(110, 118, 132, 0.05); } .box-hole { position: absolute; top: 0; left: 0; width: 100%; height: 100%; z-index: 9999999999; } .resize-handle { position: absolute; width: 10px; height: 10px; border-radius: 50%; background-color: white; border: 2px solid rgba(0, 0, 0, 0.5); box-sizing: border-box; } .resize-handle.top-left { bottom: 0; left: 0; top: 0; right: 0; margin: auto; cursor: nwse-resize; } .resize-handle.top { top: 0; left: 50%; transform: translateX(-50%); cursor: ns-resize; } .resize-handle.top-right { bottom: 0; left: 0; top: 0; right: 0; margin: auto; cursor: nesw-resize; } .resize-handle.right { top: 50%; right: 0; transform: translateY(-50%); cursor: ew-resize; } .resize-handle.bottom-right { bottom: 0; left: 0; top: 0; right: 0; margin: auto; cursor: nwse-resize; } .resize-handle.bottom { bottom: 0; left: 50%; transform: translateX(-50%); cursor: ns-resize; } .resize-handle.bottom-left { bottom: 0; left: 0; top: 0; right: 0; margin: auto; cursor: nesw-resize; } .resize-handle.left { top: 50%; left: 0; transform: translateY(-50%); cursor: ew-resize; } .region-recording * { pointer-events: none !important; } .WarningViewport { --viewport-padding: 25px; position: fixed; top: 0; right: 0; left: 0; margin: auto !important; display: flex; flex-direction: column; padding: var(--viewport-padding); gap: 14px; max-width: 100vw; width: -moz-fit-content; width: fit-content; list-style: none; z-index: 2147483647; outline: none; pointer-events: all !important; } .warning-root { background-color: #29292f; color: #fff; border-radius: 30px; box-shadow: hsla(206, 22%, 7%, 0.35) 0px 10px 38px -10px, hsla(206, 22%, 7%, 0.2) 0px 10px 20px -15px; padding: 14px 20px; display: flex; flex-direction: row; justify-content: center; gap: 8px; font-size: 15px; line-height: 1.5; max-width: 350px; overflow: hidden; align-items: center; text-align: left; align-items: flex-start; } .warning-content { display: flex; flex-direction: column; justify-content: left; align-items: flex-start; gap: 8px; width: 100%; } .warning-root[data-state=open] { animation: slideIn2 150ms cubic-bezier(0.16, 1, 0.3, 1); } .warning-root[data-state=closed] { animation: hide 100ms ease-in; } .warning-root[data-swipe=move] { transform: translateY(var(--radix-toast-swipe-move-y)); } .warning-root[data-swipe=cancel] { transform: translateY(0); transition: transform 200ms ease-out; } .warning-root[data-swipe=end] { animation: swipeOut2 100ms ease-out; } @keyframes hide { from { opacity: 1; } to { opacity: 0 !important; } } @keyframes slideIn2 { from { transform: translateY(calc(-100% - var(--viewport-padding))); } to { transform: translateY(0); } } @keyframes swipeOut2 { from { transform: translateY(var(--radix-toast-swipe-end-y)); } to { transform: translateY(calc(-100% - var(--viewport-padding))); } } .warning-title { color: #fff; font-family: "Satoshi-Medium", sans-serif; line-height: 1.4; } .warning-description { color: #fff; opacity: 0.8; font-family: "Satoshi-Medium", sans-serif; line-height: 1.5; } .ToastAction { color: #fff; font-family: "Satoshi-Medium", sans-serif; text-align: right; background-color: #51515f; padding: 0px 12px !important; height: 24px !important; cursor: pointer; } .warning-close { z-index: 999999; } .warning-close:hover { cursor: pointer; } :host { font-size: 16px; line-height: normal; letter-spacing: normal; word-spacing: normal; text-transform: none; font-style: normal; font-variant: normal; text-indent: 0; direction: ltr; white-space: normal; } .screenity-scrollbar::-webkit-scrollbar { background-color: rgba(0, 0, 0, 0); width: 16px; height: 16px; z-index: 999999; } .screenity-scrollbar::-webkit-scrollbar-track { background-color: rgba(0, 0, 0, 0); } .screenity-scrollbar::-webkit-scrollbar-thumb { background-color: rgba(0, 0, 0, 0); border-radius: 16px; border: 0px solid #fff; } .screenity-scrollbar::-webkit-scrollbar-button { display: none; } .screenity-scrollbar:hover::-webkit-scrollbar-thumb { background-color: #a0a0a5; border: 4px solid #fff; } ::-webkit-scrollbar-thumb:hover { background-color: #a0a0a5; border: 4px solid #f4f4f4; } .ScreenityDropdownMenuContent { min-width: 200px; background-color: white; margin-top: 4px; margin-right: 8px; padding-top: 12px; padding-bottom: 12px; border-radius: 15px; z-index: 99999; font-family: "Satoshi-Medium", sans-serif; color: #29292f; box-shadow: 0px 10px 38px -10px rgba(22, 23, 24, 0.35), 0px 10px 20px -15px rgba(22, 23, 24, 0.2); animation-duration: 400ms; animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1); will-change: transform, opacity; } .ScreenityDropdownMenuContent[data-side=top] { animation-name: slideDownAndFade; } .ScreenityDropdownMenuContent[data-side=right] { animation-name: slideLeftAndFade; } .ScreenityDropdownMenuContent[data-side=bottom] { animation-name: slideUpAndFade; } .ScreenityDropdownMenuContent[data-side=left] { animation-name: slideRightAndFade; } .ScreenityItemIndicator { position: absolute; right: 12px; width: 18px; height: 18px; background: #3080f8; border-radius: 50%; display: inline-flex; align-items: center; justify-content: center; } .ScreenityDropdownMenuItem, .ScreenityDropdownMenuRadioItem { font-size: 14px; line-height: 1; display: flex; align-items: center; height: 40px; padding: 0 5px; position: relative; padding-left: 22px; padding-right: 22px; -webkit-user-select: none; -moz-user-select: none; user-select: none; outline: none; } .ScreenityDropdownMenuItem:hover { background-color: #f6f7fb !important; cursor: pointer; } .ScreenityDropdownMenuItem[data-disabled] { color: #6e7684 !important; cursor: not-allowed; background-color: #f6f7fb !important; } .driver-popover.ScreenityOnboardingPopover, .ScreenityOnboardingPopover, .onboarding-popover { border-radius: 30px !important; background: #fff !important; color: #29292f !important; font-family: "Satoshi-Regular", sans-serif !important; box-shadow: 0 4px 20px rgba(38, 38, 52, 0.08) !important; padding: 20px !important; max-width: 340px !important; font-size: 14px !important; z-index: 99999999999 !important; } .driver-popover.ScreenityOnboardingPopover .driver-popover-title, .ScreenityOnboardingPopover .driver-popover-title, .onboarding-popover .driver-popover-title { font-size: 1rem !important; font-family: "Satoshi-Medium", sans-serif !important; margin-bottom: 12px !important; color: #29292f !important; } .driver-popover.ScreenityOnboardingPopover .driver-popover-description, .ScreenityOnboardingPopover .driver-popover-description, .onboarding-popover .driver-popover-description { font-size: 14px !important; font-family: "Satoshi-Medium", sans-serif !important; color: #6e7684 !important; line-height: 1.5 !important; margin-bottom: 18px !important; } .driver-popover.ScreenityOnboardingPopover .driver-popover-description a, .ScreenityOnboardingPopover .driver-popover-description a, .onboarding-popover .driver-popover-description a { color: #3b82f6 !important; font-family: "Satoshi-Bold", sans-serif !important; text-decoration: none !important; } .driver-popover.ScreenityOnboardingPopover .driver-popover-close-btn, .ScreenityOnboardingPopover .driver-popover-close-btn, .onboarding-popover .driver-popover-close-btn { top: 14px; right: 12px; color: #6e7684 !important; font-size: 1.5rem !important; } .driver-popover.ScreenityOnboardingPopover .driver-popover-arrow, .ScreenityOnboardingPopover .driver-popover-arrow, .onboarding-popover .driver-popover-arrow { width: 0px; height: 0px; background: none !important; box-shadow: none !important; box-sizing: border-box; } .driver-popover.ScreenityOnboardingPopover .driver-popover-arrow-side-top, .ScreenityOnboardingPopover .driver-popover-arrow-side-top, .onboarding-popover .driver-popover-arrow-side-top { border-left: 8px solid transparent !important; border-right: 8px solid transparent !important; border-top: 8px solid #fff !important; } .driver-popover.ScreenityOnboardingPopover .driver-popover-arrow-side-bottom, .ScreenityOnboardingPopover .driver-popover-arrow-side-bottom, .onboarding-popover .driver-popover-arrow-side-bottom { border-left: 8px solid transparent !important; border-right: 8px solid transparent !important; border-bottom: 8px solid #fff !important; } .driver-popover.ScreenityOnboardingPopover .driver-popover-arrow-side-left, .ScreenityOnboardingPopover .driver-popover-arrow-side-left, .onboarding-popover .driver-popover-arrow-side-left { border-top: 8px solid transparent !important; border-bottom: 8px solid transparent !important; border-left: 8px solid #fff !important; } .driver-popover.ScreenityOnboardingPopover .driver-popover-arrow-side-right, .ScreenityOnboardingPopover .driver-popover-arrow-side-right, .onboarding-popover .driver-popover-arrow-side-right { border-top: 8px solid transparent !important; border-bottom: 8px solid transparent !important; border-right: 8px solid #fff !important; } .driver-popover.ScreenityOnboardingPopover .driver-popover-arrow-align-start, .ScreenityOnboardingPopover .driver-popover-arrow-align-start, .onboarding-popover .driver-popover-arrow-align-start { top: 35px !important; } .driver-popover.ScreenityOnboardingPopover .driver-popover-arrow-align-end, .ScreenityOnboardingPopover .driver-popover-arrow-align-end, .onboarding-popover .driver-popover-arrow-align-end { bottom: 35px !important; } .driver-popover.ScreenityOnboardingPopover .driver-popover-arrow-align-left, .ScreenityOnboardingPopover .driver-popover-arrow-align-left, .onboarding-popover .driver-popover-arrow-align-left { left: 35px !important; } .driver-popover.ScreenityOnboardingPopover .driver-popover-arrow-align-right, .ScreenityOnboardingPopover .driver-popover-arrow-align-right, .onboarding-popover .driver-popover-arrow-align-right { right: 35px !important; } .driver-popover.ScreenityOnboardingPopover .driver-popover-progress-text, .ScreenityOnboardingPopover .driver-popover-progress-text, .onboarding-popover .driver-popover-progress-text { font-size: 0.8rem !important; font-family: "Satoshi-Medium", sans-serif !important; color: #6e7684 !important; opacity: 0.7 !important; margin-top: 2px !important; } .driver-popover.ScreenityOnboardingPopover .driver-popover-footer, .ScreenityOnboardingPopover .driver-popover-footer, .onboarding-popover .driver-popover-footer { display: flex !important; justify-content: flex-end; gap: 8px; margin-top: 16px; } .driver-popover.ScreenityOnboardingPopover .driver-popover-footer .driver-popover-navigation-btns, .ScreenityOnboardingPopover .driver-popover-footer .driver-popover-navigation-btns, .onboarding-popover .driver-popover-footer .driver-popover-navigation-btns { gap: 6px !important; } .driver-popover.ScreenityOnboardingPopover .driver-popover-footer .driver-popover-navigation-btns .driver-popover-next-btn, .driver-popover.ScreenityOnboardingPopover .driver-popover-footer .driver-popover-navigation-btns .driver-popover-prev-btn, .driver-popover.ScreenityOnboardingPopover .driver-popover-footer .driver-popover-navigation-btns .driver-popover-close-btn, .ScreenityOnboardingPopover .driver-popover-footer .driver-popover-navigation-btns .driver-popover-next-btn, .ScreenityOnboardingPopover .driver-popover-footer .driver-popover-navigation-btns .driver-popover-prev-btn, .ScreenityOnboardingPopover .driver-popover-footer .driver-popover-navigation-btns .driver-popover-close-btn, .onboarding-popover .driver-popover-footer .driver-popover-navigation-btns .driver-popover-next-btn, .onboarding-popover .driver-popover-footer .driver-popover-navigation-btns .driver-popover-prev-btn, .onboarding-popover .driver-popover-footer .driver-popover-navigation-btns .driver-popover-close-btn { background-color: #3080f8 !important; color: white !important; border: none !important; padding: 10px 14px !important; border-radius: 30px !important; font-family: "Satoshi-Medium", sans-serif !important; font-weight: 500 !important; cursor: pointer !important; font-size: 0.875rem !important; text-shadow: none !important; } .driver-popover.ScreenityOnboardingPopover .driver-popover-footer .driver-popover-navigation-btns .driver-popover-next-btn:hover, .ScreenityOnboardingPopover .driver-popover-footer .driver-popover-navigation-btns .driver-popover-next-btn:hover, .onboarding-popover .driver-popover-footer .driver-popover-navigation-btns .driver-popover-next-btn:hover { background: rgb(23.3341121495, 112.8668224299, 247.1658878505) !important; } .driver-popover.ScreenityOnboardingPopover .driver-popover-footer .driver-popover-navigation-btns .driver-popover-prev-btn, .ScreenityOnboardingPopover .driver-popover-footer .driver-popover-navigation-btns .driver-popover-prev-btn, .onboarding-popover .driver-popover-footer .driver-popover-navigation-btns .driver-popover-prev-btn { background-color: transparent !important; color: #29292f !important; border: 1px solid #e8e8e8 !important; } .driver-popover.ScreenityOnboardingPopover .driver-popover-footer .driver-popover-navigation-btns .driver-popover-prev-btn:hover, .ScreenityOnboardingPopover .driver-popover-footer .driver-popover-navigation-btns .driver-popover-prev-btn:hover, .onboarding-popover .driver-popover-footer .driver-popover-navigation-btns .driver-popover-prev-btn:hover { background: #f6f7fb !important; } .driver-popover.ScreenityOnboardingPopover .driver-popover-footer .driver-popover-navigation-btns .driver-popover-close-btn, .ScreenityOnboardingPopover .driver-popover-footer .driver-popover-navigation-btns .driver-popover-close-btn, .onboarding-popover .driver-popover-footer .driver-popover-navigation-btns .driver-popover-close-btn { background-color: transparent !important; color: #6e7684 !important; } .driver-overlay { z-index: 2147483645 !important; } .driver-stage { z-index: 2147483646 !important; } .driver-popover, .driver-popover.ScreenityOnboardingPopover { z-index: 2147483647 !important; }/*# sourceMappingURL=app.css.map */ ================================================ FILE: src/pages/Content/styles/app.scss ================================================ @use "../toolbar/styles/_Page.scss"; @use "../popup/styles/_Popup.scss"; @use "../canvas/styles/_Canvas.scss"; @use "../camera/styles/_Camera.scss"; @use "../countdown/styles/_Countdown.scss"; @use "../modal/styles/_Modal.scss"; @use "../region/styles/_Region.scss"; @use "../camera-only/styles/CameraOnly.scss"; @use "../warning/styles/Warning.scss"; @use "./_variables" as *; @use "sass:color"; // Reset inherited properties inside the shadow tree. :host { font-size: 16px; line-height: normal; letter-spacing: normal; word-spacing: normal; text-transform: none; font-style: normal; font-variant: normal; text-indent: 0; direction: ltr; white-space: normal; } .screenity-scrollbar::-webkit-scrollbar { background-color: rgba(0, 0, 0, 0); width: 16px; height: 16px; z-index: 999999; } .screenity-scrollbar::-webkit-scrollbar-track { background-color: rgba(0, 0, 0, 0); } .screenity-scrollbar::-webkit-scrollbar-thumb { background-color: rgba(0, 0, 0, 0); border-radius: 16px; border: 0px solid #fff; } .screenity-scrollbar::-webkit-scrollbar-button { display: none; } .screenity-scrollbar:hover::-webkit-scrollbar-thumb { background-color: #a0a0a5; border: 4px solid #fff; } ::-webkit-scrollbar-thumb:hover { background-color: #a0a0a5; border: 4px solid #f4f4f4; } .ScreenityDropdownMenuContent { min-width: 200px; background-color: white; margin-top: 4px; margin-right: 8px; padding-top: 12px; padding-bottom: 12px; border-radius: 15px; z-index: 99999; font-family: "Satoshi-Medium", sans-serif; color: #29292f; box-shadow: 0px 10px 38px -10px rgba(22, 23, 24, 0.35), 0px 10px 20px -15px rgba(22, 23, 24, 0.2); animation-duration: 400ms; animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1); will-change: transform, opacity; } .ScreenityDropdownMenuContent[data-side="top"] { animation-name: slideDownAndFade; } .ScreenityDropdownMenuContent[data-side="right"] { animation-name: slideLeftAndFade; } .ScreenityDropdownMenuContent[data-side="bottom"] { animation-name: slideUpAndFade; } .ScreenityDropdownMenuContent[data-side="left"] { animation-name: slideRightAndFade; } .ScreenityItemIndicator { position: absolute; right: 12px; width: 18px; height: 18px; background: #3080f8; border-radius: 50%; display: inline-flex; align-items: center; justify-content: center; } .ScreenityDropdownMenuItem, .ScreenityDropdownMenuRadioItem { font-size: 14px; line-height: 1; display: flex; align-items: center; height: 40px; padding: 0 5px; position: relative; padding-left: 22px; padding-right: 22px; user-select: none; outline: none; } .ScreenityDropdownMenuItem:hover { background-color: #f6f7fb !important; cursor: pointer; } .ScreenityDropdownMenuItem[data-disabled] { color: #6e7684 !important; cursor: not-allowed; background-color: #f6f7fb !important; } // Onboarding (driver.js) .driver-popover.ScreenityOnboardingPopover, .ScreenityOnboardingPopover, .onboarding-popover { border-radius: $container-border-radius !important; background: $color-background !important; color: $color-text-primary !important; font-family: $font-regular !important; box-shadow: $container-soft-shadow !important; padding: 20px !important; max-width: 340px !important; font-size: $font-size-normal !important; z-index: $z-index-max !important; .driver-popover-title { font-size: 1rem !important; font-family: $font-medium !important; margin-bottom: 12px !important; color: $color-text-primary !important; } .driver-popover-description { font-size: $font-size-normal !important; font-family: $font-medium !important; color: $color-text-secondary !important; line-height: 1.5 !important; margin-bottom: 18px !important; a { color: #3b82f6 !important; font-family: $font-bold !important; text-decoration: none !important; } } .driver-popover-close-btn { top: 14px; right: 12px; color: $color-text-secondary !important; font-size: 1.5rem !important; } .driver-popover-arrow { width: 0px; height: 0px; // border: 4px solid #fff; background: none !important; box-shadow: none !important; box-sizing: border-box; } .driver-popover-arrow-side-top { border-left: 8px solid transparent !important; border-right: 8px solid transparent !important; border-top: 8px solid $color-background !important; } .driver-popover-arrow-side-bottom { border-left: 8px solid transparent !important; border-right: 8px solid transparent !important; border-bottom: 8px solid $color-background !important; } .driver-popover-arrow-side-left { border-top: 8px solid transparent !important; border-bottom: 8px solid transparent !important; border-left: 8px solid $color-background !important; } .driver-popover-arrow-side-right { border-top: 8px solid transparent !important; border-bottom: 8px solid transparent !important; border-right: 8px solid $color-background !important; } .driver-popover-arrow-align-start { top: 35px !important; } .driver-popover-arrow-align-end { bottom: 35px !important; } .driver-popover-arrow-align-left { left: 35px !important; } .driver-popover-arrow-align-right { right: 35px !important; } .driver-popover-progress-text { font-size: $font-size-detail !important; font-family: $font-medium !important; color: $color-text-secondary !important; opacity: 0.7 !important; margin-top: 2px !important; } .driver-popover-footer { display: flex !important; justify-content: flex-end; gap: 8px; margin-top: 16px; .driver-popover-navigation-btns { gap: 6px !important; .driver-popover-next-btn, .driver-popover-prev-btn, .driver-popover-close-btn { background-color: $color-primary !important; color: white !important; border: none !important; padding: 10px 14px !important; border-radius: 30px !important; font-family: $font-medium !important; font-weight: 500 !important; cursor: pointer !important; font-size: 0.875rem !important; text-shadow: none !important; } .driver-popover-next-btn { &:hover { background: color.adjust($color-primary, $lightness: -5%) !important; } } .driver-popover-prev-btn { background-color: transparent !important; color: $color-text-primary !important; border: 1px solid $color-border !important; &:hover { background: $color-light-grey !important; } } .driver-popover-close-btn { background-color: transparent !important; color: $color-text-secondary !important; } } } } // Keep driver overlay/stage above extension UI layers during onboarding. .driver-overlay { z-index: 2147483645 !important; } .driver-stage { z-index: 2147483646 !important; } .driver-popover, .driver-popover.ScreenityOnboardingPopover { z-index: 2147483647 !important; } ================================================ FILE: src/pages/Content/styles/dist/app.css ================================================ /* Colors */ /* Font */ /* Spacing */ /* $spacing-01: 0.125rem; $spacing-02: 0.25rem; $spacing-03: 0.5rem; $spacing-04: 0.75rem; $spacing-05: 1rem; */ /* Container */ /* Gradients */ /* Events */ /* Z-index */ /* reset */ a, button { all: unset; } iframe { width: 100%; height: 100%; position: fixed; overflow: scroll; z-index: -9999; top: 0px; left: 0px; border: 0px; pointer-events: all !important; } .container { pointer-events: none !important; } .ToolbarBounds { position: fixed; top: 0px; left: 0px; box-sizing: border-box; width: 100%; height: 100%; border: 10px solid #3080F8; pointer-events: none; transform: scale(1.2); opacity: 0; transition: transform 0.25s cubic-bezier(0.61, 0.11, 0.08, 0.96), opacity 0.25s ease-in-out; } .ToolbarBounds.ToolbarShake { transform: scale(1); opacity: 0.4; } .react-draggable { pointer-events: all; } .ToolbarShake .react-draggable { width: 100%; height: 100%; } .ToolbarElastic { transition: all 0.25s cubic-bezier(0.68, -0.55, 0.265, 1.55); } .ToolbarShake .ToolbarRoot { animation: subtleshake 0.9s cubic-bezier(0.36, 0.07, 0.19, 0.97) both; animation-iteration-count: infinite !important; background-color: white !important; } .ToolbarDragging .ToolbarRoot { transform: scale(1.02); } .ToolbarDragging .ToolbarRoot::after { filter: drop-shadow(0px 20px 50px rgba(0, 0, 0, 0.5)); } @keyframes shake { 0% { transform: translate(1px, 1px) rotate(0deg); } 20% { transform: translate(-3px, 0px) rotate(1deg); } 30% { transform: translate(3px, 2px) rotate(0deg); } 40% { transform: translate(1px, -1px) rotate(1deg); } 50% { transform: translate(-1px, 2px) rotate(-1deg); } 60% { transform: translate(-3px, 1px) rotate(0deg); } 70% { transform: translate(3px, 1px) rotate(-1deg); } 80% { transform: translate(-1px, -1px) rotate(1deg); } 90% { transform: translate(1px, 2px) rotate(0deg); } 100% { transform: translate(1px, -2px) rotate(-1deg); } } @keyframes subtleshake { 0% { transform: translate(0px, 0px) rotate(0deg); } 10% { transform: translate(-1px, 1px) rotate(1deg); } 20% { transform: translate(-1px, -1px) rotate(-1deg); } 30% { transform: translate(1px, 0px) rotate(0deg); } 40% { transform: translate(-1px, 1px) rotate(-1deg); } 50% { transform: translate(1px, -1px) rotate(1deg); } 60% { transform: translate(-1px, 1px) rotate(-1deg); } 70% { transform: translate(-1px, -1px) rotate(1deg); } 80% { transform: translate(1px, 1px) rotate(0deg); } 90% { transform: translate(0px, -1px) rotate(-1deg); } 100% { transform: translate(-1px, 1px) rotate(1deg); } } .ToolbarRoot { display: flex; align-items: center; padding-left: 10px; transition: opacity 0.25s cubic-bezier(0.61, 0.11, 0.08, 0.96), transform 0.2s cubic-bezier(0.61, 0.11, 0.08, 0.96); min-width: -moz-max-content; min-width: max-content; background-color: white; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.15); padding-right: 10px; height: 48px; position: absolute; bottom: 20px; left: 20px; border-radius: 30px; } .ToolbarRoot::after { content: ""; display: block; width: 100%; height: 100%; background: #FFF; z-index: -9999999; position: absolute; left: 0px; top: 0px; border-radius: 30px; filter: drop-shadow(0px 4px 50px rgba(0, 0, 0, 0.3)); transition: filter 0.2s ease-in-out; } .ToolbarRecordingControls { display: flex; justify-content: center; align-items: center; background: #F6F7FB; border-radius: 30px; font-family: "Satoshi-Medium", sans-serif; height: calc(100% - 12px); padding-left: 2px; padding-right: 2px; } .ToolbarRecordingTime { margin-right: 4px; width: 42px; color: #29292F; font-size: 13px; } .ToolbarToggleGroup { display: flex; align-items: center; } .ToolbarToggleWrap { flex: 1 1 auto; align-items: center; justify-content: flex-start; position: relative; flex: 0 0 auto; width: 32px; height: 32px; display: inline-flex; line-height: 1; align-items: center; justify-content: center; } .ToolbarToggleItem, .ToolbarModeItem, .ToolbarModeItemSingle, .ToolbarLink, .ToolbarButton { display: flex; justify-content: center; align-items: center; color: #000; height: 32px; width: 32px; text-align: center; font-size: 13px; line-height: 1; border-radius: 50%; transition: background-color 0.25s ease-in-out; background-color: rgba(124, 139, 165, 0); } .ToolbarToggleItem svg, .ToolbarModeItem svg, .ToolbarModeItemSingle svg, .ToolbarLink svg, .ToolbarButton svg { color: #9797A4; } .ToolbarToggleItem:disabled, .ToolbarModeItem:disabled, .ToolbarModeItemSingle:disabled, .ToolbarLink:disabled, .ToolbarButton:disabled { opacity: 0.5; cursor: not-allowed !important; background: none !important; } .ToolbarToggleItem:hover, .ToolbarModeItem:hover, .ToolbarModeItemSingle:hover, .ToolbarLink:hover, .ToolbarButton:hover { cursor: pointer; background-color: rgba(124, 139, 165, 0.1) !important; } .ToolbarToggleItem:focus-visible, .ToolbarModeItemSingle:focus-visible, .ToolbarModeItem:focus-visible, .ToolbarLink:focus-visible, .ToolbarButton:focus-visible { position: relative; box-shadow: 0px 0px 0px 2px rgba(48, 128, 248, 0.5); } .ToolbarModeItemSingle { display: flex; justify-content: center; align-items: center; z-index: 99999; position: relative; } .ToolbarModeItemSingle:first-child { margin-left: 0; } .ToolbarModeItemSingle[data-state=on] { background: rgba(120, 192, 114, 0.1); } .ToolbarModeItemSingle[data-state=on] svg { color: #78C072; } .ToolbarModeItemSingle[data-state=on]::before { transform: translateY(0px) scale(1) !important; opacity: 1 !important; } .ToolbarModeItem { display: flex; justify-content: center; align-items: center; z-index: 99999; position: relative; } .ToolbarModeItem:first-child { margin-left: 0; } .ToolbarModeItem::before { content: ""; display: block; width: 100%; height: 50%; border-radius: 5rem 5rem 0% 0%; box-sizing: border-box; position: absolute; top: -16px; left: 0; z-index: -999999; transition: transform 0.25s cubic-bezier(0.61, 0.11, 0.08, 0.96), opacity 0.25s ease-in-out; transform: translateY(5px) scale(0) !important; border-right: 3px solid white; border-top: 9px solid white; border-left: 3px solid white; background-color: transparent; opacity: 0; } .ToolbarModeItem[data-state=on] { background: rgba(56, 126, 247, 0.1); } .ToolbarModeItem[data-state=on] svg { color: #3080F8; } .ToolbarModeItem[data-state=on]::before { transform: translateY(0px) scale(1) !important; opacity: 1 !important; } .ToolbarBottom .ToolbarModeItem::before { transform: translateY(-5px) scale(0.5) !important; bottom: -16px; top: unset !important; border-bottom: 9px solid white !important; border-radius: 0% 0% 5rem 5rem !important; border-top: none !important; } .ToolbarBottom .ToolbarModeItem[data-state=on]::before { transform: translateY(0px) scale(1) !important; } .ToolbarToggleItem { display: flex; justify-content: center; align-items: center; z-index: 99999; position: relative; } .ToolbarToggleItem:first-child { margin-left: 0; } .ToolbarToggleItem[data-state=on] { background: radial-gradient(117.41% 117.78% at 35.44% 0%, #2BAEF8 23.13%, #3582F6 46.35%, #486DEF 74.48%, #7B9AEA 100%); color: #FFF; } .ToolbarToggleItem[data-state=on] svg { color: #FFF; } .ToolbarSeparator { width: 1px; height: 19px; background-color: #E8E8E8; margin: 0 8px; } .ToolbarLink { background-color: transparent; color: var(--mauve11); display: inline-flex; justify-content: center; align-items: center; } .ToolbarLink:hover { background-color: transparent; cursor: pointer; } body { background-color: white !important; } .DrawingToolbar.show-toolbar { opacity: 1 !important; pointer-events: all !important; transform: scale(1) translate(calc(-50% + 16px), 0px) !important; } .ToolbarBottom .DrawingToolbar { transform-origin: 0 -100% !important; } .DrawingToolbar { opacity: 0; pointer-events: none; align-items: center; display: flex; min-width: -moz-max-content; min-width: max-content; padding-left: 10px; padding-right: 10px; border-radius: 6px; box-shadow: 0 2px 10px var(--blackA7); position: absolute; height: 44px; left: 0px; transform: translate(calc(-50% + 16px)); transform-origin: 0 100%; border-radius: 15px; z-index: 99999999; transition: transform 0.25s cubic-bezier(0.61, 0.11, 0.08, 0.96), opacity 0.25s cubic-bezier(0.61, 0.11, 0.08, 0.96); transform: scale(0.5) translate(calc(-50% + 16px), 10px); } .DrawingToolbar::after { content: ""; display: block; width: 100%; height: 100%; filter: blur(10px); opacity: 0.15; background-color: #000; position: absolute; left: 0px; top: 0px; z-index: -999999999999999 !important; } .DrawingToolbar::before { content: ""; display: block; width: 100%; height: 100%; background: rgba(242, 241, 242, 0.85); -webkit-backdrop-filter: blur(5px); backdrop-filter: blur(5px); background-clip: content-box; -webkit-mask-image: radial-gradient(circle at 50% 59px, transparent 20px, #000 20px); mask-image: radial-gradient(circle at 50% 59px, transparent 20px, #000 20px); background-position: center bottom 50px; border-radius: 15px; position: absolute; top: 0px; left: 0px; z-index: -2; } .DrawingToolbar .ToolbarSeparator { background-color: #dddcdc; } .ToolbarTop .DrawingToolbar { bottom: 49px !important; } .ToolbarBottom .DrawingToolbar { top: 48px !important; } .ToolbarBottom .DrawingToolbar::before { -webkit-mask-image: radial-gradient(circle at 50% -14px, transparent 20px, #000 20px) !important; mask-image: radial-gradient(circle at 50% -14px, transparent 20px, #000 20px); background-position: center bottom 50px !important; } .ColorPicker { width: 14px; height: 14px; background: #ED6C3A; border: 1.5px solid rgba(0, 0, 0, 0.2); border-radius: 50%; } .shapeToolbar { position: absolute; display: flex; align-items: center; justify-content: center; box-shadow: 0 2px 10px var(--blackA7); left: 165px; padding: 4px; opacity: 0; bottom: 45px; transform: translateY(calc(-50% + 16px)); transform-origin: 0 100%; border-radius: 15px; z-index: 99999999; transition: transform 0.25s cubic-bezier(0.61, 0.11, 0.08, 0.96), opacity 0.25s cubic-bezier(0.61, 0.11, 0.08, 0.96); transform: scale(0.5) translatY(calc(-50% + 16px), 10px); } .shapeToolbar::after { content: ""; display: block; width: 100%; height: 100%; background: #FFF; z-index: -9999999; position: absolute; left: 0px; top: 0px; border-radius: 15px; filter: drop-shadow(0px 4px 50px rgba(0, 0, 0, 0.3)); transition: filter 0.2s ease-in-out; } .shapeToolbar.show-toolbar { opacity: 1 !important; pointer-events: all !important; transform: scale(1) translateY(calc(-50% + 16px), 0px) !important; } .TooltipContent { border-radius: 30px; background-color: #29292F; padding: 10px 15px; font-size: 12px; margin-bottom: 10px; bottom: 100px; line-height: 1; font-family: "Satoshi-Medium", sans-serif; z-index: 99999999 !important; color: #FFF; box-shadow: hsla(206, 22%, 7%, 0.35) 0px 10px 38px -10px, hsla(206, 22%, 7%, 0.2) 0px 10px 20px -15px; -webkit-user-select: none; -moz-user-select: none; user-select: none; transition: opacity 0.3 ease-in-out; will-change: transform, opacity; animation-duration: 400ms; animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1); will-change: transform, opacity; } .hide-tooltip { display: none !important; } .tooltip-tall { margin-bottom: 20px; } .tooltip-small { margin-bottom: 5px; } .TooltipContent[data-state=delayed-open][data-side=top] { animation-name: slideDownAndFade; } .TooltipContent[data-state=delayed-open][data-side=right] { animation-name: slideLeftAndFade; } .TooltipContent[data-state=delayed-open][data-side=bottom] { animation-name: slideUpAndFade; } .TooltipContent[data-state=delayed-open][data-side=left] { animation-name: slideRightAndFade; } @keyframes slideUpAndFade { from { opacity: 0; transform: translateY(2px); } to { opacity: 1; transform: translateY(0); } } @keyframes slideRightAndFade { from { opacity: 0; transform: translateX(-2px); } to { opacity: 1; transform: translateX(0); } } @keyframes slideDownAndFade { from { opacity: 0; transform: translateY(-2px); } to { opacity: 1; transform: translateY(0); } } @keyframes slideLeftAndFade { from { opacity: 0; transform: translateX(2px); } to { opacity: 1; transform: translateX(0); } } #screenity-ui [data-radix-popper-content-wrapper] { z-index: 99999999999 !important; } .radial-menu { position: absolute; z-index: 9999999999999; width: 100px; height: 100px; top: -66px; left: -49px; pointer-events: none; opacity: 1; transform: scale(1); transition: transform 0.3s cubic-bezier(0.68, -0.55, 0.265, 1.55), opacity 0.25s ease-in-out; } .radial-menu::after { content: ""; position: absolute; top: 0; left: 0; width: 100%; height: 100%; z-index: -1; border: 28px solid #FFF; box-sizing: border-box; border-radius: 50%; -webkit-backdrop-filter: blur(40px); backdrop-filter: blur(40px); opacity: 0; filter: drop-shadow(0px 4px 50px rgba(0, 0, 0, 0.3)); transform: scale(0); transition: transform 0.25s cubic-bezier(0.18, -0.55, 0.265, 1.45), opacity 0.2s ease-in-out; transition-delay: 0.05s; } .radial-menu[data-state=open] { transform: scale(1); opacity: 1; pointer-events: all !important; } .radial-menu[data-state=open]::after { transform: scale(1); border: 28px solid #FFF; opacity: 1; } .color-wheel::after { opacity: 0 !important; } .eyedropper { position: absolute; left: 0px; top: 0px; right: 0px; bottom: 0px; margin: auto; width: 16px; height: 16px; padding: 8px; z-index: 999999999; background-color: #FFF; border-radius: 50%; opacity: 0; text-align: center; transition: transform 0.25s cubic-bezier(0.68, -0.55, 0.265, 1.55), opacity 0.3s ease-in-out, background-color 0.25s ease-in-out !important; transform: scale(0); overflow: hidden; transform-style: preserve-3d; } .eyedropper svg { color: #9797A4; } .eyedropper:focus-visible { outline: none; background-color: #E6E7EA !important; } .eyedropper:hover { cursor: pointer; background-color: #E6E7EA; } .eye-active { background-color: #3080F8 !important; } .eye-active svg { color: #FFF !important; fill: #FFF !important; } .color-wheel .eyedropper { opacity: 0 !important; pointer-events: none !important; transform: scale(0) !important; } .radial-menu[data-state=open] .eyedropper { transform: scale(1) !important; opacity: 1; } .radial-menu-items { transform: rotate(10deg); z-index: 99999999; position: absolute; left: 0; right: 0; top: 0; bottom: 0; margin: auto; } .radial-menu-item { position: absolute; left: 0; right: 0; top: 0; bottom: 0; margin: auto; width: 18px; height: 18px; z-index: 999; border-radius: 50%; text-align: center; box-sizing: border-box; line-height: 50px; color: white; border: 1px solid rgba(0, 0, 0, 0.2); transition: transform 0.25s cubic-bezier(0.61, 0.11, 0.08, 0.96), opacity 0.25s cubic-bezier(0.61, 0.11, 0.08, 0.96); opacity: 0; } .radial-menu-item:hover { cursor: pointer; } .color-wheel .radial-menu-item { opacity: 0 !important; } .radial-menu-item-child { width: 100%; height: 100%; position: absolute; box-sizing: border-box; border: 0px; box-shadow: none; top: 0px; pointer-events: none; z-index: 9999999; left: 0px; border-radius: 50%; background-size: cover; } .radial-menu-item-child:focus-visible { outline: none; box-shadow: 0px 0px 0px 2px rgba(48, 128, 248, 0.5); } .radial-menu[data-state=open] .radial-menu-item-child { pointer-events: all !important; } .radial-menu-item:nth-child(1), .wheel-trigger { transform: rotate(0deg) translate(0px); } .radial-menu[data-state=open] .radial-menu-item:nth-child(1), .radial-menu[data-state=open] .wheel-trigger { transition-delay: calc(.25s - 0s); transform: rotate(0deg) translate(36px); opacity: 1; } .radial-menu-item:nth-child(2), .wheel-trigger { transform: rotate(40deg) translate(0px); } .radial-menu[data-state=open] .radial-menu-item:nth-child(2), .radial-menu[data-state=open] .wheel-trigger { transition-delay: calc(.25s - 0.02s); transform: rotate(40deg) translate(36px); opacity: 1; } .radial-menu-item:nth-child(3), .wheel-trigger { transform: rotate(80deg) translate(0px); } .radial-menu[data-state=open] .radial-menu-item:nth-child(3), .radial-menu[data-state=open] .wheel-trigger { transition-delay: calc(.25s - 0.04s); transform: rotate(80deg) translate(36px); opacity: 1; } .radial-menu-item:nth-child(4), .wheel-trigger { transform: rotate(120deg) translate(0px); } .radial-menu[data-state=open] .radial-menu-item:nth-child(4), .radial-menu[data-state=open] .wheel-trigger { transition-delay: calc(.25s - 0.06s); transform: rotate(120deg) translate(36px); opacity: 1; } .radial-menu-item:nth-child(5), .wheel-trigger { transform: rotate(160deg) translate(0px); } .radial-menu[data-state=open] .radial-menu-item:nth-child(5), .radial-menu[data-state=open] .wheel-trigger { transition-delay: calc(.25s - 0.08s); transform: rotate(160deg) translate(36px); opacity: 1; } .radial-menu-item:nth-child(6), .wheel-trigger { transform: rotate(200deg) translate(0px); } .radial-menu[data-state=open] .radial-menu-item:nth-child(6), .radial-menu[data-state=open] .wheel-trigger { transition-delay: calc(.25s - 0.1s); transform: rotate(200deg) translate(36px); opacity: 1; } .radial-menu-item:nth-child(7), .wheel-trigger { transform: rotate(240deg) translate(0px); } .radial-menu[data-state=open] .radial-menu-item:nth-child(7), .radial-menu[data-state=open] .wheel-trigger { transition-delay: calc(.25s - 0.12s); transform: rotate(240deg) translate(36px); opacity: 1; } .radial-menu-item:nth-child(8), .wheel-trigger { transform: rotate(280deg) translate(0px); } .radial-menu[data-state=open] .radial-menu-item:nth-child(8), .radial-menu[data-state=open] .wheel-trigger { transition-delay: calc(.25s - 0.14s); transform: rotate(280deg) translate(36px); opacity: 1; } .radial-menu-item:nth-child(9), .wheel-trigger { transform: rotate(320deg) translate(0px); } .radial-menu[data-state=open] .radial-menu-item:nth-child(9), .radial-menu[data-state=open] .wheel-trigger { transition-delay: calc(.25s - 0.16s); transform: rotate(320deg) translate(36px); opacity: 1; } .color-active { border: 1px solid #FFFFFF; box-shadow: 0px 0px 0px 2px #0D99FF; } .color-wheel .color-active { border: none !important; box-shadow: none !important; } .wheel-trigger { transition: transform 0.2s cubic-bezier(0.61, 0.11, 0.08, 0.96), width 0.2s cubic-bezier(0.61, 0.11, 0.08, 0.96), height 0.2s cubic-bezier(0.61, 0.11, 0.08, 0.96), opacity 0.25s cubic-bezier(0.61, 0.11, 0.08, 0.96); position: absolute; left: 0; right: 0; top: 0; bottom: 0; opacity: 0; margin: auto; width: 18px; box-sizing: border-box; height: 18px; z-index: 9999; box-sizing: border-box; background-blend-mode: screen; border-radius: 50%; } .wheel-trigger:hover { cursor: pointer; } .wheel-trigger .radial-menu-item-child { transform: rotate(30deg); } .wheel-trigger .radial-menu-item-child:after { content: ""; position: absolute; left: 0; right: 0; width: 100%; height: 100%; z-index: 9999999; border-radius: 50%; box-sizing: border-box; border: 1px solid rgba(0, 0, 0, 0.2); } .color-wheel .wheel-trigger { width: 100px !important; height: 100px !important; transform: rotate(320deg) translate(0px) !important; z-index: 999999999999 !important; filter: drop-shadow(0px 4px 50px rgba(0, 0, 0, 0.3)); } .color-wheel-handle { width: 12px !important; height: 12px !important; border-radius: 50%; left: 20px; top: 20px; opacity: 0; background-color: #F17FD7; border: 2px solid white; z-index: 999999999999; display: none; transition: width 0.25s cubic-bezier(0.61, 0.11, 0.08, 0.96), height 0.25s cubic-bezier(0.61, 0.11, 0.08, 0.96), margin-left 0.25s cubic-bezier(0.61, 0.11, 0.08, 0.96), margin-top 0.25s cubic-bezier(0.61, 0.11, 0.08, 0.96); } .color-wheel-handle:hover { width: 18px !important; height: 18px !important; margin-left: -2px; margin-top: -2px; } .color-wheel .color-wheel-handle { opacity: 0; display: block; animation: fadeIn 0.25s cubic-bezier(0.61, 0.11, 0.08, 0.96) forwards; animation-delay: 0.5s; } .w-color-wheel { pointer-events: none; position: absolute !important; width: 100% !important; height: 100% !important; z-index: 99999999 !important; } .w-color-wheel::after { content: ""; box-sizing: border-box; display: block; width: 100%; height: 100%; position: absolute; z-index: 9999999; border: 1px solid rgba(0, 0, 0, 0.2); box-sizing: border-box; border-radius: 50%; } .color-wheel .w-color-wheel { pointer-events: all !important; } .w-color-wheel-fill { box-shadow: none !important; border: 2px solid white !important; width: 14px !important; height: 14px !important; transition: width 0.2s cubic-bezier(0.61, 0.11, 0.08, 0.96), height 0.2s cubic-bezier(0.61, 0.11, 0.08, 0.96), margin 0.2s cubic-bezier(0.61, 0.11, 0.08, 0.96) !important; box-sizing: border-box !important; margin-left: -2px !important; margin-top: -2px !important; z-index: 9999999999 !important; } .w-color-wheel-pointer { z-index: 99999999999 !important; opacity: 0; animation: none !important; } .color-wheel .w-color-wheel-pointer { animation: fadeInScale 0.25s cubic-bezier(0.215, 0.61, 0.355, 1) forwards !important; animation-delay: 0.28s !important; } .w-color-wheel-fill:hover { width: 18px !important; height: 18px !important; margin-left: -4px !important; margin-top: -4px !important; } /* Fade in keyframes */ @keyframes fadeInScale { 0% { opacity: 0; } 100% { opacity: 1; } } .color-wheel, .color-wheel .wheel-trigger, .color-wheel .wheel-trigger .radial-menu-item-child, .color-wheel-handle { cursor: pointer !important; } .color-wheel-input { background: #000; border-radius: 30px; height: 29px; color: #FFF; text-align: center; line-height: 29px; padding-left: 8px; padding-right: 8px; position: absolute; margin-top: -35px; font-family: "Satoshi-Medium", sans-serif; left: 50%; transform: translate(-50%, 0); opacity: 0; } .color-wheel .color-wheel-input { animation: fadeIn 0.3s cubic-bezier(0.61, 0.11, 0.08, 0.96) forwards; animation-delay: 0.2s; pointer-events: all !important; } .color-wheel-input { pointer-events: none; } @keyframes fadeIn { 0% { opacity: 0; margin-top: -35px; } 100% { opacity: 1; margin-top: -40px; } } .color-active .color-preview { opacity: 1; } .radial-menu[data-state=closed] .color-preview { opacity: 0 !important; } .color-preview { width: 90%; height: 90%; box-sizing: border-box; border-radius: 50%; position: absolute; left: 50%; top: 50%; z-index: 9999999999; transform: translate(-50%, -50%); opacity: 0; animation: none; pointer-events: none; border: 1px solid #FFF; box-sizing: border-box; } .color-wheel .color-preview { opacity: 0 !important; } .wheel-trigger .color-active { box-shadow: none !important; } .color-active .w-color-wheel { transform: scale(1.15) !important; } .color-active .w-color-wheel::after { border: none !important; } .color-wheel .w-color-wheel { transform: scale(1) !important; } .color-wheel .w-color-wheel::after { border: 1px solid rgba(0, 0, 0, 0.2) !important; } .radial-menu[data-state=closed] .w-color-wheel { transform: scale(1) !important; } .stroke-width-item span { width: 18px; height: 18px; display: block; } .stroke-width-item div[data-state=on] { background: #3080F8 !important; } .stroke-width-item div[data-state=on] svg { color: #FFF !important; fill: #FFF !important; } .stroke-width-item div[data-state=off] svg { fill: #201F1D; } .stroke-icon svg { text-align: center; margin: auto; display: block; width: 100%; height: 100%; } .ToastViewport { --viewport-padding: 25px; position: fixed; bottom: 0; right: 0; left: 0; margin: auto !important; display: flex; flex-direction: column; padding: var(--viewport-padding); gap: 14px; max-width: 100vw; width: -moz-fit-content; width: fit-content; list-style: none; z-index: 2147483647; outline: none; pointer-events: all !important; } .ToastRoot { background-color: #29292F; color: #FFF; border-radius: 30px; box-shadow: hsla(206, 22%, 7%, 0.35) 0px 10px 38px -10px, hsla(206, 22%, 7%, 0.2) 0px 10px 20px -15px; padding: 10px 14px; display: flex; flex-direction: row; gap: 8px; font-size: 15px; line-height: 1.5; max-width: 100%; overflow: hidden; justify-content: center; align-items: center; } .ToastRoot[data-state=open] { animation: slideIn 150ms cubic-bezier(0.16, 1, 0.3, 1); } .ToastRoot[data-state=closed] { animation: hide 100ms ease-in; } .ToastRoot[data-swipe=move] { transform: translateY(var(--radix-toast-swipe-move-y)); } .ToastRoot[data-swipe=cancel] { transform: translateY(0); transition: transform 200ms ease-out; } .ToastRoot[data-swipe=end] { animation: swipeOut 100ms ease-out; } @keyframes hide { from { opacity: 1; } to { opacity: 0; } } @keyframes slideIn { from { transform: translateY(calc(100% + var(--viewport-padding))); } to { transform: translateY(0); } } @keyframes swipeOut { from { transform: translateY(var(--radix-toast-swipe-end-y)); } to { transform: translateY(calc(100% + var(--viewport-padding))); } } .ToastTitle { color: #FFF; font-family: "Satoshi-Medium", sans-serif; } .ToastDescription { color: var(--slate-11); font-family: "Satoshi-Medium", sans-serif; } .ToastAction { color: #FFF; font-family: "Satoshi-Medium", sans-serif; text-align: right; background-color: #51515F; padding: 0px 12px !important; height: 24px !important; cursor: pointer; } .toolbar-page { width: 100%; height: 100%; pointer-events: none !important; } .tempimg { height: 100%; opacity: 1; position: fixed; right: 0px; top: 20px; } .container { width: 100%; height: 100%; position: fixed; top: 0px; left: 0px; z-index: 999999999; font-family: "Satoshi-Medium", sans-serif; font-size: 14px; } /* Recording popup parent */ .popup-container { width: 356px; position: fixed; top: 32px; right: 28px; z-index: 99999999999; filter: drop-shadow(0px 4px 100px rgba(0, 0, 0, 0.35)); pointer-events: all; } .popup-container::before { content: ""; display: block; width: 100%; height: 100%; transition: 2s; background: #FFF; background-clip: content-box; -webkit-mask-image: radial-gradient(circle at center top, transparent 31px, #000 31px); mask-image: radial-gradient(circle at center top, transparent 31px, #000 31px); background-position: center bottom 50px; border-radius: 30px; position: absolute; top: 0px; left: 0px; } /* .popup-shape { width: 100%; height: 100%; background: $color-background; background-clip: content-box; -webkit-mask-image: radial-gradient(circle at center top, transparent 31px, #000 31px); mask-image: radial-gradient(circle at center top, transparent 31px, #000 31px); background-position: center bottom 50px; border-radius: $container-border-radius; position: relative; } */ .popup-cutout { width: 44px; height: 44px; border-radius: 50%; text-align: center; position: absolute; display: flex; justify-content: center; align-items: center; top: -22px; left: 0px; right: 0px; margin: auto; } .popup-cutout img { text-align: center; margin: auto; display: inline-block; width: 100%; border-radius: 50%; } /* Recording nav area */ .popup-nav { width: 100%; position: relative; } /* Recording content area */ .popup-content { position: relative; width: 100%; height: 100%; border-radius: 30px; overflow: hidden; } .waveform { width: 100%; margin-top: 12px; margin-bottom: 12px; } .popup-content-divider { width: 100%; height: 1px; background: #E8E8E8; margin-top: 12px; margin-bottom: 12px; } .popup-warning { display: flex; width: calc(100% + 2rem); height: 80px; justify-content: space-between; align-items: center; position: relative; overflow: hidden; background-color: rgba(56, 126, 247, 0.1); margin-left: -1rem; margin-top: -1rem; margin-bottom: 0.5rem; } .popup-warning .popup-warning-right { color: #3080F8; font-family: "Satoshi-Medium", sans-serif; width: 90px; } .popup-warning-left, .popup-warning-right { width: 50px; display: flex; align-items: center; text-align: center; height: 100%; justify-content: center; } .popup-warning-left svg, .popup-warning-right svg { color: #3080F8; } .popup-warning-right { cursor: pointer; } .popup-warning-middle { flex: 1; } .popup-warning-middle .popup-warning-title { font-family: "Satoshi-Bold", sans-serif; color: #29292F; } .popup-warning-middle .popup-warning-description { font-family: "Satoshi-Medium", sans-serif; color: #6E7684; margin-top: 4px; } .permission-button { background: rgba(48, 128, 248, 0.1); border-radius: 30px; color: #3080F8; display: flex; align-items: center; justify-content: center; width: 100%; height: 44px; gap: 8px; margin-top: 8px; margin-bottom: 8px; } .permission-button:first-child { margin-top: 4px !important; } .permission-button:last-child { margin-bottom: 4px !important; } .permission-button:hover { background: rgba(48, 128, 248, 0.15); cursor: pointer; } .permission-button svg { color: #3080F8; } .CollapsibleTrigger { margin-top: 12px; font-weight: "Satoshi-Bold", sans-serif; padding-top: 4px; padding-bottom: 4px; padding-left: 12px; padding-right: 12px; margin-left: auto; margin-right: auto; text-align: center; display: block; border-radius: 30px; } .CollapsibleTrigger:focus-visible { box-shadow: 0px 0px 0px 2px rgba(48, 128, 248, 0.5); } .CollapsibleLabel { color: #6E7684; font-weight: "Satoshi-Bold", sans-serif; text-align: center; display: inline-block; } .CollapsibleLabel img { margin-left: 4px; } .CollapsibleTrigger:hover { cursor: pointer; background: #FFF; } .CollapsibleRoot[data-state=open] > .CollapsibleTrigger > .CollapsibleLabel img { transform: scaleY(-1); margin-bottom: 2px; } .video-ui { position: relative; /* Blur the background */ } /* .video-ui:before { content: ""; position: absolute; -webkit-backdrop-filter: blur(5px); backdrop-filter: blur(5px); top: 0px; left: 0px; width: 100%; height: 100%; background: rgba(255, 255, 255, 0.5); z-index: 999; } */ .videos-list { padding-bottom: 10px !important; max-height: calc(96vh - 260px); overflow-y: overlay; padding: 16px; } .bottom-section { width: 100%; bottom: 0px; left: 0px; box-sizing: border-box; padding-top: 8px !important; padding-bottom: 12px !important; padding: 16px; } .ModalSoon { display: flex; flex-direction: column; align-items: center; justify-content: center; height: -moz-fit-content; height: fit-content; width: 70%; padding: 30px 16px; position: absolute; left: 0px; right: 0px; top: 0px; bottom: 0px; margin: auto; border-radius: 30px; background: white; box-shadow: 0px 4px 100px 0px rgba(0, 0, 0, 0.15); z-index: 9999999; } .ModalSoonEmoji { font-size: 20px; margin-bottom: 8px; } .ModalSoonTitle { font-weight: 700; color: #29292F; margin-bottom: 8px; text-align: center; } .ModalSoonDescription { text-align: center; color: #6E7684; text-align: center; margin-bottom: 8px; } .ModalSoonButton { margin-top: 8px; color: #FFF; text-align: center; border-radius: 30px; background: radial-gradient(117.41% 117.78% at 35.44% 0%, #2BAEF8 23.13%, #3582F6 46.35%, #486DEF 74.48%, #7B9AEA 100%); padding: 8px 16px; } .ModalSoonButton:hover { cursor: pointer; } /* reset */ button { all: unset; } .SelectTrigger { display: inline-flex; align-items: center; justify-content: space-between; border-radius: 30px; line-height: 1; height: 44px; gap: 5px; background-color: #FFF; color: #29292F; width: 100%; box-sizing: border-box; margin-top: 4px; margin-bottom: 4px; } .SelectTrigger:hover { box-shadow: 2px 2px 10px rgba(0, 0, 0, 0.1); cursor: pointer; } .SelectTrigger:focus { box-shadow: 0px 0px 0px 2px rgba(48, 128, 248, 0.5) !important; } .SelectTrigger[data-placeholder] { color: var(--violet9); } .SelectTrigger[data-state=open] { box-shadow: 0px 0px 0px 2px rgba(48, 128, 248, 0.5); } .SelectValue { flex: 1; display: block; width: 100%; height: 100%; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; box-sizing: border-box; } .SelectValue span { overflow: hidden; text-overflow: ellipsis; height: 100%; line-height: 44px; } .SelectIconDrop, .SelectIconType { text-align: center; } .SelectIconType { padding-left: 0.4rem; padding-right: 0px; } .SelectIconDrop { padding-right: 16px; } .SelectTrigger[data-state=open] .SelectIconDrop img { transform: rotate(180deg); } .SelectContent { overflow: hidden; z-index: 99999999999; width: var(--radix-select-trigger-width); max-height: var(--radix-select-content-available-height); font-family: "Satoshi-Medium", sans-serif; background-color: white; border-radius: 15px; margin-top: 4px; box-shadow: 2px 2px 10px rgba(0, 0, 0, 0.1); } .SelectItem { font-size: 14px; line-height: 1; color: var(--violet11); display: flex; align-items: center; height: 44px; padding-left: 16px; padding-right: 16px; color: #29292F; position: relative; -webkit-user-select: none; -moz-user-select: none; user-select: none; } .SelectItem[data-disabled] { color: #6E7684; pointer-events: none; } .SelectItem[data-highlighted] { background: #F6F7FB; outline: none !important; } .SelectItem:hover { background: #F6F7FB; cursor: pointer; } .SelectSeparator { height: 1px; background-color: #E8E8E8; width: calc(100% - 24px); margin: auto; border-radius: 30px; margin-top: 4px; margin-bottom: 4px; } .SelectItemIndicator { position: absolute; right: 12px; width: 24px; height: 24px; background: #3080F8; border-radius: 50%; display: inline-flex; align-items: center; justify-content: center; } .SelectScrollButton { display: flex; align-items: center; justify-content: center; height: 25px; background-color: white; color: var(--violet11); cursor: default; } .SelectOff { background: #FAF0F4; color: #D2234D; padding-left: 12px; padding-right: 12px; padding-top: 8px; padding-bottom: 8px; margin-right: 4px; border-radius: 30px; font-size: 12px; font-weight: 700; } .SelectIconButton { border-radius: 30px; position: relative; padding: 8px; } .SelectIconButton:hover { background-color: #F6F7FB; } /* Radix tabs navigation */ /* reset */ button, fieldset, input { all: unset; } .TabsRoot { width: 100%; margin: auto; flex: 1 1 auto; display: flex; flex-direction: column; } .TabsList { margin: auto; flex-shrink: 0; display: flex; width: -moz-fit-content; width: fit-content; } .TabsTrigger { padding-left: 12px; padding-right: 12px; color: #6E7684; -webkit-user-select: none; -moz-user-select: none; user-select: none; cursor: pointer; } .TabsTrigger[data-state=active] { color: #29292F; } .TabsTrigger:focus-visible { position: relative; box-shadow: 0px 0px 0px 2px rgba(48, 128, 248, 0.5) !important; } /* Content of the Radix tabs */ .TabsContent { width: 100%; display: block; height: 100%; box-sizing: border-box; flex: 1 1 auto; } .TabsContent:focus { outline: none; } .TabsContent:focus-visible { box-shadow: inset 0px 0px 0px 2px rgba(48, 128, 248, 0.5); } .TabsContent[data-state=inactive] { display: none; } /* Pill animation */ .pill-anim { position: absolute; height: 32px; top: 0px; bottom: 0px; margin-top: auto; margin-bottom: auto; border-radius: 30px; background: #FFF; box-shadow: 2px 2px 10px rgba(0, 0, 0, 0.1); transition: all 0.2s cubic-bezier(0.25, 0.46, 0.45, 0.94); } .TabsList[data-value=record] .pill-anim { left: 8px; width: 102px; } .TabsList[data-value=dashboard] .pill-anim { left: 109px; width: 132px; } /* Specific to the top level tabs */ .TabsRoot.tl { height: calc(100% - 40px); margin-top: 40px; } .TabsList.tl { border-radius: 30px; background: #F6F7FB; padding: 6px; font-family: "Satoshi-Bold", sans-serif; position: relative; } .TabsTrigger.tl { border-radius: 30px; background: transparent; height: 32px; display: flex; align-items: center; padding-left: 16px; padding-right: 17px; z-index: 2; position: relative; } .TabsTrigger.tl[data-state=inactive]:hover :before { content: ""; position: absolute; display: block; box-sizing: border-box; height: 100%; width: calc(100% - 10px); margin-left: 5px; background: #EDEEF3; z-index: -2; left: 0px; border-radius: 30px; } .TabsTriggerIcon { width: 20px; height: 20px; text-align: center; display: inline-flex; align-items: center; justify-content: center; border-radius: 30px; margin-right: 4px; } /* Specific to recording tab context */ .recording-ui { width: 100%; flex: 1 1 auto; display: flex; flex-direction: column; height: 100%; } .recording-ui .TabsRoot { margin-top: 8px; } .recording-ui .TabsList { width: 100%; border-bottom: 1px solid #E8E8E8; margin: auto; justify-content: center; } .recording-ui .TabsTrigger { padding-top: 8px; padding-bottom: 12px; box-sizing: border-box; position: relative; display: block; padding-left: 16px; padding-right: 16px; } .recording-ui .TabsTrigger:hover { background: #F6F7FB; border-top-right-radius: 15px; border-top-left-radius: 15px; } .recording-ui .TabsTrigger:focus-visible { border-radius: 10px 10px 0px 0px !important; } .recording-ui .TabsTrigger[data-state=active]::after { content: ""; display: block; position: absolute; width: 80%; left: 0px; right: 0px; bottom: 0px; margin: auto; height: 2px; border-radius: 30px; background: #3080F8; } .recording-ui .TabsTrigger[data-state=active] > .TabsTriggerLabel { color: #29292F !important; } .recording-ui .TabsTriggerLabel { text-align: center; } .recording-ui .TabsTriggerIcon { width: 20px; height: 20px; display: inline-flex; align-items: center; justify-content: center; text-align: center; margin: auto; margin-bottom: 8px; border-radius: 30px; } .recording-ui .TabsContent { background: #F6F7FB; padding: 16px; border-bottom-left-radius: 30px; border-bottom-right-radius: 30px; max-height: calc(95vh - 200px); overflow-y: overlay; } .recording-ui span { display: block; } .video-ui { width: 100%; flex: 1 1 auto; display: flex; flex-direction: column; height: 100%; } .video-ui .TabsRoot { margin-top: 8px; } .video-ui .TabsList { width: 100%; border-bottom: 1px solid #E8E8E8; margin: auto; justify-content: space-between; padding-left: 12px; padding-right: 12px; box-sizing: border-box; } .video-ui .TabsTriggerWrap { display: flex !important; align-items: center; flex-direction: row; justify-content: left; position: relative; display: block; box-sizing: border-box; } .video-ui .TabsTrigger { padding-top: 8px; padding-bottom: 12px; box-sizing: border-box; position: relative; display: block; padding-left: 20px; padding-right: 20px; } .video-ui .TabsTrigger:hover { background: #F6F7FB; border-top-right-radius: 15px; border-top-left-radius: 15px; } .video-ui .TabsTrigger:focus-visible { border-radius: 10px 10px 0px 0px !important; } .video-ui .TabsTrigger[data-state=active]::after { content: ""; display: block; position: absolute; width: 80%; left: 0px; right: 0px; bottom: 0px; margin: auto; height: 2px; border-radius: 30px; background: #3080F8; } .video-ui .TabsTrigger[data-state=active] > .TabsTriggerLabel { color: #29292F !important; } .video-ui .TabsTriggerLabel { text-align: center; } .video-ui .TabsContent { background: #F6F7FB; border-bottom-left-radius: 30px; border-bottom-right-radius: 30px; } .video-ui span { display: block; } .video-ui .TabsSort { margin-right: 12px; border-radius: 30px; padding-left: 8px; padding-right: 8px; padding-top: 8px; padding-bottom: 8px; margin-bottom: 5px; } .video-ui .TabsSort:hover { cursor: pointer; background: #F6F7FB; } .video-ui .TabsSortLabel { display: flex; flex-direction: row; justify-content: right; align-items: center; color: #6E7684; } .video-ui .TabsSortLabel img { margin-left: 8px; } .video-ui .TabsSort:focus-visible { box-shadow: 0px 0px 0px 2px rgba(48, 128, 248, 0.5); outline: none !important; } /* reset */ button { all: unset; } .SwitchRow { width: 100%; display: flex; align-items: center; justify-content: space-between; height: 40px; } .SwitchRoot { width: 34px; height: 22px; background-color: #E8E8E8; border-radius: 9999px; position: relative; -webkit-tap-highlight-color: rgba(0, 0, 0, 0); } .SwitchRoot:focus { box-shadow: 0px 0px 0px 2px rgba(48, 128, 248, 0.5); } .SwitchRoot[data-state=checked] { background-color: #3080F8; } .SwitchRoot:hover { cursor: pointer; } .SwitchThumb { display: block; width: 14px; height: 14px; background-color: white; border-radius: 9999px; box-shadow: 0px 1px 10px rgba(0, 0, 0, 0.1); transition: transform 100ms; transform: translateX(2px); will-change: transform; } .SwitchThumb[data-state=checked] { transform: translateX(18px); } .Label { color: #6E7684; display: flex; } .ExperimentalLabel { color: #FFF; font-size: 12px; background-color: #3080F8; border-radius: 15px; padding: 2px 8px; display: inline-block; margin-left: 8px; } .video-item-root { width: calc(100% - 2 * 8px); border-radius: 15px; padding: 8px; display: block; } .video-item-root .video-item { width: 100%; display: flex; flex-direction: row; align-items: center; justify-content: space-between; } .video-item-root .video-item-left { display: flex; flex-grow: 1; flex-direction: row; align-items: center; min-width: 0; justify-content: left; } .video-item-root .video-item-thumbnail { min-width: 48px; width: 48px; height: 38px; background: grey; border-radius: 5px; margin-right: 12px; } .video-item-root .video-item-info { display: block; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; padding-right: 4px; } .video-item-root .video-item-info-title { color: #29292F; white-space: nowrap; text-overflow: ellipsis; overflow: hidden; } .video-item-root .video-item-info-date { margin-top: 4px; color: #6E7684; font-size: 12px; white-space: nowrap; text-overflow: ellipsis; overflow: hidden; } .video-item-root:hover { cursor: pointer; background: #E9EAEE; } .video-item-root:focus-visible { box-shadow: 0px 0px 0px 2px rgba(48, 128, 248, 0.5); outline: none !important; } /* Actions */ .video-item-right { display: flex; align-items: center; justify-content: right; gap: 8px; min-width: -moz-max-content; min-width: max-content; opacity: 0; } .video-item-right .copy-link { background: #FFF; height: 32px; padding-left: 8px; padding-right: 8px; border-radius: 10px; display: flex; flex-direction: row; justify-content: center; align-items: center; } .video-item-right .copy-link img { margin-right: 4px; } .video-item-right .copy-link:focus-visible { box-shadow: 0px 0px 0px 2px rgba(48, 128, 248, 0.5); outline: none !important; } .video-item-right .more-actions { height: 32px; width: 32px; background: #FFF; text-align: center; border-radius: 10px; display: flex; align-items: center; justify-content: center; } .video-item-right .more-actions:focus-visible { box-shadow: 0px 0px 0px 2px rgba(48, 128, 248, 0.5); outline: none !important; } .video-item-root:hover .video-item-right, .video-item-root:focus-visible .video-item-right { opacity: 1; } .video-item-right:focus-within { opacity: 1; } .main-button { width: 100%; height: 45px; border-radius: 30px; display: flex; flex-direction: row; justify-content: center; align-items: center; position: relative; box-sizing: border-box; } .main-button .main-button-label { color: #FFF; text-align: center; vertical-align: middle; align-items: center; } .main-button .main-button-shortcut { position: absolute; font-size: 12px; right: 16px; color: #FFF; opacity: 0.7; } .main-button:hover { cursor: pointer; } .main-button:disabled { cursor: not-allowed; opacity: 0.5; } .main-button:focus { box-shadow: 0px 0px 0px 2px rgba(48, 128, 248, 0.5) !important; } @property --x { syntax: ""; inherits: false; initial-value: 35.44%; } @property --y { syntax: ""; inherits: false; initial-value: 0%; } .recording-button { --x: 35.44%; --y: 0%; margin-top: 8px; filter: drop-shadow(0px 4px 20px rgba(86, 123, 218, 0.5)); background: radial-gradient(127.41% 127.78% at var(--x) var(--y), #2BAEF8 23.13%, #3582F6 46.35%, #486DEF 74.48%, #7B9AEA 100%); transition: --x 0.2s ease-in-out, --y 0.2s ease-in-out; animation: 0; animation: gradient-animation 15s linear infinite; animation-play-state: paused; position: relative; z-index: 2; } .recording-button:hover { animation-play-state: running; } .recording-button:before { content: ""; position: absolute; display: block; top: 0px; left: 0px; width: 100%; height: 100%; box-sizing: border-box; border-radius: 30px; transition: all 0.25s ease-in-out; } .recording-button:hover:before { box-shadow: 0px 0px 0px 4px rgba(52, 138, 247, 0.25); } @keyframes pulse-animation { 0% { box-shadow: 0px 0px 0px 2px rgba(52, 138, 247, 0.25); } 25% { box-shadow: 0px 0px 0px 6px rgba(52, 138, 247, 0.25); } 50% { box-shadow: 0px 0px 0px 2px rgba(52, 138, 247, 0.25); } 100% { box-shadow: 0px 0px 0px 2px rgba(52, 138, 247, 0.25); } } @keyframes gradient-animation { 0% { --x: 35.44%; --y: 0%; } 25% { --x: 100%; --y: 30%; } 50% { --x: 70%; --y: 100%; } 75% { --x: 30%; --y: 90%; } 100% { --x: 35.44%; --y: 0%; } } .dashboard-button { background: #29292F; box-shadow: 0px 0px 0px 0px rgba(41, 41, 47, 0.25); transition: all 0.25s ease-in-out; } .dashboard-button:hover { box-shadow: 0px 0px 0px 4px rgba(41, 41, 47, 0.25); } .alarm-time-button { display: flex; justify-content: center; align-items: center; border-radius: 15px; padding: 4px 8px; position: absolute; color: #FFF; opacity: 0.7; font-family: "Satoshi-Medium", sans-serif; font-size: 0.75rem; left: 6px; } .alarm-time-button svg { margin-top: 4px; margin-right: 4px; width: 14px; } .background-effects-toggle-group { display: flex; height: 40px; width: 100%; gap: 8px; margin-bottom: 8px; margin-top: 8px; } .background-effect { display: flex; width: 40px; height: 40px; align-items: center; justify-content: center; border-radius: 30px; position: relative; color: #FFF; } .background-effect span { position: absolute; top: 0px; left: 0px; width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; font-size: 12px; font-weight: 600; z-index: 99999; font-weight: 500; } .background-effect[data-state=on]::after { content: ""; border-radius: 50%; display: block; width: 46px; height: 46px; position: absolute; border: 2px solid #3080F8; box-sizing: border-box; } .background-effect img { width: 100%; height: 100%; border-radius: 50%; position: absolute; top: 0px; left: 0px; } .background-effect:hover:not([data-state=on]) { cursor: pointer; } .background-effect:hover:not([data-state=on])::after { content: ""; border-radius: 50%; display: block; width: 46px; height: 46px; position: absolute; border: 2px solid #3080F8; opacity: 0.5; box-sizing: border-box; } .background-effect:focus-visible { box-shadow: 0px 0px 0px 2px rgba(48, 128, 248, 0.5); } .region-dimensions { width: 100%; display: flex; gap: 10px; margin-top: 10px; margin-bottom: 10px; } .region-input { flex: 1; position: relative; } .region-input input { color: #29292F !important; border-radius: 30px; height: 40px; box-sizing: border-box; position: relative; width: 100%; padding-left: 18px; padding-right: 40px; font-family: "Satoshi-Medium", sans-serif; background-color: #FFF; } .region-input input:focus-visible { outline: none; box-shadow: 0px 0px 0px 2px rgba(48, 128, 248, 0.5); } .region-input span { color: #6E7684; font-family: "Satoshi-Medium", sans-serif; position: absolute; right: 18px; bottom: 12px; -webkit-user-select: none; -moz-user-select: none; user-select: none; } .time-set-parent { width: 100%; display: flex; gap: 10px; margin-top: 10px; margin-bottom: 10px; } .time-set-input { flex: 1; position: relative; } .time-set-input input { border-radius: 30px; height: 40px; box-sizing: border-box; position: relative; width: 100%; padding-left: 18px; padding-right: 40px; font-family: "Satoshi-Medium", sans-serif; background-color: #FFF; -webkit-appearance: textfield; -moz-appearance: textfield; appearance: textfield; } .time-set-input input:focus-visible { outline: none; box-shadow: 0px 0px 0px 2px rgba(48, 128, 248, 0.5); } .time-set-input span { color: #6E7684; font-family: "Satoshi-Medium", sans-serif; position: absolute; right: 18px; bottom: 12px; -webkit-user-select: none; -moz-user-select: none; user-select: none; } .CanvasContainer { width: 100%; height: 100%; position: absolute; pointer-events: all !important; top: 0px !important; left: 0px !important; z-index: 99999999999 !important; } .canvas { width: 100%; height: 100%; position: absolute; top: 0px !important; left: 0px !important; z-index: 99999999999 !important; } .canvas-container { width: 100vw !important; height: 100vh !important; top: 0px !important; left: 0px !important; z-index: 99999999999; position: absolute !important; } .camera-draggable { width: 100%; height: 100%; transform-origin: left top; border-radius: 50%; } .camera-grab { width: 100%; height: 100%; position: absolute; border-radius: 50%; z-index: 99999999 !important; cursor: grab; } .camera-flipped { transform: scaleX(-1); } .camera-toolbar { display: flex; align-items: center; padding-left: 4px; padding-right: 4px; transition: opacity 0.25s cubic-bezier(0.61, 0.11, 0.08, 0.96); min-width: -moz-max-content; min-width: max-content; background-color: rgba(30, 30, 30, 0.8); box-shadow: 0 2px 10px rgba(0, 0, 0, 0.15); height: 28px; position: absolute; left: 10px; top: 10px; border-radius: 30px; -webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px); z-index: 99999999999; opacity: 0; border: 3px solid rgba(255, 255, 255, 0.2); } .camera-draggable:hover .camera-toolbar, .camera-draggable:hover .camera-resize { opacity: 1 !important; } .camera-toolbar:hover, .camera-resize:hover { opacity: 1 !important; } .CameraToolbarSeparator { width: 1px; height: 18px; background-color: rgba(255, 255, 255, 0.3); margin: 0 4px; } .CameraToggleItem, .CameraToolbarButton { display: flex; justify-content: center; align-items: center; color: #000; height: 22px; width: 22px; text-align: center; font-size: 13px; line-height: 1; border-radius: 50%; transition: background-color 0.25s ease-in-out; background-color: rgba(124, 139, 165, 0); } .CameraToggleItem svg, .CameraToolbarButton svg { color: #9797A4; } .CameraToggleItem:hover, .CameraToolbarButton:hover { background-color: rgba(124, 139, 165, 0.2) !important; cursor: pointer; } .CameraToggleItem:disabled, .CameraToolbarButton:disabled { opacity: 0.5; pointer-events: none; } .CameraToggleItem[data-state=on], .CameraToolbarButton[data-state=on] { color: #FFF; } .CameraToggleItem[data-state=on] svg, .CameraToolbarButton[data-state=on] svg { color: #FFF; } .CameraToggleItem:hover, .CameraToggleButton:hover { cursor: pointer; } .CameraToggleItem:focus-visible, .CameraToggleButton:focus-visible { position: relative; box-shadow: 0px 0px 0px 2px rgba(48, 128, 248, 0.5); } .CameraToggleGroup { display: flex; align-items: center; justify-content: center; } .CameraToggleGroup, .CameraToolbarSeparator { display: none; } .camera-resize { position: absolute; bottom: 20px; right: 20px; z-index: 99999999999; height: 28px; width: 28px; border-radius: 50%; background-color: rgba(30, 30, 30, 0.8); box-shadow: 0 2px 10px rgba(0, 0, 0, 0.15); border: 3px solid rgba(255, 255, 255, 0.2); -webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px); align-items: center; box-sizing: border-box; display: flex; justify-content: center; align-items: center; opacity: 0; } .camera-resize svg { color: #9797A4; text-align: center; margin: auto; display: block; } .countdown { width: 100%; height: 100%; position: absolute; top: 0px; left: 0px; z-index: 99999999999; } .countdown-circle { width: 200px; height: 200px; display: flex; justify-content: center; align-items: center; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); z-index: 999; text-align: center; } .countdown-overlay { width: 100%; height: 100%; background: rgba(0, 0, 0, 0.5); position: absolute; top: 0px; left: 0px; z-index: 99; display: flex; justify-content: center; align-items: center; } .countdown-number { position: absolute; width: 20px; height: 60px; z-index: 9999999; left: 0px; right: 0px; top: 0px; bottom: 0px; margin: auto; font-weight: 300; font-family: "Satoshi-Light", sans-serif; font-size: 3rem; color: #FFF; text-align: center; display: block; transition: all 0.6s ease-in-out; } .background { width: 100%; height: 100%; position: absolute; left: 0px; top: 0px; filter: url("#goo"); transform: rotate(0deg); transition: all 3s ease-in-out; } .circle { z-index: 9; position: absolute; transform: scale(0.8); top: 0px; left: 0px; right: 0px; bottom: 0px; margin: auto; border-radius: 50%; background: radial-gradient(118.3% 119.01% at 35.44% 0%, #2BAEF8 23.13%, #3582F6 46.35%, #486DEF 74.48%, #7B9AEA 100%); width: 200px; height: 200px; transition: all 1.5s ease-in-out; } .c { width: 50px; height: 50px; z-index: 999; border-radius: 50%; position: absolute; top: 0px; right: 0px; left: 0px; bottom: 0px; margin: auto; transition: cubic-bezier(0.82, 0.1, 0.24, 0.99) 1.5s; opacity: 1; } .c2 { background: radial-gradient(118.3% 119.01% at 35.44% 0%, #2B96F8 23.13%, #356BF6 64.58%); transform: translate(20px, 20px); } .c3 { background: radial-gradient(118.3% 119.01% at 35.44% 0%, #4884CA 15.3%, #2B89F8 78.83%); transform: translate(-30px, -40px); } .c3:after { content: ""; position: absolute; width: 150px; height: 150px; filter: blur(50px); border-radius: 50%; top: 0px; right: 0px; left: 0px; bottom: 0px; margin: auto; transition: cubic-bezier(0.82, 0.1, 0.24, 0.99) 1.5s; background: #CBE8F7; z-index: -1; } .recording-countdown .c2 { transform: translate(-15px, 15px); } .recording-countdown .c3 { transform: translate(-10px, -5px); } .countdown-info { position: absolute; left: 0px; right: 0px; margin: auto; bottom: 20px; border-radius: 30px; border: 2px solid rgba(255, 255, 255, 0.3); text-align: center; display: block; padding: 10px 20px; font-family: "Satoshi-Medium", sans-serif; color: #FFF; z-index: 99999999999; width: -moz-fit-content; width: fit-content; } /* reset */ button { all: unset; } .AlertDialogOverlay { background-color: rgba(0, 0, 0, 0.5); position: fixed; inset: 0; animation: overlayShow 150ms cubic-bezier(0.16, 1, 0.3, 1); z-index: 99999999999; } .AlertDialogContent { background-color: white; border-radius: 30px; box-shadow: hsla(206, 22%, 7%, 0.35) 0px 10px 38px -10px, hsla(206, 22%, 7%, 0.2) 0px 10px 20px -15px; position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 90vw; max-width: 500px; max-height: 85vh; padding: 35px 25px; animation: contentShow 150ms cubic-bezier(0.16, 1, 0.3, 1); z-index: 99999999999; } .AlertDialogContent:focus { outline: none; } .AlertDialogTitle { margin: 0; color: #29292F; font-size: 14px; font-family: "Satoshi-Medium", sans-serif; font-weight: 700; } .AlertDialogDescription { margin-bottom: 20px; color: #6E7684; font-size: 14px; line-height: 1.5; } .Button { display: inline-flex; align-items: center; justify-content: center; border-radius: 30px; padding: 0 15px; font-size: 15px; line-height: 1; font-weight: 500; height: 35px; } .Button.red { background-color: rgba(247, 56, 90, 0.1); color: rgb(247, 56, 90); } .Button.red:hover { background-color: rgba(247, 56, 90, 0.15); cursor: pointer; } .Button.red:focus { box-shadow: 0px 0px 0px 2px rgba(48, 128, 248, 0.5); } .Button.grey { background: rgba(110, 118, 132, 0.1); color: #6E7684; } .Button.grey:hover { background: rgba(110, 118, 132, 0.15); cursor: pointer; } .Button.grey:focus { box-shadow: 0px 0px 0px 2px rgba(48, 128, 248, 0.5); } @keyframes overlayShow { from { opacity: 0; } to { opacity: 1; } } @keyframes contentShow { from { opacity: 0; transform: translate(-50%, -48%) scale(0.96); } to { opacity: 1; transform: translate(-50%, -50%) scale(1); } } .box-hole { position: absolute; top: 0; left: 0; width: 100%; height: 100%; z-index: 9999999999; } .resize-handle { position: absolute; width: 10px; height: 10px; border-radius: 50%; background-color: white; border: 2px solid rgba(0, 0, 0, 0.5); box-sizing: border-box; } .resize-handle.top-left { bottom: 0; left: 0; top: 0; right: 0; margin: auto; cursor: nwse-resize; } .resize-handle.top { top: 0; left: 50%; transform: translateX(-50%); cursor: ns-resize; } .resize-handle.top-right { bottom: 0; left: 0; top: 0; right: 0; margin: auto; cursor: nesw-resize; } .resize-handle.right { top: 50%; right: 0; transform: translateY(-50%); cursor: ew-resize; } .resize-handle.bottom-right { bottom: 0; left: 0; top: 0; right: 0; margin: auto; cursor: nwse-resize; } .resize-handle.bottom { bottom: 0; left: 50%; transform: translateX(-50%); cursor: ns-resize; } .resize-handle.bottom-left { bottom: 0; left: 0; top: 0; right: 0; margin: auto; cursor: nesw-resize; } .resize-handle.left { top: 50%; left: 0; transform: translateY(-50%); cursor: ew-resize; } .region-recording * { pointer-events: none !important; } html { font-size: 16px; } ================================================ FILE: src/pages/Content/toolbar/Toolbar.jsx ================================================ import React from "react"; import ToolbarWrap from "./layout/ToolbarWrap"; const Toolbar = () => { return (
); }; export default Toolbar; ================================================ FILE: src/pages/Content/toolbar/components/ColorWheel.jsx ================================================ import React, { useRef, useState, useContext } from "react"; import Wheel from "@uiw/react-color-wheel"; import { hsvaToHex } from "@uiw/color-convert"; // Components import TooltipWrap from "./TooltipWrap"; // Context import { contentStateContext } from "../../context/ContentState"; const ColorWheel = (props) => { const [hsva, setHsva] = React.useState({ h: 200, s: 50, v: 100, a: 1 }); const [contentState, setContentState] = useContext(contentStateContext); const wheelRef = useRef(null); const stateRef = useRef(); stateRef.current = props.fullwheel; const handleClick = (e) => { if (!props.fullwheel) { props.setFullWheel(true); } }; return (
{contentState.color.toUpperCase()}
{ setHsva({ ...hsva, ...color.hsva }); setContentState((prevContentState) => ({ ...prevContentState, color: hsvaToHex({ h: hsva.h, s: hsva.s, v: hsva.v, a: hsva.a }), swatch: 5, })); chrome.storage.local.set({ color: hsvaToHex({ h: hsva.h, s: hsva.s, v: hsva.v, a: hsva.a }), swatch: 5, }); }} />
); }; export default ColorWheel; ================================================ FILE: src/pages/Content/toolbar/components/MicToggle.jsx ================================================ import React, { useContext } from "react"; import * as Toggle from "@radix-ui/react-toggle"; // Components import TooltipWrap from "./TooltipWrap"; import { MicIcon } from "./SVG"; // Context import { contentStateContext } from "../../context/ContentState"; const MicToggle = (props) => { const [contentState, setContentState] = useContext(contentStateContext); return (
{ setContentState((prevContentState) => ({ ...prevContentState, micActive: pressed, })); chrome.storage.local.set({ micActive: pressed, }); chrome.runtime.sendMessage({ type: "set-mic-active-tab", active: pressed, defaultAudioInput: contentState.defaultAudioInput, }); // Show toast contentState.openToast( pressed ? chrome.i18n.getMessage("micOnToast") : chrome.i18n.getMessage("micOffToast"), () => {} ); }} >
); }; export default MicToggle; ================================================ FILE: src/pages/Content/toolbar/components/RadialMenu.jsx ================================================ import React, { useEffect, useState } from "react"; import * as Popover from "@radix-ui/react-popover"; // Icons import { EyeDropperIcon } from "./SVG"; // Context import { contentStateContext } from "../../context/ContentState"; import * as ToggleGroup from "@radix-ui/react-toggle-group"; // Components import TooltipWrap from "./TooltipWrap"; import ColorWheel from "./ColorWheel"; import StrokeWeight from "./StrokeWeight"; const RadialMenu = (props) => { const [contentState, setContentState] = React.useContext(contentStateContext); const ref = React.useRef(null); const buttonRef = React.useRef(null); const radialMenuRef = React.useRef(null); const [fullwheel, setFullWheel] = useState(false); const [open, setOpen] = useState(false); const [eyeDropperActive, setEyeDropperActive] = useState(false); const [renderColorWheel, setRenderColorWheel] = useState(false); // Colors in menu const [colors, setColors] = useState([ { color: "#FED252", label: "Yellow" }, { color: "#4597F7", label: "Blue" }, { color: "#F24822", label: "Red" }, { color: "#FFFFFF", label: "White" }, { color: "#201F1D", label: "Black" }, ]); useEffect(() => { if (!open) { setFullWheel(false); setRenderColorWheel(false); } else { const timeout = setTimeout(() => { setRenderColorWheel(true); }, 50); return () => clearTimeout(timeout); } }, [open]); const selectColor = () => { const eyeDropper = new window.EyeDropper(); setEyeDropperActive(true); eyeDropper .open() .then((color) => { setContentState((prevContentState) => ({ ...prevContentState, color: color.sRGBHex, swatch: 5, })); setEyeDropperActive(false); }) .catch((err) => { setEyeDropperActive(false); }); }; useEffect(() => { if (!buttonRef.current) return; if (!radialMenuRef.current) return; const left = buttonRef.current.getBoundingClientRect().left + buttonRef.current.getBoundingClientRect().width / 2; const top = buttonRef.current.getBoundingClientRect().top + buttonRef.current.getBoundingClientRect().height / 2; radialMenuRef.current.style.left = `${left}px`; radialMenuRef.current.style.top = `${top}px`; }, [buttonRef, radialMenuRef]); return ( setOpen(!open)}>
{ selectColor(); }} >
{ setContentState((prevContentState) => ({ ...prevContentState, strokeWidth: value, })); }} > {colors.map((color, index) => { return (
{ setContentState((prevContentState) => ({ ...prevContentState, color: color.color, swatch: index, })); chrome.storage.local.set({ color: color.color, swatch: index, }); }} className={ contentState.swatch === index ? "radial-menu-item-child color-active" : "radial-menu-item-child" } > {index}
); })} {renderColorWheel && ( )}
); }; export default RadialMenu; ================================================ FILE: src/pages/Content/toolbar/components/SVG.jsx ================================================ import React from "react"; import { ReactSVG } from "react-svg"; const URL = "chrome-extension://" + chrome.i18n.getMessage("@@extension_id") + "/assets/"; const GrabIcon = (props) => { return ( ); }; /* */ // Convert all to ReactSVG const StopIcon = (props) => { return ( ); }; const DrawIcon = (props) => { return ( ); }; const PauseIcon = (props) => { return ( ); }; const ResumeIcon = (props) => { return ( ); }; const CursorIcon = (props) => { return ( ); }; const CommentIcon = (props) => { return ( ); }; const MicIcon = (props) => { return ( ); }; const MoreIcon = (props) => { return ( ); }; const RestartIcon = (props) => { return ( ); }; const DiscardIcon = (props) => { return ( ); }; const EyeDropperIcon = (props) => { return ( ); }; const Stroke1Icon = (props) => { return ( ); }; const Stroke2Icon = (props) => { return ( ); }; const Stroke3Icon = (props) => { return ( ); }; const TargetCursorIcon = (props) => { return ( ); }; const HighlightCursorIcon = (props) => { return ( ); }; const HideCursorIcon = (props) => { return ( ); }; const TextIcon = (props) => { return ( ); }; const ArrowIcon = (props) => { return ( ); }; const EraserIcon = (props) => { return ( ); }; const PenIcon = (props) => { return ( ); }; const ShapeIcon = (props) => { return ( ); }; const SelectIcon = (props) => { return ( ); }; const UndoIcon = (props) => { return ( ); }; const RedoIcon = (props) => { return ( ); }; const ImageIcon = (props) => { return ( ); }; const TransformIcon = (props) => { return ( ); }; const HighlighterIcon = (props) => { return ( ); }; const RectangleIcon = (props) => { return ( ); }; const CircleIcon = (props) => { return ( ); }; const TriangleIcon = (props) => { return ( ); }; const RectangleFilledIcon = (props) => { return ( ); }; const CircleFilledIcon = (props) => { return ( ); }; const TriangleFilledIcon = (props) => { return ( ); }; const TrashIcon = (props) => { return ( ); }; const VideoOffIcon = (props) => { return ( ); }; const CameraCloseIcon = (props) => { return ( ); }; const CameraMoreIcon = (props) => { return ( ); }; const CameraResizeIcon = (props) => { return ( ); }; const CameraIcon = (props) => { return ( ); }; const BlurIcon = (props) => { return ( ); }; const AlertIcon = (props) => { return ( ); }; const TimeIcon = (props) => { return ( ); }; const SpotlightCursorIcon = (props) => { return ( ); }; const Pip = (props) => { return ( ); }; const CloseIconPopup = (props) => { return ( ); }; const GrabIconPopup = (props) => { return ( ); }; const MoreIconPopup = (props) => { return ( ); }; const OnboardingArrow = (props) => { return ( ); }; const NoInternet = (props) => { return ( ); }; const CloseButtonToolbar = (props) => { return ( ); }; const HelpIconPopup = (props) => { return ( ); }; const AudioIcon = (props) => { return ( ); }; const NotSupportedIcon = (props) => { return ( ); }; export { GrabIcon, StopIcon, DrawIcon, PauseIcon, ResumeIcon, CursorIcon, CommentIcon, MicIcon, MoreIcon, RestartIcon, DiscardIcon, EyeDropperIcon, Stroke1Icon, Stroke2Icon, Stroke3Icon, TargetCursorIcon, HighlightCursorIcon, HideCursorIcon, TextIcon, ArrowIcon, EraserIcon, PenIcon, ShapeIcon, SelectIcon, UndoIcon, RedoIcon, ImageIcon, TransformIcon, HighlighterIcon, RectangleIcon, CircleIcon, TriangleIcon, RectangleFilledIcon, CircleFilledIcon, TriangleFilledIcon, TrashIcon, VideoOffIcon, CameraCloseIcon, CameraMoreIcon, CameraResizeIcon, CameraIcon, BlurIcon, AlertIcon, TimeIcon, SpotlightCursorIcon, Pip, CloseIconPopup, GrabIconPopup, OnboardingArrow, NoInternet, CloseButtonToolbar, HelpIconPopup, MoreIconPopup, AudioIcon, NotSupportedIcon, }; ================================================ FILE: src/pages/Content/toolbar/components/StrokeWeight.jsx ================================================ import React from "react"; // Icons import { Stroke1Icon, Stroke2Icon, Stroke3Icon } from "./SVG"; // Context import { contentStateContext } from "../../context/ContentState"; // Components import TooltipWrap from "./TooltipWrap"; import * as ToggleGroup from "@radix-ui/react-toggle-group"; const StrokeWeight = (props) => { const [contentState, setContentState] = React.useContext(contentStateContext); return (
); }; export default StrokeWeight; ================================================ FILE: src/pages/Content/toolbar/components/Toast.jsx ================================================ import React, { useState, useEffect, useContext, useCallback, useRef, } from "react"; import * as ToastEl from "@radix-ui/react-toast"; // Context import { contentStateContext } from "../../context/ContentState"; const Toast = () => { const [contentState, setContentState] = useContext(contentStateContext); const [open, setOpen] = useState(false); const [title, setTitle] = useState(""); const [trigger, setTrigger] = useState(() => {}); const triggerRef = useRef(trigger); const openRef = useRef(open); const contentStateRef = useRef(contentState); const [toastDuration, setToastDuration] = useState(2000); useEffect(() => { contentStateRef.current = contentState; }, [contentState]); const openToast = useCallback((title, action, durationMs = 2000) => { if (contentStateRef.current.hideUI) return; setTitle(title); setOpen(true); setTrigger(() => action); setToastDuration(durationMs); }); useEffect(() => { setContentState((prevContentState) => ({ ...prevContentState, openToast: openToast, })); return () => { setContentState((prevContentState) => ({ ...prevContentState, openToast: null, })); }; }, []); useEffect(() => { openRef.current = open; }, [open]); useEffect(() => { triggerRef.current = trigger; return () => { triggerRef.current = () => {}; }; }, [trigger]); return ( { e.stopPropagation(); e.preventDefault(); triggerRef.current(); setOpen(false); }} > {title} { trigger(); }} > ); }; export default Toast; ================================================ FILE: src/pages/Content/toolbar/components/ToolTrigger.jsx ================================================ import React from "react"; import * as Toolbar from "@radix-ui/react-toolbar"; // Components import TooltipWrap from "./TooltipWrap"; const ToolTrigger = (props) => { const grab = props.grab ? " grab" : ""; const resume = props.resume ? " resume" : ""; return ( {props.type === "button" ? ( {props.children} ) : props.type === "mode" ? (
{props.children}
) : (
{props.children}
)}
); }; export default ToolTrigger; ================================================ FILE: src/pages/Content/toolbar/components/TooltipWrap.jsx ================================================ import React, { useEffect, useContext, useState } from "react"; import * as Tooltip from "@radix-ui/react-tooltip"; // Context import { contentStateContext } from "../../context/ContentState"; const TooltipWrap = (props) => { const [contentState, setContentState] = useContext(contentStateContext); const classname = props.name ? props.name : ""; const [override, setOverride] = useState(""); const content = props.shortcut && props.content ? `${props.content} (${props.shortcut})` : props.content; useEffect(() => { // Check if hideUI is set if (contentState.hideUI) { setOverride("override"); } else { setOverride(""); } }, [contentState.hideUI]); return (
{props.content == "" ? (
{props.children}
) : ( {props.children} {content} )}
); }; export default TooltipWrap; ================================================ FILE: src/pages/Content/toolbar/layout/BlurToolbar.jsx ================================================ import React from "react"; import * as Toolbar from "@radix-ui/react-toolbar"; // Components import ToolTrigger from "../components/ToolTrigger"; // Icons import { TransformIcon, TrashIcon } from "../components/SVG"; const BlurToolbar = (props) => { return (
{ // Remove class screenity-blur from all elements const blurredElements = document.querySelectorAll(".screenity-blur"); blurredElements.forEach((element) => { element.classList.remove("screenity-blur"); }); }} >
); }; export default BlurToolbar; ================================================ FILE: src/pages/Content/toolbar/layout/CursorToolbar.jsx ================================================ import React, { useState, useEffect, useContext, useRef } from "react"; import * as Toolbar from "@radix-ui/react-toolbar"; import TooltipWrap from "../components/TooltipWrap"; // Context import { contentStateContext } from "../../context/ContentState"; // Icons import { CursorIcon, TargetCursorIcon, HighlightCursorIcon, SpotlightCursorIcon, HideCursorIcon, } from "../components/SVG"; const CursorToolbar = (props) => { const [contentState, setContentState] = useContext(contentStateContext); const lastClickedEffectRef = useRef(contentState.cursorMode || "none"); useEffect(() => { if (contentState.cursorMode) { lastClickedEffectRef.current = contentState.cursorMode; } }, [contentState.cursorMode]); const deriveCursorMode = (effects, fallback) => { if (effects.length === 0) return "none"; if (effects.length === 1) return effects[0]; if (fallback && effects.includes(fallback)) return fallback; return effects[0] || "none"; }; const applyCursorSelection = (effect, shiftKey) => { if (effect === "none") { lastClickedEffectRef.current = "none"; setContentState((prev) => ({ ...prev, cursorEffects: [], cursorMode: "none", })); if (!shiftKey) { props.setMode(false); } chrome.storage.local.set({ cursorEffects: [], cursorMode: "none", }); return; } const currentEffects = Array.isArray(contentState.cursorEffects) ? contentState.cursorEffects : []; let nextEffects = []; if (shiftKey) { if (currentEffects.includes(effect)) { nextEffects = currentEffects.filter((item) => item !== effect); } else { nextEffects = [...currentEffects, effect]; } } else { nextEffects = [effect]; } lastClickedEffectRef.current = effect; const nextMode = deriveCursorMode( nextEffects, lastClickedEffectRef.current ); setContentState((prev) => ({ ...prev, cursorEffects: nextEffects, cursorMode: nextMode, })); if (!shiftKey) { props.setMode(false); } chrome.storage.local.set({ cursorEffects: nextEffects, cursorMode: nextMode, }); }; const handleClick = (effect) => (event) => { applyCursorSelection(effect, Boolean(event.shiftKey)); }; const cursorEffects = Array.isArray(contentState.cursorEffects) ? contentState.cursorEffects : []; const isDefault = cursorEffects.length === 0; const toggleValues = isDefault ? ["none"] : cursorEffects; const isEffectActive = (effect) => effect === "none" ? isDefault : cursorEffects.includes(effect); return ( {}} >
); }; export default CursorToolbar; ================================================ FILE: src/pages/Content/toolbar/layout/DrawingToolbar.jsx ================================================ import React, { useRef, useEffect, useCallback, useContext, useState, } from "react"; import * as Toolbar from "@radix-ui/react-toolbar"; // Components import ToolTrigger from "../components/ToolTrigger"; import RadialMenu from "../components/RadialMenu"; import ShapeToolbar from "./ShapeToolbar"; // Canvas utils import { undoCanvas, redoCanvas, saveCanvas, } from "../../canvas/modules/History"; // Icons import { DrawIcon, EraserIcon, ArrowIcon, ImageIcon, UndoIcon, RedoIcon, TransformIcon, HighlighterIcon, TextIcon, RectangleIcon, TriangleIcon, CircleIcon, RectangleFilledIcon, CircleFilledIcon, TriangleFilledIcon, TrashIcon, } from "../components/SVG"; // Rewrite imports above with the chrome-extension URL inline import TooltipWrap from "../components/TooltipWrap"; import ImageTool from "../../canvas/modules/ImageTool"; // Context import { contentStateContext } from "../../context/ContentState"; const DrawingToolbar = (props) => { const [contentState, setContentState] = useContext(contentStateContext); const [tool, setTool] = useState(""); const contentStateRef = useRef(contentState); useEffect(() => { contentStateRef.current = contentState; }, [contentState]); const imageFileInput = useRef(null); useEffect(() => { setTool(contentState.tool); }, [contentState.tool]); const handleImageChange = useCallback( (e) => { const reader = new FileReader(); reader.onload = (e) => { setContentState((prevContentState) => ({ ...prevContentState, isAddingImage: true, })); // De-select all objects contentState.canvas.discardActiveObject(); contentState.canvas.requestRenderAll(); // Make all objects unselectable contentState.canvas.forEachObject((obj) => { obj.selectable = false; }); const imgTool = ImageTool( contentState.canvas, e.target.result, contentState, setContentState, saveCanvas, contentStateRef.current ); imageFileInput.current.value = ""; return () => { imgTool.removeEventListeners(); }; }; reader.readAsDataURL(e.target.files[0]); }, [contentState, setContentState, saveCanvas] ); return ( { if (value) setContentState((prevContentState) => ({ ...prevContentState, tool: value, })); }} > {contentState.shape === "rectangle" && contentState.shapeFill ? ( ) : contentState.shape === "circle" && contentState.shapeFill ? ( ) : contentState.shape === "triangle" && contentState.shapeFill ? ( ) : contentState.shape === "rectangle" && !contentState.shapeFill ? ( ) : contentState.shape === "circle" && !contentState.shapeFill ? ( ) : contentState.shape === "triangle" && !contentState.shapeFill ? ( ) : null} imageFileInput.current.click()} > undoCanvas(contentState, setContentState)} > redoCanvas(contentState, setContentState)} > { if (!contentState.canvas) return; contentState.canvas.clear(); contentState.canvas.renderAll(); contentState.canvas.requestRenderAll(); saveCanvas(contentState, setContentState); }} > ); }; export default DrawingToolbar; ================================================ FILE: src/pages/Content/toolbar/layout/ShapeToolbar.jsx ================================================ import React, { useEffect, useState, useContext, useRef } from "react"; import * as Toolbar from "@radix-ui/react-toolbar"; import ToolTrigger from "../components/ToolTrigger"; // Icons import { RectangleIcon, CircleIcon, TriangleIcon, RectangleFilledIcon, CircleFilledIcon, TriangleFilledIcon, } from "../components/SVG"; // Context import { contentStateContext } from "../../context/ContentState"; const ShapeToolbar = (props) => { const [contentState, setContentState] = useContext(contentStateContext); return (
{ if (value) setContentState((prevContentState) => ({ ...prevContentState, shape: value, })); chrome.storage.local.set({ shape: value }); }} >
{contentState.shapeFill ? ( ) : ( )}
{contentState.shapeFill ? : }
{contentState.shapeFill ? : }
{ setContentState((prevContentState) => ({ ...prevContentState, shapeFill: !contentState.shapeFill, })); chrome.storage.local.set({ shapeFill: !contentState.shapeFill }); }} > {contentState.shape === "rectangle" && contentState.shapeFill ? ( ) : contentState.shape === "circle" && contentState.shapeFill ? ( ) : contentState.shape === "triangle" && contentState.shapeFill ? ( ) : contentState.shape === "rectangle" && !contentState.shapeFill ? ( ) : contentState.shape === "circle" && !contentState.shapeFill ? ( ) : contentState.shape === "triangle" && !contentState.shapeFill ? ( ) : null}
); }; export default ShapeToolbar; ================================================ FILE: src/pages/Content/toolbar/layout/ToolbarWrap.jsx ================================================ import React, { useLayoutEffect, useEffect, useContext, useState, useRef, } from "react"; import * as Toolbar from "@radix-ui/react-toolbar"; import { Rnd } from "react-rnd"; // Layout import DrawingToolbar from "./DrawingToolbar"; import CursorToolbar from "./CursorToolbar"; import BlurToolbar from "./BlurToolbar"; // Components import ToolTrigger from "../components/ToolTrigger"; import Toast from "../components/Toast"; import { CloseIconPopup } from "../components/SVG"; // Context import { contentStateContext } from "../../context/ContentState"; // Icons import { GrabIcon, StopIcon, DrawIcon, PauseIcon, ResumeIcon, CursorIcon, TargetCursorIcon, HighlightCursorIcon, SpotlightCursorIcon, RestartIcon, DiscardIcon, CameraIcon, BlurIcon, OnboardingArrow, CloseButtonToolbar, } from "../components/SVG"; import MicToggle from "../components/MicToggle"; const ToolbarWrap = () => { const [contentState, setContentState, t, setT] = useContext(contentStateContext); const [mode, setMode] = React.useState(""); const modeRef = React.useRef(mode); const [hovering, setHovering] = React.useState(false); const DragRef = React.useRef(null); const ToolbarRef = React.useRef(null); const [side, setSide] = React.useState("ToolbarTop"); const [elastic, setElastic] = React.useState(""); const [shake, setShake] = React.useState(""); const [dragging, setDragging] = React.useState(""); const [timer, setTimer] = React.useState(0); const [timestamp, setTimestamp] = React.useState("00:00"); const [transparent, setTransparent] = React.useState(false); const [forceTransparent, setForceTransparent] = React.useState(""); const [visuallyHidden, setVisuallyHidden] = useState(false); const timeRef = React.useRef(""); useEffect(() => { modeRef.current = mode; }, [mode]); useEffect(() => { setContentState((prev) => ({ ...prev, setToolbarMode: setMode, toolbarMode: mode, })); }, [mode, setContentState]); useEffect(() => { if (contentState.toolbarHover && contentState.hideUI) { setTransparent("ToolbarTransparent"); } else { setTransparent(false); setForceTransparent(""); } }, [contentState.toolbarHover, contentState.hideUI]); // If mouse is down and toolbarHover is true, set forceTransparent useEffect(() => { if (!contentState.toolbarHover) return; if (!contentState.shadowRef) return; if (!contentState.hideUI) return; const handleMouseDown = (e) => { if (contentState.toolbarHover && contentState.hideUI) { // check if mouse is over toolbar if (ToolbarRef.current && ToolbarRef.current.contains(e.target)) return; if ( contentState.shadowRef && (contentState.shadowRef.contains(e.target) || contentState.shadowRef === e.target || contentState.shadowRef === e.target.parentNode) ) return; setForceTransparent("ForceTransparent"); } }; const handleMouseUp = (e) => { setForceTransparent(""); }; document.addEventListener("mousedown", handleMouseDown); document.addEventListener("mouseup", handleMouseUp); return () => { document.removeEventListener("mousedown", handleMouseDown); document.removeEventListener("mouseup", handleMouseUp); }; }, [contentState.toolbarHover, contentState.shadowRef, contentState.hideUI]); useEffect(() => { if (!isNaN(t)) { setTimer(t); const clampedT = Math.max(0, t); // prevent negative values const hours = Math.floor(clampedT / 3600); const minutes = Math.floor((clampedT % 3600) / 60); const seconds = clampedT % 60; // Determine the timestamp format based on the total duration (t) let newTimestamp = hours > 0 ? `${hours.toString().padStart(2, "0")}:${minutes .toString() .padStart(2, "0")}:${seconds.toString().padStart(2, "0")}` : `${minutes.toString().padStart(2, "0")}:${seconds .toString() .padStart(2, "0")}`; // Adjust the width of the time display based on the duration if (hours > 0) { // Adjust for HH:MM:SS format when hours are present timeRef.current.style.width = "58px"; // You might need to adjust this value based on your actual UI } else { // Adjust for MM:SS format when there are no hours timeRef.current.style.width = "42px"; // Adjust this value as needed } setTimestamp(newTimestamp); } }, [t]); useLayoutEffect(() => { function setToolbarPosition(e) { let xpos = DragRef.current.getDraggablePosition().x; let ypos = DragRef.current.getDraggablePosition().y; // Width and height of toolbar const width = ToolbarRef.current.getBoundingClientRect().width; const height = ToolbarRef.current.getBoundingClientRect().height; // Keep toolbar positioned relative to the bottom and right of the screen, proportionally if (xpos + width + 30 > window.innerWidth) { xpos = window.innerWidth - width - 30; } if (ypos + height - 60 > window.innerHeight) { ypos = window.innerHeight - height + 60; } DragRef.current.updatePosition({ x: xpos, y: ypos }); } window.addEventListener("resize", setToolbarPosition); setToolbarPosition(); return () => window.removeEventListener("resize", setToolbarPosition); }, []); const handleChange = (value) => { setMode(value); }; const handleDragStart = (e, d) => { setDragging("ToolbarDragging"); }; const handleDrag = (e, d) => { // Width and height const width = ToolbarRef.current.getBoundingClientRect().width; const height = ToolbarRef.current.getBoundingClientRect().height; if (d.y < 130) { setSide("ToolbarBottom"); } else { setSide("ToolbarTop"); } if ( d.x < -25 || d.x + width > window.innerWidth || d.y < 60 || d.y + height - 80 > window.innerHeight ) { setShake("ToolbarShake"); } else { setShake(""); } }; const handleDrop = (e, d) => { setShake(""); setDragging(""); let xpos = d.x; let ypos = d.y; // Width and height const width = ToolbarRef.current.getBoundingClientRect().width; const height = ToolbarRef.current.getBoundingClientRect().height; // Check if toolbar is off screen if (d.x < -10) { setElastic("ToolbarElastic"); xpos = -10; } else if (d.x + width + 30 > window.innerWidth) { setElastic("ToolbarElastic"); xpos = window.innerWidth - width - 30; } if (d.y < 130) { setSide("ToolbarBottom"); } else { setSide("ToolbarTop"); } if (d.y < 80) { setElastic("ToolbarElastic"); ypos = 80; } else if (d.y + height - 60 > window.innerHeight) { setElastic("ToolbarElastic"); ypos = window.innerHeight - height + 60; } DragRef.current.updatePosition({ x: xpos, y: ypos }); setTimeout(() => { setElastic(""); }, 250); setContentState((prevContentState) => ({ ...prevContentState, toolbarPosition: { ...prevContentState.toolbarPosition, offsetX: xpos, offsetY: ypos, left: xpos < window.innerWidth / 2 ? true : false, right: xpos < window.innerWidth / 2 ? false : true, top: ypos < window.innerHeight / 2 ? true : false, bottom: ypos < window.innerHeight / 2 ? false : true, }, })); // Is it on the left or right, also top or bottom let left = xpos < window.innerWidth / 2 ? true : false; let right = xpos < window.innerWidth / 2 ? false : true; let top = ypos < window.innerHeight / 2 ? true : false; let bottom = ypos < window.innerHeight / 2 ? false : true; let offsetX = xpos; let offsetY = ypos; if (right) { offsetX = window.innerWidth - xpos; } if (bottom) { offsetY = window.innerHeight - ypos; } setContentState((prevContentState) => ({ ...prevContentState, toolbarPosition: { ...prevContentState.toolbarPosition, offsetX: offsetX, offsetY: offsetY, left: left, right: right, top: top, bottom: bottom, }, })); chrome.storage.local.set({ toolbarPosition: { offsetX: offsetX, offsetY: offsetY, left: left, right: right, top: top, bottom: bottom, }, }); }; useEffect(() => { let x = contentState.toolbarPosition.offsetX; let y = contentState.toolbarPosition.offsetY; if (contentState.toolbarPosition.bottom) { y = window.innerHeight - contentState.toolbarPosition.offsetY; } if (contentState.toolbarPosition.right) { x = window.innerWidth - contentState.toolbarPosition.offsetX; } DragRef.current.updatePosition({ x: x, y: y }); handleDrop(null, { x: x, y: y }); }, []); useEffect(() => { if (!contentState.openToast) return; if (contentState.drawingMode) { contentState.openToast(chrome.i18n.getMessage("drawingModeToast"), () => { setMode(""); }); } if (contentState.blurMode) { contentState.openToast(chrome.i18n.getMessage("blurModeToast"), () => { setMode(""); }); } }, [contentState.drawingMode, contentState.blurMode, contentState.openToast]); // useEffect(() => { // let nextMode = modeRef.current; // if (contentState.drawingMode) { // nextMode = "draw"; // } else if (contentState.blurMode) { // nextMode = "blur"; // } else if (modeRef.current === "draw" || modeRef.current === "blur") { // nextMode = ""; // } // if (nextMode !== modeRef.current) { // setMode(nextMode); // } // }, [contentState.drawingMode, contentState.blurMode]); useEffect(() => { // one-time init if (contentState.drawingMode) setMode("draw"); else if (contentState.blurMode) setMode("blur"); else setMode(""); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); useEffect(() => { if (mode === "draw") { setContentState((prevContentState) => ({ ...prevContentState, drawingMode: true, showOnboardingArrow: false, })); } else { setContentState((prevContentState) => ({ ...prevContentState, drawingMode: false, })); } if (mode === "blur") { setContentState((prevContentState) => ({ ...prevContentState, blurMode: true, drawingMode: false, })); } else { setContentState((prevContentState) => ({ ...prevContentState, blurMode: false, })); } }, [mode]); const enableCamera = () => { setContentState((prevContentState) => ({ ...prevContentState, cameraActive: true, })); chrome.storage.local.set({ cameraActive: true, }); setContentState((prevContentState) => ({ ...prevContentState, pipEnded: true, })); }; return (
{ setHovering(true); }} onMouseLeave={() => { setHovering(false); }} > {!contentState.recording && (
{ // Show the toast first if (contentState.openToast) { contentState.openToast( chrome.i18n.getMessage("reopenToolbarToast"), () => {}, ); } // Visually hide the toolbar setVisuallyHidden(true); setContentState((prev) => ({ ...prev, drawingMode: false, blurMode: false, })); // After toast finishes (~3s), apply real hiding logic setTimeout(() => { setContentState((prev) => ({ ...prev, hideToolbar: true, drawingMode: false, blurMode: false, hideUIAlerts: false, toolbarHover: false, hideUI: true, })); chrome.storage.local.set({ hideToolbar: true, hideUIAlerts: false, toolbarHover: false, hideUI: true, }); }, 3000); // match your toast duration }} >
)}
{ contentState.stopRecording(); }} >
{timestamp}
{ contentState.tryRestartRecording(); }} > {!contentState.paused && ( { contentState.pauseRecording(); }} > )} {contentState.recording && contentState.paused && ( { contentState.resumeRecording(); }} > )} { if (contentState.tryDismissRecording !== undefined) { contentState.tryDismissRecording(); } }} >
{contentState.showOnboardingArrow && (
{chrome.i18n.getMessage("clickHereDrawOnboarding")}
)} {mode === "draw" && } {mode !== "draw" && }
{mode === "blur" && } {mode !== "blur" && }
{contentState.cursorMode === "target" && } {contentState.cursorMode === "highlight" && ( )} {contentState.cursorMode === "spotlight" && ( )} {contentState.cursorMode === "none" && }
{(!contentState.cameraActive || contentState.defaultVideoInput === "none") && (!contentState.isSubscribed || !contentState.recording) && contentState.recordingType != "camera-only" && ( )}
); }; export default ToolbarWrap; ================================================ FILE: src/pages/Content/toolbar/styles/_Page.scss ================================================ @use '../../styles/_variables' as *; @use './layout/Toolbar'; @use './layout/DrawingToolbar'; @use './layout/ShapeToolbar'; @use './components/Tooltip'; @use './components/RadialMenu'; @use './components/ColorWheel'; @use './components/StrokeWidth'; @use './components/Toast'; .toolbar-page { width: 100%; height: 100%; pointer-events: none!important; } ================================================ FILE: src/pages/Content/toolbar/styles/components/_ColorWheel.scss ================================================ @use '../../../styles/_variables' as *; .wheel-trigger { transition: transform .2s cubic-bezier(.61,.11,.08,.96), width .2s cubic-bezier(.61,.11,.08,.96), height .2s cubic-bezier(.61,.11,.08,.96), opacity .25s cubic-bezier(.61,.11,.08,.96); position: absolute; left: 0; right: 0; top: 0; bottom: 0; opacity: 0; margin: auto; width: 18px; box-sizing: border-box; height: 18px; z-index: 9999; box-sizing: border-box; background-blend-mode: screen; border-radius: 50%; &:hover { cursor: pointer; } } .wheel-trigger .radial-menu-item-child { transform: rotate(30deg); &:after { content: ''; position: absolute; left: 0; right: 0; width: 100%; height: 100%; z-index: 9999999; border-radius: 50%; box-sizing: border-box; border: 1px solid rgba(0,0,0,.2); } } .color-wheel .wheel-trigger { width: 100px!important; height: 100px!important; transform: rotate(320deg) translate(0px)!important; z-index: 999999999999!important; filter: drop-shadow(0px 4px 50px rgba(0, 0, 0, 0.3)); } .color-wheel-handle { width: 12px!important; height: 12px!important; border-radius: 50%; left: 20px; top: 20px; opacity: 0; background-color: #F17FD7; border: 2px solid white; z-index: 999999999999; display: none; transition: width .25s cubic-bezier(.61,.11,.08,.96), height .25s cubic-bezier(.61,.11,.08,.96), margin-left .25s cubic-bezier(.61,.11,.08,.96), margin-top .25s cubic-bezier(.61,.11,.08,.96); &:hover { width: 18px!important; height: 18px!important; margin-left: -2px; margin-top: -2px; } } .color-wheel .color-wheel-handle { opacity: 0; display: block; animation: fadeIn 0.25s cubic-bezier(.61,.11,.08,.96) forwards; animation-delay: .5s; } .w-color-wheel { pointer-events: none; position: absolute!important; width: 100%!important; height: 100%!important; z-index: 99999999!important; &::after { content: ""; box-sizing: border-box; display: block; width: 100%; height: 100%; position: absolute; z-index: 9999999; border: 1px solid rgba(0,0,0,.2); box-sizing: border-box; border-radius: 50%; } } .color-wheel .w-color-wheel { pointer-events: all!important; } .w-color-wheel-fill { box-shadow: none!important; border: 2px solid white!important; width: 14px!important; height: 14px!important; transition: width .2s cubic-bezier(.61,.11,.08,.96), height .2s cubic-bezier(.61,.11,.08,.96), margin .2s cubic-bezier(.61,.11,.08,.96)!important; box-sizing: border-box!important; margin-left: -2px!important; margin-top: -2px!important; z-index: 9999999999!important; } .w-color-wheel-pointer { z-index: 99999999999!important; opacity: 0; animation: none!important } .color-wheel .w-color-wheel-pointer { animation: fadeInScale .25s cubic-bezier(0.215, 0.610, 0.355, 1) forwards!important; animation-delay: .28s!important; } .w-color-wheel-fill:hover { width: 18px!important; height: 18px!important; margin-left: -4px!important; margin-top: -4px!important; } /* Fade in keyframes */ @keyframes fadeInScale { 0% { opacity: 0; } 100% { opacity: 1; } } .color-wheel, .color-wheel .wheel-trigger, .color-wheel .wheel-trigger .radial-menu-item-child, .color-wheel-handle { cursor: pointer!important; } .color-wheel-input { background: #000; border-radius: 30px; height: 29px; color: #FFF; text-align: center; line-height: 29px; padding-left: 8px; padding-right: 8px; position: absolute; margin-top: -35px; font-family: $font-medium; left: 50%; transform: translate(-50%, 0); opacity: 0; } .color-wheel .color-wheel-input { animation: fadeIn .3s cubic-bezier(.61,.11,.08,.96) forwards; animation-delay: .2s; pointer-events: all!important; } .color-wheel-input { pointer-events: none; } @keyframes fadeIn { 0% { opacity: 0; margin-top: -35px; } 100% { opacity: 1; margin-top: -40px; } } .color-active .color-preview { opacity: 1; } .radial-menu[data-state='closed'] .color-preview { opacity: 0!important; } .color-preview { width: 90%; height: 90%; box-sizing: border-box; border-radius: 50%; position: absolute; left: 50%; top: 50%; z-index: 9999999999; transform: translate(-50%, -50%); opacity: 0; animation: none; pointer-events: none; border: 1px solid #FFF; box-sizing: border-box; } .color-wheel .color-preview { opacity: 0!important; } .wheel-trigger .color-active { box-shadow: none!important; } .color-active .w-color-wheel { transform: scale(1.15)!important; &::after { border: none!important; } } .color-wheel .w-color-wheel { transform: scale(1)!important; &::after { border: 1px solid rgba(0,0,0,.2)!important; } } .radial-menu[data-state='closed'] .w-color-wheel { transform: scale(1)!important; } ================================================ FILE: src/pages/Content/toolbar/styles/components/_RadialMenu.scss ================================================ @use '../../../styles/_variables' as *; .radial-menu { position: absolute; z-index: 9999999999999; width: 100px; height: 100px; top: -66px; left: -49px; pointer-events: none; opacity: 1; transform: scale(1); transition: transform .3s cubic-bezier(0.68, -0.55, 0.265, 1.55), opacity .25s ease-in-out; &::after { content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; z-index: -1; border: 28px solid #FFF; box-sizing: border-box; border-radius: 50%; backdrop-filter: blur(40px); opacity: 0; filter: drop-shadow(0px 4px 50px rgba(0, 0, 0, 0.3)); transform: scale(0); transition: transform .25s cubic-bezier(0.18, -0.55, 0.265, 1.45), opacity .2s ease-in-out; transition-delay: .05s; } &[data-state='open'] { transform: scale(1); opacity: 1; pointer-events: all!important; } &[data-state='open']::after { transform: scale(1); border: 28px solid #FFF; opacity: 1; } } .color-wheel::after { opacity: 0!important; } .eyedropper { position: absolute; left: 0px; top: 0px; right: 0px; bottom: 0px; margin: auto; width: 16px; height: 16px; padding: 8px; z-index: 999999999; background-color: #FFF; border-radius: 50%; opacity: 0; text-align: center; transition: transform .25s cubic-bezier(0.68, -0.55, 0.265, 1.55), opacity .3s ease-in-out, background-color .25s ease-in-out!important; transform: scale(0); overflow: hidden; transform-style: preserve-3d; svg { color: $color-icon; } &:focus-visible { outline: none; background-color: #E6E7EA!important; } &:hover { cursor: pointer; background-color: #E6E7EA; } } .eye-active { background-color: $color-primary!important; svg { color: #FFF!important; fill: #FFF!important; } } .color-wheel .eyedropper { opacity: 0!important; pointer-events: none!important; transform: scale(0)!important; } .radial-menu[data-state='open'] { .eyedropper { transform: scale(1)!important; opacity: 1; } } .radial-menu-items { transform: rotate(10deg); z-index: 99999999; position: absolute; left: 0; right: 0; top: 0; bottom: 0; margin: auto; } .radial-menu-item { position: absolute; left: 0; right: 0; top: 0; bottom: 0; margin: auto; width: 18px; height: 18px; z-index: 999; border-radius: 50%; text-align: center; box-sizing: border-box; line-height: 50px; color: white; border: 1px solid rgba(0,0,0,.2); transition: transform .25s cubic-bezier(.61,.11,.08,.96), opacity .25s cubic-bezier(.61,.11,.08,.96); opacity: 0; &:hover { cursor: pointer; } } .color-wheel .radial-menu-item { opacity: 0!important; } .radial-menu-item-child { width: 100%; height: 100%; position: absolute; box-sizing: border-box; border: 0px; box-shadow: none; top: 0px; pointer-events: none; z-index: 9999999; left: 0px; border-radius: 50%; background-size: cover; &:focus-visible { outline: none; box-shadow: $focus-border; } } .radial-menu[data-state='open'] .radial-menu-item-child { pointer-events: all!important; } $elements: 9; @for $i from 0 to $elements { .radial-menu-item:nth-child(#{$i + 1}), .wheel-trigger { transform: rotate(#{360 / $elements * $i}deg) translate(0px) } .radial-menu[data-state='open'] .radial-menu-item:nth-child(#{$i + 1}), .radial-menu[data-state='open'] .wheel-trigger { transition-delay:calc(.25s - #{.02 * $i}s); transform: rotate(#{360 / $elements * $i}deg) translate(36px); opacity: 1; } } $stops: (); $totalStops:12; $radius:0.9; @for $i from 0 through $totalStops{ $stops: append($stops,hsl($i *(360deg/$totalStops),100%,50%),comma); } .color-active { border: 1px solid #FFFFFF; box-shadow: 0px 0px 0px 2px #0D99FF; } .color-wheel .color-active { border: none!important; box-shadow: none!important; } ================================================ FILE: src/pages/Content/toolbar/styles/components/_StrokeWidth.scss ================================================ @use '../../../styles/_variables' as *; .stroke-width-item { span { width: 18px; height: 18px; display: block; } div[data-state='on'] { background: $color-primary!important; svg { color: #FFF!important; fill: #FFF!important; } } div[data-state='off'] { svg { fill: #201F1D; } } } .stroke-icon svg { text-align: center; margin: auto; display: block; width: 100%; height: 100%; } ================================================ FILE: src/pages/Content/toolbar/styles/components/_Toast.scss ================================================ @use '../../../styles/_variables' as *; .ToastViewport { --viewport-padding: 25px; position: fixed; bottom: 0; right: 0; left: 0; margin: auto!important; display: flex; flex-direction: column; padding: var(--viewport-padding); gap: 14px; max-width: 100vw; width: fit-content; list-style: none; z-index: 2147483647; outline: none; pointer-events: all!important; } .ToastRoot { background-color: $color-text-primary; color: $color-text-contrast; border-radius: $container-border-radius; box-shadow: hsl(206 22% 7% / 35%) 0px 10px 38px -10px, hsl(206 22% 7% / 20%) 0px 10px 20px -15px; padding: 10px 14px; display: flex; flex-direction: row; gap: 8px; font-size: 15px; line-height: 1.5; max-width: 100%; overflow: hidden; justify-content: center; align-items: center; } .ToastRoot[data-state='open'] { animation: slideIn 150ms cubic-bezier(0.16, 1, 0.3, 1); } .ToastRoot[data-state='closed'] { animation: hide 100ms ease-in; } .ToastRoot[data-swipe='move'] { transform: translateY(var(--radix-toast-swipe-move-y)); } .ToastRoot[data-swipe='cancel'] { transform: translateY(0); transition: transform 200ms ease-out; } .ToastRoot[data-swipe='end'] { animation: swipeOut 100ms ease-out; } @keyframes hide { from { opacity: 1; } to { opacity: 0; } } @keyframes slideIn { from { transform: translateY(calc(100% + var(--viewport-padding))); } to { transform: translateY(0); } } @keyframes swipeOut { from { transform: translateY(var(--radix-toast-swipe-end-y)); } to { transform: translateY(calc(100% + var(--viewport-padding))); } } .ToastTitle { color: $color-text-contrast; font-family: $font-medium; } .ToastDescription { color: var(--slate-11); font-family: $font-medium; } .ToastAction { color: $color-text-contrast; font-family: $font-medium; text-align: right; background-color: #51515F; padding: 0px 12px!important; height: 24px!important; cursor: pointer; } ================================================ FILE: src/pages/Content/toolbar/styles/components/_Tooltip.scss ================================================ @use "../../../styles/_variables" as *; .TooltipContent { border-radius: $container-border-radius; background-color: $color-text-primary; padding: 10px 15px; font-size: 12px; margin-bottom: 10px; bottom: 100px; line-height: 1; font-family: $font-medium; z-index: 99999999 !important; color: $color-text-contrast; box-shadow: hsl(206 22% 7% / 35%) 0px 10px 38px -10px, hsl(206 22% 7% / 20%) 0px 10px 20px -15px; user-select: none; transition: opacity 0.3 ease-in-out !important; will-change: transform, opacity; animation-duration: 400ms; animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1); will-change: transform, opacity; } .hide-tooltip { display: none !important; } .tooltip-tall { margin-bottom: 20px; } .tooltip-small { margin-bottom: 5px; } .TooltipContent[data-state="delayed-open"][data-side="top"] { animation-name: slideDownAndFade; } .TooltipContent[data-state="delayed-open"][data-side="right"] { animation-name: slideLeftAndFade; } .TooltipContent[data-state="delayed-open"][data-side="bottom"] { animation-name: slideUpAndFade; } .TooltipContent[data-state="delayed-open"][data-side="left"] { animation-name: slideRightAndFade; } @keyframes slideUpAndFade { from { opacity: 0; transform: translateY(2px); } to { opacity: 1; transform: translateY(0); } } @keyframes slideRightAndFade { from { opacity: 0; transform: translateX(-2px); } to { opacity: 1; transform: translateX(0); } } @keyframes slideDownAndFade { from { opacity: 0; transform: translateY(-2px); } to { opacity: 1; transform: translateY(0); } } @keyframes slideLeftAndFade { from { opacity: 0; transform: translateX(2px); } to { opacity: 1; transform: translateX(0); } } #screenity-ui [data-radix-popper-content-wrapper] { z-index: $z-index-max !important; } .override { display: none !important; opacity: 0 !important; visibility: hidden !important; } ================================================ FILE: src/pages/Content/toolbar/styles/layout/_DrawingToolbar.scss ================================================ @use '../../../styles/_variables' as *; body { background-color: white!important; } .DrawingToolbar.show-toolbar { opacity: 1!important; pointer-events: all!important; transform: scale(1) translate(calc(-50% + 16px), 0px)!important; } .ToolbarBottom .DrawingToolbar { transform-origin: 0 -100%!important; } .DrawingToolbar { opacity: 0; pointer-events: none; align-items: center; display: flex; min-width: max-content; padding-left: 10px; padding-right: 10px; border-radius: 6px; box-shadow: 0 2px 10px var(--blackA7); position: absolute; height: 44px; left: 0px; transform: translate(calc(-50% + 16px)); transform-origin: 0 100%; border-radius: 15px; z-index: 99999999; transition: transform 0.25s cubic-bezier(.61,.11,.08,.96), opacity 0.25s cubic-bezier(.61,.11,.08,.96); transform: scale(0.5) translate(calc(-50% + 16px), 10px); &::after { content: ''; display: block; width: 100%; height: 100%; filter: blur(10px); opacity: .15; background-color: #000; position: absolute; left: 0px; top: 0px; z-index: -999999999999999!important; } &::before { content: ''; display: block; width: 100%; height: 100%; background: rgba(242, 241, 242, 0.85); backdrop-filter: blur(5px); background-clip: content-box; -webkit-mask-image: radial-gradient(circle at 50% 59px, transparent 20px, #000 20px); mask-image: radial-gradient(circle at 50% 59px, transparent 20px, #000 20px); background-position: center bottom 50px; border-radius: 15px; position: absolute; top: 0px; left: 0px; z-index: -2; } .ToolbarSeparator { background-color: #dddcdc; } } .ToolbarTop .DrawingToolbar { bottom: 49px!important; } .ToolbarBottom .DrawingToolbar { top: 48px!important; &::before { -webkit-mask-image: radial-gradient(circle at 50% -14px, transparent 20px, #000 20px)!important; mask-image: radial-gradient(circle at 50% -14px, transparent 20px, #000 20px); background-position: center bottom 50px!important; } } .ColorPicker { width: 14px; height: 14px; background: #ED6C3A; border: 1.5px solid rgba(0, 0, 0, 0.2); border-radius: 50%; } ================================================ FILE: src/pages/Content/toolbar/styles/layout/_ShapeToolbar.scss ================================================ @use '../../../styles/_variables' as *; .shapeToolbar { position: absolute; display: flex; align-items: center; justify-content: center; box-shadow: 0 2px 10px var(--blackA7); left: 165px; padding: $spacing-02; opacity: 0; bottom: 45px; transform: translateY(calc(-50% + 16px)); transform-origin: 0 100%; border-radius: 15px; z-index: 99999999; transition: transform 0.25s cubic-bezier(.61,.11,.08,.96), opacity 0.25s cubic-bezier(.61,.11,.08,.96); transform: scale(0.5) translatY(calc(-50% + 16px), 10px); pointer-events: none; &::after { content: ''; display: block; width: 100%; height: 100%; background: #FFF; z-index: -9999999; position: absolute; left: 0px; top: 0px; border-radius: 15px; filter: drop-shadow(0px 4px 50px rgba(0, 0, 0, 0.3)); transition: filter .2s ease-in-out; } } .shapeToolbar.show-toolbar { opacity: 1!important; pointer-events: all!important; transform: scale(1) translateY(calc(-50% + 16px), 0px)!important; } ================================================ FILE: src/pages/Content/toolbar/styles/layout/_Toolbar.scss ================================================ @use "../../../styles/_variables" as *; @use "sass:color"; /* reset */ a, button { all: unset; } iframe { width: 100%; height: 100%; position: fixed; overflow: scroll; z-index: -9999; top: 0px; left: 0px; border: 0px; pointer-events: all !important; } .container { pointer-events: none !important; } .visually-hidden-toolbar { opacity: 0 !important; visibility: hidden !important; pointer-events: none !important; transition: opacity 0.3s ease, visibility 0s linear 0.3s; } .toolbar-controls { opacity: 0; cursor: pointer; position: absolute; top: -10px; right: -10px; box-sizing: border-box; border-radius: $container-border-radius; border: 1px solid $color-border; display: flex; align-items: center; justify-content: center; gap: 8px; z-index: 99999999999999; background: rgba(240, 238, 238, 1); backdrop-filter: blur(10px); padding-left: 8px; padding-right: 8px; padding-top: 6px; padding-bottom: 6px; transition: opacity 0.2s cubic-bezier(0.4, 0, 0.2, 1); pointer-events: none; &.open { opacity: 1 !important; pointer-events: auto; } .popup-control { svg { color: $color-icon; margin-bottom: -2px; } } } .ToolbarBounds { position: fixed; top: 0px; left: 0px; box-sizing: border-box; width: 100%; height: 100%; border: 10px solid $color-primary; pointer-events: none; transform: scale(1.2); opacity: 0; transition: transform 0.25s cubic-bezier(0.61, 0.11, 0.08, 0.96), opacity 0.25s ease-in-out; } .ToolbarBounds.ToolbarShake { transform: scale(1); opacity: 0.4; } .react-draggable { pointer-events: all; } .ToolbarShake .react-draggable { width: 100%; height: 100%; } .ToolbarElastic { transition: all 0.25s cubic-bezier(0.68, -0.55, 0.265, 1.55); } .ToolbarShake .ToolbarRoot { animation: subtleshake 0.9s cubic-bezier(0.36, 0.07, 0.19, 0.97) both; animation-iteration-count: infinite !important; background-color: white !important; } .ToolbarDragging .ToolbarRoot { transform: scale(1.02); &::after { filter: drop-shadow(0px 20px 50px rgba(0, 0, 0, 0.5)); } } // Shake animation for the toolbar @keyframes shake { 0% { transform: translate(1px, 1px) rotate(0deg); } 20% { transform: translate(-3px, 0px) rotate(1deg); } 30% { transform: translate(3px, 2px) rotate(0deg); } 40% { transform: translate(1px, -1px) rotate(1deg); } 50% { transform: translate(-1px, 2px) rotate(-1deg); } 60% { transform: translate(-3px, 1px) rotate(0deg); } 70% { transform: translate(3px, 1px) rotate(-1deg); } 80% { transform: translate(-1px, -1px) rotate(1deg); } 90% { transform: translate(1px, 2px) rotate(0deg); } 100% { transform: translate(1px, -2px) rotate(-1deg); } } // Subtle shake animation, smooth @keyframes subtleshake { 0% { transform: translate(0px, 0px) rotate(0deg); } 10% { transform: translate(-1px, 1px) rotate(1deg); } 20% { transform: translate(-1px, -1px) rotate(-1deg); } 30% { transform: translate(1px, 0px) rotate(0deg); } 40% { transform: translate(-1px, 1px) rotate(-1deg); } 50% { transform: translate(1px, -1px) rotate(1deg); } 60% { transform: translate(-1px, 1px) rotate(-1deg); } 70% { transform: translate(-1px, -1px) rotate(1deg); } 80% { transform: translate(1px, 1px) rotate(0deg); } 90% { transform: translate(0px, -1px) rotate(-1deg); } 100% { transform: translate(-1px, 1px) rotate(1deg); } } .ToolbarTransparent { opacity: 0; &:hover { opacity: 1; } } .ToolbarHoverZone { display: inline-block; position: relative; // optional: give it a bit of invisible hover padding padding: 4px; // if you want to *visually* match the toolbar area only: width: fit-content; height: fit-content; } .ToolbarRoot { display: flex; align-items: center; padding-left: 10px; transition: opacity 0.25s cubic-bezier(0.61, 0.11, 0.08, 0.96), transform 0.2s cubic-bezier(0.61, 0.11, 0.08, 0.96); min-width: max-content; background-color: white; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.15); padding-right: 10px; height: 48px; position: absolute; bottom: 20px; left: 20px; border-radius: $container-border-radius; &::after { content: ""; display: block; width: 100%; height: 100%; background: #fff; z-index: -9999999; position: absolute; left: 0px; top: 0px; border-radius: $container-border-radius; filter: drop-shadow(0px 4px 50px rgba(0, 0, 0, 0.3)); transition: filter 0.2s ease-in-out; } } .ForceTransparent { opacity: 0 !important; } .ToolbarRecordingControls { display: flex; justify-content: center; align-items: center; background: #f6f7fb; border-radius: $container-border-radius; font-family: $font-medium; height: calc(100% - 12px); padding-left: 2px; padding-right: 2px; } .ToolbarRecordingTime { margin-right: 4px; width: 42px; color: $color-text-primary; font-size: 13px; line-height: 1; user-select: none; -webkit-user-select: none; -moz-user-select: none; } .TimerWarning { color: #ff4c4c; /* or add a pulsing effect */ font-weight: bold; animation: pulse 1s infinite alternate; } @keyframes pulse { from { opacity: 1; } to { opacity: 0.6; } } .ToolbarToggleGroup { display: flex; align-items: center; } .ToolbarToggleWrap { flex: 1 1 auto; align-items: center; justify-content: flex-start; position: relative; flex: 0 0 auto; width: 32px; height: 32px; display: inline-flex; line-height: 1; align-items: center; justify-content: center; } .ToolbarToggleItem, .ToolbarModeItem, .ToolbarModeItemSingle, .ToolbarLink, .ToolbarButton { display: flex; justify-content: center; align-items: center; color: #000; height: 32px; width: 32px; text-align: center; font-size: 13px; line-height: 1; border-radius: 50%; transition: background-color 0.25s ease-in-out; background-color: rgba(124, 139, 165, 0); svg { color: $color-icon; } &:disabled { opacity: 0.5; cursor: not-allowed !important; background: none !important; } &.resume { svg { color: #f7387d !important; } } } .ToolbarToggleItem:hover, .ToolbarModeItem:hover, .ToolbarModeItemSingle:hover, .ToolbarLink:hover, .ToolbarButton:hover { cursor: pointer; background-color: rgba(124, 139, 165, 0.1) !important; } .ToolbarToggleItem:focus-visible, .ToolbarModeItemSingle:focus-visible, .ToolbarModeItem:focus-visible, .ToolbarLink:focus-visible, .ToolbarButton:focus-visible { position: relative; box-shadow: $focus-border; } .ToolbarModeItemSingle { display: flex; justify-content: center; align-items: center; z-index: 99999; position: relative; &:first-child { margin-left: 0; } &[data-state="on"] { background: rgba(120, 192, 114, 0.1); svg { color: #78c072; } &::before { transform: translateY(0px) scale(1) !important; opacity: 1 !important; } } } .ToolbarModeItem { display: flex; justify-content: center; align-items: center; z-index: 99999; position: relative; &:first-child { margin-left: 0; } &::before { content: ""; display: block; width: 100%; height: 50%; border-radius: 80px 80px 0% 0%; box-sizing: border-box; position: absolute; top: -16px; left: 0; z-index: -999999; transition: transform 0.25s cubic-bezier(0.61, 0.11, 0.08, 0.96), opacity 0.25s ease-in-out; transform: translateY(5px) scale(0) !important; border-right: 3px solid white; border-top: 9px solid white; border-left: 3px solid white; background-color: transparent; opacity: 0; } &[data-state="on"] { background: rgba(56, 126, 247, 0.1); svg { color: $color-primary; } &::before { transform: translateY(0px) scale(1) !important; opacity: 1 !important; } } } .ToolbarBottom .ToolbarModeItem { &::before { transform: translateY(-5px) scale(0.5) !important; bottom: -16px; top: unset !important; border-bottom: 9px solid white !important; border-radius: 0% 0% 80px 80px !important; border-top: none !important; } &[data-state="on"] { &::before { transform: translateY(0px) scale(1) !important; } } } .ToolbarToggleItem { display: flex; justify-content: center; align-items: center; z-index: 99999; position: relative; &:first-child { margin-left: 0; } &[data-state="on"] { background: $gradient-primary; color: #fff; svg { color: #fff; } } } .ToolbarSeparator { width: 1px; height: 19px; background-color: $color-border; margin: 0 8px; } .ToolbarLink { background-color: transparent; color: var(--mauve11); display: inline-flex; justify-content: center; align-items: center; } .ToolbarLink:hover { background-color: transparent; cursor: pointer; } .ToolbarPaused { position: fixed; top: 0px; left: 0px; width: 100%; height: 100%; box-sizing: border-box; border: 10px solid #f7387d; pointer-events: none; opacity: 0.5; &.hidden { display: none; } } .OnboardingArrow { position: absolute; z-index: $z-index-max; width: max-content; display: flex; flex-direction: column; align-items: center; gap: 16px; left: -69px; bottom: 23px; transform: rotate(12deg); pointer-events: none !important; } // Hide legacy "click here to draw" hint while Driver.js onboarding is active. body.driver-active .OnboardingArrow { display: none !important; } .OnboardingText { font-size: 32px; color: $color-icon; font-family: "Gloria-Hallelujah", sans-serif !important; } .ArrowShape { margin-left: -15px; } // Onboarding (driver.js) .driver-popover.ScreenityOnboardingPopover, .ScreenityOnboardingPopover, .onboarding-popover { border-radius: $container-border-radius !important; background: $color-background !important; color: $color-text-primary !important; font-family: $font-regular !important; box-shadow: $container-soft-shadow !important; padding: 20px !important; max-width: 340px !important; font-size: $font-size-normal !important; z-index: $z-index-max !important; .driver-popover-title { font-size: 1rem !important; font-family: $font-bold !important; margin-bottom: 12px !important; color: $color-text-primary !important; } .driver-popover-description { font-size: $font-size-normal !important; font-family: $font-medium !important; color: $color-text-secondary !important; line-height: 1.5 !important; margin-bottom: 18px !important; a { color: $color-primary !important; font-family: $font-bold !important; text-decoration: none !important; cursor: pointer !important; } } .driver-popover-close-btn { display: none !important; top: 14px; right: 12px; color: $color-text-secondary !important; font-size: 1.5rem !important; } .driver-popover-arrow { width: 0px; height: 0px; // border: 4px solid #fff; background: none !important; box-shadow: none !important; box-sizing: border-box; } .driver-popover-arrow-side-top { border-left: 8px solid transparent !important; border-right: 8px solid transparent !important; border-top: 8px solid $color-background !important; } .driver-popover-arrow-side-bottom { border-left: 8px solid transparent !important; border-right: 8px solid transparent !important; border-bottom: 8px solid $color-background !important; } .driver-popover-arrow-side-left { border-top: 8px solid transparent !important; border-bottom: 8px solid transparent !important; border-left: 8px solid $color-background !important; } .driver-popover-arrow-side-right { border-top: 8px solid transparent !important; border-bottom: 8px solid transparent !important; border-right: 8px solid $color-background !important; } .driver-popover-arrow-align-start { top: 35px !important; } .driver-popover-arrow-align-end { bottom: 35px !important; } .driver-popover-arrow-align-left { left: 35px !important; } .driver-popover-arrow-align-right { right: 35px !important; } .driver-popover-progress-text { font-size: $font-size-detail !important; font-family: $font-medium !important; color: $color-text-secondary !important; opacity: 0.7 !important; margin-top: 2px !important; } .driver-popover-footer { display: flex !important; justify-content: flex-end; gap: 8px; margin-top: 16px; .driver-popover-navigation-btns { gap: 6px !important; .driver-popover-next-btn, .driver-popover-prev-btn, .driver-popover-close-btn { background-color: $color-primary !important; color: white !important; border: none !important; padding: 10px 14px !important; border-radius: 30px !important; font-family: $font-medium !important; font-weight: 500 !important; cursor: pointer !important; font-size: 0.875rem !important; text-shadow: none !important; } .driver-popover-next-btn { &:hover { background: color.adjust($color-primary, $lightness: -5%) !important; } } .driver-popover-prev-btn { background-color: transparent !important; color: $color-text-primary !important; border: 1px solid $color-border !important; &:hover { background: $color-light-grey !important; } } .driver-popover-close-btn { background-color: transparent !important; color: $color-text-secondary !important; } } } } ================================================ FILE: src/pages/Content/utils/BlurTool.jsx ================================================ import React, { useLayoutEffect, useState, useRef, useContext, useEffect, } from "react"; // Context import { contentStateContext } from "../context/ContentState"; const BlurTool = () => { const [contentState, setContentState] = useContext(contentStateContext); const hoveredElementRef = useRef(null); const blurModeRef = useRef(null); const [showOutline, setShowOutline] = useState(false); const [outlineRect, setOutlineRect] = useState(null); useEffect(() => { blurModeRef.current = contentState.blurMode; }, [contentState.blurMode]); useEffect(() => { if (!contentState.showExtension) { setShowOutline(false); // Remove blur from all elements const elements = document.querySelectorAll(".screenity-blur"); elements.forEach((element) => { element.classList.remove("screenity-blur"); }); } }, [contentState.showExtension]); useLayoutEffect(() => { const updateOutline = (target) => { if (!target) return; hoveredElementRef.current = target; const rect = target.getBoundingClientRect(); setOutlineRect({ top: rect.top + window.scrollY, left: rect.left + window.scrollX, width: target.offsetWidth, height: target.offsetHeight, }); setShowOutline(true); }; const handleMouseMove = (event) => { if (!blurModeRef.current) { setShowOutline(false); setOutlineRect(null); return; } const target = event.target; if ( !target.classList.contains("screenity-outline") && !target.closest("#screenity-ui #screenity-ui *") ) { updateOutline(target); document.body.style.cursor = "pointer"; } else { document.body.style.cursor = "auto"; setShowOutline(false); setOutlineRect(null); } }; const handleMouseOut = () => { setShowOutline(false); setOutlineRect(null); }; const handleMouseDown = (event) => { if (!blurModeRef.current) { setShowOutline(false); return; } const target = event.target; if (target.closest("#screenity-ui, #screenity-ui *")) { return; } event.preventDefault(); event.stopPropagation(); }; const handleElementClick = (event) => { if (!blurModeRef.current) { setShowOutline(false); return; } const target = event.target; if (target.closest("#screenity-ui, #screenity-ui *")) { return; } event.preventDefault(); event.stopPropagation(); target.classList.toggle("screenity-blur"); }; const handleMouseUp = (event) => { if (!blurModeRef.current) { setShowOutline(false); setOutlineRect(null); return; } const target = event.target; if (target.closest("#screenity-ui, #screenity-ui *")) { return; } event.preventDefault(); event.stopPropagation(); }; const handleScroll = () => { if (!blurModeRef.current || !hoveredElementRef.current) return; updateOutline(hoveredElementRef.current); }; document.body.addEventListener("mousemove", handleMouseMove, true); document.body.addEventListener("mousedown", handleMouseDown, true); document.body.addEventListener("mouseout", handleMouseOut, true); document.body.addEventListener("mouseup", handleMouseUp, true); document.body.addEventListener("click", handleElementClick, true); document.addEventListener("scroll", handleScroll, true); return () => { document.body.removeEventListener("mousemove", handleMouseMove, true); document.body.removeEventListener("mousedown", handleMouseDown, true); document.body.removeEventListener("mouseout", handleMouseOut, true); document.body.removeEventListener("mouseup", handleMouseUp, true); document.body.removeEventListener("click", handleElementClick, true); document.removeEventListener("scroll", handleScroll, true); }; }, []); return (
{showOutline && outlineRect && (
)}
); }; export default BlurTool; ================================================ FILE: src/pages/Content/utils/CursorModes.jsx ================================================ import React, { useState, useEffect, useContext, useRef } from "react"; // Context import { contentStateContext } from "../context/ContentState"; const CursorModes = () => { const [contentState, setContentState] = useContext(contentStateContext); const effectsRef = useRef(new Set()); useEffect(() => { const nextEffects = Array.isArray(contentState.cursorEffects) ? contentState.cursorEffects : []; effectsRef.current = new Set(nextEffects); }, [contentState.cursorEffects]); const mouseDownHandler = (e) => { if (effectsRef.current.has("target")) { const target = document.querySelector(".cursor-click-target"); if (!target) return; target.style.transform = "translate(-50%, -50%) scale(1)"; target.style.opacity = "1"; } }; const mouseUpHandler = (e) => { if (effectsRef.current.has("target")) { const target = document.querySelector(".cursor-click-target"); if (!target) return; target.style.transform = "translate(-50%, -50%) scale(0)"; target.style.opacity = "0"; window.setTimeout(() => { const targetReset = document.querySelector(".cursor-click-target"); if (!targetReset) return; targetReset.style.transform = "translate(-50%, -50%) scale(1)"; }, 350); } }; const [lastMousePosition, setLastMousePosition] = useState({ x: 0, y: 0 }); const lastMouseRef = useRef(lastMousePosition); useEffect(() => { lastMouseRef.current = lastMousePosition; }, [lastMousePosition]); const updateCursorPosition = () => { const scrollTop = window.scrollY; const scrollLeft = window.scrollX; if (effectsRef.current.has("target")) { const target = document.querySelector(".cursor-click-target"); if (target) { target.style.top = lastMouseRef.current.y + scrollTop + "px"; target.style.left = lastMouseRef.current.x + scrollLeft + "px"; } } if (effectsRef.current.has("highlight")) { const highlight = document.querySelector(".cursor-highlight"); if (highlight) { highlight.style.top = lastMouseRef.current.y + scrollTop + "px"; highlight.style.left = lastMouseRef.current.x + scrollLeft + "px"; } } if (effectsRef.current.has("spotlight")) { const spotlight = document.querySelector(".spotlight"); if (spotlight) { spotlight.style.top = lastMouseRef.current.y + scrollTop + "px"; spotlight.style.left = lastMouseRef.current.x + scrollLeft + "px"; } } }; const mouseMoveHandler = (e) => { const next = { x: e.clientX, y: e.clientY }; lastMouseRef.current = next; setLastMousePosition(next); updateCursorPosition(); }; const scrollHandler = () => { updateCursorPosition(); }; // Show click target when user clicks anywhere for 1 second, animate scale up and fade out useEffect(() => { document.addEventListener("mousedown", mouseDownHandler); document.addEventListener("mousemove", mouseMoveHandler); document.addEventListener("mouseup", mouseUpHandler); document.addEventListener("scroll", scrollHandler); return () => { document.removeEventListener("mousedown", mouseDownHandler); document.removeEventListener("mousemove", mouseMoveHandler); document.removeEventListener("mouseup", mouseUpHandler); document.removeEventListener("scroll", scrollHandler); }; }, []); return (
); }; export default CursorModes; ================================================ FILE: src/pages/Content/utils/ZoomContainer.jsx ================================================ import React, { useState, useEffect, useRef, useContext } from "react"; // Context import { contentStateContext } from "../context/ContentState"; const ZoomContainer = () => { const [contentState, setContentState] = useContext(contentStateContext); const [zoomLevel, setZoomLevel] = useState(1); const scaleRef = useRef(1); const translateXRef = useRef(0); const translateYRef = useRef(0); const cursorXRef = useRef(0); const cursorYRef = useRef(0); const isKeyDownRef = useRef(false); const isKeyUpRef = useRef(false); const zoomSelector = useRef(null); const oldPosition = useRef(null); const oldWidth = useRef(null); const oldHeight = useRef(null); const oldOverflow = useRef(null); const oldTop = useRef(null); const oldLeft = useRef(null); const contentStateRef = useRef(contentState); const observer = useRef(null); useEffect(() => { oldPosition.current = document.body.style.position; oldWidth.current = document.body.style.width; oldHeight.current = document.body.style.height; oldOverflow.current = document.body.style.overflow; oldTop.current = document.body.style.top; oldLeft.current = document.body.style.left; }, []); useEffect(() => { contentStateRef.current = contentState; }, [contentState]); const handleKeyDown = (e) => { // Alt / Option + Shift + Z if (e.code === "KeyE" && e.altKey && e.shiftKey) { //if (!contentStateRef.current.recording) return; if (!contentStateRef.current.zoomEnabled) return; if (isKeyDownRef.current) return; isKeyDownRef.current = true; zoomIn(); } }; const handleKeyUp = (e) => { if (e.code === "KeyE" || e.altKey || e.shiftKey) { isKeyDownRef.current = false; isKeyUpRef.current = true; zoomOut(); setTimeout(() => { isKeyUpRef.current = false; setTimeout(() => { enableScrolling(); }, 500); }, 500); } }; const handleMouseMove = (e) => { //if (!contentStateRef.current.recording) return; if (!contentStateRef.current.zoomEnabled) return; const { top, left } = document.documentElement.getBoundingClientRect(); cursorXRef.current = e.clientX - left; cursorYRef.current = e.clientY - top; applyTransform(); }; const zoomIn = () => { scaleRef.current *= 1.5; setZoomLevel(scaleRef.current); applyTransformWithTransition(); preventScrolling(); }; const zoomOut = () => { scaleRef.current = 1; translateXRef.current = 0; translateYRef.current = 0; setZoomLevel(scaleRef.current); //applyTransformWithTransition(); //enableScrolling(); }; const applyTransform = () => { if (!zoomSelector.current) return; //if (!contentStateRef.current.recording) return; const { current: scale } = scaleRef; const { current: translateX } = translateXRef; const { current: translateY } = translateYRef; const originX = cursorXRef.current - translateX; const originY = cursorYRef.current - translateY; zoomSelector.current.style.transform = `scale(${scale}) translate(${translateX}px, ${translateY}px)`; zoomSelector.current.style.transformOrigin = `${originX}px ${originY}px`; // I also need to apply the transform to the #canvas-wrapper, if it exists const canvasWrapper = document.querySelector("#canvas-wrapper-screenity"); // Substract scroll position const fixedOriginX = originX - window.scrollX; const fixedOriginY = originY - window.scrollY; if (canvasWrapper) { canvasWrapper.style.transform = `scale(${scale}) translate(${translateX}px, ${translateY}px)`; canvasWrapper.style.transformOrigin = `${fixedOriginX}px ${fixedOriginY}px`; } // Also to #mockup-wrapper //const mockupWrapper = document.querySelector("#mockup-wrapper"); //if (mockupWrapper) { // mockupWrapper.style.transform = `scale(${scale}) translate(${translateX}px, ${translateY}px)`; // mockupWrapper.style.transformOrigin = `${originX}px ${originY}px`; //} }; const applyTransformWithTransition = () => { if (!zoomSelector.current) return; zoomSelector.current.style.transition = "transform 0.5s"; if (document.querySelector("#canvas-wrapper-screenity")) { document.querySelector("#canvas-wrapper-screenity").style.transition = "transform 0.5s"; } //if (document.querySelector("#mockup-wrapper")) { // document.querySelector("#mockup-wrapper").style.transition = // "transform 0.5s"; //} applyTransform(); }; const preventScrolling = () => { /* zoomSelector.current.style.position = "fixed"; zoomSelector.current.style.top = "0"; zoomSelector.current.style.left = "0"; zoomSelector.current.style.overflow = "hidden"; zoomSelector.current.style.width = "100vw"; zoomSelector.current.style.height = "100vh"; */ }; const enableScrolling = () => { if (!zoomSelector.current) return; zoomSelector.current.style.position = oldPosition.current; zoomSelector.current.style.top = oldTop.current; zoomSelector.current.style.left = oldLeft.current; zoomSelector.current.style.overflow = oldOverflow.current; zoomSelector.current.style.width = oldWidth.current; zoomSelector.current.style.height = oldHeight.current; }; useEffect(() => { window.addEventListener("keydown", handleKeyDown); window.addEventListener("keyup", handleKeyUp); window.addEventListener("mousemove", handleMouseMove); return () => { window.removeEventListener("keydown", handleKeyDown); window.removeEventListener("keyup", handleKeyUp); window.removeEventListener("mousemove", handleMouseMove); }; }, [contentState.zoomEnabled, contentState.showExtension]); useEffect(() => { if (!contentState.zoomEnabled) return; if (!contentState.showPopup) return; setTimeout(() => { //if (!contentState.recording) return; if (document.querySelector("#screenity-zoom-wrap")) return; const div = document.createElement("div"); div.id = "screenity-zoom-wrap"; div.style.width = "100vw"; div.style.height = "100vh"; // Move the body's children into this wrapper while ( document.body.firstChild && document.body.firstChild.id !== "screenity-ui" ) { if (document.body.firstChild.id !== "screenity-ui") { div.appendChild(document.body.firstChild); } } // Append the wrapper to the body document.body.prepend(div); document.body.appendChild(document.getElementById("screenity-ui")); zoomSelector.current = document.querySelector("#screenity-zoom-wrap"); observer.current = new MutationObserver((mutations) => { if (!contentState.showExtension) { mutations.forEach((mutation) => { if (mutation.addedNodes.length > 0) { const screenityUi = document.querySelector("#screenity-ui"); if (screenityUi) { // Disconnect the observer observer.current.disconnect(); } } }); } }); observer.current.observe(document.body, { childList: true, subtree: true, }); }, 500); return () => { setTimeout(() => { if (observer.current && typeof observer.current === "object") { observer.current.disconnect(); } const zoomWrap = document.querySelector("#screenity-zoom-wrap"); if (zoomWrap) { while (zoomWrap.firstChild) { document.body.prepend(zoomWrap.firstChild); } if (document.body.contains(zoomWrap)) { document.body.removeChild(zoomWrap); } } // reset scaleRef.current = 1; translateXRef.current = 0; translateYRef.current = 0; setZoomLevel(scaleRef.current); }, 500); }; }, [contentState.zoomEnabled, contentState.showExtension]); useEffect(() => { setTimeout(() => { if (!contentState.zoomEnabled || !contentState.showExtension) { const zoomWrap = document.querySelector("#screenity-zoom-wrap"); if (zoomWrap) { while (zoomWrap.firstChild) { document.body.prepend(zoomWrap.firstChild); } if (document.body.contains(zoomWrap)) { document.body.removeChild(zoomWrap); } } // reset scaleRef.current = 1; translateXRef.current = 0; translateYRef.current = 0; setZoomLevel(scaleRef.current); } }, 500); }, [contentState.zoomEnabled, contentState.showExtension]); useEffect(() => { if (!zoomSelector.current) return; //if (!contentStateRef.current.recording) return; if (!contentStateRef.current.zoomEnabled) return; if (isKeyDownRef.current || isKeyUpRef.current) { //preventScrolling(); applyTransform(); } }, [zoomLevel]); return null; }; export default ZoomContainer; ================================================ FILE: src/pages/Content/warning/Warning.jsx ================================================ import React, { useState, useEffect, useContext, useCallback, useRef, } from "react"; import { AudioIcon, CameraCloseIcon, NotSupportedIcon, CameraIcon, } from "../toolbar/components/SVG"; import * as ToastEl from "@radix-ui/react-toast"; // Context import { contentStateContext } from "../context/ContentState"; const Warning = () => { const [contentState, setContentState] = useContext(contentStateContext); const [open, setOpen] = useState(false); const [title, setTitle] = useState("Record computer audio"); const [description, setDescription] = useState(""); const [icon, setIcon] = useState("AudioIcon"); const [duration, setDuration] = useState(10000); const openWarning = useCallback((title, description, icon, duration) => { setTitle(title); setDescription(description); setIcon(icon); setDuration(duration); setOpen(true); }, []); useEffect(() => { setContentState((prevContentState) => ({ ...prevContentState, openWarning: openWarning, })); return () => { setContentState((prevContentState) => ({ ...prevContentState, openWarning: null, })); }; }, []); useEffect(() => { if (icon === "AudioIcon") { if (contentState.recordingType === "region") { setOpen(false); } } }, [contentState.recordingType]); useEffect(() => { if (contentState.recording) { setOpen(false); } }, [contentState.recording]); return ( { setOpen(false); }} >
{icon === "AudioIcon" && } {icon === "NotSupportedIcon" && } {icon === "CameraIcon" && }
{title} {description}
{ setOpen(false); }} >
); }; export default Warning; ================================================ FILE: src/pages/Content/warning/styles/_Warning.scss ================================================ @use "../../styles/_variables" as *; .WarningViewport { --viewport-padding: 25px; position: fixed; top: 0; right: 0; left: 0; margin: auto !important; display: flex; flex-direction: column; padding: var(--viewport-padding); gap: 14px; max-width: 100vw; width: fit-content; list-style: none; z-index: 2147483647; outline: none; pointer-events: all !important; } .warning-root { background-color: $color-text-primary; color: $color-text-contrast; border-radius: $container-border-radius; box-shadow: hsl(206 22% 7% / 35%) 0px 10px 38px -10px, hsl(206 22% 7% / 20%) 0px 10px 20px -15px; padding: 14px 20px; display: flex; flex-direction: row; justify-content: center; gap: 8px; font-size: 15px; line-height: 1.5; max-width: 350px; overflow: hidden; align-items: center; text-align: left; align-items: flex-start; } .warning-content { display: flex; flex-direction: column; justify-content: left; align-items: flex-start; gap: 8px; width: 100%; } .warning-root[data-state="open"] { animation: slideIn2 150ms cubic-bezier(0.16, 1, 0.3, 1); } .warning-root[data-state="closed"] { animation: hide 100ms ease-in; } .warning-root[data-swipe="move"] { transform: translateY(var(--radix-toast-swipe-move-y)); } .warning-root[data-swipe="cancel"] { transform: translateY(0); transition: transform 200ms ease-out; } .warning-root[data-swipe="end"] { animation: swipeOut2 100ms ease-out; } @keyframes hide { from { opacity: 1; } to { opacity: 0 !important; } } @keyframes slideIn2 { from { transform: translateY(calc(-100% - var(--viewport-padding))); } to { transform: translateY(0); } } @keyframes swipeOut2 { from { transform: translateY(var(--radix-toast-swipe-end-y)); } to { transform: translateY(calc(-100% - var(--viewport-padding))); } } .warning-title { color: $color-text-contrast; font-family: $font-medium; line-height: 1.4; } .warning-description { color: $color-text-contrast; opacity: 0.8; font-family: $font-medium; line-height: 1.5; } .ToastAction { color: $color-text-contrast; font-family: $font-medium; text-align: right; background-color: #51515f; padding: 0px 12px !important; height: 24px !important; cursor: pointer; } .warning-close { z-index: 999999; } .warning-close:hover { cursor: pointer; } ================================================ FILE: src/pages/Download/Download.jsx ================================================ import React, { useState, useCallback, useEffect } from "react"; import localforage from "localforage"; localforage.config({ driver: localforage.INDEXEDDB, name: "screenity", version: 1, }); // Get chunks store const chunksStore = localforage.createInstance({ name: "chunks" }); const cameraChunksStore = localforage.createInstance({ name: "cameraChunks" }); const audioChunksStore = localforage.createInstance({ name: "audioChunks" }); const Download = () => { const base64ToUint8Array = (base64) => { const dataUrlRegex = /^data:(.*?);base64,/; const matches = base64.match(dataUrlRegex); if (matches !== null) { // Base64 is a data URL const mimeType = matches[1]; const binaryString = atob(base64.slice(matches[0].length)); const bytes = new Uint8Array(binaryString.length); for (let i = 0; i < binaryString.length; i++) { bytes[i] = binaryString.charCodeAt(i); } return new Blob([bytes], { type: mimeType }); } else { // Base64 is a regular string const binaryString = atob(base64); const bytes = new Uint8Array(binaryString.length); for (let i = 0; i < binaryString.length; i++) { bytes[i] = binaryString.charCodeAt(i); } return new Blob([bytes], { type: "video/webm" }); } }; const handleMessage = useCallback((message, sender, sendResponse) => { if (message.type === "download-video") { const base64 = message.base64; const blob = base64ToUint8Array(base64); const title = message.title.replace(/[\/\\:?~<>|*"]/g, "_"); const url = URL.createObjectURL(blob); chrome.downloads .download({ url: url, filename: title, saveAs: true, }) .then(() => { URL.revokeObjectURL(url); // Close this tab window.close(); }); } else if (message.type === "recover-indexed-db") { // Rewrite in localforage const chunkArray = []; chunksStore .iterate((value, key, iterationNumber) => { chunkArray.push(value.chunk); }) .then(() => { const blob = new Blob(chunkArray, { type: "video/webm" }); const url = URL.createObjectURL(blob); chrome.downloads .download({ url: url, filename: "recovered-video.webm", saveAs: true, }) .then(() => { URL.revokeObjectURL(url); // Close this tab window.close(); }); }); } else if (message.type === "recover-cloud-indexed-db") { (async () => { const ts = new Date().toISOString().replace(/[:.]/g, "-"); let downloaded = 0; const downloadTrack = async (store, label, mimeType) => { const entries = []; await store.iterate((value) => { if (value?.chunk) entries.push(value); }); entries.sort((a, b) => (a.index || 0) - (b.index || 0)); if (!entries.length) return; const blob = new Blob(entries.map((e) => e.chunk), { type: mimeType }); const url = URL.createObjectURL(blob); await chrome.downloads.download({ url, filename: `Screenity-Recovery-${label}-${ts}.webm`, saveAs: false, }); URL.revokeObjectURL(url); downloaded++; }; await downloadTrack(chunksStore, "Screen", "video/webm"); await downloadTrack(cameraChunksStore, "Camera", "video/webm"); await downloadTrack(audioChunksStore, "Audio", "audio/webm"); // Clear all three stores after download is initiated await Promise.allSettled([ chunksStore.clear(), cameraChunksStore.clear(), audioChunksStore.clear(), ]); const { recorderSession } = await chrome.storage.local.get([ "recorderSession", ]); // Remove stale TUS journal + scene keys so the next recording cannot // accidentally resume the crashed upload or reuse the old scene. // Mirrors clearStaleUploadJournals() in CloudRecorder.jsx. const journalKeysToRemove = ["sceneId", "sceneIdStatus"]; const tracks = recorderSession?.tracks || {}; for (const trackData of Object.values(tracks)) { const upl = trackData?.uploader; if (!upl) continue; if (upl.journalKey) journalKeysToRemove.push(upl.journalKey); if (upl.journalLookupKey) journalKeysToRemove.push(upl.journalLookupKey); const pid = upl.projectId || recorderSession?.projectId || null; const sid = upl.sceneId || null; const t = upl.type || upl.trackType || null; if (pid && t) { journalKeysToRemove.push( `bunnyVideoMap-${pid}-${sid || "none"}-${t || "none"}`, ); } } try { await chrome.storage.local.remove([...new Set(journalKeysToRemove)]); } catch (err) { console.warn("[CloudRestore] failed to remove stale journal keys", err); } // Mark session as recovered so the popup button disables on next open if (recorderSession) { await chrome.storage.local.set({ recorderSession: { ...recorderSession, status: "recovered", recoveredAt: Date.now(), }, }); } if (downloaded > 0) window.close(); })(); } else if (message.type === "recover-indexed-db-mp4") { const chunkArray = []; chunksStore .iterate((value) => { if (value && typeof value.index === "number" && value.chunk) { chunkArray.push({ index: value.index, chunk: value.chunk }); } }) .then(() => { chunkArray.sort((a, b) => a.index - b.index); const blob = new Blob( chunkArray.map((entry) => entry.chunk), { type: "video/mp4" } ); const url = URL.createObjectURL(blob); const filename = `screenity-recording-${Date.now()}.mp4`; chrome.downloads .download({ url: url, filename, saveAs: true, }) .then(() => { URL.revokeObjectURL(url); window.close(); }); }); } }); useEffect(() => { // chrome on message chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { handleMessage(message); }); return () => { chrome.runtime.onMessage.removeListener( (message, sender, sendResponse) => { handleMessage(message); } ); }; }, []); return
; }; export default Download; ================================================ FILE: src/pages/Download/index.html ================================================ Screenity - Video editor
================================================ FILE: src/pages/Download/index.jsx ================================================ import React from "react"; import { createRoot } from "react-dom/client"; import Download from "./Download"; // Find the container to render into const container = window.document.querySelector("#app-container"); if (container) { const root = createRoot(container); root.render(); } // Hot Module Replacement if (module.hot) { module.hot.accept(); } ================================================ FILE: src/pages/Editor/Sandbox.jsx ================================================ import React, { useEffect, useRef } from "react"; // Import all the utils import addAudioToVideo from "./utils/addAudioToVideo"; import base64ToBlob from "./utils/base64toBlob"; import blobToArrayBuffer from "./utils/blobToArrayBuffer"; import cropVideo from "./utils/cropVideo"; import cutVideo from "./utils/cutVideo"; import getFrame from "./utils/getFrame"; import hasAudio from "./utils/hasAudio"; import muteVideo from "./utils/muteVideo"; import reencodeVideo from "./utils/reencodeVideo"; import toGIF from "./utils/toGIF"; import toWebM from "./utils/toWebM"; const Sandbox = () => { const iframeRef = useRef(null); const scriptLoaded = useRef(false); const triggerLoad = useRef(false); const ffmpegInstance = useRef(null); const sendMessage = (message) => { iframeRef.current.contentWindow.postMessage(message, "*"); }; const loadFfmpeg = async () => { if (!scriptLoaded.current) return; if (!triggerLoad.current) return; if (ffmpegInstance.current) return; try { const { createFFmpeg } = FFmpeg; // Initialize ffmpeg.js ffmpegInstance.current = createFFmpeg({ log: false, progress: (params) => { // Send progress updates to parent window if (params.ratio && params.ratio >= 0) { const percentage = Math.min(Math.round(params.ratio * 100), 100); sendMessage({ type: "ffmpeg-progress", progress: percentage, }); } }, corePath: "assets/vendor/ffmpeg-core.js", }); await ffmpegInstance.current.load(); sendMessage({ type: "ffmpeg-loaded" }); } catch (error) { sendMessage({ type: "ffmpeg-load-error", error: JSON.stringify(error), fallback: false, }); } }; useEffect(() => { const script = document.createElement("script"); script.src = "assets/vendor/ffmpeg.min.js"; script.async = true; // On load, set scriptLoaded to true script.onload = () => { scriptLoaded.current = true; loadFfmpeg(); }; document.body.appendChild(script); return () => { document.body.removeChild(script); }; }, []); const toBase64 = (blob) => { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.readAsDataURL(blob); reader.onloadend = () => { resolve(reader.result); }; reader.onerror = reject; }); }; const onMessage = async (message) => { if (message.type === "load-ffmpeg") { triggerLoad.current = true; loadFfmpeg(); } else if (message.type === "add-audio-to-video") { try { const blob = await addAudioToVideo( ffmpegInstance.current, message.blob, message.audio, message.duration, message.volume, message.replaceAudio, ); const base64 = await toBase64(blob); sendMessage({ type: "updated-blob", base64: base64, topLevel: true }); } catch (error) { sendMessage({ type: "ffmpeg-error", error: JSON.stringify(error) }); } } else if (message.type === "base64-to-blob") { try { const blob = await base64ToBlob(ffmpegInstance.current, message.base64); const base64 = await toBase64(blob); sendMessage({ type: "updated-blob", base64: base64, topLevel: true, edited: false, }); } catch (error) { sendMessage({ type: "ffmpeg-error", error: JSON.stringify(error) }); } } else if (message.type === "blob-to-array-buffer") { try { const arrayBuffer = await blobToArrayBuffer( ffmpegInstance.current, message.blob, ); sendMessage({ type: "updated-array-buffer", arrayBuffer: arrayBuffer }); } catch (error) { sendMessage({ type: "ffmpeg-error", error: JSON.stringify(error) }); } } else if (message.type === "crop-video") { try { const blob = await cropVideo(ffmpegInstance.current, message.blob, { x: message.x, y: message.y, width: message.width, height: message.height, }); const base64 = await toBase64(blob); sendMessage({ type: "updated-blob", base64: base64, topLevel: true }); //sendMessage({ type: "crop-update" }); } catch (error) { sendMessage({ type: "ffmpeg-error", error: JSON.stringify(error) }); } } else if (message.type === "cut-video") { try { const blob = await cutVideo( ffmpegInstance.current, message.blob, message.startTime, message.endTime, message.cut, message.duration, message.encode, ); const base64 = await toBase64(blob); sendMessage({ type: "updated-blob", base64: base64, addToHistory: true, }); } catch (error) { sendMessage({ type: "ffmpeg-error", error: JSON.stringify(error) }); } } else if (message.type === "get-frame") { try { const blob = await getFrame( ffmpegInstance.current, message.blob, message.time, ); sendMessage({ type: "new-frame", frame: blob }); } catch (error) { sendMessage({ type: "ffmpeg-error", error: JSON.stringify(error) }); } } else if (message.type === "has-audio") { try { const audio = await hasAudio(ffmpegInstance.current, message.video); sendMessage({ type: "updated-has-audio", hasAudio: audio }); } catch (error) { sendMessage({ type: "ffmpeg-error", error: JSON.stringify(error) }); } } else if (message.type === "mute-video") { try { const blob = await muteVideo( ffmpegInstance.current, message.blob, message.startTime, message.endTime, message.duration, ); const base64 = await toBase64(blob); sendMessage({ type: "updated-blob", base64: base64, addToHistory: true, }); } catch (error) { sendMessage({ type: "ffmpeg-error", error: JSON.stringify(error) }); } } else if (message.type === "reencode-video") { try { const blob = await reencodeVideo( ffmpegInstance.current, message.blob, message.duration, ); const base64 = await toBase64(blob); sendMessage({ type: "updated-blob", base64: base64, topLevel: true }); } catch (error) { sendMessage({ type: "ffmpeg-error", error: JSON.stringify(error) }); } } else if (message.type === "to-gif") { try { const blob = await toGIF(ffmpegInstance.current, message.blob); const base64 = await toBase64(blob); sendMessage({ type: "download-gif", base64: base64 }); } catch (error) { sendMessage({ type: "ffmpeg-error", error: JSON.stringify(error) }); } } else if (message.type === "to-webm") { try { const blob = await toWebM( ffmpegInstance.current, message.blob, message.duration, ); const base64 = await toBase64(blob); sendMessage({ type: "download-webm", base64, topLevel: true, }); } catch (error) { sendMessage({ type: "ffmpeg-error", error: JSON.stringify(error), }); } } }; useEffect(() => { const handler = (event) => onMessage(event.data); window.addEventListener("message", handler); return () => window.removeEventListener("message", handler); }, []); return (
); }; export default Sandbox; ================================================ FILE: src/pages/Editor/index.html ================================================ Screenity - Video editor
================================================ FILE: src/pages/Editor/index.jsx ================================================ import React from "react"; import { createRoot } from "react-dom/client"; import Sandbox from "./Sandbox"; // Find the container to render into const container = window.document.querySelector("#app-container"); if (container) { const root = createRoot(container); root.render(); } // Hot Module Replacement if (module.hot) { module.hot.accept(); } ================================================ FILE: src/pages/Editor/utils/addAudioToVideo.js ================================================ async function addAudioToVideo( ffmpeg, videoBlob, audioBlob, videoDuration, audioVolume = 1.0, replaceAudio = false ) { const videoData = new Uint8Array(await videoBlob.arrayBuffer()); const audioData = new Uint8Array(await audioBlob.arrayBuffer()); // Set the input video and audio file names ffmpeg.FS("writeFile", "input-video.mp4", videoData); ffmpeg.FS("writeFile", "input-audio.mp3", audioData); // Set the output video file name const outputFileName = "output-with-audio.mp4"; // Build FFmpeg command for merging video and audio with volume adjustment let ffmpegCommand = [ "-i", "input-video.mp4", "-i", "input-audio.mp3", "-filter_complex", `[0:a]volume=1[a];[1:a]volume=${audioVolume}[b];[a][b]amix=inputs=2:duration=first:dropout_transition=2[v]`, "-map", "0:v", // Map the original video stream "-map", "[v]", // Map the merged audio "-c:v", "copy", "-c:a", "aac", "-strict", "experimental", "-shortest", outputFileName, ]; if (replaceAudio) { // Remove the original audio stream and replace it with the audio from the audio blob ffmpegCommand = [ "-i", "input-video.mp4", "-i", "input-audio.mp3", "-filter_complex", "[0:a]volume=0[a];[1:a]volume=1[b];[a][b]amix=inputs=2:duration=first:dropout_transition=2[v]", "-map", "0:v", // Map the original video stream "-map", "[v]", // Map the merged audio "-c:v", "copy", "-c:a", "aac", "-strict", "experimental", "-shortest", outputFileName, ]; } // Run FFmpeg to merge video and audio with volume adjustment await ffmpeg.run(...ffmpegCommand); // Get the merged video data const data = ffmpeg.FS("readFile", outputFileName); // Create a Blob from the merged video data const videoWithAudioBlob = new Blob([data.buffer], { type: "video/mp4" }); // Return the video with merged audio Blob return videoWithAudioBlob; } export default addAudioToVideo; ================================================ FILE: src/pages/Editor/utils/base64toBlob.js ================================================ function base64ToUint8Array(base64) { const dataURLRegex = /^data:.+;base64,/; if (dataURLRegex.test(base64)) { base64 = base64.replace(dataURLRegex, ""); } const binary_string = window.atob(base64); const len = binary_string.length; const bytes = new Uint8Array(len); for (let i = 0; i < len; i++) { bytes[i] = binary_string.charCodeAt(i); } return bytes; } async function base64ToBlob(ffmpeg, base64) { const input = base64ToUint8Array(base64); ffmpeg.FS("writeFile", "input.webm", input); await ffmpeg.run( "-i", "input.webm", "-max_muxing_queue_size", "512", "-preset", "superfast", "-threads", "0", "-r", "30", "-tune", "fastdecode", "output.mp4" ); const data = ffmpeg.FS("readFile", "output.mp4"); const videoBlob = new Blob([data.buffer], { type: "video/mp4" }); return videoBlob; } export default base64ToBlob; ================================================ FILE: src/pages/Editor/utils/blobToArrayBuffer.js ================================================ async function blobToArrayBuffer(blob) { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onloadend = () => { if (reader.result instanceof ArrayBuffer) { resolve(reader.result); } else { reject(new Error("Failed to convert Blob to ArrayBuffer")); } }; reader.onerror = reject; reader.readAsArrayBuffer(blob); }); } export default blobToArrayBuffer; ================================================ FILE: src/pages/Editor/utils/cropVideo.js ================================================ async function cropVideo(ffmpeg, videoBlob, cropParameters) { const videoData = new Uint8Array(await videoBlob.arrayBuffer()); // Set the input video file name ffmpeg.FS("writeFile", "input.mp4", videoData); // Set the output video file name const outputFileName = "output-cropped.mp4"; // Build FFmpeg command for cropping const ffmpegCommand = [ "-i", "input.mp4", "-vf", `crop=${cropParameters.width}:${cropParameters.height}:${cropParameters.x}:${cropParameters.y}`, "-c:a", "copy", "-preset", "superfast", "-threads", "0", "-r", "30", "-tune", "fastdecode", outputFileName, ]; // Run FFmpeg to crop the video await ffmpeg.run(...ffmpegCommand); // Get the cropped video data const data = ffmpeg.FS("readFile", outputFileName); // Create a Blob from the cropped video data const croppedVideoBlob = new Blob([data.buffer], { type: "video/mp4" }); // Return the cropped video Blob return croppedVideoBlob; } export default cropVideo; ================================================ FILE: src/pages/Editor/utils/cutVideo.js ================================================ async function cutVideo(ffmpeg, videoBlob, start, end, cut, duration, encode) { const videoData = new Uint8Array(await videoBlob.arrayBuffer()); // Set the input video file name ffmpeg.FS("writeFile", "input.mp4", videoData); // Set the output video file name const outputFileName = cut ? "output-cut.mp4" : "output-trimmed.mp4"; let encodeOptions = [ "-c:v", "copy", "-c:a", "copy", "-reset_timestamps", "1", ]; if (encode) { encodeOptions = [ "-preset", "superfast", "-threads", "0", "-r", "30", "-tune", "fastdecode", ]; } if (cut) { if (start > 0 && end < duration) { await ffmpeg.run( "-ss", "0", "-i", "input.mp4", "-to", start.toString(), ...encodeOptions, "part1.mp4" ); // Then, cut the video from the end time to the end await ffmpeg.run( "-ss", end.toString(), "-i", "input.mp4", "-to", duration.toString(), ...encodeOptions, "part2.mp4" ); // Create a text file with the list of input videos ffmpeg.FS("writeFile", "input.txt", "file 'part1.mp4'\nfile 'part2.mp4'"); // Concatenate the two remaining parts await ffmpeg.run( "-f", "concat", "-safe", "0", "-i", "input.txt", "-c", "copy", outputFileName ); // Get the edited video data const data = ffmpeg.FS("readFile", outputFileName); // Create a Blob from the edited video data const editedVideoBlob = new Blob([data.buffer], { type: "video/mp4" }); // Return the edited video Blob return editedVideoBlob; } else if (start == 0 && end < duration) { await ffmpeg.run( "-ss", end.toString(), "-i", "input.mp4", "-to", duration.toString(), ...encodeOptions, outputFileName ); // Get the edited video data const data = ffmpeg.FS("readFile", outputFileName); // Create a Blob from the edited video data const editedVideoBlob = new Blob([data.buffer], { type: "video/mp4" }); // Return the edited video Blob return editedVideoBlob; } else if (start > 0 && end == duration) { await ffmpeg.run( "-ss", "0", "-i", "input.mp4", "-to", start.toString(), ...encodeOptions, outputFileName ); // Get the edited video data const data = ffmpeg.FS("readFile", outputFileName); // Create a Blob from the edited video data const editedVideoBlob = new Blob([data.buffer], { type: "video/mp4" }); // Return the edited video Blob return editedVideoBlob; } } else { await ffmpeg.run( "-ss", start.toString(), "-i", "input.mp4", "-t", (end - start).toString(), ...encodeOptions, outputFileName ); // Get the edited video data const data = ffmpeg.FS("readFile", outputFileName); // Create a Blob from the edited video data const editedVideoBlob = new Blob([data.buffer], { type: "video/mp4" }); // Return the edited video Blob return editedVideoBlob; } } export default cutVideo; ================================================ FILE: src/pages/Editor/utils/getFrame.js ================================================ /* getFrame.js */ async function getFrame(ffmpeg, videoBlob, time = 0) { const videoData = new Uint8Array(await videoBlob.arrayBuffer()); // Write video data to a file ffmpeg.FS("writeFile", "input.mp4", videoData); const outputFileName = "output.jpg"; // Use FFmpeg to extract a frame as a JPEG image await ffmpeg.run( "-i", "input.mp4", "-ss", time.toString(), "-frames:v", "1", "-preset", "superfast", "-threads", "0", "-r", "30", "-tune", "fastdecode", outputFileName ); // Read the generated frame image const frameData = ffmpeg.FS("readFile", outputFileName); // Create a Blob from the frame data const frameBlob = new Blob([frameData.buffer], { type: "image/jpeg", }); // Clean up ffmpeg.FS("unlink", "input.mp4"); ffmpeg.FS("unlink", outputFileName); return frameBlob; } export default getFrame; ================================================ FILE: src/pages/Editor/utils/hasAudio.js ================================================ const hasAudio = async (videoBlob) => { const videoElement = document.createElement("video"); videoElement.src = URL.createObjectURL(videoBlob); return new Promise(async (resolve, reject) => { try { videoElement.addEventListener("loadedmetadata", async () => { try { const mediaSource = new MediaSource(); videoElement.src = URL.createObjectURL(mediaSource); await mediaSource.addSourceBuffer(videoBlob.type); mediaSource.onsourceopen = () => { const audioContext = new AudioContext(); const source = audioContext.createMediaElementSource(videoElement); source.connect(audioContext.destination); source.onended = () => { // If audio plays and ends, it has audio tracks resolve(true); }; source.onerror = () => { // If there's an error, it doesn't have audio tracks resolve(false); }; // Start playing the video videoElement.play(); }; } catch (error) { resolve(false); // MediaSource or AudioContext not supported } }); videoElement.addEventListener("error", (error) => { reject(error); }); videoElement.load(); } catch (error) { reject(error); } finally { URL.revokeObjectURL(videoElement.src); } }); }; export default hasAudio; ================================================ FILE: src/pages/Editor/utils/muteVideo.js ================================================ async function muteVideo(ffmpeg, videoBlob, start, end) { try { const videoData = new Uint8Array(await videoBlob.arrayBuffer()); // Set the input video file name ffmpeg.FS("writeFile", "input.mp4", videoData); // Set the output video file name const outputFileName = "output.mp4"; // Mute the audio in the specified time range await ffmpeg.run( "-i", "input.mp4", "-af", `volume='if(between(t,${start},${end}),0,1)':eval=frame`, "-c:v", "copy", "-c:a", "aac", outputFileName ); // Get the edited video data const data = ffmpeg.FS("readFile", outputFileName); // Create a Blob from the edited video data const editedVideoBlob = new Blob([data.buffer], { type: "video/mp4" }); // Return the edited video Blob return editedVideoBlob; } catch (error) { console.error("Error muting video:", error); return null; } } export default muteVideo; ================================================ FILE: src/pages/Editor/utils/reencodeVideo.js ================================================ async function reencodeVideo(ffmpeg, blob) { const videoData = new Uint8Array(await blob.arrayBuffer()); const outputFileName = "output.mp4"; ffmpeg.FS("writeFile", "input.mp4", videoData); await ffmpeg.run( "-i", "input.mp4", "-preset", "superfast", "-threads", "0", "-r", "30", "-tune", "fastdecode", outputFileName ); const data = ffmpeg.FS("readFile", outputFileName); const editedVideoBlob = new Blob([data.buffer], { type: "video/mp4", }); return editedVideoBlob; } export default reencodeVideo; ================================================ FILE: src/pages/Editor/utils/toGIF.js ================================================ async function toGIF(ffmpeg, blob) { const videoData = new Uint8Array(await blob.arrayBuffer()); const outputFileName = "output.gif"; ffmpeg.FS("writeFile", "input.mp4", videoData); await ffmpeg.run( "-i", "input.mp4", "-preset", "superfast", "-threads", "0", "-r", "30", "-tune", "fastdecode", outputFileName ); const data = ffmpeg.FS("readFile", outputFileName); const editedVideoBlob = new Blob([data.buffer], { type: "image/gif", }); return editedVideoBlob; } export default toGIF; ================================================ FILE: src/pages/Editor/utils/toWebM.js ================================================ async function toWebM(ffmpeg, blob) { const inputData = new Uint8Array(await blob.arrayBuffer()); ffmpeg.FS("writeFile", "input.mp4", inputData); const output = "output.webm"; await ffmpeg.run( "-i", "input.mp4", "-c:v", "libvpx", // VP8 (much faster) "-b:v", "2M", // higher bitrate = fewer CPU cycles "-crf", "10", // low compression work "-speed", "8", // EXTREMELY important for WASM "-threads", "0", "-auto-alt-ref", "0", // turn off slow filtering output ); const data = ffmpeg.FS("readFile", output); return new Blob([data.buffer], { type: "video/webm" }); } ================================================ FILE: src/pages/EditorViewer/Sandbox.jsx ================================================ import React, { useEffect, useRef } from "react"; const Sandbox = () => { const iframeRef = useRef(null); const sendMessage = (message) => { if (iframeRef.current?.contentWindow) { iframeRef.current.contentWindow.postMessage(message, "*"); } }; const onMessage = async (message) => { if (message.type === "load-ffmpeg") { sendMessage({ type: "ffmpeg-load-error", fallback: true }); } else if ( message.type === "add-audio-to-video" || message.type === "base64-to-blob" || message.type === "crop-video" || message.type === "cut-video" || message.type === "mute-video" || message.type === "reencode-video" || message.type === "to-gif" ) { sendMessage({ type: "ffmpeg-error", error: "Processing not available in viewer mode. Please use a modern browser (Chrome 94+) for editing features.", }); } }; useEffect(() => { const handler = (event) => onMessage(event.data); window.addEventListener("message", handler); return () => window.removeEventListener("message", handler); }, []); return (