Просмотр исходного кода

feat: much better a11y for cfg menu & hotkey input

Sv443 9 месяцев назад
Родитель
Сommit
d2f9f12450

+ 44 - 16
assets/translations/README.md

@@ -16,15 +16,15 @@ To submit or edit a translation, please follow [this guide](../../contributing.m
 ### Translation progress:
 |   | Locale | Translated keys | Based on |
 | :----: | ------ | --------------- | :------: |
-| ─ | [`en_US`](./en_US.json) | 276 (default locale) |  |
-| ‼️ | [`de_DE`](./de_DE.json) | `205/276` (74.3%) | ─ |
-| ─ | [`en_UK`](./en_UK.json) | `276` (100%) | `en_US` |
-| ‼️ | [`es_ES`](./es_ES.json) | `205/276` (74.3%) | ─ |
-| ‼️ | [`fr_FR`](./fr_FR.json) | `205/276` (74.3%) | ─ |
-| ‼️ | [`hi_IN`](./hi_IN.json) | `205/276` (74.3%) | ─ |
-| ‼️ | [`ja_JA`](./ja_JA.json) | `205/276` (74.3%) | ─ |
-| ‼️ | [`pt_BR`](./pt_BR.json) | `205/276` (74.3%) | ─ |
-| ‼️ | [`zh_CN`](./zh_CN.json) | `205/276` (74.3%) | ─ |
+| ─ | [`en_US`](./en_US.json) | 278 (default locale) |  |
+| ‼️ | [`de_DE`](./de_DE.json) | `203/278` (73%) | ─ |
+| ─ | [`en_UK`](./en_UK.json) | `278` (100%) | `en_US` |
+| ‼️ | [`es_ES`](./es_ES.json) | `203/278` (73%) | ─ |
+| ‼️ | [`fr_FR`](./fr_FR.json) | `203/278` (73%) | ─ |
+| ‼️ | [`hi_IN`](./hi_IN.json) | `203/278` (73%) | ─ |
+| ‼️ | [`ja_JA`](./ja_JA.json) | `203/278` (73%) | ─ |
+| ‼️ | [`pt_BR`](./pt_BR.json) | `203/278` (73%) | ─ |
+| ‼️ | [`zh_CN`](./zh_CN.json) | `203/278` (73%) | ─ |
 
 <sub>
 ✅ - Fully translated
@@ -45,7 +45,7 @@ This means to figure out which keys are untranslated, you will need to manually
 
 ### Missing keys:
 
-<details><summary><code>de_DE</code> - 71 missing keys <i>(click to show)</i></summary><br>
+<details><summary><code>de_DE</code> - 75 missing keys <i>(click to show)</i></summary><br>
 
 | Key | English text |
 | --- | ------------ |
@@ -68,6 +68,10 @@ This means to figure out which keys are untranslated, you will need to manually
 | `remove_entry` | `Remove this entry` |
 | `edit_entry` | `Edit this entry` |
 | `open_lyrics_search_prompt` | `Enter the song title and artist to search for the lyrics:` |
+| `hotkey_input_click_to_change_tooltip` | `%1 - Currently set to: %2 - Enter any key combination to change. Note: some screen readers might block certain key combinations.` |
+| `hotkey_input_click_to_reset_tooltip` | `Reset to the last saved key combination` |
+| `hotkey_key_none` | `No hotkey selected` |
+| `feature_help_button_tooltip` | `Click to get more information about the following feature: "%1"` |
 | `auto_like_channels_dialog_title` | `Auto-liked Channels` |
 | `auto_like_channels_dialog_desc` | `Here you can see what channels you have set to auto-like and you can edit, enable, disable and remove them.\nYou can also manually create entries, though it's easier to just visit the channel page and click the button there.` |
 | `auto_like` | `Auto-like` |
@@ -123,7 +127,7 @@ This means to figure out which keys are untranslated, you will need to manually
 
 <br></details>
 
-<details><summary><code>es_ES</code> - 71 missing keys <i>(click to show)</i></summary><br>
+<details><summary><code>es_ES</code> - 75 missing keys <i>(click to show)</i></summary><br>
 
 | Key | English text |
 | --- | ------------ |
@@ -146,6 +150,10 @@ This means to figure out which keys are untranslated, you will need to manually
 | `remove_entry` | `Remove this entry` |
 | `edit_entry` | `Edit this entry` |
 | `open_lyrics_search_prompt` | `Enter the song title and artist to search for the lyrics:` |
+| `hotkey_input_click_to_change_tooltip` | `%1 - Currently set to: %2 - Enter any key combination to change. Note: some screen readers might block certain key combinations.` |
+| `hotkey_input_click_to_reset_tooltip` | `Reset to the last saved key combination` |
+| `hotkey_key_none` | `No hotkey selected` |
+| `feature_help_button_tooltip` | `Click to get more information about the following feature: "%1"` |
 | `auto_like_channels_dialog_title` | `Auto-liked Channels` |
 | `auto_like_channels_dialog_desc` | `Here you can see what channels you have set to auto-like and you can edit, enable, disable and remove them.\nYou can also manually create entries, though it's easier to just visit the channel page and click the button there.` |
 | `auto_like` | `Auto-like` |
@@ -201,7 +209,7 @@ This means to figure out which keys are untranslated, you will need to manually
 
 <br></details>
 
-<details><summary><code>fr_FR</code> - 71 missing keys <i>(click to show)</i></summary><br>
+<details><summary><code>fr_FR</code> - 75 missing keys <i>(click to show)</i></summary><br>
 
 | Key | English text |
 | --- | ------------ |
@@ -224,6 +232,10 @@ This means to figure out which keys are untranslated, you will need to manually
 | `remove_entry` | `Remove this entry` |
 | `edit_entry` | `Edit this entry` |
 | `open_lyrics_search_prompt` | `Enter the song title and artist to search for the lyrics:` |
+| `hotkey_input_click_to_change_tooltip` | `%1 - Currently set to: %2 - Enter any key combination to change. Note: some screen readers might block certain key combinations.` |
+| `hotkey_input_click_to_reset_tooltip` | `Reset to the last saved key combination` |
+| `hotkey_key_none` | `No hotkey selected` |
+| `feature_help_button_tooltip` | `Click to get more information about the following feature: "%1"` |
 | `auto_like_channels_dialog_title` | `Auto-liked Channels` |
 | `auto_like_channels_dialog_desc` | `Here you can see what channels you have set to auto-like and you can edit, enable, disable and remove them.\nYou can also manually create entries, though it's easier to just visit the channel page and click the button there.` |
 | `auto_like` | `Auto-like` |
@@ -279,7 +291,7 @@ This means to figure out which keys are untranslated, you will need to manually
 
 <br></details>
 
-<details><summary><code>hi_IN</code> - 71 missing keys <i>(click to show)</i></summary><br>
+<details><summary><code>hi_IN</code> - 75 missing keys <i>(click to show)</i></summary><br>
 
 | Key | English text |
 | --- | ------------ |
@@ -302,6 +314,10 @@ This means to figure out which keys are untranslated, you will need to manually
 | `remove_entry` | `Remove this entry` |
 | `edit_entry` | `Edit this entry` |
 | `open_lyrics_search_prompt` | `Enter the song title and artist to search for the lyrics:` |
+| `hotkey_input_click_to_change_tooltip` | `%1 - Currently set to: %2 - Enter any key combination to change. Note: some screen readers might block certain key combinations.` |
+| `hotkey_input_click_to_reset_tooltip` | `Reset to the last saved key combination` |
+| `hotkey_key_none` | `No hotkey selected` |
+| `feature_help_button_tooltip` | `Click to get more information about the following feature: "%1"` |
 | `auto_like_channels_dialog_title` | `Auto-liked Channels` |
 | `auto_like_channels_dialog_desc` | `Here you can see what channels you have set to auto-like and you can edit, enable, disable and remove them.\nYou can also manually create entries, though it's easier to just visit the channel page and click the button there.` |
 | `auto_like` | `Auto-like` |
@@ -357,7 +373,7 @@ This means to figure out which keys are untranslated, you will need to manually
 
 <br></details>
 
-<details><summary><code>ja_JA</code> - 71 missing keys <i>(click to show)</i></summary><br>
+<details><summary><code>ja_JA</code> - 75 missing keys <i>(click to show)</i></summary><br>
 
 | Key | English text |
 | --- | ------------ |
@@ -380,6 +396,10 @@ This means to figure out which keys are untranslated, you will need to manually
 | `remove_entry` | `Remove this entry` |
 | `edit_entry` | `Edit this entry` |
 | `open_lyrics_search_prompt` | `Enter the song title and artist to search for the lyrics:` |
+| `hotkey_input_click_to_change_tooltip` | `%1 - Currently set to: %2 - Enter any key combination to change. Note: some screen readers might block certain key combinations.` |
+| `hotkey_input_click_to_reset_tooltip` | `Reset to the last saved key combination` |
+| `hotkey_key_none` | `No hotkey selected` |
+| `feature_help_button_tooltip` | `Click to get more information about the following feature: "%1"` |
 | `auto_like_channels_dialog_title` | `Auto-liked Channels` |
 | `auto_like_channels_dialog_desc` | `Here you can see what channels you have set to auto-like and you can edit, enable, disable and remove them.\nYou can also manually create entries, though it's easier to just visit the channel page and click the button there.` |
 | `auto_like` | `Auto-like` |
@@ -435,7 +455,7 @@ This means to figure out which keys are untranslated, you will need to manually
 
 <br></details>
 
-<details><summary><code>pt_BR</code> - 71 missing keys <i>(click to show)</i></summary><br>
+<details><summary><code>pt_BR</code> - 75 missing keys <i>(click to show)</i></summary><br>
 
 | Key | English text |
 | --- | ------------ |
@@ -458,6 +478,10 @@ This means to figure out which keys are untranslated, you will need to manually
 | `remove_entry` | `Remove this entry` |
 | `edit_entry` | `Edit this entry` |
 | `open_lyrics_search_prompt` | `Enter the song title and artist to search for the lyrics:` |
+| `hotkey_input_click_to_change_tooltip` | `%1 - Currently set to: %2 - Enter any key combination to change. Note: some screen readers might block certain key combinations.` |
+| `hotkey_input_click_to_reset_tooltip` | `Reset to the last saved key combination` |
+| `hotkey_key_none` | `No hotkey selected` |
+| `feature_help_button_tooltip` | `Click to get more information about the following feature: "%1"` |
 | `auto_like_channels_dialog_title` | `Auto-liked Channels` |
 | `auto_like_channels_dialog_desc` | `Here you can see what channels you have set to auto-like and you can edit, enable, disable and remove them.\nYou can also manually create entries, though it's easier to just visit the channel page and click the button there.` |
 | `auto_like` | `Auto-like` |
@@ -513,7 +537,7 @@ This means to figure out which keys are untranslated, you will need to manually
 
 <br></details>
 
-<details><summary><code>zh_CN</code> - 71 missing keys <i>(click to show)</i></summary><br>
+<details><summary><code>zh_CN</code> - 75 missing keys <i>(click to show)</i></summary><br>
 
 | Key | English text |
 | --- | ------------ |
@@ -536,6 +560,10 @@ This means to figure out which keys are untranslated, you will need to manually
 | `remove_entry` | `Remove this entry` |
 | `edit_entry` | `Edit this entry` |
 | `open_lyrics_search_prompt` | `Enter the song title and artist to search for the lyrics:` |
+| `hotkey_input_click_to_change_tooltip` | `%1 - Currently set to: %2 - Enter any key combination to change. Note: some screen readers might block certain key combinations.` |
+| `hotkey_input_click_to_reset_tooltip` | `Reset to the last saved key combination` |
+| `hotkey_key_none` | `No hotkey selected` |
+| `feature_help_button_tooltip` | `Click to get more information about the following feature: "%1"` |
 | `auto_like_channels_dialog_title` | `Auto-liked Channels` |
 | `auto_like_channels_dialog_desc` | `Here you can see what channels you have set to auto-like and you can edit, enable, disable and remove them.\nYou can also manually create entries, though it's easier to just visit the channel page and click the button there.` |
 | `auto_like` | `Auto-like` |

+ 0 - 2
assets/translations/de_DE.json

@@ -76,7 +76,6 @@
     "lyrics_cache_changed_clear_confirm": "Du hast Einstellungen geändert, die die Daten im Songtext-Cache beeinflussen, was Songtext-Suchen kaputt macht.\nMöchtest du den Cache jetzt löschen?",
 
     "hotkey_input_click_to_change": "Zum Ändern klicken",
-    "hotkey_input_click_to_change_tooltip": "Klicke, dann drücke die gewünschte Tastenkombination",
     "hotkey_input_click_to_cancel_tooltip": "Klicke, um abzubrechen",
     "hotkey_key_ctrl": "Strg",
     "hotkey_key_shift": "Shift",
@@ -88,7 +87,6 @@
     "open_config_menu_tooltip": "Klicke, um das Einstellungsmenü zu öffnen",
     "open_changelog": "Änderungsprotokoll",
     "open_changelog_tooltip": "Klicke, um das Änderungsprotokoll zu öffnen",
-    "feature_help_button_tooltip": "Klicke, um mehr Informationen über diese Funktion zu erhalten",
     "welcome_text_line_1": "Vielen Dank für die Installation!",
     "welcome_text_line_2": "Ich hoffe, du hast genauso viel Spaß mit %1 wie ich beim Erstellen hatte 😃",
     "welcome_text_line_3": "Wenn dir %1 gefällt, hinterlasse bitte eine Bewertung auf %2GreasyFork%3 oder %4OpenUserJS%5",

+ 4 - 2
assets/translations/en_US.json

@@ -96,19 +96,21 @@
     "lyrics_cache_changed_clear_confirm": "You have changed settings that affect the data in the lyrics cache, which breaks lyrics URL lookups.\nDo you want to clear the cache now?",
 
     "hotkey_input_click_to_change": "Click to change",
-    "hotkey_input_click_to_change_tooltip": "Click, then press the desired key combination",
+    "hotkey_input_click_to_change_tooltip": "%1 - Currently set to: %2 - Enter any key combination to change. Note: some screen readers might block certain key combinations.",
     "hotkey_input_click_to_cancel_tooltip": "Click to cancel",
+    "hotkey_input_click_to_reset_tooltip": "Reset to the last saved key combination",
     "hotkey_key_ctrl": "Ctrl",
     "hotkey_key_shift": "Shift",
     "hotkey_key_mac_option": "Option",
     "hotkey_key_alt": "Alt",
+    "hotkey_key_none": "No hotkey selected",
 
     "welcome_menu_title": "Welcome to %1!",
     "config_menu": "Config Menu",
     "open_config_menu_tooltip": "Click to open the configuration menu",
     "open_changelog": "Changelog",
     "open_changelog_tooltip": "Click to open the changelog",
-    "feature_help_button_tooltip": "Click to get more information about this feature",
+    "feature_help_button_tooltip": "Click to get more information about the following feature: \"%1\"",
     "welcome_text_line_1": "Thank you for installing!",
     "welcome_text_line_2": "I hope you enjoy using %1 as much as I enjoyed making it 😃",
     "welcome_text_line_3": "If you like %1, please leave a rating on %2GreasyFork%3 or %4OpenUserJS%5",

+ 0 - 2
assets/translations/es_ES.json

@@ -76,7 +76,6 @@
     "lyrics_cache_changed_clear_confirm": "Ha cambiado la configuración que afecta los datos en la caché de letras, lo que rompe las búsquedas de URL de letras.\n¿Quieres borrar la caché ahora?",
 
     "hotkey_input_click_to_change": "Haga clic para cambiar",
-    "hotkey_input_click_to_change_tooltip": "Haga clic, luego presione la tecla de acceso rápido deseada",
     "hotkey_input_click_to_cancel_tooltip": "Haga clic para cancelar",
     "hotkey_key_ctrl": "Ctrl",
     "hotkey_key_shift": "Mayús",
@@ -88,7 +87,6 @@
     "open_config_menu_tooltip": "Haga clic para abrir el menú de configuración",
     "open_changelog": "Registro de cambios",
     "open_changelog_tooltip": "Haga clic para abrir el registro de cambios",
-    "feature_help_button_tooltip": "Haga clic para obtener más información sobre esta función",
     "welcome_text_line_1": "Gracias por instalar!",
     "welcome_text_line_2": "Espero que disfrutes usando %1 tanto como yo disfruté haciéndolo 😃",
     "welcome_text_line_3": "Si te gusta %1, por favor deja una calificación en %2GreasyFork%3 o %4OpenUserJS%5",

+ 0 - 2
assets/translations/fr_FR.json

@@ -76,7 +76,6 @@
     "lyrics_cache_changed_clear_confirm": "Vous avez modifié des paramètres qui affectent les données dans le cache des paroles, ce qui casse les recherches d'URL de paroles.\nVoulez-vous vider le cache maintenant?",
 
     "hotkey_input_click_to_change": "Cliquez pour changer",
-    "hotkey_input_click_to_change_tooltip": "Cliquez, puis appuyez sur la combinaison de touches souhaitée",
     "hotkey_input_click_to_cancel_tooltip": "Cliquez pour annuler",
     "hotkey_key_ctrl": "Ctrl",
     "hotkey_key_shift": "Maj",
@@ -88,7 +87,6 @@
     "open_config_menu_tooltip": "Cliquez pour ouvrir le menu de configuration",
     "open_changelog": "Historique des modifications",
     "open_changelog_tooltip": "Cliquez pour ouvrir l'historique des modifications",
-    "feature_help_button_tooltip": "Cliquez pour obtenir plus d'informations sur cette fonctionnalité",
     "welcome_text_line_1": "Merci d'avoir installé!",
     "welcome_text_line_2": "J'espère que vous apprécierez d'utiliser %1 autant que j'ai apprécié de le faire 😃",
     "welcome_text_line_3": "Si vous aimez %1, laissez une note sur %2GreasyFork%3 ou %4OpenUserJS%5",

+ 0 - 2
assets/translations/hi_IN.json

@@ -76,7 +76,6 @@
     "lyrics_cache_changed_clear_confirm": "आपने उन सेटिंग्स को बदल दिया है जो बोल कैश में डेटा पर प्रभाव डालते हैं, जो बोल URL खोजों को तोड़ देते हैं।\nक्या आप वाकई अब बोल कैश हटाना चाहते हैं?",
 
     "hotkey_input_click_to_change": "बदलने के लिए क्लिक करें",
-    "hotkey_input_click_to_change_tooltip": "बदलने के लिए क्लिक करें, फिर दबाएं",
     "hotkey_input_click_to_cancel_tooltip": "बदलने के लिए क्लिक करें, फिर रिकवर करें",
     "hotkey_key_ctrl": "Ctrl",
     "hotkey_key_shift": "Shift",
@@ -88,7 +87,6 @@
     "open_config_menu_tooltip": "कॉन्फ़िगरेशन मेनू खोलने के लिए क्लिक करें",
     "open_changelog": "चेंजलॉग खोलें",
     "open_changelog_tooltip": "चेंजलॉग खोलने के लिए क्लिक करें",
-    "feature_help_button_tooltip": "इस सुविधा के बारे में अधिक जानकारी प्राप्त करने के लिए क्लिक करें",
     "welcome_text_line_1": "स्थापित करने के लिए धन्यवाद!",
     "welcome_text_line_2": "मैं आशा करता हूं कि आप %1 का उपयोग करने में इतना मज़ा लेंगे जितना मैंने इसे बनाने में लिया है 😃",
     "welcome_text_line_3": "यदि आप %1 पसंद करते हैं, तो कृपया %2GreasyFork%3 या %4OpenUserJS%5 पर एक रेटिंग दें",

+ 0 - 2
assets/translations/ja_JA.json

@@ -76,7 +76,6 @@
     "lyrics_cache_changed_clear_confirm": "歌詞キャッシュに影響を与える設定を変更しました。これにより歌詞 URL の検索が壊れます。\nキャッシュをクリアしますか?",
 
     "hotkey_input_click_to_change": "クリックして変更",
-    "hotkey_input_click_to_change_tooltip": "クリックしてホットキーを変更する",
     "hotkey_input_click_to_cancel_tooltip": "クリックしてキャンセル",
     "hotkey_key_ctrl": "Ctrl",
     "hotkey_key_shift": "Shift",
@@ -88,7 +87,6 @@
     "open_config_menu_tooltip": "クリックして構成メニューを開く",
     "open_changelog": "更新履歴",
     "open_changelog_tooltip": "クリックして更新履歴を開く",
-    "feature_help_button_tooltip": "クリックしてこの機能についての詳細情報を取得する",
     "welcome_text_line_1": "ようこそ!",
     "welcome_text_line_2": "%1 を使っていただきありがとうございます 😃",
     "welcome_text_line_3": "もし %1 を気に入っていただけたら、%2GreasyFork%3 か %4OpenUserJS%5 で評価をお願いします",

+ 0 - 2
assets/translations/pt_BR.json

@@ -76,7 +76,6 @@
     "lyrics_cache_changed_clear_confirm": "Você alterou configurações que afetam os dados no cache de letras, o que quebra as pesquisas de URL de letras.\nVocê deseja limpar o cache agora?",
 
     "hotkey_input_click_to_change": "Clique para alterar",
-    "hotkey_input_click_to_change_tooltip": "Clique, depois pressione a tecla desejada",
     "hotkey_input_click_to_cancel_tooltip": "Clique para cancelar",
     "hotkey_key_ctrl": "Ctrl",
     "hotkey_key_shift": "Shift",
@@ -88,7 +87,6 @@
     "open_config_menu_tooltip": "Clique para abrir o menu de configuração",
     "open_changelog": "Registro de alterações",
     "open_changelog_tooltip": "Clique para abrir o registro de alterações",
-    "feature_help_button_tooltip": "Clique para obter mais informações sobre este recurso",
     "welcome_text_line_1": "Obrigado por instalar!",
     "welcome_text_line_2": "Espero que você goste de usar o %1 tanto quanto eu gostei de fazê-lo 😃",
     "welcome_text_line_3": "Se você gosta do %1, por favor, deixe uma avaliação no %2GreasyFork%3 ou %4OpenUserJS%5",

+ 0 - 2
assets/translations/zh_CN.json

@@ -76,7 +76,6 @@
     "lyrics_cache_changed_clear_confirm": "您已更改了影响歌词缓存中数据的设置,这会破坏歌词 URL 查找。\n您是否要现在清除缓存?",
 
     "hotkey_input_click_to_change": "点击更改",
-    "hotkey_input_click_to_change_tooltip": "点击,然后按下所需的键组合",
     "hotkey_input_click_to_cancel_tooltip": "点击取消",
     "hotkey_key_ctrl": "Ctrl",
     "hotkey_key_shift": "Shift",
@@ -88,7 +87,6 @@
     "open_config_menu_tooltip": "点击打开配置菜单",
     "open_changelog": "更新日志",
     "open_changelog_tooltip": "点击打开更新日志",
-    "feature_help_button_tooltip": "点击获取有关此功能的更多信息",
     "welcome_text_line_1": "感谢您安装!",
     "welcome_text_line_2": "我希望您使用 %1 的过程中能够愉快 😃",
     "welcome_text_line_3": "如果您喜欢 %1,请在 %2GreasyFork%3 或 %4OpenUserJS%5 上留下评分",

+ 177 - 132
dist/BetterYTM.user.js

@@ -17,7 +17,7 @@
 // @license           AGPL-3.0-only
 // @author            Sv443
 // @copyright         Sv443 (https://github.com/Sv443)
-// @icon              https://raw.githubusercontent.com/Sv443/BetterYTM/d8edad1a/assets/images/logo/logo_dev_48.png
+// @icon              http://localhost:8710/assets/images/logo/logo_dev_48.png?b=679ce956-1cfe-4651-827c-93e8c765dbaf
 // @match             https://music.youtube.com/*
 // @match             https://www.youtube.com/*
 // @run-at            document-start
@@ -33,51 +33,51 @@
 // @grant             GM.openInTab
 // @grant             unsafeWindow
 // @noframes
-// @resource          css-bundle              https://raw.githubusercontent.com/Sv443/BetterYTM/d8edad1a/dist/BetterYTM.css
-// @resource          css-above_queue_btns    https://raw.githubusercontent.com/Sv443/BetterYTM/d8edad1a/assets/style/aboveQueueBtns.css
-// @resource          css-anchor_improvements https://raw.githubusercontent.com/Sv443/BetterYTM/d8edad1a/assets/style/anchorImprovements.css
-// @resource          css-fix_hdr             https://raw.githubusercontent.com/Sv443/BetterYTM/d8edad1a/assets/style/fixHDR.css
-// @resource          css-fix_spacing         https://raw.githubusercontent.com/Sv443/BetterYTM/d8edad1a/assets/style/fixSpacing.css
-// @resource          css-show_votes          https://raw.githubusercontent.com/Sv443/BetterYTM/d8edad1a/assets/style/showVotes.css
-// @resource          css-vol_slider_size     https://raw.githubusercontent.com/Sv443/BetterYTM/d8edad1a/assets/style/volSliderSize.css
-// @resource          doc-changelog           https://raw.githubusercontent.com/Sv443/BetterYTM/d8edad1a/changelog.md
-// @resource          icon-advanced_mode      https://raw.githubusercontent.com/Sv443/BetterYTM/d8edad1a/assets/icons/plus_circle_small.svg
-// @resource          icon-arrow_down         https://raw.githubusercontent.com/Sv443/BetterYTM/d8edad1a/assets/icons/arrow_down.svg
-// @resource          icon-auto_like_enabled  https://raw.githubusercontent.com/Sv443/BetterYTM/d8edad1a/assets/icons/auto_like_enabled.svg
-// @resource          icon-auto_like          https://raw.githubusercontent.com/Sv443/BetterYTM/d8edad1a/assets/icons/auto_like.svg
-// @resource          icon-clear_list         https://raw.githubusercontent.com/Sv443/BetterYTM/d8edad1a/assets/icons/clear_list.svg
-// @resource          icon-copy               https://raw.githubusercontent.com/Sv443/BetterYTM/d8edad1a/assets/icons/copy.svg
-// @resource          icon-delete             https://raw.githubusercontent.com/Sv443/BetterYTM/d8edad1a/assets/icons/delete.svg
-// @resource          icon-edit               https://raw.githubusercontent.com/Sv443/BetterYTM/d8edad1a/assets/icons/edit.svg
-// @resource          icon-error              https://raw.githubusercontent.com/Sv443/BetterYTM/d8edad1a/assets/icons/error.svg
-// @resource          icon-experimental       https://raw.githubusercontent.com/Sv443/BetterYTM/d8edad1a/assets/icons/beaker_small.svg
-// @resource          icon-globe_small        https://raw.githubusercontent.com/Sv443/BetterYTM/d8edad1a/assets/icons/globe_small.svg
-// @resource          icon-globe              https://raw.githubusercontent.com/Sv443/BetterYTM/d8edad1a/assets/icons/globe.svg
-// @resource          icon-help               https://raw.githubusercontent.com/Sv443/BetterYTM/d8edad1a/assets/icons/help.svg
-// @resource          icon-image_filled       https://raw.githubusercontent.com/Sv443/BetterYTM/d8edad1a/assets/icons/image_filled.svg
-// @resource          icon-image              https://raw.githubusercontent.com/Sv443/BetterYTM/d8edad1a/assets/icons/image.svg
-// @resource          icon-link               https://raw.githubusercontent.com/Sv443/BetterYTM/d8edad1a/assets/icons/link.svg
-// @resource          icon-lyrics             https://raw.githubusercontent.com/Sv443/BetterYTM/d8edad1a/assets/icons/lyrics.svg
-// @resource          icon-reload             https://raw.githubusercontent.com/Sv443/BetterYTM/d8edad1a/assets/icons/refresh.svg
-// @resource          icon-skip_to            https://raw.githubusercontent.com/Sv443/BetterYTM/d8edad1a/assets/icons/skip_to.svg
-// @resource          icon-spinner            https://raw.githubusercontent.com/Sv443/BetterYTM/d8edad1a/assets/icons/spinner.svg
-// @resource          icon-upload             https://raw.githubusercontent.com/Sv443/BetterYTM/d8edad1a/assets/icons/upload.svg
-// @resource          img-close               https://raw.githubusercontent.com/Sv443/BetterYTM/d8edad1a/assets/images/close.png
-// @resource          img-discord             https://raw.githubusercontent.com/Sv443/BetterYTM/d8edad1a/assets/images/external/discord.png
-// @resource          img-github              https://raw.githubusercontent.com/Sv443/BetterYTM/d8edad1a/assets/images/external/github.png
-// @resource          img-greasyfork          https://raw.githubusercontent.com/Sv443/BetterYTM/d8edad1a/assets/images/external/greasyfork.png
-// @resource          img-logo_dev            https://raw.githubusercontent.com/Sv443/BetterYTM/d8edad1a/assets/images/logo/logo_dev_48.png
-// @resource          img-logo                https://raw.githubusercontent.com/Sv443/BetterYTM/d8edad1a/assets/images/logo/logo_48.png
-// @resource          img-openuserjs          https://raw.githubusercontent.com/Sv443/BetterYTM/d8edad1a/assets/images/external/openuserjs.png
-// @resource          trans-de_DE             https://raw.githubusercontent.com/Sv443/BetterYTM/d8edad1a/assets/translations/de_DE.json
-// @resource          trans-en_US             https://raw.githubusercontent.com/Sv443/BetterYTM/d8edad1a/assets/translations/en_US.json
-// @resource          trans-en_UK             https://raw.githubusercontent.com/Sv443/BetterYTM/d8edad1a/assets/translations/en_UK.json
-// @resource          trans-es_ES             https://raw.githubusercontent.com/Sv443/BetterYTM/d8edad1a/assets/translations/es_ES.json
-// @resource          trans-fr_FR             https://raw.githubusercontent.com/Sv443/BetterYTM/d8edad1a/assets/translations/fr_FR.json
-// @resource          trans-hi_IN             https://raw.githubusercontent.com/Sv443/BetterYTM/d8edad1a/assets/translations/hi_IN.json
-// @resource          trans-ja_JA             https://raw.githubusercontent.com/Sv443/BetterYTM/d8edad1a/assets/translations/ja_JA.json
-// @resource          trans-pt_BR             https://raw.githubusercontent.com/Sv443/BetterYTM/d8edad1a/assets/translations/pt_BR.json
-// @resource          trans-zh_CN             https://raw.githubusercontent.com/Sv443/BetterYTM/d8edad1a/assets/translations/zh_CN.json
+// @resource          css-bundle              http://localhost:8710/dist/BetterYTM.css?b=679ce956-1cfe-4651-827c-93e8c765dbaf
+// @resource          css-above_queue_btns    http://localhost:8710/assets/style/aboveQueueBtns.css?b=679ce956-1cfe-4651-827c-93e8c765dbaf
+// @resource          css-anchor_improvements http://localhost:8710/assets/style/anchorImprovements.css?b=679ce956-1cfe-4651-827c-93e8c765dbaf
+// @resource          css-fix_hdr             http://localhost:8710/assets/style/fixHDR.css?b=679ce956-1cfe-4651-827c-93e8c765dbaf
+// @resource          css-fix_spacing         http://localhost:8710/assets/style/fixSpacing.css?b=679ce956-1cfe-4651-827c-93e8c765dbaf
+// @resource          css-show_votes          http://localhost:8710/assets/style/showVotes.css?b=679ce956-1cfe-4651-827c-93e8c765dbaf
+// @resource          css-vol_slider_size     http://localhost:8710/assets/style/volSliderSize.css?b=679ce956-1cfe-4651-827c-93e8c765dbaf
+// @resource          doc-changelog           http://localhost:8710/changelog.md?b=679ce956-1cfe-4651-827c-93e8c765dbaf
+// @resource          icon-advanced_mode      http://localhost:8710/assets/icons/plus_circle_small.svg?b=679ce956-1cfe-4651-827c-93e8c765dbaf
+// @resource          icon-arrow_down         http://localhost:8710/assets/icons/arrow_down.svg?b=679ce956-1cfe-4651-827c-93e8c765dbaf
+// @resource          icon-auto_like_enabled  http://localhost:8710/assets/icons/auto_like_enabled.svg?b=679ce956-1cfe-4651-827c-93e8c765dbaf
+// @resource          icon-auto_like          http://localhost:8710/assets/icons/auto_like.svg?b=679ce956-1cfe-4651-827c-93e8c765dbaf
+// @resource          icon-clear_list         http://localhost:8710/assets/icons/clear_list.svg?b=679ce956-1cfe-4651-827c-93e8c765dbaf
+// @resource          icon-copy               http://localhost:8710/assets/icons/copy.svg?b=679ce956-1cfe-4651-827c-93e8c765dbaf
+// @resource          icon-delete             http://localhost:8710/assets/icons/delete.svg?b=679ce956-1cfe-4651-827c-93e8c765dbaf
+// @resource          icon-edit               http://localhost:8710/assets/icons/edit.svg?b=679ce956-1cfe-4651-827c-93e8c765dbaf
+// @resource          icon-error              http://localhost:8710/assets/icons/error.svg?b=679ce956-1cfe-4651-827c-93e8c765dbaf
+// @resource          icon-experimental       http://localhost:8710/assets/icons/beaker_small.svg?b=679ce956-1cfe-4651-827c-93e8c765dbaf
+// @resource          icon-globe_small        http://localhost:8710/assets/icons/globe_small.svg?b=679ce956-1cfe-4651-827c-93e8c765dbaf
+// @resource          icon-globe              http://localhost:8710/assets/icons/globe.svg?b=679ce956-1cfe-4651-827c-93e8c765dbaf
+// @resource          icon-help               http://localhost:8710/assets/icons/help.svg?b=679ce956-1cfe-4651-827c-93e8c765dbaf
+// @resource          icon-image_filled       http://localhost:8710/assets/icons/image_filled.svg?b=679ce956-1cfe-4651-827c-93e8c765dbaf
+// @resource          icon-image              http://localhost:8710/assets/icons/image.svg?b=679ce956-1cfe-4651-827c-93e8c765dbaf
+// @resource          icon-link               http://localhost:8710/assets/icons/link.svg?b=679ce956-1cfe-4651-827c-93e8c765dbaf
+// @resource          icon-lyrics             http://localhost:8710/assets/icons/lyrics.svg?b=679ce956-1cfe-4651-827c-93e8c765dbaf
+// @resource          icon-reload             http://localhost:8710/assets/icons/refresh.svg?b=679ce956-1cfe-4651-827c-93e8c765dbaf
+// @resource          icon-skip_to            http://localhost:8710/assets/icons/skip_to.svg?b=679ce956-1cfe-4651-827c-93e8c765dbaf
+// @resource          icon-spinner            http://localhost:8710/assets/icons/spinner.svg?b=679ce956-1cfe-4651-827c-93e8c765dbaf
+// @resource          icon-upload             http://localhost:8710/assets/icons/upload.svg?b=679ce956-1cfe-4651-827c-93e8c765dbaf
+// @resource          img-close               http://localhost:8710/assets/images/close.png?b=679ce956-1cfe-4651-827c-93e8c765dbaf
+// @resource          img-discord             http://localhost:8710/assets/images/external/discord.png?b=679ce956-1cfe-4651-827c-93e8c765dbaf
+// @resource          img-github              http://localhost:8710/assets/images/external/github.png?b=679ce956-1cfe-4651-827c-93e8c765dbaf
+// @resource          img-greasyfork          http://localhost:8710/assets/images/external/greasyfork.png?b=679ce956-1cfe-4651-827c-93e8c765dbaf
+// @resource          img-logo_dev            http://localhost:8710/assets/images/logo/logo_dev_48.png?b=679ce956-1cfe-4651-827c-93e8c765dbaf
+// @resource          img-logo                http://localhost:8710/assets/images/logo/logo_48.png?b=679ce956-1cfe-4651-827c-93e8c765dbaf
+// @resource          img-openuserjs          http://localhost:8710/assets/images/external/openuserjs.png?b=679ce956-1cfe-4651-827c-93e8c765dbaf
+// @resource          trans-de_DE             http://localhost:8710/assets/translations/de_DE.json?b=679ce956-1cfe-4651-827c-93e8c765dbaf
+// @resource          trans-en_US             http://localhost:8710/assets/translations/en_US.json?b=679ce956-1cfe-4651-827c-93e8c765dbaf
+// @resource          trans-en_UK             http://localhost:8710/assets/translations/en_UK.json?b=679ce956-1cfe-4651-827c-93e8c765dbaf
+// @resource          trans-es_ES             http://localhost:8710/assets/translations/es_ES.json?b=679ce956-1cfe-4651-827c-93e8c765dbaf
+// @resource          trans-fr_FR             http://localhost:8710/assets/translations/fr_FR.json?b=679ce956-1cfe-4651-827c-93e8c765dbaf
+// @resource          trans-hi_IN             http://localhost:8710/assets/translations/hi_IN.json?b=679ce956-1cfe-4651-827c-93e8c765dbaf
+// @resource          trans-ja_JA             http://localhost:8710/assets/translations/ja_JA.json?b=679ce956-1cfe-4651-827c-93e8c765dbaf
+// @resource          trans-pt_BR             http://localhost:8710/assets/translations/pt_BR.json?b=679ce956-1cfe-4651-827c-93e8c765dbaf
+// @resource          trans-zh_CN             http://localhost:8710/assets/translations/zh_CN.json?b=679ce956-1cfe-4651-827c-93e8c765dbaf
 // @require           https://cdn.jsdelivr.net/npm/@sv443-network/[email protected]/dist/index.global.js
 // @require           https://cdn.jsdelivr.net/npm/[email protected]/dist/fuse.basic.js
 // @require           https://cdn.jsdelivr.net/npm/[email protected]/lib/marked.umd.js
@@ -191,7 +191,7 @@ var PluginIntent;
 const modeRaw = "development";
 const branchRaw = "develop";
 const hostRaw = "github";
-const buildNumberRaw = "d8edad1a";
+const buildNumberRaw = "77eb58e8";
 /** The mode in which the script was built (production or development) */
 const mode = (modeRaw.match(/^#{{.+}}$/) ? "production" : modeRaw);
 /** The branch to use in various URLs that point to the GitHub repo */
@@ -1033,31 +1033,43 @@ function initSiteEvents() {
                         lastFullscreen = isFullscreen;
                     }
                 });
-                addSelectorListener("mainPanel", "ytmusic-player#player", {
-                    listener: (el) => {
-                        playerFullscreenObs.observe(el, {
-                            attributeFilter: ["player-ui-state"],
-                        });
-                    },
-                });
+                if (getDomain() === "ytm") {
+                    const registerFullScreenObs = () => addSelectorListener("mainPanel", "ytmusic-player#player", {
+                        listener: (el) => {
+                            playerFullscreenObs.observe(el, {
+                                attributeFilter: ["player-ui-state"],
+                            });
+                        },
+                    });
+                    if (globserversReady)
+                        registerFullScreenObs();
+                    else
+                        window.addEventListener("bytm:observersReady", registerFullScreenObs, { once: true });
+                }
             }
             window.addEventListener("bytm:ready", () => {
                 runIntervalChecks();
                 setInterval(runIntervalChecks, 100);
-                addSelectorListener("mainPanel", "ytmusic-player #song-video #movie_player .ytp-title-text > a", {
-                    listener(el) {
-                        const urlRefObs = new MutationObserver(([{ target }]) => {
-                            var _a;
-                            if (!target || !((_a = target === null || target === void 0 ? void 0 : target.href) === null || _a === void 0 ? void 0 : _a.includes("/watch")))
-                                return;
-                            const watchId = new URL(target.href).searchParams.get("v");
-                            checkWatchIdChange(watchId);
-                        });
-                        urlRefObs.observe(el, {
-                            attributeFilter: ["href"],
-                        });
-                    }
-                });
+                if (getDomain() === "ytm") {
+                    addSelectorListener("mainPanel", "ytmusic-player #song-video #movie_player .ytp-title-text > a", {
+                        listener(el) {
+                            const urlRefObs = new MutationObserver(([{ target }]) => {
+                                var _a;
+                                if (!target || !((_a = target === null || target === void 0 ? void 0 : target.href) === null || _a === void 0 ? void 0 : _a.includes("/watch")))
+                                    return;
+                                const watchId = new URL(target.href).searchParams.get("v");
+                                checkWatchIdChange(watchId);
+                            });
+                            urlRefObs.observe(el, {
+                                attributeFilter: ["href"],
+                            });
+                        }
+                    });
+                }
+                if (getDomain() === "ytm") {
+                    setInterval(checkWatchIdChange, 250);
+                    checkWatchIdChange();
+                }
             }, {
                 once: true,
             });
@@ -1086,9 +1098,9 @@ function emitSiteEvent(key, ...args) {
 function checkWatchIdChange(newId) {
     const newWatchId = newId !== null && newId !== void 0 ? newId : new URL(location.href).searchParams.get("v");
     if (newWatchId && newWatchId !== lastWatchId) {
+        lastWatchId = newWatchId;
         info(`Detected watch ID change - old ID: "${lastWatchId}" - new ID: "${newWatchId}"`);
         emitSiteEvent("watchIdChanged", newWatchId, lastWatchId);
-        lastWatchId = newWatchId;
     }
 }
 /** Periodically called to check for changes in the URL and emit associated siteEvents */
@@ -1102,26 +1114,28 @@ function runIntervalChecks() {
 }let otherHotkeyInputActive = false;
 const reservedKeys = ["ShiftLeft", "ShiftRight", "ControlLeft", "ControlRight", "AltLeft", "AltRight", "Meta", "Tab", "Space", " "];
 /** Creates a hotkey input element */
-function createHotkeyInput({ initialValue, onChange }) {
+function createHotkeyInput({ initialValue, onChange, createTitle }) {
     var _a;
     const initialHotkey = initialValue;
     let currentHotkey;
+    if (!createTitle)
+        createTitle = (value) => value;
     const wrapperElem = document.createElement("div");
     wrapperElem.classList.add("bytm-hotkey-wrapper");
     const infoElem = document.createElement("span");
     infoElem.classList.add("bytm-hotkey-info");
-    const inputElem = document.createElement("input");
-    inputElem.type = "button";
+    const inputElem = document.createElement("button");
+    inputElem.role = "button";
     inputElem.classList.add("bytm-ftconf-input", "bytm-hotkey-input", "bytm-btn");
     inputElem.dataset.state = "inactive";
-    inputElem.value = (_a = initialValue === null || initialValue === void 0 ? void 0 : initialValue.code) !== null && _a !== void 0 ? _a : t("hotkey_input_click_to_change");
-    inputElem.ariaLabel = inputElem.title = t("hotkey_input_click_to_change_tooltip");
+    inputElem.innerText = (_a = initialValue === null || initialValue === void 0 ? void 0 : initialValue.code) !== null && _a !== void 0 ? _a : t("hotkey_input_click_to_change");
+    inputElem.ariaLabel = inputElem.title = createTitle(hotkeyToString(initialValue));
     const resetElem = document.createElement("span");
     resetElem.classList.add("bytm-hotkey-reset", "bytm-link", "bytm-hidden");
     resetElem.role = "button";
     resetElem.tabIndex = 0;
     resetElem.textContent = `(${t("reset")})`;
-    resetElem.ariaLabel = resetElem.title = t("reset");
+    resetElem.ariaLabel = resetElem.title = t("hotkey_input_click_to_reset_tooltip");
     const deactivate = () => {
         var _a;
         if (!otherHotkeyInputActive)
@@ -1129,9 +1143,9 @@ function createHotkeyInput({ initialValue, onChange }) {
         emitSiteEvent("hotkeyInputActive", false);
         otherHotkeyInputActive = false;
         const curHk = currentHotkey !== null && currentHotkey !== void 0 ? currentHotkey : initialValue;
-        inputElem.value = (_a = curHk === null || curHk === void 0 ? void 0 : curHk.code) !== null && _a !== void 0 ? _a : t("hotkey_input_click_to_change");
+        inputElem.innerText = (_a = curHk === null || curHk === void 0 ? void 0 : curHk.code) !== null && _a !== void 0 ? _a : t("hotkey_input_click_to_change");
         inputElem.dataset.state = "inactive";
-        inputElem.ariaLabel = inputElem.title = t("hotkey_input_click_to_change_tooltip");
+        inputElem.ariaLabel = inputElem.title = createTitle(hotkeyToString(curHk));
         infoElem.innerHTML = curHk ? getHotkeyInfoHtml(curHk) : "";
     };
     const activate = () => {
@@ -1139,7 +1153,7 @@ function createHotkeyInput({ initialValue, onChange }) {
             return;
         emitSiteEvent("hotkeyInputActive", true);
         otherHotkeyInputActive = true;
-        inputElem.value = "< ... >";
+        inputElem.innerText = "< ... >";
         inputElem.dataset.state = "active";
         inputElem.ariaLabel = inputElem.title = t("hotkey_input_click_to_cancel_tooltip");
     };
@@ -1149,7 +1163,7 @@ function createHotkeyInput({ initialValue, onChange }) {
         onChange(initialValue);
         currentHotkey = initialValue;
         deactivate();
-        inputElem.value = initialValue.code;
+        inputElem.innerText = initialValue.code;
         infoElem.innerHTML = getHotkeyInfoHtml(initialValue);
         resetElem.classList.add("bytm-hidden");
     };
@@ -1170,7 +1184,7 @@ function createHotkeyInput({ initialValue, onChange }) {
             ctrl: e.ctrlKey,
             alt: e.altKey,
         };
-        inputElem.value = hotkey.code;
+        inputElem.innerText = hotkey.code;
         inputElem.dataset.state = "inactive";
         infoElem.innerHTML = getHotkeyInfoHtml(hotkey);
         inputElem.ariaLabel = inputElem.title = t("hotkey_input_click_to_cancel_tooltip");
@@ -1206,7 +1220,7 @@ function createHotkeyInput({ initialValue, onChange }) {
         }
         else
             resetElem.classList.add("bytm-hidden");
-        inputElem.value = hotkey.code;
+        inputElem.innerText = hotkey.code;
         inputElem.dataset.state = "inactive";
         infoElem.innerHTML = getHotkeyInfoHtml(hotkey);
     });
@@ -1228,6 +1242,7 @@ function createHotkeyInput({ initialValue, onChange }) {
     wrapperElem.appendChild(inputElem);
     return wrapperElem;
 }
+/** Returns HTML for the hotkey modifier keys info element */
 function getHotkeyInfoHtml(hotkey) {
     const modifiers = [];
     hotkey.ctrl && modifiers.push(`<kbd class="bytm-kbd">${t("hotkey_key_ctrl")}</kbd>`);
@@ -1248,6 +1263,20 @@ function getOS() {
     if (navigator.userAgent.match(/mac(\s?os|intel)/i))
         return "mac";
     return "other";
+}
+/** Converts a hotkey object to a string */
+function hotkeyToString(hotkey) {
+    if (!hotkey)
+        return t("hotkey_key_none");
+    let str = "";
+    if (hotkey.ctrl)
+        str += `${t("hotkey_key_ctrl")}+`;
+    if (hotkey.shift)
+        str += `${t("hotkey_key_shift")}+`;
+    if (hotkey.alt)
+        str += `${getOS() === "mac" ? t("hotkey_key_mac_option") : t("hotkey_key_alt")}+`;
+    str += hotkey.code;
+    return str;
 }/**
  * Creates a generic, circular, long button element with an icon and text.
  * Has classes for the enabled and disabled states for easier styling.
@@ -2416,7 +2445,6 @@ function mountCfgMenu() {
                         featLeftSideElem.title = `${featKey}${rel}${adv}${extraTxts.length > 0 ? `\n${extraTxts.join(" - ")}` : ""}`;
                     }
                     const textElem = document.createElement("span");
-                    textElem.tabIndex = 0;
                     textElem.textContent = t(`feature_desc_${featKey}`);
                     let adornmentElem;
                     const adornContent = (_b = ftInfo.textAdornment) === null || _b === void 0 ? void 0 : _b.call(ftInfo);
@@ -2437,7 +2465,7 @@ function mountCfgMenu() {
                         if (helpElemImgHtml) {
                             helpElem = document.createElement("div");
                             helpElem.classList.add("bytm-ftitem-help-btn", "bytm-generic-btn");
-                            helpElem.ariaLabel = helpElem.title = t("feature_help_button_tooltip");
+                            helpElem.ariaLabel = helpElem.title = t("feature_help_button_tooltip", t(`feature_desc_${featKey}`));
                             helpElem.role = "button";
                             helpElem.tabIndex = 0;
                             helpElem.innerHTML = helpElemImgHtml;
@@ -2524,6 +2552,7 @@ function mountCfgMenu() {
                         const inputElem = document.createElement(inputTag);
                         inputElem.classList.add("bytm-ftconf-input");
                         inputElem.id = inputElemId;
+                        inputElem.ariaLabel = t(`feature_desc_${featKey}`);
                         if (inputType)
                             inputElem.type = inputType;
                         if ("min" in ftInfo && typeof ftInfo.min !== "undefined")
@@ -2616,6 +2645,7 @@ function mountCfgMenu() {
                                 customInputEl = createHotkeyInput({
                                     initialValue: typeof initialVal === "object" ? initialVal : undefined,
                                     onChange: (hotkey) => confChanged(featKey, initialVal, hotkey),
+                                    createTitle: (value) => t("hotkey_input_click_to_change_tooltip", t(`feature_desc_${featKey}`), value),
                                 });
                                 break;
                             case "toggle":
@@ -2630,7 +2660,8 @@ function mountCfgMenu() {
                                 customInputEl = document.createElement("button");
                                 customInputEl.classList.add("bytm-btn");
                                 customInputEl.tabIndex = 0;
-                                customInputEl.textContent = customInputEl.ariaLabel = customInputEl.title = hasKey(`feature_btn_${featKey}`) ? t(`feature_btn_${featKey}`) : t("trigger_btn_action");
+                                customInputEl.textContent = hasKey(`feature_btn_${featKey}`) ? t(`feature_btn_${featKey}`) : t("trigger_btn_action");
+                                customInputEl.ariaLabel = customInputEl.title = t(`feature_desc_${featKey}`);
                                 onInteraction(customInputEl, () => __awaiter(this, void 0, void 0, function* () {
                                     if (customInputEl.disabled)
                                         return;
@@ -2654,6 +2685,8 @@ function mountCfgMenu() {
                                 }));
                                 break;
                         }
+                        if (customInputEl && !customInputEl.hasAttribute("aria-label"))
+                            customInputEl.ariaLabel = t(`feature_desc_${featKey}`);
                         ctrlElem.appendChild(customInputEl);
                     }
                     ftConfElem.appendChild(ctrlElem);
@@ -3793,9 +3826,9 @@ function initShowVotes() {
                                     return error("Couldn't fetch votes from the Return YouTube Dislike API");
                                 labelLikes.dataset.watchId = (_a = getWatchId()) !== null && _a !== void 0 ? _a : "";
                                 labelLikes.textContent = formatVoteNumber(voteObj.likes);
-                                labelLikes.title = labelLikes.ariaLabel = tp("vote_label_likes", voteObj.likes, formatVoteNumber(voteObj.likes, "full"));
+                                labelLikes.title = labelLikes.ariaLabel = tp("vote_label_likes", voteObj.likes, formatVoteNumber(voteObj.likes, "long"));
                                 labelDislikes.textContent = formatVoteNumber(voteObj.dislikes);
-                                labelDislikes.title = labelDislikes.ariaLabel = tp("vote_label_dislikes", voteObj.dislikes, formatVoteNumber(voteObj.dislikes, "full"));
+                                labelDislikes.title = labelDislikes.ariaLabel = tp("vote_label_dislikes", voteObj.dislikes, formatVoteNumber(voteObj.dislikes, "long"));
                                 labelDislikes.dataset.watchId = (_b = getWatchId()) !== null && _b !== void 0 ? _b : "";
                             }));
                         }
@@ -3818,7 +3851,7 @@ function addVoteNumbers(voteCont, voteObj) {
         const label = document.createElement("span");
         label.classList.add("bytm-vote-label", "bytm-no-select", type);
         label.textContent = String(formatVoteNumber(amount));
-        label.title = label.ariaLabel = tp(`vote_label_${type}`, amount, formatVoteNumber(amount, "full"));
+        label.title = label.ariaLabel = tp(`vote_label_${type}`, amount, formatVoteNumber(amount, "long"));
         label.dataset.watchId = (_a = getWatchId()) !== null && _a !== void 0 ? _a : "";
         label.addEventListener("click", (e) => {
             var _a;
@@ -3835,8 +3868,8 @@ function addVoteNumbers(voteCont, voteObj) {
     dislikeBtn.insertAdjacentElement("afterend", dislikeLblEl);
 }
 /** Formats a number formatted based on the config or the passed {@linkcode notation} */
-function formatVoteNumber(num, notation = getFeature("showVotesFormat")) {
-    return num.toLocaleString(getLocale().replace(/_/g, "-"), notation === "short"
+function formatVoteNumber(num, notation) {
+    return num.toLocaleString(getLocale().replace(/_/g, "-"), (notation !== null && notation !== void 0 ? notation : getFeature("showVotesFormat")) === "short"
         ? {
             notation: "compact",
             compactDisplay: "short",
@@ -4264,12 +4297,12 @@ function initAutoLike() {
                     }, ((_a = getFeature("autoLikeTimeout")) !== null && _a !== void 0 ? _a : 5) * 1000);
                 });
                 siteEvents.on("pathChanged", (path) => {
-                    if (path.match(/(\/?@|\/channel\/).+/)) {
+                    if (path.match(/(\/?@|\/?channel\/)\S+/)) {
                         const chanId = getCurrentChannelId();
                         if (!chanId)
                             return error("Couldn't extract channel ID from URL");
                         document.querySelectorAll(".bytm-auto-like-toggle-btn").forEach((btn) => clearNode(btn));
-                        addSelectorListener("ytChannelHeader", "#channel-header-container", {
+                        addSelectorListener("ytAppHeader", "#channel-header-container, #page-header", {
                             listener(headerCont) {
                                 var _a, _b;
                                 const titleCont = headerCont.querySelector("ytd-channel-name #container");
@@ -4278,10 +4311,8 @@ function initAutoLike() {
                                 const chanName = (_b = (_a = titleCont.querySelector("yt-formatted-string")) === null || _a === void 0 ? void 0 : _a.textContent) !== null && _b !== void 0 ? _b : null;
                                 const buttonsCont = headerCont.querySelector("#inner-header-container #buttons");
                                 if (buttonsCont) {
-                                    addSelectorListener("ytChannelHeader", "#channel-header-container #other-buttons", {
-                                        listener(otherBtns) {
-                                            addAutoLikeToggleBtn(otherBtns, chanId, chanName, ["left-margin"]);
-                                        }
+                                    addSelectorListener("ytAppHeader", "#channel-header-container #other-buttons, yt-subscribe-button-view-model", {
+                                        listener: (otherBtns) => addAutoLikeToggleBtn(otherBtns, chanId, chanName, ["left-margin"]),
                                     });
                                 }
                                 else if (titleCont)
@@ -5333,7 +5364,7 @@ const featInfo = {
         type: "select",
         category: "layout",
         options: () => [
-            { value: "full", label: t("votes_format_full") },
+            { value: "long", label: t("votes_format_full") },
             { value: "short", label: t("votes_format_short") },
         ],
         default: "short",
@@ -6166,7 +6197,7 @@ function resolveToken(token) {
     var _a, _b;
     return typeof token === "string" && token.length > 0
         ? (_b = (_a = [...registeredPluginTokens.entries()]
-            .find(([, t]) => token === t)) === null || _a === void 0 ? void 0 : _a[0]) !== null && _b !== void 0 ? _b : undefined
+            .find(([k, t]) => registeredPlugins.has(k) && token === t)) === null || _a === void 0 ? void 0 : _a[0]) !== null && _b !== void 0 ? _b : undefined
         : undefined;
 }
 //#region proxy funcs
@@ -6232,16 +6263,28 @@ const defaultObserverOptions = {
 };
 /** Global SelectorObserver instances usable throughout the script for improved performance */
 const globservers = {};
+/** Whether all observers have been initialized */
+let globserversReady = false;
 //#region add listener func
 /**
  * Interface function for adding listeners to the {@linkcode globservers}
+ * If the observers haven't been initialized yet, the function will queue calls until the `bytm:observersReady` event is emitted
  * @param selector Relative to the observer's root element, so the selector can only start at of the root element's children at the earliest!
  * @param options Options for the listener
  * @template TElem The type of the element that the listener will be attached to. If set to `0`, the default type `HTMLElement` will be used.
  * @template TDomain This restricts which observers are available with the current domain
  */
 function addSelectorListener(observerName, selector, options) {
-    globservers[observerName].addListener(selector, options);
+    try {
+        if (!globserversReady) {
+            window.addEventListener("bytm:observersReady", () => addSelectorListener(observerName, selector, options), { once: true });
+            return;
+        }
+        globservers[observerName].addListener(selector, options);
+    }
+    catch (err) {
+        error(`Couldn't add listener to globserver '${observerName}':`, err);
+    }
 }
 //#region init
 /** Call after DOM load to initialize all SelectorObserver instances */
@@ -6366,13 +6409,13 @@ function initObservers() {
                 globservers.body.addListener(ytdBrowseSelector, {
                     listener: () => globservers.ytdBrowse.enable(),
                 });
-                //#region ytChannelHeader
-                // -> header of a channel page
+                //#region ytAppHeader
+                // -> header of the page
                 //    enabled by "ytdBrowse"
-                const ytChannelHeaderSelector = "#header tp-yt-app-header #channel-header";
-                globservers.ytChannelHeader = new UserUtils.SelectorObserver(ytChannelHeaderSelector, Object.assign(Object.assign({}, defaultObserverOptions), { subtree: true }));
-                globservers.ytdBrowse.addListener(ytChannelHeaderSelector, {
-                    listener: () => globservers.ytChannelHeader.enable(),
+                const ytAppHeaderSelector = "#header tp-yt-app-header";
+                globservers.ytAppHeader = new UserUtils.SelectorObserver(ytAppHeaderSelector, Object.assign(Object.assign({}, defaultObserverOptions), { subtree: true }));
+                globservers.ytdBrowse.addListener(ytAppHeaderSelector, {
+                    listener: () => globservers.ytAppHeader.enable(),
                 });
                 //#region ytWatchFlexy
                 // -> the main content of the /watch page
@@ -6401,6 +6444,7 @@ function initObservers() {
             }
         }
         //#region finalize
+        globserversReady = true;
         emitInterface("bytm:observersReady");
     }
     catch (err) {
@@ -6510,18 +6554,7 @@ function waitVideoElementReady() {
         waitForEl();
     }));
 }
-//#region other
-/** Removes all child nodes of an element without invoking the slow-ish HTML parser */
-function clearInner(element) {
-    while (element.hasChildNodes())
-        clearNode(element.firstChild);
-}
-/** Removes all child nodes of an element recursively and also removes the element itself */
-function clearNode(element) {
-    while (element.hasChildNodes())
-        clearNode(element.firstChild);
-    element.parentNode.removeChild(element);
-}
+//#region css utils
 /**
  * Adds a style element to the DOM at runtime.
  * @param css The CSS stylesheet to add
@@ -6537,6 +6570,20 @@ function addStyle(css_1, ref_1) {
         return elem;
     });
 }
+/**
+ * Adds a global style element with the contents fetched from the specified CSS resource.
+ * The CSS can be transformed using the provided function before being added to the DOM.
+ */
+function addStyleFromResource(key_1) {
+    return __awaiter(this, arguments, void 0, function* (key, transform = (c) => c) {
+        const css = yield fetchCss(key);
+        if (css) {
+            addStyle(transform(css), key.slice(4));
+            return true;
+        }
+        return false;
+    });
+}
 /** Sets a global CSS variable on the &lt;document&gt; element */
 function setGlobalCssVar(name, value) {
     document.documentElement.style.setProperty(`--bytm-global-${name}`, String(value));
@@ -6546,6 +6593,18 @@ function setGlobalCssVars(vars) {
     for (const [name, value] of Object.entries(vars))
         setGlobalCssVar(name, value);
 }
+//#region other
+/** Removes all child nodes of an element without invoking the slow-ish HTML parser */
+function clearInner(element) {
+    while (element.hasChildNodes())
+        clearNode(element.firstChild);
+}
+/** Removes all child nodes of an element recursively and also removes the element itself */
+function clearNode(element) {
+    while (element.hasChildNodes())
+        clearNode(element.firstChild);
+    element.parentNode.removeChild(element);
+}
 /**
  * Checks if the currently playing media is a song or a video.
  * This function should only be called after awaiting {@linkcode waitVideoElementReady}!
@@ -6556,20 +6615,6 @@ function currentMediaType() {
         throw new Error("Couldn't find the song image element. Use this function only after `await waitVideoElementReady()`!");
     return UserUtils.getUnsafeWindow().getComputedStyle(songImgElem).display !== "none" ? "song" : "video";
 }
-/**
- * Adds a global style element with the contents fetched from the specified CSS resource.
- * The CSS can be transformed using the provided function before being added to the DOM.
- */
-function addStyleFromResource(key_1) {
-    return __awaiter(this, arguments, void 0, function* (key, transform = (c) => c) {
-        const css = yield fetchCss(key);
-        if (css) {
-            addStyle(transform(css), key.slice(4));
-            return true;
-        }
-        return false;
-    });
-}
 /** Copies the provided text to the clipboard and shows an error message for manual copying if the grant `GM.setClipboard` is not given. */
 function copyToClipboard(text) {
     try {
@@ -6840,7 +6885,7 @@ function getChangelogHtmlWithDetails() {
             const changelogMd = yield getChangelogMd();
             let changelogHtml = yield parseMarkdown(changelogMd);
             const getVerId = (verStr) => verStr.trim().replace(/[._#\s-]/g, "");
-            changelogHtml = changelogHtml.replace(/<div\s+class="split">\s*<\/div>\s*\n?\s*<br(\s\/)?>/gm, "</details>\n<br>\n<details class=\"bytm-changelog-version-details\">");
+            changelogHtml = changelogHtml.replace(/<div\s+class="split">\s*<\/div>\s*\n?\s*<br(\s\/)?>/gm, "</details>\n<br>\n<details class=\"bytm-changelog-version-details\" tabindex=\"0\">");
             const h2Matches = Array.from(changelogHtml.matchAll(/<h2(\s+id=".+")?>([\d\w\s.]+)<\/h2>/gm));
             for (const match of h2Matches) {
                 const [fullMatch, , verStr] = match;
@@ -6849,7 +6894,7 @@ function getChangelogHtmlWithDetails() {
                 const summaryElem = `<summary tab-index="0">${h2Elem}</summary>`;
                 changelogHtml = changelogHtml.replace(fullMatch, `${summaryElem}`);
             }
-            changelogHtml = `<details class="bytm-changelog-version-details">${changelogHtml}</details>`;
+            changelogHtml = `<details class="bytm-changelog-version-details" tabindex="0">${changelogHtml}</details>`;
             return changelogHtml;
         }
         catch (err) {

+ 33 - 12
src/components/hotkeyInput.ts

@@ -6,6 +6,8 @@ import "./hotkeyInput.css";
 interface HotkeyInputProps {
   initialValue?: HotkeyObj;
   onChange: (hotkey: HotkeyObj) => void;
+  /** Function that returns the title and aria-label for the input element, given the hotkey value */
+  createTitle?: (value: string) => string;
 }
 
 let otherHotkeyInputActive = false;
@@ -13,29 +15,32 @@ let otherHotkeyInputActive = false;
 const reservedKeys = ["ShiftLeft", "ShiftRight", "ControlLeft", "ControlRight", "AltLeft", "AltRight", "Meta", "Tab", "Space", " "];
 
 /** Creates a hotkey input element */
-export function createHotkeyInput({ initialValue, onChange }: HotkeyInputProps): HTMLElement {
+export function createHotkeyInput({ initialValue, onChange, createTitle }: HotkeyInputProps): HTMLElement {
   const initialHotkey: HotkeyObj | undefined = initialValue;
   let currentHotkey: HotkeyObj | undefined;
 
+  if(!createTitle)
+    createTitle = (value) => value;
+
   const wrapperElem = document.createElement("div");
   wrapperElem.classList.add("bytm-hotkey-wrapper");
 
   const infoElem = document.createElement("span");
   infoElem.classList.add("bytm-hotkey-info");
 
-  const inputElem = document.createElement("input");
-  inputElem.type = "button";
+  const inputElem = document.createElement("button");
+  inputElem.role = "button";
   inputElem.classList.add("bytm-ftconf-input", "bytm-hotkey-input", "bytm-btn");
   inputElem.dataset.state = "inactive";
-  inputElem.value = initialValue?.code ?? t("hotkey_input_click_to_change");
-  inputElem.ariaLabel = inputElem.title = t("hotkey_input_click_to_change_tooltip");
+  inputElem.innerText = initialValue?.code ?? t("hotkey_input_click_to_change");
+  inputElem.ariaLabel = inputElem.title = createTitle(hotkeyToString(initialValue));
 
   const resetElem = document.createElement("span");
   resetElem.classList.add("bytm-hotkey-reset", "bytm-link", "bytm-hidden");
   resetElem.role = "button";
   resetElem.tabIndex = 0;
   resetElem.textContent = `(${t("reset")})`;
-  resetElem.ariaLabel = resetElem.title = t("reset");
+  resetElem.ariaLabel = resetElem.title = t("hotkey_input_click_to_reset_tooltip");
 
   const deactivate = () => {
     if(!otherHotkeyInputActive)
@@ -43,9 +48,9 @@ export function createHotkeyInput({ initialValue, onChange }: HotkeyInputProps):
     emitSiteEvent("hotkeyInputActive", false);
     otherHotkeyInputActive = false;
     const curHk = currentHotkey ?? initialValue;
-    inputElem.value = curHk?.code ?? t("hotkey_input_click_to_change");
+    inputElem.innerText = curHk?.code ?? t("hotkey_input_click_to_change");
     inputElem.dataset.state = "inactive";
-    inputElem.ariaLabel = inputElem.title = t("hotkey_input_click_to_change_tooltip");
+    inputElem.ariaLabel = inputElem.title = createTitle(hotkeyToString(curHk));
     infoElem.innerHTML = curHk ? getHotkeyInfoHtml(curHk) : "";
   };
 
@@ -54,7 +59,7 @@ export function createHotkeyInput({ initialValue, onChange }: HotkeyInputProps):
       return;
     emitSiteEvent("hotkeyInputActive", true);
     otherHotkeyInputActive = true;
-    inputElem.value = "< ... >";
+    inputElem.innerText = "< ... >";
     inputElem.dataset.state = "active";
     inputElem.ariaLabel = inputElem.title = t("hotkey_input_click_to_cancel_tooltip");
   };
@@ -66,7 +71,7 @@ export function createHotkeyInput({ initialValue, onChange }: HotkeyInputProps):
     onChange(initialValue!);
     currentHotkey = initialValue!;
     deactivate();
-    inputElem.value = initialValue!.code;
+    inputElem.innerText = initialValue!.code;
     infoElem.innerHTML = getHotkeyInfoHtml(initialValue!);
     resetElem.classList.add("bytm-hidden");
   };
@@ -93,7 +98,7 @@ export function createHotkeyInput({ initialValue, onChange }: HotkeyInputProps):
       alt: e.altKey,
     } satisfies HotkeyObj;
 
-    inputElem.value = hotkey.code;
+    inputElem.innerText = hotkey.code;
     inputElem.dataset.state = "inactive";
     infoElem.innerHTML = getHotkeyInfoHtml(hotkey);
     inputElem.ariaLabel = inputElem.title = t("hotkey_input_click_to_cancel_tooltip");
@@ -137,7 +142,7 @@ export function createHotkeyInput({ initialValue, onChange }: HotkeyInputProps):
     else
       resetElem.classList.add("bytm-hidden");
 
-    inputElem.value = hotkey.code;
+    inputElem.innerText = hotkey.code;
     inputElem.dataset.state = "inactive";
     infoElem.innerHTML = getHotkeyInfoHtml(hotkey);
   });
@@ -164,6 +169,7 @@ export function createHotkeyInput({ initialValue, onChange }: HotkeyInputProps):
   return wrapperElem;
 }
 
+/** Returns HTML for the hotkey modifier keys info element */
 function getHotkeyInfoHtml(hotkey: HotkeyObj) {
   const modifiers = [] as string[];
   hotkey.ctrl && modifiers.push(`<kbd class="bytm-kbd">${t("hotkey_key_ctrl")}</kbd>`);
@@ -186,3 +192,18 @@ function getOS() {
     return "mac";
   return "other";
 }
+
+/** Converts a hotkey object to a string */
+function hotkeyToString(hotkey: HotkeyObj | undefined) {
+  if(!hotkey)
+    return t("hotkey_key_none");
+  let str = "";
+  if(hotkey.ctrl)
+    str += `${t("hotkey_key_ctrl")}+`;
+  if(hotkey.shift)
+    str += `${t("hotkey_key_shift")}+`;
+  if(hotkey.alt)
+    str += `${getOS() === "mac" ? t("hotkey_key_mac_option") : t("hotkey_key_alt")}+`;
+  str += hotkey.code;
+  return str;
+}

+ 8 - 3
src/menu/menu_old.ts

@@ -410,7 +410,6 @@ async function mountCfgMenu() {
         }
 
         const textElem = document.createElement("span");
-        textElem.tabIndex = 0;
         textElem.textContent = t(`feature_desc_${featKey}`);
 
         let adornmentElem: undefined | HTMLElement;
@@ -436,7 +435,7 @@ async function mountCfgMenu() {
           if(helpElemImgHtml) {
             helpElem = document.createElement("div");
             helpElem.classList.add("bytm-ftitem-help-btn", "bytm-generic-btn");
-            helpElem.ariaLabel = helpElem.title = t("feature_help_button_tooltip");
+            helpElem.ariaLabel = helpElem.title = t("feature_help_button_tooltip", t(`feature_desc_${featKey}`));
             helpElem.role = "button";
             helpElem.tabIndex = 0;
             helpElem.innerHTML = helpElemImgHtml;
@@ -542,6 +541,7 @@ async function mountCfgMenu() {
           const inputElem = document.createElement(inputTag) as HTMLInputElement;
           inputElem.classList.add("bytm-ftconf-input");
           inputElem.id = inputElemId;
+          inputElem.ariaLabel = t(`feature_desc_${featKey}`);
           if(inputType)
             inputElem.type = inputType;
 
@@ -649,6 +649,7 @@ async function mountCfgMenu() {
             customInputEl = createHotkeyInput({
               initialValue: typeof initialVal === "object" ? initialVal as HotkeyObj : undefined,
               onChange: (hotkey) => confChanged(featKey as keyof FeatureConfig, initialVal, hotkey),
+              createTitle: (value: string) => t("hotkey_input_click_to_change_tooltip", t(`feature_desc_${featKey}`), value),
             });
             break;
           case "toggle":
@@ -663,7 +664,8 @@ async function mountCfgMenu() {
             customInputEl = document.createElement("button");
             customInputEl.classList.add("bytm-btn");
             customInputEl.tabIndex = 0;
-            customInputEl.textContent = customInputEl.ariaLabel = customInputEl.title = hasKey(`feature_btn_${featKey}`) ? t(`feature_btn_${featKey}`) : t("trigger_btn_action");
+            customInputEl.textContent = hasKey(`feature_btn_${featKey}`) ? t(`feature_btn_${featKey}`) : t("trigger_btn_action");
+            customInputEl.ariaLabel = customInputEl.title = t(`feature_desc_${featKey}`);
 
             onInteraction(customInputEl, async () => {
               if((customInputEl as HTMLButtonElement).disabled)
@@ -694,6 +696,9 @@ async function mountCfgMenu() {
             break;
           }
 
+          if(customInputEl && !customInputEl.hasAttribute("aria-label"))
+            customInputEl.ariaLabel = t(`feature_desc_${featKey}`);
+
           ctrlElem.appendChild(customInputEl!);
         }
 

+ 2 - 2
src/utils/misc.ts

@@ -250,7 +250,7 @@ export async function getChangelogHtmlWithDetails() {
 
     const getVerId = (verStr: string) => verStr.trim().replace(/[._#\s-]/g, "");
 
-    changelogHtml = changelogHtml.replace(/<div\s+class="split">\s*<\/div>\s*\n?\s*<br(\s\/)?>/gm, "</details>\n<br>\n<details class=\"bytm-changelog-version-details\">");
+    changelogHtml = changelogHtml.replace(/<div\s+class="split">\s*<\/div>\s*\n?\s*<br(\s\/)?>/gm, "</details>\n<br>\n<details class=\"bytm-changelog-version-details\" tabindex=\"0\">");
 
     const h2Matches = Array.from(changelogHtml.matchAll(/<h2(\s+id=".+")?>([\d\w\s.]+)<\/h2>/gm));
     for(const match of h2Matches) {
@@ -261,7 +261,7 @@ export async function getChangelogHtmlWithDetails() {
       changelogHtml = changelogHtml.replace(fullMatch, `${summaryElem}`);
     }
 
-    changelogHtml = `<details class="bytm-changelog-version-details">${changelogHtml}</details>`;
+    changelogHtml = `<details class="bytm-changelog-version-details" tabindex="0">${changelogHtml}</details>`;
 
     return changelogHtml;
   }