Remove distractions from YouTube
// ==UserScript==
// @name YouTube Focus
// @namespace https://gitlab.com/LVKoba
// @version 4.0
// @description Remove distractions from YouTube
// @match *://*.youtube.com/*
// @match *://*.youtube-nocookie.com/*
// @run-at document-end
// @license MIT
// @grant GM.getValue
// @grant GM.setValue
// @grant GM.registerMenuCommand
// @homepageURL https://gitlab.com/LVKoba/userscripts
// ==/UserScript==
const FEATURES = {
removeNotifications: {
label: "Notifications",
css: "ytd-notification-topbar-button-renderer { display: none !important; }",
default: true,
},
removeHomeFeed: {
label: "Home Feed",
css: "ytd-browse[page-subtype='home'] #contents, #header { display: none !important; }",
default: true,
},
removeSidebar: {
label: "Sidebar / Related",
css: "ytd-watch-flexy #secondary, ytd-watch-flexy #related { display: none !important; }",
default: true,
},
removeComments: {
label: "Comments",
css: "ytd-comments { display: none !important; }",
default: true,
},
removeChat: {
label: "Live Chat",
css: "ytd-live-chat-frame, #chat-container { display: none !important; }",
default: true,
},
removePanels: {
label: "Panels",
css: "ytd-watch-flexy #panels-full-bleed-container { display: none !important; }",
default: true,
},
removeLogo: {
label: "Logo",
css: "#logo-icon { display: none !important; }",
default: true,
},
removeMiniGuide: {
label: "Mini Guide",
css: "ytd-mini-guide-renderer { display: none !important; }",
default: true,
},
removeShorts: {
label: "Shorts",
css: "ytd-rich-shelf-renderer[is-shorts], ytd-reel-shelf-renderer, a[title='Shorts'], ytd-mini-guide-entry-renderer[aria-label='Shorts'] { display: none !important; }",
default: true,
},
removeStart: {
label: "Start Menu",
css: "ytd-masthead #start, #guide-button, ytd-topbar-logo-renderer { display: none !important; }",
default: true,
},
collapseGuide: {
label: "Collapse Guide",
css: null,
default: true,
},
};
let currentConfig = {};
const styleEl = document.createElement("style");
styleEl.id = "yt-focus-styles";
applyStyles(
Object.fromEntries(Object.entries(FEATURES).map(([k, v]) => [k, v.default]))
);
(document.head || document.documentElement).appendChild(styleEl);
async function loadConfig() {
const cfg = {};
await Promise.all(
Object.entries(FEATURES).map(async ([key, feat]) => {
cfg[key] = await GM.getValue(key, feat.default);
})
);
return cfg;
}
function applyStyles(config) {
let cssRules = [];
for (const [key, feat] of Object.entries(FEATURES)) {
if (config[key] && feat.css) {
cssRules.push(`/* ${feat.label} */\n${feat.css}`);
}
}
styleEl.textContent = cssRules.join("\n\n");
}
function collapseGuide() {
if (!currentConfig.collapseGuide) return;
const guide = document.querySelector("#guide");
if (!guide) return;
guide.removeAttribute("guide-persistent-and-visible");
guide.removeAttribute("opened");
if (!guide.hasAttribute("mini-guide-visible")) {
guide.setAttribute("mini-guide-visible", "");
}
}
function registerMenu(config) {
for (const [key, feat] of Object.entries(FEATURES)) {
const state = config[key] ? "✅" : "❌";
const action = config[key] ? "Disable" : "Enable";
GM.registerMenuCommand(`${state} ${action} ${feat.label}`, async () => {
config[key] = !config[key];
await GM.setValue(key, config[key]);
applyStyles(config);
if (key === 'collapseGuide') collapseGuide();
});
}
GM.registerMenuCommand("↩ Reset to Defaults", async () => {
await Promise.all(
Object.entries(FEATURES).map(([key, feat]) => {
config[key] = feat.default;
return GM.setValue(key, feat.default);
})
);
applyStyles(config);
collapseGuide();
});
}
(() => {
"use strict";
loadConfig().then((config) => {
currentConfig = config;
applyStyles(config);
registerMenu(config);
window.addEventListener("yt-navigate-finish", requestAnimationFrame(collapseGuide));
if (document.readyState === "loading") {
window.addEventListener("DOMContentLoaded", collapseGuide);
} else {
collapseGuide();
}
});
})();