Sven 1 anno fa
parent
commit
6b0801edcc
1 ha cambiato i file con 292 aggiunte e 186 eliminazioni
  1. 292 186
      dist/BetterYTM.user.js

+ 292 - 186
dist/BetterYTM.user.js

@@ -8,7 +8,7 @@
 // @license         MIT
 // @author          Sv443
 // @copyright       Sv443 (https://github.com/Sv443)
-// @icon            https://raw.githubusercontent.com/Sv443/BetterYTM/develop/assets/icon/icon.png
+// @icon            https://raw.githubusercontent.com/Sv443/BetterYTM/develop/assets/icon/icon_48.png
 // @match           https://music.youtube.com/*
 // @match           https://www.youtube.com/*
 // @run-at          document-start
@@ -20,12 +20,13 @@
 // @grant           GM.getResourceUrl
 // @grant           unsafeWindow
 // @noframes
-// @resource        icon https://raw.githubusercontent.com/Sv443/BetterYTM/develop/assets/icon/icon.png
+// @resource        icon https://raw.githubusercontent.com/Sv443/BetterYTM/develop/assets/icon/icon_48.png
 // @resource        close https://raw.githubusercontent.com/Sv443/BetterYTM/develop/assets/close.png
 // @resource        delete https://raw.githubusercontent.com/Sv443/BetterYTM/develop/assets/delete.svg
 // @resource        error https://raw.githubusercontent.com/Sv443/BetterYTM/develop/assets/error.svg
 // @resource        lyrics https://raw.githubusercontent.com/Sv443/BetterYTM/develop/assets/lyrics.svg
 // @resource        spinner https://raw.githubusercontent.com/Sv443/BetterYTM/develop/assets/spinner.svg
+// @resource        arrow_down https://raw.githubusercontent.com/Sv443/BetterYTM/develop/assets/arrow_down.svg
 // @resource        github https://raw.githubusercontent.com/Sv443/BetterYTM/develop/assets/external/github.png
 // @resource        greasyfork https://raw.githubusercontent.com/Sv443/BetterYTM/develop/assets/external/greasyfork.png
 // ==/UserScript==
@@ -480,7 +481,7 @@ const scriptInfo = {
     name: GM.info.script.name,
     version: GM.info.script.version,
     namespace: GM.info.script.namespace,
-    lastCommit: "291b209", // assert as generic string instead of literal
+    lastCommit: "59434ac", // assert as generic string instead of literal
 };
 
 
@@ -624,6 +625,7 @@ __webpack_require__.r(__webpack_exports__);
 /* harmony export */   addMediaCtrlLyricsBtn: function() { return /* reexport safe */ _lyrics__WEBPACK_IMPORTED_MODULE_3__.addMediaCtrlLyricsBtn; },
 /* harmony export */   addMenu: function() { return /* reexport safe */ _menu_menu_old__WEBPACK_IMPORTED_MODULE_5__.addMenu; },
 /* harmony export */   addWatermark: function() { return /* reexport safe */ _layout__WEBPACK_IMPORTED_MODULE_2__.addWatermark; },
+/* harmony export */   categoryNames: function() { return /* binding */ categoryNames; },
 /* harmony export */   closeMenu: function() { return /* reexport safe */ _menu_menu_old__WEBPACK_IMPORTED_MODULE_5__.closeMenu; },
 /* harmony export */   createLyricsBtn: function() { return /* reexport safe */ _lyrics__WEBPACK_IMPORTED_MODULE_3__.createLyricsBtn; },
 /* harmony export */   disableBeforeUnload: function() { return /* reexport safe */ _input__WEBPACK_IMPORTED_MODULE_1__.disableBeforeUnload; },
@@ -658,45 +660,14 @@ __webpack_require__.r(__webpack_exports__);
 
 
 
+/** Mapping of feature category identifiers to readable strings */
+const categoryNames = {
+    input: "Input",
+    layout: "Layout",
+    lyrics: "Lyrics",
+};
 /** Contains all possible features with their default values and other config */
 const featInfo = {
-    //#SECTION input
-    arrowKeySupport: {
-        desc: "Arrow keys to skip forwards and backwards by 10 seconds",
-        type: "toggle",
-        category: "input",
-        default: true,
-    },
-    switchBetweenSites: {
-        desc: "Add F9 as a hotkey to switch between the YT and YTM sites on a video / song",
-        type: "toggle",
-        category: "input",
-        default: true,
-    },
-    switchSitesHotkey: {
-        desc: "TODO(v1.1): Which hotkey needs to be pressed to switch sites?",
-        type: "hotkey",
-        category: "input",
-        default: {
-            key: "F9",
-            shift: false,
-            ctrl: false,
-            meta: false,
-        },
-        hidden: true,
-    },
-    disableBeforeUnloadPopup: {
-        desc: "Disable the confirmation popup that sometimes appears when trying to leave the site",
-        type: "toggle",
-        category: "input",
-        default: false,
-    },
-    anchorImprovements: {
-        desc: "TODO:FIXME: Add link elements all over the page so things can be opened in a new tab easier",
-        type: "toggle",
-        category: "input",
-        default: true,
-    },
     //#SECTION layout
     removeUpgradeTab: {
         desc: "Remove the Upgrade / Premium tab",
@@ -705,7 +676,7 @@ const featInfo = {
         default: true,
     },
     volumeSliderLabel: {
-        desc: "Add a percentage label to the volume slider",
+        desc: "Add a percentage label next to the volume slider",
         type: "toggle",
         category: "layout",
         default: true,
@@ -743,7 +714,7 @@ const featInfo = {
         default: true,
     },
     closeToastsTimeout: {
-        desc: "After how long to close permanent notifications - 0 to only close them manually (default behavior)",
+        desc: "After how many seconds to close permanent notifications - 0 to only close them manually (default behavior)",
         type: "number",
         category: "layout",
         min: 0,
@@ -752,6 +723,43 @@ const featInfo = {
         default: 0,
         unit: "s",
     },
+    //#SECTION input
+    arrowKeySupport: {
+        desc: "Use arrow keys to skip forwards and backwards by 10 seconds",
+        type: "toggle",
+        category: "input",
+        default: true,
+    },
+    switchBetweenSites: {
+        desc: "Add F9 as a hotkey to switch between the YT and YTM sites on a video / song",
+        type: "toggle",
+        category: "input",
+        default: true,
+    },
+    switchSitesHotkey: {
+        desc: "TODO(v1.1): Which hotkey needs to be pressed to switch sites?",
+        type: "hotkey",
+        category: "input",
+        default: {
+            key: "F9",
+            shift: false,
+            ctrl: false,
+            meta: false,
+        },
+        hidden: true,
+    },
+    disableBeforeUnloadPopup: {
+        desc: "Disable the confirmation popup that sometimes appears when trying to leave the site",
+        type: "toggle",
+        category: "input",
+        default: false,
+    },
+    anchorImprovements: {
+        desc: "TODO:FIXME: Add link elements all over the page so things can be opened in a new tab easier",
+        type: "toggle",
+        category: "input",
+        default: true,
+    },
     //#SECTION lyrics
     geniusLyrics: {
         desc: "Add a button to the media controls of the currently playing song to open its lyrics on genius.com",
@@ -780,7 +788,6 @@ __webpack_require__.r(__webpack_exports__);
 /* harmony export */ });
 /* harmony import */ var _sv443_network_userutils__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @sv443-network/userutils */ "./node_modules/@sv443-network/userutils/dist/index.mjs");
 /* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../utils */ "./src/utils.ts");
-/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../config */ "./src/config.ts");
 var __awaiter = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {
     function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
     return new (P || (P = Promise))(function (resolve, reject) {
@@ -792,7 +799,6 @@ var __awaiter = (undefined && undefined.__awaiter) || function (thisArg, _argume
 };
 
 
-
 //#MARKER arrow key skip
 function initArrowKeySkip() {
     document.addEventListener("keydown", onKeyDown);
@@ -937,8 +943,6 @@ function initBeforeUnloadHook() {
         };
         // @ts-ignore
     })(window.__proto__.addEventListener);
-    if ((0,_config__WEBPACK_IMPORTED_MODULE_2__.getFeatures)().disableBeforeUnloadPopup)
-        disableBeforeUnload();
 }
 
 
@@ -1023,7 +1027,7 @@ function addWatermark() {
     });
     const logoElem = document.querySelector("#left-content");
     (0,_sv443_network_userutils__WEBPACK_IMPORTED_MODULE_0__.insertAfter)(logoElem, watermark);
-    (0,_utils__WEBPACK_IMPORTED_MODULE_2__.log)("Added watermark element", watermark);
+    (0,_utils__WEBPACK_IMPORTED_MODULE_2__.log)("Added watermark element");
 }
 /** Turns the regular `<img>`-based logo into inline SVG to be able to animate and modify parts of it */
 function improveLogo() {
@@ -1099,7 +1103,7 @@ function addConfigMenuOption(container) {
         cfgOptItemElem.appendChild(cfgOptTextElem);
         cfgOptElem.appendChild(cfgOptItemElem);
         container.appendChild(cfgOptElem);
-        (0,_utils__WEBPACK_IMPORTED_MODULE_2__.log)("Added BYTM-Configuration button to menu popover", cfgOptElem);
+        (0,_utils__WEBPACK_IMPORTED_MODULE_2__.log)("Added BYTM-Configuration button to menu popover");
     });
 }
 //#MARKER remove upgrade tab
@@ -1164,7 +1168,6 @@ function addVolumeSliderLabel(sliderElem, sliderCont) {
     (0,_sv443_network_userutils__WEBPACK_IMPORTED_MODULE_0__.onSelector)("#bytm-vol-slider-cont", {
         listener: (volumeCont) => {
             volumeCont.appendChild(labelElem);
-            (0,_utils__WEBPACK_IMPORTED_MODULE_2__.log)("Added volume slider label", labelElem);
         },
     });
     let lastSliderVal = Number(sliderElem.value);
@@ -1487,24 +1490,22 @@ function initAutoCloseToasts() {
         (0,_sv443_network_userutils__WEBPACK_IMPORTED_MODULE_0__.onSelector)("tp-yt-paper-toast#toast", {
             all: true,
             continuous: true,
-            listener: (toastElems) => {
+            listener: (toastElems) => __awaiter(this, void 0, void 0, function* () {
+                var _a;
                 for (const toastElem of toastElems) {
                     if (!toastElem.hasAttribute("allow-click-through"))
                         continue;
                     if (toastElem.classList.contains("bytm-closing"))
                         continue;
                     toastElem.classList.add("bytm-closing");
-                    setTimeout(() => {
-                        toastElem.classList.remove("paper-toast-open");
-                        // wait for the transition to finish
-                        setTimeout(() => {
-                            var _a;
-                            toastElem.style.display = "none";
-                            (0,_utils__WEBPACK_IMPORTED_MODULE_2__.log)(`Automatically closed toast '${(_a = toastElem.querySelector("#text-container yt-formatted-string")) === null || _a === void 0 ? void 0 : _a.innerText}' after ${closeTimeout + animTimeout}ms`);
-                        }, animTimeout);
-                    }, closeTimeout);
+                    yield (0,_sv443_network_userutils__WEBPACK_IMPORTED_MODULE_0__.pauseFor)(closeTimeout);
+                    toastElem.classList.remove("paper-toast-open");
+                    (0,_utils__WEBPACK_IMPORTED_MODULE_2__.log)(`Automatically closed toast '${(_a = toastElem.querySelector("#text-container yt-formatted-string")) === null || _a === void 0 ? void 0 : _a.innerText}' after ${features.closeToastsTimeout * 1000}ms`);
+                    // wait for the transition to finish
+                    yield (0,_sv443_network_userutils__WEBPACK_IMPORTED_MODULE_0__.pauseFor)(animTimeout);
+                    toastElem.style.display = "none";
                 }
-            },
+            }),
         });
         (0,_utils__WEBPACK_IMPORTED_MODULE_2__.log)("Initialized automatic toast closing");
     }
@@ -1910,6 +1911,7 @@ var __awaiter = (undefined && undefined.__awaiter) || function (thisArg, _argume
 
 //#MARKER create menu elements
 let isMenuOpen = false;
+let scrollIndicatorShown = true;
 /**
  * Adds an element to open the BetterYTM menu
  * @deprecated to be replaced with new menu - see https://github.com/Sv443/BetterYTM/issues/23
@@ -1941,26 +1943,23 @@ function addMenu() {
         menuContainer.style.justifyContent = "space-between";
         //#SECTION title bar
         const titleCont = document.createElement("div");
-        titleCont.style.padding = "8px 20px 15px 20px";
-        titleCont.style.display = "flex";
-        titleCont.style.justifyContent = "space-between";
         titleCont.id = "bytm-menu-titlecont";
         const titleElem = document.createElement("h2");
         titleElem.id = "bytm-menu-title";
-        titleElem.classList.add("bytm-no-select");
-        titleElem.innerText = `${_constants__WEBPACK_IMPORTED_MODULE_2__.scriptInfo.name} - Configuration`;
+        titleElem.role = "heading";
+        titleElem.ariaLevel = "1";
+        titleElem.innerText = `${_constants__WEBPACK_IMPORTED_MODULE_2__.scriptInfo.name} Configuration`;
         const linksCont = document.createElement("div");
         linksCont.id = "bytm-menu-linkscont";
         const addLink = (imgSrc, href, title) => {
             const anchorElem = document.createElement("a");
-            anchorElem.className = "bytm-menu-link";
+            anchorElem.className = "bytm-menu-link bytm-no-select";
             anchorElem.rel = "noopener noreferrer";
             anchorElem.target = "_blank";
             anchorElem.href = href;
             anchorElem.title = title;
-            anchorElem.style.marginLeft = "10px";
             const imgElem = document.createElement("img");
-            imgElem.className = "bytm-menu-img bytm-no-select";
+            imgElem.className = "bytm-menu-img";
             imgElem.src = imgSrc;
             imgElem.style.width = "32px";
             imgElem.style.height = "32px";
@@ -1994,119 +1993,160 @@ function addMenu() {
             featConf[key] = newVal;
             yield (0,_config__WEBPACK_IMPORTED_MODULE_1__.saveFeatures)(featConf);
         }));
-        const features = (0,_config__WEBPACK_IMPORTED_MODULE_1__.getFeatures)();
-        for (const key in features) {
-            const ftInfo = _index__WEBPACK_IMPORTED_MODULE_3__.featInfo[key];
-            // @ts-ignore
-            if (!ftInfo || ftInfo.hidden === true)
-                continue;
-            const { desc, type, default: ftDefault } = ftInfo;
-            // @ts-ignore
-            const step = (_a = ftInfo === null || ftInfo === void 0 ? void 0 : ftInfo.step) !== null && _a !== void 0 ? _a : undefined;
-            const val = features[key];
-            const initialVal = (_b = val !== null && val !== void 0 ? val : ftDefault) !== null && _b !== void 0 ? _b : undefined;
-            const ftConfElem = document.createElement("div");
-            ftConfElem.id = `betterytm-ftconf-${key}`;
-            ftConfElem.style.display = "flex";
-            ftConfElem.style.flexDirection = "row";
-            ftConfElem.style.justifyContent = "space-between";
-            ftConfElem.style.padding = "8px 20px";
-            {
-                const textElem = document.createElement("span");
-                textElem.style.display = "inline-block";
-                textElem.style.fontSize = "15px";
-                textElem.innerText = desc;
-                ftConfElem.appendChild(textElem);
-            }
-            {
-                let inputType = "text";
-                switch (type) {
-                    case "toggle":
-                        inputType = "checkbox";
-                        break;
-                    case "slider":
-                        inputType = "range";
-                        break;
-                    case "number":
-                        inputType = "number";
-                        break;
-                }
-                const inputElemId = `betterytm-ftconf-${key}-input`;
-                const ctrlElem = document.createElement("span");
-                ctrlElem.style.display = "inline-flex";
-                ctrlElem.style.alignItems = "center";
-                ctrlElem.style.whiteSpace = "nowrap";
-                const inputElem = document.createElement("input");
-                inputElem.id = inputElemId;
-                inputElem.type = inputType;
-                if (type === "toggle")
-                    inputElem.style.marginLeft = "5px";
-                if (typeof initialVal !== "undefined")
-                    inputElem.value = String(initialVal);
-                if (type === "number" && step)
-                    inputElem.step = step;
+        const featureCfg = (0,_config__WEBPACK_IMPORTED_MODULE_1__.getFeatures)();
+        const featureCfgWithCategories = Object.entries(_index__WEBPACK_IMPORTED_MODULE_3__.featInfo)
+            .reduce((acc, [key, { category }]) => {
+            if (!acc[category])
+                acc[category] = {};
+            acc[category][key] = featureCfg[key];
+            return acc;
+        }, {});
+        for (const category in featureCfgWithCategories) {
+            const featObj = featureCfgWithCategories[category];
+            const catHeaderElem = document.createElement("h3");
+            catHeaderElem.classList.add("bytm-ftconf-category-header");
+            catHeaderElem.role = "heading";
+            catHeaderElem.ariaLevel = "2";
+            catHeaderElem.innerText = `${_index__WEBPACK_IMPORTED_MODULE_3__.categoryNames[category]}:`;
+            featuresCont.appendChild(catHeaderElem);
+            for (const featKey in featObj) {
+                const ftInfo = _index__WEBPACK_IMPORTED_MODULE_3__.featInfo[featKey];
                 // @ts-ignore
-                if (typeof ftInfo.min !== "undefined" && ftInfo.max !== "undefined") {
-                    // @ts-ignore
-                    inputElem.min = ftInfo.min;
-                    // @ts-ignore
-                    inputElem.max = ftInfo.max;
-                }
-                if (type === "toggle" && typeof initialVal !== "undefined")
-                    inputElem.checked = Boolean(initialVal);
+                if (!ftInfo || ftInfo.hidden === true)
+                    continue;
+                const { desc, type, default: ftDefault } = ftInfo;
                 // @ts-ignore
-                const unitTxt = typeof ftInfo.unit === "string" ? " " + ftInfo.unit : "";
-                const fmtVal = (v) => String(v).trim();
-                const toggleLabelText = (toggled) => toggled ? "On" : "Off";
-                let labelElem;
-                if (type === "slider") {
-                    labelElem = document.createElement("label");
-                    labelElem.classList.add("bytm-ftconf-label");
-                    labelElem.style.marginRight = "20px";
-                    labelElem.style.fontSize = "16px";
-                    labelElem.htmlFor = inputElemId;
-                    labelElem.innerText = fmtVal(initialVal) + unitTxt;
-                    inputElem.addEventListener("input", () => {
-                        if (labelElem)
-                            labelElem.innerText = fmtVal(parseInt(inputElem.value)) + unitTxt;
-                    });
+                const step = (_a = ftInfo === null || ftInfo === void 0 ? void 0 : ftInfo.step) !== null && _a !== void 0 ? _a : undefined;
+                const val = featureCfg[featKey];
+                const initialVal = (_b = val !== null && val !== void 0 ? val : ftDefault) !== null && _b !== void 0 ? _b : undefined;
+                const ftConfElem = document.createElement("div");
+                ftConfElem.classList.add("betterytm-ftconf-item");
+                {
+                    const textElem = document.createElement("span");
+                    textElem.style.display = "inline-block";
+                    textElem.style.fontSize = "15px";
+                    textElem.innerText = desc;
+                    ftConfElem.appendChild(textElem);
                 }
-                else if (type === "toggle") {
-                    labelElem = document.createElement("label");
-                    labelElem.classList.add("bytm-ftconf-label");
-                    labelElem.style.paddingLeft = "10px";
-                    labelElem.style.paddingRight = "5px";
-                    labelElem.style.fontSize = "16px";
-                    labelElem.htmlFor = inputElemId;
-                    labelElem.innerText = toggleLabelText(Boolean(initialVal)) + unitTxt;
+                {
+                    let inputType = "text";
+                    switch (type) {
+                        case "toggle":
+                            inputType = "checkbox";
+                            break;
+                        case "slider":
+                            inputType = "range";
+                            break;
+                        case "number":
+                            inputType = "number";
+                            break;
+                    }
+                    const inputElemId = `bytm-ftconf-${featKey}-input`;
+                    const ctrlElem = document.createElement("span");
+                    ctrlElem.style.display = "inline-flex";
+                    ctrlElem.style.alignItems = "center";
+                    ctrlElem.style.whiteSpace = "nowrap";
+                    const inputElem = document.createElement("input");
+                    inputElem.classList.add("bytm-ftconf-input");
+                    inputElem.id = inputElemId;
+                    inputElem.type = inputType;
+                    if (type === "toggle")
+                        inputElem.style.marginLeft = "5px";
+                    if (typeof initialVal !== "undefined")
+                        inputElem.value = String(initialVal);
+                    if (type === "number" && step)
+                        inputElem.step = step;
+                    // @ts-ignore
+                    if (typeof ftInfo.min !== "undefined" && ftInfo.max !== "undefined") {
+                        // @ts-ignore
+                        inputElem.min = ftInfo.min;
+                        // @ts-ignore
+                        inputElem.max = ftInfo.max;
+                    }
+                    if (type === "toggle" && typeof initialVal !== "undefined")
+                        inputElem.checked = Boolean(initialVal);
+                    // @ts-ignore
+                    const unitTxt = typeof ftInfo.unit === "string" ? " " + ftInfo.unit : "";
+                    const fmtVal = (v) => String(v).trim();
+                    const toggleLabelText = (toggled) => toggled ? "On" : "Off";
+                    let labelElem;
+                    if (type === "slider") {
+                        labelElem = document.createElement("label");
+                        labelElem.classList.add("bytm-ftconf-label");
+                        labelElem.style.marginRight = "10px";
+                        labelElem.style.fontSize = "16px";
+                        labelElem.htmlFor = inputElemId;
+                        labelElem.innerText = fmtVal(initialVal) + unitTxt;
+                        inputElem.addEventListener("input", () => {
+                            if (labelElem)
+                                labelElem.innerText = fmtVal(parseInt(inputElem.value)) + unitTxt;
+                        });
+                    }
+                    else if (type === "toggle") {
+                        labelElem = document.createElement("label");
+                        labelElem.classList.add("bytm-ftconf-label");
+                        labelElem.style.paddingLeft = "10px";
+                        labelElem.style.paddingRight = "5px";
+                        labelElem.style.fontSize = "16px";
+                        labelElem.htmlFor = inputElemId;
+                        labelElem.innerText = toggleLabelText(Boolean(initialVal)) + unitTxt;
+                        inputElem.addEventListener("input", () => {
+                            if (labelElem)
+                                labelElem.innerText = toggleLabelText(inputElem.checked) + unitTxt;
+                        });
+                    }
                     inputElem.addEventListener("input", () => {
-                        if (labelElem)
-                            labelElem.innerText = toggleLabelText(inputElem.checked) + unitTxt;
+                        let v = Number(String(inputElem.value).trim());
+                        if (isNaN(v))
+                            v = Number(inputElem.value);
+                        if (typeof initialVal !== "undefined")
+                            confChanged(featKey, initialVal, (type !== "toggle" ? v : inputElem.checked));
                     });
+                    labelElem && ctrlElem.appendChild(labelElem);
+                    ctrlElem.appendChild(inputElem);
+                    ftConfElem.appendChild(ctrlElem);
                 }
-                inputElem.addEventListener("input", () => {
-                    let v = Number(String(inputElem.value).trim());
-                    if (isNaN(v))
-                        v = Number(inputElem.value);
-                    if (typeof initialVal !== "undefined")
-                        confChanged(key, initialVal, (type !== "toggle" ? v : inputElem.checked));
-                });
-                labelElem && ctrlElem.appendChild(labelElem);
-                ctrlElem.appendChild(inputElem);
-                ftConfElem.appendChild(ctrlElem);
+                featuresCont.appendChild(ftConfElem);
             }
-            featuresCont.appendChild(ftConfElem);
         }
+        //#SECTION scroll indicator
+        const scrollIndicator = document.createElement("img");
+        scrollIndicator.id = "bytm-menu-scroll-indicator";
+        scrollIndicator.role = "button";
+        scrollIndicator.title = "Click to scroll to the bottom";
+        scrollIndicator.src = "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiBoZWlnaHQ9IjQ4IiB2aWV3Qm94PSIwIC05NjAgOTYwIDk2MCIgd2lkdGg9IjQ4Ij4KICA8cGF0aCBmaWxsPSIjZmZmZmZmIiBkPSJNNDU3LjMwOC03NzkuOTk5djUxMy42OTJMMjEyLjAwMS01MTEuOTk5bC0zMiAzMS45OTlMNDgwLTE4MC4wMDEgNzc5Ljk5OS00ODBsLTMyLTMxLjk5OS0yNDUuMzA3IDI0NS42OTJ2LTUxMy42OTJoLTQ1LjM4NFoiLz4KPC9zdmc+";
+        //#DEBUG scrollIndicatorElem.src = await getResourceUrl("arrow_down");
+        featuresCont.appendChild(scrollIndicator);
+        scrollIndicator.addEventListener("click", () => {
+            const bottomAnchor = document.querySelector("#bytm-menu-bottom-anchor");
+            bottomAnchor === null || bottomAnchor === void 0 ? void 0 : bottomAnchor.scrollIntoView({
+                behavior: "smooth",
+            });
+        });
+        featuresCont.addEventListener("scroll", (evt) => __awaiter(this, void 0, void 0, function* () {
+            var _c, _d;
+            const scrollPos = (_d = (_c = evt.target) === null || _c === void 0 ? void 0 : _c.scrollTop) !== null && _d !== void 0 ? _d : 0;
+            const scrollIndicator = document.querySelector("#bytm-menu-scroll-indicator");
+            if (!scrollIndicator)
+                return;
+            if (scrollPos > 10 && scrollIndicatorShown) {
+                scrollIndicatorShown = false;
+                scrollIndicator.classList.add("hidden");
+                yield (0,_sv443_network_userutils__WEBPACK_IMPORTED_MODULE_0__.pauseFor)(200);
+                scrollIndicator.style.visibility = "hidden";
+            }
+            else if (scrollPos <= 10 && !scrollIndicatorShown) {
+                scrollIndicatorShown = true;
+                scrollIndicator.style.visibility = "initial";
+                scrollIndicator.classList.remove("hidden");
+            }
+        }));
+        const bottomAnchor = document.createElement("div");
+        bottomAnchor.id = "bytm-menu-bottom-anchor";
+        featuresCont.appendChild(bottomAnchor);
         //#SECTION footer
         const footerCont = document.createElement("div");
         footerCont.id = "bytm-menu-footer-cont";
-        footerCont.style.display = "flex";
-        footerCont.style.flexDirection = "row";
-        footerCont.style.justifyContent = "space-between";
-        footerCont.style.padding = "20px 20px 10px 20px";
-        footerCont.style.marginTop = "10px";
-        footerCont.style.position = "sticky";
-        footerCont.style.bottom = "0";
         const footerElem = document.createElement("div");
         footerElem.id = "bytm-menu-footer";
         footerElem.style.fontSize = "17px";
@@ -2139,8 +2179,7 @@ function addMenu() {
         versionCont.style.display = "flex";
         versionCont.style.justifyContent = "space-around";
         versionCont.style.fontSize = "1.15em";
-        versionCont.style.marginTop = "10px";
-        versionCont.style.marginBottom = "5px";
+        versionCont.style.marginTop = "5px";
         const versionElem = document.createElement("span");
         versionElem.id = "bytm-menu-version";
         versionElem.innerText = `v${_constants__WEBPACK_IMPORTED_MODULE_2__.scriptInfo.version}`;
@@ -2150,7 +2189,7 @@ function addMenu() {
         menuContainer.appendChild(versionCont);
         backgroundElem.appendChild(menuContainer);
         document.body.appendChild(backgroundElem);
-        (0,_utils__WEBPACK_IMPORTED_MODULE_4__.log)("Added menu element:", backgroundElem);
+        (0,_utils__WEBPACK_IMPORTED_MODULE_4__.log)("Added menu element");
     });
 }
 //#MARKER utilities
@@ -2159,7 +2198,7 @@ function closeMenu(e) {
         return;
     isMenuOpen = false;
     (e === null || e === void 0 ? void 0 : e.bubbles) && e.stopPropagation();
-    document.body.classList.remove("bytm-disable-scroll");
+    document.body.removeAttribute("no-y-overflow");
     const menuBg = document.querySelector("#bytm-menu-bg");
     menuBg.style.visibility = "hidden";
     menuBg.style.display = "none";
@@ -2169,10 +2208,18 @@ function openMenu() {
     if (isMenuOpen)
         return;
     isMenuOpen = true;
-    document.body.classList.add("bytm-disable-scroll");
+    document.body.setAttribute("no-y-overflow", "");
     const menuBg = document.querySelector("#bytm-menu-bg");
     menuBg.style.visibility = "visible";
     menuBg.style.display = "block";
+    const featuresCont = document.querySelector("#bytm-menu-opts");
+    const scrollIndicator = document.querySelector("#bytm-menu-scroll-indicator");
+    // disable scroll indicator if container doesn't scroll
+    if (featuresCont && scrollIndicator && !(0,_utils__WEBPACK_IMPORTED_MODULE_4__.isScrollable)(featuresCont).vertical) {
+        scrollIndicatorShown = false;
+        scrollIndicator.classList.add("hidden");
+        scrollIndicator.style.visibility = "hidden";
+    }
 }
 
 
@@ -2192,6 +2239,7 @@ __webpack_require__.r(__webpack_exports__);
 /* harmony export */   getResourceUrl: function() { return /* binding */ getResourceUrl; },
 /* harmony export */   getVideoTime: function() { return /* binding */ getVideoTime; },
 /* harmony export */   info: function() { return /* binding */ info; },
+/* harmony export */   isScrollable: function() { return /* binding */ isScrollable; },
 /* harmony export */   log: function() { return /* binding */ log; },
 /* harmony export */   setLogLevel: function() { return /* binding */ setLogLevel; },
 /* harmony export */   warn: function() { return /* binding */ warn; }
@@ -2366,10 +2414,9 @@ function ytForceShowVideoTime() {
  * @throws Throws if script runs on an unexpected website
  */
 function getDomain() {
-    const { hostname } = new URL(location.href);
-    if (hostname.includes("music.youtube"))
+    if (location.hostname.match(/^music\.youtube/))
         return "ytm";
-    else if (hostname.includes("youtube"))
+    else if (location.hostname.match(/youtube\./))
         return "yt";
     else
         throw new Error("BetterYTM is running on an unexpected website. Please don't tamper with the @match directives in the userscript header.");
@@ -2378,6 +2425,14 @@ function getDomain() {
 function getResourceUrl(name) {
     return GM.getResourceUrl(name);
 }
+/** Checks if an element is scrollable in the horizontal and vertical directions */
+function isScrollable(element) {
+    const { overflowX, overflowY } = getComputedStyle(element);
+    return {
+        vertical: (overflowY === "scroll" || overflowY === "auto") && element.scrollHeight > element.clientHeight,
+        horizontal: (overflowX === "scroll" || overflowX === "auto") && element.scrollWidth > element.clientWidth,
+    };
+}
 
 
 /***/ }),
@@ -2540,6 +2595,8 @@ function init() {
         }
         try {
             (0,_features_index__WEBPACK_IMPORTED_MODULE_5__.preInitLayout)(yield (0,_config__WEBPACK_IMPORTED_MODULE_1__.initConfig)());
+            if ((0,_config__WEBPACK_IMPORTED_MODULE_1__.getFeatures)().disableBeforeUnloadPopup)
+                (0,_features_index__WEBPACK_IMPORTED_MODULE_5__.disableBeforeUnload)();
         }
         catch (err) {
             (0,_utils__WEBPACK_IMPORTED_MODULE_3__.error)("Error while initializing ConfigManager:", err);
@@ -2562,7 +2619,9 @@ function onDomLoad() {
   !*** css ./node_modules/css-loader/dist/cjs.js!./src/features/menu/menu_old.css ***!
   \**********************************************************************************/
 :root {
+  --bytm-menu-width: calc(min(70vw, 1000px));
   --bytm-menu-bg: #212121;
+  --bytm-menu-bg-highlight: #111111;
 }
 
 #bytm-menu-bg {
@@ -2579,9 +2638,9 @@ function onDomLoad() {
 #bytm-menu {
   display: inline-block;
   position: fixed;
-  width: 50vw;
+  width: var(--bytm-menu-width);
   height: auto;
-  left: 25vw;
+  left: calc((100vw - var(--bytm-menu-width)) / 2);
   top: 10vh;
   z-index: 16;
   padding: 10px 25px;
@@ -2590,27 +2649,34 @@ function onDomLoad() {
 }
 
 #bytm-menu-opts {
+  position: relative;
   max-height: 70vh;
   overflow: auto;
 }
 
 #bytm-menu-titlecont {
   display: flex;
+  justify-content: space-between;
+  padding: 8px 20px 15px 20px;
+  margin-bottom: 5px;
+  background-color: var(--bytm-menu-bg);
 }
 
 #bytm-menu-title {
   font-size: 20px;
-  margin-top: 5px;
-  margin-bottom: 8px;
+  margin: 5px 0;
 }
 
 #bytm-menu-linkscont {
   display: flex;
+  align-items: center;
 }
 
 .bytm-menu-link {
+  display: inline-flex;
+  align-items: center;
+  margin-left: 10px;
   cursor: pointer;
-  display: inline-block;
 }
 
 #bytm-menu-close {
@@ -2618,16 +2684,56 @@ function onDomLoad() {
 }
 
 #bytm-menu-footer-cont {
+  display: flex;
+  flex-direction: row;
+  justify-content: space-between;
+  margin-top: 10px;
+  padding: 32px 20px 8px 20px;
+  position: sticky;
+  bottom: 0;
   background: var(--bytm-menu-bg);
-  background: linear-gradient(to bottom, rgba(0, 0, 0, 0) 0%, var(--bytm-menu-bg) 18%, var(--bytm-menu-bg) 100%);
+  background: linear-gradient(to bottom, rgba(0, 0, 0, 0) 0%, var(--bytm-menu-bg) 25%, var(--bytm-menu-bg) 100%);
+}
+
+#bytm-menu-scroll-indicator {
+  position: sticky;
+  bottom: 60px;
+  left: 50%;
+  transform: translateX(-50%);
+  width: 32px;
+  height: 32px;
+  padding: 6px;
+  z-index: 1000;
+  background-color: var(--bytm-menu-bg-highlight);
+  border-radius: 50%;
+  cursor: pointer;
+}
+
+.bytm-ftconf-category-header {
+  font-size: 18px;
+  margin-top: 32px;
+  margin-bottom: 8px;
+  padding: 0 20px;
+}
+
+.bytm-ftconf-category-header:first-of-type {
+  margin-top: 0;
+}
+
+.betterytm-ftconf-item {
+  display: flex;
+  flex-direction: row;
+  justify-content: space-between;
+  align-items: center;
+  padding: 8px 20px;
 }
 
 .bytm-ftconf-label {
   user-select: none;
 }
 
-body.bytm-disable-scroll {
-  overflow-y: hidden !important;
+.bytm-ftconf-input[type=number] {
+  width: 100px;
 }
 
 /*!***************************************************************************!*\