Pornhub Pro-ish

Alters and improves the PH experience, see addition info

// ==UserScript==
// @name         Pornhub Pro-ish
// @namespace    https://www.reddit.com/user/Alpacinator
// @version      3.5.7
// @include      *://*.pornhub.com/*
// @grant        none
// @description  Alters and improves the PH experience, see addition info
// ==/UserScript==

(function() {
    'use strict';

    // central logging function
    function log(message) {
        console.log(`Pornhub Pro-ish: ${message}`);
    }

    // still gotta find a better place for this
    let inputFilterWords;

    // these are the toggles that are dynamically created. 
    // Only the changeEvent should be created manually.
    // the creation of the locally stored states is done automatically thanks to createToggle & getToggleState
    const toggles = [{
        label: 'Sort within playlists',
        key: 'sortWithinPlaylistsState',
        changeEvent: initializeSortWithinPlaylists,
        id: 'sortWithinPlaylistsToggle',
        defaultState: false
    }, {
        label: 'Sort videos by 🏆',
        key: 'sortByTrophyState',
        changeEvent: initializeSortByTrophy,
        id: 'sortByTrophyToggle',
        defaultState: false
    }, {
        label: 'Sort videos by duration',
        key: 'sortByDurationState',
        changeEvent: initializeSortByDuration,
        id: 'sortByDurationToggle',
        defaultState: false
    }, {
        label: 'Hide watched videos',
        key: 'hideWatchedState',
        changeEvent: hideVideos,
        id: 'hideWatchedToggle',
        defaultState: false
    }, {
        label: 'Hide paid content',
        key: 'hidePaidContentState',
        changeEvent: hideVideos,
        id: 'hidePaidContentToggle',
        defaultState: true
    }, {
        label: 'Always use English',
        key: 'redirectToEnglishState',
        changeEvent: redirectToEnglish,
        id: 'redirectToEnglishToggle',
        defaultState: true
    }, {
        label: 'Mute by default',
        key: 'muteState',
        changeEvent: initializeMuteVideos,
        id: 'muteToggle',
        defaultState: false
    }, {
        label: 'Hide cursor on video',
        key: 'cursorHideState',
        changeEvent: initializeCursorHide,
        id: 'cursorHideToggle',
        defaultState: true
    }];

    function createSideMenu() {
        log('Creating menu..');
        let menuShowState = getToggleState('menuShowState', true); // Get initial state from localStorage for the menu, default is true(show)
        const sideMenu = document.createElement('div');
        sideMenu.id = 'sideMenu'; // Assign an ID for easier reference
        sideMenu.style.position = 'fixed';
        sideMenu.style.top = '0';
        sideMenu.style.left = '0';
        sideMenu.style.padding = '60px 20px 20px 20px'; // Top padding 40px, rest 20px
        sideMenu.style.height = '100%'; // Full height for mobile view
        sideMenu.style.backgroundColor = 'rgba(0, 0, 0, 0.8)';
        sideMenu.style.zIndex = '9999';
        sideMenu.style.display = 'flex';
        sideMenu.style.flexDirection = 'column'; // Stack items vertically
        sideMenu.style.justifyContent = 'center';
        sideMenu.style.alignItems = 'left';

        // Add the toggles dynamically to the side menu
        // using const toggles and createToggle
        toggles.forEach(({
            label,
            key,
            changeEvent,
            id,
            defaultState
        }) => {
            const toggle = createToggle(label, key, changeEvent, id, defaultState);
            sideMenu.appendChild(toggle);
        });


        // create the two manual buttons
        var sortByTrophyManualButton = createButton(
            'Put 🏆 first manually',
            'black',
            sortByTrophyButton
        );
        sideMenu.appendChild(sortByTrophyManualButton);

        var sortBylengthManualButton = createButton(
            'Sort by duration',
            'black',
            sortByDurationButton
        );
        sideMenu.appendChild(sortBylengthManualButton);

        // this shows a little label above the filter input for the user
        var filterInfo = document.createElement('span');
        filterInfo.textContent = 'Words to filter out:';
        filterInfo.style.color = 'white';
        filterInfo.style.marginTop = '20px';
        filterInfo.style.paddingLeft = '20%';
        filterInfo.style.lineHeight = '20px'; // Match the height of the toggle for vertical alignment
        filterInfo.style.alignItems = 'center';
        filterInfo.style.justifyContent = 'center';
        filterInfo.style.fontSize = '14px'; // Adjust font size if needed

        sideMenu.appendChild(filterInfo);

        // create the text input for the filter
        inputFilterWords = createTextInput('inputFilterWords', 'Seperate with space or comma', updateFilterWords);
        var savedFilterWords = localStorage.getItem('savedFilterWords');
        if (savedFilterWords) {
            inputFilterWords.value = savedFilterWords; // Set the value of the input field
        }

        sideMenu.appendChild(inputFilterWords);

        // Insert the menu into the body
        document.body.insertBefore(sideMenu, document.body.firstChild);

        // Add the expand menu toggle
        createMenuToggle();

        // load the visibility state of the side menu and apply the according styling
        syncSideMenu();
    }

    // this block is responsible for hiding and showing the side menu
    // creating the toggle, and syncing the state with the saved show/hide menu state
    function createMenuToggle() {
        const symbol = document.createElement('div');
        const menuShowState = getToggleState('menuShowState');
        symbol.id = 'menuToggle';
        symbol.textContent = menuShowState ? 'Hide Menu' : 'Show Menu';
        symbol.style.position = 'fixed';
        symbol.style.left = '5px'; // Position on the right
        symbol.style.top = '5px';
        symbol.style.fontSize = '12pt';

        symbol.style.color = 'orange';
        symbol.style.cursor = 'pointer';

        symbol.style.zIndex = '10000';
        symbol.style.transition = 'all 0.3s';

        symbol.style.padding = '5px 10px';
        symbol.style.backgroundColor = 'black';
        symbol.style.border = '1px solid orange';
        symbol.style.borderRadius = '15px';

        symbol.addEventListener('click', toggleMenuShow);
        document.body.appendChild(symbol);
    }

    function showMenu() {
        const sideMenu = document.getElementById('sideMenu');
        if (sideMenu) {
            // Show menu
            sideMenu.style.visibility = 'visible';
            sideMenu.style.transition = 'opacity 0.5s ease, transform 0.5s ease, visibility 0s ease';
            sideMenu.style.opacity = '1';
            sideMenu.style.transform = 'translateX(0)';

            const menuToggle = document.getElementById('menuToggle');
            menuToggle.textContent = 'Hide Menu';
        }
    }

    function hideMenu() {
        const sideMenu = document.getElementById('sideMenu');
        if (sideMenu) {
            // Hide menu
            sideMenu.style.visibility = 'hidden';
            sideMenu.style.transition = 'opacity 0.5s ease, transform 0.5s ease, visibility 0s ease 0.5s';
            sideMenu.style.opacity = '0';
            sideMenu.style.transform = 'translateX(-100%)';

            const menuToggle = document.getElementById('menuToggle');
            menuToggle.textContent = 'Show Menu';
        }
    }

    function toggleMenuShow() {
        const sideMenu = document.getElementById('sideMenu');
        let menuShowState = getToggleState('menuShowState');
        if (sideMenu) {
            if (!menuShowState) {
                showMenu();
            } else {
                hideMenu();
            }

            menuShowState = !menuShowState;
            localStorage.setItem('menuShowState', menuShowState);

        }
    }

    function syncSideMenu() {
        let menuShowState = getToggleState('menuShowState');
        if (menuShowState) {
            showMenu();
        } else {
            hideMenu();
        }
    }
    // end block for showing/hiding menu

    // Update the toggle creation to include default states
    function createToggle(labelText, localStorageKey, changeEvent, id, defaultState = false) {
        var container = document.createElement('div');
        container.style.display = 'flex';
        container.style.alignItems = 'center';
        container.style.marginBottom = '15px'; // Add space between toggle and next element if needed

        var toggle = document.createElement('div');
        toggle.style.position = 'relative';
        toggle.style.width = '40px';
        toggle.style.height = '20px';
        toggle.style.backgroundColor = getToggleState(localStorageKey, defaultState) ? 'orange' : 'grey';
        toggle.style.borderRadius = '20px';
        toggle.style.cursor = 'pointer';
        toggle.style.transition = 'background-color 0.2s';
        toggle.id = id;

        var slider = document.createElement('div');
        slider.style.position = 'absolute';
        slider.style.left = getToggleState(localStorageKey, defaultState) ? '22px' : '2px';
        slider.style.width = '16px';
        slider.style.height = '16px';
        slider.style.backgroundColor = 'white';
        slider.style.borderRadius = '50%';
        slider.style.transition = 'left 0.2s';
        slider.style.top = '2px'; // Vertically center the slider within the toggle

        toggle.appendChild(slider);


        // this is used to save the state of the toggle to localStorage automatically
        toggle.addEventListener('click', function() {
            var currentState = getToggleState(localStorageKey, defaultState);
            localStorage.setItem(localStorageKey, !currentState);
            toggle.style.backgroundColor = !currentState ? 'orange' : 'grey';
            slider.style.left = !currentState ? '22px' : '2px';
            changeEvent();
        });

        var span = document.createElement('span');
        span.textContent = labelText;
        span.style.color = 'white';
        span.style.marginLeft = '15px'; // Space between toggle and text
        span.style.lineHeight = '20px'; // Match the height of the toggle for vertical alignment
        span.style.fontSize = '14px'; // Adjust font size if needed
        span.style.paddingTop = '3px'; // Adjust font size if needed

        container.appendChild(toggle);
        container.appendChild(span);

        return container;
    }

    // this checks the current state of the toggles using the ID and what they should be according to the saved values in LocalStorage
    function updateToggleStates() {
        toggles.forEach(({
            id,
            key
        }) => {
            const toggleElement = document.getElementById(id);
            const currentState = getToggleState(key);

            if (toggleElement) {
                const expectedColor = currentState ? 'orange' : 'grey';
                const slider = toggleElement.querySelector('div');

                toggleElement.style.backgroundColor = expectedColor;
                if (slider) {
                    slider.style.left = currentState ? '22px' : '2px';
                }
            }
        });
    }

    function createButton(text, bgColor, clickEvent) {
        var button = document.createElement('button');
        button.textContent = text;
        button.style.marginRight = '0px';
        button.style.marginBottom = '15px';
        button.style.padding = '5px 10px';
        button.style.backgroundColor = bgColor;
        button.style.color = 'white';
        button.style.border = '1px solid white';
        button.style.borderRadius = '10px';
        button.style.cursor = 'pointer';
        button.style.transition = 'background-color 0.3s, color 0.3s, border-color 0.3s';

        // JavaScript hover effects
        button.addEventListener('mouseover', function() {
            button.style.color = 'orange';
            button.style.borderColor = 'orange';
        });
        button.addEventListener('mouseout', function() {
            button.style.color = 'white';
            button.style.borderColor = 'white';
        });

        button.addEventListener('click', function() {
            button.style.backgroundColor = 'orange';
            setTimeout(function() {
                button.style.backgroundColor = bgColor;
            }, 100);

            clickEvent();
        });

        return button;
    }
    // dynamically create text inputs function for the menu
    function createTextInput(id, placeholder, inputEvent) {
        var input = document.createElement('input');
        input.type = 'text';
        input.id = id;
        input.placeholder = placeholder;
        input.style.marginRight = '0px';
        input.style.marginBottom = '15px';
        input.style.border = '1px solid grey';
        input.style.borderRadius = '15px';
        input.style.padding = '5px 10px';
        input.addEventListener('input', inputEvent);
        return input;
    }

    // this is used to fetch the state of a setting in LocalStorage
    function getToggleState(localStorageKey, defaultState = false) {
        const savedState = localStorage.getItem(localStorageKey);
        if (savedState === null) { // No saved state exists
            localStorage.setItem(localStorageKey, defaultState); // Set the default state in localStorage
            return defaultState;
        }
        return savedState === 'true';
    }

    // manage the filter words to hide certain videos if the title includes certain words
    function updateFilterWords() {
        var inputFilterWordsValue = inputFilterWords.value;
        localStorage.setItem('savedFilterWords', inputFilterWordsValue);
        hideVideos();
    }

    // Check whatever the state of the toggles is and execute the functions accordingly
    function initializeMuteVideos() {
        var muteVideosEnabled = getToggleState('muteState');
        if (muteVideosEnabled) {
            clickMuteButton();
        }
    }

    function initializeSortByTrophy() {
        var reorderItemsEnabled = getToggleState('sortByTrophyState');
        if (reorderItemsEnabled) {
            sortByTrophy();
        }
    }

    function initializeSortByDuration() {
        var sortByDurationEnabled = getToggleState('sortByDurationState');
        if (sortByDurationEnabled) {
            sortByDuration();
        }

    }

    // this one will only be run when the toggle is flipped because it consists of two other initializes that are already called upon mutation/page load
    function initializeSortWithinPlaylists() {
        initializeSortByTrophy();
        initializeSortByDuration();
    }

    function initializeCursorHide() {
        var cursorHideStateEnabled = getToggleState('cursorHideState');
        if (cursorHideStateEnabled) {
            cursorHide(true);
        } else {
            cursorHide(false);
        }
    }

    function hideVideos() {
        var hideWatchedEnabled = getToggleState('hideWatchedState');
        var hidePaidContentEnabled = getToggleState('hidePaidContentState');

        // Use findVideoULs to get the <ul> elements
        const ulElements = findVideoULs(true);

        // Get all <li> elements within the found <ul> elements
        const liElements = ulElements.flatMap(ul => Array.from(ul.querySelectorAll('li')));

        const savedFilterWords = localStorage.getItem('savedFilterWords') || ''; // Handle null case
        const filterWords = savedFilterWords ? savedFilterWords.split(/,\s*|\s+/).map(word => word.trim().toLowerCase()) : [];
        const hidePaidContentState = getToggleState('hidePaidContentState');
        const hideWatchedState = getToggleState('hideWatchedState');

        liElements.forEach(function(li) {
            const liTextContent = li.textContent.toLowerCase();

            // Check if the current <li> has a watched indicator, excluding those with the 'hidden' class
            const watchedDiv = li.querySelector('.watchedVideoText, .watchedVideo');
            const isWatched = watchedDiv && !(watchedDiv.classList.contains('watchedVideoText') && watchedDiv.classList.contains('hidden'));

            // Selectors for paid/premium/private content
            const priceSpan = li.querySelector('span.price');
            const premiumIcon = li.querySelector('.premiumicon'); // Corrected selector
            const aHrefJavaVoid = li.querySelector('a');
            const privateOverlay = li.querySelector('img.privateOverlay');

            // Determine if the video should be hidden
            const shouldHideByFilterWords = filterWords.length > 0 && filterWords.some(word => word.length >= 3 && liTextContent.includes(word));
            const shouldHideWatchedVideos = hideWatchedState && isWatched; // Corrected condition to check isWatched
            const shouldHidePaidContent = hidePaidContentState && (priceSpan || premiumIcon || privateOverlay || (aHrefJavaVoid && aHrefJavaVoid.getAttribute('href') === 'javascript:void(0)'));

            // Hide or show the <li> element based on conditions
            li.style.display = (shouldHideByFilterWords || shouldHideWatchedVideos || shouldHidePaidContent) ? 'none' : 'block';
        });
    }




    // Utility function to find all <ul> elements with class 'videos',
    // excluding those with specified IDs
    // used for sortByDuration and sortByTrophy
    function findVideoULs(sortWithinPlaylistsEnabled = true) {
        // Define IDs for <ul> elements that should be excluded when sorting within playlists is disabled
        const playlistIdsToExclude = ['videoPlaylist', 'videoPlaylistSection', 'playListSection'];

        // Get all <ul> elements with class 'videos' or 'videoList'
        const allVideoULs = document.querySelectorAll('ul.videos, ul.videoList');

        // Convert NodeList to an Array
        const videoULArray = Array.from(allVideoULs);

        // Filter <ul> elements: Exclude playlists if sorting within playlists is disabled
        const filteredULs = videoULArray.filter(ul => {
            // Exclude IDs only when sortWithinPlaylistsEnabled is false
            const shouldExclude = !sortWithinPlaylistsEnabled && playlistIdsToExclude.includes(ul.id);
            // Include the <ul> if it should not be excluded
            return !shouldExclude;
        });

        return filteredULs;
    }


    function sortByDuration() {
        function parseDuration(durationString) {
            const [minutes, seconds] = durationString.split(':').map(Number);
            return minutes * 60 + seconds;
        }

        function sortLiElementsByDurationDesc(ulElement) {
            const liElements = Array.from(ulElement.querySelectorAll('li'))
                .filter(li => li.querySelector('.duration'));

            if (liElements.length === 0) {
                console.warn(`No elements with duration found in the list container.`);
                return;
            }

            liElements.sort((a, b) => {
                const durationA = parseDuration(a.querySelector('.duration').textContent);
                const durationB = parseDuration(b.querySelector('.duration').textContent);
                return durationB - durationA;
            });

            liElements.forEach(li => {
                ulElement.appendChild(li);
            });
        }

        // Use forced disable toggle or check the actual toggle state
        const sortWithinPlaylistsEnabled = getToggleState('sortWithinPlaylistsState');
        const ulsToSort = findVideoULs(sortWithinPlaylistsEnabled);

        if (ulsToSort.length === 0) {
            console.warn('No <ul> elements with class "videos" found.');
            return;
        }

        ulsToSort.forEach(ulElement => {
            sortLiElementsByDurationDesc(ulElement);
        });
    }

    function sortByTrophy() {
        function sortLiElementsByTrophy(ulElement) {
            const liElements = Array.from(ulElement.querySelectorAll('li'));
            const freePremiumVideoItems = [];
            const otherItems = [];

            liElements.forEach(li => {
                const childSpan = li.querySelector('span.award-icon');
                if (childSpan) {
                    freePremiumVideoItems.push(li);
                } else {
                    otherItems.push(li);
                }
            });

            const reorderedLiElements = freePremiumVideoItems.concat(otherItems);
            reorderedLiElements.forEach(li => {
                ulElement.appendChild(li);
            });
        }

        // Use forced disable toggle or check the actual toggle state
        const sortWithinPlaylistsEnabled = getToggleState('sortWithinPlaylistsState');
        const ulsToSort = findVideoULs(sortWithinPlaylistsEnabled);

        if (ulsToSort.length === 0) {
            console.warn('No <ul> elements with class "videos" found.');
            return;
        }

        ulsToSort.forEach(ulElement => {
            sortLiElementsByTrophy(ulElement);
        });
    }

    function sortByDurationButton() {
        function parseDuration(durationString) {
            const [minutes, seconds] = durationString.split(':').map(Number);
            return minutes * 60 + seconds;
        }

        function sortLiElementsByDurationDesc(ulElement) {
            const liElements = Array.from(ulElement.querySelectorAll('li'))
                .filter(li => li.querySelector('.duration'));

            if (liElements.length === 0) {
                console.warn(`No elements with duration found in the list container.`);
                return;
            }

            liElements.sort((a, b) => {
                const durationA = parseDuration(a.querySelector('.duration').textContent);
                const durationB = parseDuration(b.querySelector('.duration').textContent);
                return durationB - durationA;
            });

            liElements.forEach(li => {
                ulElement.appendChild(li);
            });
        }

        const ulsToSort = findVideoULs(true);

        if (ulsToSort.length === 0) {
            console.warn('No <ul> elements with class "videos" found.');
            return;
        }

        ulsToSort.forEach(ulElement => {
            sortLiElementsByDurationDesc(ulElement);
        });
    }

    function sortByTrophyButton() {
        function sortLiElementsByTrophy(ulElement) {
            const liElements = Array.from(ulElement.querySelectorAll('li'));
            const freePremiumVideoItems = [];
            const otherItems = [];

            liElements.forEach(li => {
                const childSpan = li.querySelector('span.award-icon');
                if (childSpan) {
                    freePremiumVideoItems.push(li);
                } else {
                    otherItems.push(li);
                }
            });

            const reorderedLiElements = freePremiumVideoItems.concat(otherItems);
            reorderedLiElements.forEach(li => {
                ulElement.appendChild(li);
            });
        }

        // Use forced disable toggle or check the actual toggle state
        const ulsToSort = findVideoULs(true);

        if (ulsToSort.length === 0) {
            console.warn('No <ul> elements with class "videos" found.');
            return;
        }

        ulsToSort.forEach(ulElement => {
            sortLiElementsByTrophy(ulElement);
        });
    }



    // this simulates a click on the mute button in the videoplayer
    function clickMuteButton() {
        function simulateMouse(element, eventType) {
            const event = new Event(eventType, {
                view: window,
                bubbles: true,
                cancelable: true
            });
            element.dispatchEvent(event);
        }
        const muteDivs = document.querySelectorAll('div.mgp_volume > div[data-text="Mute"]');

        if (muteDivs.length === 0) return;

        muteDivs.forEach((div, index) => {
            simulateMouse(div, 'mouseover');
            simulateMouse(div, 'focus');
            simulateMouse(div, 'mousedown');
            simulateMouse(div, 'mouseup');
            simulateMouse(div, 'click');

            if (div) {
                const event = new MouseEvent('mouseover', {
                    view: window,
                    bubbles: true
                });
                div.dispatchEvent(event);
            }
        });

        log(`${muteDivs.length} video elements were muted.`);
    }

    // Hide the load more buttons, all the items should already show
    function hideElements() {
        log('Hiding elements..');

        // Hide the welcoming messages regarding different countries and the store
        if (document.getElementById('countryRedirectMessage')) {
            document.getElementById('countryRedirectMessage').style.display = 'none';
        }

        if (document.getElementById('welcome')) {
            document.getElementById('welcome').style.display = 'none';
        }

        // This will hide the ugly long empty blocks under videos while going through pages
        // Select all divs with the class 'pornInLangWrapper'
        const divs = document.querySelectorAll('div.pornInLangWrapper');

        // Loop through each div and hide it
        divs.forEach(div => {
            div.style.display = 'none';
        });


        // remove load more buttons 
        // under the video
        if (document.getElementById('loadMoreRelatedVideosCenter')) {
            document.getElementById('loadMoreRelatedVideosCenter').style.display = 'none';
        }
        // on the right side of the video
        const recommendedLoadMoreElements = document.querySelectorAll('[data-label="recommended_load_more"]');
        recommendedLoadMoreElements.forEach(element => {
            element.style.display = 'none';
        });

    }

    // Usage
    // cursorHide(true);  // To enable hiding the cursor
    // cursorHide(false); // To disable hiding the cursor
    // this makes use of the class that is added to the videowrapper when a video is playing, it is removed when paused
    function cursorHide(enable) {
        // Check if the style element already exists
        let existingStyle = document.getElementById('cursor-hide-style');

        if (enable) {
            if (!existingStyle) {
                log("cursorHide is enabled but the style doesn't exist yet, creating cursorHide style..");
                // Create and append the style element if it doesn't exist
                const style = document.createElement('style');
                style.id = 'cursor-hide-style';
                style.textContent = `
                    /* Define the cursor hiding animation */
                    @keyframes hideCursor {
                        0% {
                            cursor: default;
                        }
                        99% {
                            cursor: default;
                        }
                        100% {
                            cursor: none;
                        }
                    }

                    /* Apply the animation on hover */
                    .mgp_playingState {
                        animation: none;
                    }

                    .mgp_playingState:hover {
                        animation: hideCursor 3s forwards;
                    }
                `;

                document.head.appendChild(style);
                log('cursorHide style added.');
            }
        } else {
            if (existingStyle) {
                log("cursorHide is disabled but the style still exists, removing cursorHide style..");
                // Remove the style element if it exists
                existingStyle.remove();
                log('cursorHide style removed.');
            }
        }
    }

    // Change the language of the website to English if that is not the case
    // Since this script sometimes uses words to function this is still neccesary
    function redirectToEnglish() {
	    var redirectToEnglishStateEnabled = getToggleState('redirectToEnglishState');
        if (redirectToEnglishStateEnabled) {
			
			const isNotEnglish = () => {
				const languageDropdownLI = document.querySelector('li.languageDropdown');
				return !languageDropdownLI || (languageDropdownLI.querySelector('span.networkTab') && languageDropdownLI.querySelector('span.networkTab').textContent.trim().toLowerCase() !== 'en');
			};

			const findEnLink = () => {
				const enLanguageOptionLI = document.querySelector('li[data-lang="en"]');
				if (!enLanguageOptionLI) return null;

				const enLanguageLink = enLanguageOptionLI.querySelector('a.networkTab');
				return !enLanguageLink ? console.error('Anchor element with class "networkTab" not found within the specified <li> to check the current language.') : enLanguageLink;
			};

			function checkAndClick() {
				if (isNotEnglish()) {
					const enLink = findEnLink();

					// If we've already tried clicking and it didn't work, stop trying
					if (!enLink) return log('No English link to click. Giving up.');

					enLink.click();
					log('Clicked the link:', enLink);
				} else {
					log('Current language is already English. No action needed.');
				}
			}

			setTimeout(checkAndClick, 1000); // Adjust delay if necessary
		}
    }


    // this adds a transparant red square over items if you press delete 
    // to show you which you have deleted when editing a playlist  
    function addRedOverlay(element) {
        var overlay = document.createElement('div');
        overlay.style.position = 'absolute';
        overlay.style.top = '0';
        overlay.style.left = '0';
        overlay.style.width = '100%';
        overlay.style.height = '100%';
        overlay.style.backgroundColor = 'red';
        overlay.style.opacity = '0.5';
        overlay.style.pointerEvents = 'none';

        var parentLi = element.closest('li');
        if (parentLi) {
            parentLi.style.position = 'relative';
            parentLi.appendChild(overlay);
        }
    }

    // when editing playlists, whenever the delete button on a video is clicked, this will create a transparent red square around the video
    document.addEventListener('click', function(event) {
        if (event.target && event.target.matches('button[onclick="deleteFromPlaylist(this);"]')) {
            addRedOverlay(event.target);
            log('Added red overlay to the playlist item');
        }
    });

    // run updateToggleStates when changing to a tab (using visibilityState to see if it is active/visible) so the state will be synced:
    document.addEventListener('visibilitychange', function() {
        if (document.visibilityState === 'visible') {
            log('Tab is visible, updating toggle states..');
            // update toggle states
            updateToggleStates();
            // this should trigger a DOM change, so runOnMutation should run and things should update

            // make sure the menu visibility matches the state
            syncSideMenu();

            // change language to English
            redirectToEnglish();

            // check if the cursor should be hidden
            initializeCursorHide();
        }
    });

    // this will be run when the observer spots mutations
    function initializeEverything() {
        initializeSortByTrophy();
        initializeSortByDuration();
        initializeMuteVideos();
        hideVideos();
    }

    // run this when the page is fully loaded
    window.onload = function() {
        // hide 'Load More' buttons and the like
        hideElements();

        // create the menu
        createSideMenu();

        // change lang to english
        redirectToEnglish();

        // check if the cursor should be hidden
        initializeCursorHide();

        // no need to initialize everything manually, that will trigger on page load (runOnMutation will take care of the rest)
        // but this makes the page ready to go as soon as it is done loading, so we will keep it for now
        initializeEverything();

        // keep checking for mutations using an observer
        function runOnMutation() {
            clearTimeout(runOnMutation.timeout);
            runOnMutation.timeout = setTimeout(() => {
                initializeEverything();
            }, 500);
        }

        const observer = new MutationObserver(function(mutations) {
            runOnMutation();
        });

        observer.observe(document.body, {
            childList: true,
            subtree: true
        });

    };

})();